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.
DongLoRa Firmware
Embedded Rust firmware for DongLoRa boards. Exposes the LoRa radio over USB CDC-ACM using a COBS-framed binary protocol.
Building
just setup # one-time toolchain install
just build heltec_v4 # build for a specific board
just flash heltec_v4 # build + flash
just check-all # compile-check all boards
just clippy heltec_v4 # lint
just test # host-side protocol unit tests
All commands are run from the firmware/ directory.
Supported Boards
| Board | Feature | MCU | Radio | USB | Target |
|---|---|---|---|---|---|
| Heltec V3 | heltec_v3 |
ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
| Heltec V3 (UART) | heltec_v3_uart |
ESP32-S3 | SX1262 | CP2102 bridge | xtensa-esp32s3-none-elf |
| Heltec V4 | heltec_v4 |
ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
| ELECROW ThinkNode-M2 | elecrow_thinknode_m2 |
ESP32-S3 | SX1262 | CP2102 bridge | xtensa-esp32s3-none-elf |
| LilyGo T-Beam | lilygo_tbeam |
ESP32 | SX1276 | CP2102 bridge | xtensa-esp32-none-elf |
| LilyGo T-Beam S3 Supreme | lilygo_tbeam_supreme |
ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
| RAK WisBlock 4631 | rak_wisblock_4631 |
nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
| Waveshare RP2040-LoRa | waveshare_rp2040_lora |
RP2040 | SX1262 | Native CDC-ACM | thumbv6m-none-eabi |
| Wio Tracker L1 | wio_tracker_l1 |
nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
Heltec V3 USB Note
The default heltec_v3 build uses native USB CDC-ACM, which requires a
hardware modification to your board. Stock Heltec V3 boards route USB through a
CP2102 bridge chip to UART — the ESP32-S3's native USB pins (GPIO19/GPIO20) are
routed to header pins but not connected to the USB connector.
To enable native USB on a Heltec V3:
- Solder 0-ohm (or 22-ohm) resistors at pads R29 (D+) and R3 (D-)
- Disconnect the CP2102 from the USB connector's D+/D- lines (cut traces or remove the CP2102 bridge resistors)
After this mod, the board appears as /dev/ttyACM0 with the DongLoRa VID:PID
(1209:5741), just like the V4 and nRF boards.
If you have a stock (unmodified) V3, use heltec_v3_uart instead:
just build heltec_v3_uart
just flash heltec_v3_uart
This builds firmware that communicates via the CP2102 bridge (appears as
/dev/ttyUSB0).
Adding a Board
See src/board/PORTING.md.
Protocol
See PROTOCOL.md for the complete wire format specification.