Skip to content

docs(protocol): document the COMMAND_RESPONSE body (#894, #891) - #915

Merged
ryanbr merged 2 commits into
mainfrom
docs/command-response-body
Jul 28, 2026
Merged

docs(protocol): document the COMMAND_RESPONSE body (#894, #891)#915
ryanbr merged 2 commits into
mainfrom
docs/command-response-body

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Docs only. Fills a gap #899 opened and I did not close.

The gap

PROTOCOL.md documents the envelope down to payload [11..] and stops — so the two bytes every reply actually opens with are described nowhere, even though #899 made both decoders publish them and added CommandResult to the shared schema.

Three places were describing the same layout ad hoc instead: DeviceConfigReadProbe and FeatureFlagProbe each say "the 2-byte response header (pay[1] is the 5/MG result code)" in their own comments, and the ECG paragraph mentions a result code in passing.

New §2.4 states it once, for both families — resp_cmd/resp_seq/result at [6][7][8] on 4.0 and the same +4 on 5/MG — plus that resp_seq is the strap's own counter, not the echoed host-assigned envelope seq (a real capture shows envelope seq 147 beside resp_seq 2, and a repeated resp_seq is what identified a duplicated write in #791).

The part worth writing down

The first body byte. GET_BATTERY_LEVEL puts the charge percentage there — 47 in the hardware-confirmed fixture — so the slot carries real data. On every other captured command it has only ever been 1:

capture command result first body byte
real 5/MG GET_BATTERY_LEVEL SUCCESS 47
real 5/MG GET_DATA_RANGE SUCCESS 1
real MG SELECT_WRIST, accepted SUCCESS 1
real MG SELECT_WRIST, refused FAILURE 1
real MG TOGGLE_LABRADOR_* SUCCESS 1

For the wrist and ECG commands what that 1 means is recorded as open. Sending SELECT_WRIST with argument 0 got 1 back, which refutes an echo of the request — but every frame captured had a stored value of 1, so "reads back stored state" and "this handler writes a literal 1" predict identically on all of them. The decoder deliberately leaves the byte unnamed; this documents why, and what would settle it (a reply from a strap whose stored value is 0 — the #891 ask).

Mechanics

Old §2.4 Checksums and §2.5 Reassembly shift to §2.5/§2.6. I checked: no cross-reference anywhere in the tree points at either, and the only internal PROTOCOL.md section reference is §2.2, unchanged.

Section is in §2 near the envelope, so it does not collide with #896, #898 or #907 — all three touch PROTOCOL.md but in the ECG and device-config sections.

Every claim was verified against source before writing: both platforms' offsets read out of the decoders, the PENDING-then-SUCCESS and UNSUPPORTED(3)-on-haptics results traced to the captures that produced them, and the body-byte table recomputed from the committed fixtures rather than copied from the issue thread.

PROTOCOL.md documented the envelope down to `payload [11..]` and stopped, so the
two bytes every reply actually opens with were described nowhere — even though
#899 made both decoders publish them and added CommandResult to the schema.

Three places had been describing the same layout ad hoc instead:
DeviceConfigReadProbe and FeatureFlagProbe each say "the 2-byte response header
(pay[1] is the 5/MG result code)" in their own comments, and the ECG paragraph
mentions a result code in passing. New §2.4 states it once, for both families:
resp_cmd/resp_seq/result at [6][7][8] on 4.0 and the same +4 on 5/MG.

It also records that resp_seq is the strap's counter rather than the echoed
host-assigned envelope seq — a real capture shows envelope seq 147 beside
resp_seq 2, and the repeated-resp_seq signature is what identified a duplicated
write in #791.

The part worth writing down is the first BODY byte. GET_BATTERY_LEVEL puts the
charge percentage there (47 in the hardware-confirmed fixture), so the slot
carries real data, but on every other captured command it has only ever been 1.
For the wrist and ECG toggles what that means is open: sending SELECT_WRIST with
argument 0 got 1 back, which refutes an echo of the request, but every frame
captured so far had a stored value of 1, so "reads back stored state" and "this
handler writes a literal 1" predict identically on all of them. Documented as
open, with what would settle it, rather than resolved — the decoder deliberately
leaves the byte unnamed and this explains why to the next reader.

Old §2.4 Checksums and §2.5 Reassembly shift to §2.5/§2.6. No cross-reference in
the tree points at either; the only internal PROTOCOL.md section reference is
§2.2, unchanged.

Every claim checked against source before writing: both platforms' offsets from
the decoders, the PENDING-then-SUCCESS and UNSUPPORTED(3)-on-haptics results from
the captures that produced them, and the body-byte table recomputed from the
committed fixtures.
ryanbr added a commit that referenced this pull request Jul 28, 2026
Cutting 9.2.1 left main failing the i18n gate. appchangelog-gen.py writes the
Android title as a raw Kotlin literal:

    title = "{esc_kt(wn["title"])}",                       # appchangelog-gen.py:60

so every release adds one hardcoded UI string, which is exactly what #878
described. That issue is closed, but the generator was never changed — 9.2.0's
title was localised by hand in #886 and the issue closed on the strength of it.
Same thing here, so the same manual fix, and #878 reopened for the generator.

Two things made it worse than a normal red check. The release workflow pushes
with GITHUB_TOKEN, and GitHub does not trigger workflows on those pushes, so no
run exists on 7054aef or 21e36a2 — main was red with nothing reporting it. And
the audit is a whole-tree gate rather than a diff, so it red-checks every open PR
on a line none of them touched; #915 hit it first.

Key follows the established convention — first six slug words plus
sha1(value)[:8], verified by reproducing 9.2.0's key from its title — and the
title is translated in all five locales alongside the English, matching how every
earlier release title is carried.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant