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

8.1 KiB
Raw Permalink Blame History

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

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:
    • BootSweepBooting; Ev::SweepDoneEv::BootDone; led_rgb_sweepboot_sweep.
    • WakingDisplay (flat) → WakingHost parent with WakingWithKeyboard
      • WakingWithMouse substates; animate_wakewake_with_mouse, matched by new wake_with_keyboard.
    • ShowingRunningSpinning; Ev::RunDoneEv::SpinDone; animate_runninganimate_spinner.
    • Ending::ShutdownAnimEnding::Spiraling; Ev::ShutdownAnimDoneEv::SpiralDone; animate_shutdownanimate_final_spiral; SHUTDOWN_RADIUS_START/SHUTDOWN_TURNS/SHUTDOWN_FRAMESFINAL_SPIRAL_*.
    • Ctx.writer: WriterCtx.mouse: MouseHid (parallel to new Ctx.kbd: KbdHid); sendsend_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.