fix(ble): restore peripheral state and keep advertising

This commit is contained in:
2026-01-08 22:27:11 +00:00
parent d89aedd5af
commit 214af08625
2 changed files with 45 additions and 7 deletions

View File

@@ -16,6 +16,7 @@ final class BlePeripheralManager: NSObject, ObservableObject {
static let serviceUUID = CBUUID(string: "A0B0C0D0-E0F0-4A0B-9C0D-0E0F1A2B3C4D") static let serviceUUID = CBUUID(string: "A0B0C0D0-E0F0-4A0B-9C0D-0E0F1A2B3C4D")
static let feedTxUUID = CBUUID(string: "A0B0C0D1-E0F0-4A0B-9C0D-0E0F1A2B3C4D") static let feedTxUUID = CBUUID(string: "A0B0C0D1-E0F0-4A0B-9C0D-0E0F1A2B3C4D")
static let controlRxUUID = CBUUID(string: "A0B0C0D2-E0F0-4A0B-9C0D-0E0F1A2B3C4D") static let controlRxUUID = CBUUID(string: "A0B0C0D2-E0F0-4A0B-9C0D-0E0F1A2B3C4D")
private static let restoreIdentifier = "iris.ble.peripheral.v1"
@Published private(set) var bluetoothState: CBManagerState = .unknown @Published private(set) var bluetoothState: CBManagerState = .unknown
@Published var advertisingEnabled: Bool = true @Published var advertisingEnabled: Bool = true
@@ -31,7 +32,13 @@ final class BlePeripheralManager: NSObject, ObservableObject {
@Published private(set) var lastDataAt: Date? = nil @Published private(set) var lastDataAt: Date? = nil
private let queue = DispatchQueue(label: "iris.ble.peripheral.queue") private let queue = DispatchQueue(label: "iris.ble.peripheral.queue")
private lazy var peripheral = CBPeripheralManager(delegate: self, queue: queue) private lazy var peripheral = CBPeripheralManager(
delegate: self,
queue: queue,
options: [
CBPeripheralManagerOptionRestoreIdentifierKey: Self.restoreIdentifier,
]
)
private var service: CBMutableService? private var service: CBMutableService?
private var feedTx: CBMutableCharacteristic? private var feedTx: CBMutableCharacteristic?
@@ -111,8 +118,6 @@ final class BlePeripheralManager: NSObject, ObservableObject {
guard peripheral.state == .poweredOn else { return } guard peripheral.state == .poweredOn else { return }
guard service == nil else { return } guard service == nil else { return }
peripheral.removeAllServices()
let feedTx = CBMutableCharacteristic( let feedTx = CBMutableCharacteristic(
type: Self.feedTxUUID, type: Self.feedTxUUID,
properties: [.notify, .read], properties: [.notify, .read],
@@ -139,7 +144,7 @@ final class BlePeripheralManager: NSObject, ObservableObject {
stopAdvertising() stopAdvertising()
return return
} }
if advertisingEnabled, subscribedCentralIds.isEmpty { if advertisingEnabled {
startAdvertising() startAdvertising()
} else { } else {
stopAdvertising() stopAdvertising()
@@ -147,16 +152,21 @@ final class BlePeripheralManager: NSObject, ObservableObject {
} }
private func startAdvertising() { private func startAdvertising() {
guard !isAdvertising else { return } guard !peripheral.isAdvertising else {
publish { self.isAdvertising = true }
return
}
peripheral.startAdvertising([ peripheral.startAdvertising([
CBAdvertisementDataLocalNameKey: "GlassNow", CBAdvertisementDataLocalNameKey: "GlassNow",
CBAdvertisementDataServiceUUIDsKey: [Self.serviceUUID], CBAdvertisementDataServiceUUIDsKey: [Self.serviceUUID],
]) ])
publish { self.isAdvertising = true }
} }
private func stopAdvertising() { private func stopAdvertising() {
guard isAdvertising else { return } guard peripheral.isAdvertising else {
publish { self.isAdvertising = false }
return
}
peripheral.stopAdvertising() peripheral.stopAdvertising()
publish { self.isAdvertising = false } publish { self.isAdvertising = false }
} }
@@ -313,6 +323,27 @@ final class BlePeripheralManager: NSObject, ObservableObject {
} }
extension BlePeripheralManager: CBPeripheralManagerDelegate { extension BlePeripheralManager: CBPeripheralManagerDelegate {
func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String: Any]) {
queue.async { [weak self] in
guard let self else { return }
if let services = dict[CBPeripheralManagerRestoredStateServicesKey] as? [CBService],
let restoredService = services.first(where: { $0.uuid == Self.serviceUUID }),
let restoredMutableService = restoredService as? CBMutableService,
let characteristics = restoredService.characteristics {
self.service = restoredMutableService
self.feedTx = characteristics.first(where: { $0.uuid == Self.feedTxUUID }) as? CBMutableCharacteristic
self.controlRx = characteristics.first(where: { $0.uuid == Self.controlRxUUID }) as? CBMutableCharacteristic
}
self.publish {
self.isAdvertising = peripheral.isAdvertising
}
self.applyAdvertisingPolicy()
}
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
queue.async { [weak self] in queue.async { [weak self] in
guard let self = self else { return } guard let self = self else { return }

View File

@@ -9,11 +9,13 @@ import SwiftUI
@main @main
struct irisApp: App { struct irisApp: App {
@Environment(\.scenePhase) private var scenePhase
@StateObject private var ble: BlePeripheralManager @StateObject private var ble: BlePeripheralManager
@StateObject private var orchestrator: ContextOrchestrator @StateObject private var orchestrator: ContextOrchestrator
init() { init() {
let bleManager = BlePeripheralManager() let bleManager = BlePeripheralManager()
bleManager.start()
_ble = StateObject(wrappedValue: bleManager) _ble = StateObject(wrappedValue: bleManager)
_orchestrator = StateObject(wrappedValue: ContextOrchestrator(ble: bleManager)) _orchestrator = StateObject(wrappedValue: ContextOrchestrator(ble: bleManager))
} }
@@ -23,6 +25,11 @@ struct irisApp: App {
ContentView() ContentView()
.environmentObject(ble) .environmentObject(ble)
.environmentObject(orchestrator) .environmentObject(orchestrator)
.onChange(of: scenePhase) { phase in
if phase == .active || phase == .background {
ble.start()
}
}
} }
} }
} }