Files
jiggly/CHANGELOG.md
T
swaitsandClaude Opus 4.7 d0d9c451aa chore: cut 0.2.0 — README, LICENSE, publish metadata, runtime tune, USB identity, F13
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>
2026-05-01 22:32:17 -06:00

151 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 ~1020 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:0013: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:1512: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