Finishes the hsmc migration that was staged across the prior two commits: - Radio task is now a single `machine.run().await`; all I/O lives in state-scoped `during:` activities (`next_command`, `next_packet`, `perform_tx`). The old `push/drain` helpers, the imperative top-level `select`, `handle_cmd`, `handle_rx_result`, and `execute_transmit` are gone. `StartingRx` / `ResumingRx` intermediate states model the host-visible difference between a responding and a silent RX re-arm. - Host/USB task is now an hsmc statechart (Disconnected/Connected) with entry/exit actions handling display wake, radio StopRx, and display reset — replacing the open-coded `was_connected` edge detector. The `protocol_loop` keeps its imperative read path because `read_packet` blocks. - New `debug-checkpoint` cargo feature ships a dedicated `debug_blink.rs` task and a shared atomic counter that code along the boot/dispatch/TX path bumps at notable points. A ladder-latching write policy preserves the deepest TX phase reached on the first attempt until reboot — diagnostic output for UART-only boards where defmt would corrupt the host protocol. Zero footprint when the feature is off. - Silence a stray `clippy::unit_arg` on `led_off` / `led_blip` in the display task for boards with `LedDriver = ()`.
86 lines
2.7 KiB
Rust
86 lines
2.7 KiB
Rust
//! UART host task: COBS-framed fixed-size LE command/response protocol.
|
|
//!
|
|
//! Used for boards with USB-UART bridge chips (e.g. CP2102 on Heltec V3).
|
|
|
|
use defmt::warn;
|
|
use embassy_executor::task;
|
|
use embedded_io_async::Write;
|
|
|
|
use super::framing::{self, CobsDecoder, MAX_FRAME};
|
|
use crate::channel::{CommandChannel, DisplayCommand, DisplayCommandChannel, ResponseChannel};
|
|
|
|
#[task]
|
|
pub async fn host_task(
|
|
parts: crate::board::UartParts,
|
|
commands: &'static CommandChannel,
|
|
responses: &'static ResponseChannel,
|
|
display_commands: &'static DisplayCommandChannel,
|
|
has_display: bool,
|
|
mac: [u8; 6],
|
|
) {
|
|
let (mut rx, mut tx) = parts.driver.split();
|
|
|
|
// UART has no DTR — assume a host is always present.
|
|
if has_display {
|
|
display_commands.send(DisplayCommand::On).await;
|
|
}
|
|
|
|
let mut read_buf = [0u8; 64];
|
|
let mut write_buf = [0u8; MAX_FRAME];
|
|
let mut cobs_encode_buf = [0u8; MAX_FRAME];
|
|
let mut decoder = CobsDecoder::new();
|
|
|
|
loop {
|
|
use embassy_futures::select::{select, Either};
|
|
|
|
match select(
|
|
embedded_io_async::Read::read(&mut rx, &mut read_buf),
|
|
responses.receive(),
|
|
)
|
|
.await
|
|
{
|
|
Either::First(result) => {
|
|
let n = match result {
|
|
Ok(0) => continue,
|
|
Ok(n) => n,
|
|
Err(_) => {
|
|
decoder.reset();
|
|
embassy_time::Timer::after_millis(100).await;
|
|
continue;
|
|
}
|
|
};
|
|
|
|
let mut cmds = heapless::Vec::<_, 4>::new();
|
|
decoder.feed(&read_buf[..n], |cmd| {
|
|
let _ = cmds.push(cmd);
|
|
});
|
|
for cmd in cmds {
|
|
framing::route_command(
|
|
cmd,
|
|
commands,
|
|
responses,
|
|
display_commands,
|
|
has_display,
|
|
mac,
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
Either::Second(response) => {
|
|
#[cfg(feature = "debug-checkpoint")]
|
|
crate::debug_blink::set(13);
|
|
if let Some(frame) =
|
|
framing::cobs_encode_response(response, &mut write_buf, &mut cobs_encode_buf)
|
|
{
|
|
if tx.write_all(frame).await.is_err() {
|
|
warn!("UART write failed, response dropped");
|
|
} else {
|
|
#[cfg(feature = "debug-checkpoint")]
|
|
crate::debug_blink::set(14);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|