Ride state machine: idle / running / paused / stopped #11

Closed
opened 2026-08-31 17:14:37 +02:00 by robert · 1 comment
robert commented 2026-08-31 17:14:37 +02:00 (Migrated from git.butzei.de)

Goal

Explicit ride lifecycle driving what is recorded and what is displayed.

Acceptance criteria

  • States: idle -> running -> paused -> running -> stopped
  • Select starts and pauses; long Select stops with confirmation
  • Moving time accumulates only while running
  • State changes sent to the phone via CMD
  • Vibration feedback distinguishes start, pause and stop

Files

  • watchapp/src/c/state.c
  • watchapp/src/c/state.h

Update — 2026-09-02: commands survive a dropout, and the watch never auto-pauses

The phone is authoritative on RIDE_STATE once connected; the rider can pause from the wrist; the
watch runs on its own through a dropout; and dropouts of tens of seconds are ordinary (D31). Put
together, the rider pauses at a café mid-dropout, the phone reconnects asserting running, and the
pause is silently undone (D43).

  • Every CMD is stamped with CMD_TIME and held in a watch-side queue until the phone
    acknowledges it via STATE_ACK
  • On reconnect the queue drains oldest-first, before any incoming RIDE_STATE is applied
  • An unacknowledged command is never overwritten by a RIDE_STATE push
  • The watch never auto-pauses. Auto-pause is a phone function (#69); the watch has no speed
    source when disconnected and cannot implement it even in principle
  • While disconnected the watch counts wall-clock elapsed time and displays it as wall-clock,
    not as moving time; on reconnect the phone's moving time replaces it
  • Test: pause on the watch during a simulated dropout, reconnect, and confirm the ride is still
    paused
## Goal Explicit ride lifecycle driving what is recorded and what is displayed. ## Acceptance criteria - [ ] States: idle -> running -> paused -> running -> stopped - [ ] Select starts and pauses; long Select stops with confirmation - [ ] Moving time accumulates only while running - [ ] State changes sent to the phone via `CMD` - [ ] Vibration feedback distinguishes start, pause and stop ## Files - `watchapp/src/c/state.c` - `watchapp/src/c/state.h` ## Update — 2026-09-02: commands survive a dropout, and the watch never auto-pauses The phone is authoritative on `RIDE_STATE` once connected; the rider can pause from the wrist; the watch runs on its own through a dropout; and dropouts of tens of seconds are ordinary (D31). Put together, the rider pauses at a café mid-dropout, the phone reconnects asserting `running`, and the pause is silently undone (D43). - [ ] Every `CMD` is stamped with `CMD_TIME` and held in a watch-side queue until the phone acknowledges it via `STATE_ACK` - [ ] On reconnect the queue drains **oldest-first, before** any incoming `RIDE_STATE` is applied - [ ] An unacknowledged command is **never** overwritten by a `RIDE_STATE` push - [ ] **The watch never auto-pauses.** Auto-pause is a phone function (#69); the watch has no speed source when disconnected and cannot implement it even in principle - [ ] While disconnected the watch counts **wall-clock** elapsed time and displays it as wall-clock, not as moving time; on reconnect the phone's moving time replaces it - [ ] Test: pause on the watch during a simulated dropout, reconnect, and confirm the ride is still paused
Owner

Closed by PR #84 (merged): state.h/state.c (host-testable, zero SDK calls) implements idle/running/paused/stopped, the D43 dropout-survival CMD queue (cumulative STATE_ACK, drop-newest not oldest on overflow), and the moving-time/wall-clock split while disconnected. ride_link.c is the SDK-facing shell (AppMessage send/decode, connection_service, 1Hz tick, vibration). 24 new host test cases including the D43 café scenario end to end; ride_link.c itself verified live in the emery emulator (pebble emu-bt-connection) rather than host-tested, since faithfully stubbing AppMessage timing would test a fake transport, not real logic.\n\nFlagging two things for whoever lands #7 (codegen): (1) RideCmd's numeric values are this PR's own interpretation of PROTOCOL.md section 3's prose list (start/pause/resume/stop/lap/re-centre/zoom in/zoom out) — PROTOCOL.md gives CMD no numbered table the way RIDE_STATE gets one, so these are provisional; a mismatch against #7's codegen would silently send the wrong byte with no compiler error, since both ends only see a bare uint8. (2) The wire key ids (RIDE_STATE=4, STATE_ACK=6, CMD=71, CMD_TIME=74) are local #defines hand-copied from PROTOCOL.md's tables in ride_link.c, since #7's generated header does not exist yet (package.json's messageKeys is still ["dummy"]). Both should be reconciled against #7's actual output when it lands, not just assumed compatible.\n\nSelect-button overlap with #8/#60: not rewired — page_view.c's Select still only cycles pages. state.h's rider-facing functions (start_pause/stop_request/stop_confirm/stop_cancel) are exposed but deliberately called from no click handler yet; wiring DESIGN.md section 5's real button assignment (Select=start/pause, long Select=stop, Up/Down=carousel) is #60's job.

Closed by PR #84 (merged): state.h/state.c (host-testable, zero SDK calls) implements idle/running/paused/stopped, the D43 dropout-survival CMD queue (cumulative STATE_ACK, drop-newest not oldest on overflow), and the moving-time/wall-clock split while disconnected. ride_link.c is the SDK-facing shell (AppMessage send/decode, connection_service, 1Hz tick, vibration). 24 new host test cases including the D43 café scenario end to end; ride_link.c itself verified live in the emery emulator (`pebble emu-bt-connection`) rather than host-tested, since faithfully stubbing AppMessage timing would test a fake transport, not real logic.\n\n**Flagging two things for whoever lands #7 (codegen)**: (1) `RideCmd`'s numeric values are this PR's own interpretation of PROTOCOL.md section 3's prose list (start/pause/resume/stop/lap/re-centre/zoom in/zoom out) — PROTOCOL.md gives CMD no numbered table the way RIDE_STATE gets one, so these are provisional; a mismatch against #7's codegen would silently send the wrong byte with no compiler error, since both ends only see a bare uint8. (2) The wire key ids (RIDE_STATE=4, STATE_ACK=6, CMD=71, CMD_TIME=74) are local #defines hand-copied from PROTOCOL.md's tables in ride_link.c, since #7's generated header does not exist yet (`package.json`'s messageKeys is still `["dummy"]`). Both should be reconciled against #7's actual output when it lands, not just assumed compatible.\n\n**Select-button overlap with #8/#60**: not rewired — page_view.c's Select still only cycles pages. state.h's rider-facing functions (start_pause/stop_request/stop_confirm/stop_cancel) are exposed but deliberately called from no click handler yet; wiring DESIGN.md section 5's real button assignment (Select=start/pause, long Select=stop, Up/Down=carousel) is #60's job.
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
robert/PedalPebble#11
No description provided.