This repository has been archived on 2026-08-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
donglora/DESIGN.md
T
swaits 0e9315280b refactor: drop Heltec V3, enforce standard toolchain, remove dead code
Heltec V3 (ESP32-S3) requires the espup Xtensa toolchain fork, which
violates the standard-toolchain-only principle. Removed until upstream
Rust supports Xtensa targets via stock rustup.

Also removes all #[allow(dead_code)] band-aids — unused items are
deleted rather than suppressed. DESIGN.md updated with standard
toolchain principle (#10) and peripheral model (input/telemetry slots).
2026-03-26 15:29:58 -06:00

4.1 KiB

Design Principles

These are the non-negotiable first principles of this project. Every design decision, naming choice, module boundary, and abstraction is evaluated against them. When in doubt, choose simplicity and clarity over cleverness.

What This Is

A transparent LoRa radio over USB. Plug the dongle into your PC, talk to any LoRa radio within range. That's it.

What This Is Not

Not a mesh network node. Not a Meshtastic device. Not a protocol implementation. Not an application framework. This firmware has no opinion about what you do with your radio — it just gives you clean access to it.

Principles

  1. Single purpose. Transparent LoRa radio over USB. No mesh, no routing, no application logic. Ever.

  2. Protocol-agnostic. The firmware is a dumb pipe. It exposes clean LoRa parameters (frequency, bandwidth, spreading factor, coding rate, sync word, TX power) and nothing else. No raw register access, no chip-specific leakage.

  3. Host-driven. The firmware makes zero autonomous decisions. The radio idles on boot until the host commands it. No persistence across reboots. The host is always in control.

  4. Zero-config. Boots, enumerates USB, waits for commands. The host plugs in and goes.

  5. Robustness over features. Tasks never panic. Errors are reported to the host. The radio self-recovers. Ship fewer features, but make them bulletproof.

  6. Minimal footprint. Avoid heavy dependencies. Keep the binary small. Every dependency must justify its inclusion.

  7. Auto-detect peripherals. If the board has a display, keyboard, or sensors, use them. No user configuration required.

  8. World-class Rust. Fully leverage the type system for safety. Strict no_std. Always clippy-clean. Thoughtfully designed — everything has an obvious home. Anyone who reads this code should immediately think "this is nice."

  9. Easy board support. Adding a new board means dropping a .rs file and adding a Cargo feature. Nothing else changes.

  10. Standard toolchain only. No custom Rust forks, no espup, no special SDKs. If a board can't build with stock rustup, it doesn't ship. This rules out Xtensa-based ESP32 chips (S2, S3) until upstream Rust fully supports them. RISC-V ESP32 variants (C3, C6, H2) are fine.

Architecture

Host PC
  │ USB CDC-ACM (COBS-framed postcard)
  ▼
usb_task ──Command──► radio_task ──► SX1262
         ◄──Response──     │
                      StatusWatch
                           ▼
                     display_task (optional) ──► OLED/TFT

Each task exclusively owns its peripheral. No shared state except typed channels and a watch signal. Pure actor model.

Peripheral Model

Boards can have optional peripherals beyond the radio and USB. The board's into_parts() returns Option<T> slots for each, and the corresponding task is only spawned when present.

Peripheral Slot Task Examples
Display Option<DisplayParts> display_task SSD1306 OLED, ST7789 TFT
Input Option<InputParts> input_task T-Deck keyboard, buttons
Telemetry Option<TelemetryParts> telemetry_task Battery voltage, temperature

Board files are always separate. A T-Deck is t_deck.rs, not a variant of Heltec V3. Each board owns its own init code and pin mappings. Shared logic between boards that use the same MCU can be factored into helper modules if it earns its keep, but never prematurely.

Technical Decisions

Decision Choice Why
USB framing COBS + postcard Compact, no_std native, battle-tested
Serialization postcard + serde Zero-alloc binary codec, perfect for embedded
Async runtime Embassy De facto standard for async embedded Rust
Display Radio status dashboard Freq, BW, SF, packet counts, RSSI/SNR
Boot behavior Idle until host commands Host-driven principle
Persistence None Always start fresh, true dumb pipe
Board codegen Jinja2 via build.rs Auto-discovers boards from filesystem
Rust toolchain Standard rustup only No forks, no espup, no special SDKs