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 = "语音助理耳机" typealias DeviceDiscoveredCallback = (UUID) -> Void private var onDeviceDiscovered: DeviceDiscoveredCallback? // MARK: - 初始化 private override init() { super.init() } // MARK: - 公共方法 func setBleService(_ service: Any) { self.bleService = service as? BleService os_log("已设置BLE服务实例", log: logger, type: .debug) } func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { self.onDeviceDiscovered = callback } // 处理发现的设备并连接 func connectToAccessory(uuid: UUID) { guard let service = bleService else { os_log("BleService未设置,无法连接到设备", log: logger, type: .error) return } // 连接设备 service.connectToAccessory(uuid: uuid) } // 从视图控制器显示设备选择器 - 主要入口点 func showDevicePickerFromViewController(_ viewController: UIViewController) { // 初始化会话(如果尚未初始化) initializeSession() } // 清除蓝牙状态(仅用于测试或重置,不会清除系统授权) func resetBluetoothState() { if let service = bleService { service.disconnect() } } // MARK: - 会话管理 func initializeSession() { // 检查是否已在初始化或已初始化 if isInitializing || (session != nil && isSessionActivated) { os_log("会话已在初始化或已激活", log: logger, type: .debug) return } // 开始初始化过程 isInitializing = true os_log("初始化设备发现会话", log: logger, type: .info) do { session = try ASAccessorySession() session?.activate(on: .main) { [weak self] event in guard let self = self else { return } self.handleSessionEvent(event) } } catch { handleSessionInitError(error) } } func resetSession() { session?.invalidate() session = nil isSessionActivated = false isInitializing = false os_log("会话已重置", log: logger, type: .debug) } // MARK: - 设备选择器显示 private func showDevicePicker() { guard let session = session, isSessionActivated else { os_log("会话未激活,无法显示设备选择器", log: logger, type: .error) return } // 创建设备描述符 let descriptor = ASDiscoveryDescriptor() descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier) // 创建显示项 let deviceImage = UIImage(systemName: "headphones") ?? UIImage() let displayItem = ASPickerDisplayItem( name: deviceDisplayName, productImage: deviceImage, descriptor: descriptor ) os_log("显示设备选择器", log: logger, type: .info) DispatchQueue.main.async { 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}@, 域: %{public}@, 错误码: %d", log: self.logger, type: .error, error.localizedDescription, nsError.domain, nsError.code) } else { os_log("设备选择器已显示", log: self.logger, type: .debug) } } } } // MARK: - 私有辅助方法 private func handleSessionEvent(_ event: ASAccessoryEvent) { switch event.eventType { case .activated: handleSessionActivated() case .accessoryAdded: handleAccessoryAdded(event.accessory) case .accessoryRemoved: handleAccessoryRemoved(event.accessory) case .accessoryChanged: os_log("设备已变更", log: logger, type: .debug) case .pickerDidPresent: os_log("选择器已显示", log: logger, type: .info) case .pickerDidDismiss: os_log("选择器已关闭", log: logger, type: .info) case .pickerSetupFailed: os_log("选择器设置失败: %{public}@", log: logger, type: .error, event.error?.localizedDescription ?? "未知错误") default: os_log("未处理的事件类型: %d", log: logger, type: .debug, event.eventType.rawValue) } } private func handleSessionActivated() { os_log("会话已激活", log: logger, type: .info) isSessionActivated = true isInitializing = false // 检查是否有已授权设备 if let accessories = session?.accessories, !accessories.isEmpty { // 二次进入:已有授权设备,可以先创建Central再连接 os_log("发现%d个已授权设备", log: logger, type: .info, accessories.count) // 初始化BleService的Central if let service = bleService { // 确保CBCentralManager已初始化 let _ = service.getCentralManager() // 连接所有授权设备 for accessory in accessories { if let uuid = accessory.bluetoothIdentifier { os_log("连接已授权设备: %{public}@", log: logger, type: .info, uuid.uuidString) onDeviceDiscovered?(uuid) connectToAccessory(uuid: uuid) } } } } else { // 首次配对:不创建Central,直接显示设备选择器 os_log("没有已授权设备,显示设备选择器", log: logger, type: .info) showDevicePicker() } } private func handleAccessoryAdded(_ accessory: ASAccessory?) { os_log("发现已授权设备: %{public}@", log: logger, type: .info, accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { os_log("发现设备但缺少UUID信息", log: logger, type: .error) return } // 通知发现设备并连接 onDeviceDiscovered?(uuid) connectToAccessory(uuid: uuid) } private func handleAccessoryRemoved(_ accessory: ASAccessory?) { guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { return } os_log("设备已移除授权: %{public}@", log: logger, type: .info, uuid.uuidString) // 断开连接 if let service = bleService { service.disconnect() } } private func handleSessionInitError(_ error: Error) { os_log("初始化会话失败: %{public}@", log: logger, type: .error, error.localizedDescription) isInitializing = false session = nil } // MARK: - 蓝牙状态检查 func isBluetoothAvailable() -> Bool { // 使用AVAudioSession检查蓝牙连接 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 } } os_log("未检测到蓝牙音频设备", log: logger, type: .debug) } catch { os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription) } // 默认返回true避免阻止功能 return true } // MARK: - 清除配对关系 func clearAssociations() { os_log("清除设备配对关系", log: logger, type: .info) // 先断开当前连接 resetBluetoothState() // 清除会话授权关系 #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { // 方法1: 重置整个会话 resetSession() // 重新初始化会话 initializeSession() os_log("会话已重置,所有授权关系已清除", log: logger, type: .info) } #endif } }