From f17d4b477460967d4af4a820b8dad97ce20fc216 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 8 Oct 2026 19:25:10 -0500 Subject: [PATCH] Parse G7 glucose messages with a nonzero status A failed sensor answers the glucose request with status 0x80, its last reading and algorithm state 27 (transmitterFailed). The parser rejected any nonzero status, so the manager logged "Unable to handle glucose control response" every five minutes, never learned the state, and showed the sensor as connecting instead of raising the sensor-failed alert. The message now keeps the status and decodes the timestamp, sequence, age and algorithm state for any status. Glucose, prediction and trend are nil when the status is nonzero, so a reading the sensor did not mark current is never stored. --- G7SensorKit/Messages/G7GlucoseMessage.swift | 16 +++++++--------- G7SensorKitTests/G7GlucoseMessageTests.swift | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/G7SensorKit/Messages/G7GlucoseMessage.swift b/G7SensorKit/Messages/G7GlucoseMessage.swift index e0594a3..2aac30a 100644 --- a/G7SensorKit/Messages/G7GlucoseMessage.swift +++ b/G7SensorKit/Messages/G7GlucoseMessage.swift @@ -10,8 +10,8 @@ import Foundation import LoopKit public struct G7GlucoseMessage: SensorMessage, Equatable { - //public let status: UInt8 - //public let sequence: UInt32 + /// Nonzero when the sensor has no current reading, e.g. 0x80 from a failed sensor; glucose is then nil. + public let status: UInt8 public let glucose: UInt16? public let predicted: UInt16? public let glucoseIsDisplayOnly: Bool @@ -84,9 +84,7 @@ public struct G7GlucoseMessage: SensorMessage, Equatable { return nil } - guard data[1] == 00 else { - return nil - } + status = data[1] messageTimestamp = data[2..<6].toInt() @@ -95,7 +93,7 @@ public struct G7GlucoseMessage: SensorMessage, Equatable { age = data[10..<12].to(UInt16.self) let glucoseData = data[12..<14].to(UInt16.self) - if glucoseData != 0xffff { + if glucoseData != 0xffff, status == 0 { glucose = glucoseData & 0xfff glucoseIsDisplayOnly = (data[18] & 0x10) > 0 } else { @@ -104,7 +102,7 @@ public struct G7GlucoseMessage: SensorMessage, Equatable { } let predictionData = data[16..<18].to(UInt16.self) - if predictionData != 0xffff { + if predictionData != 0xffff, status == 0 { predicted = predictionData & 0xfff } else { predicted = nil @@ -112,7 +110,7 @@ public struct G7GlucoseMessage: SensorMessage, Equatable { algorithmState = AlgorithmState(rawValue: data[14]) - if data[15] == 0x7f { + if data[15] == 0x7f || status != 0 { trend = nil } else { trend = Double(Int8(bitPattern: data[15])) / 10 @@ -125,6 +123,6 @@ public struct G7GlucoseMessage: SensorMessage, Equatable { extension G7GlucoseMessage: CustomDebugStringConvertible { public var debugDescription: String { - return "G7GlucoseMessage(glucose:\(String(describing: glucose)), sequence:\(sequence) glucoseIsDisplayOnly:\(glucoseIsDisplayOnly) state:\(String(describing: algorithmState)) messageTimestamp:\(messageTimestamp) age:\(age), data:\(data.hexadecimalString))" + return "G7GlucoseMessage(status:\(status) glucose:\(String(describing: glucose)), sequence:\(sequence) glucoseIsDisplayOnly:\(glucoseIsDisplayOnly) state:\(String(describing: algorithmState)) messageTimestamp:\(messageTimestamp) age:\(age), data:\(data.hexadecimalString))" } } diff --git a/G7SensorKitTests/G7GlucoseMessageTests.swift b/G7SensorKitTests/G7GlucoseMessageTests.swift index 5d61445..dc5cafa 100644 --- a/G7SensorKitTests/G7GlucoseMessageTests.swift +++ b/G7SensorKitTests/G7GlucoseMessageTests.swift @@ -127,6 +127,21 @@ final class G7GlucoseMessageTests: XCTestCase { XCTAssertEqual(907385, message.glucoseTimestamp) } + func testFailedSensorStatusStillReportsAlgorithmState() { + // A failed sensor keeps answering with status 0x80 and its last reading, 103 mg/dL from 2.7 h earlier. + let data = Data(hexadecimalString: "4e80856f0a00f6010001f32567001b02670000")! + let message = G7GlucoseMessage(data: data)! + XCTAssertEqual(0x80, message.status) + XCTAssertEqual(.known(.transmitterFailed), message.algorithmState) + XCTAssertTrue(message.algorithmState.sensorFailed) + XCTAssertNil(message.glucose) + XCTAssertNil(message.predicted) + XCTAssertNil(message.trend) + XCTAssertEqual(683909, message.messageTimestamp) + XCTAssertEqual(9715, message.age) + XCTAssertEqual(502, message.sequence) + } + func testBackfill() { let data = Data(hexadecimalString: "cf5802008f00060f10")! let message = G7BackfillMessage(data: data)!