Splash holds bright for 10 s on (re-)entry, then drops to dim while LearnMore/Info keep alternating. The dashboard stays bright for 3 s after the last packet or mode change, then dims in place without disturbing the Listening/Transmitting state or the 1 s sparkline timer. Cross-leaf TX↔RX hops stay bright throughout — `set_dim` is fired by the 3 s timer handler as an action rather than as a sibling `exit:` action, so mode-change leaf-crossings no longer flash dim→bright on every swap. Two driver families (`ssd1306::Ssd1306Async` and the in-tree `Sh1106`) wrapped behind a single `DisplayBrightness` trait in `src/driver/mod.rs`; the board trait's `DisplayDriver` associated type now requires it. Dim/bright contrast values centralized as `CONTRAST_BRIGHT` / `CONTRAST_DIM` constants — tune in one place. `On.on(CmdReset)` now targets `LearnMore` directly rather than the ancestor `Splash`. Under hsmc 0.2 up-transition semantics, targeting an already-active ancestor unwinds only below it without re-descent; the USB-disconnect reset path was previously relying on the old re-enter-and-descend behavior and would have parked at `Splash` with a dead alternation timer. Bumps `hsmc` 0.1.0 → 0.2 for the up-transition semantic fix.
323 lines
15 KiB
Markdown
323 lines
15 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to this project will be documented in this file.
|
|
|
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
|
## [Unreleased]
|
|
|
|
### Added
|
|
|
|
- **OLED auto-dim after inactivity.** The display is bright on any
|
|
activity — splash entry, mode change into a dashboard leaf, packet
|
|
RX/TX — and dims after a short idle window. Splash holds bright for
|
|
10 s, then drops to dim while the LearnMore / Info carousel keeps
|
|
alternating. The dashboard stays bright for 3 s after the last
|
|
packet or mode change, then dims in place without changing
|
|
Listening/Transmitting state. Cross-leaf TX↔RX hops transition
|
|
bright→bright with no intermediate dim — `set_dim` is fired as an
|
|
action by the 3 s timer handler rather than as a sibling `exit:`
|
|
action, so mode-change leaf-crossings never see a flash.
|
|
- `DisplayBrightness` trait in `src/driver/mod.rs` unifies the
|
|
two-level backlight API across the `ssd1306` crate's `Ssd1306Async`
|
|
and the in-tree `Sh1106` driver. Contrast values centralized as
|
|
`CONTRAST_BRIGHT` (`0xFF`) and `CONTRAST_DIM` (`0x08`) constants so
|
|
the two levels can be tuned in one place.
|
|
|
|
### Changed
|
|
|
|
- `src/display.rs` statechart restructured:
|
|
- `Splash` gains `entry: set_bright` plus an action-only
|
|
`on(after 10s) => set_dim` handler so the splash bright window
|
|
is time-scoped without disturbing the LM↔Info alternation.
|
|
- `Dashboard` `Listening` and `Transmitting` each gain a nested
|
|
`*Bright` substate owning the `entry: set_bright` action. Packet
|
|
handlers moved into the leaves and target the leaf's `*Bright` —
|
|
sibling-to-sibling within the leaf restarts the 3 s timer without
|
|
re-entering the leaf itself (paint / 1 s sparkline timer
|
|
preserved). The 3 s timeout handler fires `set_dim` as an action
|
|
and up-transitions to the leaf, parking the machine at the leaf
|
|
with no active child.
|
|
- `On.on(CmdReset)` now targets `LearnMore` directly rather than
|
|
`Splash`. Under hsmc 0.2 up-transition semantics, targeting
|
|
`Splash` from within `Splash.LearnMore` (USB-disconnect race path)
|
|
would only unwind below `Splash` and park with no active child,
|
|
leaving the LM↔Info alternation timer dead and the QR frozen on
|
|
screen.
|
|
- `LoRaBoard::DisplayDriver` trait bound extended with
|
|
`+ DisplayBrightness` so brightness is part of the board contract
|
|
alongside `DrawTarget<Color = BinaryColor>`.
|
|
|
|
### Fixed
|
|
|
|
- Elecrow ThinkNode-M2 board doc corrected: the shipping revision
|
|
carries a WCH CH340K USB-UART bridge (USB `1a86:7522`), not a
|
|
Silicon Labs CP2102. `just flash elecrow_thinknode_m2` now matches
|
|
the CH340K / CH340 VID:PIDs directly instead of falling through
|
|
to espflash auto-detect.
|
|
|
|
### Dependencies
|
|
|
|
- `hsmc` bumped to `0.2` to pick up the up-transition fix (transition
|
|
to an already-active ancestor unwinds only the subtree strictly
|
|
below the target; no exit/entry on the target; no default descent).
|
|
|
|
## [1.1.0] - 2026-04-22
|
|
|
|
Small but high-value behavior change: `SET_CONFIG` issued while the
|
|
radio is in continuous RX no longer drops the radio (and the OLED)
|
|
back to idle. The radio now auto-re-arms RX with the new modulation
|
|
once reconfiguration completes, matching DongLoRa Protocol 1.1's
|
|
§3.5 / §6.3 amendments.
|
|
|
|
### Changed
|
|
|
|
- `src/radio.rs`: inside `state Receiving`, add `on(ConfigApplied)
|
|
=> ResumingRx` to shadow the parent `Configured` state's
|
|
`on(ConfigApplied) => Idle` handler. The reconfigure path now
|
|
runs `apply_set_config` → `ConfigApplied` → `ResumingRx.entry:
|
|
try_start_rx_hw` → `StartRxOk` → `Receiving` without ever
|
|
entering `Idle`.
|
|
- The OLED's `Dashboard → Splash` transition was previously driven
|
|
by `RadioEvent::Idle` firing on `Configured.Idle.entry`. That
|
|
event no longer fires during a SET_CONFIG-from-Receiving
|
|
sequence, so the display stays on Dashboard.
|
|
|
|
### Dependencies
|
|
|
|
- `donglora-protocol` bumped to `1.1.0` (spec-only change; matches
|
|
the new normative text the firmware now implements).
|
|
|
|
### Use-case unlock
|
|
|
|
- Scanner / time-division / dynamic-modulation workflows that
|
|
rotate LoRa parameters mid-RX no longer need to re-issue
|
|
`RX_START` after every `SET_CONFIG`. One call, radio continues
|
|
to receive.
|
|
|
|
## [1.0.0] - 2026-04-22
|
|
|
|
The 1.0 release. Firmware now speaks DongLoRa Protocol v2 end to end,
|
|
depends on the freshly-published `donglora-protocol` 1.0.0 and `hsmc`
|
|
0.1.0 from crates.io, and ships GitHub Release binaries for every
|
|
supported board.
|
|
|
|
### Breaking
|
|
|
|
- **DongLoRa Protocol v2 wire format** (wire-incompatible with 0.x
|
|
clients and firmware). Every `type_id`, tag-correlation rule, and
|
|
field layout follows `PROTOCOL.md` v1.0.
|
|
- **Host framing replaced.** `src/host/framing.rs` now drives a
|
|
streaming `FrameDecoder` from `donglora-protocol` and emits
|
|
`(type_id, tag, payload)` frames; the 0.x COBS-per-response shim is
|
|
gone.
|
|
- **Radio task reshape.** `src/radio.rs` emits v2 `TX_DONE` /
|
|
`SetConfigResult` / `RxPayload` / `RxOrigin` shapes directly; no 0.x
|
|
translation layer remains.
|
|
|
|
### Changed
|
|
|
|
- `hsmc` switched from path-dep on the local workspace to
|
|
`version = "0.1.0"` from crates.io.
|
|
- `donglora-protocol` switched from path-dep on `../protocol` to
|
|
`version = "1.0.0"` from crates.io; firmware builds are now
|
|
reproducible from a clean checkout with no sibling repo required.
|
|
- `PROTOCOL.md` updated to the DongLoRa Protocol v2 normative spec,
|
|
kept byte-for-byte in sync with the `donglora-protocol` crate's
|
|
copy.
|
|
|
|
### Infrastructure
|
|
|
|
- Cargo.toml `repository` + `homepage` now point at the canonical
|
|
`https://github.com/donglora/firmware` (the `swaits/donglora`
|
|
repo is archived).
|
|
|
|
## [0.5.0] - 2026-04-16
|
|
|
|
### Added
|
|
|
|
- BLE soft-mesh multiplexer design document (`BLE_MUX_DESIGN_DOC.md`) —
|
|
specification for turning a dongle from a 1:1 USB device into a shared
|
|
near-field radio gateway for multiple BLE-attached phones. Three-phase
|
|
rollout (v1 MVP, v2 hardening, v3 multi-dongle mesh) with regulatory
|
|
duty-cycle and TX-access-control enforcement from v1. Targets nRF52840
|
|
first; ESP32-S3 reserved for v2.
|
|
- `debug-checkpoint` cargo feature with a dedicated `debug_blink` task and
|
|
a latching atomic checkpoint — LED-pattern diagnostic for UART-only
|
|
boards where defmt output would corrupt the host protocol. Zero
|
|
footprint when the feature is off.
|
|
|
|
### Changed
|
|
|
|
- Radio task rewritten as an `hsmc` statechart (`Unconfigured` /
|
|
`Configured.{Idle, StartingRx, Receiving, ResumingRx,
|
|
Transmitting.{FromIdle, FromRx}}`). The main loop is now a single
|
|
`machine.run().await`; command reading, packet RX, and TX are
|
|
state-scoped `during:` activities. Post-TX RX re-arm is modeled as
|
|
state identity (`TransmittingFromRx` → `ResumingRx`) instead of a
|
|
`was_receiving` flag.
|
|
- Display task rewritten as an `hsmc` statechart (`Off` /
|
|
`On.{Splash.{LearnMore, Info}, Dashboard.{Listening, Transmitting}}`).
|
|
The 5 s splash alternation and 1 s sparkline tick use `on(after …)` /
|
|
`on(every …)` transitions — no external timer task.
|
|
- USB host task now carries a small `hsmc` statechart (`Disconnected` /
|
|
`Connected`) whose entry/exit actions handle display wake, radio
|
|
`StopRx`, and display reset — replacing the open-coded `was_connected`
|
|
edge detector.
|
|
- Radio → Display channel switched from a `RadioStatus` watch to a typed
|
|
`RadioEvent` queue. The radio emits discrete `EnteredRx` / `EnteredTx`
|
|
/ `Idle` / `PacketRx` / `PacketTx` / `ConfigChanged` events; downstream
|
|
consumers observe transitions directly rather than diffing snapshots.
|
|
- Wire protocol types and COBS framing moved out of firmware into the
|
|
shared `donglora-protocol` crate (path dep on `../protocol`). Firmware
|
|
`src/protocol.rs` is now a re-export shim preserving every call site;
|
|
`src/host/framing.rs` keeps only the embassy-specific `route_command`.
|
|
|
|
### Fixed
|
|
|
|
- USB disconnect recovery: on a read error the DTR-edge-detect branch
|
|
now runs unconditionally (was early-continuing in some paths, starving
|
|
the display-reset / radio-stop handoff).
|
|
|
|
## [0.4.0] - 2026-04-11
|
|
|
|
### Added
|
|
|
|
- ELECROW ThinkNode-M2 board support (ESP32-S3 + SX1262 + 1.3" SH1106 OLED
|
|
via CP2102 UART bridge, 1000 mAh Li-Po handheld)
|
|
- Pin map verified against Meshtastic's
|
|
`variants/esp32s3/ELECROW-ThinkNode-M2`
|
|
- DIO3 TCXO driven at 3.3 V (not 1.8 V like Heltec)
|
|
- Dedicated `LORA_POWER_EN` (GPIO48) sequenced HIGH before SPI init
|
|
- `VEXT_ENABLE` (GPIO46) is active HIGH — opposite polarity from Heltec
|
|
- Reuses the existing async SH1106 driver at I2C address 0x3C
|
|
|
|
### Changed
|
|
|
|
- `hal::esp32s3::{init_spi, init_i2c}` and `board::esp32s3::init_radio` are
|
|
now generic over GPIO type, so ESP32-S3 boards with different pin
|
|
assignments can share the helpers.
|
|
- `board::esp32s3::init_radio` takes a `TcxoCtrlVoltage` parameter instead
|
|
of hardcoding `Ctrl1V8` — needed for ThinkNode-M2's 3.3 V TCXO.
|
|
- SSD1306 display items in `board::esp32s3` are now cfg-gated to Heltec
|
|
boards (ThinkNode-M2 supplies its own SH1106 `create_display`).
|
|
- `justfile` uses `cargo +esp` for Xtensa targets instead of `cargo +nightly`
|
|
with a `RUSTC=esp` override — the self-contained esp toolchain keeps its
|
|
cargo, rustc, and rust-src versions aligned. Public nightly's rust-src
|
|
had drifted ahead of the esp fork, breaking `-Zbuild-std`.
|
|
- Migrated to embassy-executor 0.10 and embassy-rp 0.10
|
|
- ESP crates (esp-hal, esp-rtos, esp-backtrace, esp-println) patched from
|
|
esp-rs/esp-hal main branch until esp-rtos publishes with 0.10 support
|
|
- Adapted to embassy-executor 0.10 SpawnToken API (task functions now return
|
|
`Result<SpawnToken, SpawnError>`, `Spawner::spawn` no longer returns Result)
|
|
- Adapted to esp-hal main branch API changes: `efuse::base_mac_address()`,
|
|
`esp_rtos::start` now requires `SoftwareInterrupt`, embassy-rp SPI
|
|
constructor now requires DMA interrupt bindings
|
|
- `just check` now runs clippy with `-D warnings` instead of plain `cargo check`
|
|
- Rust toolchain bumped to 1.94 in mise.toml
|
|
|
|
### Fixed
|
|
|
|
- `core::mem::forget(output)` pattern in ESP32-S3 board files is now
|
|
`let _vext = Output::new(...)` — current esp-hal makes `Output` non-Drop,
|
|
which made `forget` a no-op and tripped clippy's `forget_non_drop` lint.
|
|
- UART boards (heltec_v3_uart) display stuck on splash screen after boot.
|
|
Host task sent `DisplayCommand::Reset` (which sets disconnected=true) instead
|
|
of `DisplayCommand::On`. UART has no DTR, so nothing ever cleared it.
|
|
|
|
## [0.3.0] - 2026-04-10
|
|
|
|
### Added
|
|
|
|
- Waveshare RP2040-LoRa board support (RP2040 + SX1262, no OLED, LED on GPIO25)
|
|
- First RP2040 board — third MCU family after ESP32-S3 and nRF52840
|
|
- New `hal/rp2040.rs` module with MCU primitives (SPI, USB, flash unique ID)
|
|
- USB-C via FPC ribbon cable to adapter board
|
|
- PE4259 antenna switch on GPIO17, 850-930 MHz (HF)
|
|
|
|
### Changed
|
|
|
|
- `cortex-m-rt/set-vtor` moved from global dependency to per-board feature
|
|
(Cortex-M0+ has no VTOR register)
|
|
- `build.rs` memory.x selection is now target-aware (thumbv6m vs thumbv7em)
|
|
|
|
## [0.2.1] - 2026-04-07
|
|
|
|
### Fixed
|
|
|
|
- Display stuck on status screen after USB disconnect (stale radio status
|
|
updates re-rendered dashboard over splash because `disconnected` flag was
|
|
not set in the `Reset` handler)
|
|
- Radio left running after host disconnect, now sends `StopRx` when DTR drops
|
|
- System deadlock when host app closes serial port while radio is receiving
|
|
(unsolicited responses in radio task now use `try_send` to avoid blocking
|
|
on a full response channel)
|
|
- LED orphaned if OLED display initialization fails (display task now falls
|
|
back to a LED-only event loop instead of returning early)
|
|
|
|
## [0.2.0] - 2026-04-07
|
|
|
|
### Added
|
|
|
|
- Wio Tracker L1 board support (nRF52840 + SX1262, SH1106 OLED)
|
|
- LED blink on packet activity: brief flash on RX and TX when display is on
|
|
- RX brightness scales with SNR (stronger signal = brighter)
|
|
- Works on all boards with a user LED (Heltec V3/V4, Wio Tracker)
|
|
- Native USB CDC-ACM support for Heltec V3 (requires hardware mod: solder R29/R3)
|
|
- `heltec_v3_uart` feature for stock (unmodified) V3 boards via CP2102 bridge
|
|
- `RgbLed` trait and `SimpleLed` driver for GPIO-based LEDs
|
|
- `LoRaBoard` trait with full associated types (`RadioParts`, `CommParts`,
|
|
`DisplayParts`, `DisplayDriver`, `LedDriver`) and `BoardParts<R, C, D, L>` struct
|
|
|
|
### Changed
|
|
|
|
- Major firmware architecture reorganization:
|
|
- `hal/` module for MCU-specific primitives (ESP32-S3, nRF52840)
|
|
- `driver/` module for hardware peripheral drivers (SH1106, SimpleLed)
|
|
- `host/` module for unified USB/UART communication with internal cfg dispatch
|
|
- `board/` contains only board definitions — no MCU helpers or drivers mixed in
|
|
- Board files are declarative wiring diagrams calling hal primitives
|
|
- `run()` function has zero board-specific conditional compilation
|
|
- Display init moved to board layer (`create_display()`) — display task has no cfg
|
|
- Flattened `radio/` and `display/` directories (no single-file directories)
|
|
- `build.rs` auto-discovers boards by content (implements `LoRaBoard`) and
|
|
helper modules by `use super::` imports — no exclusion lists
|
|
- `check` and `clippy` commands now use `--release` profile
|
|
- Deduplicated USB/UART protocol framing (shared `CobsDecoder` + `route_command`)
|
|
- Removed `ErrorCode::CrcError` (SX1262 silently drops bad-CRC packets)
|
|
|
|
### Fixed
|
|
|
|
- Display stuck on splash screen for UART boards (disconnected flag started true)
|
|
|
|
## [0.1.0] - 2026-03-15
|
|
|
|
### Added
|
|
|
|
- Transparent LoRa radio over USB CDC-ACM (VID `1209`, PID `5741`)
|
|
- COBS-framed fixed-size LE binary protocol (9 commands, 7 responses)
|
|
- Radio configuration: frequency, bandwidth, spreading factor, coding rate, TX power
|
|
- Continuous RX mode with RSSI/SNR per packet
|
|
- Single-shot TX with optional per-packet config override
|
|
- SSD1306 OLED display support with status dashboard
|
|
- Radio state, frequency, modulation parameters
|
|
- Packet counters (RX/TX)
|
|
- RSSI sparkline (1-minute history, TX shown as dotted bars)
|
|
- Combined splash/waiting screen on disconnect
|
|
- Auto-detect USB host connect/disconnect via DTR
|
|
- Config validation against per-board hardware limits
|
|
- `TX_POWER_MAX` sentinel (`-128`): auto-use the board's maximum TX power
|
|
- Board support: Heltec V3, Heltec V4, RAK WisBlock 4631
|
|
- Python host library (`donglora` on PyPI) with `pip install` and `donglora-mux` CLI
|
|
- USB multiplexer for sharing one dongle with multiple applications
|
|
- Two-way LoRa bridge over TCP (`examples/lora_bridge.py`)
|
|
|
|
### Technical
|
|
|
|
- Embassy async runtime (no threads, CPU sleeps when idle)
|
|
- Zero-config: boots and waits for host commands
|
|
- Protocol-agnostic: firmware has no opinion about what you transmit
|
|
- Tasks never panic: all errors reported to host or logged
|
|
- Board abstraction: one `.rs` file + one Cargo feature per board
|