diag: capture the 4.0 GET_BATTERY_LEVEL frame in the strap log (#900) - #1400
Merged
Conversation
#900 is blocked on one thing: a real WHOOP 4.0 GET_BATTERY_LEVEL COMMAND_RESPONSE of known provenance, to settle whether its zeroed [seq][result] prefix is prefix-absence or a genuine failure. Today the strap log only carries the decoder's verdict (the #923 annotation) — which is the interpretation in dispute — not the raw bytes, so a pasted log can't resolve it. Make the log self-capture the artefact: on any non-SUCCESS COMMAND_RESPONSE, dump the FULL raw frame (0xAA..crc32) once per command per connection. Full frame, not the post-prefix payload helper, because the disputed bytes ARE the prefix; contiguous-hex format matches the GET_DATA_RANGE dump (#451) and the aa0f00.. form the #900 fixtures are quoted in. Rate-limited (a 4.0 hits this branch every battery poll) and re-armed each connection. The same line also captures a genuinely-failing command in the same session — the comparison frame the issue asks for. Default log: a battery reply carries no serial/key (unlike the TestCentre-gated GET_HELLO dump #1303). Parity: Swift FrameRouter had no twin of the #923 annotation at all — it gains both the annotation and the dump, byte-identical to the Kotlin WhoopBleClient text. Log-only, no behaviour/decode change. StrandTests pins the annotation, the once-per-session dump, and the per-connection re-arm; Android compiled locally.
ryanbr
added a commit
that referenced
this pull request
Aug 17, 2026
… build 221) Stay on 10.1.1 (marketing/versionName unchanged); bump only the build numbers so the fresh testing build installs over the previous one (versionCode 339 > 338 staging high-water, iOS build 221 > 220). Bundles this session's merged fixes: #1400 (#900 battery-frame capture), #1401 (baseline-relative daytime stress), #1402 (analyzeRecent cache backfill fix), #1403 (computed -noop spine + day-span diag), #1404 (VO2max trend).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
#900 has been blocked for three weeks on one thing: a real WHOOP 4.0
GET_BATTERY_LEVELCOMMAND_RESPONSE of known provenance, to settle whether its zeroed[seq][result]prefix means prefix-absence (the value lives at a fixed offset, the two leading bytes are padding) or a genuine failure. Between zero and one confirmed-real captures exist in-tree; the rest are declared-synthetic fixtures.The blocker is that a pasted strap log can't currently resolve it: today the log carries only the decoder's verdict (
FAILURE(0) … carried a value, the #923 annotation) — which is the very interpretation in dispute — not the raw bytes.What
On any non-SUCCESS
COMMAND_RESPONSE, dump the full raw frame (0xAA..crc32) to the strap log, once per command per connection:[seq][result]prefix that a payload dump hides. Contiguous-hex format matches theGET_DATA_RANGEraw-frame line (l10n(de): localize the Trends / Explore / Deep Timeline screens (Android) #451) and theaa0f00…form Need one real WHOOP 4.0 GET_BATTERY_LEVEL capture: every in-tree fixture may be synthetic #900's fixtures are quoted in.00 00vs a real failure's non-zero echo +0x00).GET_HELLOdump WHOOP serial identity: adopt a stable serial id (mirror the Oura path) — multi-strap keystone #1303).This doesn't decide what the result byte means — it makes a normal strap-log sync self-capture the artefact so the issue can finally be resolved (confirm prefix-absence → per-reply-shape fix; or refute → close #900, relabel fixtures).
Parity
Swift
FrameRouterhad no twin of the #923 annotation at all — a standing parity gap. It gains both the annotation and the dump, byte-identical to the KotlinWhoopBleClienttext (enumName/CommandNames.labelproduce the sameNAME(raw)label; same result table). Both platforms: log-only, no behaviour/decode change.Verification
StrandTests/BatteryResultProvenanceDumpTests(app-build gate): pins the value-carrying annotation, the once-per-connection dump carrying the full frame, and the per-connection re-arm — driven throughFrameRouter.handlewith the real Need one real WHOOP 4.0 GET_BATTERY_LEVEL capture: every in-tree fixture may be synthetic #900 fixture.compileFullDebugKotlinclean locally.WhoopBleClientisn't unit-testable without a full GATT harness (the codebase tests extracted protocol helpers, not the client log path); the Kotlin dump is a structural twin of the Swift-pinned behaviour (mutableSetOf().add()gate).raw frame (#900 …)line forGET_BATTERY_LEVELand for any genuine failure in the same session — that pasted log is the capture Need one real WHOOP 4.0 GET_BATTERY_LEVEL capture: every in-tree fixture may be synthetic #900 wants.Closes nothing on its own; unblocks #900.