From e5aeb3896abc835045ee4e7085b0fd21d1aca315 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Mon, 27 Apr 2026 22:46:08 -0600 Subject: [PATCH] fix(radio): guarantee TX_DONE/TX_FAILED reply on every CmdTx via Transmitting exit action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add exit: respond_tx_failed on the Transmitting super-state. Per hsmc spec §6, exit actions fire on every departure from a state, including the Operational-level on(WedgeDetected) => Recovering transition that bypasses Transmitting's own on(TransmitFailed) => respond_tx_failed handler. respond_tx_failed is a clean no-op when last_tx_tag is None (the normal-completion path consumed it via .take()), so this is a safe unconditional exit action. Net effect: every CmdTx gets exactly one tag-correlated TX_DONE/TX_FAILED/CHANNEL_BUSY reply no matter how Transmitting is exited. The host (mux/ai-bot) stops eating 2s firmware- timeouts on every CAD wedge mid-TX. --- src/radio.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/radio.rs b/src/radio.rs index 834cec7..a015648 100644 --- a/src/radio.rs +++ b/src/radio.rs @@ -418,6 +418,19 @@ statechart! { state Transmitting { entry: announce_entered_tx; + // Per hsmc README rule §6, exit actions fire on every + // departure from Transmitting — including the + // Operational-level `on(WedgeDetected) => Recovering` + // transition that bypasses Transmitting's own + // `on(TransmitFailed) => respond_tx_failed` handler. + // Without this the host never gets a TX_DONE/TX_FAILED + // reply on a wedge mid-TX and waits its 2 s firmware- + // timeout. `respond_tx_failed` no-ops if `last_tx_tag` + // is None (the normal-completion path consumed it via + // `take()`), so it's a clean tag-correlated reply + // GUARANTEE: every CmdTx gets a response no matter + // how Transmitting is exited. + exit: respond_tx_failed; exit: announce_packet_tx; default(TransmittingFromIdle);