From 067dc8dd3b1f5816dd8c97b234446d41aab03e7a Mon Sep 17 00:00:00 2001 From: Mohammad Abdul Sahil <127765312+abdulsaheel@users.noreply.github.com> Date: Sat, 19 Sep 2026 02:53:32 +0530 Subject: [PATCH] gate GET_BODY_LOCATION_AND_STATUS on cmd_status too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit one more sibling of the getClock/getDataRange/getAlarmTime/selectWrist status-gate pattern — a failed outer reply's body bytes are stale and would mint a confident (and wrong) body-location reading. this one fell through the cracks tonight when its plan-stage agent's structured output kept failing schema validation and never reached implementation. --- lib/src/control.dart | 19 +++++++++----- test/control_plane_offsets_test.dart | 38 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/src/control.dart b/lib/src/control.dart index 1b07e5e..0369444 100644 --- a/lib/src/control.dart +++ b/lib/src/control.dart @@ -1007,12 +1007,19 @@ CmdResponse? parseCommandResponse(Uint8List inner, // starts after the response header, i.e. at payload[2] — reading from // payload[0] landed on the echoed-seq/status pair, so `locationRaw` was // the status byte and resolved to "wrist" on every successful reply. - dec['body_location_status'] = BodyLocationStatusResponse( - revision: payload[2], - locationRaw: payload[5], - confidence: payload[4], // constant 0xFF on every observed reply - status: payload[3], // constant 0 - ); + // + // Status-gated like the siblings above: a failed outer reply does not + // populate the body, so its bytes are stale and would otherwise mint a + // confident (and wrong) body-location reading. + final statusOk = !profile.isGen5 || status == 1; + if (statusOk) { + dec['body_location_status'] = BodyLocationStatusResponse( + revision: payload[2], + locationRaw: payload[5], + confidence: payload[4], // constant 0xFF on every observed reply + status: payload[3], // constant 0 + ); + } } else if ((op == Cmd.enterHighFreqSync || op == Cmd.exitHighFreqSync)) { dec['high_freq_sync'] = HighFreqSyncResponse(op); } else if (op == Cmd.selectWrist && payload.length >= 3) { diff --git a/test/control_plane_offsets_test.dart b/test/control_plane_offsets_test.dart index cc82f6b..1f881fe 100644 --- a/test/control_plane_offsets_test.dart +++ b/test/control_plane_offsets_test.dart @@ -347,6 +347,44 @@ void main() { expect(r.decoded['alarm_active'], isTrue); }); + test( + 'GET_BODY_LOCATION_AND_STATUS: gen5 a non-success outer status emits ' + 'nothing', () { + // Same convention as getClock/getDataRange/getAlarmTime: a failure + // reply's body bytes are stale leftovers from a prior successful read. + final body = [0x01, 0x00, 0xFF, 0x07]; + for (final status in [0, 2, 3]) { + final r = parseCommandResponse( + cmdResponse(Cmd.getBodyLocationAndStatus, body, status: status), + profile: BandProfile.gen5)!; + expect(r.decoded.containsKey('body_location_status'), isFalse, + reason: 'status=$status'); + } + }); + + test('GET_BODY_LOCATION_AND_STATUS: gen5 a success outer status still ' + 'decodes normally', () { + final body = [0x01, 0x00, 0xFF, 0x07]; + final r = parseCommandResponse( + cmdResponse(Cmd.getBodyLocationAndStatus, body, status: 1), + profile: BandProfile.gen5)!; + final decoded = + r.decoded['body_location_status'] as BodyLocationStatusResponse; + expect(decoded.revision, 1); + expect(decoded.locationRaw, 7); + }); + + test('GET_BODY_LOCATION_AND_STATUS: gen4 is left ungated on outer status', + () { + final body = [0x01, 0x00, 0xFF, 0x07]; + final r = parseCommandResponse( + cmdResponse(Cmd.getBodyLocationAndStatus, body, status: 0))!; + final decoded = + r.decoded['body_location_status'] as BodyLocationStatusResponse; + expect(decoded.revision, 1); + expect(decoded.locationRaw, 7); + }); + test('gen5 GET_CUSTOM_ADVERTISING_NAME (0x8D) decodes like gen4 0x4C', () { // Reply body: revision, status, length, then the ASCII name — the same // shape at the same offsets on both generations.