Rewrite the Operational subtree of the radio chart so that every
cancel-unsafe lora-phy future (lora.rx() / lora.cad() / lora.tx() /
lora.init()) runs to natural completion, honoring lora-phy's documented
'must run to completion' contract.
Architectural pivot: every cancel-unsafe future is the body of a leaf-
state during whose host state declares NO outbound event handlers other
than events the during itself emits on natural completion. Per hsmc spec
§6, the chart cannot drop the during because no transition can fire.
RX subtree (RxSession super-state):
- irq_pump (cancel-safe: wait_for_irq + get_irq_state + clear_irq_status)
runs at RxSession scope, emits Dio1Asserted(IrqState) to chart
- try_start_rx_hw chains prepare_for_rx -> start_rx so the chip is
actually receiving when RxActive is entered (both single SPI ops)
- RxActive's collect_rx_packet action does post-IRQ get_rx_result work
- RxDraining's enter_standby cleanly stops RX before transitions out
TX subtree (TxSession super-state):
- TxCadPrep / TxCadWait / TxBackoff / TxPrep / TxOnAir / TxFanout
leaf states each owning one cancel-safe SPI op or one sandboxed
cancel-unsafe await
- lora.cad() / lora.tx() called AS-IS in states with zero non-self
event handlers
- chart timers (on(after Duration)) set context flags for wedge
detection; the during checks the flag at its next cancel-safe
wakeup and returns cleanly
- mark_pending_reset action defers Reset events until natural
completion (per spec §5 emit() semantics)
Removes lora_setup_with wrappers around lora.cad(), lora.tx(),
lora.init() -- those wrappers used embassy_time::with_timeout which
DROPS the wrapped future on timeout, the proximate cause of every
'lora_setup timeout: do_tx.cad' warn we've seen.
Keeps lora_setup around enter_standby / prepare_for_rx /
prepare_for_cad / prepare_for_tx -- those are short SPI sequences where
drop is documented-safe.
Documented latency budget: chart-cycle overhead <100us; regulatory
LBT (CAD-to-TX <50ms) well within budget.