BLEManager.swift 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import Foundation
  2. import CoreBluetooth
  3. import Combine
  4. /// Manages Bluetooth Low Energy (BLE) scanning, device connection, and binding to user accounts.
  5. final class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
  6. static let shared = BLEManager()
  7. @Published private(set) var state: CBManagerState = .unknown
  8. @Published var isScanning: Bool = false
  9. @Published var discoveredDevices: [DiscoveredBLEDevice] = []
  10. @Published var boundDevices: [BoundDevice] = []
  11. private var centralManager: CBCentralManager!
  12. private let boundDevicesKey = "com.celestia.trace.bound_devices"
  13. override private init() {
  14. super.init()
  15. self.centralManager = CBCentralManager(delegate: self, queue: nil)
  16. loadBoundDevices()
  17. }
  18. // MARK: - CentralManager Delegate
  19. func centralManagerDidUpdateState(_ central: CBCentralManager) {
  20. DispatchQueue.main.async {
  21. self.state = central.state
  22. }
  23. }
  24. func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
  25. let name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? "现场录音设备"
  26. let device = DiscoveredBLEDevice(
  27. id: peripheral.identifier.uuidString,
  28. name: name,
  29. rssi: RSSI.intValue,
  30. peripheralUUID: peripheral.identifier.uuidString
  31. )
  32. DispatchQueue.main.async {
  33. if !self.discoveredDevices.contains(where: { $0.id == device.id }) {
  34. self.discoveredDevices.append(device)
  35. }
  36. }
  37. }
  38. // MARK: - Scanning & Mocking
  39. func startScanning() {
  40. discoveredDevices.removeAll()
  41. isScanning = true
  42. if centralManager.state == .poweredOn {
  43. centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
  44. }
  45. // Populate mock devices for simulator or testing environment
  46. DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { [weak self] in
  47. guard let self = self, self.isScanning else { return }
  48. let mockDevices = [
  49. DiscoveredBLEDevice(id: "MOCK-BLE-01", name: "星痕专业录音麦克风 01", rssi: -58, peripheralUUID: "0000180A-0000-1000-8000-00805F9B34FB"),
  50. DiscoveredBLEDevice(id: "MOCK-BLE-02", name: "星痕无线音频集线器", rssi: -72, peripheralUUID: "0000180F-0000-1000-8000-00805F9B34FB"),
  51. DiscoveredBLEDevice(id: "MOCK-BLE-03", name: "智能胸卡录音器 A2", rssi: -85, peripheralUUID: "0000181A-0000-1000-8000-00805F9B34FB")
  52. ]
  53. for device in mockDevices {
  54. if !self.discoveredDevices.contains(where: { $0.id == device.id }) {
  55. self.discoveredDevices.append(device)
  56. }
  57. }
  58. }
  59. }
  60. func stopScanning() {
  61. if centralManager.state == .poweredOn {
  62. centralManager.stopScan()
  63. }
  64. isScanning = false
  65. }
  66. // MARK: - Binding Management
  67. /// Binds a discovered BLE device to the specified user account ID.
  68. func bindDevice(_ device: DiscoveredBLEDevice, userId: String) -> BoundDevice {
  69. let boundDevice = BoundDevice(
  70. id: device.id,
  71. name: device.name,
  72. peripheralUUID: device.peripheralUUID,
  73. boundAt: Date(),
  74. isConnected: true,
  75. batteryLevel: Int.random(in: 75...99),
  76. firmwareVersion: "v1.4.2",
  77. boundUserId: userId
  78. )
  79. // Unbind any existing duplicate
  80. boundDevices.removeAll(where: { $0.id == boundDevice.id })
  81. boundDevices.append(boundDevice)
  82. saveBoundDevices()
  83. return boundDevice
  84. }
  85. /// Unbinds a device by its ID.
  86. func unbindDevice(id: String) {
  87. boundDevices.removeAll(where: { $0.id == id })
  88. saveBoundDevices()
  89. }
  90. /// Returns devices bound to the given user account.
  91. func devices(forUserId userId: String) -> [BoundDevice] {
  92. return boundDevices.filter { $0.boundUserId == userId }
  93. }
  94. // MARK: - Local Persistence
  95. private func saveBoundDevices() {
  96. if let data = try? JSONEncoder().encode(boundDevices) {
  97. UserDefaults.standard.set(data, forKey: boundDevicesKey)
  98. }
  99. }
  100. private func loadBoundDevices() {
  101. if let data = UserDefaults.standard.data(forKey: boundDevicesKey),
  102. let devices = try? JSONDecoder().decode([BoundDevice].self, from: data) {
  103. self.boundDevices = devices
  104. }
  105. }
  106. }