feat: composite HID mouse+keyboard with WakingHost statechart
Refactor the boot wake sequence into a WakingHost parent state with
WakingWithKeyboard (default, taps Left Shift 4×) timing out after 500 ms
into WakingWithMouse (existing shake). macOS does not reliably wake from
raw HID mouse motion; a keyboard event does — Shift is a no-op modifier
so it has no character side effect. Parent catches WakeDone (emitted by
the mouse path) and exits to Settling.
USB identity: 046d:c07d "G502 Mouse" → 046d:c52b "USB Receiver"
(Logitech Unifying Receiver — a real composite mouse+keyboard device).
Implementation uses two HidWriters on one embassy_usb::Builder; standard
USB composite, no hub simulation.
Naming cleanup throughout the statechart:
- BootSweep → Booting; SweepDone → BootDone; led_rgb_sweep → boot_sweep
- WakingDisplay → WakingHost{WakingWithKeyboard, WakingWithMouse}
- ShowingRunning → Spinning; RunDone → SpinDone; animate_running → animate_spinner
- Ending::ShutdownAnim → Ending::Spiraling; ShutdownAnimDone → SpiralDone
- animate_shutdown → animate_final_spiral; SHUTDOWN_* → FINAL_SPIRAL_*
- Ctx.writer/Writer → Ctx.mouse/MouseHid (+ new Ctx.kbd/KbdHid)
- send → send_mouse (+ new send_kbd)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+36
-7
@@ -9,11 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- **Composite USB device — mouse + keyboard HID** under one VID/PID. A new
|
||||
`WakingHost` parent state wraps two substates: `WakingWithKeyboard`
|
||||
(`default`) taps Left Shift 4× then idles, and parent's
|
||||
`KBD_WAKE_DURATION` timeout drives the transition to `WakingWithMouse`
|
||||
which runs the existing shake animation. Reason: macOS does not reliably
|
||||
wake from raw HID mouse motion alone; a keyboard event does. Shift is a
|
||||
modifier with no character side effect, so it's safe even with focus on a
|
||||
text field at the moment of wake. 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 → wake-up animation → settle → running animation → active
|
||||
jiggle/flash → end-of-day shutdown 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.
|
||||
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
|
||||
@@ -38,6 +48,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
|
||||
- **USB descriptor: Logitech G502 (`046d:c07d`, mouse-only) → Logitech
|
||||
Unifying Receiver (`046d:c52b`, real composite mouse+keyboard).** Product
|
||||
string `"G502 Mouse"` → `"USB Receiver"`. Bumping the PID also invalidates
|
||||
the host's cached HID descriptor on first plug after reflash.
|
||||
- **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` from 8 hours to 3 h 50 m**. Math-optimal under the
|
||||
user's 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)`) for "screen
|
||||
@@ -46,12 +75,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- 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.
|
||||
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.
|
||||
- USB descriptor unchanged (still spoofs Logitech G502 VID `0x046d` /
|
||||
PID `0xc07d`).
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
+135
-58
@@ -26,7 +26,7 @@ use libm::{cosf, powf, roundf, sinf};
|
||||
use panic_reset as _;
|
||||
use smart_leds::RGB8;
|
||||
use static_cell::StaticCell;
|
||||
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
|
||||
use usbd_hid::descriptor::{KeyboardReport, MouseReport, SerializedDescriptor};
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
USBCTRL_IRQ => UsbInterruptHandler<USB>;
|
||||
@@ -36,7 +36,8 @@ bind_interrupts!(struct Irqs {
|
||||
|
||||
type UsbDriver = Driver<'static, USB>;
|
||||
type Neo = PioWs2812<'static, PIO0, 0, 1, Grb>;
|
||||
type Writer = HidWriter<'static, UsbDriver, 5>;
|
||||
type MouseHid = HidWriter<'static, UsbDriver, 5>;
|
||||
type KbdHid = HidWriter<'static, UsbDriver, 8>;
|
||||
|
||||
// ── Lifecycle timing (statechart Durations) ────────────────────────
|
||||
// 3h50m: math-optimal under the user's schedule for "screen sleeps during
|
||||
@@ -83,10 +84,24 @@ const WAKE_AMPLITUDE: f32 = 60.0;
|
||||
const WAKE_JITTER: f32 = 1.0;
|
||||
|
||||
// 2 s pause after the shake so the display has time to actually wake before
|
||||
// we draw the "running" circles. The user wants this delay to live *here*,
|
||||
// not before the shake.
|
||||
// we draw the spinner. The user wants this delay to live *here*, not before
|
||||
// the shake.
|
||||
const SETTLING_DELAY: Duration = Duration::from_secs(2);
|
||||
|
||||
// ── Keyboard wake (host-wake first pass) ───────────────────────────
|
||||
// macOS often won't wake from raw HID mouse motion alone, but reliably wakes
|
||||
// from a keyboard event. Tap Left Shift four times before the mouse shake;
|
||||
// Shift is a modifier, so it produces no visible character even if focus is
|
||||
// on a text field at the moment of wake.
|
||||
const KBD_WAKE_TAPS: u32 = 4;
|
||||
const KBD_TAP_HOLD: EDuration = EDuration::from_millis(30);
|
||||
const KBD_TAP_GAP: EDuration = EDuration::from_millis(50);
|
||||
// Total parent dwell: 4 × (30 + 50) = 320 ms of taps + ~180 ms slack so the
|
||||
// host has a chance to start coming up before the mouse shake begins.
|
||||
const KBD_WAKE_DURATION: Duration = Duration::from_millis(500);
|
||||
// USB HID Boot Keyboard modifier byte: bit 1 = Left Shift.
|
||||
const KBD_MOD_LEFT_SHIFT: u8 = 0x02;
|
||||
|
||||
// Three quick clockwise circles read more clearly as "spinner / running"
|
||||
// than one slow lap. 25 frames per circle × 3 × 8 ms = 600 ms total.
|
||||
const RUN_RADIUS: f32 = 40.0;
|
||||
@@ -100,10 +115,10 @@ const RUN_CIRCLES: u32 = 3;
|
||||
const EASE_POW: f32 = 2.5;
|
||||
const SPIRAL_RADIUS_END: f32 = 2.0;
|
||||
|
||||
// Final shutdown — the dramatic full version, 30 s before USB goes silent.
|
||||
const SHUTDOWN_RADIUS_START: f32 = 80.0;
|
||||
const SHUTDOWN_TURNS: f32 = 5.0;
|
||||
const SHUTDOWN_FRAMES: u32 = 625; // 5.0 s @ 8 ms/frame
|
||||
// Final spiral — the dramatic full version, 30 s before USB goes silent.
|
||||
const FINAL_SPIRAL_RADIUS_START: f32 = 80.0;
|
||||
const FINAL_SPIRAL_TURNS: f32 = 5.0;
|
||||
const FINAL_SPIRAL_FRAMES: u32 = 625; // 5.0 s @ 8 ms/frame
|
||||
|
||||
// 5-min warning — medium escalation.
|
||||
const WARN5_RADIUS_START: f32 = 50.0;
|
||||
@@ -137,7 +152,8 @@ const BOOT_SWEEP_STEP: EDuration = EDuration::from_millis(60);
|
||||
const SHUTDOWN_FLASH_STEP: EDuration = EDuration::from_millis(400);
|
||||
|
||||
pub struct Ctx {
|
||||
pub writer: Writer,
|
||||
pub mouse: MouseHid,
|
||||
pub kbd: KbdHid,
|
||||
pub neo: Neo,
|
||||
pub neo_pwr: Output<'static>,
|
||||
pub active_start: Option<Instant>,
|
||||
@@ -145,39 +161,52 @@ pub struct Ctx {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Ev {
|
||||
SweepDone,
|
||||
BootDone,
|
||||
WakeDone,
|
||||
RunDone,
|
||||
SpinDone,
|
||||
Jiggled,
|
||||
WarnDone,
|
||||
ShutdownAnimDone,
|
||||
SpiralDone,
|
||||
}
|
||||
|
||||
statechart! {
|
||||
Jiggly {
|
||||
context: Ctx;
|
||||
events: Ev;
|
||||
default(BootSweep);
|
||||
default(Booting);
|
||||
|
||||
state BootSweep {
|
||||
during: led_rgb_sweep(neo, neo_pwr);
|
||||
on(SweepDone) => WakingDisplay;
|
||||
state Booting {
|
||||
during: boot_sweep(neo, neo_pwr);
|
||||
on(BootDone) => WakingHost;
|
||||
}
|
||||
|
||||
state WakingDisplay {
|
||||
during: animate_wake(writer);
|
||||
// Wake the host using whichever input the host actually responds to.
|
||||
// Keyboard first (more reliable on macOS), then a mouse shake as
|
||||
// belt-and-suspenders. The parent state catches WakeDone (emitted by the
|
||||
// mouse path) and exits to Settling.
|
||||
state WakingHost {
|
||||
default(WakingWithKeyboard);
|
||||
on(WakeDone) => Settling;
|
||||
|
||||
state WakingWithKeyboard {
|
||||
during: wake_with_keyboard(kbd);
|
||||
on(after KBD_WAKE_DURATION) => WakingWithMouse;
|
||||
}
|
||||
|
||||
state WakingWithMouse {
|
||||
during: wake_with_mouse(mouse);
|
||||
}
|
||||
}
|
||||
|
||||
// Quiet pause so the display has time to come out of sleep before the
|
||||
// cursor starts drawing the "running" circles.
|
||||
// cursor starts drawing the spinner.
|
||||
state Settling {
|
||||
on(after SETTLING_DELAY) => ShowingRunning;
|
||||
on(after SETTLING_DELAY) => Spinning;
|
||||
}
|
||||
|
||||
state ShowingRunning {
|
||||
during: animate_running(writer);
|
||||
on(RunDone) => Active;
|
||||
state Spinning {
|
||||
during: animate_spinner(mouse);
|
||||
on(SpinDone) => Active;
|
||||
}
|
||||
|
||||
state Active {
|
||||
@@ -199,23 +228,23 @@ Jiggly {
|
||||
}
|
||||
|
||||
state Warning10 {
|
||||
during: animate_warning_10(writer);
|
||||
during: animate_warning_10(mouse);
|
||||
on(WarnDone) => Breathing;
|
||||
}
|
||||
|
||||
state Warning5 {
|
||||
during: animate_warning_5(writer);
|
||||
during: animate_warning_5(mouse);
|
||||
on(WarnDone) => Breathing;
|
||||
}
|
||||
}
|
||||
|
||||
state Ending {
|
||||
during: blink_fast_red(neo);
|
||||
default(ShutdownAnim);
|
||||
default(Spiraling);
|
||||
|
||||
state ShutdownAnim {
|
||||
during: animate_shutdown(writer);
|
||||
on(ShutdownAnimDone) => Quiet;
|
||||
state Spiraling {
|
||||
during: animate_final_spiral(mouse);
|
||||
on(SpiralDone) => Quiet;
|
||||
}
|
||||
|
||||
state Quiet {
|
||||
@@ -241,9 +270,9 @@ impl JigglyActions for JigglyActionContext<'_> {
|
||||
} else {
|
||||
(0, 1)
|
||||
};
|
||||
send(&mut self.writer, dx, dy).await;
|
||||
send_mouse(&mut self.mouse, dx, dy).await;
|
||||
Timer::after(PIXEL_DWELL).await;
|
||||
send(&mut self.writer, -dx, -dy).await;
|
||||
send_mouse(&mut self.mouse, -dx, -dy).await;
|
||||
let _ = self.emit(Ev::Jiggled);
|
||||
}
|
||||
|
||||
@@ -267,7 +296,7 @@ impl JigglyActions for JigglyActionContext<'_> {
|
||||
|
||||
// ── During activities (free async fns) ─────────────────────────────
|
||||
|
||||
async fn led_rgb_sweep(neo: &mut Neo, neo_pwr: &mut Output<'static>) -> Ev {
|
||||
async fn boot_sweep(neo: &mut Neo, neo_pwr: &mut Output<'static>) -> Ev {
|
||||
neo_pwr.set_high();
|
||||
Timer::after_millis(2).await;
|
||||
|
||||
@@ -278,10 +307,28 @@ async fn led_rgb_sweep(neo: &mut Neo, neo_pwr: &mut Output<'static>) -> Ev {
|
||||
paint(neo, 0, 0, BREATHE_PEAK).await;
|
||||
Timer::after(BOOT_SWEEP_STEP).await;
|
||||
paint(neo, 0, 0, 0).await;
|
||||
Ev::SweepDone
|
||||
Ev::BootDone
|
||||
}
|
||||
|
||||
async fn animate_wake(writer: &mut Writer) -> Ev {
|
||||
// Tap Left Shift 4 times — macOS reliably wakes from a keyboard event but is
|
||||
// inconsistent about waking from raw mouse motion. The during loop ends after
|
||||
// the taps and idles in a long sleep; the parent state's KBD_WAKE_DURATION
|
||||
// timeout drives the transition out, not a completion event from this fn.
|
||||
async fn wake_with_keyboard(kbd: &mut KbdHid) -> Ev {
|
||||
for _ in 0..KBD_WAKE_TAPS {
|
||||
send_kbd(kbd, KBD_MOD_LEFT_SHIFT, [0; 6]).await;
|
||||
Timer::after(KBD_TAP_HOLD).await;
|
||||
send_kbd(kbd, 0, [0; 6]).await;
|
||||
Timer::after(KBD_TAP_GAP).await;
|
||||
}
|
||||
// Idle until the parent timeout fires. The hsmc runtime cancels this
|
||||
// future on transition, so the long sleep just parks us.
|
||||
loop {
|
||||
Timer::after(EDuration::from_secs(60)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn wake_with_mouse(mouse: &mut MouseHid) -> Ev {
|
||||
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;
|
||||
@@ -296,7 +343,7 @@ async fn animate_wake(writer: &mut Writer) -> Ev {
|
||||
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(writer, dx, dy).await;
|
||||
send_mouse(mouse, dx, dy).await;
|
||||
prev_x = next_x;
|
||||
prev_y = next_y;
|
||||
Timer::after(ANIM_FRAME).await;
|
||||
@@ -304,7 +351,7 @@ async fn animate_wake(writer: &mut Writer) -> Ev {
|
||||
Ev::WakeDone
|
||||
}
|
||||
|
||||
async fn animate_running(writer: &mut Writer) -> Ev {
|
||||
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;
|
||||
@@ -318,16 +365,16 @@ async fn animate_running(writer: &mut Writer) -> Ev {
|
||||
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(writer, dx, dy).await;
|
||||
send_mouse(mouse, dx, dy).await;
|
||||
prev_x = next_x;
|
||||
prev_y = next_y;
|
||||
Timer::after(ANIM_FRAME).await;
|
||||
}
|
||||
Ev::RunDone
|
||||
Ev::SpinDone
|
||||
}
|
||||
|
||||
async fn animate_spiral(
|
||||
writer: &mut Writer,
|
||||
mouse: &mut MouseHid,
|
||||
radius_start: f32,
|
||||
radius_end: f32,
|
||||
turns: f32,
|
||||
@@ -348,28 +395,28 @@ async fn animate_spiral(
|
||||
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(writer, dx, dy).await;
|
||||
send_mouse(mouse, dx, dy).await;
|
||||
prev_x = next_x;
|
||||
prev_y = next_y;
|
||||
Timer::after(ANIM_FRAME).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn animate_shutdown(writer: &mut Writer) -> Ev {
|
||||
async fn animate_final_spiral(mouse: &mut MouseHid) -> Ev {
|
||||
animate_spiral(
|
||||
writer,
|
||||
SHUTDOWN_RADIUS_START,
|
||||
mouse,
|
||||
FINAL_SPIRAL_RADIUS_START,
|
||||
SPIRAL_RADIUS_END,
|
||||
SHUTDOWN_TURNS,
|
||||
SHUTDOWN_FRAMES,
|
||||
FINAL_SPIRAL_TURNS,
|
||||
FINAL_SPIRAL_FRAMES,
|
||||
)
|
||||
.await;
|
||||
Ev::ShutdownAnimDone
|
||||
Ev::SpiralDone
|
||||
}
|
||||
|
||||
async fn animate_warning_5(writer: &mut Writer) -> Ev {
|
||||
async fn animate_warning_5(mouse: &mut MouseHid) -> Ev {
|
||||
animate_spiral(
|
||||
writer,
|
||||
mouse,
|
||||
WARN5_RADIUS_START,
|
||||
SPIRAL_RADIUS_END,
|
||||
WARN5_TURNS,
|
||||
@@ -379,9 +426,9 @@ async fn animate_warning_5(writer: &mut Writer) -> Ev {
|
||||
Ev::WarnDone
|
||||
}
|
||||
|
||||
async fn animate_warning_10(writer: &mut Writer) -> Ev {
|
||||
async fn animate_warning_10(mouse: &mut MouseHid) -> Ev {
|
||||
animate_spiral(
|
||||
writer,
|
||||
mouse,
|
||||
WARN10_RADIUS_START,
|
||||
SPIRAL_RADIUS_END,
|
||||
WARN10_TURNS,
|
||||
@@ -481,7 +528,7 @@ fn step_delta(
|
||||
|
||||
// ── HID + LED primitives ───────────────────────────────────────────
|
||||
|
||||
async fn send(writer: &mut Writer, x: i8, y: i8) {
|
||||
async fn send_mouse(mouse: &mut MouseHid, x: i8, y: i8) {
|
||||
let report = MouseReport {
|
||||
buttons: 0,
|
||||
x,
|
||||
@@ -489,7 +536,17 @@ async fn send(writer: &mut Writer, x: i8, y: i8) {
|
||||
wheel: 0,
|
||||
pan: 0,
|
||||
};
|
||||
let _ = with_timeout(EDuration::from_secs(3), writer.write_serialize(&report)).await;
|
||||
let _ = with_timeout(EDuration::from_secs(3), mouse.write_serialize(&report)).await;
|
||||
}
|
||||
|
||||
async fn send_kbd(kbd: &mut KbdHid, modifier: u8, keycodes: [u8; 6]) {
|
||||
let report = KeyboardReport {
|
||||
modifier,
|
||||
reserved: 0,
|
||||
leds: 0,
|
||||
keycodes,
|
||||
};
|
||||
let _ = with_timeout(EDuration::from_secs(3), kbd.write_serialize(&report)).await;
|
||||
}
|
||||
|
||||
async fn paint(neo: &mut Neo, r: u8, g: u8, b: u8) {
|
||||
@@ -537,9 +594,12 @@ async fn main(spawner: Spawner) {
|
||||
|
||||
let driver = Driver::new(p.USB, Irqs);
|
||||
|
||||
let mut config = UsbConfig::new(0x046d, 0xc07d);
|
||||
// Spoof a Logitech Unifying Receiver — a real-world composite device that
|
||||
// exposes both mouse and keyboard HID interfaces under one VID/PID, which
|
||||
// matches what this firmware now does.
|
||||
let mut config = UsbConfig::new(0x046d, 0xc52b);
|
||||
config.manufacturer = Some("Logitech");
|
||||
config.product = Some("G502 Mouse");
|
||||
config.product = Some("USB Receiver");
|
||||
config.max_power = 100;
|
||||
config.max_packet_size_0 = 64;
|
||||
|
||||
@@ -547,7 +607,8 @@ async fn main(spawner: Spawner) {
|
||||
static BOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new();
|
||||
static MSOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new();
|
||||
static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
|
||||
static HID_STATE: StaticCell<State> = StaticCell::new();
|
||||
static MOUSE_HID_STATE: StaticCell<State> = StaticCell::new();
|
||||
static KBD_HID_STATE: StaticCell<State> = StaticCell::new();
|
||||
|
||||
let mut builder = Builder::new(
|
||||
driver,
|
||||
@@ -558,7 +619,7 @@ async fn main(spawner: Spawner) {
|
||||
CONTROL_BUF.init([0; 64]),
|
||||
);
|
||||
|
||||
let hid_config = HidConfig {
|
||||
let mouse_config = HidConfig {
|
||||
report_descriptor: MouseReport::desc(),
|
||||
request_handler: None,
|
||||
// 8 ms (125 Hz) — standard for full-speed mice. At the previous 60 ms
|
||||
@@ -568,7 +629,22 @@ async fn main(spawner: Spawner) {
|
||||
hid_subclass: HidSubclass::Boot,
|
||||
hid_boot_protocol: HidBootProtocol::Mouse,
|
||||
};
|
||||
let writer = HidWriter::<_, 5>::new(&mut builder, HID_STATE.init(State::new()), hid_config);
|
||||
let mouse = HidWriter::<_, 5>::new(
|
||||
&mut builder,
|
||||
MOUSE_HID_STATE.init(State::new()),
|
||||
mouse_config,
|
||||
);
|
||||
|
||||
let kbd_config = HidConfig {
|
||||
report_descriptor: KeyboardReport::desc(),
|
||||
request_handler: None,
|
||||
// The keyboard only fires once at boot; no need for fast polling.
|
||||
poll_ms: 10,
|
||||
max_packet_size: 8,
|
||||
hid_subclass: HidSubclass::Boot,
|
||||
hid_boot_protocol: HidBootProtocol::Keyboard,
|
||||
};
|
||||
let kbd = HidWriter::<_, 8>::new(&mut builder, KBD_HID_STATE.init(State::new()), kbd_config);
|
||||
|
||||
let usb = builder.build();
|
||||
spawner.spawn(usb_task(usb).unwrap());
|
||||
@@ -577,7 +653,8 @@ async fn main(spawner: Spawner) {
|
||||
static EVENT_CHAN: Channel<CriticalSectionRawMutex, Ev, 8> = Channel::new();
|
||||
|
||||
let ctx = Ctx {
|
||||
writer,
|
||||
mouse,
|
||||
kbd,
|
||||
neo,
|
||||
neo_pwr,
|
||||
active_start: None,
|
||||
|
||||
Reference in New Issue
Block a user