Skip to content

Fix biometric logging on bands with empty GET_CLOCK / long-dormant RTC#1

Open
eledroos wants to merge 1 commit into
johnmiddleton12:mainfrom
eledroos:fix/stale-strap-clock-recovery
Open

Fix biometric logging on bands with empty GET_CLOCK / long-dormant RTC#1
eledroos wants to merge 1 commit into
johnmiddleton12:mainfrom
eledroos:fix/stale-strap-clock-recovery

Conversation

@eledroos

@eledroos eledroos commented Jun 2, 2026

Copy link
Copy Markdown

Problem

On some WHOOP 4.0 firmware, GET_CLOCK returns an empty payload, so ClockCorrelation never lands. Because the live Collector gates persistence on clockRef, 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

  1. Clock correlation from the realtime stream (BLE/BLEManager.swift): when clockRef is nil and a REALTIME_DATA frame arrives, derive clockRef from that frame's own device timestamp paired with wall-now, instead of depending on a GET_CLOCK round-trip the firmware doesn't answer.

  2. Stale-strap clock re-latch (BLE/BLEManager.swift, BLE/Commands.swift): adds WhoopCommand.rebootStrap (29) and a one-shot, on-connect recovery: when the data-range looks stale (newest record < 2025-01-01), re-send SET_CLOCK then REBOOT_STRAP to latch the clock, which re-arms biometric logging (per docs/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 (not FORCE_TRIM).

  3. .gitignore server/.env for 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.

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).
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