From 058bf2d7ad95c0132515986bc6926a8958af3d53 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Wed, 5 Jan 2022 15:28:47 +0100 Subject: [PATCH 01/13] Testing changes for locked state --- OmniBLE/Bluetooth/BluetoothManager.swift | 30 ++++++----- OmniBLE/Bluetooth/Pair/LTKExchanger.swift | 12 ++--- .../Bluetooth/PeripheralManager+Omnipod.swift | 4 +- OmniBLE/Bluetooth/PeripheralManager.swift | 14 ++--- OmniBLE/PumpManager/OmnipodPumpManager.swift | 52 +++++++++---------- 5 files changed, 59 insertions(+), 53 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index 9b5bbd37e..912f9347a 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -151,11 +151,6 @@ class BluetoothManager: NSObject { return } -// if let peripheralID = peripheralIdentifier, let peripheral = manager.retrievePeripherals(withIdentifiers: [peripheralID]).first { -// log.debug("Re-connecting to known peripheral %{public}@", peripheral.identifier.uuidString) -// self.peripheral = peripheral -// self.manager.connect(peripheral) -// } else if let peripheral = manager.retrieveConnectedPeripherals(withServices: [ OmnipodServiceUUID.advertisement.cbUUID, OmnipodServiceUUID.service.cbUUID @@ -166,18 +161,27 @@ class BluetoothManager: NSObject { self.peripheral = peripheral self.manager.connect(peripheral) } else { - log.debug("Scanning for peripherals") - manager.scanForPeripherals(withServices: [OmnipodServiceUUID.advertisement.cbUUID], + // TEST: There might be a race condition where PeripheralManager considers the device already connected, although it isn't + // TODO: Investigate more + // Related https://github.com/randallknutson/OmniBLE/pull/10#pullrequestreview-837692407 + if let peripheralID = peripheralIdentifier, let peripheral = manager.retrievePeripherals(withIdentifiers: [peripheralID]).first { + log.debug("Re-connecting to known peripheral %{public}@", peripheral.identifier.uuidString) + self.peripheral = peripheral + self.manager.connect(peripheral) + } else { + log.debug("Scanning for peripherals") + manager.scanForPeripherals(withServices: [OmnipodServiceUUID.advertisement.cbUUID], options: nil - ) + ) + } } } /** - + Persistent connections don't seem to work with the transmitter shutoff: The OS won't re-wake the app unless it's scanning. - + The sleep gives the transmitter time to shut down, but keeps the app running. */ @@ -301,7 +305,7 @@ extension BluetoothManager: CBCentralManagerDelegate { extension BluetoothManager: PeripheralManagerDelegate { func peripheralManager(_ manager: PeripheralManager, didReadRSSI RSSI: NSNumber, error: Error?) { - + } func peripheralManagerDidUpdateName(_ manager: PeripheralManager) { @@ -313,9 +317,9 @@ extension BluetoothManager: PeripheralManagerDelegate { } func peripheralManager(_ manager: PeripheralManager, didUpdateNotificationStateFor characteristic: CBCharacteristic) { - + } - + func peripheralManager(_ manager: PeripheralManager, didUpdateValueFor characteristic: CBCharacteristic) { } diff --git a/OmniBLE/Bluetooth/Pair/LTKExchanger.swift b/OmniBLE/Bluetooth/Pair/LTKExchanger.swift index 5a771810c..600b3dd82 100644 --- a/OmniBLE/Bluetooth/Pair/LTKExchanger.swift +++ b/OmniBLE/Bluetooth/Pair/LTKExchanger.swift @@ -56,10 +56,10 @@ class LTKExchanger { log.debug("Reading sps1") let podSps1 = try manager.readMessage(true) - guard let podSps1 = podSps1 else { + guard let _ = podSps1 else { throw BluetoothErrors.PairingException("Could not read SPS1") } - try processSps1FromPod(podSps1) + try processSps1FromPod(podSps1!) // now we have all the data to generate: confPod, confPdm, ltk and noncePrefix log.debug("Sending sps2") @@ -74,10 +74,10 @@ class LTKExchanger { try throwOnSendError(sps2.message, LTKExchanger.SPS2) let podSps2 = try manager.readMessage() - guard let podSps2 = podSps2 else { + guard let _ = podSps2 else { throw BluetoothErrors.PairingException("Could not read SPS2") } - try validatePodSps2(podSps2) + try validatePodSps2(podSps2!) // No exception throwing after this point. It is possible that the pod saved the LTK seq += 1 @@ -95,10 +95,10 @@ class LTKExchanger { } let p0 = try manager.readMessage() - guard let p0 = p0 else { + guard let _ = p0 else { throw BluetoothErrors.PairingException("Could not read P0") } - try validateP0(p0) + try validateP0(p0!) guard keyExchange.ltk.count == 16 else { throw BluetoothErrors.InvalidLTKKey("Invalid Key, got \(String(data: keyExchange.ltk, encoding: .utf8) ?? "")") diff --git a/OmniBLE/Bluetooth/PeripheralManager+Omnipod.swift b/OmniBLE/Bluetooth/PeripheralManager+Omnipod.swift index 55e683191..afb0e771f 100644 --- a/OmniBLE/Bluetooth/PeripheralManager+Omnipod.swift +++ b/OmniBLE/Bluetooth/PeripheralManager+Omnipod.swift @@ -89,11 +89,11 @@ extension PeripheralManager { var expected: UInt8 = 0 let firstPacket = try readData(sequence: expected, timeout: 5) - guard let firstPacket = firstPacket else { + guard let _ = firstPacket else { return nil } - let joiner = try PayloadJoiner(firstPacket: firstPacket) + let joiner = try PayloadJoiner(firstPacket: firstPacket!) for _ in 1...joiner.fullFragments { expected += 1 diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index be61e5bb3..a073129ae 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -32,13 +32,13 @@ class PeripheralManager: NSObject { } } } - + var dataQueue: [Data] = [] var dataEvent: (() -> Void)? var cmdQueue: [Data] = [] var cmdEvent: (() -> Void)? let queueLock = NSCondition() - + /// The dispatch queue used to serialize operations on the peripheral let queue = DispatchQueue(label: "com.loopkit.PeripheralManager.queue", qos: .unspecified) @@ -128,7 +128,7 @@ extension PeripheralManager { do { try self.applyConfiguration() self.needsConfiguration = false - + if let delegate = self.delegate { try delegate.completeConfiguration(for: self) self.log.default("Delegate configuration notified") @@ -195,6 +195,7 @@ extension PeripheralManager { // Prelude dispatchPrecondition(condition: .onQueue(queue)) guard central?.state == .poweredOn && peripheral.state == .connected else { + self.log("runCommand guard failed - bluetooth not running or peripheral not connected: peripheral %@", peripheral) throw PeripheralManagerError.notReady } @@ -225,6 +226,7 @@ extension PeripheralManager { } guard signaled else { + self.log("runCommand lock timeout reached - not signalled") throw PeripheralManagerError.notReady } @@ -416,13 +418,13 @@ extension PeripheralManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { commandLock.lock() - + var notifyDelegate = false if let macro = configuration.valueUpdateMacros[characteristic.uuid] { macro(self) } - + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .valueUpdate(characteristic: characteristic, matching: let matching) = condition { return matching?(characteristic.value) ?? true @@ -486,7 +488,7 @@ extension CBPeripheral { return service.characteristics?.itemWithUUID(OmnipodCharacteristicUUID.command.cbUUID) } - + func getDataCharacteristic() -> CBCharacteristic? { guard let service = services?.itemWithUUID(OmnipodServiceUUID.service.cbUUID) else { return nil diff --git a/OmniBLE/PumpManager/OmnipodPumpManager.swift b/OmniBLE/PumpManager/OmnipodPumpManager.swift index 771d95822..f396db259 100644 --- a/OmniBLE/PumpManager/OmnipodPumpManager.swift +++ b/OmniBLE/PumpManager/OmnipodPumpManager.swift @@ -40,7 +40,7 @@ extension OmnipodPumpManagerError: LocalizedError { return LocalizedString("Pod is not in a state ready for cannula insertion.", comment: "Error message when cannula insertion fails because the pod is in an unexpected state") } } - + public var failureReason: String? { switch self { case .noPodPaired: @@ -51,7 +51,7 @@ extension OmnipodPumpManagerError: LocalizedError { return nil } } - + public var recoverySuggestion: String? { switch self { case .noPodPaired: @@ -69,7 +69,7 @@ public class OmnipodPumpManager: DeviceManager { public init(state: OmnipodPumpManagerState) { self.lockedState = Locked(state) self.omnipod = Omnipod(state.podState) - + self.omnipod.delegate = self self.podComms.delegate = self @@ -93,7 +93,7 @@ public class OmnipodPumpManager: DeviceManager { omnipod.podComms = newValue } } - + public let omnipod: Omnipod // public for easier diagnostic display private let podStateObservers = WeakSynchronizedSet() @@ -163,7 +163,7 @@ public class OmnipodPumpManager: DeviceManager { return returnType } - + private let lockedState: Locked private let statusObservers = WeakSynchronizedSet() @@ -177,7 +177,7 @@ public class OmnipodPumpManager: DeviceManager { observer.pumpManager(self, didUpdate: status, oldStatus: oldStatus) } } - + private func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) { var podAddress = "noPod" if let podState = self.state.podState { @@ -190,15 +190,15 @@ public class OmnipodPumpManager: DeviceManager { private let pumpDelegate = WeakSynchronizedDelegate() - public let log = OSLog(category: "OmnipodPumpManager") + public let log = OSLog(category: "OmniBLEPumpManager") private var lastLoopRecommendation: Date? - + // MARK: - CustomDebugStringConvertible public var debugDescription: String { let lines = [ - "## OmnipodPumpManager", + "## OmniBLEPumpManager", "podComms: \(String(reflecting: podComms))", "state: \(String(reflecting: state))", "status: \(String(describing: status))", @@ -211,11 +211,11 @@ public class OmnipodPumpManager: DeviceManager { extension OmnipodPumpManager { // MARK: - PodStateObserver - + public func addPodStateObserver(_ observer: PodStateObserver, queue: DispatchQueue) { podStateObservers.insert(observer, queue: queue) } - + public func removePodStateObserver(_ observer: PodStateObserver) { podStateObservers.removeElement(observer) } @@ -425,7 +425,7 @@ extension OmnipodPumpManager { } } - + // MARK: - Pairing // Called on the main thread @@ -469,7 +469,7 @@ extension OmnipodPumpManager { if needsPairing { self.log.default("Pairing pod before priming") - + // Create random address with 20 bits to match PDM, could easily use 24 bits instead if self.state.pairingAttemptAddress == nil { self.lockedState.mutate { (state) in @@ -478,13 +478,13 @@ extension OmnipodPumpManager { } self.podComms.pairAndSetupPod(address: self.state.pairingAttemptAddress!, timeZone: .currentFixed, messageLogger: self) { (result) in - + if case .success = result { self.lockedState.mutate { (state) in state.pairingAttemptAddress = nil } } - + // Calls completion primeSession(result) } @@ -597,7 +597,7 @@ extension OmnipodPumpManager { completion?(.failure(PodCommsError.unfinalizedBolus)) return } - + podComms.runSession(withName: "Get pod status") { (result) in do { switch result { @@ -650,7 +650,7 @@ extension OmnipodPumpManager { } public func setTime(completion: @escaping (Error?) -> Void) { - + guard state.hasActivePod else { completion(OmnipodPumpManagerError.noPodPaired) return @@ -861,7 +861,7 @@ extension OmnipodPumpManager { let tempBasalCompletionBeep = self.confirmationBeeps && tempBasalConfirmationBeeps let bolusCompletionBeep = self.confirmationBeeps let result = session.beepConfig(beepConfigType: .bipBeepBipBeepBipBeepBipBeep, basalCompletionBeep: basalCompletionBeep, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep) - + switch result { case .success: completion(nil) @@ -1124,7 +1124,7 @@ extension OmnipodPumpManager: PumpManager { public func setMustProvideBLEHeartbeat(_ mustProvideBLEHeartbeat: Bool) { // do nothing here for Dash } - + // Called only from pumpDelegate notify block private func recommendLoopIfNeeded(_ delegate: PumpManagerDelegate?) { if lastLoopRecommendation == nil || lastLoopRecommendation!.timeIntervalSinceNow < .minutes(-4.5) { @@ -1142,7 +1142,7 @@ extension OmnipodPumpManager: PumpManager { return state.isPumpDataStale } - + switch shouldFetchStatus { case .none: return // No active pod @@ -1187,7 +1187,7 @@ extension OmnipodPumpManager: PumpManager { completion(.failure(SetBolusError.certain(error))) return } - + defer { self.setState({ (state) in state.bolusEngageState = .stable @@ -1271,10 +1271,10 @@ extension OmnipodPumpManager: PumpManager { self.setState({ (state) in state.bolusEngageState = .disengaging }) - + if let bolus = self.state.podState?.unfinalizedBolus, !bolus.isFinished, bolus.scheduledCertainty == .uncertain { let status = try session.getStatus() - + if !status.deliveryStatus.bolusing { completion(.success(nil)) return @@ -1471,11 +1471,11 @@ extension OmnipodPumpManager: MessageLogger { extension OmnipodPumpManager: OmnipodDelegate { public func omnipod(_ omnipod: Omnipod) { - + } - + public func omnipod(_ omnipod: Omnipod, didError error: Error) { - + } } From 697308f424cf9024c83f558edcdfc0d2699e1c15 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Wed, 5 Jan 2022 15:57:27 +0100 Subject: [PATCH 02/13] Update PeripheralManager.swift --- OmniBLE/Bluetooth/PeripheralManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index a073129ae..ebcf776c4 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -195,7 +195,7 @@ extension PeripheralManager { // Prelude dispatchPrecondition(condition: .onQueue(queue)) guard central?.state == .poweredOn && peripheral.state == .connected else { - self.log("runCommand guard failed - bluetooth not running or peripheral not connected: peripheral %@", peripheral) + self.log.info("runCommand guard failed - bluetooth not running or peripheral not connected: peripheral %@", peripheral) throw PeripheralManagerError.notReady } @@ -226,7 +226,7 @@ extension PeripheralManager { } guard signaled else { - self.log("runCommand lock timeout reached - not signalled") + self.log.info("runCommand lock timeout reached - not signalled") throw PeripheralManagerError.notReady } From 02f8bbd8e9e6992c1fffe11cd09f3c732016453a Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Wed, 5 Jan 2022 23:18:05 +0100 Subject: [PATCH 03/13] More updates --- OmniBLE/Bluetooth/BluetoothManager.swift | 8 ++++++++ OmniBLE/Bluetooth/PeripheralManager.swift | 11 +++++++++++ OmniBLE/PumpManager/Omnipod.swift | 5 ++++- OmniBLE/PumpManager/OmnipodPumpManager.swift | 9 +++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index 912f9347a..59038c4c1 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -235,12 +235,14 @@ extension BluetoothManager: CBCentralManagerDelegate { func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { dispatchPrecondition(condition: .onQueue(managerQueue)) + log.info("%{public}@: %{public}@", #function, dict) if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] { for peripheral in peripherals { if delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral, advertisementData: nil) { log.default("Restoring peripheral from state: %{public}@", peripheral.identifier.uuidString) self.peripheral = peripheral + // TODO: Maybe connect to peripheral if its state is disconnected? } } } @@ -316,6 +318,12 @@ extension BluetoothManager: PeripheralManagerDelegate { self.delegate?.bluetoothManager(self, didCompleteConfiguration: manager) } + // throws? + func reconnectLatestPeripheral() { + scanForPeripheral() + } + + func peripheralManager(_ manager: PeripheralManager, didUpdateNotificationStateFor characteristic: CBCharacteristic) { } diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index ebcf776c4..d0581b1cc 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -113,6 +113,8 @@ protocol PeripheralManagerDelegate: AnyObject { func peripheralManagerDidUpdateName(_ manager: PeripheralManager) func completeConfiguration(for manager: PeripheralManager) throws + + func reconnectLatestPeripheral() } @@ -124,6 +126,15 @@ extension PeripheralManager { self.log.error("Configured peripheral has no services. Reconfiguring…") } + // TODO: Reconnect the peripheral + if self.peripheral.state == .disconnected { + self.log.info("Peripheral is not connected - connecting...") + // TODO: This might throw... Also - thread-safety? + if let delegate = self.delegate { + delegate.reconnectLatestPeripheral() + } + } + if self.needsConfiguration || self.peripheral.services == nil { do { try self.applyConfiguration() diff --git a/OmniBLE/PumpManager/Omnipod.swift b/OmniBLE/PumpManager/Omnipod.swift index ae95bf809..129dffc1b 100644 --- a/OmniBLE/PumpManager/Omnipod.swift +++ b/OmniBLE/PumpManager/Omnipod.swift @@ -99,7 +99,7 @@ public class Omnipod { } } - public func stopScanning() { + public func disconnect() { bluetoothManager.disconnect() } @@ -122,8 +122,11 @@ public class Omnipod { } set { bluetoothManager.stayConnected = newValue + + self.log.info("stayConnected %@", newValue) if newValue { + self.log.info("stayConnected triggering scanForPeripheral") bluetoothManager.scanForPeripheral() } } diff --git a/OmniBLE/PumpManager/OmnipodPumpManager.swift b/OmniBLE/PumpManager/OmnipodPumpManager.swift index f396db259..9f6051de8 100644 --- a/OmniBLE/PumpManager/OmnipodPumpManager.swift +++ b/OmniBLE/PumpManager/OmnipodPumpManager.swift @@ -190,7 +190,7 @@ public class OmnipodPumpManager: DeviceManager { private let pumpDelegate = WeakSynchronizedDelegate() - public let log = OSLog(category: "OmniBLEPumpManager") + public let log = OSLog(category: "OmnipodPumpManagerBLE") private var lastLoopRecommendation: Date? @@ -389,6 +389,7 @@ extension OmnipodPumpManager { // Does not support concurrent callers. Not thread-safe. private func forgetPod(completion: @escaping () -> Void) { + omnipod.disconnect() let resetPodState = { (_ state: inout OmnipodPumpManagerState) in self.podComms = PodComms(podState: nil, lotNo: nil, lotSeq: nil) self.podComms.delegate = self @@ -598,6 +599,10 @@ extension OmnipodPumpManager { return } + // scanForPeripheral->managerQueue_scanForPeripheral has a guard so it only does actual work/connecting if current peripheral state is not connected + // so we *should* be fine if it is called in multiple places + omnipod.stayConnected = true + podComms.runSession(withName: "Get pod status") { (result) in do { switch result { @@ -1169,7 +1174,7 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { + public func enactBolus(units: Double, at startDate: Date, automatic: Bool, willRequest: @escaping (DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { guard self.hasActivePod else { completion(.failure(SetBolusError.certain(OmnipodPumpManagerError.noPodPaired))) return From 3a644998d8d12323f93ffe9588f459ea228b1148 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Thu, 6 Jan 2022 01:03:54 +0100 Subject: [PATCH 04/13] Try fixing using semaphore --- OmniBLE/Bluetooth/BluetoothManager.swift | 38 +++++++++++++++++++- OmniBLE/Bluetooth/PeripheralManager.swift | 17 ++++++++- OmniBLE/PumpManager/Omnipod.swift | 33 ++++++++--------- OmniBLE/PumpManager/OmnipodPumpManager.swift | 4 --- 4 files changed, 68 insertions(+), 24 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index 59038c4c1..f1d4734ff 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -58,6 +58,8 @@ class BluetoothManager: NSObject { private let log = OSLog(category: "BluetoothManager") + private let connectionSemaphore = DispatchSemaphore(value: 0) + /// Isolated to `managerQueue` private var manager: CBCentralManager! = nil @@ -139,6 +141,31 @@ class BluetoothManager: NSObject { } } + func reconnectPeripheral() { + dispatchPrecondition(condition: .notOnQueue(managerQueue)) + + managerQueue.sync { + self.managerQueue_scanForPeripheral() + // connectionSemaphore.wait() + } + + log.info("Waiting for peripheral reconnect semaphore") + connectionSemaphore.wait() + log.info("Peripheral reconnect semaphore finished") + } + + func waitForPeripheralConnection() { + dispatchPrecondition(condition: .notOnQueue(managerQueue)) + + // managerQueue.sync { + // connectionSemaphore.wait() + // } + + log.info("Waiting for peripheral reconnect semaphore") + connectionSemaphore.wait() + log.info("Peripheral reconnect semaphore finished") + } + private func managerQueue_scanForPeripheral() { dispatchPrecondition(condition: .onQueue(managerQueue)) @@ -272,6 +299,8 @@ extension BluetoothManager: CBCentralManagerDelegate { if case .poweredOn = manager.state, case .connected = peripheral.state, let peripheralManager = peripheralManager { self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: nil) } + + connectionSemaphore.signal() } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { @@ -301,6 +330,9 @@ extension BluetoothManager: CBCentralManagerDelegate { if stayConnected { scanAfterDelay() } + + // Don't freeze the thread + connectionSemaphore.signal() } } @@ -320,7 +352,11 @@ extension BluetoothManager: PeripheralManagerDelegate { // throws? func reconnectLatestPeripheral() { - scanForPeripheral() + reconnectPeripheral() + } + + func waitForPeripheral() { + waitForPeripheral() } diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index d0581b1cc..0621a0a2c 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -113,8 +113,10 @@ protocol PeripheralManagerDelegate: AnyObject { func peripheralManagerDidUpdateName(_ manager: PeripheralManager) func completeConfiguration(for manager: PeripheralManager) throws - + func reconnectLatestPeripheral() + + func waitForPeripheral() } @@ -126,6 +128,10 @@ extension PeripheralManager { self.log.error("Configured peripheral has no services. Reconfiguring…") } + if self.delegate == nil { + self.log.error("PeripheralManager delegate is nil") + } + // TODO: Reconnect the peripheral if self.peripheral.state == .disconnected { self.log.info("Peripheral is not connected - connecting...") @@ -135,6 +141,15 @@ extension PeripheralManager { } } + // TODO: Reconnect the peripheral + if self.peripheral.state == .connecting { + self.log.info("Peripheral is still connecting - waiting...") + // TODO: This might throw... Also - thread-safety? + if let delegate = self.delegate { + delegate.waitForPeripheral() + } + } + if self.needsConfiguration || self.peripheral.services == nil { do { try self.applyConfiguration() diff --git a/OmniBLE/PumpManager/Omnipod.swift b/OmniBLE/PumpManager/Omnipod.swift index 129dffc1b..7606b531f 100644 --- a/OmniBLE/PumpManager/Omnipod.swift +++ b/OmniBLE/PumpManager/Omnipod.swift @@ -23,9 +23,9 @@ public class Omnipod { var sequenceNo: UInt32? var lotNo: UInt64? var podId: UInt32? = nil - + private var pairNew = false - + private var serviceUUIDs: [CBUUID] private let log = OSLog(category: "Omnipod") @@ -33,16 +33,16 @@ public class Omnipod { // private let manager: PeripheralManager private let bluetoothManager = BluetoothManager() - + private let delegateQueue = DispatchQueue(label: "com.randallknutson.OmnipodKit.delegateQueue", qos: .unspecified) private var sessionQueueOperationCountObserver: NSKeyValueObservation! /// Serializes access to device state private var lock = os_unfair_lock() - + private let connectLock = NSCondition() - + /// The queue used to serialize sessions and observe when they've drained private let sessionQueue: OperationQueue = { let queue = OperationQueue() @@ -60,7 +60,7 @@ public class Omnipod { self.podComms = PodComms(podState: state, lotNo: lotNo, lotSeq: sequenceNo) self.bluetoothManager.delegate = self } - + // Only valid to access on the session serial queue private var state: PodState? { didSet { @@ -70,9 +70,9 @@ public class Omnipod { } } } - + public weak var delegate: OmnipodDelegate? - + public var podComms: PodComms func connectNew() throws { @@ -87,7 +87,7 @@ public class Omnipod { bluetoothManager.scanForPeripheral() let signaled = connectLock.wait(until: Date(timeIntervalSinceNow: 10)) - + guard signaled else { throw PeripheralManagerError.notReady } @@ -122,16 +122,13 @@ public class Omnipod { } set { bluetoothManager.stayConnected = newValue - - self.log.info("stayConnected %@", newValue) if newValue { - self.log.info("stayConnected triggering scanForPeripheral") bluetoothManager.scanForPeripheral() } } } - + } // MARK: - Reading pump data @@ -145,7 +142,7 @@ extension Omnipod { lotNo = parseLotNo() sequenceNo = parseSeqNo() } - + private func validateServiceUUIDs() throws { // For some reason the pod simulator doesn't have two values. if (serviceUUIDs.count == 7) { @@ -169,11 +166,11 @@ extension Omnipod { ) } } - + private func parsePodId() { podId = UInt32(serviceUUIDs[3].uuidString + serviceUUIDs[4].uuidString, radix: 16) } - + private func parseLotNo() -> UInt64? { print(serviceUUIDs[5].uuidString + serviceUUIDs[6].uuidString) let lotNo: String = serviceUUIDs[5].uuidString + serviceUUIDs[6].uuidString + serviceUUIDs[7].uuidString @@ -211,7 +208,7 @@ extension Omnipod: BluetoothManagerDelegate { podComms.manager = peripheralManager } } - + func bluetoothManager(_ manager: BluetoothManager, shouldConnectPeripheral peripheral: CBPeripheral, advertisementData: [String : Any]?) -> Bool { do { if (advertisementData == nil) { @@ -230,7 +227,7 @@ extension Omnipod: BluetoothManagerDelegate { return false } } - + func bluetoothManager(_ manager: BluetoothManager, didCompleteConfiguration peripheralManager: PeripheralManager) { peripheralManager.runSession(withName: "Complete pod configuration") { [weak self] in do { diff --git a/OmniBLE/PumpManager/OmnipodPumpManager.swift b/OmniBLE/PumpManager/OmnipodPumpManager.swift index 9f6051de8..425c429f5 100644 --- a/OmniBLE/PumpManager/OmnipodPumpManager.swift +++ b/OmniBLE/PumpManager/OmnipodPumpManager.swift @@ -599,10 +599,6 @@ extension OmnipodPumpManager { return } - // scanForPeripheral->managerQueue_scanForPeripheral has a guard so it only does actual work/connecting if current peripheral state is not connected - // so we *should* be fine if it is called in multiple places - omnipod.stayConnected = true - podComms.runSession(withName: "Get pod status") { (result) in do { switch result { From ce00dd538e7fd265d62128ff6bb2b66a2e4408b1 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Thu, 6 Jan 2022 01:24:22 +0100 Subject: [PATCH 05/13] Fix typo --- OmniBLE/Bluetooth/BluetoothManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index f1d4734ff..2f3c07a6c 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -161,7 +161,7 @@ class BluetoothManager: NSObject { // connectionSemaphore.wait() // } - log.info("Waiting for peripheral reconnect semaphore") + log.info("Waiting for peripheral reconnect semaphore") connectionSemaphore.wait() log.info("Peripheral reconnect semaphore finished") } @@ -356,7 +356,7 @@ extension BluetoothManager: PeripheralManagerDelegate { } func waitForPeripheral() { - waitForPeripheral() + waitForPeripheralConnection() } From 8ea36c04c6e0d6cd8ebfd9cef6b4a00b65d5b48d Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Thu, 6 Jan 2022 17:06:27 +0100 Subject: [PATCH 06/13] Testing --- OmniBLE/Bluetooth/BluetoothManager.swift | 16 +++++++++++----- OmniBLE/Bluetooth/PeripheralManager.swift | 14 +++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index 2f3c07a6c..f92e7fe9f 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -142,7 +142,10 @@ class BluetoothManager: NSObject { } func reconnectPeripheral() { - dispatchPrecondition(condition: .notOnQueue(managerQueue)) + dispatchPrecondition(condition: .notOnQueue(managerQueue)) + + // Reset the semaphore because of the implementation + connectionSemaphore = DispatchSemaphore(value: 0) managerQueue.sync { self.managerQueue_scanForPeripheral() @@ -155,11 +158,14 @@ class BluetoothManager: NSObject { } func waitForPeripheralConnection() { - dispatchPrecondition(condition: .notOnQueue(managerQueue)) + dispatchPrecondition(condition: .notOnQueue(managerQueue)) - // managerQueue.sync { - // connectionSemaphore.wait() - // } + // Reset the semaphore because of the implementation + connectionSemaphore = DispatchSemaphore(value: 0) + + // managerQueue.sync { + // connectionSemaphore.wait() + // } log.info("Waiting for peripheral reconnect semaphore") connectionSemaphore.wait() diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index 0621a0a2c..25e33225f 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -133,20 +133,20 @@ extension PeripheralManager { } // TODO: Reconnect the peripheral - if self.peripheral.state == .disconnected { - self.log.info("Peripheral is not connected - connecting...") + if self.peripheral.state == .connecting { + self.log.info("Peripheral is still connecting - waiting...") // TODO: This might throw... Also - thread-safety? if let delegate = self.delegate { - delegate.reconnectLatestPeripheral() + delegate.waitForPeripheral() } } - // TODO: Reconnect the peripheral - if self.peripheral.state == .connecting { - self.log.info("Peripheral is still connecting - waiting...") + // TODO: Reconnect the peripheral + if self.peripheral.state == .disconnected { + self.log.info("Peripheral is not connected - connecting...") // TODO: This might throw... Also - thread-safety? if let delegate = self.delegate { - delegate.waitForPeripheral() + delegate.reconnectLatestPeripheral() } } From 267a6ced3014db6a84bde6348867c47290488bd4 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Thu, 6 Jan 2022 20:15:04 +0100 Subject: [PATCH 07/13] Clean up semaphore logic --- OmniBLE/Bluetooth/BluetoothManager.swift | 55 ++++++++++++----------- OmniBLE/Bluetooth/PeripheralManager.swift | 16 +------ 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index f92e7fe9f..aeddddd99 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -60,6 +60,8 @@ class BluetoothManager: NSObject { private let connectionSemaphore = DispatchSemaphore(value: 0) + private let concurrentReconnectSemaphore = DispatchSemaphore(value: 1) + /// Isolated to `managerQueue` private var manager: CBCentralManager! = nil @@ -144,32 +146,34 @@ class BluetoothManager: NSObject { func reconnectPeripheral() { dispatchPrecondition(condition: .notOnQueue(managerQueue)) - // Reset the semaphore because of the implementation - connectionSemaphore = DispatchSemaphore(value: 0) + // Make sure only one reconnect loop is happening concurrently + log.debug("reconnectPeripheral concurrency semaphore check") + concurrentReconnectSemaphore.wait() + log.debug("reconnectPeripheral concurrency semaphore check - is free, continuing") - managerQueue.sync { - self.managerQueue_scanForPeripheral() - // connectionSemaphore.wait() + let currentState = peripheral?.state ?? .disconnected + guard currentState != .connected else { + return } - log.info("Waiting for peripheral reconnect semaphore") - connectionSemaphore.wait() - log.info("Peripheral reconnect semaphore finished") - } - - func waitForPeripheralConnection() { - dispatchPrecondition(condition: .notOnQueue(managerQueue)) - - // Reset the semaphore because of the implementation + // Reset the peripheral readiness semaphore before starting a new run connectionSemaphore = DispatchSemaphore(value: 0) - // managerQueue.sync { - // connectionSemaphore.wait() - // } + // Possible states are disconnected, disconnecting, connected and connecting + // We guard against connected earlier and in case of connecting we only need to wait for the semaphore + if currentState == .disconnected || currentState == .disconnecting { + managerQueue.sync { + self.managerQueue_scanForPeripheral() + } + } - log.info("Waiting for peripheral reconnect semaphore") + log.debug("Waiting for peripheral reconnect semaphore to be signalled (peripheral is connected)") connectionSemaphore.wait() - log.info("Peripheral reconnect semaphore finished") + log.debug("Peripheral reconnect semaphore finished") + + // Release reconnect loop for other callers + log.debug("reconnectPeripheral concurrency semaphore signaling") + concurrentReconnectSemaphore.signal() } private func managerQueue_scanForPeripheral() { @@ -184,6 +188,10 @@ class BluetoothManager: NSObject { return } + guard currentState != .connecting else { + return + } + if let peripheral = manager.retrieveConnectedPeripherals(withServices: [ OmnipodServiceUUID.advertisement.cbUUID, OmnipodServiceUUID.service.cbUUID @@ -306,6 +314,7 @@ extension BluetoothManager: CBCentralManagerDelegate { self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: nil) } + log.debug("Signaling connectionSemaphore - didConnect") connectionSemaphore.signal() } @@ -337,7 +346,8 @@ extension BluetoothManager: CBCentralManagerDelegate { scanAfterDelay() } - // Don't freeze the thread + // Don't freeze the thread if connection fails + log.debug("Signaling connectionSemaphore - didFailToConnect") connectionSemaphore.signal() } } @@ -361,11 +371,6 @@ extension BluetoothManager: PeripheralManagerDelegate { reconnectPeripheral() } - func waitForPeripheral() { - waitForPeripheralConnection() - } - - func peripheralManager(_ manager: PeripheralManager, didUpdateNotificationStateFor characteristic: CBCharacteristic) { } diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index 25e33225f..0ec9e2250 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -115,8 +115,6 @@ protocol PeripheralManagerDelegate: AnyObject { func completeConfiguration(for manager: PeripheralManager) throws func reconnectLatestPeripheral() - - func waitForPeripheral() } @@ -132,18 +130,8 @@ extension PeripheralManager { self.log.error("PeripheralManager delegate is nil") } - // TODO: Reconnect the peripheral - if self.peripheral.state == .connecting { - self.log.info("Peripheral is still connecting - waiting...") - // TODO: This might throw... Also - thread-safety? - if let delegate = self.delegate { - delegate.waitForPeripheral() - } - } - - // TODO: Reconnect the peripheral - if self.peripheral.state == .disconnected { - self.log.info("Peripheral is not connected - connecting...") + if self.peripheral.state == .disconnected || self.peripheral.state == .connecting { + self.log.info("Peripheral is not connected - triggering reconnectLatestPeripheral...") // TODO: This might throw... Also - thread-safety? if let delegate = self.delegate { delegate.reconnectLatestPeripheral() From f7390863bf32ed3b5c6f9f761641f4cdae97e929 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Thu, 6 Jan 2022 23:13:10 +0100 Subject: [PATCH 08/13] Update BluetoothManager.swift --- OmniBLE/Bluetooth/BluetoothManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index aeddddd99..f0421f04f 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -58,7 +58,7 @@ class BluetoothManager: NSObject { private let log = OSLog(category: "BluetoothManager") - private let connectionSemaphore = DispatchSemaphore(value: 0) + private var connectionSemaphore = DispatchSemaphore(value: 0) private let concurrentReconnectSemaphore = DispatchSemaphore(value: 1) From baafc0c3b94f4edca56fe1da5220376ff7730cda Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Fri, 7 Jan 2022 00:52:37 +0100 Subject: [PATCH 09/13] Add more verbosity to logging --- OmniBLE/Bluetooth/BluetoothManager.swift | 16 +++++++++++++++- OmniBLE/Bluetooth/PeripheralManager.swift | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index f0421f04f..1956cde19 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -151,9 +151,17 @@ class BluetoothManager: NSObject { concurrentReconnectSemaphore.wait() log.debug("reconnectPeripheral concurrency semaphore check - is free, continuing") + guard manager.state == .poweredOn else { + log.debug("reconnectPeripheral error - manager.state != .poweredOn") + return + } + let currentState = peripheral?.state ?? .disconnected guard currentState != .connected else { - return + if let _ = peripheral { + log.debug("reconnectPeripheral error - peripheral is already connected %@", peripheral!) + } + return } // Reset the peripheral readiness semaphore before starting a new run @@ -162,8 +170,13 @@ class BluetoothManager: NSObject { // Possible states are disconnected, disconnecting, connected and connecting // We guard against connected earlier and in case of connecting we only need to wait for the semaphore if currentState == .disconnected || currentState == .disconnecting { + if let _ = peripheral { + log.debug("reconnectPeripheral running managerQueue_scanForPeripheral for peripheral %@", peripheral!) + } managerQueue.sync { + log.debug("reconnectPeripheral - in managerQueue.sync") self.managerQueue_scanForPeripheral() + log.debug("reconnectPeripheral - finished managerQueue.sync") } } @@ -296,6 +309,7 @@ extension BluetoothManager: CBCentralManagerDelegate { if delegate == nil || delegate!.bluetoothManager(self, shouldConnectPeripheral: peripheral, advertisementData: advertisementData) { self.peripheral = peripheral + log.debug("connecting to peripheral %@", peripheral) central.connect(peripheral, options: nil) } } diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index 0ec9e2250..a1c3683d6 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -476,8 +476,10 @@ extension PeripheralManager: CBPeripheralDelegate { extension PeripheralManager: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { + self.log.debug("PeripheralManager - centralManagerDidUpdateState: %@", central) switch central.state { case .poweredOn: + self.log.debug("PeripheralManager - centralManagerDidUpdateState - running assertConfiguration") assertConfiguration() default: break @@ -485,8 +487,10 @@ extension PeripheralManager: CBCentralManagerDelegate { } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + self.log.debug("PeripheralManager - didConnect: %@", peripheral) switch peripheral.state { case .connected: + self.log.debug("PeripheralManager - didConnect - running assertConfiguration") assertConfiguration() default: break From be2126945528bda1108190691fd36d6a30c2c9c1 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Sat, 8 Jan 2022 00:08:40 +0100 Subject: [PATCH 10/13] Change how reconnection is done, remove semaphore --- OmniBLE/Bluetooth/BluetoothManager.swift | 20 ++----- OmniBLE/Bluetooth/PeripheralManager.swift | 63 +++++++++++++++++++++-- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index 1956cde19..47cf3dfa9 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -58,8 +58,6 @@ class BluetoothManager: NSObject { private let log = OSLog(category: "BluetoothManager") - private var connectionSemaphore = DispatchSemaphore(value: 0) - private let concurrentReconnectSemaphore = DispatchSemaphore(value: 1) /// Isolated to `managerQueue` @@ -153,6 +151,7 @@ class BluetoothManager: NSObject { guard manager.state == .poweredOn else { log.debug("reconnectPeripheral error - manager.state != .poweredOn") + concurrentReconnectSemaphore.signal() return } @@ -161,12 +160,10 @@ class BluetoothManager: NSObject { if let _ = peripheral { log.debug("reconnectPeripheral error - peripheral is already connected %@", peripheral!) } + concurrentReconnectSemaphore.signal() return } - // Reset the peripheral readiness semaphore before starting a new run - connectionSemaphore = DispatchSemaphore(value: 0) - // Possible states are disconnected, disconnecting, connected and connecting // We guard against connected earlier and in case of connecting we only need to wait for the semaphore if currentState == .disconnected || currentState == .disconnecting { @@ -180,10 +177,6 @@ class BluetoothManager: NSObject { } } - log.debug("Waiting for peripheral reconnect semaphore to be signalled (peripheral is connected)") - connectionSemaphore.wait() - log.debug("Peripheral reconnect semaphore finished") - // Release reconnect loop for other callers log.debug("reconnectPeripheral concurrency semaphore signaling") concurrentReconnectSemaphore.signal() @@ -327,9 +320,6 @@ extension BluetoothManager: CBCentralManagerDelegate { if case .poweredOn = manager.state, case .connected = peripheral.state, let peripheralManager = peripheralManager { self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: nil) } - - log.debug("Signaling connectionSemaphore - didConnect") - connectionSemaphore.signal() } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { @@ -351,6 +341,8 @@ extension BluetoothManager: CBCentralManagerDelegate { func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { dispatchPrecondition(condition: .onQueue(managerQueue)) + peripheralManager?.centralManager(central, didFailToConnect: peripheral, error: error) + log.error("%{public}@: %{public}@", #function, String(describing: error)) if let error = error, let peripheralManager = peripheralManager { self.delegate?.bluetoothManager(self, peripheralManager: peripheralManager, isReadyWithError: error) @@ -359,10 +351,6 @@ extension BluetoothManager: CBCentralManagerDelegate { if stayConnected { scanAfterDelay() } - - // Don't freeze the thread if connection fails - log.debug("Signaling connectionSemaphore - didFailToConnect") - connectionSemaphore.signal() } } diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index a1c3683d6..a362c3a29 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -83,7 +83,8 @@ class PeripheralManager: NSObject { peripheral.delegate = self - assertConfiguration() + // TODO: Commenting out temporarily + // assertConfiguration() } } @@ -130,13 +131,13 @@ extension PeripheralManager { self.log.error("PeripheralManager delegate is nil") } - if self.peripheral.state == .disconnected || self.peripheral.state == .connecting { + /*if self.peripheral.state == .disconnected || self.peripheral.state == .connecting { self.log.info("Peripheral is not connected - triggering reconnectLatestPeripheral...") // TODO: This might throw... Also - thread-safety? if let delegate = self.delegate { delegate.reconnectLatestPeripheral() } - } + }*/ if self.needsConfiguration || self.peripheral.services == nil { do { @@ -163,8 +164,37 @@ extension PeripheralManager { queue.async(execute: configureAndRun(block)) } + /*func perform(_ block: @escaping (_ manager: PeripheralManager) -> Void) { + // TODO: Add reconnection dispatch group here! + if peripheral.state == .disconnected || peripheral.state == .connecting { + log.info("Peripheral is not connected - triggering reconnectLatestPeripheral...") + let group = DispatchGroup() + // TODO: This might throw... Also - thread-safety? + if let delegate = self.delegate { + // reconnectLatestPeripheral also handle + delegate.reconnectLatestPeripheral() + } + // Wait for reconnection + group.enter() + Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in + if self.peripheral.state == .connected { + group.leave() + timer.invalidate() + } + } + group.notify(queue: queue) { + self.queue.async(execute: self.configureAndRun(block)) + } + } else { + queue.async(execute: configureAndRun(block)) + } + }*/ + private func assertConfiguration() { - perform { (_) in + /*perform { (_) in + // Intentionally empty to trigger configuration if necessary + }*/ + self.runSession(withName: "Assert peripheral configuration") { () in // Intentionally empty to trigger configuration if necessary } } @@ -480,16 +510,26 @@ extension PeripheralManager: CBCentralManagerDelegate { switch central.state { case .poweredOn: self.log.debug("PeripheralManager - centralManagerDidUpdateState - running assertConfiguration") - assertConfiguration() + if peripheral.state == .connected { + sessionQueue.isSuspended = true + assertConfiguration() + } default: break } } + func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { + // Clear the queue in case of connection error + sessionQueue.cancelAllOperations() + } + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { self.log.debug("PeripheralManager - didConnect: %@", peripheral) switch peripheral.state { case .connected: + self.log.debug("PeripheralManager - didConnect - resuming sessionQueue") + sessionQueue.isSuspended = false self.log.debug("PeripheralManager - didConnect - running assertConfiguration") assertConfiguration() default: @@ -520,6 +560,19 @@ extension CBPeripheral { extension PeripheralManager { public func runSession(withName name: String , _ block: @escaping () -> Void) { self.log.default("Scheduling session %{public}@", name) + + // TODO: What about .disconnecting? + if self.peripheral.state == .disconnected || self.peripheral.state == .connecting { + sessionQueue.isSuspended = true + self.log.info("runSession - Peripheral is not connected...") + if self.peripheral.state == .disconnected { + if let delegate = self.delegate { + self.log.debug("runSession - Peripheral is not connected - triggering reconnectLatestPeripheral...") + delegate.reconnectLatestPeripheral() + } + } + } + sessionQueue.addOperation({ [weak self] in self?.perform { (manager) in manager.log.default("======================== %{public}@ ===========================", name) From f59a7e2d98a7086e210a3faf0ebd1a2efed2d2b8 Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Sat, 8 Jan 2022 17:44:59 +0100 Subject: [PATCH 11/13] Update PeripheralManager.swift --- OmniBLE/Bluetooth/PeripheralManager.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index a362c3a29..42e33801a 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -510,8 +510,11 @@ extension PeripheralManager: CBCentralManagerDelegate { switch central.state { case .poweredOn: self.log.debug("PeripheralManager - centralManagerDidUpdateState - running assertConfiguration") - if peripheral.state == .connected { + if peripheral.state != .connected { sessionQueue.isSuspended = true + } + if peripheral.state == .connected { + sessionQueue.isSuspended = false assertConfiguration() } default: From be1ee3a825a98ad2dda7346d2a4185814a52b0d6 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sat, 8 Jan 2022 09:16:23 -0800 Subject: [PATCH 12/13] revert automatic arg in enactBolus --- OmniBLE/PumpManager/OmnipodPumpManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OmniBLE/PumpManager/OmnipodPumpManager.swift b/OmniBLE/PumpManager/OmnipodPumpManager.swift index 425c429f5..96d75efff 100644 --- a/OmniBLE/PumpManager/OmnipodPumpManager.swift +++ b/OmniBLE/PumpManager/OmnipodPumpManager.swift @@ -1170,7 +1170,7 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactBolus(units: Double, at startDate: Date, automatic: Bool, willRequest: @escaping (DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { + public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { guard self.hasActivePod else { completion(.failure(SetBolusError.certain(OmnipodPumpManagerError.noPodPaired))) return From e7d6f8ff9385e6b9009a520de79dab55deba254b Mon Sep 17 00:00:00 2001 From: Jakob Murko Date: Sat, 8 Jan 2022 22:14:09 +0100 Subject: [PATCH 13/13] Try to fix forgetting the pod --- OmniBLE/Bluetooth/BluetoothManager.swift | 22 +++++++++++++++++++- OmniBLE/PumpManager/Omnipod.swift | 4 ++-- OmniBLE/PumpManager/OmnipodPumpManager.swift | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/OmniBLE/Bluetooth/BluetoothManager.swift b/OmniBLE/Bluetooth/BluetoothManager.swift index 47cf3dfa9..f7ba71de0 100644 --- a/OmniBLE/Bluetooth/BluetoothManager.swift +++ b/OmniBLE/Bluetooth/BluetoothManager.swift @@ -54,6 +54,8 @@ class BluetoothManager: NSObject { } private let lockedStayConnected: Locked = Locked(true) + private var isPermanentlyDisconnecting: Bool = false + weak var delegate: BluetoothManagerDelegate? private let log = OSLog(category: "BluetoothManager") @@ -127,15 +129,22 @@ class BluetoothManager: NSObject { } } - func disconnect() { + // This is a actually `permanentDisconnect` - we do not plan on connecting to this device anymore + func permanentDisconnect() { dispatchPrecondition(condition: .notOnQueue(managerQueue)) + log.debug("permanentDisconnect called") + + // TODO: This could also be async? managerQueue.sync { if manager.isScanning { + log.debug("permanentDisconnect - running stopScan") manager.stopScan() } if let peripheral = peripheral { + isPermanentlyDisconnecting = true + log.debug("permanentDisconnect - running cancelPeripheralConnection") manager.cancelPeripheralConnection(peripheral) } } @@ -333,6 +342,17 @@ extension BluetoothManager: CBCentralManagerDelegate { } } + // Make sure if permanent disconnect is requested, we are actually permanently clearing the peripheral + if isPermanentlyDisconnecting { + log.debug("isPermanentlyDisconnecting is true - nullifying peripheral") + // nullify the peripheral if we don't want it anymore + // TODO: check why stayConnected is never set? + self.stayConnected = false + self.peripheral = nil // this should also nullify peripheralManager and peripheralIdentifier + log.debug("isPermanentlyDisconnecting done - setting isPermanentlyDisconnecting to false") + self.isPermanentlyDisconnecting = false + } + if stayConnected { scanAfterDelay() } diff --git a/OmniBLE/PumpManager/Omnipod.swift b/OmniBLE/PumpManager/Omnipod.swift index 7606b531f..c655c8c11 100644 --- a/OmniBLE/PumpManager/Omnipod.swift +++ b/OmniBLE/PumpManager/Omnipod.swift @@ -99,8 +99,8 @@ public class Omnipod { } } - public func disconnect() { - bluetoothManager.disconnect() + public func permanentDisconnect() { + bluetoothManager.permanentDisconnect() } public var isScanning: Bool { diff --git a/OmniBLE/PumpManager/OmnipodPumpManager.swift b/OmniBLE/PumpManager/OmnipodPumpManager.swift index 425c429f5..f0eea07bb 100644 --- a/OmniBLE/PumpManager/OmnipodPumpManager.swift +++ b/OmniBLE/PumpManager/OmnipodPumpManager.swift @@ -389,7 +389,7 @@ extension OmnipodPumpManager { // Does not support concurrent callers. Not thread-safe. private func forgetPod(completion: @escaping () -> Void) { - omnipod.disconnect() + omnipod.permanentDisconnect() let resetPodState = { (_ state: inout OmnipodPumpManagerState) in self.podComms = PodComms(podState: nil, lotNo: nil, lotSeq: nil) self.podComms.delegate = self