Skip to content

docs: expand protocol coverage and clarify WHOOP 4/5 compatibility - #2183

Merged
ryanbr merged 11 commits into
ryanbr:mainfrom
bhelm:feature/protocol-legacy-cleanup
Sep 15, 2026
Merged

ryanbr merged 11 commits into
ryanbr:mainfrom
bhelm:feature/protocol-legacy-cleanup

Conversation

@bhelm

@bhelm bhelm commented Sep 13, 2026

Copy link
Copy Markdown

What this PR does

Expands and consolidates the existing protocol reference so developers can construct more WHOOP 5/MG requests, decode sensor records, and implement session and recovery behavior with fewer assumptions.

Adds concrete WHOOP 5/MG request layouts for at least 30 additional command IDs, including nine image-transfer and certificate/authorization commands with documented request and response bodies. This counts distinct command IDs with newly explicit request layouts, rather than repeated mentions or relocated content; the nine paired request/response contracts are included in the 30. It measures documentation additions, not newly supported application commands or a percentage of the entire protocol.

  • Expands ECG raw/filtered record layouts, signed sample decoding, contact fields, live/storage controls, settling behavior, and repeated-start edge cases.
  • Clarifies alarms, history transfer and reconnect behavior, configuration, IMU/sensor records, and update/authorization boundaries, including the distinction between acknowledgements, completed operations, and persistent changes.
  • Replaces the long entry page with shared concepts, separate WHOOP 4 and WHOOP 5/MG profiles, and focused topic chapters. Device/firmware scope is centralized; shared WHOOP 5/MG image behavior does not imply identical hardware capabilities.
  • Removes contradicted explanations and duplicated layouts, updates legacy pages to refer to the canonical reference, and preserves existing section anchors without a visible compatibility-link appendix.

This is a documentation-only contribution with executable documentation examples. Application behavior, the BLE command allowlist, and existing capture/analysis tools are unchanged. Legacy tool assumptions that differ from the reference are explicitly qualified.

Remaining limits are stated in the relevant chapters: absolute ECG sample-rate/voltage calibration, some format/quality semantics, and complete update acceptance still need additional validation. This does not claim complete protocol coverage or a tested update installation.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

How it was tested

  • python3 docs/protocol-examples/validate_examples.py passes. These are constructed field-arithmetic examples covering ECG bounds, padding and signed values, contact boundaries, sensor clipping, RR arithmetic, and selected response/configuration rules; they do not validate CRC framing, device behavior, or calibration.
  • Local documentation checks pass for links and anchors, command/configuration inventories, preserved entry anchors, and whitespace.
  • An independent documentation consistency review was completed; reported inconsistencies were corrected and the affected checks rerun.
  • No new real-hardware test was performed for this documentation contribution. Swift/Android builds and package tests were not run because no application or package sources changed.

Checklist

  • Swift package tests pass for any package I touched (swift test in Packages/<name>) — N/A; no packages changed.
  • Android unit tests pass if I touched android/ (./gradlew testFullDebugUnitTest) — N/A; Android unchanged.
  • No new build warnings introduced — application builds not run; no application/build sources changed.
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing — N/A; no UI changes.
  • No hardcoded hex frame bytes; protocol facts live in the schema / decoders — N/A to runtime code; this PR documents wire layouts and adds constructed examples only.
  • Follows the conventions in docs/CONTRIBUTING.md.
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores.

Related issues

No issue is closed by this PR.

@bhelm
bhelm force-pushed the feature/protocol-legacy-cleanup branch from 6c214f9 to 177a4aa Compare September 13, 2026 13:43
@bhelm

bhelm commented Sep 13, 2026

Copy link
Copy Markdown
Author

Sharing in case this helps with your ongoing work:

@Zebsi235 — the expanded ECG documentation covers session control, output switches, and raw/filtered record layouts, which may be useful for #1969 and the planned ECG probes.

@UtkuDenizAltiok — the haptics and history/recovery sections may help with the double-tap and buzz handling in #2099.

Feel free to use whichever parts are useful. Remaining unknowns are marked in the docs.

@bhelm
bhelm force-pushed the feature/protocol-legacy-cleanup branch 3 times, most recently from 37e8faa to 8ffa4fc Compare September 13, 2026 15:59
@bhelm
bhelm force-pushed the feature/protocol-legacy-cleanup branch from 8ffa4fc to 6eda97e Compare September 13, 2026 16:28
@ryanbr

