From 214af08625720f8824051136a285e14e3f8ecd52 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Thu, 8 Jan 2026 22:27:11 +0000 Subject: [PATCH] fix(ble): restore peripheral state and keep advertising --- .../iris/Bluetooth/BlePeripheralManager.swift | 45 ++++++++++++++++--- IrisCompanion/iris/irisApp.swift | 7 +++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/IrisCompanion/iris/Bluetooth/BlePeripheralManager.swift b/IrisCompanion/iris/Bluetooth/BlePeripheralManager.swift index edd2f3a..5f4186e 100644 --- a/IrisCompanion/iris/Bluetooth/BlePeripheralManager.swift +++ b/IrisCompanion/iris/Bluetooth/BlePeripheralManager.swift @@ -16,6 +16,7 @@ final class BlePeripheralManager: NSObject, ObservableObject { static let serviceUUID = CBUUID(string: "A0B0C0D0-E0F0-4A0B-9C0D-0E0F1A2B3C4D") static let feedTxUUID = CBUUID(string: "A0B0C0D1-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 var advertisingEnabled: Bool = true @@ -31,7 +32,13 @@ final class BlePeripheralManager: NSObject, ObservableObject { @Published private(set) var lastDataAt: Date? = nil 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 feedTx: CBMutableCharacteristic? @@ -111,8 +118,6 @@ final class BlePeripheralManager: NSObject, ObservableObject { guard peripheral.state == .poweredOn else { return } guard service == nil else { return } - peripheral.removeAllServices() - let feedTx = CBMutableCharacteristic( type: Self.feedTxUUID, properties: [.notify, .read], @@ -139,7 +144,7 @@ final class BlePeripheralManager: NSObject, ObservableObject { stopAdvertising() return } - if advertisingEnabled, subscribedCentralIds.isEmpty { + if advertisingEnabled { startAdvertising() } else { stopAdvertising() @@ -147,16 +152,21 @@ final class BlePeripheralManager: NSObject, ObservableObject { } private func startAdvertising() { - guard !isAdvertising else { return } + guard !peripheral.isAdvertising else { + publish { self.isAdvertising = true } + return + } peripheral.startAdvertising([ CBAdvertisementDataLocalNameKey: "GlassNow", CBAdvertisementDataServiceUUIDsKey: [Self.serviceUUID], ]) - publish { self.isAdvertising = true } } private func stopAdvertising() { - guard isAdvertising else { return } + guard peripheral.isAdvertising else { + publish { self.isAdvertising = false } + return + } peripheral.stopAdvertising() publish { self.isAdvertising = false } } @@ -313,6 +323,27 @@ final class BlePeripheralManager: NSObject, ObservableObject { } 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) { queue.async { [weak self] in guard let self = self else { return } diff --git a/IrisCompanion/iris/irisApp.swift b/IrisCompanion/iris/irisApp.swift index d9719e7..e2ff870 100644 --- a/IrisCompanion/iris/irisApp.swift +++ b/IrisCompanion/iris/irisApp.swift @@ -9,11 +9,13 @@ import SwiftUI @main struct irisApp: App { + @Environment(\.scenePhase) private var scenePhase @StateObject private var ble: BlePeripheralManager @StateObject private var orchestrator: ContextOrchestrator init() { let bleManager = BlePeripheralManager() + bleManager.start() _ble = StateObject(wrappedValue: bleManager) _orchestrator = StateObject(wrappedValue: ContextOrchestrator(ble: bleManager)) } @@ -23,6 +25,11 @@ struct irisApp: App { ContentView() .environmentObject(ble) .environmentObject(orchestrator) + .onChange(of: scenePhase) { phase in + if phase == .active || phase == .background { + ble.start() + } + } } } }