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)
- Each board exports TX_POWER_RANGE constant (min, max dBm)
- RadioConfig::validate() uses the board's range, not hardcoded limits
- TX_POWER_MAX sentinel (-128 on the wire) means "use board's maximum"
— resolved to the actual max before storing config
- GetConfig returns the resolved actual power, not the sentinel
- Default configs in examples use TX_POWER_MAX
- Protocol docs updated
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
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>
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).