Files
jiggly/CHANGELOG.md
swaits 359e693646 chore: repoint remaining links at git.swaits.com
Rewrites lingering github.com / *.sr.ht references to this Gitea instance,
translating GitHub and Sourcehut URL shapes to Gitea's (/blob/<ref>/ and
sr.ht /tree/<ref>/item/ -> /src/branch/<ref>/, /tree/<tag> -> /src/tag/<tag>,
/actions/workflows/X -> /actions?workflow=X, /edit/ -> /_edit/).

Left pointing at the old hosts on purpose: pull-request links (PRs were not
migrated), forks that were never migrated, and GitHub-only surfaces Gitea
lacks. OpenPGP identity proofs are untouched by design.
2026-08-31 18:32:19 -06:00

10 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.3.0 - 2026-05-07

Added

  • tuning/ crate — a host-side multi-objective NSGA-III tuner for jiggly's four lifecycle constants. Runs against published heuropt 0.8 (with the parallel rayon feature) and prints the Pareto front, extreme tradeoffs per objective, the firmware's current shipping defaults, and a single weighted-rank recommendation. The crate is its own workspace root with a local .cargo/config.toml overriding the firmware's inherited thumbv6m-none-eabi build target so it can use std. Invoked via just tune or cargo run --release from inside tuning/.

Changed

  • Lifecycle timings retuned via the new tuner's 4-objective (work-time failure, lunch sleep, presses, after-hours waste) Pareto search, then collapsed by explicit decision weights. New shipping values: RUN_DURATION 4h00m → 3h51m, YELLOW_AT 30 → 22, RED_AT 25 → 11, FAST_RED_AT 20 → 4 (LED thresholds in minutes-remaining). The 0.2.0 single-composite-score grid had baked the user's weight choices into the search itself; the new approach surfaces the legitimate tradeoffs first and applies preferences afterward. Across 1,000 simulated workdays the new combination averages 26 minutes of lunch sleep, lands in the 12:1512:45 sweet spot on ~57 % of days, with zero mean work-time failure and ~2 min/day of after-hours waste. The 0.2.0 shipping defaults survive on the new Pareto front but rank well below the new pick under the same weights.
  • config.device_release 0x0200 → 0x0300 — matches firmware version 0.3.0.
  • README section heading "Why four hours…" → "Why these timings…", rewritten to describe the new methodology, the four objectives, the explicit decision weights, and the actual run statistics.

Removed

  • scripts/tune_runtime.py — the Python single-composite-score grid search is superseded by the in-repo tuning/ crate's NSGA-III multi-objective search. The new tuner ships with the firmware, builds reproducibly through cargo/mise, and is just a normal Rust dependency on heuropt (no separate uv invocation).

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.