Commit Graph
83 Commits
Author SHA1 Message Date
swaits 25f4b347f5 docs: improve architecture diagram with direct serial and mux multiplexing modes 2026-04-03 10:33:40 -06:00
swaits 89c2d51693 chore: massive repo reorganization 2026-04-03 10:28:38 -06:00
swaits 48f8d6c472 chore: update to latest ucogs 2026-04-01 13:06:52 -06:00
swaitsandClaude Opus 4.6 405f4f2f13 chore: tooling improvements
- Add cargo-binutils to mise.toml (needed for `just size`)
- Remove jj from mise.toml (optional, not a build dependency)
- Silence `mise install` output when tools are current (--quiet)
- Add `just outdated` recipe with embassy-executor exclusion
  (esp-rtos 0.2 pins embassy-executor ^0.9; 0.10 renamed arch-*
  features to platform-*, which crashes cargo-outdated)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:57:44 -06:00
swaitsandClaude Opus 4.6 d01de9d14d fix: MeshCore spec compliance audit and packet quality warnings
Audited against meshcore-spec and fixed all compliance gaps:

- Fix trace hash size encoding: use 1<<(flags&3) not (flags&3)+1
- Reject reserved path hash_size_code 3 per spec Section 3
- Minimum packet size 3 bytes (was 2)
- Add MAX_PACKET_PAYLOAD (184) and min payload (1) validation
- Tighten payload minimums: ANON_REQ≥51, encrypted≥20, group≥19,
  multipart≥2
- Add node_type 0 ("none") to NODE_TYPES
- Fix node_type fallback display (was "node=type=14", now "node=0x0e")

Add packet quality warnings for likely-corrupt frames:
- decode_meshcore_packet returns (str, list[str]) with quality warnings
- ADVERT: flag bad node_type, bad UTF-8 in name, future/bad timestamps
- Combine content warnings with low-SNR signal at display layer
- Warnings stack: one flag = maybe, multiple = high confidence corrupt

Update test corpus and generators for tightened validation rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:57:32 -06:00
swaitsandClaude Opus 4.6 d551095f3f chore: stop tracking generated corpus and pycache
These paths are already in .gitignore but were committed before the
ignore rules existed. Remove them from the index so they stay local.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:57:14 -06:00
swaits a4544ba10f fix: audit improvements (SAFETY comment, warn on invalid params, lint config)
Add SAFETY comment to nRF52840 FICR unsafe block explaining the
register addresses and why read_volatile is correct.

Replace silent fallback defaults in to_sf()/to_cr() with defmt::warn
so invalid values are logged instead of silently accepted.

Remove unnecessary #[allow(dead_code)] on DisplayCommand enum.
Add clippy::undocumented_unsafe_blocks = "deny" lint to Cargo.toml.
2026-03-31 17:32:09 -06:00
swaits a0fbd150cf feat: MAC-based USB serial number and protocol unit tests
Replace hardcoded USB serial "001" with the board's MAC address so
multiple dongles are uniquely identifiable on one host. Thread MAC
through usb_task as a parameter, removing the Board import.

Add 31 unit tests for protocol serialization: RadioConfig roundtrips,
all Command tags, all Response variants, validation boundaries, and
edge cases. Gate firmware modules with #[cfg(not(test))] and add
DONGLORA_HOST_TEST env var so `just test` runs on the host.
2026-03-31 17:32:02 -06:00
swaits 0943274496 refactor: extract shared ESP32-S3 board module
Move common type aliases, peripheral bundles, and init helpers for
ESP32-S3 boards into src/board/esp32s3.rs. Heltec V3 (146->48 lines)
and V4 (155->57 lines) become thin wrappers containing only
board-specific code (Vext GPIO pin, display reset sequence).

