| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- import Foundation
- import CoreBluetooth
- import Combine
- /// Manages Bluetooth Low Energy (BLE) scanning, device connection, and binding to user accounts.
- final class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
- static let shared = BLEManager()
- @Published private(set) var state: CBManagerState = .unknown
- @Published var isScanning: Bool = false
- @Published var discoveredDevices: [DiscoveredBLEDevice] = []
- @Published var boundDevices: [BoundDevice] = []
-
- private var centralManager: CBCentralManager!
- private let boundDevicesKey = "com.celestia.trace.bound_devices"
-
- override private init() {
- super.init()
- self.centralManager = CBCentralManager(delegate: self, queue: nil)
- loadBoundDevices()
- }
-
- // MARK: - CentralManager Delegate
-
- func centralManagerDidUpdateState(_ central: CBCentralManager) {
- DispatchQueue.main.async {
- self.state = central.state
- }
- }
-
- func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
- let name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? "现场录音设备"
- let device = DiscoveredBLEDevice(
- id: peripheral.identifier.uuidString,
- name: name,
- rssi: RSSI.intValue,
- peripheralUUID: peripheral.identifier.uuidString
- )
-
- DispatchQueue.main.async {
- if !self.discoveredDevices.contains(where: { $0.id == device.id }) {
- self.discoveredDevices.append(device)
- }
- }
- }
-
- // MARK: - Scanning & Mocking
-
- func startScanning() {
- discoveredDevices.removeAll()
- isScanning = true
-
- if centralManager.state == .poweredOn {
- centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
- }
-
- // Populate mock devices for simulator or testing environment
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { [weak self] in
- guard let self = self, self.isScanning else { return }
- let mockDevices = [
- DiscoveredBLEDevice(id: "MOCK-BLE-01", name: "星痕专业录音麦克风 01", rssi: -58, peripheralUUID: "0000180A-0000-1000-8000-00805F9B34FB"),
- DiscoveredBLEDevice(id: "MOCK-BLE-02", name: "星痕无线音频集线器", rssi: -72, peripheralUUID: "0000180F-0000-1000-8000-00805F9B34FB"),
- DiscoveredBLEDevice(id: "MOCK-BLE-03", name: "智能胸卡录音器 A2", rssi: -85, peripheralUUID: "0000181A-0000-1000-8000-00805F9B34FB")
- ]
- for device in mockDevices {
- if !self.discoveredDevices.contains(where: { $0.id == device.id }) {
- self.discoveredDevices.append(device)
- }
- }
- }
- }
-
- func stopScanning() {
- if centralManager.state == .poweredOn {
- centralManager.stopScan()
- }
- isScanning = false
- }
-
- // MARK: - Binding Management
-
- /// Binds a discovered BLE device to the specified user account ID.
- func bindDevice(_ device: DiscoveredBLEDevice, userId: String) -> BoundDevice {
- let boundDevice = BoundDevice(
- id: device.id,
- name: device.name,
- peripheralUUID: device.peripheralUUID,
- boundAt: Date(),
- isConnected: true,
- batteryLevel: Int.random(in: 75...99),
- firmwareVersion: "v1.4.2",
- boundUserId: userId
- )
-
- // Unbind any existing duplicate
- boundDevices.removeAll(where: { $0.id == boundDevice.id })
- boundDevices.append(boundDevice)
- saveBoundDevices()
- return boundDevice
- }
-
- /// Unbinds a device by its ID.
- func unbindDevice(id: String) {
- boundDevices.removeAll(where: { $0.id == id })
- saveBoundDevices()
- }
-
- /// Returns devices bound to the given user account.
- func devices(forUserId userId: String) -> [BoundDevice] {
- return boundDevices.filter { $0.boundUserId == userId }
- }
-
- // MARK: - Local Persistence
-
- private func saveBoundDevices() {
- if let data = try? JSONEncoder().encode(boundDevices) {
- UserDefaults.standard.set(data, forKey: boundDevicesKey)
- }
- }
-
- private func loadBoundDevices() {
- if let data = UserDefaults.standard.data(forKey: boundDevicesKey),
- let devices = try? JSONDecoder().decode([BoundDevice].self, from: data) {
- self.boundDevices = devices
- }
- }
- }
|