ryanbr commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Reviewed at e30d4ea. Careful work, and the things I most expected to go wrong in a reorganisation this size did not. One thing to sort before it goes in.

What I checked rather than took on trust

Provenance survives, which mattered more here than anywhere else. This codebase is not clean-room and says so, so a 3,400-line protocol rewrite is exactly where attribution gets quietly lost. It did not: the per-fact credits table in BLE_REVERSE_ENGINEERING.md is intact, naming what came from johnmiddleton12/my-whoop and what came from b-nnett/goose down to the individual GATT service, CRC and frame, and the "transcribed from Goose" and "my-whoop reference device" notes are still in place. PROTOCOL.md carries a credits section pointing at ATTRIBUTION.md on top of that.

It really is docs-only. Every path is under docs/. No application source, no package, no workflow, no tool outside the new example validator.

The split does not contradict the shipped decoders, which was my main worry with a WHOOP 5/MG catalog sitting beside WHOOP 4 code. I went at it from the direction most likely to break: command 7 is marked U, and NOOP reads WHOOP 4 firmware from REPORT_VERSION_INFO on both platforms, so at a glance that looked wrong. It is not. U is scoped to the documented 5/MG command context, the catalog says so twice, and there is a paragraph naming REPORT_VERSION_INFO specifically as version-specific rather than obsolete. Command 35 lands the same way: U in the 5/MG catalog, live in PROTOCOL_WHOOP4.md with the serial at offset 14, which is exactly what Whoop4HelloSerial reads. The profile split is doing real work.

The validator passes, run here rather than believed: PASS across the constructed bounds, signed values, contact boundaries, clipping, RR arithmetic and request cases, with its own coverage line stating it proves field arithmetic and not CRC, device or calibration.

The one thing to sort

Nothing runs validate_examples.py. No workflow references it, and the PR adds no wiring, so 216 lines of checker sit in the tree with nothing calling them. Every other invariant here is enforced: the i18n audit, the doc-comment lint, the parity ledger, the keyword gate. An unenforced checker is worse than none, because the next person edits an example, breaks the arithmetic, and the file that would have caught it stays quiet while reading as though it is covering them.

It needs a job that runs it, ideally path-filtered to docs/ so it costs nothing on other work. Happy for that to be a one-line addition to an existing workflow rather than a new one.

Not blocking

The scope statements are the part I would keep hardest in any later edit. "Defined does not mean fully decoded, available on every variant, permitted in every state, or successfully exercised on a device" is the sentence that keeps this catalog honest, and it would be easy to erode in a future pass.

Thanks @bhelm. Wire the validator up and this is good to go.

…cy-cleanup

Conflict in docs/WHOOP5_DEEP_DATA.md: keep main's new "Console record
sequencing and text reassembly" section verbatim, placed ahead of this
branch's "Existing public sources" heading. The old "The frame format"
heading stays replaced by the branch's pointer section (its anchor is
kept there).
docs/protocol-examples/validate_examples.py was referenced from the
protocol docs but nothing executed it. Run it as one extra step in the
existing Source Hygiene job, which already runs on every pull request
and on push to main.

