import Foundation import CoreBluetooth import os.log @available(iOS 13.0, *) class BleService: NSObject { // MARK: - 单例实现 static let shared = BleService() // MARK: - 回调代理协议 protocol Callback: AnyObject { // 扫描相关回调 func onScanResult(devices: [[String: Any]]) // 连接状态相关回调 func onConnectionStateChanged(state: Int) // 数据相关回调 func onAudioDataReceived(data: Data) // 设备信息相关回调 func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) } // MARK: - 属性 private var centralManager: CBCentralManager! private var peripheralDevice: CBPeripheral? private var writeCharacteristic: CBCharacteristic? private var notifyCharacteristic: CBCharacteristic? private var audioCharacteristic: CBCharacteristic? private var isScanning = false private var delegate: Callback? private var connectionState = BleConst.STATE_DISCONNECTED private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService") // MARK: - 初始化 private override init() { super.init() // 初始化中央管理器,提供恢复标识以支持后台恢复 let options: [String: Any] = [ CBCentralManagerOptionRestoreIdentifierKey: "BLEAssistantCentral", CBCentralManagerOptionShowPowerAlertKey: true ] centralManager = CBCentralManager(delegate: self, queue: nil, options: options) } // MARK: - 公共方法 /// 设置回调代理 func setDelegate(_ delegate: Callback) { self.delegate = delegate } /// 获取中央管理器 func getCentralManager() -> CBCentralManager { return centralManager } /// 开始扫描BLE设备 func startScan() { guard centralManager.state == .poweredOn else { os_log("蓝牙未打开", log: logger, type: .error) return } if isScanning { stopScan() } isScanning = true // 扫描特定服务的设备 let serviceUUIDs = [BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID] centralManager.scanForPeripherals(withServices: serviceUUIDs, options: [ CBCentralManagerScanOptionAllowDuplicatesKey: false ]) os_log("开始扫描BLE设备", log: logger, type: .info) } /// 停止扫描 func stopScan() { if isScanning { centralManager.stopScan() isScanning = false os_log("停止扫描BLE设备", log: logger, type: .info) } } /// 连接到指定设备 func connect(to peripheral: CBPeripheral) { if connectionState == BleConst.STATE_CONNECTED || connectionState == BleConst.STATE_CONNECTING { os_log("已连接或正在连接中", log: logger, type: .info) return } updateConnectionState(BleConst.STATE_CONNECTING) peripheralDevice = peripheral peripheralDevice?.delegate = self centralManager.connect(peripheral, options: [ CBConnectPeripheralOptionNotifyOnConnectionKey: true, CBConnectPeripheralOptionNotifyOnDisconnectionKey: true, CBConnectPeripheralOptionNotifyOnNotificationKey: true ]) } /// 断开当前连接 func disconnect() { guard let peripheral = peripheralDevice, connectionState == BleConst.STATE_CONNECTED else { os_log("未连接设备", log: logger, type: .info) return } updateConnectionState(BleConst.STATE_DISCONNECTING) centralManager.cancelPeripheralConnection(peripheral) } /// 检查是否已连接 func isConnected() -> Bool { return connectionState == BleConst.STATE_CONNECTED } // MARK: - 获取设备信息 /// 获取设备版本号 func getDeviceVersion() -> Bool { return sendCommand(BleConst.CMD_GET_VERSION) } /// 获取产品ID func getProductId() -> Bool { return sendCommand(BleConst.CMD_GET_PRODUCT_ID) } /// 获取电池信息 func getBatteryInfo() -> Bool { return sendCommand(BleConst.CMD_GET_BATTERY_INFO) } // MARK: - 编解码控制 /// 打开编码器 func openEncoder() -> Bool { return sendCommandWithParam(BleConst.CMD_CONTROL_CODEC, param: BleConst.CODEC_CONTROL_ENCODE_ON) } /// 打开解码器 func openDecoder() -> Bool { return sendCommandWithParam(BleConst.CMD_CONTROL_CODEC, param: BleConst.CODEC_CONTROL_DECODE_ON) } /// 打开A2DP解码器 func openA2DPDecoder() -> Bool { return sendCommandWithParam(BleConst.CMD_CONTROL_CODEC, param: BleConst.CODEC_CONTROL_A2DP_PLAY) } /// 关闭编解码器 func closeCodec() -> Bool { return sendCommandWithParam(BleConst.CMD_CONTROL_CODEC, param: BleConst.CODEC_CONTROL_CLOSE) } /// 播放A2DP func playA2DP() -> Bool { return sendCommand(BleConst.CMD_PLAY_A2DP) } // MARK: - 私有方法 /// 更新连接状态并通知代理 private func updateConnectionState(_ state: Int) { connectionState = state delegate?.onConnectionStateChanged(state: state) } /// 向设备发送命令(无参数) private func sendCommand(_ cmd: UInt8) -> Bool { guard let characteristic = writeCharacteristic, connectionState == BleConst.STATE_CONNECTED else { os_log("设备未连接或写特征未找到", log: logger, type: .error) return false } // 构造帧: 帧头 + 命令ID + 长度(0) + 校验和 let frameData = Data([BleConst.APP_REQUEST_HEADER, cmd, 0x00, BleConst.APP_REQUEST_HEADER ^ cmd ^ 0x00]) peripheralDevice?.writeValue(frameData, for: characteristic, type: .withResponse) return true } /// 向设备发送带参数的命令 private func sendCommandWithParam(_ cmd: UInt8, param: UInt8) -> Bool { guard let characteristic = writeCharacteristic, connectionState == BleConst.STATE_CONNECTED else { os_log("设备未连接或写特征未找到", log: logger, type: .error) return false } // 构造帧: 帧头 + 命令ID + 长度(1) + 参数 + 校验和 let frameData = Data([BleConst.APP_REQUEST_HEADER, cmd, 0x01, param, BleConst.APP_REQUEST_HEADER ^ cmd ^ 0x01 ^ param]) peripheralDevice?.writeValue(frameData, for: characteristic, type: .withResponse) return true } /// 处理接收到的响应数据 private func handleResponseData(_ data: Data) { guard data.count >= 4 else { os_log("接收数据格式错误", log: logger, type: .error) return } // 提取帧信息 let header = data[0] let cmd = data[1] let length = data[2] // 检查帧头,确保是设备响应 guard header == BleConst.DEVICE_RESPONSE_HEADER else { // 如果是设备主动上报 if header == BleConst.DEVICE_NOTIFY_HEADER { handleNotificationData(data) } return } // 根据命令类型处理设备响应 switch cmd { case BleConst.CMD_GET_VERSION: if length > 0 && data.count >= 4 + Int(length) { let versionData = data.subdata(in: 3..<(3 + Int(length))) if let versionStr = String(data: versionData, encoding: .utf8) { delegate?.onDeviceInfoReceived(infoType: Int(BleConst.CMD_GET_VERSION), infoData: ["version": versionStr]) } } case BleConst.CMD_GET_PRODUCT_ID: if length >= 2 && data.count >= 5 { let productId = UInt16(data[3]) << 8 | UInt16(data[4]) delegate?.onDeviceInfoReceived(infoType: Int(BleConst.CMD_GET_PRODUCT_ID), infoData: ["productId": productId]) } case BleConst.CMD_GET_BATTERY_INFO: if length >= 1 && data.count >= 4 { let batteryLevel = Int(data[3]) delegate?.onDeviceInfoReceived(infoType: Int(BleConst.CMD_GET_BATTERY_INFO), infoData: ["batteryLevel": batteryLevel]) } default: os_log("未知的响应命令: %{public}d", log: logger, type: .debug, cmd) } } /// 处理设备主动上报的通知数据 private func handleNotificationData(_ data: Data) { guard data.count >= 4 else { return } let cmd = data[1] let length = data[2] switch cmd { case BleConst.CMD_WAKE_UP: // 处理唤醒指令 os_log("收到设备唤醒指令", log: logger, type: .info) // 可以在这里添加唤醒回调 default: os_log("未知的通知命令: %{public}d", log: logger, type: .debug, cmd) } } } // MARK: - CBCentralManagerDelegate @available(iOS 13.0, *) extension BleService: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch central.state { case .poweredOn: os_log("蓝牙已开启", log: logger, type: .info) // 蓝牙已开启,可以开始扫描 case .poweredOff: os_log("蓝牙已关闭", log: logger, type: .info) updateConnectionState(BleConst.STATE_DISCONNECTED) case .resetting: os_log("蓝牙正在重置", log: logger, type: .info) case .unauthorized: os_log("蓝牙未授权", log: logger, type: .error) case .unsupported: os_log("设备不支持蓝牙", log: logger, type: .error) case .unknown: os_log("蓝牙状态未知", log: logger, type: .info) @unknown default: os_log("蓝牙状态未定义", log: logger, type: .info) } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { // 将扫描结果转换为字典格式 var deviceInfo: [String: Any] = [ "name": peripheral.name ?? "Unknown", "id": peripheral.identifier.uuidString, "rssi": RSSI.intValue ] // 添加广播数据 if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { deviceInfo["manufacturerData"] = manufacturerData } if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] { deviceInfo["serviceUUIDs"] = serviceUUIDs.map { $0.uuidString } } // 报告给回调 delegate?.onScanResult(devices: [deviceInfo]) } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { os_log("已连接到设备: %{public}@", log: logger, type: .info, peripheral.name ?? "Unknown") updateConnectionState(BleConst.STATE_CONNECTED) // 查找服务 peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]) } func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { os_log("连接失败: %{public}@", log: logger, type: .error, error?.localizedDescription ?? "Unknown error") updateConnectionState(BleConst.STATE_ERROR) } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { os_log("设备断开连接: %{public}@", log: logger, type: .info, error?.localizedDescription ?? "Normal disconnection") updateConnectionState(BleConst.STATE_DISCONNECTED) } func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { os_log("恢复先前的连接状态", log: logger, type: .info) // 从恢复状态中获取之前连接的外设 if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral], let peripheral = peripherals.first { peripheralDevice = peripheral peripheralDevice?.delegate = self // 如果之前已连接,更新状态 if peripheral.state == .connected { updateConnectionState(BleConst.STATE_CONNECTED) // 重新发现服务 peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]) } else { // 如果之前未连接,尝试重新连接 central.connect(peripheral, options: nil) } } } } // MARK: - CBPeripheralDelegate @available(iOS 13.0, *) extension BleService: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let error = error { os_log("服务发现错误: %{public}@", log: logger, type: .error, error.localizedDescription) return } guard let services = peripheral.services else { return } for service in services { os_log("发现服务: %{public}@", log: logger, type: .debug, service.uuid.uuidString) if service.uuid == BleConst.PRIMARY_SERVICE_UUID { // 主服务,发现写入和通知特征 peripheral.discoverCharacteristics([BleConst.WRITE_CHAR_UUID, BleConst.NOTIFY_CHAR_UUID], for: service) } else if service.uuid == BleConst.AUDIO_SERVICE_UUID { // 音频服务,发现音频特征 peripheral.discoverCharacteristics([BleConst.RECEIVE_AUDIO_CHAR_UUID], for: service) } } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let error = error { os_log("特征发现错误: %{public}@", log: logger, type: .error, error.localizedDescription) return } guard let characteristics = service.characteristics else { return } for characteristic in characteristics { os_log("发现特征: %{public}@", log: logger, type: .debug, characteristic.uuid.uuidString) if characteristic.uuid == BleConst.WRITE_CHAR_UUID { writeCharacteristic = characteristic } else if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID { notifyCharacteristic = characteristic // 订阅通知 peripheral.setNotifyValue(true, for: characteristic) } else if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID { audioCharacteristic = characteristic // 订阅音频通知 peripheral.setNotifyValue(true, for: characteristic) } } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { os_log("接收数据错误: %{public}@", log: logger, type: .error, error.localizedDescription) return } guard let data = characteristic.value, !data.isEmpty else { return } if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID { // 处理通知数据 handleResponseData(data) } else if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID { // 处理音频数据 delegate?.onAudioDataReceived(data: data) } } func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { os_log("写入数据错误: %{public}@", log: logger, type: .error, error.localizedDescription) } } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let error = error { os_log("订阅通知错误: %{public}@", log: logger, type: .error, error.localizedDescription) return } os_log("特征 %{public}@ 的通知状态更新为 %{public}d", log: logger, type: .debug, characteristic.uuid.uuidString, characteristic.isNotifying ? 1 : 0) } }