Skip to content

G7 End of session detection bugfix #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion G7SensorKit/AlgorithmError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
35 changes: 25 additions & 10 deletions G7SensorKit/AlgorithmState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,29 @@ 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
case sessionEnded = 26
}

case known(State)
Expand Down Expand Up @@ -48,7 +67,7 @@ public enum AlgorithmState: RawRepresentable {
}

switch state {
case .sensorFailed:
case .sensorFailed, .sensorFailedDuetoCountsAberration, .sensorFailedDuetoResidualAberration, .sessionFailedDueToTransmitterError, .sessionFailedDueToUnrecoverableError, .sensorFailedDueToProgressiveSensorDecline, .sensorFailedDueToHighCountsAberration, .sensorFailedDueToLowCountsAberration, .sensorFailedDueToRestart:
return true
default:
return false
Expand All @@ -68,13 +87,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
Expand All @@ -88,14 +107,10 @@ public enum AlgorithmState: RawRepresentable {
}

switch state {
case .stopped,
.warmup,
.questionMarks,
.expired,
.sensorFailed:
return false
case .ok:
return true
default:
return false
}
}
}
Expand Down
32 changes: 24 additions & 8 deletions G7SensorKit/G7CGMManager/G7BluetoothManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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))

Expand All @@ -191,19 +200,26 @@ 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 and listening for connection events")

centralManager.registerForConnectionEvents(options: [CBConnectionEventMatchingOption.serviceUUIDs: [
SensorServiceUUID.advertisement.cbUUID,
SensorServiceUUID.cgmService.cbUUID
]])

centralManager.scanForPeripherals(withServices: [
SensorServiceUUID.advertisement.cbUUID
],
Expand Down Expand Up @@ -257,7 +273,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
Expand All @@ -273,7 +289,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,
Expand Down Expand Up @@ -311,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)
}
Expand All @@ -332,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)
Expand Down
16 changes: 16 additions & 0 deletions G7SensorKit/G7CGMManager/G7CGMManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -335,6 +340,17 @@ extension G7CGMManager: G7SensorDelegate {
return
}

if message.algorithmState.sensorFailed {
logDeviceCommunication("Detected failed sensor... scanning for new sensor.", type: .receive)
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)
return
Expand Down
10 changes: 7 additions & 3 deletions G7SensorKit/G7CGMManager/G7Sensor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -194,7 +196,9 @@ public final class G7Sensor: G7BluetoothManagerDelegate {
if let sensorID = sensorID, sensorID == peripheralManager.peripheral.name {

let suspectedEndOfSession: Bool
if pendingAuth && wasRemoteDisconnect {

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
Expand Down Expand Up @@ -233,7 +237,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?:
Expand All @@ -252,7 +256,7 @@ public final class G7Sensor: G7BluetoothManagerDelegate {
}
}
default:
// We ignore all other known opcodes
self.delegate?.sensor(self, logComms: response.hexadecimalString)
break
}
}
Expand Down
1 change: 1 addition & 0 deletions G7SensorKit/Messages/G7Opcode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation

enum G7Opcode: UInt8 {
case authChallengeRx = 0x05
case sessionStopTx = 0x28
case glucoseTx = 0x4e
case backfillFinished = 0x59
}
2 changes: 1 addition & 1 deletion G7SensorKitUI/G7CGMManager/G7CGMManager+UI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down