diff --git a/G7SensorKit.xcodeproj/project.pbxproj b/G7SensorKit.xcodeproj/project.pbxproj index c6c8c6a..0011513 100644 --- a/G7SensorKit.xcodeproj/project.pbxproj +++ b/G7SensorKit.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ 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 */; }; C139829829295D7D0047DB5F /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17F514A291EB6F000555EB5 /* HKUnit.swift */; }; @@ -106,6 +108,8 @@ /* 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 = ""; }; C109F149291ECCE2008EA5B6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -202,6 +206,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3B0FD2A52D803BF100E5E921 /* LoopKitUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -336,6 +341,7 @@ C17F5128291EAFA100555EB5 /* Frameworks */ = { isa = PBXGroup; children = ( + 3B0FD2A42D803BF000E5E921 /* LoopKitUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -366,6 +372,7 @@ C17F5154291EBD7100555EB5 /* Extensions */ = { isa = PBXGroup; children = ( + B60BB2E32BC649DA00D2BB39 /* Bundle.swift */, C17F5155291EBD8600555EB5 /* Image.swift */, ); path = Extensions; @@ -636,6 +643,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/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/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, 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/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 d5c0b46..aa88883 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]) @@ -193,8 +195,13 @@ 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 - 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 @@ -215,7 +222,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 @@ -232,7 +240,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?: @@ -244,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: - // We ignore all other known opcodes + 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/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/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) } } 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/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", 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/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 { 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() 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