You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
963 lines
35 KiB
963 lines
35 KiB
import Foundation
|
|
import CoreBluetooth
|
|
import os.log
|
|
|
|
/// BLE服务类 - 单例实现
|
|
actor BleService {
|
|
// MARK: - 单例实现
|
|
static let shared = BleService()
|
|
|
|
// MARK: - 常量
|
|
|
|
/// 用于状态恢复的唯一标识符
|
|
private let restoreIdentifier = "com.yunqiinnovation.ble_service.restore"
|
|
|
|
private init() {
|
|
// 初始化连接状态
|
|
connectionState = BleConst.STATE_DISCONNECTED
|
|
|
|
// 将通知处理移到非actor方法中
|
|
setupNotifications()
|
|
}
|
|
|
|
deinit {
|
|
// 移除通知观察者
|
|
for observer in notificationObservers {
|
|
NotificationCenter.default.removeObserver(observer)
|
|
}
|
|
}
|
|
|
|
// MARK: - 属性
|
|
|
|
/// 中央管理器
|
|
private var centralManager: CBCentralManager?
|
|
|
|
/// 当前连接的外设
|
|
private var peripheral: CBPeripheral?
|
|
|
|
/// 主服务特征
|
|
private var writeCharacteristic: CBCharacteristic?
|
|
private var notifyCharacteristic: CBCharacteristic?
|
|
private var audioCharacteristic: CBCharacteristic?
|
|
|
|
/// 连接状态
|
|
private(set) var connectionState: Int {
|
|
didSet {
|
|
if oldValue != connectionState {
|
|
Task { await notifyConnectionStateChanged(state: connectionState) }
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 是否正在扫描
|
|
private(set) var isScanning = false
|
|
|
|
/// 扫描结果缓存
|
|
private var scanResults = [CBPeripheral: [String: Any]]()
|
|
|
|
/// 扫描超时任务
|
|
private var scanTimeoutTask: Task<Void, Never>?
|
|
|
|
/// 是否已初始化
|
|
private var isInitialized = false
|
|
|
|
/// 回调代理列表 - 使用actor保证线程安全
|
|
private var delegates = [BleDelegate]()
|
|
|
|
/// 日志
|
|
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService")
|
|
|
|
/// 通知观察者
|
|
private var notificationObservers = [NSObjectProtocol]()
|
|
|
|
// MARK: - 通知设置
|
|
|
|
/// 设置应用生命周期通知
|
|
private nonisolated func setupNotifications() {
|
|
// 使用nonisolated函数以避免actor隔离问题
|
|
let becomeActiveObserver = NotificationCenter.default.addObserver(
|
|
forName: UIApplication.didBecomeActiveNotification,
|
|
object: nil,
|
|
queue: .main
|
|
) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
Task {
|
|
await self.handleAppDidBecomeActive()
|
|
}
|
|
}
|
|
|
|
let enterBackgroundObserver = NotificationCenter.default.addObserver(
|
|
forName: UIApplication.didEnterBackgroundNotification,
|
|
object: nil,
|
|
queue: .main
|
|
) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
Task {
|
|
await self.handleAppDidEnterBackground()
|
|
}
|
|
}
|
|
|
|
// 存储观察者以便后续移除
|
|
notificationObservers = [becomeActiveObserver, enterBackgroundObserver]
|
|
}
|
|
|
|
// MARK: - 公共方法
|
|
|
|
/// 初始化BLE服务
|
|
/// - Returns: 是否初始化成功
|
|
func initialize() -> Bool {
|
|
if isInitialized {
|
|
return true
|
|
}
|
|
|
|
do {
|
|
// 初始化中央管理器 - 配置状态恢复选项
|
|
let options: [String: Any] = [
|
|
CBCentralManagerOptionRestoreIdentifierKey: restoreIdentifier,
|
|
CBCentralManagerOptionShowPowerAlertKey: true
|
|
]
|
|
|
|
centralManager = CBCentralManager(
|
|
delegate: CBManagerHandler.shared,
|
|
queue: nil,
|
|
options: options
|
|
)
|
|
|
|
CBManagerHandler.shared.bleService = self
|
|
|
|
isInitialized = true
|
|
os_log("BLE服务初始化成功", log: logger, type: .info)
|
|
return true
|
|
} catch {
|
|
os_log("BLE服务初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription)
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// 添加回调代理
|
|
/// - Parameter delegate: 要添加的代理
|
|
/// - Returns: 是否添加成功
|
|
func addDelegate(_ delegate: BleDelegate) -> Bool {
|
|
if !delegates.contains(where: { $0 === delegate }) {
|
|
delegates.append(delegate)
|
|
os_log("已添加BLE回调, 当前回调数: %d", log: logger, type: .debug, delegates.count)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// 移除回调代理
|
|
/// - Parameter delegate: 要移除的代理
|
|
/// - Returns: 是否移除成功
|
|
func removeDelegate(_ delegate: BleDelegate) -> Bool {
|
|
if let index = delegates.firstIndex(where: { $0 === delegate }) {
|
|
delegates.remove(at: index)
|
|
os_log("已移除BLE回调, 当前回调数: %d", log: logger, type: .debug, delegates.count)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// 清除所有回调代理
|
|
func clearDelegates() {
|
|
delegates.removeAll()
|
|
os_log("已清除所有BLE回调", log: logger, type: .debug)
|
|
}
|
|
|
|
/// 检查是否已连接
|
|
/// - Returns: 是否已连接
|
|
func isConnected() -> Bool {
|
|
return connectionState == BleConst.STATE_CONNECTED
|
|
}
|
|
|
|
// MARK: - 扫描方法
|
|
|
|
/// 开始主动扫描BLE设备
|
|
/// - Parameter timeout: 扫描超时时间(秒)
|
|
/// - Returns: 是否成功开始扫描
|
|
func startScan(timeout: TimeInterval = 15.0) -> Bool {
|
|
if !isInitialized {
|
|
os_log("BLE服务未初始化", log: logger, type: .error)
|
|
return false
|
|
}
|
|
|
|
if isScanning {
|
|
return false
|
|
}
|
|
|
|
guard let manager = centralManager, manager.state == .poweredOn else {
|
|
os_log("蓝牙未开启", log: logger, type: .error)
|
|
return false
|
|
}
|
|
|
|
os_log("开始主动扫描BLE设备...", log: logger, type: .info)
|
|
unregisterBackgroundScan() // 避免双重扫描
|
|
scanResults.removeAll()
|
|
|
|
// 扫描选项 - 允许重复结果,以获取最新的RSSI值
|
|
let options: [String: Any] = [
|
|
CBCentralManagerScanOptionAllowDuplicatesKey: true
|
|
]
|
|
|
|
// 设置过滤服务UUID
|
|
let scanFilters = [
|
|
// 如果需要过滤,添加CBUUID
|
|
// CBUUID(string: BleConst.PRIMARY_SERVICE_UUID.uuidString)
|
|
]
|
|
|
|
// 使用过滤器开始扫描
|
|
manager.scanForPeripherals(
|
|
withServices: scanFilters.isEmpty ? nil : scanFilters,
|
|
options: options
|
|
)
|
|
|
|
isScanning = true
|
|
|
|
// 设置扫描超时
|
|
scanTimeoutTask?.cancel()
|
|
scanTimeoutTask = Task { [weak self] in
|
|
guard let self = self else { return }
|
|
|
|
do {
|
|
try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
|
|
if !Task.isCancelled {
|
|
await self.stopScan()
|
|
}
|
|
} catch {
|
|
// 任务被取消,忽略错误
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/// 停止主动扫描
|
|
/// - Returns: 是否成功停止扫描
|
|
func stopScan() -> Bool {
|
|
if !isScanning {
|
|
return false
|
|
}
|
|
|
|
os_log("停止主动扫描", log: logger, type: .info)
|
|
|
|
centralManager?.stopScan()
|
|
scanTimeoutTask?.cancel()
|
|
scanTimeoutTask = nil
|
|
isScanning = false
|
|
|
|
// 重新注册后台扫描,保证唤醒能力
|
|
registerBackgroundScan()
|
|
|
|
return true
|
|
}
|
|
|
|
/// 注册后台扫描
|
|
/// - Returns: 是否成功注册
|
|
func registerBackgroundScan() -> Bool {
|
|
// iOS中无法直接实现与Android相同的后台扫描唤醒功能
|
|
os_log("iOS不支持直接的后台扫描唤醒,将使用状态保存和恢复功能", log: logger, type: .info)
|
|
return true
|
|
}
|
|
|
|
/// 注销后台扫描
|
|
/// - Returns: 是否成功注销
|
|
func unregisterBackgroundScan() -> Bool {
|
|
// iOS没有直接的后台扫描注销机制
|
|
os_log("iOS不支持直接的后台扫描注销", log: logger, type: .info)
|
|
return true
|
|
}
|
|
|
|
// MARK: - 连接方法
|
|
|
|
/// 连接到指定地址的BLE设备
|
|
/// - Parameter address: 设备UUID字符串
|
|
func connect(address: String) async {
|
|
if connectionState == BleConst.STATE_CONNECTED {
|
|
os_log("已经连接中,忽略连接请求", log: logger, type: .warning)
|
|
return
|
|
}
|
|
|
|
guard let central = centralManager, central.state == .poweredOn else {
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
return
|
|
}
|
|
|
|
// 关闭现有连接
|
|
disconnectGatt()
|
|
|
|
// 查找对应的外设
|
|
if let uuid = UUID(uuidString: address),
|
|
let targetPeripheral = scanResults.keys.first(where: { $0.identifier == uuid }) {
|
|
// 如果在扫描结果中找到,直接连接
|
|
os_log("从扫描结果中找到目标设备,开始连接...", log: logger, type: .debug)
|
|
await connectToPeripheral(targetPeripheral)
|
|
} else {
|
|
// 如果没有找到,尝试从已知设备中恢复
|
|
os_log("尝试使用UUID恢复设备连接: %{public}@", log: logger, type: .debug, address)
|
|
if let uuid = UUID(uuidString: address) {
|
|
let peripherals = central.retrievePeripherals(withIdentifiers: [uuid])
|
|
if let targetPeripheral = peripherals.first {
|
|
await connectToPeripheral(targetPeripheral)
|
|
} else {
|
|
os_log("未找到指定UUID的设备: %{public}@", log: logger, type: .error, address)
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
}
|
|
} else {
|
|
os_log("无效的UUID格式: %{public}@", log: logger, type: .error, address)
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 连接到指定外设
|
|
/// - Parameter peripheral: 要连接的外设
|
|
private func connectToPeripheral(_ peripheral: CBPeripheral) async {
|
|
self.peripheral = peripheral
|
|
peripheral.delegate = CBPeripheralHandler.shared
|
|
CBPeripheralHandler.shared.bleService = self
|
|
updateConnectionState(BleConst.STATE_CONNECTING)
|
|
centralManager?.connect(peripheral, options: nil)
|
|
}
|
|
|
|
/// 断开当前连接
|
|
func disconnect() async {
|
|
guard let peripheral = peripheral, let central = centralManager else {
|
|
return
|
|
}
|
|
|
|
updateConnectionState(BleConst.STATE_DISCONNECTING)
|
|
central.cancelPeripheralConnection(peripheral)
|
|
}
|
|
|
|
/// 安全关闭GATT连接
|
|
private func disconnectGatt() {
|
|
if let peripheral = peripheral, let central = centralManager {
|
|
central.cancelPeripheralConnection(peripheral)
|
|
}
|
|
|
|
peripheral = nil
|
|
writeCharacteristic = nil
|
|
notifyCharacteristic = nil
|
|
audioCharacteristic = nil
|
|
}
|
|
|
|
/// 更新连接状态并通知回调
|
|
private func updateConnectionState(_ state: Int) {
|
|
connectionState = state
|
|
}
|
|
|
|
// MARK: - 设备通信方法
|
|
|
|
/// 获取设备版本号
|
|
/// - Returns: 是否成功发送请求
|
|
func getDeviceVersion() -> Bool {
|
|
return sendCommand(BleConst.CMD_GET_VERSION)
|
|
}
|
|
|
|
/// 获取电池信息
|
|
/// - Returns: 是否成功发送请求
|
|
func getBatteryInfo() -> Bool {
|
|
return sendCommand(BleConst.CMD_GET_BATTERY_INFO)
|
|
}
|
|
|
|
/// 打开编码器
|
|
/// - Returns: 是否成功发送请求
|
|
func openEncoder() -> Bool {
|
|
return sendCodecControl(BleConst.CODEC_CONTROL_ENCODE_ON)
|
|
}
|
|
|
|
/// 打开解码器
|
|
/// - Returns: 是否成功发送请求
|
|
func openDecoder() -> Bool {
|
|
return sendCodecControl(BleConst.CODEC_CONTROL_DECODE_ON)
|
|
}
|
|
|
|
/// 关闭编解码器
|
|
/// - Returns: 是否成功发送请求
|
|
func closeCodec() -> Bool {
|
|
return sendCodecControl(BleConst.CODEC_CONTROL_CLOSE)
|
|
}
|
|
|
|
/// 播放A2DP
|
|
/// - Returns: 是否成功发送请求
|
|
func playA2DP() -> Bool {
|
|
return sendCodecControl(BleConst.CODEC_CONTROL_A2DP_PLAY)
|
|
}
|
|
|
|
/// 发送编解码控制指令
|
|
/// - Parameter control: 控制值
|
|
/// - Returns: 是否成功发送
|
|
private func sendCodecControl(_ control: UInt8) -> Bool {
|
|
let data = Data([BleConst.APP_REQUEST_HEADER, BleConst.CMD_CONTROL_CODEC, control])
|
|
return writeData(data)
|
|
}
|
|
|
|
/// 发送命令
|
|
/// - Parameter command: 命令字
|
|
/// - Returns: 是否成功发送
|
|
private func sendCommand(_ command: UInt8) -> Bool {
|
|
let data = Data([BleConst.APP_REQUEST_HEADER, command])
|
|
return writeData(data)
|
|
}
|
|
|
|
/// 写入数据
|
|
/// - Parameter data: 要写入的数据
|
|
/// - Returns: 是否成功写入
|
|
private func writeData(_ data: Data) -> Bool {
|
|
guard let peripheral = peripheral,
|
|
let characteristic = writeCharacteristic,
|
|
connectionState == BleConst.STATE_CONNECTED else {
|
|
os_log("无法写入数据,设备未连接或特征未找到", log: logger, type: .error)
|
|
return false
|
|
}
|
|
|
|
let hexString = data.map { String(format: "0x%02X", $0) }.joined(separator: " ")
|
|
os_log("写入数据: %{public}@", log: logger, type: .debug, hexString)
|
|
|
|
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
|
|
return true
|
|
}
|
|
|
|
// MARK: - 数据处理方法
|
|
|
|
/// 处理音频数据
|
|
/// - Parameter data: 音频数据
|
|
func processAudioData(_ data: Data) async {
|
|
os_log("收到音频数据: %d字节", log: logger, type: .debug, data.count)
|
|
await notifyAudioDataReceived(data: data)
|
|
}
|
|
|
|
/// 处理设备响应
|
|
/// - Parameter data: 设备响应数据
|
|
func processDeviceResponse(_ data: Data) async {
|
|
guard data.count >= 2 else { return }
|
|
|
|
let cmd = data[1]
|
|
os_log("收到设备响应: CMD=0x%02X", log: logger, type: .debug, cmd)
|
|
|
|
switch cmd {
|
|
case BleConst.CMD_GET_VERSION:
|
|
if data.count >= 3 {
|
|
// 解析版本号
|
|
let version = String(format: "%d.%d.%d", data[2], data.count > 3 ? data[3] : 0, data.count > 4 ? data[4] : 0)
|
|
let infoData: [String: Any] = ["version": version]
|
|
await notifyDeviceInfoReceived(infoType: Int(cmd), infoData: infoData)
|
|
}
|
|
case BleConst.CMD_GET_BATTERY_INFO:
|
|
if data.count >= 3 {
|
|
// 解析电池电量
|
|
let batteryLevel = Int(data[2])
|
|
let infoData: [String: Any] = ["level": batteryLevel]
|
|
await notifyDeviceInfoReceived(infoType: Int(cmd), infoData: infoData)
|
|
}
|
|
default:
|
|
os_log("未处理的响应命令: 0x%02X", log: logger, type: .debug, cmd)
|
|
}
|
|
}
|
|
|
|
/// 处理设备通知
|
|
/// - Parameter data: 设备通知数据
|
|
func processDeviceNotification(_ data: Data) async {
|
|
guard data.count >= 2 else { return }
|
|
|
|
let notifyType = data[1]
|
|
os_log("收到设备通知: Type=0x%02X", log: logger, type: .debug, notifyType)
|
|
|
|
switch notifyType {
|
|
case BleConst.CMD_WAKE_UP:
|
|
// 设备唤醒信号
|
|
await notifyWakeupSignalReceived()
|
|
default:
|
|
os_log("未处理的通知类型: 0x%02X", log: logger, type: .debug, notifyType)
|
|
}
|
|
}
|
|
|
|
// MARK: - 应用状态处理
|
|
|
|
/// 处理应用进入活跃状态
|
|
private func handleAppDidBecomeActive() async {
|
|
os_log("应用进入活跃状态", log: logger, type: .debug)
|
|
// 重新初始化蓝牙管理器,如果需要
|
|
if centralManager == nil {
|
|
_ = initialize()
|
|
}
|
|
}
|
|
|
|
/// 处理应用进入后台
|
|
private func handleAppDidEnterBackground() async {
|
|
os_log("应用进入后台", log: logger, type: .debug)
|
|
// 在iOS中,后台蓝牙操作有一定限制,这里可以做一些清理工作
|
|
}
|
|
|
|
// MARK: - 回调通知方法
|
|
|
|
/// 通知扫描结果
|
|
/// - Parameter devices: 设备列表
|
|
func notifyScanResult(devices: [[String: Any]]) async {
|
|
let currentDelegates = delegates
|
|
|
|
for delegate in currentDelegates {
|
|
await MainActor.run {
|
|
delegate.onScanResult(devices: devices)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知连接状态变化
|
|
/// - Parameter state: 连接状态
|
|
func notifyConnectionStateChanged(state: Int) async {
|
|
let currentDelegates = delegates
|
|
|
|
for delegate in currentDelegates {
|
|
await MainActor.run {
|
|
delegate.onConnectionStateChanged(state: state)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知音频数据接收
|
|
/// - Parameter data: 音频数据
|
|
func notifyAudioDataReceived(data: Data) async {
|
|
let currentDelegates = delegates
|
|
|
|
for delegate in currentDelegates {
|
|
await MainActor.run {
|
|
delegate.onAudioDataReceived(data: data)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知唤醒信号接收
|
|
func notifyWakeupSignalReceived() async {
|
|
let currentDelegates = delegates
|
|
|
|
for delegate in currentDelegates {
|
|
await MainActor.run {
|
|
delegate.onWakeupSignalReceived()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知设备信息接收
|
|
/// - Parameters:
|
|
/// - infoType: 信息类型
|
|
/// - infoData: 信息数据
|
|
func notifyDeviceInfoReceived(infoType: Int, infoData: [String: Any]) async {
|
|
let currentDelegates = delegates
|
|
|
|
for delegate in currentDelegates {
|
|
await MainActor.run {
|
|
delegate.onDeviceInfoReceived(infoType: infoType, infoData: infoData)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 处理扫描到的设备
|
|
func handleDiscoveredPeripheral(_ peripheral: CBPeripheral, advertisementData: [String: Any], rssi: NSNumber) async {
|
|
// 打印外设信息
|
|
os_log("扫描到设备: 名称=%{public}@, ID=%{public}@, RSSI=%d", log: logger, type: .debug,
|
|
peripheral.name ?? "未知", peripheral.identifier.uuidString, rssi.intValue)
|
|
|
|
// 提取服务UUID
|
|
var uuids: [String] = []
|
|
if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
|
|
uuids = serviceUUIDs.map { $0.uuidString }
|
|
}
|
|
|
|
// 创建设备信息字典
|
|
let deviceInfo: [String: Any] = [
|
|
"id": peripheral.identifier.uuidString,
|
|
"name": peripheral.name ?? "未知",
|
|
"rssi": rssi.intValue,
|
|
"uuids": uuids
|
|
]
|
|
|
|
// 更新扫描缓存
|
|
scanResults[peripheral] = deviceInfo
|
|
|
|
// 通知扫描结果
|
|
await notifyScanResult(devices: [deviceInfo])
|
|
}
|
|
|
|
/// 处理连接结果
|
|
func handleConnectionResult(peripheral: CBPeripheral, success: Bool, error: Error?) async {
|
|
if success {
|
|
os_log("已连接到设备: %{public}@", log: logger, type: .info, peripheral.identifier.uuidString)
|
|
updateConnectionState(BleConst.STATE_CONNECTED)
|
|
|
|
// 开始服务发现
|
|
peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID])
|
|
} else {
|
|
os_log("连接设备失败: %{public}@", log: logger, type: .error, error?.localizedDescription ?? "未知错误")
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
|
|
// 清理资源
|
|
self.peripheral = nil
|
|
}
|
|
}
|
|
|
|
/// 处理断开连接
|
|
func handleDisconnection(peripheral: CBPeripheral, error: Error?) async {
|
|
if let error = error {
|
|
os_log("设备连接断开(错误): %{public}@", log: logger, type: .error, error.localizedDescription)
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
} else {
|
|
os_log("设备连接断开(正常)", log: logger, type: .info)
|
|
updateConnectionState(BleConst.STATE_DISCONNECTED)
|
|
}
|
|
|
|
// 清理资源
|
|
if self.peripheral?.identifier == peripheral.identifier {
|
|
self.peripheral = nil
|
|
self.writeCharacteristic = nil
|
|
self.notifyCharacteristic = nil
|
|
self.audioCharacteristic = nil
|
|
}
|
|
}
|
|
|
|
/// 处理发现服务
|
|
func handleDiscoveredServices(peripheral: CBPeripheral, error: Error?) async {
|
|
if let error = error {
|
|
os_log("发现服务失败: %{public}@", log: logger, type: .error, error.localizedDescription)
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
return
|
|
}
|
|
|
|
guard let services = peripheral.services else {
|
|
os_log("没有发现任何服务", log: logger, type: .error)
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
return
|
|
}
|
|
|
|
// 发现所有服务的特征
|
|
for service in services {
|
|
os_log("发现服务: %{public}@", log: logger, type: .debug, service.uuid.uuidString)
|
|
peripheral.discoverCharacteristics(nil, for: service)
|
|
}
|
|
}
|
|
|
|
/// 处理发现的特征
|
|
func handleDiscoveredCharacteristics(for service: CBService, peripheral: CBPeripheral, error: Error?) async {
|
|
if let error = error {
|
|
os_log("发现特征失败: %{public}@", log: logger, type: .error, error.localizedDescription)
|
|
return
|
|
}
|
|
|
|
guard let characteristics = service.characteristics else {
|
|
os_log("服务没有任何特征: %{public}@", log: logger, type: .warning, service.uuid.uuidString)
|
|
return
|
|
}
|
|
|
|
// 根据UUID识别和保存特征
|
|
for characteristic in characteristics {
|
|
os_log("发现特征: %{public}@", log: logger, type: .debug, characteristic.uuid.uuidString)
|
|
|
|
// 主服务特征
|
|
if service.uuid == BleConst.PRIMARY_SERVICE_UUID {
|
|
if characteristic.uuid == BleConst.WRITE_CHAR_UUID {
|
|
writeCharacteristic = characteristic
|
|
os_log("找到写入特征", log: logger, type: .debug)
|
|
} else if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID {
|
|
notifyCharacteristic = characteristic
|
|
os_log("找到通知特征", log: logger, type: .debug)
|
|
// 设置通知
|
|
await setupNotification(for: characteristic, peripheral: peripheral)
|
|
}
|
|
}
|
|
|
|
// 音频服务特征
|
|
if service.uuid == BleConst.AUDIO_SERVICE_UUID {
|
|
if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID {
|
|
audioCharacteristic = characteristic
|
|
os_log("找到音频特征", log: logger, type: .debug)
|
|
// 设置通知
|
|
await setupNotification(for: characteristic, peripheral: peripheral)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查是否找到所有必要的特征
|
|
if writeCharacteristic == nil || notifyCharacteristic == nil {
|
|
os_log("未找到所有必要的特征", log: logger, type: .error)
|
|
updateConnectionState(BleConst.STATE_ERROR)
|
|
}
|
|
}
|
|
|
|
/// 设置特征的通知
|
|
/// - Parameters:
|
|
/// - characteristic: 特征
|
|
/// - peripheral: 外设
|
|
private func setupNotification(for characteristic: CBCharacteristic, peripheral: CBPeripheral) async {
|
|
if characteristic.properties.contains(.notify) {
|
|
peripheral.setNotifyValue(true, for: characteristic)
|
|
}
|
|
}
|
|
|
|
/// 处理特征值更新
|
|
func handleValueUpdate(for characteristic: CBCharacteristic, peripheral: CBPeripheral, error: Error?) async {
|
|
if let error = error {
|
|
os_log("特征值更新失败: %{public}@", log: logger, type: .error, error.localizedDescription)
|
|
return
|
|
}
|
|
|
|
guard let data = characteristic.value else {
|
|
os_log("特征没有值", log: logger, type: .warning)
|
|
return
|
|
}
|
|
|
|
let hexString = data.map { String(format: "%02X", $0) }.joined(separator: " ")
|
|
os_log("收到特征数据: %{public}@ - %{public}@", log: logger, type: .debug, characteristic.uuid.uuidString, hexString)
|
|
|
|
// 根据特征UUID处理不同类型的数据
|
|
if characteristic.uuid == BleConst.RECEIVE_AUDIO_CHAR_UUID {
|
|
// 音频数据处理
|
|
await processAudioData(data)
|
|
} else if characteristic.uuid == BleConst.NOTIFY_CHAR_UUID {
|
|
// 通知特征处理
|
|
if !data.isEmpty {
|
|
switch data[0] {
|
|
case BleConst.DEVICE_RESPONSE_HEADER:
|
|
// 设备响应
|
|
await processDeviceResponse(data)
|
|
case BleConst.DEVICE_NOTIFY_HEADER:
|
|
// 设备通知
|
|
await processDeviceNotification(data)
|
|
default:
|
|
os_log("未知数据类型: %d", log: logger, type: .debug, data[0])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 状态恢复处理
|
|
|
|
/// 处理蓝牙状态恢复
|
|
func handleStateRestoration(dict: [String: Any]) async {
|
|
os_log("正在恢复蓝牙状态...", log: logger, type: .info)
|
|
|
|
// 1. 恢复之前连接的外设
|
|
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
|
|
os_log("恢复 %d 个已连接的外设", log: logger, type: .debug, peripherals.count)
|
|
|
|
for peripheral in peripherals {
|
|
self.peripheral = peripheral
|
|
peripheral.delegate = CBPeripheralHandler.shared
|
|
CBPeripheralHandler.shared.bleService = self
|
|
|
|
// 根据外设当前状态采取不同操作
|
|
if peripheral.state == .connected {
|
|
os_log("外设 %{public}@ 已连接", log: logger, type: .debug, peripheral.identifier.uuidString)
|
|
updateConnectionState(BleConst.STATE_CONNECTED)
|
|
|
|
// 重新发现服务
|
|
peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID])
|
|
} else if peripheral.state == .connecting {
|
|
os_log("外设 %{public}@ 正在连接中", log: logger, type: .debug, peripheral.identifier.uuidString)
|
|
updateConnectionState(BleConst.STATE_CONNECTING)
|
|
} else {
|
|
os_log("外设 %{public}@ 状态异常: %d", log: logger, type: .debug, peripheral.identifier.uuidString, peripheral.state.rawValue)
|
|
// 尝试重新连接
|
|
updateConnectionState(BleConst.STATE_CONNECTING)
|
|
centralManager?.connect(peripheral, options: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. 恢复扫描状态
|
|
if let scanServices = dict[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID] {
|
|
os_log("恢复扫描任务,服务UUID数: %d", log: logger, type: .debug, scanServices.count)
|
|
|
|
let scanOptions = dict[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any] ?? [
|
|
CBCentralManagerScanOptionAllowDuplicatesKey: true
|
|
]
|
|
|
|
// 重新启动扫描任务
|
|
centralManager?.scanForPeripherals(
|
|
withServices: scanServices.isEmpty ? nil : scanServices,
|
|
options: scanOptions
|
|
)
|
|
|
|
isScanning = true
|
|
|
|
// 设置新的扫描超时(默认15秒)
|
|
scanTimeoutTask?.cancel()
|
|
scanTimeoutTask = Task { [weak self] in
|
|
guard let self = self else { return }
|
|
|
|
do {
|
|
try await Task.sleep(nanoseconds: UInt64(15.0 * 1_000_000_000))
|
|
if !Task.isCancelled {
|
|
await self.stopScan()
|
|
}
|
|
} catch {
|
|
// 任务被取消,忽略错误
|
|
}
|
|
}
|
|
}
|
|
|
|
// 通知外部:BLE状态已恢复
|
|
await notifyStateRestored()
|
|
}
|
|
|
|
/// 通知状态恢复
|
|
private func notifyStateRestored() async {
|
|
let currentDelegates = delegates
|
|
|
|
for delegate in currentDelegates {
|
|
if let stateDelegate = delegate as? BleStateRestorationDelegate {
|
|
await MainActor.run {
|
|
stateDelegate.onBleStateRestored()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 中央管理器委托处理器
|
|
|
|
class CBManagerHandler: NSObject, CBCentralManagerDelegate {
|
|
static let shared = CBManagerHandler()
|
|
weak var bleService: BleService?
|
|
|
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
Task {
|
|
guard let service = bleService else { return }
|
|
|
|
switch central.state {
|
|
case .poweredOn:
|
|
await MainActor.run {
|
|
os_log("蓝牙已开启", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .info)
|
|
}
|
|
case .poweredOff:
|
|
await MainActor.run {
|
|
os_log("蓝牙已关闭", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .warning)
|
|
}
|
|
await service.updateConnectionState(BleConst.STATE_DISCONNECTED)
|
|
case .unauthorized, .unsupported:
|
|
await MainActor.run {
|
|
os_log("蓝牙不可用: %d", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .error, central.state.rawValue)
|
|
}
|
|
await service.updateConnectionState(BleConst.STATE_ERROR)
|
|
default:
|
|
await MainActor.run {
|
|
os_log("蓝牙状态变化: %d", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .debug, central.state.rawValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
|
Task {
|
|
await bleService?.handleDiscoveredPeripheral(peripheral, advertisementData: advertisementData, rssi: RSSI)
|
|
}
|
|
}
|
|
|
|
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
|
Task {
|
|
await bleService?.handleConnectionResult(peripheral: peripheral, success: true, error: nil)
|
|
}
|
|
}
|
|
|
|
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
|
Task {
|
|
await bleService?.handleConnectionResult(peripheral: peripheral, success: false, error: error)
|
|
}
|
|
}
|
|
|
|
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
|
Task {
|
|
await bleService?.handleDisconnection(peripheral: peripheral, error: error)
|
|
}
|
|
}
|
|
|
|
/// 状态恢复处理
|
|
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
|
|
os_log("蓝牙状态恢复被触发", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .info)
|
|
|
|
// 打印恢复信息
|
|
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
|
|
os_log("恢复外设数量: %d", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .debug, peripherals.count)
|
|
for (i, peripheral) in peripherals.enumerated() {
|
|
os_log("恢复外设[%d]: %{public}@", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .debug, i, peripheral.identifier.uuidString)
|
|
}
|
|
}
|
|
|
|
if let services = dict[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID] {
|
|
os_log("恢复扫描服务数量: %d", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .debug, services.count)
|
|
}
|
|
|
|
// 异步处理状态恢复
|
|
Task {
|
|
if let service = bleService {
|
|
await service.handleStateRestoration(dict: dict)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 外设委托处理器
|
|
|
|
class CBPeripheralHandler: NSObject, CBPeripheralDelegate {
|
|
static let shared = CBPeripheralHandler()
|
|
weak var bleService: BleService?
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
|
Task {
|
|
await bleService?.handleDiscoveredServices(peripheral: peripheral, error: error)
|
|
}
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
|
Task {
|
|
await bleService?.handleDiscoveredCharacteristics(for: service, peripheral: peripheral, error: error)
|
|
}
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
Task {
|
|
await bleService?.handleValueUpdate(for: characteristic, peripheral: peripheral, error: error)
|
|
}
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
if let error = error {
|
|
os_log("写入特征值失败: %{public}@", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .error, error.localizedDescription)
|
|
} else {
|
|
os_log("写入特征值成功", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .debug)
|
|
}
|
|
}
|
|
|
|
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
|
if let error = error {
|
|
os_log("设置通知状态失败: %{public}@", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .error, error.localizedDescription)
|
|
} else {
|
|
os_log("设置通知状态成功: %{public}@", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"), type: .debug, characteristic.uuid.uuidString)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - BLE状态恢复代理
|
|
/// 蓝牙状态恢复代理
|
|
protocol BleStateRestorationDelegate: AnyObject {
|
|
/// 当蓝牙状态被系统恢复时调用
|
|
func onBleStateRestored()
|
|
}
|
|
|
|
// MARK: - BLE代理
|
|
/// 蓝牙事件代理
|
|
protocol BleDelegate: AnyObject {
|
|
/// 扫描结果回调
|
|
func onScanResult(devices: [[String: Any]])
|
|
|
|
/// 连接状态变化回调
|
|
func onConnectionStateChanged(state: Int)
|
|
|
|
/// 音频数据接收回调
|
|
func onAudioDataReceived(data: Data)
|
|
|
|
/// 唤醒信号接收回调
|
|
func onWakeupSignalReceived()
|
|
|
|
/// 设备信息接收回调
|
|
func onDeviceInfoReceived(infoType: Int, infoData: [String: Any])
|
|
}
|