Release-prep work for a publish-worthy 0.2.0: - LICENSE: MIT, © 2026 Stephen Waits. - README.md: tagline, what-it-does, hardware, build/flash recipes, ASCII statechart overview, "Why four hours…" runtime-tuning rationale, USB identity section. - Cargo.toml: 0.1.0 → 0.2.0; description, license, repository, readme, keywords, categories; publish = false (firmware, not a library); release profile tightened (lto = "fat", opt-level = "z", panic = "abort"). Flashed binary stays 47 KB; the size knobs are explicit rather than relying on defaults. - CHANGELOG.md: collapse Unreleased → [0.2.0] - 2026-05-01. - scripts/tune_runtime.py: 4-D Monte Carlo over (RUN_DURATION, YELLOW_AT, RED_AT, FAST_RED_AT). PEP 723 inline deps so `uv run` just works. Runtime + LED thresholds re-derived from a typical office workday distribution with a per-minute press-on-warning user model. Joint optimum: RUN_DURATION: 4h00m YELLOW_AT / RED_AT / FAST_RED_AT (min remaining): 30 / 25 / 20 USB identity: VID/PID: 046d:c07d (G502) → 1209:b0b0 (pid.codes) manufacturer: "Logitech" → "swaits.com" product: "G502 Mouse" → "jiggly" bcdDevice: default 0x0010 → 0x0200 (matches firmware version) serial: (none) → RP2040 chip ID as 16 hex chars Bug fix in the descriptor change: an interim version spoofed the Logitech Unifying Receiver (046d:c52b). On Linux, `hid-logitech-dj` matches that exact PID and tries to talk Logitech's HID++ protocol to enumerate paired wireless devices. The firmware doesn't speak HID++, so the driver waits through ~10–20 s of control-transfer timeouts on every plug before unbinding and letting `hid-generic` actually start polling. macOS has no such driver and was always fast. Moving to a pid.codes VID routes the device straight to `hid-generic`. Wake key: tapped Left Shift in early versions to wake the host. In practice that turned out to be exactly the nightmare scenario it sounds like — if the deadline preempted the loop between a Shift-down report and its Shift-up, the host would silently capitalise every keystroke from the user's real keyboard until the device was unplugged. Switched to F13: still wakes any modern OS, but no mainstream OS maps F13 by default, so a stuck F13 has zero visible effect. Also added an unconditional all-keys-released cleanup report at the end of the wake action, bounded by KBD_RELEASE_DEADLINE = 100 ms, so even a deadline that fires mid-press can't leave anything held. Wake refactor: WakingWithKeyboard and WakingWithMouse use oneshot `entry:` actions that race their work against an internal deadline via `embassy_futures::select`; the chart timer (`on(after KBD_PHASE_DURATION)` / `on(after MOUSE_PHASE_DURATION)`) advances. Removes a class of "slow USB ⇒ chart stalls" failure modes from the wake path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
151 lines
8.1 KiB
Markdown
151 lines
8.1 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this project will be documented in this file.
|
||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
## [Unreleased]
|
||
|
||
## [0.2.0] - 2026-05-01
|
||
|
||
### Added
|
||
|
||
- **Composite USB device — mouse + keyboard HID** under one VID/PID. A new
|
||
`WakingHost` parent state wraps two substates: `WakingWithKeyboard`
|
||
taps **F13** 4× then idles, and a chart timer transitions to
|
||
`WakingWithMouse` which runs the existing shake animation. Reason:
|
||
macOS does not reliably wake from raw HID mouse motion alone; a
|
||
keyboard event does. F13 was picked over the more intuitive `Shift`
|
||
because mainstream OSes don't map F13 by default — if a key ever gets
|
||
stuck on the host (e.g. the wake deadline preempts the loop between a
|
||
key-down and key-up report), nothing visible happens. Earlier versions
|
||
used Left Shift and exhibited exactly that nightmare scenario in
|
||
practice (host typing was capitalised until the device was unplugged).
|
||
Belt-and-suspenders: `keyboard_wake` always sends an all-keys-released
|
||
report after its work loop, regardless of which side of the deadline
|
||
won. 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 → 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
|
||
display."
|
||
- **Running** (`ShowingRunning`): 3 quick clockwise circles
|
||
(radius 40 px, ~600 ms) — universal "spinner / in progress" cue.
|
||
- **Pre-shutdown** (`Ending → ShutdownAnim`): eased inward spiral
|
||
80 → 2 px over 5 turns (~5 s), starting 30 s before lifetime end. A
|
||
shared phase `u(t) = t^2.5` drives both radius and angle, so the
|
||
rotation accelerates as the radius collapses — the visual signature
|
||
of a coin/Euler-disk spinning down.
|
||
- **Warnings** (`Active → Warning10 / Warning5`): mini versions of
|
||
the same spiral fire 10 min and 5 min before shutdown — 30 → 2 px
|
||
over 2 turns (~1.5 s) and 50 → 2 px over 3 turns (~2.5 s) — so the
|
||
user gets escalating kinetic foreshadowing of the death gesture
|
||
that's coming.
|
||
- 2 s `Settling` state between wake-up and running animations so the
|
||
display has time to come out of sleep before the spinner draws.
|
||
- All animations carry sub-pixel residue forward across `i8` HID delta
|
||
reports so the rendered shape matches the intended geometry rather than
|
||
losing ~half its motion to truncation.
|
||
|
||
### Changed
|
||
|
||
- **USB descriptor: Logitech G502 (`046d:c07d`, mouse-only) → pid.codes
|
||
hobby slot (`1209:b0b0`).** Product string `"G502 Mouse"` →
|
||
`"jiggly"`, manufacturer `"Logitech"` → `"swaits.com"`. Briefly
|
||
spoofed the Logitech Unifying Receiver (`046d:c52b`) on the way from
|
||
G502 → final, but Linux's `hid-logitech-dj` kernel driver matches that
|
||
PID and runs ~10–20 s of HID++ control-transfer probes the firmware
|
||
doesn't answer, blocking actual endpoint polling for that long on
|
||
every plug. macOS doesn't have the driver and was unaffected.
|
||
Switching to a pid.codes VID lets `hid-generic` bind immediately on
|
||
Linux.
|
||
- **`config.device_release = 0x0200`** (was unset / default `0x0010`).
|
||
Matches firmware version `0.2.0`.
|
||
- **`config.serial_number`** now derived from the RP2040's 64-bit unique
|
||
chip ID, rendered as a 16-hex-char `&'static str` in a `StaticCell`.
|
||
Hosts now treat each replug as the same device, and two boards have
|
||
distinct identities.
|
||
- **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` 8 h → 4 h 00 m** and **LED phase boundaries
|
||
60 / 30 / 10 → 30 / 25 / 20** (`YELLOW_AT` / `RED_AT` / `FAST_RED_AT`,
|
||
in minutes-remaining). Joint optimum from a 4-D Monte Carlo over a
|
||
typical office workday distribution (start `triangular(8.0,
|
||
mode=8.5, 9.5)`, lunch 12:00–13:00, end `triangular(16.0, mode=17.5,
|
||
19.0)`) with a per-minute press-on-warning user model (yellow
|
||
~1.5 %/min, red ~4 %/min, fast-red ~6 %/min, plus small bumps at the
|
||
10'/5' spiral animations). The hand-picked `60/30/10` was almost
|
||
exactly 50ᵗʰ-percentile; pushing the warning thresholds much closer
|
||
to death lifts `P(any lunch sleep)` from 53 % → 74 % and `P(sweet
|
||
12:15–12:45 spot)` from 34 % → 52 %. The mechanism is non-obvious —
|
||
long visible warnings cause more accidental morning RESET-presses
|
||
that extend the cycle into the afternoon, which is the opposite of
|
||
what the user wants. See `scripts/tune_runtime.py` for the
|
||
simulation.
|
||
- 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
|
||
(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.
|
||
|
||
### Removed
|
||
|
||
- `JIGGLE_FLAG: AtomicBool` and all `swap`/`store` calls.
|
||
- `Phase` enum and the per-tick `phase_for(remaining)` switch — the
|
||
breathing color is now read directly from elapsed time inside the
|
||
`breathe_color` `during:`.
|
||
- `led_task` and `sleep_chunked` helper — both subsumed by the chart.
|
||
- `RUN_DURATION = Duration::from_secs(8 * 60 * 60)` and similar
|
||
raw-integer-millisecond constants. All timing is now `Duration`-typed
|
||
with named constructors (`from_hours`, `from_mins`, `from_millis`).
|
||
|
||
## [0.1.0] - 2026-04-27
|
||
|
||
### Added
|
||
|
||
- Initial Embassy-based `no_std` firmware for the Seeed Studio Xiao RP2040.
|
||
- USB HID Boot Mouse, masquerading as a Dell MS116 (VID `0x413c`, PID `0x301a`).
|
||
- Jiggle pattern: pick a random axis from the RP2040 ROSC `RANDOMBIT`, nudge
|
||
+1 px, dwell 25 ms, nudge −1 px, then sleep 4.5 minutes before the next
|
||
cycle.
|
||
- 8-hour workday window after boot or `RESET (R)` press, then silent idle
|
||
(watchdog still fed) until the next manual reset.
|
||
- 8-second hardware watchdog, fed in 5-second chunks during long sleeps.
|
||
- `mise.toml` pinning rust 1.95.0 with explicit components
|
||
(`cargo`, `clippy`, `llvm-tools`, `rust-src`, `rust-std`, `rustc`,
|
||
`rustfmt`) and the `thumbv6m-none-eabi` target, plus cargo helpers
|
||
(`cargo-binutils`, `uf2conv`, `cargo-watch`, `cargo-bloat`,
|
||
`cargo-expand`).
|
||
- `justfile` recipes for `build`, `release`, `check`, `clippy`, `fmt`,
|
||
`lint`, `ci`, `bin`, `uf2`, `flash`, `size`, `bloat`, `expand`, and
|
||
`bootstrap`. All recipes execute inside `mise exec -- sh -eu -c` so the
|
||
pinned toolchain is used regardless of shell activation state.
|
||
|
||
[Unreleased]: https://github.com/swaits/jiggly/compare/v0.2.0...HEAD
|
||
[0.2.0]: https://github.com/swaits/jiggly/compare/v0.1.0...v0.2.0
|
||
[0.1.0]: https://github.com/swaits/jiggly/releases/tag/v0.1.0
|