MeshCore is a protocol implementation ON TOP of DongLoRa, not part of the
dongle itself. Moving it to examples/ makes the separation clear while
keeping it accessible as a real-world usage example.
CodingRate was an enum with repr(u8) values that didn't match postcard's
wire encoding (variant index). Instead of papering over the mismatch,
make cr a plain u8 in RadioConfig. Host sends the denominator directly:
5 for CR 4/5, 6 for CR 4/6, etc. No lookup tables, no index confusion.
Remove CodingRate enum, cr_denom() helper, and the Bandwidth import that
was only needed for cr_denom. ErrorCode repr values also fixed to 0-based.
Firmware:
- fix(protocol): SNR type i8→i16 to match lora_phy PacketStatus
- fix(display): combine splash + waiting into one screen
- fix(display): move TX power to row 0 right-justified
- feat(display): sparkline distinguishes TX from RX — TX bars are dotted
(alternating pixel rows), RX bars are solid. TX-only slots show a short
marker. TX takes precedence over RX in the same time slot.
Host tool (tools/meshcore_rx.py):
- feat: full MeshCore packet decoder with colored ANSI output
- feat: GRP_TXT decryption using 276 channels from channels.csv
- feat: Watchman loop/collision reporter to #watchman
- feat: ADVERT-informed node registry with 12h TTL persistence
- feat: transport code fallback for older MeshCore firmware
- fix: robust error handling for long-running operation
- fix: collision detection uses evidence-based reasoning from advert cache
Channel directory (tools/channels.csv): 276 channels
Test corpus (corpus/): JSON Schema, 28 hand-crafted vectors, Hypothesis generator
- Poll DTR every 250ms via select3 timer arm (read_packet blocks
forever after host closes port, so DTR check must not depend on it)
- DTR drop → send DisplayCommand::Reset (blocking send, not try_send)
- DTR rise → send DisplayCommand::On
- Display task: disconnected flag ignores StatusWatch while set,
preventing stale radio status from overriding the idle screen
- On reconnect, display grabs current RadioStatus from watch and
renders dashboard immediately (no waiting for next radio event)
- Simplified clippy to use _cargo helper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch all ESP32-S3 boards from UsbSerialJtag to USB OTG CDC-ACM via
embassy-usb. The USB device stack is now shared code — boards only
provide the platform-specific embassy-usb Driver.
- Custom VID:PID 1209:5741 (open-source VID, "WA" PID)
- CDC-ACM class with proper device enumeration
- COBS+postcard protocol over CDC-ACM packet read/write
- Concurrent USB device loop + protocol loop via join()
- On disconnect: sends DisplayCommand::Reset to show "Waiting for host..."
- Justfile: espflash no longer uses --monitor (OTG steals PHY)
- Test script updated to detect by new VID:PID
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix CodingRate encoding: postcard uses 0-based variant index, not
repr(u8) value. CR 4/5 is variant 0, not 1.
- Shorten RSSI/SNR display line to fit 21-char width
- Python script auto-detects DongLoRa by USB VID:PID (303a:1001),
reconnects on device reset/disconnect, no more crash on RST
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Protocol:
- SetConfig, StartRx, StopRx now send Response::Ok on success
(host was timing out waiting for acks that never came)
Display:
- Full frequency precision: 910.525MHz not 910.5MHz
- Full bandwidth labels: 62.5kHz not 62k
- RSSI/SNR row shows units (dBm, dB)
- Separator line moved down 3px to give RSSI/SNR breathing room
- Sparkline is now time-based: each bar = 1 second, scrolls left,
showing ~1 minute of RSSI history. Empty slots = no packets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up the lora-phy driver in radio_task:
- SetConfig stores radio parameters
- StartRx enters continuous receive mode via lora-phy
- rx_once waits for a packet, reports RSSI/SNR/payload to host
- StopRx returns to standby
- Transmit sends a payload with the configured (or overridden) params
- Concurrent RX: select between incoming packets and host commands
- Automatic RX restart after receiving a packet or completing TX
- Error handling: radio errors are reported to host, RX fallback to idle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up UsbSerialJtag on ESP32-S3 boards for host communication:
- Read COBS-framed postcard commands from host via serial
- Route DisplayOn/Off to display task, everything else to radio
- Serialize responses back to host as COBS frames
- Use postcard::CobsAccumulator for chunked deserialization
- Concurrent read/write via embassy select
RAK nRF52840 USB (embassy-usb CDC-ACM) is still a stub — different
init path, will be implemented separately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebrand from lora-dongle to DongLoRa (portmanteau of dongle + LoRa).
Lowercase donglora in Cargo package name, firmware filenames, and code.
Splash screen shows "DongLoRa" in 9x15 bold.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root causes found and fixed:
- USB OTG init stole the PHY from USB-Serial-JTAG (the only USB on V4)
- executor-thread feature conflicted with esp-hal-embassy's executor
- Linker flags needed -Wl, prefix for GCC-to-LD passthrough
- Missing exception-handler feature on esp-backtrace
- Missing alloc in build-std
- Missing LTO and codegen-units=1 in release profile
- Vext on V4 is active LOW (datasheet correct, Arduino lib was wrong)
Now matches the official esp-hal v0.22.0 examples exactly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- flash command uses espflash for Xtensa boards, probe-rs for ARM
(ESP32-S3 has a USB-serial bootloader, not a debug probe)
- Pre-flight checks before flash: missing tools get a clear error with
the exact install command instead of a cryptic failure
- Pre-flight check for esp toolchain before build/clippy too
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes:
- Add .cargo/config.toml with -Tlinkall.x for xtensa target (resolves
ROM functions, interrupt handlers, memory layout)
- Add integrated-timers feature to embassy-executor for ESP32 boards
(resolves _embassy_time_schedule_wake)
- Add `use esp_println as _` to pull in defmt global_logger (was being
stripped as dead code because nothing referenced the crate)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three tiers: supported now, next up (popular/straightforward), and
future (niche/contributions welcome). Board names follow MeshCore
flasher conventions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Source ~/export-esp.sh in Justfile for xtensa builds (provides GCC)
- Add build-all target for producing firmware
- Trap SIGINT in build-all/check-all so Ctrl+C stops immediately
- Add .cargo/config.toml to .gitignore (may be generated)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add heltec_v4 board: ESP32-S3R2, SX1262, SSD1315 display, native USB,
Vext on GPIO36 (active HIGH)
- Wire up heltec_v3 with real esp-hal peripherals (was a stub)
- V3 and V4 share radio/I2C pins, differ in Vext pin/polarity and
display reset
- Restructure main.rs: cfg_if selects embassy_executor::main (Cortex-M)
vs esp_hal_embassy::main (Xtensa) per board
- Display detection via SSD1306 init in display_task — boards always
provide I2C bus, task exits gracefully if no display responds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use nightly cargo + esp rustc (RUSTC override) + -Zbuild-std=core for
Xtensa targets. This avoids the esp cargo's inability to parse modern
crate manifests (edition 2024) while still using esp's LLVM with full
Xtensa support.
Pin proc-macro-crate to 3.2.0 to avoid transitive deps requiring
rustc 1.85+.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts the removal of heltec_v3 — it's a major board. Xtensa builds
use the esp toolchain (+esp via espup); check-all gracefully skips
boards whose toolchain isn't installed.
Build artifacts now land in firmware/ with readable names like
lora-dongle-rak_4631-release.elf instead of being buried in target/.
DESIGN.md updated with "support all common boards" principle (#10)
and corrected toolchain policy (#11).
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).