Skip to content

fix(ble): standing-connect reconnect regime for WHOOP — survive suspension (#1413) - #1421

Merged
ryanbr merged 1 commit into
mainfrom
whoop-standing-reconnect-1413
Aug 17, 2026
Merged

fix(ble): standing-connect reconnect regime for WHOOP — survive suspension (#1413)#1421
ryanbr merged 1 commit into
mainfrom
whoop-standing-reconnect-1413

Conversation

@ryanbr

@ryanbr ryanbr commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Ports OuraLiveSource's #1286 standing-connect fix to BLEManager (thanks @justinjor-bit — detailed report + overnight strap log on real 5/MG).

Bug: BLEManager's two involuntary-reconnect paths (didDisconnectPeripheral, didFailToConnect) armed a DispatchQueue.main.asyncAfter backoff. That timer doesn't fire in a suspended app AND — the real damage — after a failed connect the app held nothing outstanding with CoreBluetooth, so iOS had no reason to wake it. Measured: the reconnect fired 10h46m50s late on a 5/MG; the night was spent unreachable, then drained in the foreground the next morning. standingConnect did not exist in BLEManager. (Distinct from state restoration #1213/#1215 — that's termination+relaunch; this is mere suspension, the common overnight case.)

Fix: mirror Oura's regime.

  • Keep the short timed backoff for the first standingConnectAfterAttempts (3) failures — the app is awake, a 3s/6s retry fixes a transient blip.

  • After that, hand off to a standing central.connect — no timeout, stays outstanding indefinitely, lets iOS wake the app when the strap re-advertises, including from suspension.

  • standingConnectFastFailureS / standingConnectRetryFloor guards stop a near-instant failure hot-looping.

  • Both call-sites route through the shared scheduleReconnect() (the disconnect path is where the night died).

  • BLE-safe: central.connect is the same call connectFromSystem's targeted path already makes — no new outbound command, nothing written to the strap. Peripheral resolved via preferredPeripheralUUIDretrievePeripherals (robust even if the dropped CBPeripheral were released), else the held peripheral, else the scanning fallback. An intentional disconnect() clears the marker and cancels the pending standing connect.

  • iOS/macOS only: Android's WhoopConnectionService runs a foreground service (not suspended; timed backoffs fire), so no twin.

  • Tests: BLEManagerReconnectPolicyTests mirrors OuraReconnectPolicyTests — the pure reconnectStep policy (3s/6s backoff, hand-off at attempt 3, immediate re-issue on a slow failure, floored timer only on an instant one). No strap needed for the policy.

Validation caveat: BLE path — app-build only proves it compiles (dispatched). Whether a standing central.connect really survives suspension is a hardware question, and @justinjor-bit has real 5/MG hardware (5AM/WS50_r00) and offered to confirm. I'd hold the merge for that hardware run. It also pairs with the #1414 fuse fix (that reduces the drops; this fixes the recovery).

@ryanbr
ryanbr merged commit d235692 into main Aug 17, 2026
4 checks passed
@ryanbr
ryanbr deleted the whoop-standing-reconnect-1413 branch August 17, 2026 23:17
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