From 5b903f43f9026f5bb7c51611ebe72e9d9eb2d505 Mon Sep 17 00:00:00 2001 From: Jamie Keene Date: Tue, 2 Jan 2024 23:29:54 +0000 Subject: [PATCH 01/11] Add link to Dexcom app in G7 settings I found myself missing the 'Open app' button from the G6, and that my muscle memory kept expecting it to be there, so in this PR I've added it into the G7 management screen. I opted to create a new section rather than include it with the other management buttons beneath to try to prevent users accidentally tapping the more destructive actions. --- G7SensorKitUI/Views/G7SettingsView.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/G7SensorKitUI/Views/G7SettingsView.swift b/G7SensorKitUI/Views/G7SettingsView.swift index 489ba22..b5b50e4 100644 --- a/G7SensorKitUI/Views/G7SettingsView.swift +++ b/G7SensorKitUI/Views/G7SettingsView.swift @@ -123,6 +123,14 @@ struct G7SettingsView: View { Toggle(LocalizedString("Upload Readings", comment: "title for g7 config settings to upload readings"), isOn: $viewModel.uploadReadings) } } + + Section () { + Button(LocalizedString("Open Dexcom App", comment:"Opens the dexcom G7 app to allow users to manage active sensors"), action: { + if let appURL = URL(string: "dexcomg7://") { + UIApplication.shared.open(appURL) + } + }) + } Section () { if !self.viewModel.scanning { From 6b37518895b9aa2d39b58f2d9915911be60daed4 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Fri, 22 Mar 2024 14:11:29 -0700 Subject: [PATCH 02/11] Add Dexcom One+ to allowed prefix --- G7SensorKit/G7CGMManager/G7Sensor.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/G7SensorKit/G7CGMManager/G7Sensor.swift b/G7SensorKit/G7CGMManager/G7Sensor.swift index d5c0b46..b1745a1 100644 --- a/G7SensorKit/G7CGMManager/G7Sensor.swift +++ b/G7SensorKit/G7CGMManager/G7Sensor.swift @@ -215,7 +215,8 @@ public final class G7Sensor: G7BluetoothManagerDelegate { } /// The Dexcom G7 advertises a peripheral name of "DXCMxx", and later reports a full name of "Dexcomxx" - if name.hasPrefix("DXCM") { + /// Dexcom One+ peripheral name start with "DX02" + if name.hasPrefix("DXCM") || name.hasPrefix("DX02"){ // If we're following this name or if we're scanning, connect if let sensorName = sensorID, name.suffix(2) == sensorName.suffix(2) { return .makeActive From 45030412cd9543bae946bf09183388bf1755e233 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Fri, 22 Mar 2024 14:50:01 -0700 Subject: [PATCH 03/11] Make G7 startup message more generic --- G7SensorKitUI/Views/G7StartupView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/G7SensorKitUI/Views/G7StartupView.swift b/G7SensorKitUI/Views/G7StartupView.swift index edc999c..44c19ba 100644 --- a/G7SensorKitUI/Views/G7StartupView.swift +++ b/G7SensorKitUI/Views/G7StartupView.swift @@ -26,7 +26,7 @@ struct G7StartupView: View { .frame(height: 120) .padding(.horizontal) }.frame(maxWidth: .infinity) - Text(LocalizedString("Loop can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView")) + Text(LocalizedString("This app can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView")) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.secondary) Spacer() From a6a6e4c69601e357c139aabde9ac8b25a41ac515 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 6 Apr 2024 16:53:05 -0500 Subject: [PATCH 04/11] Cleanup unused properties --- G7SensorKit/BluetoothServices.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/G7SensorKit/BluetoothServices.swift b/G7SensorKit/BluetoothServices.swift index cf35cb7..cb17342 100644 --- a/G7SensorKit/BluetoothServices.swift +++ b/G7SensorKit/BluetoothServices.swift @@ -15,23 +15,13 @@ extension CBUUIDRawValue where RawValue == String { } } - enum SensorServiceUUID: String, CBUUIDRawValue { - case deviceInfo = "180A" case advertisement = "FEBC" case cgmService = "F8083532-849E-531C-C594-30F1F86A4EA5" case serviceB = "F8084532-849E-531C-C594-30F1F86A4EA5" } - -enum DeviceInfoCharacteristicUUID: String, CBUUIDRawValue { - // Read - // "DexcomUN" - case manufacturerNameString = "2A29" -} - - enum CGMServiceCharacteristicUUID: String, CBUUIDRawValue { // Read/Notify @@ -61,7 +51,6 @@ extension G7PeripheralManager.Configuration { return G7PeripheralManager.Configuration( serviceCharacteristics: [ SensorServiceUUID.cgmService.cbUUID: [ - //CGMServiceCharacteristicUUID.communication.cbUUID, // Unused for now CGMServiceCharacteristicUUID.authentication.cbUUID, CGMServiceCharacteristicUUID.control.cbUUID, CGMServiceCharacteristicUUID.backfill.cbUUID, From 29ec310a7c7e958f778294f4846049e27f7705dc Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 9 Apr 2024 08:18:40 -0700 Subject: [PATCH 05/11] update text to use appName instead of Loop --- G7SensorKitUI/Views/G7StartupView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/G7SensorKitUI/Views/G7StartupView.swift b/G7SensorKitUI/Views/G7StartupView.swift index 44c19ba..b7a4d07 100644 --- a/G7SensorKitUI/Views/G7StartupView.swift +++ b/G7SensorKitUI/Views/G7StartupView.swift @@ -13,6 +13,8 @@ struct G7StartupView: View { var didContinue: (() -> Void)? var didCancel: (() -> Void)? + @Environment(\.appName) private var appName + var body: some View { VStack(alignment: .center, spacing: 20) { Spacer() @@ -26,7 +28,7 @@ struct G7StartupView: View { .frame(height: 120) .padding(.horizontal) }.frame(maxWidth: .infinity) - Text(LocalizedString("This app can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView")) + Text(String(format: LocalizedString("%1$@ can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView (1: appName)"), self.appName)) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.secondary) Spacer() From 3ad6902ce393a8b3f66e42f7b2e10e2bd1d803fc Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 9 Apr 2024 21:29:22 -0700 Subject: [PATCH 06/11] Defer appName changes --- G7SensorKitUI/Views/G7StartupView.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/G7SensorKitUI/Views/G7StartupView.swift b/G7SensorKitUI/Views/G7StartupView.swift index b7a4d07..edc999c 100644 --- a/G7SensorKitUI/Views/G7StartupView.swift +++ b/G7SensorKitUI/Views/G7StartupView.swift @@ -13,8 +13,6 @@ struct G7StartupView: View { var didContinue: (() -> Void)? var didCancel: (() -> Void)? - @Environment(\.appName) private var appName - var body: some View { VStack(alignment: .center, spacing: 20) { Spacer() @@ -28,7 +26,7 @@ struct G7StartupView: View { .frame(height: 120) .padding(.horizontal) }.frame(maxWidth: .infinity) - Text(String(format: LocalizedString("%1$@ can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView (1: appName)"), self.appName)) + Text(LocalizedString("Loop can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView")) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.secondary) Spacer() From b5e992e211d2ac6224acb105dd97fb484767da72 Mon Sep 17 00:00:00 2001 From: Marion Barker Date: Sun, 19 May 2024 07:34:55 -0700 Subject: [PATCH 07/11] Use appName instead of hard-coded Loop in strings (#26) * enable use of environment appName * restore comment to original * modify text with appName so translation string remains constant * update Xcode placement of Bundle.swift, fix typo in header --- G7SensorKit.xcodeproj/project.pbxproj | 4 ++++ G7SensorKitUI/Extensions/Bundle.swift | 15 +++++++++++++++ G7SensorKitUI/G7CGMManager/G7UICoordinator.swift | 1 + G7SensorKitUI/Views/G7StartupView.swift | 4 +++- 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 G7SensorKitUI/Extensions/Bundle.swift diff --git a/G7SensorKit.xcodeproj/project.pbxproj b/G7SensorKit.xcodeproj/project.pbxproj index c6c8c6a..238d8fc 100644 --- a/G7SensorKit.xcodeproj/project.pbxproj +++ b/G7SensorKit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + B60BB2E42BC649DA00D2BB39 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BB2E32BC649DA00D2BB39 /* Bundle.swift */; }; C109F14A291ECCE2008EA5B6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C109F149291ECCE2008EA5B6 /* Assets.xcassets */; }; C109F14C291ED66F008EA5B6 /* G7GlucoseMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C109F14B291ED66F008EA5B6 /* G7GlucoseMessageTests.swift */; }; C139829829295D7D0047DB5F /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17F514A291EB6F000555EB5 /* HKUnit.swift */; }; @@ -106,6 +107,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + B60BB2E32BC649DA00D2BB39 /* Bundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bundle.swift; path = G7SensorKitUI/Extensions/Bundle.swift; sourceTree = SOURCE_ROOT; }; C1086B0E29C9169100D46E65 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C1086B0F29C9169100D46E65 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C109F149291ECCE2008EA5B6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -366,6 +368,7 @@ C17F5154291EBD7100555EB5 /* Extensions */ = { isa = PBXGroup; children = ( + B60BB2E32BC649DA00D2BB39 /* Bundle.swift */, C17F5155291EBD8600555EB5 /* Image.swift */, ); path = Extensions; @@ -636,6 +639,7 @@ buildActionMask = 2147483647; files = ( C17F5108291EAC9D00555EB5 /* G7SettingsView.swift in Sources */, + B60BB2E42BC649DA00D2BB39 /* Bundle.swift in Sources */, C17F5157291EBD9900555EB5 /* TimeInterval.swift in Sources */, C19C9F4E29C91C4C00A6D3D0 /* LocalizedString.swift in Sources */, C17F5156291EBD8600555EB5 /* Image.swift in Sources */, diff --git a/G7SensorKitUI/Extensions/Bundle.swift b/G7SensorKitUI/Extensions/Bundle.swift new file mode 100644 index 0000000..73a1f57 --- /dev/null +++ b/G7SensorKitUI/Extensions/Bundle.swift @@ -0,0 +1,15 @@ +// +// Bundle.swift +// G7SensorKit +// +// Created by Darin Krauss on 1/23/21. +// Copyright © 2021 LoopKit Authors. All rights reserved. +// + +import Foundation + +extension Bundle { + var bundleDisplayName: String { + return object(forInfoDictionaryKey: "CFBundleDisplayName") as! String + } +} diff --git a/G7SensorKitUI/G7CGMManager/G7UICoordinator.swift b/G7SensorKitUI/G7CGMManager/G7UICoordinator.swift index 14d4359..0906587 100644 --- a/G7SensorKitUI/G7CGMManager/G7UICoordinator.swift +++ b/G7SensorKitUI/G7CGMManager/G7UICoordinator.swift @@ -54,6 +54,7 @@ class G7UICoordinator: UINavigationController, CGMManagerOnboarding, CompletionN } } ) + .environment(\.appName, Bundle.main.bundleDisplayName) let hostingController = DismissibleHostingController(content: rootView, colorPalette: colorPalette) hostingController.navigationItem.largeTitleDisplayMode = .never hostingController.title = nil diff --git a/G7SensorKitUI/Views/G7StartupView.swift b/G7SensorKitUI/Views/G7StartupView.swift index edc999c..ccd84a3 100644 --- a/G7SensorKitUI/Views/G7StartupView.swift +++ b/G7SensorKitUI/Views/G7StartupView.swift @@ -13,6 +13,8 @@ struct G7StartupView: View { var didContinue: (() -> Void)? var didCancel: (() -> Void)? + @Environment(\.appName) private var appName + var body: some View { VStack(alignment: .center, spacing: 20) { Spacer() @@ -26,7 +28,7 @@ struct G7StartupView: View { .frame(height: 120) .padding(.horizontal) }.frame(maxWidth: .infinity) - Text(LocalizedString("Loop can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView")) + Text(String(format: LocalizedString("%1$@ can read G7 CGM data, but you must still use the Dexcom G7 App for pairing, calibration, and other sensor management.", comment: "Descriptive text on G7StartupView (1: appName)"), self.appName)) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.secondary) Spacer() From 2be3eb29b0a18aa89f8b60281341e46e07d024e5 Mon Sep 17 00:00:00 2001 From: Mike Plante <82073483+MikePlante1@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:00:05 -0400 Subject: [PATCH 08/11] Add ONE+ to the display name (#28) --- G7SensorPlugin/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/G7SensorPlugin/Info.plist b/G7SensorPlugin/Info.plist index 86c3457..996e0e1 100644 --- a/G7SensorPlugin/Info.plist +++ b/G7SensorPlugin/Info.plist @@ -23,7 +23,7 @@ NSPrincipalClass G7SensorPlugin com.loopkit.Loop.CGMManagerDisplayName - Dexcom G7 + Dexcom G7 / ONE+ com.loopkit.Loop.CGMManagerIdentifier G7CGMManager From f1b09f35aa1ab8dd1655d4ba84bcf9563a38ecbb Mon Sep 17 00:00:00 2001 From: Sam King Date: Tue, 11 Mar 2025 02:54:13 -0700 Subject: [PATCH 09/11] Add build dependencies --- G7SensorKit.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/G7SensorKit.xcodeproj/project.pbxproj b/G7SensorKit.xcodeproj/project.pbxproj index 238d8fc..0011513 100644 --- a/G7SensorKit.xcodeproj/project.pbxproj +++ b/G7SensorKit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3B0FD2A52D803BF100E5E921 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0FD2A42D803BF000E5E921 /* LoopKitUI.framework */; }; B60BB2E42BC649DA00D2BB39 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BB2E32BC649DA00D2BB39 /* Bundle.swift */; }; C109F14A291ECCE2008EA5B6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C109F149291ECCE2008EA5B6 /* Assets.xcassets */; }; C109F14C291ED66F008EA5B6 /* G7GlucoseMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C109F14B291ED66F008EA5B6 /* G7GlucoseMessageTests.swift */; }; @@ -107,6 +108,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 3B0FD2A42D803BF000E5E921 /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B60BB2E32BC649DA00D2BB39 /* Bundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bundle.swift; path = G7SensorKitUI/Extensions/Bundle.swift; sourceTree = SOURCE_ROOT; }; C1086B0E29C9169100D46E65 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C1086B0F29C9169100D46E65 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -204,6 +206,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3B0FD2A52D803BF100E5E921 /* LoopKitUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -338,6 +341,7 @@ C17F5128291EAFA100555EB5 /* Frameworks */ = { isa = PBXGroup; children = ( + 3B0FD2A42D803BF000E5E921 /* LoopKitUI.framework */, ); name = Frameworks; sourceTree = ""; From 67c55231dc2f4913ed1b04d402985e6692acb37f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 14 Apr 2025 12:45:29 -0500 Subject: [PATCH 10/11] G7 End of session detection bugfix (#34) * Continue with sensor even after auth without control msg * Add handling for failed sensor, and add more logs * Add additional failure types * Handle possible new state of session ended * Additional logs * Check for connection event, for cases where iOS fails to call didDiscover * Use remote disconnect without auth/data as end-of-session detection again. --- G7SensorKit/AlgorithmError.swift | 4 ++- G7SensorKit/AlgorithmState.swift | 35 +++++++++++++------ .../G7CGMManager/G7BluetoothManager.swift | 32 ++++++++++++----- G7SensorKit/G7CGMManager/G7CGMManager.swift | 16 +++++++++ G7SensorKit/G7CGMManager/G7Sensor.swift | 10 ++++-- G7SensorKit/Messages/G7Opcode.swift | 1 + .../G7CGMManager/G7CGMManager+UI.swift | 2 +- 7 files changed, 77 insertions(+), 23 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..1bce93f 100644 --- a/G7SensorKit/AlgorithmState.swift +++ b/G7SensorKit/AlgorithmState.swift @@ -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) @@ -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 @@ -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 @@ -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 } } } diff --git a/G7SensorKit/G7CGMManager/G7BluetoothManager.swift b/G7SensorKit/G7CGMManager/G7BluetoothManager.swift index 62ab5fc..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)) @@ -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 ], @@ -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 @@ -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, @@ -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) } @@ -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) diff --git a/G7SensorKit/G7CGMManager/G7CGMManager.swift b/G7SensorKit/G7CGMManager/G7CGMManager.swift index 198d5b3..ccddaeb 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,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 diff --git a/G7SensorKit/G7CGMManager/G7Sensor.swift b/G7SensorKit/G7CGMManager/G7Sensor.swift index b1745a1..ea215d1 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]) @@ -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 @@ -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?: @@ -252,7 +256,7 @@ public final class G7Sensor: G7BluetoothManagerDelegate { } } default: - // We ignore all other known opcodes + self.delegate?.sensor(self, logComms: response.hexadecimalString) break } } 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 } 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 46047ed13c44d21bf75c7ca99642f954a35ed6c7 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 17 Apr 2025 14:47:48 -0500 Subject: [PATCH 11/11] Fix calibration decoding, and lagged backfill processing (#35) --- .../G7CGMManager/G7BackfillMessage.swift | 7 ++++--- G7SensorKit/G7CGMManager/G7Sensor.swift | 20 +++++++++++++------ G7SensorKitTests/G7GlucoseMessageTests.swift | 17 ++++++++++++++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/G7SensorKit/G7CGMManager/G7BackfillMessage.swift b/G7SensorKit/G7CGMManager/G7BackfillMessage.swift index 2658cb4..90cc3eb 100644 --- a/G7SensorKit/G7CGMManager/G7BackfillMessage.swift +++ b/G7SensorKit/G7CGMManager/G7BackfillMessage.swift @@ -32,18 +32,19 @@ public struct G7BackfillMessage: Equatable { return nil } - timestamp = data[0..<4].toInt() + + timestamp = data[0..<3].toInt() let glucoseBytes = data[4..<6].to(UInt16.self) if glucoseBytes != 0xffff { glucose = glucoseBytes & 0xfff - glucoseIsDisplayOnly = (glucoseBytes & 0xf000) > 0 } else { glucose = nil - glucoseIsDisplayOnly = false } + glucoseIsDisplayOnly = data[7] & 0x10 != 0 + algorithmState = AlgorithmState(rawValue: data[6]) if data[8] == 0x7f { diff --git a/G7SensorKit/G7CGMManager/G7Sensor.swift b/G7SensorKit/G7CGMManager/G7Sensor.swift index ea215d1..aa88883 100644 --- a/G7SensorKit/G7CGMManager/G7Sensor.swift +++ b/G7SensorKit/G7CGMManager/G7Sensor.swift @@ -195,6 +195,9 @@ public final class G7Sensor: G7BluetoothManagerDelegate { func peripheralDidDisconnect(_ manager: G7BluetoothManager, peripheralManager: G7PeripheralManager, wasRemoteDisconnect: Bool) { if let sensorID = sensorID, sensorID == peripheralManager.peripheral.name { + // Sometimes we do not receive the backfillFinished message before disconnect + flushBackfillBuffer() + let suspectedEndOfSession: Bool self.log.info("Sensor disconnected: wasRemoteDisconnect:%{public}@", String(describing: wasRemoteDisconnect)) @@ -249,18 +252,23 @@ public final class G7Sensor: G7BluetoothManagerDelegate { } } case .backfillFinished: - if backfillBuffer.count > 0 { - delegateQueue.async { - self.delegate?.sensor(self, didReadBackfill: self.backfillBuffer) - self.backfillBuffer = [] - } - } + flushBackfillBuffer() default: self.delegate?.sensor(self, logComms: response.hexadecimalString) break } } + func flushBackfillBuffer() { + if backfillBuffer.count > 0 { + let backfill = backfillBuffer + self.backfillBuffer = [] + delegateQueue.async { + self.delegate?.sensor(self, didReadBackfill: backfill) + } + } + } + func bluetoothManager(_ manager: G7BluetoothManager, didReceiveBackfillResponse response: Data) { log.debug("Received backfill response: %{public}@", response.hexadecimalString) diff --git a/G7SensorKitTests/G7GlucoseMessageTests.swift b/G7SensorKitTests/G7GlucoseMessageTests.swift index 739f7e0..5d61445 100644 --- a/G7SensorKitTests/G7GlucoseMessageTests.swift +++ b/G7SensorKitTests/G7GlucoseMessageTests.swift @@ -131,6 +131,23 @@ final class G7GlucoseMessageTests: XCTestCase { let data = Data(hexadecimalString: "cf5802008f00060f10")! let message = G7BackfillMessage(data: data)! XCTAssertEqual(153807, message.timestamp) + XCTAssertEqual(143, message.glucose) + XCTAssertEqual(.known(.ok), message.algorithmState) + XCTAssertNil(message.condition) + XCTAssertEqual(false, message.glucoseIsDisplayOnly) + XCTAssertEqual(true, message.hasReliableGlucose) + } + + func testBackfillTimestampWithHighByte() { + let data = Data(hexadecimalString: "f20e0d00ba00060ffb")! + let message = G7BackfillMessage(data: data)! + XCTAssertEqual(855794, message.timestamp) + } + + func testBackfillCalibration() { + let data = Data(hexadecimalString: "f63d00008500061efe")! + let message = G7BackfillMessage(data: data)! + XCTAssertEqual(true, message.glucoseIsDisplayOnly) } }