feat: composite HID mouse+keyboard with WakingHost statechart

Refactor the boot wake sequence into a WakingHost parent state with
WakingWithKeyboard (default, taps Left Shift 4×) timing out after 500 ms
into WakingWithMouse (existing shake). macOS does not reliably wake from
raw HID mouse motion; a keyboard event does — Shift is a no-op modifier
so it has no character side effect. Parent catches WakeDone (emitted by
the mouse path) and exits to Settling.

USB identity: 046d:c07d "G502 Mouse" → 046d:c52b "USB Receiver"
(Logitech Unifying Receiver — a real composite mouse+keyboard device).
Implementation uses two HidWriters on one embassy_usb::Builder; standard
USB composite, no hub simulation.

Naming cleanup throughout the statechart:
- BootSweep → Booting; SweepDone → BootDone; led_rgb_sweep → boot_sweep
- WakingDisplay → WakingHost{WakingWithKeyboard, WakingWithMouse}
- ShowingRunning → Spinning; RunDone → SpinDone; animate_running → animate_spinner
- Ending::ShutdownAnim → Ending::Spiraling; ShutdownAnimDone → SpiralDone
- animate_shutdown → animate_final_spiral; SHUTDOWN_* → FINAL_SPIRAL_*
- Ctx.writer/Writer → Ctx.mouse/MouseHid (+ new Ctx.kbd/KbdHid)
- send → send_mouse (+ new send_kbd)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 12:14:47 -06:00
co-authored by Claude Opus 4.7
parent cb95dadc2a
commit 01b96bdbf6
2 changed files with 171 additions and 65 deletions
+36 -7
View File
@@ -9,11 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- **Composite USB device — mouse + keyboard HID** under one VID/PID. A new
`WakingHost` parent state wraps two substates: `WakingWithKeyboard`
(`default`) taps Left Shift 4× then idles, and parent's
`KBD_WAKE_DURATION` timeout drives the transition to `WakingWithMouse`
which runs the existing shake animation. Reason: macOS does not reliably
wake from raw HID mouse motion alone; a keyboard event does. Shift is a
modifier with no character side effect, so it's safe even with focus on a
text field at the moment of wake. Adds a second `HidWriter` against the
same `embassy_usb::Builder` (no hub simulation; standard USB composite),
plus `KbdHid`/`KBD_HID_STATE` machinery and a `send_kbd` helper.
- `hsmc` 0.5.1 statechart drives the entire device lifecycle. The control
flow (boot → wake-up animation → settle → running animation → active
jiggle/flash → end-of-day shutdown spiral → power-down) is expressed
declaratively in one `statechart!` block; no more atomic flags, no more
per-tick `Phase` enum, no hand-rolled main loop.
flow (boot → host wake (kbd → mouse) → settle → spinner → active
jiggle/flash → end-of-day spiral → power-down) is expressed declaratively
in one `statechart!` block; no more atomic flags, no more per-tick
`Phase` enum, no hand-rolled main loop.
- Mouse animations driven by the chart's `during:` activities:
- **Wake-up** (`WakingDisplay`): 10 frantic horizontal sin-shaped sweeps
(~12 Hz, ±60 px, ~640 ms) — simulates "shake the mouse to wake the
@@ -38,6 +48,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- **USB descriptor: Logitech G502 (`046d:c07d`, mouse-only) → Logitech
Unifying Receiver (`046d:c52b`, real composite mouse+keyboard).** Product
string `"G502 Mouse"``"USB Receiver"`. Bumping the PID also invalidates
the host's cached HID descriptor on first plug after reflash.
- **Statechart names cleaned up for symmetry**:
- `BootSweep``Booting`; `Ev::SweepDone``Ev::BootDone`;
`led_rgb_sweep``boot_sweep`.
- `WakingDisplay` (flat) → `WakingHost` parent with `WakingWithKeyboard`
+ `WakingWithMouse` substates; `animate_wake``wake_with_mouse`,
matched by new `wake_with_keyboard`.
- `ShowingRunning``Spinning`; `Ev::RunDone``Ev::SpinDone`;
`animate_running``animate_spinner`.
- `Ending::ShutdownAnim``Ending::Spiraling`;
`Ev::ShutdownAnimDone``Ev::SpiralDone`;
`animate_shutdown``animate_final_spiral`;
`SHUTDOWN_RADIUS_START`/`SHUTDOWN_TURNS`/`SHUTDOWN_FRAMES`
`FINAL_SPIRAL_*`.
- `Ctx.writer: Writer``Ctx.mouse: MouseHid` (parallel to new
`Ctx.kbd: KbdHid`); `send``send_mouse`.
- **`RUN_DURATION` from 8 hours to 3 h 50 m**. Math-optimal under the
user's workday distribution (start `triangular(8.0, mode=8.5, 9.5)`,
lunch 12:0013:00, end `triangular(16.0, mode=17.5, 19.0)`) for "screen
@@ -46,12 +75,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- HID `poll_ms` 60 → 8 (125 Hz). At the previous 60 ms poll the host
was discarding ~7 of every 8 animation frames the firmware emitted.
- Boot LED `R→G→B` step 180 ms → 60 ms; 1.5 s pre-animation USB-settle
delay removed. Reset → wake-shake latency dropped from ~2 s to ~240 ms.
delay removed. Reset → wake-shake latency dropped from ~2 s to ~240 ms
(mouse-shake now ~740 ms after reset since 500 ms of keyboard-wake taps
precede it).
- Watchdog feeding moved out of the imperative main loop into its own
`#[embassy_executor::task]`; the chart owns user-visible state, the
watchdog task owns the periodic kick.
- USB descriptor unchanged (still spoofs Logitech G502 VID `0x046d` /
PID `0xc07d`).
### Removed