From eb3b92a29180a149383e64e1d8d6d4dea4cb7473 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Tue, 27 May 2025 19:13:22 +0100 Subject: [PATCH] ble --- .../ios/Classes/BleAccessorySetup.swift | 10 + .../ble_service/ios/Classes/BleService.swift | 287 +++++++++--------- 2 files changed, 153 insertions(+), 144 deletions(-) diff --git a/local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift b/local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift index 93ba3ac23..4d687b71e 100644 --- a/local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift +++ b/local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift @@ -43,6 +43,14 @@ class BleAccessorySetup: NSObject { service.connectToAccessory(uuid: uuid) } + func addToReconnectQueue(uuid: UUID) { + guard let service = bleService else { + return + } + + service.addToReconnectQueue(uuid: uuid) + } + // 从视图控制器显示设备选择器 - 主要入口点 func showDevicePickerFromViewController(_ viewController: UIViewController) { initializeSession() @@ -113,9 +121,11 @@ class BleAccessorySetup: NSObject { private func handleSessionEvent(_ event: ASAccessoryEvent) { switch event.eventType { case .activated: + os_log("会话已激活", log: logger, type: .info) handleSessionActivated() case .accessoryAdded: + os_log("发现新设备: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") handleAccessoryAdded(event.accessory) case .accessoryRemoved: diff --git a/local_plugins/ble_service/ios/Classes/BleService.swift b/local_plugins/ble_service/ios/Classes/BleService.swift index 038e98fe3..9d45f7ca7 100644 --- a/local_plugins/ble_service/ios/Classes/BleService.swift +++ b/local_plugins/ble_service/ios/Classes/BleService.swift @@ -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() 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) } }