feat: initial lora-dongle firmware scaffold

Transparent LoRa-over-USB dongle firmware supporting Heltec V3 (ESP32-S3)
and RAK 4631 (nRF52840) boards. Actor-model architecture with typed
channels between USB, radio, and display tasks.

- Protocol: Command/Response enums with COBS+postcard framing design
- Board abstraction: auto-discovered via build.rs Jinja2 codegen
- Radio task: SX1262 state machine (Idle → Receiving ↔ Transmitting)
- Display task: optional SSD1306 dashboard via StatusWatch
- RAK 4631: fully wired SPI/USB/I2C peripherals, clippy-clean
- Heltec V3: structure in place, awaits espup Xtensa toolchain
This commit is contained in:
swaits committed 2026-03-26 15:16:42 -06:00
commit c30f480c34
20 files changed
+2599

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
mod render;
mod task;
pub use task::display_task;
+25
View File
@@ -0,0 +1,25 @@
use crate::channel::{RadioState, RadioStatus};
/// Render the status dashboard to a framebuffer.
///
/// Pure logic — no hardware dependency. Takes a status snapshot and produces
/// draw calls that the display task flushes to the screen.
pub fn dashboard(status: &RadioStatus) {
// TODO: use embedded-graphics to render:
// Line 1: board name + radio state
// Line 2: freq / BW / SF
// Line 3: TX power / coding rate
// Line 4: RX count / TX count
// Line 5: last RSSI / SNR
let _ = status;
}
/// Format radio state as a short display string.
#[allow(dead_code)]
pub fn state_label(state: RadioState) -> &'static str {
match state {
RadioState::Idle => "IDLE",
RadioState::Receiving => "RX",
RadioState::Transmitting => "TX",
}
}
+22
View File
@@ -0,0 +1,22 @@
use embassy_executor::task;
use crate::board::DisplayParts;
use crate::channel::StatusWatch;
use super::render;
#[task]
pub async fn display_task(parts: DisplayParts, status: &'static StatusWatch) {
let _i2c = parts.i2c;
// TODO: init SSD1306 driver from I2C handle
// TODO: initial render of empty dashboard
let mut receiver = status.receiver().unwrap();
loop {
let state = receiver.changed().await;
render::dashboard(&state);
// TODO: flush framebuffer to display
}
}