Skip to content

docs: add WHOOP 5.0 (Gen 5) BLE protocol reference - #1

Open
DropTabl wants to merge 2 commits into
OpenStrap:mainfrom
DropTabl:docs/whoop-5-protocol-reference
Open

docs: add WHOOP 5.0 (Gen 5) BLE protocol reference#1
DropTabl wants to merge 2 commits into
OpenStrap:mainfrom
DropTabl:docs/whoop-5-protocol-reference

Conversation

@DropTabl

Copy link
Copy Markdown

WHOOP 5.0 is a different device family from the Gen 4 band this repo documents — different service UUID, CRC-16/Modbus instead of CRC-8, a different frame envelope, and a different record layout. A Gen 4 decoder pointed at a Gen 5 band produces CRC failures, so the two need separate references.

Adds PROTOCOL-GEN5.md, written to match PROTOCOL.md's structure and its verified / empirical / unknown confidence markers.

What it covers

BLE transport and framing, the command set and the response-correlation rule, session bring-up, packet types and revisions, the 99-byte revision-18 1 Hz record, realtime type-40 HR, revisions 20/21/26 in brief, the historical sync contract and its count gate, and the persistent device-config / feature-flag exchange.

Two findings worth calling out

Gen 5 exposes beat-to-beat R-R intervals in milliseconds. The README currently lists "anything resembling HRV or beat-to-beat timing" as genuinely unknown — that holds for Gen 4, but Gen 5 carries it in two places under one contract (type-40 offsets 9..17, and revision-18 body 2 and 3..10). §7 documents the production path and, more importantly, the motion gate: intervals are emitted only while a rolling 30-second filtered acceleration envelope stays below a threshold. That gate drives the decoder rules — a zero count is ambiguous (no new interval vs. suppressed by motion), and the series is sparse and must never be bridged across a zero-count record, a timestamp gap, or non-adjacent records.

Revision-18 body 16 is a trap. It looks like a second heart rate and tracks the published HR closely enough that small samples read as a duplicate. Over 1.29M records it only ever takes 0 or values in 30..2101..29 and 211..255 never occur — so it is definitely an HR-domain scalar and definitely not a bitfield. But how often it equals the published HR ranges from 4% to 80% across ordinary captures of the same band, depending on an internal branch the packet does not serialise. Documented as retain-raw, do-not-name, and explicitly not usable as a validity or trust signal.

Footguns documented

  • The config and feature-flag stores use ASCII "1" = enabled, "2" = disabled, and "0" = raw/unset. A band commonly sits at "2", so writing "0" to enable something turns it off. Raw zero is not restorable, which makes a blanket enable-then-restore sequence irreversible.
  • The history count gate has explicit type membership; getting it wrong makes the band re-offer the same burst forever — the Gen 5 analogue of the Groundhog Day bug.
  • Revision 22 is historical, not realtime; the selected writer is not the emitted tag; and it is mostly redundant against R18/R20/R26. §9 says so rather than encouraging people to enable it.

Scope and limitations

  • Stated in the document: validated against a single strap on firmware 50.40.1.0. Cross-device confirmation would be welcome.
  • Adds a short Gen 4 scope note to PROTOCOL.md and README.md pointing at the new file, so nobody aims the wrong decoder at the wrong generation. Happy to drop those two edits and keep the cross-reference here instead if you'd prefer the existing docs untouched.
  • Documentation only — no code changes, no captures or binaries, and no device-identifying data.

🤖 Generated with Claude Code

WHOOP 5.0 is a different device family from the Gen 4 band this repo
documents: different service UUID, CRC-16/Modbus instead of CRC-8, a
different envelope, and a different record layout. A Gen 4 decoder pointed
at a Gen 5 band produces CRC failures.

Adds PROTOCOL-GEN5.md covering transport and framing, the command set and
response-correlation rule, session bring-up, packet types and revisions,
the 99-byte revision-18 1 Hz record, realtime type-40 HR, the historical
sync contract and its count gate, and the persistent config/feature-flag
exchange.

Two findings worth calling out:

- Gen 5 exposes beat-to-beat R-R intervals in milliseconds, which
  PROTOCOL.md lists as genuinely unknown for Gen 4. Documented with its
  motion gate and the decoder rules that gate implies -- a zero count is
  ambiguous and the series must never be bridged across records.
- Revision-18 body 16 looks like a second heart rate and matches the
  published HR closely in small samples, but agreement ranges from 4% to
  80% across ordinary captures of the same band. Documented as retain-raw,
  do-not-name, and explicitly not usable as a validity signal.

Confidence is marked verified/empirical/unknown throughout, matching
PROTOCOL.md. Validated against a single strap, which is stated in the
document as a limitation.

Also adds a Gen 4 scope note to PROTOCOL.md and README.md pointing at the
new file, so nobody aims the wrong decoder at the wrong generation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e71ea78d-6ca5-4927-8ccc-474bea34a7b9


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Two fields in the revision-18 flag words look like data and are not. Both
have been decoded the wrong way in a real Gen 5 decoder, so they are worth
stating explicitly rather than leaving as "two signal-processing flag
words".

- Flags-B bit 0x80 is a diagnostic-format / special-state marker, not an
  HR-present, HR-valid, confidence, corroboration or walk-detected bit.
  Heart rate is routinely present and in range while it is clear. It has
  two producers and the record does not serialise which one fired. What
  drives it is marked unknown: neither documented producer condition
  reproduces cleanly against captured history.
- Flags-B bits 16..31 are a frequency-derived pair in cycles per minute,
  not R-R intervals. Real R-R lives at body 2 and body 3..10 in
  milliseconds, so a reader meeting these without a warning has an obvious
  wrong guess available.

Also documents flags-A's structure -- only the low half is flag-like, bits
29..30 are one 2-bit enum, and bits 16..27 plus bit 31 are a rotating
telemetry stream -- so neither word gets flattened into 32 booleans.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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