From 2cc3f47b4f4e2cbc05270cd11100924040c2de2d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 22 Mar 2025 11:44:45 -0500 Subject: [PATCH 1/7] Continue with sensor even after auth without control msg --- G7SensorKit/G7CGMManager/G7Sensor.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/G7SensorKit/G7CGMManager/G7Sensor.swift b/G7SensorKit/G7CGMManager/G7Sensor.swift index b1745a1..93d75d1 100644 --- a/G7SensorKit/G7CGMManager/G7Sensor.swift +++ b/G7SensorKit/G7CGMManager/G7Sensor.swift @@ -194,7 +194,10 @@ public final class G7Sensor: G7BluetoothManagerDelegate { if let sensorID = sensorID, sensorID == peripheralManager.peripheral.name { let suspectedEndOfSession: Bool - if pendingAuth && wasRemoteDisconnect { + + if let activationDate = activationDate, Date() > activationDate.addingTimeInterval(G7Sensor.lifetime + G7Sensor.gracePeriod), pendingAuth, wasRemoteDisconnect + { + self.log.info("Sensor disconnected at %{public}@", activationDate.description) suspectedEndOfSession = true // Normal disconnect without auth is likely that G7 app stopped this session } else { suspectedEndOfSession = false @@ -233,7 +236,7 @@ public final class G7Sensor: G7BluetoothManagerDelegate { guard response.count > 0 else { return } - log.debug("Received control response: %{public}@", response.hexadecimalString) + log.default("Received control response: %{public}@", response.hexadecimalString) switch G7Opcode(rawValue: response[0]) { case .glucoseTx?: From 88a0d7b8a45d7d321736dcbdb2edd941d2d546ca Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 30 Mar 2025 22:10:40 -0500 Subject: [PATCH 2/7] Add handling for failed sensor, and add more logs --- G7SensorKit/G7CGMManager/G7CGMManager.swift | 10 ++++++++++ G7SensorKit/G7CGMManager/G7Sensor.swift | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/G7SensorKit/G7CGMManager/G7CGMManager.swift b/G7SensorKit/G7CGMManager/G7CGMManager.swift index 198d5b3..e575e82 100644 --- a/G7SensorKit/G7CGMManager/G7CGMManager.swift +++ b/G7SensorKit/G7CGMManager/G7CGMManager.swift @@ -323,6 +323,11 @@ extension G7CGMManager: G7SensorDelegate { } } + public func sensor(_ sensor: G7Sensor, logComms comms: String) { + logDeviceCommunication("Sensor comms \(comms)", type: .receive) + } + + public func sensor(_ sensor: G7Sensor, didError error: Error) { logDeviceCommunication("Sensor error \(error)", type: .error) } @@ -335,6 +340,11 @@ extension G7CGMManager: G7SensorDelegate { return } + if message.algorithmState.sensorFailed { + logDeviceCommunication("Detected failed sensor... scanning for new sensor.", type: .receive) + scanForNewSensor() + } + guard let activationDate = sensor.activationDate else { logDeviceCommunication("Unable to process sensor reading without activation date.", type: .error) return diff --git a/G7SensorKit/G7CGMManager/G7Sensor.swift b/G7SensorKit/G7CGMManager/G7Sensor.swift index 93d75d1..a224ee4 100644 --- a/G7SensorKit/G7CGMManager/G7Sensor.swift +++ b/G7SensorKit/G7CGMManager/G7Sensor.swift @@ -19,6 +19,8 @@ public protocol G7SensorDelegate: AnyObject { func sensor(_ sensor: G7Sensor, didError error: Error) + func sensor(_ sensor: G7Sensor, logComms comms: String) + func sensor(_ sensor: G7Sensor, didRead glucose: G7GlucoseMessage) func sensor(_ sensor: G7Sensor, didReadBackfill backfill: [G7BackfillMessage]) @@ -255,7 +257,7 @@ public final class G7Sensor: G7BluetoothManagerDelegate { } } default: - // We ignore all other known opcodes + self.delegate?.sensor(self, logComms: response.hexadecimalString) break } } From 50970ff053dab62f613868ae50b843dbc88ee968 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 31 Mar 2025 17:13:18 -0500 Subject: [PATCH 3/7] Add additional failure types --- G7SensorKit/AlgorithmError.swift | 4 ++- G7SensorKit/AlgorithmState.swift | 34 +++++++++++++------ G7SensorKit/G7CGMManager/G7CGMManager.swift | 1 + .../G7CGMManager/G7CGMManager+UI.swift | 2 +- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/G7SensorKit/AlgorithmError.swift b/G7SensorKit/AlgorithmError.swift index e46f2be..9d83482 100644 --- a/G7SensorKit/AlgorithmError.swift +++ b/G7SensorKit/AlgorithmError.swift @@ -37,12 +37,14 @@ extension AlgorithmState { return LocalizedString("Sensor is OK", comment: "The description of sensor algorithm state when sensor is ok.") case .stopped: return LocalizedString("Sensor is stopped", comment: "The description of sensor algorithm state when sensor is stopped.") - case .warmup, .questionMarks: + case .warmup, .temporarySensorIssue: return LocalizedString("Sensor is warming up", comment: "The description of sensor algorithm state when sensor is warming up.") case .expired: return LocalizedString("Sensor expired", comment: "The description of sensor algorithm state when sensor is expired.") case .sensorFailed: return LocalizedString("Sensor failed", comment: "The description of sensor algorithm state when sensor failed.") + default: + return "Sensor state: \(String(describing: state))" } case .unknown(let rawValue): return String(format: LocalizedString("Sensor is in unknown state %1$d", comment: "The description of sensor algorithm state when raw value is unknown. (1: missing data details)"), rawValue) diff --git a/G7SensorKit/AlgorithmState.swift b/G7SensorKit/AlgorithmState.swift index 93e5c40..34642e7 100644 --- a/G7SensorKit/AlgorithmState.swift +++ b/G7SensorKit/AlgorithmState.swift @@ -15,8 +15,26 @@ public enum AlgorithmState: RawRepresentable { public enum State: RawValue { case stopped = 1 case warmup = 2 + case excessNoise = 3 + case firstOfTwoBGsNeeded = 4 + case secondOfTwoBGsNeeded = 5 case ok = 6 - case questionMarks = 18 + case needsCalibration = 7 + case calibrationError1 = 8 + case calibrationError2 = 9 + case calibrationLinearityFitFailure = 10 + case sensorFailedDuetoCountsAberration = 11 + case sensorFailedDuetoResidualAberration = 12 + case outOfCalibrationDueToOutlier = 13 + case outlierCalibrationRequest = 14 + case sessionExpired = 15 + case sessionFailedDueToUnrecoverableError = 16 + case sessionFailedDueToTransmitterError = 17 + case temporarySensorIssue = 18 + case sensorFailedDueToProgressiveSensorDecline = 19 + case sensorFailedDueToHighCountsAberration = 20 + case sensorFailedDueToLowCountsAberration = 21 + case sensorFailedDueToRestart = 22 case expired = 24 case sensorFailed = 25 } @@ -48,7 +66,7 @@ public enum AlgorithmState: RawRepresentable { } switch state { - case .sensorFailed: + case .sensorFailed, .sensorFailedDuetoCountsAberration, .sensorFailedDuetoResidualAberration, .sessionFailedDueToTransmitterError, .sessionFailedDueToUnrecoverableError, .sensorFailedDueToProgressiveSensorDecline, .sensorFailedDueToHighCountsAberration, .sensorFailedDueToLowCountsAberration, .sensorFailedDueToRestart: return true default: return false @@ -68,13 +86,13 @@ public enum AlgorithmState: RawRepresentable { } } - public var isInSensorError: Bool { + public var hasTemporaryError: Bool { guard case .known(let state) = self else { return false } switch state { - case .questionMarks: + case .temporarySensorIssue: return true default: return false @@ -88,14 +106,10 @@ public enum AlgorithmState: RawRepresentable { } switch state { - case .stopped, - .warmup, - .questionMarks, - .expired, - .sensorFailed: - return false case .ok: return true + default: + return false } } } diff --git a/G7SensorKit/G7CGMManager/G7CGMManager.swift b/G7SensorKit/G7CGMManager/G7CGMManager.swift index e575e82..cc045d6 100644 --- a/G7SensorKit/G7CGMManager/G7CGMManager.swift +++ b/G7SensorKit/G7CGMManager/G7CGMManager.swift @@ -345,6 +345,7 @@ extension G7CGMManager: G7SensorDelegate { scanForNewSensor() } + guard let activationDate = sensor.activationDate else { logDeviceCommunication("Unable to process sensor reading without activation date.", type: .error) return diff --git a/G7SensorKitUI/G7CGMManager/G7CGMManager+UI.swift b/G7SensorKitUI/G7CGMManager/G7CGMManager+UI.swift index f3e0306..fb91acb 100644 --- a/G7SensorKitUI/G7CGMManager/G7CGMManager+UI.swift +++ b/G7SensorKitUI/G7CGMManager/G7CGMManager+UI.swift @@ -74,7 +74,7 @@ extension G7CGMManager: CGMManagerUI { state: .warning) } - if let latestReading = latestReading, latestReading.algorithmState.isInSensorError { + if let latestReading = latestReading, latestReading.algorithmState.hasTemporaryError { return G7DeviceStatusHighlight( localizedMessage: LocalizedString("Sensor\nIssue", comment: "G7 Status highlight text for sensor error"), imageName: "exclamationmark.circle.fill", From 59bad50ca45a52b4c39073ed83e4ec808925e7da Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 1 Apr 2025 07:37:26 -0500 Subject: [PATCH 4/7] Handle possible new state of session ended --- G7SensorKit/AlgorithmState.swift | 1 + G7SensorKit/G7CGMManager/G7CGMManager.swift | 4 ++++ G7SensorKit/Messages/G7Opcode.swift | 1 + 3 files changed, 6 insertions(+) diff --git a/G7SensorKit/AlgorithmState.swift b/G7SensorKit/AlgorithmState.swift index 34642e7..1bce93f 100644 --- a/G7SensorKit/AlgorithmState.swift +++ b/G7SensorKit/AlgorithmState.swift @@ -37,6 +37,7 @@ public enum AlgorithmState: RawRepresentable { case sensorFailedDueToRestart = 22 case expired = 24 case sensorFailed = 25 + case sessionEnded = 26 } case known(State) diff --git a/G7SensorKit/G7CGMManager/G7CGMManager.swift b/G7SensorKit/G7CGMManager/G7CGMManager.swift index cc045d6..e1793d4 100644 --- a/G7SensorKit/G7CGMManager/G7CGMManager.swift +++ b/G7SensorKit/G7CGMManager/G7CGMManager.swift @@ -345,6 +345,10 @@ extension G7CGMManager: G7SensorDelegate { scanForNewSensor() } + if message.algorithmState == .known(.sessionEnded) { + logDeviceCommunication("Detected session ended... scanning for new sensor.", type: .receive) + scanForNewSensor() + } guard let activationDate = sensor.activationDate else { logDeviceCommunication("Unable to process sensor reading without activation date.", type: .error) diff --git a/G7SensorKit/Messages/G7Opcode.swift b/G7SensorKit/Messages/G7Opcode.swift index 3f4272f..5198462 100644 --- a/G7SensorKit/Messages/G7Opcode.swift +++ b/G7SensorKit/Messages/G7Opcode.swift @@ -10,6 +10,7 @@ import Foundation enum G7Opcode: UInt8 { case authChallengeRx = 0x05 + case sessionStopTx = 0x28 case glucoseTx = 0x4e case backfillFinished = 0x59 } From 25518c5461e40c8ea17e75d597e26f622faf6232 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 3 Apr 2025 11:30:35 -0500 Subject: [PATCH 5/7] Additional logs --- G7SensorKit/G7CGMManager/G7BluetoothManager.swift | 9 +++++---- G7SensorKit/G7CGMManager/G7CGMManager.swift | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/G7SensorKit/G7CGMManager/G7BluetoothManager.swift b/G7SensorKit/G7CGMManager/G7BluetoothManager.swift index 62ab5fc..3dc9c24 100644 --- a/G7SensorKit/G7CGMManager/G7BluetoothManager.swift +++ b/G7SensorKit/G7CGMManager/G7BluetoothManager.swift @@ -191,19 +191,20 @@ class G7BluetoothManager: NSObject { } if let peripheralID = activePeripheralIdentifier, let peripheral = centralManager.retrievePeripherals(withIdentifiers: [peripheralID]).first { - log.debug("Retrieved peripheral %{public}@", peripheral.identifier.uuidString) + log.default("Retrieved peripheral %{public}@", peripheral.identifier.uuidString) handleDiscoveredPeripheral(peripheral) } else { for peripheral in centralManager.retrieveConnectedPeripherals(withServices: [ SensorServiceUUID.advertisement.cbUUID, SensorServiceUUID.cgmService.cbUUID ]) { + log.default("Found system-connected peripheral: %{public}@", peripheral.identifier.uuidString) handleDiscoveredPeripheral(peripheral) } } if activePeripheral == nil { - log.debug("Scanning for peripherals") + log.default("Scanning for peripherals") centralManager.scanForPeripherals(withServices: [ SensorServiceUUID.advertisement.cbUUID ], @@ -257,7 +258,7 @@ class G7BluetoothManager: NSObject { if let delegate = delegate { switch delegate.bluetoothManager(self, shouldConnectPeripheral: peripheral) { case .makeActive: - log.debug("Making peripheral active: %{public}@", peripheral.identifier.uuidString) + log.default("Making peripheral active: %{public}@", peripheral.identifier.uuidString) if let peripheralManager = activePeripheralManager { peripheralManager.peripheral = peripheral @@ -273,7 +274,7 @@ class G7BluetoothManager: NSObject { self.centralManager.connect(peripheral) case .connect: - log.debug("Connecting to peripheral: %{public}@", peripheral.identifier.uuidString) + log.default("Connecting to peripheral: %{public}@", peripheral.identifier.uuidString) self.centralManager.connect(peripheral) let peripheralManager = G7PeripheralManager( peripheral: peripheral, diff --git a/G7SensorKit/G7CGMManager/G7CGMManager.swift b/G7SensorKit/G7CGMManager/G7CGMManager.swift index e1793d4..ccddaeb 100644 --- a/G7SensorKit/G7CGMManager/G7CGMManager.swift +++ b/G7SensorKit/G7CGMManager/G7CGMManager.swift @@ -350,6 +350,7 @@ extension G7CGMManager: G7SensorDelegate { scanForNewSensor() } + guard let activationDate = sensor.activationDate else { logDeviceCommunication("Unable to process sensor reading without activation date.", type: .error) return From 8ae21dfb02cfe2a4c8b02190b6da5f4a23787563 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 10 Apr 2025 10:59:14 -0500 Subject: [PATCH 6/7] Check for connection event, for cases where iOS fails to call didDiscover --- .../G7CGMManager/G7BluetoothManager.swift | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/G7SensorKit/G7CGMManager/G7BluetoothManager.swift b/G7SensorKit/G7CGMManager/G7BluetoothManager.swift index 3dc9c24..156bb80 100644 --- a/G7SensorKit/G7CGMManager/G7BluetoothManager.swift +++ b/G7SensorKit/G7CGMManager/G7BluetoothManager.swift @@ -156,7 +156,7 @@ class G7BluetoothManager: NSObject { private func managerQueue_stopScanning() { if centralManager.isScanning { - log.debug("Stopping scan") + log.default("Stopping scan") centralManager.stopScan() delegate?.bluetoothManagerScanningStatusDidChange(self) } @@ -167,7 +167,7 @@ class G7BluetoothManager: NSObject { managerQueue.sync { if centralManager.isScanning { - log.debug("Stopping scan on disconnect") + log.default("Stopping scan on disconnect") centralManager.stopScan() delegate?.bluetoothManagerScanningStatusDidChange(self) } @@ -178,6 +178,15 @@ class G7BluetoothManager: NSObject { } } + func centralManager(_ central: CBCentralManager, connectionEventDidOccur event: CBConnectionEvent, for peripheral: CBPeripheral) { + managerQueue.async { + if self.activePeripheralIdentifier == nil { + self.log.default("Discovered peripheral from connectionEventDidOccur %{public}@", peripheral.identifier.uuidString) + self.handleDiscoveredPeripheral(peripheral) + } + } + } + private func managerQueue_scanForPeripheral() { dispatchPrecondition(condition: .onQueue(managerQueue)) @@ -204,7 +213,13 @@ class G7BluetoothManager: NSObject { } if activePeripheral == nil { - log.default("Scanning for peripherals") + log.default("Scanning for peripherals and listening for connection events") + + centralManager.registerForConnectionEvents(options: [CBConnectionEventMatchingOption.serviceUUIDs: [ + SensorServiceUUID.advertisement.cbUUID, + SensorServiceUUID.cgmService.cbUUID + ]]) + centralManager.scanForPeripherals(withServices: [ SensorServiceUUID.advertisement.cbUUID ], @@ -312,7 +327,7 @@ extension G7BluetoothManager: CBCentralManagerDelegate { fallthrough @unknown default: if central.isScanning { - log.debug("Stopping scan on central not powered on") + log.default("Stopping scan on central not powered on") central.stopScan() delegate?.bluetoothManagerScanningStatusDidChange(self) } @@ -333,7 +348,7 @@ extension G7BluetoothManager: CBCentralManagerDelegate { func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { dispatchPrecondition(condition: .onQueue(managerQueue)) - log.info("%{public}@: %{public}@, data = %{public}@", #function, peripheral, String(describing: advertisementData)) + log.default("%{public}@: %{public}@, data = %{public}@", #function, peripheral, String(describing: advertisementData)) managerQueue.async { self.handleDiscoveredPeripheral(peripheral) From b0d424d9dbd27558b89e4d13db20569252f5a0a3 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 11 Apr 2025 09:36:40 -0500 Subject: [PATCH 7/7] Use remote disconnect without auth/data as end-of-session detection again. --- G7SensorKit/G7CGMManager/G7Sensor.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/G7SensorKit/G7CGMManager/G7Sensor.swift b/G7SensorKit/G7CGMManager/G7Sensor.swift index a224ee4..ea215d1 100644 --- a/G7SensorKit/G7CGMManager/G7Sensor.swift +++ b/G7SensorKit/G7CGMManager/G7Sensor.swift @@ -197,9 +197,8 @@ public final class G7Sensor: G7BluetoothManagerDelegate { let suspectedEndOfSession: Bool - if let activationDate = activationDate, Date() > activationDate.addingTimeInterval(G7Sensor.lifetime + G7Sensor.gracePeriod), pendingAuth, wasRemoteDisconnect - { - self.log.info("Sensor disconnected at %{public}@", activationDate.description) + self.log.info("Sensor disconnected: wasRemoteDisconnect:%{public}@", String(describing: wasRemoteDisconnect)) + if pendingAuth, wasRemoteDisconnect { suspectedEndOfSession = true // Normal disconnect without auth is likely that G7 app stopped this session } else { suspectedEndOfSession = false