Update build.rs to exclude esp32s3.rs from board discovery and
mod.rs.j2 to include the shared module under an ESP feature gate.
2026-03-31 17:31:55 -06:00
swaits 83a76d4b48 chore: update tooling (mise, justfile, examples)
Add mise.toml for tool version management, rename Justfile to justfile,
add examples/justfile for running Python examples, and update docs.
2026-03-31 17:31:47 -06:00
swaits 9560349547 feat: switch ucobs to published crate 0.2, note upgrade blockers
Switch ucobs from local path dependency to crates.io v0.2. Add note
documenting that embassy-executor 0.10 and ucobs 0.3 are blocked on
the ESP Xtensa toolchain supporting Rust >= 1.93 stable.
2026-03-30 20:46:08 -06:00
swaits dd3b7c195f feat: add #nodeops, #weekly-net, #net, #repeater-ops channels 2026-03-30 17:40:23 -06:00
swaits 53699917e0 docs: add pronunciation guide and etymology to README 2026-03-30 16:36:27 -06:00
swaits 228a0cbf65 fix: clean up meshcore_rx.py output to match themed styling
Replace raw dict dumps and debug-style prints with consistent
themed output using >>> / <<< markers and formatted responses.
Quiet TxDone noise, dim status messages, and align all output
with the 2-space indent used by RX packet lines.
2026-03-30 16:21:45 -06:00
swaits b11fcac121 feat: redesign active display layout with mode box and title box
New layout:
- Mode box (left): RX/TX indicator with vertical separator
- Title box (right): 'DongLoRa v{ver}' + radio settings (freq/bw/sf/cr)
- Centered info rows: packet counters + RSSI/SNR with labels
- Sparkline bar graph fills remaining height
- Idle state returns to splash screen (active screen only for RX/TX)
- Layout positions derived from display dimensions (h/w agnostic)
- RSSI history buffer doubled to 128 slots (~2 min at 1s/slot)
- Smart compact_count() formatting for large RX/TX counters (k/M)
2026-03-30 15:32:45 -06:00
swaits 466acfc0b6 fix: show live sparkline bar immediately on packet receipt
The rightmost graph bar now renders the current (uncommitted) time
slot, so incoming packets appear on the sparkline instantly instead
of waiting up to 1 second for the slot timer to commit them. When
the slot advances, the live bar shifts left into committed history
and a fresh live slot begins.
2026-03-30 14:44:51 -06:00
swaits b34ca99fd4 feat: upgrade all dependencies to latest versions
Major version bumps:
- defmt 0.3 → 1.0
- esp-hal 0.22 → 1.0 (full API migration)
- esp-hal-embassy 0.5 → esp-rtos 0.2 (replacement crate)
- esp-backtrace 0.14 → 0.18
- esp-println 0.12 → 0.16
- embassy-nrf 0.2 → 0.10
- embassy-embedded-hal 0.2 → 0.6
- embassy-sync 0.6 → 0.8
- embassy-time 0.3 → 0.5
- embassy-usb 0.3 → 0.6
- embassy-usb-driver 0.1 → 0.2
- embedded-io-async 0.6 → 0.7
- heapless 0.8 → 0.9
- panic-probe 0.3 → 1.0
- minijinja 2.0 → 2.18
- owo-colors 4.0 → 4.3

embassy-executor stays at 0.9 (latest with Xtensa support via esp-rtos).
Updated ESP toolchain from 1.82 to 1.93.
2026-03-30 12:59:05 -06:00
swaits 02dfa4844a chore: change display timeout in meshcore_rx example to 90s 2026-03-30 11:33:24 -06:00
swaits 4c6cab0bfe refactor: replace donglora-cobs with external ucobs crate 2026-03-30 11:27:00 -06:00
swaits 579de01fa1 chore: gitignore fuzz target directory 2026-03-30 11:07:16 -06:00
swaits 9a06a5f55e fix: move build-std from .cargo/config.toml to Justfile (Xtensa only)
build-std=core,alloc was globally enabled in .cargo/config.toml, which
conflicted with cargo-fuzz's own -Zbuild-std, causing duplicate core
errors. Now only passed for Xtensa targets via the Justfile.
2026-03-30 11:06:32 -06:00
swaits 4d71020177 fix: add empty [workspace] to fuzz Cargo.toml to prevent parent workspace discovery 2026-03-30 11:01:27 -06:00
swaits 3e1f6eb8e7 fix: exclude fuzz crate from workspace (cargo-fuzz manages its own build) 2026-03-30 11:00:36 -06:00
swaits d7bb127d92 test: proptest property testing + cargo-fuzz fuzzing for donglora-cobs 2026-03-30 10:57:57 -06:00
swaits 1fbbf110bc feat: GetMac command + MacAddress response
New protocol command (tag 8): GetMac returns the board's 6-byte hardware
address. Useful when multiple dongles are connected to one host.

Response (tag 6): MacAddress containing 6 raw bytes.

