From c7f145c78720a44592fd3ce202f861240a04738b Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 7 Apr 2026 09:39:06 -0600 Subject: [PATCH] refactor: unified host/ communication module, cfg-free run() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created host/ module with internal cfg dispatch (USB vs UART) - Moved usb/task.rs → host/usb.rs, uart/task.rs → host/uart.rs - Moved protocol_io.rs → host/framing.rs - Renamed usb_task/uart_task to host_task (uniform name) - Removed all host-comm cfg from main.rs - The run() function now has ZERO board-specific conditional compilation - Deleted old usb/, uart/, protocol_io.rs modules --- .../src/{protocol_io.rs => host/framing.rs} | 0 firmware/src/host/mod.rs | 16 +++++++ firmware/src/{uart/task.rs => host/uart.rs} | 17 +++----- firmware/src/{usb/task.rs => host/usb.rs} | 39 ++++------------- firmware/src/main.rs | 43 ++++++------------- firmware/src/uart/mod.rs | 4 -- firmware/src/usb/mod.rs | 4 -- 7 files changed, 44 insertions(+), 79 deletions(-) rename firmware/src/{protocol_io.rs => host/framing.rs} (100%) create mode 100644 firmware/src/host/mod.rs rename firmware/src/{uart/task.rs => host/uart.rs} (74%) rename firmware/src/{usb/task.rs => host/usb.rs} (74%) delete mode 100644 firmware/src/uart/mod.rs delete mode 100644 firmware/src/usb/mod.rs diff --git a/firmware/src/protocol_io.rs b/firmware/src/host/framing.rs similarity index 100% rename from firmware/src/protocol_io.rs rename to firmware/src/host/framing.rs diff --git a/firmware/src/host/mod.rs b/firmware/src/host/mod.rs new file mode 100644 index 00000000..49e0cd88 --- /dev/null +++ b/firmware/src/host/mod.rs @@ -0,0 +1,16 @@ +//! Host communication: unified USB/UART transport. +//! +//! Exports a single `host_task` regardless of transport. +//! The cfg dispatch is contained entirely within this module. + +pub mod framing; + +cfg_if::cfg_if! { + if #[cfg(feature = "heltec_v3")] { + mod uart; + pub use uart::host_task; + } else { + mod usb; + pub use usb::host_task; + } +} diff --git a/firmware/src/uart/task.rs b/firmware/src/host/uart.rs similarity index 74% rename from firmware/src/uart/task.rs rename to firmware/src/host/uart.rs index a9111199..d01146f4 100644 --- a/firmware/src/uart/task.rs +++ b/firmware/src/host/uart.rs @@ -1,18 +1,16 @@ -//! UART task: COBS-framed fixed-size LE command/response protocol. +//! UART host task: COBS-framed fixed-size LE command/response protocol. //! -//! Reads COBS frames from UART, decodes them, parses commands, routes them -//! to the radio or display task, and sends COBS-framed responses back. -//! Used for boards with USB-UART bridge chips (e.g. CP2102 on some Heltec V3). +//! 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 crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel}; -use crate::protocol_io::{self, CobsDecoder, MAX_FRAME}; +use super::framing::{self, CobsDecoder, MAX_FRAME}; #[task] -pub async fn uart_task( +pub async fn host_task( parts: crate::board::UartParts, commands: &'static CommandChannel, responses: &'static ResponseChannel, @@ -28,8 +26,7 @@ pub async fn uart_task( let mut decoder = CobsDecoder::new(); loop { - use embassy_futures::select::select; - use embassy_futures::select::Either; + use embassy_futures::select::{select, Either}; match select( embedded_io_async::Read::read(&mut rx, &mut read_buf), @@ -53,7 +50,7 @@ pub async fn uart_task( let _ = cmds.push(cmd); }); for cmd in cmds { - protocol_io::route_command( + framing::route_command( cmd, commands, responses, display_commands, has_display, mac, ) .await; @@ -61,7 +58,7 @@ pub async fn uart_task( } Either::Second(response) => { if let Some(frame) = - protocol_io::cobs_encode_response(response, &mut write_buf, &mut cobs_encode_buf) + 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"); diff --git a/firmware/src/usb/task.rs b/firmware/src/host/usb.rs similarity index 74% rename from firmware/src/usb/task.rs rename to firmware/src/host/usb.rs index aeb534d9..71e089e7 100644 --- a/firmware/src/usb/task.rs +++ b/firmware/src/host/usb.rs @@ -1,7 +1,4 @@ -//! USB CDC-ACM task: COBS-framed fixed-size LE command/response protocol. -//! -//! Reads COBS frames from USB, decodes them, parses commands, routes them -//! to the radio or display task, and sends COBS-framed responses back. +//! USB CDC-ACM host task: COBS-framed fixed-size LE command/response protocol. use defmt::warn; use embassy_executor::task; @@ -11,12 +8,12 @@ use embassy_usb::Builder; use static_cell::StaticCell; use crate::channel::{CommandChannel, DisplayCommand, DisplayCommandChannel, ResponseChannel}; -use crate::protocol_io::{self, CobsDecoder, MAX_FRAME}; +use super::framing::{self, CobsDecoder, MAX_FRAME}; const MAX_PACKET: usize = 64; #[task] -pub async fn usb_task( +pub async fn host_task( parts: crate::board::UsbParts, commands: &'static CommandChannel, responses: &'static ResponseChannel, @@ -24,10 +21,6 @@ pub async fn usb_task( has_display: bool, mac: [u8; 6], ) { - // ── USB device configuration ──────────────────────────────────── - // VID 0x1209: pid.codes open-source USB vendor ID - // PID 0x5741: DongLoRa product ID - // Class 0xEF/0x02/0x01: Miscellaneous / Interface Association Descriptor let mut config = embassy_usb::Config::new(0x1209, 0x5741); config.manufacturer = Some("DongLoRa"); config.product = Some("DongLoRa LoRa Radio"); @@ -36,7 +29,6 @@ pub async fn usb_task( config.device_protocol = 0x01; config.composite_with_iads = true; - // Use MAC address as USB serial number for unique device identification. static SERIAL_BUF: StaticCell<[u8; 12]> = StaticCell::new(); let serial_buf = SERIAL_BUF.init([0u8; 12]); const HEX: &[u8; 16] = b"0123456789ABCDEF"; @@ -64,18 +56,9 @@ pub async fn usb_task( let mut usb_dev = builder.build(); let (sender, receiver) = class.split(); - // ── Run USB device + protocol loop concurrently ──────────────── join( usb_dev.run(), - protocol_loop( - sender, - receiver, - commands, - responses, - display_commands, - has_display, - mac, - ), + protocol_loop(sender, receiver, commands, responses, display_commands, has_display, mac), ) .await; } @@ -89,8 +72,7 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>( has_display: bool, mac: [u8; 6], ) { - use embassy_futures::select::select3; - use embassy_futures::select::Either3; + use embassy_futures::select::{select3, Either3}; let mut read_buf = [0u8; MAX_PACKET]; let mut write_buf = [0u8; MAX_FRAME]; @@ -102,7 +84,7 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>( match select3( receiver.read_packet(&mut read_buf), responses.receive(), - embassy_time::Timer::after_millis(250), // DTR poll interval for disconnect detection + embassy_time::Timer::after_millis(250), ) .await { @@ -122,7 +104,7 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>( let _ = cmds.push(cmd); }); for cmd in cmds { - protocol_io::route_command( + framing::route_command( cmd, commands, responses, display_commands, has_display, mac, ) .await; @@ -130,7 +112,7 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>( } Either3::Second(response) => { if let Some(frame) = - protocol_io::cobs_encode_response(response, &mut write_buf, &mut cobs_encode_buf) + framing::cobs_encode_response(response, &mut write_buf, &mut cobs_encode_buf) { for chunk in frame.chunks(MAX_PACKET) { if sender.write_packet(chunk).await.is_err() { @@ -140,12 +122,9 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>( } } } - Either3::Third(()) => { - // Poll DTR every 250ms to detect host disconnect - } + Either3::Third(()) => {} } - // Check DTR after every select wake let connected = receiver.dtr(); if was_connected && !connected && has_display { display_commands.send(DisplayCommand::Reset).await; diff --git a/firmware/src/main.rs b/firmware/src/main.rs index a395ec37..62479bbd 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -25,15 +25,11 @@ mod display; mod driver; #[cfg(not(test))] mod hal; +#[cfg(not(test))] +mod host; mod protocol; #[cfg(not(test))] mod radio; -#[cfg(not(test))] -mod protocol_io; -#[cfg(all(not(test), feature = "heltec_v3"))] -mod uart; -#[cfg(all(not(test), not(feature = "heltec_v3")))] -mod usb; #[cfg(not(test))] use embassy_executor::Spawner; @@ -90,31 +86,16 @@ async fn run(spawner: Spawner) { .spawn(radio::radio_task(radio, &COMMANDS, &RESPONSES, &STATUS)) .expect("spawn radio_task"); - cfg_if::cfg_if! { - if #[cfg(feature = "heltec_v3")] { - spawner - .spawn(uart::uart_task( - comm, - &COMMANDS, - &RESPONSES, - &DISPLAY_COMMANDS, - has_display, - mac, - )) - .expect("spawn uart_task"); - } else { - spawner - .spawn(usb::usb_task( - comm, - &COMMANDS, - &RESPONSES, - &DISPLAY_COMMANDS, - has_display, - mac, - )) - .expect("spawn usb_task"); - } - } + spawner + .spawn(host::host_task( + comm, + &COMMANDS, + &RESPONSES, + &DISPLAY_COMMANDS, + has_display, + mac, + )) + .expect("spawn host_task"); if let Some(dp) = display { spawner diff --git a/firmware/src/uart/mod.rs b/firmware/src/uart/mod.rs deleted file mode 100644 index 3a53430b..00000000 --- a/firmware/src/uart/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! UART interface with COBS-framed command/response protocol. - -mod task; -pub use task::uart_task; diff --git a/firmware/src/usb/mod.rs b/firmware/src/usb/mod.rs deleted file mode 100644 index 60d99c5e..00000000 --- a/firmware/src/usb/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! USB CDC-ACM interface with COBS-framed command/response protocol. - -mod task; -pub use task::usb_task;