- Flattened radio/ directory to radio.rs (was just one file)
- Flattened display/ to display.rs (combined task + rendering as inline module)
- Moved SH1106 display driver reference from display/ to driver/ (done in Phase 2)
- Suppressed dead_code warnings for conditional USB/UART types in hal
- All boards: zero warnings, zero cfg in application code
- Created host/ module with internal cfg dispatch (USB vs UART)
- Moved usb/task.rs → host/usb.rs, uart/task.rs → host/uart.rs
- Moved protocol_io.rs → host/framing.rs
- Renamed usb_task/uart_task to host_task (uniform name)
- Removed all host-comm cfg from main.rs
- The run() function now has ZERO board-specific conditional compilation
- Deleted old usb/, uart/, protocol_io.rs modules
- Created hal/esp32s3.rs with MCU primitives (SPI, I2C, USB, UART, timer, MAC)
- Created hal/nrf52840.rs with shared nRF boilerplate (interrupts, bus wrapping, VBUS, MAC)
- Moved SH1106 OLED driver from display/ to driver/ (clear separation from app code)
- Board esp32s3.rs now delegates MCU init to hal, keeps only peripheral wiring (RadioParts)
- nRF board files use hal for shared StaticCell patterns, mac_address, interrupt bindings
- Board files still own pin assignments and peripheral construction
- Made protocol_io.rs compile for all boards (not just heltec_v3)
- Renamed FrameAccumulator → CobsDecoder, encode_response → cobs_encode_response
- Refactored USB task to use shared CobsDecoder and route_command from protocol_io
- Removed ~80 lines of duplicated COBS framing and command routing from usb/task.rs
- Single canonical MAX_FRAME definition in protocol_io.rs
- Track serial timeout in field instead of returning hardcoded 2s
- Always restore timeout in drain_rx, even on read errors
- Handle EINTR/Interrupted in read_frame (retry instead of error)
- Use connect_timeout for TCP mux (no more 2-min hangs)
- Set write timeout on mux sockets (unix + tcp)
- Log unexpected unsolicited frames in recv via tracing::warn
- Add unix socket timeout test for WouldBlock handling
- Add CHANGELOG.md, remove resolved CLAUDE_TODO.md
Add donglora-client (clients/rust/) and donglora-mux (mux/rust/) as
Rust implementations of the existing Python client library and mux
daemon. Both are standalone Cargo projects with no-panic guarantees
enforced by clippy deny lints.
donglora-client provides protocol types, COBS framing, USB device
discovery, serial/mux transports, and a high-level Client<T> with
send/recv. donglora-mux is a fully async (tokio) multiplexer daemon
with dongle hot-plug reconnect, per-client bounded send queues,
SetConfig locking, and StartRx/StopRx reference counting.
52 unit tests across both crates. Updates README roadmap checkboxes
and CONTRIBUTING docs to reflect the new implementations.
Drop INVALID and UNRELIABLE packets per PROTOCOL.md quality grading
to avoid retransmitting likely-corrupt data. MARGINAL packets are
forwarded with a warning tag. All RX log lines now include SNR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
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>
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>
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.
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.
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.
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.
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.
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)
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.
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.
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.
- 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
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)
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.
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.