refactor(host/usb): bump hsmc to 0.5 and collapse WaitingForFirstFrame into Connected
hsmc 0.5 makes default(...) optional on every state. Drop the WaitingForFirstFrame substate that existed only because 0.4 required Connected to declare a default child — Connected itself is now the resting "DTR up, no traffic yet" state, and on(FrameReceived) => Active descends into the inactivity-timer state on the first byte. Behavior is identical (entry/exit chain math unchanged); the chart has one fewer state and one fewer default(). Display and Radio charts left as-is — their defaults are all classic descents serving real grouping/lifecycle purposes (Splash grouping, Bright sub-state isolation, RX/TX session lifecycle), not 0.4 artifacts. No journal-replay tests in this repo, so the 0.5 journal-format change does not apply.
This commit is contained in:
3 files changed
+38
-41
No files matched your search
Generated
+4
-4
@@ -1550,9 +1550,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hsmc"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adbdeff427849daf4d03395027c8538dcdd02a0a26c6e59745f8647f5e657527"
|
||||
checksum = "a3951a3170ce7aa0f0bf4627ac4cc650857f15449c98e97647fa29fcd19c6d33"
|
||||
dependencies = [
|
||||
"defmt 0.3.100",
|
||||
"embassy-futures",
|
||||
@@ -1564,9 +1564,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hsmc-macros"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66ae4d525e3047234d69597f616c89b9ad49ac6a851e4522322a9d74fc2cc1bf"
|
||||
checksum = "4b6d97dd1d31fba9e2f96c65c0ce38d7bc1ff59abe775a0ae18e14dfae01d1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ embassy-usb-driver = "0.2"
|
||||
ucobs = { version = "0.3", features = ["legacy-msrv"] }
|
||||
heapless = "0.9"
|
||||
donglora-protocol = "1"
|
||||
hsmc = { version = "0.4", default-features = false, features = ["embassy", "trace-defmt"] }
|
||||
hsmc = { version = "0.5", default-features = false, features = ["embassy", "trace-defmt"] }
|
||||
# Local path while iterating on CAD IRQ-clear / standby-after-CAD fixes.
|
||||
# Switch back to git fork (then crates.io) once changes are upstreamed.
|
||||
lora-phy = { path = "../../lora-rs/lora-phy", features = ["defmt-03"] }
|
||||
|
||||
+33
-36
@@ -2,24 +2,24 @@
|
||||
//!
|
||||
//! ```text
|
||||
//! Host
|
||||
//! ├── Disconnected (default) no DTR
|
||||
//! └── Connected host attached
|
||||
//! ├── WaitingForFirstFrame (default) DTR up, no traffic yet
|
||||
//! │ NO inactivity timer (boot quiet
|
||||
//! │ is normal — chart structurally
|
||||
//! │ blocks the spurious 1s fire).
|
||||
//! └── Active ≥1 frame seen since connect
|
||||
//! inactivity timer (1s) armed and
|
||||
//! restarted by every frame via
|
||||
//! self-transition.
|
||||
//! ├── Disconnected (default) no DTR
|
||||
//! └── Connected host attached, DTR up
|
||||
//! │ resting here means no traffic yet —
|
||||
//! │ NO inactivity timer (boot quiet is
|
||||
//! │ normal; chart structurally blocks the
|
||||
//! │ spurious 1s fire).
|
||||
//! └── Active ≥1 frame seen since connect
|
||||
//! inactivity timer (1s) armed and
|
||||
//! restarted by every frame via
|
||||
//! self-transition.
|
||||
//! ```
|
||||
//!
|
||||
//! Two chart-level invariants made structurally impossible by the shape:
|
||||
//! 1. **No spurious inactivity at boot.** The 1-second timer only exists
|
||||
//! in `Active`, which requires a `FrameReceived` event to enter.
|
||||
//! A freshly-connected host that holds DTR high but sends nothing
|
||||
//! sits in `WaitingForFirstFrame` indefinitely — no timer, no warn,
|
||||
//! no Reset cascade.
|
||||
//! rests at `Connected` indefinitely (hsmc 0.5 optional-default
|
||||
//! "microwave Standby" pattern) — no timer, no warn, no Reset cascade.
|
||||
//! 2. **Chart-owned timer scope.** Per hsmc README rule §7 (timers
|
||||
//! armed by state lifecycle), the inactivity timer is cancelled on
|
||||
//! every exit of `Active`, restarted on every entry. No global
|
||||
@@ -58,8 +58,8 @@ const POLL_INTERVAL_MS: u64 = 100;
|
||||
pub enum HostInput {
|
||||
DtrRaised,
|
||||
DtrDropped,
|
||||
/// A USB read returned ≥1 byte. Drives `WaitingForFirstFrame →
|
||||
/// Active` (first frame after connect) and the `Active → Active`
|
||||
/// A USB read returned ≥1 byte. Drives `Connected → Active` on
|
||||
/// the first frame after DTR up, and the `Active → Active`
|
||||
/// self-transition that restarts the inactivity timer per
|
||||
/// hsmc README rule §4 (self-transition exits + re-enters target,
|
||||
/// re-arming its timer).
|
||||
@@ -122,24 +122,21 @@ statechart! {
|
||||
entry: wake_display;
|
||||
exit: reset_radio, reset_display;
|
||||
on(DtrDropped) => Disconnected;
|
||||
default(WaitingForFirstFrame);
|
||||
|
||||
// Default child of Connected. No inactivity timer yet —
|
||||
// a freshly-connected host that hasn't sent its first byte
|
||||
// is normal (mux-rs holds DTR high before any client
|
||||
// attaches). Stays here indefinitely until the first
|
||||
// frame arrives.
|
||||
state WaitingForFirstFrame {
|
||||
on(FrameReceived) => Active;
|
||||
}
|
||||
// hsmc 0.5 microwave-Standby pattern: Connected itself is the
|
||||
// resting "DTR up, no traffic yet" state. No inactivity timer
|
||||
// here — boot quiet is normal (mux-rs holds DTR high before
|
||||
// any client attaches), so the chart structurally cannot fire
|
||||
// a 1 s warn before the first byte. The first FrameReceived
|
||||
// descends into Active, which owns the inactivity timer.
|
||||
on(FrameReceived) => Active;
|
||||
|
||||
// Inactivity timer is chart-scoped: armed on every entry to
|
||||
// Active (whether from WaitingForFirstFrame or self-loop
|
||||
// after a frame), cancelled on every exit (per hsmc README
|
||||
// rule §7). The self-transition `on(FrameReceived) =>
|
||||
// Active` re-enters Active per rule §4, re-arming the
|
||||
// timer — equivalent to the "reset last_frame_at on each
|
||||
// byte" pattern but expressed in chart shape.
|
||||
// Active (from Connected on the first frame, or from the
|
||||
// self-transition below on each subsequent frame), cancelled
|
||||
// on every exit (per hsmc README rule §7). The self-transition
|
||||
// re-enters Active per rule §4, re-arming the timer —
|
||||
// equivalent to the "reset last_frame_at on each byte" pattern
|
||||
// but expressed in chart shape.
|
||||
//
|
||||
// hsmc's `after` triggers want `hsmc::Duration` (re-export
|
||||
// of `core::time::Duration`), not `embassy_time::Duration`.
|
||||
@@ -275,9 +272,9 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>(
|
||||
n,
|
||||
receiver.dtr(),
|
||||
);
|
||||
// Drives WaitingForFirstFrame → Active on first byte
|
||||
// and Active → Active (timer-restart) on every byte
|
||||
// after. The chart owns inactivity from here.
|
||||
// Drives Connected → Active on first byte and
|
||||
// Active → Active (timer-restart) on every byte after.
|
||||
// The chart owns inactivity from here.
|
||||
let _ = machine.dispatch(HostInput::FrameReceived).await;
|
||||
dispatch_bytes(&mut decoder, &read_buf[..n], commands, outbound).await;
|
||||
}
|
||||
@@ -385,9 +382,9 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>(
|
||||
|
||||
// DTR edge detection — drives Connected / Disconnected
|
||||
// transitions. The chart's `current_state()` may be
|
||||
// Disconnected, Connected/WaitingForFirstFrame, or
|
||||
// Connected/Active per the new hierarchy. We're connected if
|
||||
// current state is anywhere inside Connected.
|
||||
// Disconnected, Connected (resting), or Connected/Active.
|
||||
// We're connected if current state is anywhere inside
|
||||
// Connected.
|
||||
let dtr_high = receiver.dtr();
|
||||
let was_connected = !matches!(
|
||||
machine.current_state(),
|
||||
|
||||
Reference in new issue
Block a user