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.