Tradeoff: no docs/** path filter. A workflow-level filter on Source
Hygiene would stop the doc-comment lint from running on non-docs
changes, and GitHub has no native per-job path filter, so a filter would
mean a new workflow or runner. The script is stdlib-only and finishes in
well under a second, so the unfiltered step inside an already-running
job adds no runner and effectively no time.
@bhelm

bhelm commented Sep 14, 2026

Copy link
Copy Markdown
Author

validate_examples.py now runs as one extra step in the existing Source Hygiene job, on every PR and on push to main. No docs/** path filter, since that would need a workflow of its own; the script is stdlib-only and finishes in under a second. I also merged main to clear the conflict in WHOOP5_DEEP_DATA.md; main's console-sequencing section is kept verbatim.

@ryanbr

ryanbr commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Reviewed again at 10b1f47. The one thing I asked for is done, and the three things I went looking for all hold. Taking it.

The CI wiring

validate_examples.py now runs as a step in the Source Hygiene job, on every PR and on push to main, with the reasoning for not adding a docs/** filter written next to it. That was the whole of my previous objection: an executable example that nothing executes stops being executable the first time someone edits a layout, and nobody finds out.

I also checked it can actually fail, since a validator that always passes is worse than none: injecting a single false assertion makes it exit non-zero with the message. It is a real gate, not a decorative one.

Its own output is honest about what it is, which I want to quote because it is the right way to describe a partial check:

Coverage: field arithmetic only; no CRC, device, firmware or calibration validation

Anchors, all 79 of them

This is where a 3,400-line restructure usually bleeds, so I checked every intra-repo markdown link carrying an anchor across docs/ and the root: 79 links, 0 broken.

Worth saying how I got there, because I was twice wrong before I was right. My first pass reported 19 broken; that was my own slug rule collapsing runs of whitespace where GitHub does not, so #configuration-reads--commands-121-and-128 looked missing. Fixing that left 7. Those 7 turned out to be the ones you preserved as explicit <a id="..."> tags rather than headings, 33 of them in PROTOCOL.md alone, which is exactly what "preserves existing section anchors without a visible compatibility-link appendix" meant and which my heading-only scan could not see.

So: no broken anchors, and the preservation mechanism is better than a link appendix would have been.

The merge you did

You merged main to clear the WHOOP5_DEEP_DATA.md conflict and said main's console-sequencing section is kept verbatim. Confirmed: the diff removes none of it. That section is recent and hard-won, so I checked rather than assumed.

Provenance, again

Same conclusion as last time and still the thing I care most about in a protocol rewrite of this size. The per-fact credits in BLE_REVERSE_ENGINEERING.md naming johnmiddleton12/my-whoop and b-nnett/goose are intact, and the unknowns are still marked as unknowns rather than smoothed into confident prose. A reorganisation is where attribution goes quietly missing, and it did not.

One note, no action

Your comment pointing @Zebsi235 at the ECG sections for #1969 and @UtkuDenizAltiok at the haptics and history sections for #2099 is the sort of thing that makes a docs PR worth more than its diff. Worth knowing that #2099's double-tap work has since landed its own de-duplication, so that section may now be describing behaviour they have already had to reason about independently.

@ryanbr
ryanbr merged commit ac1fc33 into ryanbr:main Sep 15, 2026
2 checks passed
@bhelm
bhelm deleted the feature/protocol-legacy-cleanup branch September 15, 2026 22:35
ryanbr pushed a commit that referenced this pull request Sep 19, 2026
…rce-reference check (#2310)

From @bhelm. Follows #2183.

Brings the WHOOP 4 side of the protocol reference to the depth WHOOP 5/MG already
had, and turns the protocol pages into a wire reference: NOOP's own implementation
now lives in PROTOCOL_IMPLEMENTATION.md and is linked by file and symbol, rather
than being scattered across the profile and topic pages. Every topic chapter carries
a WHOOP 4 and a WHOOP 5/MG section, renamed headings keep their old anchors, and 57
contracts are mapped to the Swift and Android symbol that implements each one, with
a dash where a platform does not.

Several corrections to the merged reference come with it. Spot-checked two against
the source rather than taking them on trust: GET_DATA_RANGE at `cmdOff + 12/16/24`
is stated verbatim at DataRange.swift:67, and command 97 (exitHighFreqSync) has
exactly one sender, inside checkStrapLiveness() behind the stuck branch, so
"watchdog, not every connect" is right.

Adds docs/protocol-examples/check_source_references.py, stdlib-only, as a step in
Source Hygiene on every PR. File-plus-symbol references rot silently when code moves,
because nobody re-reads documentation during a Swift rename, so there is no docs/**
filter: the trigger is a CODE change, which is exactly what a docs-path filter would
miss. Under a second to run.

Verified the gate is not decorative by breaking a reference two ways:

    [Reassembler](.../NoSuchFile.swift)              FAIL file does not exist
    [TotallyBogusSymbolXYZ](.../Framing.swift)       FAIL symbol not found

and confirmed its clean run matches the claim, 163 references across 46 files with
0 failures, plus six unit tests. The core Tools floor moves 108 to 114, exactly the
six tests added; the margin between the floor and the 130 now collected is
pre-existing and unchanged by this.
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.

2 participants