|
|
|
@ -165,31 +165,32 @@ public class BleService: NSObject { |
|
|
|
} |
|
|
|
|
|
|
|
/// 开始扫描BLE设备 |
|
|
|
public func startScan(timeout: TimeInterval = 30.0) { |
|
|
|
public func startScan(timeout: TimeInterval = 30.0) -> Bool { |
|
|
|
if centralManager == nil { |
|
|
|
initializeCentralManager() |
|
|
|
} |
|
|
|
|
|
|
|
guard let manager = centralManager, manager.state == .poweredOn else { |
|
|
|
os_log("❌ 蓝牙未就绪,无法开始扫描", log: logger, type: .error) |
|
|
|
return |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
if isScanning { |
|
|
|
stopScan() |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
// 清空之前的扫描结果 |
|
|
|
discoveredDevices.removeAll() |
|
|
|
deviceAdvertisementData.removeAll() |
|
|
|
|
|
|
|
isScanning = true |
|
|
|
|
|
|
|
let serviceUUIDs = [BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID] |
|
|
|
manager.scanForPeripherals(withServices: serviceUUIDs, options: [ |
|
|
|
CBCentralManagerScanOptionAllowDuplicatesKey: false |
|
|
|
// 移除服务UUID过滤,扫描所有设备 |
|
|
|
manager.scanForPeripherals(withServices: nil, options: [ |
|
|
|
CBCentralManagerScanOptionAllowDuplicatesKey: true // 允许重复以获取更新的广播数据 |
|
|
|
]) |
|
|
|
|
|
|
|
os_log("🔍 开始扫描BLE设备,超时时间: %.1f秒", log: logger, type: .info, timeout) |
|
|
|
os_log("🔍 开始扫描所有BLE设备(无过滤),超时时间: %.1f秒", log: logger, type: .info, timeout) |
|
|
|
|
|
|
|
// 设置扫描超时 |
|
|
|
scanTimer?.invalidate() |
|
|
|
@ -197,6 +198,7 @@ public class BleService: NSObject { |
|
|
|
self?.stopScan() |
|
|
|
os_log("⏰ 扫描超时,自动停止", log: self?.logger ?? OSLog.default, type: .info) |
|
|
|
} |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
/// 停止扫描 |
|
|
|
@ -206,7 +208,20 @@ public class BleService: NSObject { |
|
|
|
isScanning = false |
|
|
|
scanTimer?.invalidate() |
|
|
|
scanTimer = nil |
|
|
|
os_log("⏹️ 停止BLE扫描", log: logger, type: .info) |
|
|
|
|
|
|
|
// let deviceCount = discoveredDevices.count |
|
|
|
// os_log("⏹️ 停止BLE扫描,共发现 %d 个设备", log: logger, type: .info, deviceCount) |
|
|
|
// |
|
|
|
// // 打印所有发现的设备摘要 |
|
|
|
// if deviceCount > 0 { |
|
|
|
// os_log("📋 扫描结果摘要:", log: logger, type: .info) |
|
|
|
// for (index, deviceInfo) in discoveredDevices.values.enumerated() { |
|
|
|
// if let name = deviceInfo["name"] as? String, |
|
|
|
// let rssi = deviceInfo["rssi"] as? Int { |
|
|
|
// os_log("📋 %d. %{public}@ (RSSI: %d)", log: logger, type: .info, index + 1, name, rssi) |
|
|
|
// } |
|
|
|
// } |
|
|
|
// } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -331,6 +346,75 @@ public class BleService: NSObject { |
|
|
|
return Array(discoveredDevices.values) |
|
|
|
} |
|
|
|
|
|
|
|
/// 根据设备名称获取设备列表 |
|
|
|
public func getDeviceAdvertisementData(deviceName: String) -> [[String: Any]] { |
|
|
|
var matchingDevices: [[String: Any]] = [] |
|
|
|
|
|
|
|
// 遍历所有已发现的设备,查找匹配的设备名 |
|
|
|
for (deviceId, deviceInfo) in discoveredDevices { |
|
|
|
if let name = deviceInfo["name"] as? String, name == deviceName { |
|
|
|
// 获取对应的广播数据 |
|
|
|
var deviceData = deviceInfo |
|
|
|
if let advData = deviceAdvertisementData[deviceId] { |
|
|
|
// 合并设备信息和广播数据 |
|
|
|
deviceData.merge(advData) { (current, _) in current } |
|
|
|
} |
|
|
|
matchingDevices.append(deviceData) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
os_log("根据设备名 '%{public}@' 找到 %d 个匹配设备", log: logger, type: .debug, deviceName, matchingDevices.count) |
|
|
|
return matchingDevices |
|
|
|
} |
|
|
|
|
|
|
|
/// 获取设备的制造商数据(hex格式) |
|
|
|
public func getDeviceManufacturerDataHex(deviceId: String) -> String? { |
|
|
|
guard let advData = deviceAdvertisementData[deviceId], |
|
|
|
let manufacturerData = advData["manufacturerDataRaw"] as? Data else { |
|
|
|
return nil |
|
|
|
} |
|
|
|
return manufacturerData.map { String(format: "%02x", $0) }.joined(separator: " ") |
|
|
|
} |
|
|
|
|
|
|
|
/// 获取所有设备的广播数据摘要 |
|
|
|
public func getAllDevicesAdvertisementData() -> [String: [String: Any]] { |
|
|
|
return deviceAdvertisementData |
|
|
|
} |
|
|
|
|
|
|
|
/// 获取设备信息 |
|
|
|
public func getDeviceInfo(deviceId: String) -> [String: Any]? { |
|
|
|
// 直接从缓存获取,与 Android 端逻辑保持一致 |
|
|
|
if let cachedInfo = discoveredDevices[deviceId] { |
|
|
|
os_log("获取设备信息: %{public}@", log: logger, type: .debug, deviceId) |
|
|
|
return cachedInfo |
|
|
|
} |
|
|
|
|
|
|
|
os_log("未找到设备信息: %{public}@", log: logger, type: .error, deviceId) |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
/// 清除过期的设备缓存(超过maxCacheTime的设备) |
|
|
|
public func clearExpiredDevices() { |
|
|
|
let currentTime = Date().timeIntervalSince1970 |
|
|
|
let expiredDeviceIds = deviceAdvertisementData.compactMap { (deviceId, advData) -> String? in |
|
|
|
if let timestamp = advData["timestamp"] as? TimeInterval, |
|
|
|
currentTime - timestamp > maxCacheTime { |
|
|
|
return deviceId |
|
|
|
} |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
for deviceId in expiredDeviceIds { |
|
|
|
discoveredDevices.removeValue(forKey: deviceId) |
|
|
|
deviceAdvertisementData.removeValue(forKey: deviceId) |
|
|
|
os_log("🗑️ 清除过期设备缓存: %{public}@", log: logger, type: .debug, deviceId) |
|
|
|
} |
|
|
|
|
|
|
|
if !expiredDeviceIds.isEmpty { |
|
|
|
os_log("🗑️ 已清除%d个过期设备缓存", log: logger, type: .info, expiredDeviceIds.count) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - 回调通知方法 |
|
|
|
|
|
|
|
/// 通知所有回调扫描结果 |
|
|
|
@ -1053,76 +1137,118 @@ extension BleService: CBCentralManagerDelegate { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 原有方法 |
|
|
|
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { |
|
|
|
let deviceId = peripheral.identifier.uuidString |
|
|
|
let deviceName = peripheral.name ?? "Unknown" |
|
|
|
|
|
|
|
// 构建设备基本信息 |
|
|
|
// 简化的设备信息构建 - 参考 Android 端的简洁性 |
|
|
|
let deviceInfo = buildDeviceInfo(peripheral: peripheral, advertisementData: advertisementData, rssi: RSSI) |
|
|
|
|
|
|
|
// 检查是否为新设备或信息有重要变化 |
|
|
|
var shouldNotify = false |
|
|
|
|
|
|
|
if let existingDevice = discoveredDevices[deviceId] { |
|
|
|
// 设备已存在,检查是否有重要变化 |
|
|
|
let oldRssi = existingDevice["rssi"] as? Int ?? 0 |
|
|
|
let newRssi = RSSI.intValue |
|
|
|
|
|
|
|
// 只有当RSSI变化超过阈值时才上报(避免频繁更新) |
|
|
|
if abs(newRssi - oldRssi) > 10 { |
|
|
|
shouldNotify = true |
|
|
|
os_log("📱 设备RSSI变化较大 - 名称: %{public}@, UUID: %{public}@, RSSI: %d → %d", log: logger, type: .debug, deviceName, deviceId, oldRssi, newRssi) |
|
|
|
} |
|
|
|
} else { |
|
|
|
// 新设备,需要上报 |
|
|
|
shouldNotify = true |
|
|
|
os_log("📱 发现新设备 - 名称: %{public}@, UUID: %{public}@", log: logger, type: .info, deviceName, deviceId) |
|
|
|
|
|
|
|
// 打印 hex 数据 - 直接从 deviceInfo 获取 |
|
|
|
if let hex = deviceInfo["hex"] as? String { |
|
|
|
os_log("📡 设备 hex 数据: %{public}@", log: logger, type: .info, hex) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 更新缓存(无论是否上报都要更新) |
|
|
|
discoveredDevices[deviceId] = deviceInfo |
|
|
|
|
|
|
|
// 只有在需要时才上报 |
|
|
|
if shouldNotify { |
|
|
|
notifyScanResult(devices: [deviceInfo]) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 新增:统一的设备信息构建方法 |
|
|
|
private func buildDeviceInfo(peripheral: CBPeripheral, advertisementData: [String: Any], rssi: NSNumber) -> [String: Any] { |
|
|
|
let deviceId = peripheral.identifier.uuidString |
|
|
|
let deviceName = peripheral.name ?? "Unknown" |
|
|
|
|
|
|
|
// 基本设备信息 - 与 Android 端格式对齐 |
|
|
|
var deviceInfo: [String: Any] = [ |
|
|
|
"name": deviceName, |
|
|
|
"id": deviceId, |
|
|
|
"rssi": RSSI.intValue, |
|
|
|
"name": deviceName, |
|
|
|
"rssi": rssi.intValue, |
|
|
|
"discoveredAt": Date().timeIntervalSince1970 |
|
|
|
] |
|
|
|
|
|
|
|
// 详细的广播数据记录 |
|
|
|
var advData: [String: Any] = [:] |
|
|
|
|
|
|
|
// 制造商数据 |
|
|
|
if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { |
|
|
|
deviceInfo["manufacturerData"] = manufacturerData |
|
|
|
advData["manufacturerData"] = manufacturerData.map { String(format: "%02x", $0) }.joined(separator: " ") |
|
|
|
advData["manufacturerDataRaw"] = manufacturerData |
|
|
|
} |
|
|
|
|
|
|
|
// 服务UUID列表 |
|
|
|
// 服务 UUIDs - 与 Android 端保持一致的字段名 |
|
|
|
if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] { |
|
|
|
let serviceUUIDStrings = serviceUUIDs.map { $0.uuidString } |
|
|
|
deviceInfo["serviceUUIDs"] = serviceUUIDStrings |
|
|
|
advData["serviceUUIDs"] = serviceUUIDStrings |
|
|
|
let uuids = serviceUUIDs.map { $0.uuidString } |
|
|
|
deviceInfo["uuids"] = uuids // 使用与 Android 端一致的字段名 |
|
|
|
} |
|
|
|
|
|
|
|
// 本地名称 |
|
|
|
if let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String { |
|
|
|
advData["localName"] = localName |
|
|
|
// 只存储 hex 数据 - 简化存储 |
|
|
|
if let rawData = extractRawAdvertisementData(from: advertisementData) { |
|
|
|
deviceInfo["hex"] = rawData // 直接存储 hex 数据,不使用 advertisementData 包装 |
|
|
|
} |
|
|
|
|
|
|
|
// 发射功率 |
|
|
|
if let txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber { |
|
|
|
advData["txPowerLevel"] = txPowerLevel.intValue |
|
|
|
return deviceInfo |
|
|
|
} |
|
|
|
|
|
|
|
// 提取原始广播数据的辅助方法 |
|
|
|
private func extractRawAdvertisementData(from advertisementData: [String: Any]) -> String? { |
|
|
|
// 尝试从各种数据源构建原始数据的十六进制表示 |
|
|
|
var allData = Data() |
|
|
|
|
|
|
|
if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { |
|
|
|
allData.append(manufacturerData) |
|
|
|
} |
|
|
|
|
|
|
|
// 服务数据 |
|
|
|
if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] { |
|
|
|
var serviceDataDict: [String: String] = [:] |
|
|
|
for (uuid, data) in serviceData { |
|
|
|
serviceDataDict[uuid.uuidString] = data.map { String(format: "%02x", $0) }.joined(separator: " ") |
|
|
|
for (_, data) in serviceData { |
|
|
|
allData.append(data) |
|
|
|
} |
|
|
|
advData["serviceData"] = serviceDataDict |
|
|
|
} |
|
|
|
|
|
|
|
// 可连接性 |
|
|
|
if let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber { |
|
|
|
advData["isConnectable"] = isConnectable.boolValue |
|
|
|
return allData.isEmpty ? nil : allData.map { String(format: "%02X", $0) }.joined() |
|
|
|
} |
|
|
|
|
|
|
|
// 数据清理方法 |
|
|
|
private func sanitizeData(_ dict: [String: Any]) -> [String: Any] { |
|
|
|
var sanitized: [String: Any] = [:] |
|
|
|
for (key, value) in dict { |
|
|
|
if value is Data { |
|
|
|
// 跳过 Data 对象 |
|
|
|
continue |
|
|
|
} else if let nestedDict = value as? [String: Any] { |
|
|
|
sanitized[key] = sanitizeData(nestedDict) |
|
|
|
} else if let array = value as? [Any] { |
|
|
|
var filteredArray: [Any] = [] |
|
|
|
for item in array { |
|
|
|
if item is Data { |
|
|
|
// 跳过 Data 对象 |
|
|
|
continue |
|
|
|
} else if let nestedDict = item as? [String: Any] { |
|
|
|
filteredArray.append(sanitizeData(nestedDict)) |
|
|
|
} else { |
|
|
|
filteredArray.append(item) |
|
|
|
} |
|
|
|
} |
|
|
|
sanitized[key] = filteredArray |
|
|
|
} else { |
|
|
|
sanitized[key] = value |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 完整的广播数据 |
|
|
|
advData["rssi"] = RSSI.intValue |
|
|
|
advData["timestamp"] = Date().timeIntervalSince1970 |
|
|
|
|
|
|
|
// 缓存设备信息和广播数据 |
|
|
|
discoveredDevices[deviceId] = deviceInfo |
|
|
|
deviceAdvertisementData[deviceId] = advData |
|
|
|
|
|
|
|
// 添加广播数据到设备信息中 |
|
|
|
deviceInfo["advertisementData"] = advData |
|
|
|
|
|
|
|
os_log("📡 发现设备: %{public}@ (ID: %{public}@), RSSI: %d", log: logger, type: .info, deviceName, deviceId, RSSI.intValue) |
|
|
|
os_log("📡 广播数据: %{public}@", log: logger, type: .debug, String(describing: advData)) |
|
|
|
|
|
|
|
// 报告给回调 |
|
|
|
notifyScanResult(devices: [deviceInfo]) |
|
|
|
return sanitized |
|
|
|
} |
|
|
|
|
|
|
|
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { |
|
|
|
|