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).
100 lines
4.1 KiB
Markdown
100 lines
4.1 KiB
Markdown
# 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 |
|