Fix biometric logging on bands with empty GET_CLOCK / long-dormant RTC#1
Open
eledroos wants to merge 1 commit into
Open
Fix biometric logging on bands with empty GET_CLOCK / long-dormant RTC#1eledroos wants to merge 1 commit into
eledroos wants to merge 1 commit into
Conversation
Two related fixes for WHOOP 4.0 units whose firmware returns an empty GET_CLOCK response and/or whose RTC was lost after long dormancy. On such a unit the strap suppresses type-47 biometric logging and the live HR pipeline never persists, so the app shows live HR but records no history. 1. Clock correlation from the realtime stream (BLE/BLEManager.swift). GET_CLOCK can return an empty payload on some firmware, so ClockCorrelation never lands and the live Collector (which gates persistence on clockRef) buffers HR forever / mis-dates it to ~1971. When a REALTIME_DATA frame arrives and clockRef is still nil, derive clockRef from that frame's own device timestamp paired with wall-now. 2. Stale-strap clock re-latch (BLE/BLEManager.swift, BLE/Commands.swift). A strap whose RTC was lost during long dormancy stops logging type-47 biometrics; the documented recovery is SET_CLOCK + REBOOT_STRAP to latch (docs/specs/2026-05-24-whoop-protocol-complete.md). Adds WhoopCommand.rebootStrap (29) and a one-shot, on-connect recovery that fires only when the data-range looks stale (newest record < 2025-01-01), so healthy bands are never touched. Reboot is non-destructive (not a wipe). Verified on a band dormant ~21 months: full biometric logging resumed. Also gitignore server/.env (it holds the API key + DB password).
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.
Problem
On some WHOOP 4.0 firmware,
GET_CLOCKreturns an empty payload, soClockCorrelationnever lands. Because the liveCollectorgates persistence onclockRef, live HR is buffered indefinitely (and the little that slips through is mis-dated to ~1971) — the app shows live HR but records no history.Separately, a strap whose RTC was lost during long dormancy stops logging type-47 biometrics entirely: its data-range stays frozen in the past, the offload returns only events/battery, and Recovery/Sleep/Strain never populate. This matches the stuck state in
docs/specs/2026-05-25-strap-serving-debug-handoff.md.Fixes
Clock correlation from the realtime stream (
BLE/BLEManager.swift): whenclockRefis nil and aREALTIME_DATAframe arrives, deriveclockReffrom that frame's own device timestamp paired with wall-now, instead of depending on aGET_CLOCKround-trip the firmware doesn't answer.Stale-strap clock re-latch (
BLE/BLEManager.swift,BLE/Commands.swift): addsWhoopCommand.rebootStrap (29)and a one-shot, on-connect recovery: when the data-range looks stale (newest record< 2025-01-01), re-sendSET_CLOCKthenREBOOT_STRAPto latch the clock, which re-arms biometric logging (perdocs/specs/2026-05-24-whoop-protocol-complete.md§0-bis). Gated so healthy bands are never rebooted, fires at most once per launch, and the reboot is non-destructive (notFORCE_TRIM)..gitignore server/.envfor obvious reasons.Verification
Tested on a real WHOOP 4.0 dormant ~21 months (frozen at 2024-08-30). After this change it resumed logging the full type-47 suite (HR / R-R / SpO₂ / skin-temp / resp / gravity), correctly timestamped, and the self-hosted server computed Recovery / Sleep / Strain end-to-end.
Note
The reboot recovery is automatic but only triggers on a stale band. Happy to gate it behind an explicit setting/button instead if you'd prefer it never auto-fire.