|
|
|
@ -3,6 +3,15 @@ import CoreBluetooth |
|
|
|
import os.log |
|
|
|
import AVFoundation |
|
|
|
|
|
|
|
/// BLE服务类 - 专为AccessorySetupKit设备设计,支持自动重连和状态恢复 |
|
|
|
/// |
|
|
|
/// 核心特性: |
|
|
|
/// 1. 外设掉电→上电后自动重连(通过pendingReconnectUUIDs队列) |
|
|
|
/// 2. App被系统终止后,外设广播或特征通知时自动恢复连接 |
|
|
|
/// 3. 使用CBCentralManagerOptionRestoreIdentifierKey实现系统级状态保留 |
|
|
|
/// 4. 支持bluetooth-central后台模式,允许后台BLE操作 |
|
|
|
/// 5. willRestoreState恢复系统保留的外设、扫描条件 |
|
|
|
/// 6. 统一的重连队列管理,适配AccessorySetupKit工作流程 |
|
|
|
@available(iOS 13.0, *) |
|
|
|
public class BleService: NSObject { |
|
|
|
// MARK: - 单例实现 |
|
|
|
@ -37,15 +46,13 @@ public class BleService: NSObject { |
|
|
|
private var callbacks = [Callback]() |
|
|
|
private var connectionState = BleConst.STATE_DISCONNECTED |
|
|
|
|
|
|
|
// 待连接设备UUID队列 |
|
|
|
private var pendingUUIDs = [UUID]() |
|
|
|
|
|
|
|
|
|
|
|
// 连接超时检测 |
|
|
|
private var connectionTimeoutTimer: Timer? |
|
|
|
private let connectionTimeoutSeconds: TimeInterval = 15 // 15秒连接超时 |
|
|
|
// 状态恢复相关 |
|
|
|
private var restoredPeripherals: [CBPeripheral] = [] |
|
|
|
|
|
|
|
// 需要重置的设备(从willRestoreState记录,在poweredOn时重置) |
|
|
|
private var devicesToReset: [CBPeripheral] = [] |
|
|
|
// 自动重连队列(统一管理所有待连接的设备) |
|
|
|
private var pendingReconnectUUIDs = Set<UUID>() |
|
|
|
|
|
|
|
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService") |
|
|
|
|
|
|
|
@ -200,34 +207,58 @@ public class BleService: NSObject { |
|
|
|
|
|
|
|
/// 连接到指定设备 |
|
|
|
public func connect(to peripheral: CBPeripheral) { |
|
|
|
if connectionState == BleConst.STATE_CONNECTED || connectionState == BleConst.STATE_CONNECTING { |
|
|
|
connectPeripheral(peripheral) |
|
|
|
} |
|
|
|
|
|
|
|
/// 统一的连接方法,支持恢复和重连 |
|
|
|
private func connectPeripheral(_ peripheral: CBPeripheral) { |
|
|
|
guard let manager = centralManager else { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
if centralManager == nil { |
|
|
|
initializeCentralManager() |
|
|
|
if peripheral.state == .connected { |
|
|
|
os_log("设备已连接,恢复会话: %@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
resumeSession(for: peripheral) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
guard let manager = centralManager else { |
|
|
|
if peripheral.state == .connecting { |
|
|
|
os_log("设备正在连接中: %@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
if connectionState == BleConst.STATE_CONNECTED && peripheralDevice?.identifier != peripheral.identifier { |
|
|
|
os_log("断开当前设备,连接新设备", log: logger, type: .info) |
|
|
|
disconnect() |
|
|
|
} |
|
|
|
|
|
|
|
updateConnectionState(BleConst.STATE_CONNECTING) |
|
|
|
|
|
|
|
peripheralDevice = peripheral |
|
|
|
peripheralDevice?.delegate = self |
|
|
|
|
|
|
|
manager.connect(peripheral, options: [ |
|
|
|
CBConnectPeripheralOptionNotifyOnConnectionKey: true, |
|
|
|
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true, |
|
|
|
CBConnectPeripheralOptionNotifyOnNotificationKey: true |
|
|
|
]) |
|
|
|
|
|
|
|
// 启动连接超时检测 |
|
|
|
startConnectionTimeout() |
|
|
|
|
|
|
|
os_log("开始连接到设备: %@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
} |
|
|
|
|
|
|
|
/// 恢复已连接设备的会话 |
|
|
|
private func resumeSession(for peripheral: CBPeripheral) { |
|
|
|
peripheralDevice = peripheral |
|
|
|
peripheralDevice?.delegate = self |
|
|
|
updateConnectionState(BleConst.STATE_CONNECTED) |
|
|
|
|
|
|
|
if opusProcessor?.isDecoding() != true { |
|
|
|
startOpusDecoding() |
|
|
|
} |
|
|
|
|
|
|
|
os_log("已恢复设备会话: %@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
} |
|
|
|
|
|
|
|
/// 断开当前连接 |
|
|
|
public func disconnect() { |
|
|
|
guard let peripheral = peripheralDevice, |
|
|
|
@ -319,10 +350,6 @@ public class BleService: NSObject { |
|
|
|
/// 打开编码器 |
|
|
|
func openEncoder() -> Bool { |
|
|
|
os_log("🎙️ 准备打开编码(设备侧)...", log: logger, type: .info) |
|
|
|
os_log("🔍 状态检查: 连接状态=%d (期望=%d), writeCharacteristic=%@", |
|
|
|
log: logger, type: .debug, |
|
|
|
connectionState, BleConst.STATE_CONNECTED, |
|
|
|
writeCharacteristic != nil ? "已设置" : "未设置") |
|
|
|
|
|
|
|
let paramData = Data([BleConst.CODEC_CONTROL_ENCODE_ON, BleConst.AUDIO_CHANNEL_RIGHT]) |
|
|
|
let result = sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) |
|
|
|
@ -404,34 +431,6 @@ public class BleService: NSObject { |
|
|
|
return sendCommand(BleConst.CMD_CONTROL_CODEC, data: paramData) |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 连接超时检测 |
|
|
|
|
|
|
|
/// 启动连接超时检测 |
|
|
|
private func startConnectionTimeout() { |
|
|
|
stopConnectionTimeout() // 先停止现有的检测 |
|
|
|
|
|
|
|
connectionTimeoutTimer = Timer.scheduledTimer(withTimeInterval: connectionTimeoutSeconds, repeats: false) { [weak self] _ in |
|
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
os_log("⏰ 连接超时,取消连接", log: self.logger, type: .debug) |
|
|
|
|
|
|
|
// 取消连接 |
|
|
|
if let peripheral = self.peripheralDevice { |
|
|
|
self.centralManager?.cancelPeripheralConnection(peripheral) |
|
|
|
} |
|
|
|
|
|
|
|
self.updateConnectionState(BleConst.STATE_ERROR) |
|
|
|
} |
|
|
|
|
|
|
|
os_log("⏰ 连接超时检测已启动,%d秒后超时", log: logger, type: .debug, Int(connectionTimeoutSeconds)) |
|
|
|
} |
|
|
|
|
|
|
|
/// 停止连接超时检测 |
|
|
|
private func stopConnectionTimeout() { |
|
|
|
connectionTimeoutTimer?.invalidate() |
|
|
|
connectionTimeoutTimer = nil |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 私有方法 |
|
|
|
|
|
|
|
/// 更新连接状态并通知回调 |
|
|
|
@ -863,12 +862,30 @@ public class BleService: NSObject { |
|
|
|
opusProcessor?.cleanup() |
|
|
|
opusProcessor = nil |
|
|
|
|
|
|
|
// 清理待重置设备列表 |
|
|
|
devicesToReset = [] |
|
|
|
// 清理状态恢复和重连队列 |
|
|
|
restoredPeripherals.removeAll() |
|
|
|
pendingReconnectUUIDs.removeAll() |
|
|
|
|
|
|
|
os_log("BleService资源已清理", log: logger, type: .debug) |
|
|
|
} |
|
|
|
|
|
|
|
/// 清除重连队列 |
|
|
|
public func clearReconnectQueue() { |
|
|
|
pendingReconnectUUIDs.removeAll() |
|
|
|
os_log("已清除重连队列", log: logger, type: .info) |
|
|
|
} |
|
|
|
|
|
|
|
/// 获取当前重连队列状态(用于调试) |
|
|
|
public func getReconnectQueueStatus() -> [String: Any] { |
|
|
|
return [ |
|
|
|
"reconnectQueueCount": pendingReconnectUUIDs.count, |
|
|
|
"reconnectQueueUUIDs": Array(pendingReconnectUUIDs).map { $0.uuidString }, |
|
|
|
"restoredPeripheralsCount": restoredPeripherals.count, |
|
|
|
"connectionState": connectionState, |
|
|
|
"centralManagerState": centralManager?.state.rawValue ?? -1 |
|
|
|
] |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - AccessorySetupKit |
|
|
|
|
|
|
|
/// 在接收到AccessorySetupKit的accessoryAdded事件后调用 |
|
|
|
@ -883,9 +900,8 @@ public class BleService: NSObject { |
|
|
|
initializeCentralManager() |
|
|
|
} |
|
|
|
|
|
|
|
// 将设备添加到连接队列,而不是直接连接 |
|
|
|
// 这样即使当前蓝牙状态不是poweredOn,也会在状态变为poweredOn时自动连接 |
|
|
|
enqueueConnection(uuid: uuid) |
|
|
|
// 添加到重连队列 |
|
|
|
addToReconnectQueue(uuid: uuid) |
|
|
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
@ -908,27 +924,46 @@ extension BleService: CBCentralManagerDelegate { |
|
|
|
|
|
|
|
switch central.state { |
|
|
|
case .poweredOn: |
|
|
|
os_log("🔧 蓝牙已开启", log: logger, type: .info) |
|
|
|
|
|
|
|
// 蓝牙已开启,可以开始扫描或连接 |
|
|
|
os_log("🔧 蓝牙已开启 - 这是全新初始化(非状态恢复)", log: logger, type: .info) |
|
|
|
// 注:AccessorySetupKit应用不需要恢复传统BLE扫描 |
|
|
|
|
|
|
|
// 首先检查是否有需要重置的设备(来自willRestoreState) |
|
|
|
if !devicesToReset.isEmpty { |
|
|
|
os_log("🔄 蓝牙已就绪,开始重置%d个设备", log: logger, type: .info, devicesToReset.count) |
|
|
|
// 恢复外设连接 |
|
|
|
if !restoredPeripherals.isEmpty { |
|
|
|
os_log("🔄 恢复%d个外设连接", log: logger, type: .info, restoredPeripherals.count) |
|
|
|
|
|
|
|
for peripheral in devicesToReset { |
|
|
|
os_log("🔄 强制取消设备连接: %{public}@, 状态=%d", log: logger, type: .info, |
|
|
|
peripheral.name ?? peripheral.identifier.uuidString, peripheral.state.rawValue) |
|
|
|
central.cancelPeripheralConnection(peripheral) |
|
|
|
for peripheral in restoredPeripherals { |
|
|
|
peripheral.delegate = self |
|
|
|
|
|
|
|
if peripheral.state == .connected { |
|
|
|
os_log("🔄 外设已连接,恢复会话: %{public}@", log: logger, type: .info, |
|
|
|
peripheral.identifier.uuidString) |
|
|
|
resumeSession(for: peripheral) |
|
|
|
} else { |
|
|
|
os_log("🔄 外设未连接,开始重连: %{public}@, 状态=%d", log: logger, type: .info, |
|
|
|
peripheral.identifier.uuidString, peripheral.state.rawValue) |
|
|
|
connectPeripheral(peripheral) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 清空重置列表 |
|
|
|
devicesToReset = [] |
|
|
|
os_log("🔄 设备重置完成,继续正常连接流程", log: logger, type: .info) |
|
|
|
// 清空已处理的恢复外设 |
|
|
|
restoredPeripherals.removeAll() |
|
|
|
} |
|
|
|
|
|
|
|
// 恢复待重连的设备 |
|
|
|
if !pendingReconnectUUIDs.isEmpty { |
|
|
|
os_log("🔄 尝试重连%d个待重连设备", log: logger, type: .info, pendingReconnectUUIDs.count) |
|
|
|
|
|
|
|
let peripherals = central.retrievePeripherals(withIdentifiers: Array(pendingReconnectUUIDs)) |
|
|
|
for peripheral in peripherals { |
|
|
|
os_log("🔄 重连设备: %{public}@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
connectPeripheral(peripheral) |
|
|
|
pendingReconnectUUIDs.remove(peripheral.identifier) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 检查是否有待连接的设备 |
|
|
|
tryConnectIfReady() |
|
|
|
// 尝试重连队列中的设备 |
|
|
|
tryReconnectDevices() |
|
|
|
|
|
|
|
case .poweredOff: |
|
|
|
os_log("蓝牙已关闭", log: logger, type: .info) |
|
|
|
@ -956,63 +991,35 @@ extension BleService: CBCentralManagerDelegate { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 连接队列处理 |
|
|
|
// MARK: - 重连管理 |
|
|
|
|
|
|
|
// 检查并尝试连接待连接设备 |
|
|
|
private func tryConnectIfReady() { |
|
|
|
os_log("🔍 检查并尝试连接待连接设备", log: logger, type: .debug) |
|
|
|
/// 添加设备到重连队列 |
|
|
|
public func addToReconnectQueue(uuid: UUID) { |
|
|
|
pendingReconnectUUIDs.insert(uuid) |
|
|
|
os_log("🔄 设备已加入重连队列: %{public}@", log: logger, type: .info, uuid.uuidString) |
|
|
|
|
|
|
|
// 立即尝试连接(如果蓝牙已就绪) |
|
|
|
tryReconnectDevices() |
|
|
|
} |
|
|
|
|
|
|
|
/// 尝试重连队列中的设备 |
|
|
|
private func tryReconnectDevices() { |
|
|
|
guard let manager = centralManager, manager.state == .poweredOn else { |
|
|
|
os_log("蓝牙未就绪,延迟连接", log: logger, type: .info) |
|
|
|
os_log("🔄 蓝牙未就绪,延迟重连", log: logger, type: .debug) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 防止并发修改 |
|
|
|
let uuidsToProcess = pendingUUIDs |
|
|
|
|
|
|
|
// 过滤:保留未能成功连接的设备 |
|
|
|
pendingUUIDs = uuidsToProcess.filter { uuid in |
|
|
|
let peripherals = manager.retrievePeripherals(withIdentifiers: [uuid]) |
|
|
|
|
|
|
|
if let peripheral = peripherals.first { |
|
|
|
// 如果已经连接或正在连接,则不需要再次连接 |
|
|
|
if peripheral.state == .connected || peripheral.state == .connecting { |
|
|
|
os_log("设备已连接或正在连接:state=%d,从队列中移除: %{public}@", log: logger, type: .info, peripheral.state.rawValue, peripheral.name ?? peripheral.identifier.uuidString) |
|
|
|
return false // 从队列中移除 |
|
|
|
} |
|
|
|
|
|
|
|
// 找到外设,尝试连接 |
|
|
|
os_log("从队列中连接设备: %{public}@", log: logger, type: .info, |
|
|
|
peripheral.name ?? peripheral.identifier.uuidString) |
|
|
|
|
|
|
|
// 开始连接 |
|
|
|
peripheralDevice = peripheral |
|
|
|
peripheralDevice?.delegate = self |
|
|
|
manager.connect(peripheral, options: [ |
|
|
|
CBConnectPeripheralOptionNotifyOnConnectionKey: true, |
|
|
|
CBConnectPeripheralOptionNotifyOnDisconnectionKey: true, |
|
|
|
CBConnectPeripheralOptionNotifyOnNotificationKey: true |
|
|
|
]) |
|
|
|
|
|
|
|
// 启动连接超时检测 |
|
|
|
startConnectionTimeout() |
|
|
|
|
|
|
|
return false // 连接后从队列中移除 |
|
|
|
} |
|
|
|
|
|
|
|
// 找不到外设,保留在队列中 |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 添加设备到连接队列 |
|
|
|
public func enqueueConnection(uuid: UUID) { |
|
|
|
if !pendingUUIDs.contains(uuid) { |
|
|
|
pendingUUIDs.append(uuid) |
|
|
|
os_log("设备已加入连接队列: %{public}@", log: logger, type: .info, uuid.uuidString) |
|
|
|
guard !pendingReconnectUUIDs.isEmpty else { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 立即尝试连接 |
|
|
|
tryConnectIfReady() |
|
|
|
os_log("🔄 尝试重连%d个设备", log: logger, type: .info, pendingReconnectUUIDs.count) |
|
|
|
|
|
|
|
let peripherals = manager.retrievePeripherals(withIdentifiers: Array(pendingReconnectUUIDs)) |
|
|
|
for peripheral in peripherals { |
|
|
|
os_log("🔄 重连设备: %{public}@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
connectPeripheral(peripheral) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 原有方法 |
|
|
|
@ -1042,8 +1049,8 @@ extension BleService: CBCentralManagerDelegate { |
|
|
|
os_log("🔗 连接详情: UUID=%{public}@, 服务数=%d", log: logger, type: .debug, |
|
|
|
peripheral.identifier.uuidString, peripheral.services?.count ?? 0) |
|
|
|
|
|
|
|
// 停止连接超时检测 |
|
|
|
stopConnectionTimeout() |
|
|
|
// 从重连队列中移除(如果存在) |
|
|
|
pendingReconnectUUIDs.remove(peripheral.identifier) |
|
|
|
|
|
|
|
updateConnectionState(BleConst.STATE_CONNECTED) |
|
|
|
|
|
|
|
@ -1063,10 +1070,11 @@ extension BleService: CBCentralManagerDelegate { |
|
|
|
peripheral.name ?? peripheral.identifier.uuidString, errorMsg) |
|
|
|
os_log("❌ 失败详情: UUID=%{public}@", log: logger, type: .debug, peripheral.identifier.uuidString) |
|
|
|
|
|
|
|
// 停止连接超时检测 |
|
|
|
stopConnectionTimeout() |
|
|
|
|
|
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
|
|
|
|
|
// 连接失败时,立即发起新的连接 |
|
|
|
os_log("🔄 连接失败,立即重新连接: %{public}@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
connectPeripheral(peripheral) |
|
|
|
} |
|
|
|
|
|
|
|
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { |
|
|
|
@ -1078,42 +1086,33 @@ extension BleService: CBCentralManagerDelegate { |
|
|
|
peripheral.name ?? peripheral.identifier.uuidString) |
|
|
|
} |
|
|
|
|
|
|
|
// 停止连接超时检测 |
|
|
|
stopConnectionTimeout() |
|
|
|
|
|
|
|
updateConnectionState(BleConst.STATE_DISCONNECTED) |
|
|
|
|
|
|
|
// 无论是否意外断开,都立即发起新的连接 |
|
|
|
if central.state == .poweredOn { |
|
|
|
os_log("🔄 立即重新连接设备: %{public}@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
connectPeripheral(peripheral) |
|
|
|
} else { |
|
|
|
os_log("🔄 蓝牙未就绪,添加到重连队列: %{public}@", log: logger, type: .info, peripheral.identifier.uuidString) |
|
|
|
pendingReconnectUUIDs.insert(peripheral.identifier) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { |
|
|
|
os_log("🔄 ===== iOS系统正在恢复CBCentralManager状态 =====", log: logger, type: .info) |
|
|
|
os_log("🔄 恢复数据: %{public}@", log: logger, type: .debug, dict) |
|
|
|
|
|
|
|
// 检查是否有需要恢复的外设 |
|
|
|
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral], !peripherals.isEmpty { |
|
|
|
os_log("🔄 发现%d个需要恢复的设备,记录待重置(等待蓝牙poweredOn)", log: logger, type: .info, peripherals.count) |
|
|
|
|
|
|
|
// 只记录需要重置的设备,不执行任何蓝牙操作(因为此时蓝牙可能还没有poweredOn) |
|
|
|
devicesToReset = peripherals |
|
|
|
// 恢复外设连接 |
|
|
|
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] { |
|
|
|
restoredPeripherals = peripherals |
|
|
|
os_log("🔄 恢复%d个外设连接", log: logger, type: .info, peripherals.count) |
|
|
|
|
|
|
|
for peripheral in peripherals { |
|
|
|
os_log("🔄 记录待重置设备: %{public}@, 状态=%d", log: logger, type: .debug, |
|
|
|
os_log("🔄 恢复外设: %{public}@, 状态=%d", log: logger, type: .debug, |
|
|
|
peripheral.name ?? peripheral.identifier.uuidString, peripheral.state.rawValue) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 预先清理应用层状态 |
|
|
|
peripheralDevice = nil |
|
|
|
writeCharacteristic = nil |
|
|
|
notifyCharacteristic = nil |
|
|
|
audioCharacteristic = nil |
|
|
|
updateConnectionState(BleConst.STATE_DISCONNECTED) |
|
|
|
|
|
|
|
os_log("🔄 应用层状态已清理,等待蓝牙poweredOn后执行设备重置", log: logger, type: .info) |
|
|
|
} else { |
|
|
|
os_log("🔄 没有需要恢复的设备", log: logger, type: .debug) |
|
|
|
devicesToReset = [] |
|
|
|
} |
|
|
|
|
|
|
|
// 不进行任何蓝牙操作,等待centralManagerDidUpdateState(.poweredOn) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|