Reconnect to remembered hardware instead of pairing every launch
`remembered` was a HashSet inside DeviceRegistry, so it lasted exactly as long as the process. Every launch started from nothing: find the trainer, press Connect, find the strap, press Connect, and only then ride. FR-1.5 has been a Should since the beginning and was never actually true. It is a file now — devices.json in the app data directory, written when a link actually comes up rather than when Connect is pressed. A Connect the hardware then refuses is not a pairing, and writing one down would mean a trainer the rider gave up on getting chased on every launch afterwards. Forgetting is recorded too, in its own list: absent means never seen, forgotten means the rider looked at this device and said no, and auto-connect has to keep honouring that on the next launch as well. The file is advisory — a corrupt one costs auto-connect, never a ride. Auto-connect is driven by the scan rather than fired once at startup. The hardware is asleep at startup — a trainer wakes when the cranks turn, a strap when it is put on (A-4) — so a remembered device is reconnected the moment it advertises, through the same path the rider's own click takes, scan suspension included. Bounded by AUTO_ATTEMPTS on an AUTO_RETRY cooldown and cleared when the link comes up or the rider connects by hand: an app that never stops trying can never honestly say it has stopped (FR-1.11). A device disconnected by hand is left alone for the rest of the session, since a disconnect that undoes itself two ticks later is not a disconnect. Pods now prefer the pod we know. Every Click advertises the same name and the same type byte, so before this a rider whose partner was warming up in the next room got whichever pod woke first. With nothing of that kind remembered anything still goes, or there could never be a first pairing. And the pair is one pod, not two. Confirmed on this hardware 2026-08-21: pairing the − pod alone delivers all ten buttons, its twin's included — which §2.3.1 had established for the frames but not for the pairing. So take_plus_pod holds the + pod back while a known − pod may merely be asleep, and connect_controller with no pod named means the − pod rather than both. The wait is bounded by PLUS_GRACE, because a flat − pod should cost the rider a D-pad and not a controller, and Buttons is untouched: it is what makes the handover between the two configurations invisible. Not yet tested against real hardware — nothing was advertising here. The store, the retry budget and the pod-preference rules have unit tests, and a seeded devices.json was confirmed to load and seed the − pod at launch, but the connect path itself waits for a ride. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -188,6 +188,17 @@ button we have found drives them.
|
||||
> so it is evidence of a wrong-way-round pair only while that pod has never sent its own —
|
||||
> otherwise the connection screen asks the rider to swap a pair that is filed correctly.
|
||||
|
||||
> **One link is the whole controller — confirmed 2026-08-21.** Pairing the `−` pod *alone*
|
||||
> delivers all ten buttons on this hardware: its own paddle and D-pad, plus the `+` paddle and
|
||||
> face buttons relayed from its twin. There is nothing a second link adds, and there is one
|
||||
> thing it takes away — connected as a pair, the `−` pod stops reporting its own paddle.
|
||||
>
|
||||
> So the app pairs the `−` pod and stops there (`controller::take_plus_pod`). The `+` pod is a
|
||||
> **substitute, not a second half**: it is connected on sight only when no `−` pod is known, or
|
||||
> when a known one has been unreachable for `PLUS_GRACE`, so a flat `−` pod costs the rider a
|
||||
> D-pad rather than a controller. `Buttons` stays exactly as it is — it is what makes the
|
||||
> handover between the two configurations invisible.
|
||||
|
||||
### 2.3.2 The D100's own Zwift service — telemetry, not shifting
|
||||
|
||||
The trainer answers the same handshake (`RideOn 00 09` → `RideOn 02 00`) on the original
|
||||
@@ -390,6 +401,8 @@ bikecontrol/
|
||||
| FR-1.3 | Connect to trainer and controller independently; either may connect first | Must |
|
||||
| FR-1.4 | Track the left and right pods as separate connections, since each is an independent peripheral | Must |
|
||||
| FR-1.5 | Remember paired devices and auto-connect on launch | Should |
|
||||
| FR-1.5a | Remembering survives the process: trainer, `−` pod and heart rate monitor are written to `devices.json` in the app data directory when a link actually comes up, and a device the rider forgets is recorded as refused rather than merely dropped | Should |
|
||||
| FR-1.5b | Auto-connect is driven by the scan, not by startup — the hardware is asleep at launch (A-4), so a remembered device is reconnected the moment it advertises. Bounded by `AUTO_ATTEMPTS`, so "gave up" (FR-1.11) is not contradicted two ticks later, and suspended for any device the rider disconnected by hand this session | Should |
|
||||
| FR-1.6 | Auto-reconnect on unexpected disconnect, with backoff, without ending the ride | Must |
|
||||
| FR-1.7 | Surface per-device connection state (scanning / connecting / connected / lost) | Must |
|
||||
| FR-1.8 | When nothing is found, prompt to wake the device (per A-4) — pedal the trainer, press a Click button | Must |
|
||||
|
||||
Reference in New Issue
Block a user