Skip to content

Commit 87c599c

Browse files
authored
Merge pull request #36 from LoopKit/main
Update G7SensorKit tidepool-merge with improvements from main
2 parents 8458d60 + a97e428 commit 87c599c

File tree

16 files changed

+150
-46
lines changed

16 files changed

+150
-46
lines changed

G7SensorKit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
3B0FD2A52D803BF100E5E921 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0FD2A42D803BF000E5E921 /* LoopKitUI.framework */; };
11+
B60BB2E42BC649DA00D2BB39 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BB2E32BC649DA00D2BB39 /* Bundle.swift */; };
1012
C109F14A291ECCE2008EA5B6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C109F149291ECCE2008EA5B6 /* Assets.xcassets */; };
1113
C109F14C291ED66F008EA5B6 /* G7GlucoseMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C109F14B291ED66F008EA5B6 /* G7GlucoseMessageTests.swift */; };
1214
C139829829295D7D0047DB5F /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17F514A291EB6F000555EB5 /* HKUnit.swift */; };
@@ -106,6 +108,8 @@
106108
/* End PBXCopyFilesBuildPhase section */
107109

108110
/* Begin PBXFileReference section */
111+
3B0FD2A42D803BF000E5E921 /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
112+
B60BB2E32BC649DA00D2BB39 /* Bundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bundle.swift; path = G7SensorKitUI/Extensions/Bundle.swift; sourceTree = SOURCE_ROOT; };
109113
C1086B0E29C9169100D46E65 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
110114
C1086B0F29C9169100D46E65 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
111115
C109F149291ECCE2008EA5B6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -202,6 +206,7 @@
202206
isa = PBXFrameworksBuildPhase;
203207
buildActionMask = 2147483647;
204208
files = (
209+
3B0FD2A52D803BF100E5E921 /* LoopKitUI.framework in Frameworks */,
205210
);
206211
runOnlyForDeploymentPostprocessing = 0;
207212
};
@@ -336,6 +341,7 @@
336341
C17F5128291EAFA100555EB5 /* Frameworks */ = {
337342
isa = PBXGroup;
338343
children = (
344+
3B0FD2A42D803BF000E5E921 /* LoopKitUI.framework */,
339345
);
340346
name = Frameworks;
341347
sourceTree = "<group>";
@@ -366,6 +372,7 @@
366372
C17F5154291EBD7100555EB5 /* Extensions */ = {
367373
isa = PBXGroup;
368374
children = (
375+
B60BB2E32BC649DA00D2BB39 /* Bundle.swift */,
369376
C17F5155291EBD8600555EB5 /* Image.swift */,
370377
);
371378
path = Extensions;
@@ -636,6 +643,7 @@
636643
buildActionMask = 2147483647;
637644
files = (
638645
C17F5108291EAC9D00555EB5 /* G7SettingsView.swift in Sources */,
646+
B60BB2E42BC649DA00D2BB39 /* Bundle.swift in Sources */,
639647
C17F5157291EBD9900555EB5 /* TimeInterval.swift in Sources */,
640648
C19C9F4E29C91C4C00A6D3D0 /* LocalizedString.swift in Sources */,
641649
C17F5156291EBD8600555EB5 /* Image.swift in Sources */,

G7SensorKit/AlgorithmError.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ extension AlgorithmState {
3737
return LocalizedString("Sensor is OK", comment: "The description of sensor algorithm state when sensor is ok.")
3838
case .stopped:
3939
return LocalizedString("Sensor is stopped", comment: "The description of sensor algorithm state when sensor is stopped.")
40-
case .warmup, .questionMarks:
40+
case .warmup, .temporarySensorIssue:
4141
return LocalizedString("Sensor is warming up", comment: "The description of sensor algorithm state when sensor is warming up.")
4242
case .expired:
4343
return LocalizedString("Sensor expired", comment: "The description of sensor algorithm state when sensor is expired.")
4444
case .sensorFailed:
4545
return LocalizedString("Sensor failed", comment: "The description of sensor algorithm state when sensor failed.")
46+
default:
47+
return "Sensor state: \(String(describing: state))"
4648
}
4749
case .unknown(let rawValue):
4850
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)

G7SensorKit/AlgorithmState.swift

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,29 @@ public enum AlgorithmState: RawRepresentable {
1515
public enum State: RawValue {
1616
case stopped = 1
1717
case warmup = 2
18+
case excessNoise = 3
19+
case firstOfTwoBGsNeeded = 4
20+
case secondOfTwoBGsNeeded = 5
1821
case ok = 6
19-
case questionMarks = 18
22+
case needsCalibration = 7
23+
case calibrationError1 = 8
24+
case calibrationError2 = 9
25+
case calibrationLinearityFitFailure = 10
26+
case sensorFailedDuetoCountsAberration = 11
27+
case sensorFailedDuetoResidualAberration = 12
28+
case outOfCalibrationDueToOutlier = 13
29+
case outlierCalibrationRequest = 14
30+
case sessionExpired = 15
31+
case sessionFailedDueToUnrecoverableError = 16
32+
case sessionFailedDueToTransmitterError = 17
33+
case temporarySensorIssue = 18
34+
case sensorFailedDueToProgressiveSensorDecline = 19
35+
case sensorFailedDueToHighCountsAberration = 20
36+
case sensorFailedDueToLowCountsAberration = 21
37+
case sensorFailedDueToRestart = 22
2038
case expired = 24
2139
case sensorFailed = 25
40+
case sessionEnded = 26
2241
}
2342

2443
case known(State)
@@ -48,7 +67,7 @@ public enum AlgorithmState: RawRepresentable {
4867
}
4968

5069
switch state {
51-
case .sensorFailed:
70+
case .sensorFailed, .sensorFailedDuetoCountsAberration, .sensorFailedDuetoResidualAberration, .sessionFailedDueToTransmitterError, .sessionFailedDueToUnrecoverableError, .sensorFailedDueToProgressiveSensorDecline, .sensorFailedDueToHighCountsAberration, .sensorFailedDueToLowCountsAberration, .sensorFailedDueToRestart:
5271
return true
5372
default:
5473
return false
@@ -68,13 +87,13 @@ public enum AlgorithmState: RawRepresentable {
6887
}
6988
}
7089

71-
public var isInSensorError: Bool {
90+
public var hasTemporaryError: Bool {
7291
guard case .known(let state) = self else {
7392
return false
7493
}
7594

7695
switch state {
77-
case .questionMarks:
96+
case .temporarySensorIssue:
7897
return true
7998
default:
8099
return false
@@ -88,14 +107,10 @@ public enum AlgorithmState: RawRepresentable {
88107
}
89108

90109
switch state {
91-
case .stopped,
92-
.warmup,
93-
.questionMarks,
94-
.expired,
95-
.sensorFailed:
96-
return false
97110
case .ok:
98111
return true
112+
default:
113+
return false
99114
}
100115
}
101116
}

G7SensorKit/BluetoothServices.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,13 @@ extension CBUUIDRawValue where RawValue == String {
1515
}
1616
}
1717

18-
1918
enum SensorServiceUUID: String, CBUUIDRawValue {
20-
case deviceInfo = "180A"
2119
case advertisement = "FEBC"
2220
case cgmService = "F8083532-849E-531C-C594-30F1F86A4EA5"
2321

2422
case serviceB = "F8084532-849E-531C-C594-30F1F86A4EA5"
2523
}
2624

27-
28-
enum DeviceInfoCharacteristicUUID: String, CBUUIDRawValue {
29-
// Read
30-
// "DexcomUN"
31-
case manufacturerNameString = "2A29"
32-
}
33-
34-
3525
enum CGMServiceCharacteristicUUID: String, CBUUIDRawValue {
3626

3727
// Read/Notify
@@ -61,7 +51,6 @@ extension G7PeripheralManager.Configuration {
6151
return G7PeripheralManager.Configuration(
6252
serviceCharacteristics: [
6353
SensorServiceUUID.cgmService.cbUUID: [
64-
//CGMServiceCharacteristicUUID.communication.cbUUID, // Unused for now
6554
CGMServiceCharacteristicUUID.authentication.cbUUID,
6655
CGMServiceCharacteristicUUID.control.cbUUID,
6756
CGMServiceCharacteristicUUID.backfill.cbUUID,

G7SensorKit/G7CGMManager/G7BackfillMessage.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,19 @@ public struct G7BackfillMessage: Equatable {
3333
return nil
3434
}
3535

36-
timestamp = data[0..<4].toInt()
36+
37+
timestamp = data[0..<3].toInt()
3738

3839
let glucoseBytes = data[4..<6].to(UInt16.self)
3940

4041
if glucoseBytes != 0xffff {
4142
glucose = glucoseBytes & 0xfff
42-
glucoseIsDisplayOnly = (glucoseBytes & 0xf000) > 0
4343
} else {
4444
glucose = nil
45-
glucoseIsDisplayOnly = false
4645
}
4746

47+
glucoseIsDisplayOnly = data[7] & 0x10 != 0
48+
4849
algorithmState = AlgorithmState(rawValue: data[6])
4950

5051
if data[8] == 0x7f {

G7SensorKit/G7CGMManager/G7BluetoothManager.swift

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ class G7BluetoothManager: NSObject {
156156

157157
private func managerQueue_stopScanning() {
158158
if centralManager.isScanning {
159-
log.debug("Stopping scan")
159+
log.default("Stopping scan")
160160
centralManager.stopScan()
161161
delegate?.bluetoothManagerScanningStatusDidChange(self)
162162
}
@@ -167,7 +167,7 @@ class G7BluetoothManager: NSObject {
167167

168168
managerQueue.sync {
169169
if centralManager.isScanning {
170-
log.debug("Stopping scan on disconnect")
170+
log.default("Stopping scan on disconnect")
171171
centralManager.stopScan()
172172
delegate?.bluetoothManagerScanningStatusDidChange(self)
173173
}
@@ -178,6 +178,15 @@ class G7BluetoothManager: NSObject {
178178
}
179179
}
180180

181+
func centralManager(_ central: CBCentralManager, connectionEventDidOccur event: CBConnectionEvent, for peripheral: CBPeripheral) {
182+
managerQueue.async {
183+
if self.activePeripheralIdentifier == nil {
184+
self.log.default("Discovered peripheral from connectionEventDidOccur %{public}@", peripheral.identifier.uuidString)
185+
self.handleDiscoveredPeripheral(peripheral)
186+
}
187+
}
188+
}
189+
181190
private func managerQueue_scanForPeripheral() {
182191
dispatchPrecondition(condition: .onQueue(managerQueue))
183192

@@ -191,19 +200,26 @@ class G7BluetoothManager: NSObject {
191200
}
192201

193202
if let peripheralID = activePeripheralIdentifier, let peripheral = centralManager.retrievePeripherals(withIdentifiers: [peripheralID]).first {
194-
log.debug("Retrieved peripheral %{public}@", peripheral.identifier.uuidString)
203+
log.default("Retrieved peripheral %{public}@", peripheral.identifier.uuidString)
195204
handleDiscoveredPeripheral(peripheral)
196205
} else {
197206
for peripheral in centralManager.retrieveConnectedPeripherals(withServices: [
198207
SensorServiceUUID.advertisement.cbUUID,
199208
SensorServiceUUID.cgmService.cbUUID
200209
]) {
210+
log.default("Found system-connected peripheral: %{public}@", peripheral.identifier.uuidString)
201211
handleDiscoveredPeripheral(peripheral)
202212
}
203213
}
204214

205215
if activePeripheral == nil {
206-
log.debug("Scanning for peripherals")
216+
log.default("Scanning for peripherals and listening for connection events")
217+
218+
centralManager.registerForConnectionEvents(options: [CBConnectionEventMatchingOption.serviceUUIDs: [
219+
SensorServiceUUID.advertisement.cbUUID,
220+
SensorServiceUUID.cgmService.cbUUID
221+
]])
222+
207223
centralManager.scanForPeripherals(withServices: [
208224
SensorServiceUUID.advertisement.cbUUID
209225
],
@@ -257,7 +273,7 @@ class G7BluetoothManager: NSObject {
257273
if let delegate = delegate {
258274
switch delegate.bluetoothManager(self, shouldConnectPeripheral: peripheral) {
259275
case .makeActive:
260-
log.debug("Making peripheral active: %{public}@", peripheral.identifier.uuidString)
276+
log.default("Making peripheral active: %{public}@", peripheral.identifier.uuidString)
261277

262278
if let peripheralManager = activePeripheralManager {
263279
peripheralManager.peripheral = peripheral
@@ -273,7 +289,7 @@ class G7BluetoothManager: NSObject {
273289
self.centralManager.connect(peripheral)
274290

275291
case .connect:
276-
log.debug("Connecting to peripheral: %{public}@", peripheral.identifier.uuidString)
292+
log.default("Connecting to peripheral: %{public}@", peripheral.identifier.uuidString)
277293
self.centralManager.connect(peripheral)
278294
let peripheralManager = G7PeripheralManager(
279295
peripheral: peripheral,
@@ -311,7 +327,7 @@ extension G7BluetoothManager: CBCentralManagerDelegate {
311327
fallthrough
312328
@unknown default:
313329
if central.isScanning {
314-
log.debug("Stopping scan on central not powered on")
330+
log.default("Stopping scan on central not powered on")
315331
central.stopScan()
316332
delegate?.bluetoothManagerScanningStatusDidChange(self)
317333
}
@@ -332,7 +348,7 @@ extension G7BluetoothManager: CBCentralManagerDelegate {
332348
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
333349
dispatchPrecondition(condition: .onQueue(managerQueue))
334350

335-
log.info("%{public}@: %{public}@, data = %{public}@", #function, peripheral, String(describing: advertisementData))
351+
log.default("%{public}@: %{public}@, data = %{public}@", #function, peripheral, String(describing: advertisementData))
336352

337353
managerQueue.async {
338354
self.handleDiscoveredPeripheral(peripheral)

G7SensorKit/G7CGMManager/G7CGMManager.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ extension G7CGMManager: G7SensorDelegate {
323323
}
324324
}
325325

326+
public func sensor(_ sensor: G7Sensor, logComms comms: String) {
327+
logDeviceCommunication("Sensor comms \(comms)", type: .receive)
328+
}
329+
330+
326331
public func sensor(_ sensor: G7Sensor, didError error: Error) {
327332
logDeviceCommunication("Sensor error \(error)", type: .error)
328333
}
@@ -335,6 +340,17 @@ extension G7CGMManager: G7SensorDelegate {
335340
return
336341
}
337342

343+
if message.algorithmState.sensorFailed {
344+
logDeviceCommunication("Detected failed sensor... scanning for new sensor.", type: .receive)
345+
scanForNewSensor()
346+
}
347+
348+
if message.algorithmState == .known(.sessionEnded) {
349+
logDeviceCommunication("Detected session ended... scanning for new sensor.", type: .receive)
350+
scanForNewSensor()
351+
}
352+
353+
338354
guard let activationDate = sensor.activationDate else {
339355
logDeviceCommunication("Unable to process sensor reading without activation date.", type: .error)
340356
return

G7SensorKit/G7CGMManager/G7Sensor.swift

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public protocol G7SensorDelegate: AnyObject {
1919

2020
func sensor(_ sensor: G7Sensor, didError error: Error)
2121

22+
func sensor(_ sensor: G7Sensor, logComms comms: String)
23+
2224
func sensor(_ sensor: G7Sensor, didRead glucose: G7GlucoseMessage)
2325

2426
func sensor(_ sensor: G7Sensor, didReadBackfill backfill: [G7BackfillMessage])
@@ -193,8 +195,13 @@ public final class G7Sensor: G7BluetoothManagerDelegate {
193195
func peripheralDidDisconnect(_ manager: G7BluetoothManager, peripheralManager: G7PeripheralManager, wasRemoteDisconnect: Bool) {
194196
if let sensorID = sensorID, sensorID == peripheralManager.peripheral.name {
195197

198+
// Sometimes we do not receive the backfillFinished message before disconnect
199+
flushBackfillBuffer()
200+
196201
let suspectedEndOfSession: Bool
197-
if pendingAuth && wasRemoteDisconnect {
202+
203+
self.log.info("Sensor disconnected: wasRemoteDisconnect:%{public}@", String(describing: wasRemoteDisconnect))
204+
if pendingAuth, wasRemoteDisconnect {
198205
suspectedEndOfSession = true // Normal disconnect without auth is likely that G7 app stopped this session
199206
} else {
200207
suspectedEndOfSession = false
@@ -215,7 +222,8 @@ public final class G7Sensor: G7BluetoothManagerDelegate {
215222
}
216223

217224
/// The Dexcom G7 advertises a peripheral name of "DXCMxx", and later reports a full name of "Dexcomxx"
218-
if name.hasPrefix("DXCM") {
225+
/// Dexcom One+ peripheral name start with "DX02"
226+
if name.hasPrefix("DXCM") || name.hasPrefix("DX02"){
219227
// If we're following this name or if we're scanning, connect
220228
if let sensorName = sensorID, name.suffix(2) == sensorName.suffix(2) {
221229
return .makeActive
@@ -232,7 +240,7 @@ public final class G7Sensor: G7BluetoothManagerDelegate {
232240

233241
guard response.count > 0 else { return }
234242

235-
log.debug("Received control response: %{public}@", response.hexadecimalString)
243+
log.default("Received control response: %{public}@", response.hexadecimalString)
236244

237245
switch G7Opcode(rawValue: response[0]) {
238246
case .glucoseTx?:
@@ -244,18 +252,23 @@ public final class G7Sensor: G7BluetoothManagerDelegate {
244252
}
245253
}
246254
case .backfillFinished:
247-
if backfillBuffer.count > 0 {
248-
delegateQueue.async {
249-
self.delegate?.sensor(self, didReadBackfill: self.backfillBuffer)
250-
self.backfillBuffer = []
251-
}
252-
}
255+
flushBackfillBuffer()
253256
default:
254-
// We ignore all other known opcodes
257+
self.delegate?.sensor(self, logComms: response.hexadecimalString)
255258
break
256259
}
257260
}
258261

262+
func flushBackfillBuffer() {
263+
if backfillBuffer.count > 0 {
264+
let backfill = backfillBuffer
265+
self.backfillBuffer = []
266+
delegateQueue.async {
267+
self.delegate?.sensor(self, didReadBackfill: backfill)
268+
}
269+
}
270+
}
271+
259272
func bluetoothManager(_ manager: G7BluetoothManager, didReceiveBackfillResponse response: Data) {
260273

261274
log.debug("Received backfill response: %{public}@", response.hexadecimalString)

0 commit comments

Comments
 (0)