Updated: PROTOCOL.md, CHANGELOG, donglora.py decoder, all_commands.py.
2026-03-30 09:08:59 -06:00
swaits 1fc86f413a test: exhaustive COBS interop tests against corncobs, porting guide, README
- 8 new interop tests cross-validating our COBS encoder/decoder against
  corncobs (including 500 random payloads). Total: 39 tests.
- New: src/board/PORTING.md — step-by-step guide to adding a new board
- New: crates/donglora-cobs/README.md — documents design choice and testing
- Updated README with lora_bridge.py example
2026-03-30 08:55:48 -06:00
swaits 9cdacc51f5 feat: lora_bridge.py — two-way LoRa bridge over TCP (Tailscale, WireGuard, etc.) 2026-03-30 08:51:15 -06:00
swaits 583bfb72e2 feat: auto-off display after 60s idle in meshcore_rx.py, wake on RX activity 2026-03-30 08:47:24 -06:00
swaits 57478c452a feat: move version to top row of splash screen (DongLoRa left, vX.Y.Z right) 2026-03-30 08:45:18 -06:00
swaits 2633daf437 refactor: LoRaBoard trait for compile-time board completeness verification
Every board's Board struct now implements the LoRaBoard trait, which
enforces that NAME, TX_POWER_RANGE, init(), and mac_address() are all
provided. Missing any of these is a compile error — no "good intentions"
needed.

The trait verifies metadata and initialization. Tasks use the board's
concrete types directly (Embassy tasks must be concrete). Board-specific
type aliases (RadioDriver, UsbDriver, DisplayI2c) and struct definitions
(RadioParts, UsbParts, DisplayParts) remain in each board file.

- New: src/board/traits.rs (LoRaBoard trait definition)
- Modified: all 3 board files (implement trait, remove standalone constants)
- Modified: main.rs (import trait for init)
- Modified: radio/task.rs (use Board::TX_POWER_RANGE via trait)
- Modified: display/task.rs (use Board::NAME via trait)
- Modified: build.rs (exclude traits.rs from board discovery)
- Removed: embedded-hal-async direct dep (not needed for trait approach)
2026-03-30 08:44:39 -06:00
swaits 99a01b867e fix: .expect() on board init unwraps, document forget() and fixed LoRa params
Board init: all .unwrap() → .expect() with diagnostic messages.
Vext/reset forget(): documented why pin must stay in output state.
Fixed LoRa params (PREAMBLE_LEN, CRC_ON, etc.): documented as
intentionally non-configurable.
Watch capacity: documented that display sees latest state only.
2026-03-29 22:29:04 -06:00
swaits f54579bfde feat: decode Config response in donglora.py, add all_commands.py example
Config response (GetConfig) now returns decoded RadioConfig fields instead
of raw hex. New all_commands.py exercises all 8 protocol commands in
sequence — Ping, SetConfig, GetConfig, StartRx, StopRx, Transmit,
DisplayOff, DisplayOn.
2026-03-29 22:24:46 -06:00
swaits ff8762b9e4 fix: use stored RX config for post-TX restart, not per-packet TX override
If Transmit included a per-packet config with different bandwidth/SF,
the radio would silently switch parameters when resuming RX. Now uses
state.config (the config from SetConfig) for post-TX RX restart.
2026-03-29 22:22:43 -06:00
swaits 9a13c4b951 fix: handle SerialException gracefully in all example scripts
All examples now catch serial.SerialException instead of crashing with
a traceback. This covers port contention (multiple processes), device
disconnect, and USB errors. meshcore_rx.py already had this.
2026-03-29 22:01:20 -06:00
swaits 73abecfdb5 docs: document NO_SIGNAL sentinel and DTR polling interval 2026-03-29 21:50:45 -06:00
swaits 729a65e3f3 fix: replace .unwrap() with .expect() in spawn calls and build.rs 2026-03-29 21:49:24 -06:00
swaits 379067de04 fix: remove dead COBS test module from usb/task.rs (tests live in donglora-cobs crate) 2026-03-29 21:48:12 -06:00
swaits 4e221486ee fix: remove stale postcard/serde references from README, DESIGN.md, CHANGELOG, donglora.py 2026-03-29 21:47:03 -06:00
swaits 73a2d02e9c chore: document USB VID/PID/class, abandon empty commit 2026-03-29 21:33:47 -06:00
swaits bfc0acae9b docs: module-level documentation, channel capacity rationale, architecture in main.rs 2026-03-29 21:33:08 -06:00
swaits 89308e3049 docs: fix PROTOCOL.md worked example bytes, add latency principle to DESIGN.md 2026-03-29 21:33:08 -06:00
swaits 65c43cda23 feat: donglora-cobs workspace crate with 31 spec-compliance tests
Extract COBS encoder/decoder into a separate no_std workspace crate
(crates/donglora-cobs) with comprehensive tests against the Cheshire &
Baker 1999 IEEE/ACM specification and Wikipedia canonical vectors.

