refactor: drive display via hsmc statechart

Replaces the ad-hoc display loop with a declarative hsmc statechart
(Off/On.{Splash.{LearnMore,Info}, Dashboard.{Listening,Transmitting}})
and swaps the Radio→Display channel from a RadioStatus watch to a
typed RadioEvent queue. The radio task now emits discrete mode and
packet events instead of publishing full status snapshots; the
display consumes them through the statechart, which also handles the
5 s splash alternation and 1 s sparkline tick via `on(after …)` /
`on(every …)` transitions.

Also:
- Add hsmc dep and embassy/tokio feature shims (hsmc proc macro
  evaluates `cfg(feature = "embassy")` in this crate).
- Fix USB disconnect path in `host/usb.rs` so the DTR branch always
  runs on read error (was early-continuing, starving display/radio
  reset).
This commit is contained in:
swaits committed 2026-04-16 13:26:04 -06:00
1 parent 272abceedb
commit 596bc3ed96
7 files changed
+661 -347

No files matched your search

+5 -4
View File
@@ -37,7 +37,7 @@ use embassy_executor::Spawner;
#[cfg(not(test))]
use crate::board::LoRaBoard;
#[cfg(not(test))]
use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel, StatusWatch};
use crate::channel::{CommandChannel, DisplayCommandChannel, RadioEventChannel, ResponseChannel};
#[cfg(not(test))]
cfg_if::cfg_if! {
@@ -55,7 +55,7 @@ static COMMANDS: CommandChannel = CommandChannel::new();
#[cfg(not(test))]
static RESPONSES: ResponseChannel = ResponseChannel::new();
#[cfg(not(test))]
static STATUS: StatusWatch = StatusWatch::new();
static RADIO_EVENTS: RadioEventChannel = RadioEventChannel::new();
#[cfg(not(test))]
static DISPLAY_COMMANDS: DisplayCommandChannel = DisplayCommandChannel::new();
@@ -80,7 +80,8 @@ async fn run(spawner: Spawner) {
let parts = board.into_parts();
spawner.spawn(
radio::radio_task(parts.radio, &COMMANDS, &RESPONSES, &STATUS).expect("spawn radio_task"),
radio::radio_task(parts.radio, &COMMANDS, &RESPONSES, &RADIO_EVENTS)
.expect("spawn radio_task"),
);
spawner.spawn(
@@ -98,7 +99,7 @@ async fn run(spawner: Spawner) {
if let Some(dp) = parts.display {
#[allow(clippy::unit_arg)] // LedDriver is () for boards without LEDs
spawner.spawn(
display::display_task(dp, parts.led, &STATUS, &DISPLAY_COMMANDS)
display::display_task(dp, parts.led, &RADIO_EVENTS, &DISPLAY_COMMANDS)
.expect("spawn display_task"),
);
}