refactor: split 832-line main.rs into focused modules

config (constants), usb (driver + tasks), led (NeoPixel anims), mouse
(cursor anims + HID), kbd (F13 wake + HID), chart (statechart + Ctx/Ev
+ JigglyActions). main.rs is now just Irqs + USB builder + chart.run().

No behavior change — comments, deadlines, and tunables preserved verbatim.
Builds clean on default and --features embassy,defmt; clippy -D warnings
passes.
This commit is contained in:
2026-05-04 18:56:27 -06:00
parent 334666b6e7
commit 7effaa293b
7 changed files with 791 additions and 688 deletions
+163
View File
@@ -0,0 +1,163 @@
//! Mouse HID: report send + the family of cursor animations
//! (wake-shake, spinner, eased spirals).
use core::f32::consts::PI;
use embassy_rp::clocks::RoscRng;
use embassy_time::{Duration as EDuration, Timer, with_timeout};
use embassy_usb::class::hid::HidWriter;
use libm::{cosf, powf, roundf, sinf};
use usbd_hid::descriptor::MouseReport;
use crate::chart::Ev;
use crate::config::{
ANIM_FRAME, EASE_POW, FINAL_SPIRAL_FRAMES, FINAL_SPIRAL_RADIUS_START, FINAL_SPIRAL_TURNS,
RUN_CIRCLES, RUN_FRAMES_PER_CIRCLE, RUN_RADIUS, SPIRAL_RADIUS_END, WAKE_AMPLITUDE,
WAKE_FRAMES_PER_HALF, WAKE_JITTER, WAKE_OSCILLATIONS, WARN5_FRAMES, WARN5_RADIUS_START,
WARN5_TURNS, WARN10_FRAMES, WARN10_RADIUS_START, WARN10_TURNS,
};
use crate::usb::UsbDriver;
pub(crate) type MouseHid = HidWriter<'static, UsbDriver, 5>;
// Frantic horizontal mouse shake. Plain oneshot helper — the action method
// that calls this races it against MOUSE_WAKE_DEADLINE.
pub(crate) async fn wake_with_mouse(mouse: &mut MouseHid) {
let period_frames = (WAKE_FRAMES_PER_HALF * 2) as f32;
let total_frames = WAKE_OSCILLATIONS * WAKE_FRAMES_PER_HALF * 2;
let mut prev_x: f32 = 0.0;
let mut prev_y: f32 = 0.0;
let mut acc_x: f32 = 0.0;
let mut acc_y: f32 = 0.0;
for f in 0..total_frames {
let phase = (f as f32) * 2.0 * PI / period_frames;
let next_x = WAKE_AMPLITUDE * sinf(phase);
let jitter = ((RoscRng::next_u8() as f32) / 255.0 - 0.5) * 2.0 * WAKE_JITTER;
let next_y = jitter;
let (dx, dy, used_x, used_y) = step_delta(prev_x, prev_y, next_x, next_y, acc_x, acc_y);
acc_x = used_x;
acc_y = used_y;
send_mouse(mouse, dx, dy).await;
prev_x = next_x;
prev_y = next_y;
Timer::after(ANIM_FRAME).await;
}
}
pub(crate) async fn animate_spinner(mouse: &mut MouseHid) -> Ev {
let total_frames = RUN_CIRCLES * RUN_FRAMES_PER_CIRCLE;
let period_frames = RUN_FRAMES_PER_CIRCLE as f32;
let mut prev_x: f32 = 0.0;
let mut prev_y: f32 = 0.0;
let mut acc_x: f32 = 0.0;
let mut acc_y: f32 = 0.0;
for f in 0..total_frames {
let angle = (f as f32) * 2.0 * PI / period_frames;
let next_x = RUN_RADIUS * sinf(angle);
let next_y = RUN_RADIUS * (1.0 - cosf(angle));
let (dx, dy, used_x, used_y) = step_delta(prev_x, prev_y, next_x, next_y, acc_x, acc_y);
acc_x = used_x;
acc_y = used_y;
send_mouse(mouse, dx, dy).await;
prev_x = next_x;
prev_y = next_y;
Timer::after(ANIM_FRAME).await;
}
Ev::SpinDone
}
async fn animate_spiral(
mouse: &mut MouseHid,
radius_start: f32,
radius_end: f32,
turns: f32,
frames: u32,
) {
let mut prev_x: f32 = 0.0;
let mut prev_y: f32 = 0.0;
let mut acc_x: f32 = 0.0;
let mut acc_y: f32 = 0.0;
for f in 0..frames {
let t = (f as f32) / (frames as f32);
let u = powf(t, EASE_POW);
let angle = u * 2.0 * PI * turns;
let radius = radius_start + (radius_end - radius_start) * u;
// Subtract starting offset so the spiral begins at the cursor's entry point.
let next_x = radius * cosf(angle) - radius_start;
let next_y = radius * sinf(angle);
let (dx, dy, used_x, used_y) = step_delta(prev_x, prev_y, next_x, next_y, acc_x, acc_y);
acc_x = used_x;
acc_y = used_y;
send_mouse(mouse, dx, dy).await;
prev_x = next_x;
prev_y = next_y;
Timer::after(ANIM_FRAME).await;
}
}
pub(crate) async fn animate_final_spiral(mouse: &mut MouseHid) -> Ev {
animate_spiral(
mouse,
FINAL_SPIRAL_RADIUS_START,
SPIRAL_RADIUS_END,
FINAL_SPIRAL_TURNS,
FINAL_SPIRAL_FRAMES,
)
.await;
Ev::SpiralDone
}
pub(crate) async fn animate_warning_5(mouse: &mut MouseHid) -> Ev {
animate_spiral(
mouse,
WARN5_RADIUS_START,
SPIRAL_RADIUS_END,
WARN5_TURNS,
WARN5_FRAMES,
)
.await;
Ev::WarnDone
}
pub(crate) async fn animate_warning_10(mouse: &mut MouseHid) -> Ev {
animate_spiral(
mouse,
WARN10_RADIUS_START,
SPIRAL_RADIUS_END,
WARN10_TURNS,
WARN10_FRAMES,
)
.await;
Ev::WarnDone
}
// Translate continuous (next_x, next_y) target into clamped i8 deltas while
// carrying sub-pixel residue forward, so a 90-frame circle of radius 50 doesn't
// lose ~half its motion to truncation. Returns (dx, dy, residual_x, residual_y).
fn step_delta(
prev_x: f32,
prev_y: f32,
next_x: f32,
next_y: f32,
acc_x: f32,
acc_y: f32,
) -> (i8, i8, f32, f32) {
let want_x = (next_x - prev_x) + acc_x;
let want_y = (next_y - prev_y) + acc_y;
let dx = roundf(want_x.clamp(-127.0, 127.0)) as i8;
let dy = roundf(want_y.clamp(-127.0, 127.0)) as i8;
(dx, dy, want_x - dx as f32, want_y - dy as f32)
}
// ── HID primitive ──────────────────────────────────────────────────
pub(crate) async fn send_mouse(mouse: &mut MouseHid, x: i8, y: i8) {
let report = MouseReport {
buttons: 0,
x,
y,
wheel: 0,
pan: 0,
};
let _ = with_timeout(EDuration::from_secs(3), mouse.write_serialize(&report)).await;
}