Tests cover: empty input, single/multiple zeros, block boundaries at
254/255 bytes, alternating patterns, the Ping regression case (tag 0x00),
round-trip verification, and error cases (truncation, unexpected zeros).

Replace inline cobs_decode() in usb/task.rs with the tested crate.
Replace external cobs dependency with workspace crate.
Fix encoder bug at 254-byte block boundary (spurious trailing code byte).
2026-03-29 21:33:08 -06:00
swaits ee71bb69d3 fix: add explicit SF=7 and CR=5 match arms in radio converters
to_sf() relied on the catch-all _ => _7 to handle SF=7, and to_cr()
relied on _ => _4_5 for CR=5. Both work by accident but are misleading
— a reader would think SF=7 and CR=5 are unhandled. Make them explicit.
2026-03-29 21:33:08 -06:00
swaits b2e8b1d366 fix: COBS decoder stripped trailing 0x00 data bytes (broke Ping command)
The cobs_decode() function had a bogus 'strip trailing zero' step that
removed the last byte if it was 0x00. The Ping command IS tag byte 0x00,
so its decoded payload was stripped to empty, causing from_bytes to return
None. The COBS standard algorithm doesn't produce spurious trailing zeros
— the post-processing was simply wrong.

Also fix read_frame to skip bare 0x00 sentinels on fresh USB connects.
2026-03-29 21:33:07 -06:00
swaits d22d2b92ac feat: show MAC address on splash screen
Splash/waiting screen now shows:
  Row 1: "DongLoRa" (bold)
  Row 2: "Board Name vX.Y.Z"
  Row 3: MAC address (XX:XX:XX:XX:XX:XX)
  Row 4: "Waiting for host..."

Each board reads its MAC at init:
- ESP32-S3: efuse MAC via esp_hal::efuse::Efuse::mac_address()
- nRF52840: FICR device address registers

BoardInfo struct bundles name/version/mac to keep dashboard() under
clippy's argument limit.
2026-03-29 21:33:07 -06:00
swaits 7dc79c1a24 fix: clippy-clean — rename to_bytes→write_to, fix Justfile clippy args 2026-03-29 20:42:59 -06:00
swaits fd360e0cfe docs: rewrite PROTOCOL.md for fixed-size LE encoding — no varint, no zigzag 2026-03-29 20:42:59 -06:00
swaits 7a947a1a05 refactor: update Python examples for fixed-size LE wire format
Remove all varint/zigzag encoding. Every field is now struct.pack/unpack
with fixed-size little-endian format strings. RadioConfig is a single
struct.pack('<IBBBHB', ...) call. Response decoding uses struct.unpack_from
with known offsets. No encoding helpers needed beyond struct.
2026-03-29 20:42:59 -06:00
swaits 1646564bc1 refactor: replace postcard/serde with fixed-size LE wire format
Drop postcard and serde dependencies entirely. All integers are now
fixed-width little-endian on the wire — no varints, no zigzag encoding.
A developer sending tx_power_dbm=14 sees 0x0E on the wire. No surprises.

- RadioConfig: 10 bytes fixed (freq:4 LE, bw:1, sf:1, cr:1, sync:2 LE, pwr:1)
- Commands/Responses: tag byte + fixed-size fields
- Vec<u8>: 2-byte LE length prefix + raw bytes
- Option<T>: 1-byte (0=None, 1=Some) + T
- COBS framing preserved (just byte-stuffing, no encoding tricks)
- Manual COBS decode replaces postcard's CobsAccumulator
- Removes ~15KB from firmware binary (no serde monomorphization)
2026-03-29 20:42:58 -06:00
swaits 584524dace docs: fix worked example, encoding table, and CHANGELOG for u8/i8 raw bytes and TX_POWER_MAX 2026-03-29 20:42:58 -06:00