import Foundation import CoreBluetooth import os.log import AVFoundation import AccessorySetupKit import UIKit class BleAccessorySetup: NSObject { // MARK: - 属性 static let shared = BleAccessorySetup() private var bleService: BleService? private var session: ASAccessorySession? private var isSessionActivated = false private var isInitializing = false private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") // 杰理蓝牙设备的公司标识符 private let deviceCompanyIdentifier: UInt16 = 0xEFAB private let deviceDisplayName = "语音助理耳机" // 用于存储配对设备UUID的UserDefaults键 private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid" typealias DeviceDiscoveredCallback = (UUID) -> Void private var onDeviceDiscovered: DeviceDiscoveredCallback? // MARK: - 初始化 private override init() { super.init() } // MARK: - 公共方法 func setBleService(_ service: Any) { self.bleService = service as? BleService } func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { self.onDeviceDiscovered = callback } // 处理发现的设备并连接 func connectToAccessory(uuid: UUID) { guard let service = bleService else { return } service.connectToAccessory(uuid: uuid) } func addToReconnectQueue(uuid: UUID) { guard let service = bleService else { return } service.addToReconnectQueue(uuid: uuid) } // 从视图控制器显示设备选择器 - 主要入口点 func showDevicePickerFromViewController(_ viewController: UIViewController) { initializeSession() } // 清除蓝牙状态(仅用于测试或重置,不会清除系统授权) func resetBluetoothState() { if let service = bleService { service.disconnect() } } // MARK: - 会话管理 func initializeSession() { if isInitializing { os_log("会话正在初始化中,跳过", log: logger, type: .info) return } // 如果会话已激活,检查是否有配对设备 if session != nil && isSessionActivated { os_log("会话已激活,检查配对设备", log: logger, type: .info) // 会话已激活,检查是否有配对设备 if let accessories = session?.accessories, !accessories.isEmpty { os_log("发现 %d 个已配对设备,尝试连接", log: logger, type: .info, accessories.count) // 有配对设备,尝试连接 if let service = bleService { let _ = service.getCentralManager() for accessory in accessories { if let uuid = accessory.bluetoothIdentifier { onDeviceDiscovered?(uuid) connectToAccessory(uuid: uuid) } } } } else { os_log("没有已配对设备,显示配对框", log: logger, type: .info) // 没有配对设备,直接显示配对框 showDevicePicker() } return } os_log("开始初始化新会话", log: logger, type: .info) isInitializing = true do { session = try ASAccessorySession() session?.activate(on: .main) { [weak self] event in guard let self = self else { return } self.handleSessionEvent(event) } } catch { os_log("会话初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription) handleSessionInitError(error) } } func resetSession() { session?.invalidate() session = nil isSessionActivated = false isInitializing = false } // MARK: - 设备选择器显示 private func showDevicePicker() { guard let session = session, isSessionActivated else { os_log("无法显示配对框:会话未激活", log: logger, type: .error) return } os_log("准备显示设备配对框", log: logger, type: .info) let descriptor = ASDiscoveryDescriptor() descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier) let deviceImage = UIImage(systemName: "headphones") ?? UIImage() let displayItem = ASPickerDisplayItem( name: deviceDisplayName, productImage: deviceImage, descriptor: descriptor ) DispatchQueue.main.async { os_log("正在显示设备配对框", log: self.logger, type: .info) session.showPicker(for: [displayItem]) { [weak self] error in guard let self = self else { return } if let error = error { let nsError = error as NSError os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) } else { os_log("设备选择器完成", log: self.logger, type: .info) } } } } // MARK: - 私有辅助方法 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: os_log("设备已移除: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") handleAccessoryRemoved(event.accessory) case .accessoryChanged: os_log("设备状态已改变", log: logger, type: .info) break case .pickerDidPresent: os_log("配对框已显示", log: logger, type: .info) break case .pickerDidDismiss: os_log("配对框已关闭", log: logger, type: .info) break case .pickerSetupFailed: os_log("选择器设置失败: %{public}@", log: logger, type: .error, event.error?.localizedDescription ?? "未知错误") default: os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue) break } } private func handleSessionActivated() { isSessionActivated = true isInitializing = false if let accessories = session?.accessories, !accessories.isEmpty { if let service = bleService { let _ = service.getCentralManager() for accessory in accessories { if let uuid = accessory.bluetoothIdentifier { onDeviceDiscovered?(uuid) connectToAccessory(uuid: uuid) } } } } else { showDevicePicker() } } private func handleAccessoryAdded(_ accessory: ASAccessory?) { guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { return } // 保存配对设备的UUID savePairedMacAddress(uuid) onDeviceDiscovered?(uuid) connectToAccessory(uuid: uuid) } private func handleAccessoryRemoved(_ accessory: ASAccessory?) { guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { return } if let service = bleService { service.disconnect() } } private func handleSessionInitError(_ error: Error) { isInitializing = false session = nil } // MARK: - 蓝牙状态检查 func isBluetoothAvailable() -> Bool { do { let audioSession = AVAudioSession.sharedInstance() let outputs = audioSession.currentRoute.outputs for output in outputs { if output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP || output.portType == .bluetoothLE { return true } } } catch { os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription) } return true } // MARK: - 清除配对关系 func clearAssociations() { resetBluetoothState() // 清除保存的配对设备UUID clearPairedMacAddress() #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { resetSession() initializeSession() } #endif } // MARK: - 配对设备UUID管理 /// 获取已配对设备的UUID字符串 /// 返回存储的配对设备UUID,如果没有则返回nil func getPairedMacAddress() -> String? { let uuidString = UserDefaults.standard.string(forKey: pairedDeviceUUIDKey) if let uuidString = uuidString, !uuidString.isEmpty { os_log("获取已配对设备UUID: %{public}@", log: logger, type: .info, uuidString) return uuidString } os_log("未找到已配对设备UUID", log: logger, type: .info) return nil } /// 保存配对设备的UUID /// - Parameter uuid: 要保存的设备UUID func savePairedMacAddress(_ uuid: UUID) { let uuidString = uuid.uuidString UserDefaults.standard.set(uuidString, forKey: pairedDeviceUUIDKey) UserDefaults.standard.synchronize() os_log("已保存配对设备UUID: %{public}@", log: logger, type: .info, uuidString) } /// 清除已保存的配对设备UUID private func clearPairedMacAddress() { UserDefaults.standard.removeObject(forKey: pairedDeviceUUIDKey) UserDefaults.standard.synchronize() os_log("已清除配对设备UUID", log: logger, type: .info) } }