v0.2.0: online-mode firmware (WiFi + MQTT + HA Discovery) with fades
Both nightstands now appear in Home Assistant with a button event,
white-noise switch, volume slider (0-100, snapped to the existing
preset list), and an uptime sensor. Topic prefix is the lowercase MAC
hex (`nightstand/<mac_hex>/...`) — no KNOWN_DEVICES table to maintain.
HA users name and pair devices in the UI; `unique_id` stays MAC-stable.
Short press in online mode round-trips through HA: the device publishes
the event and waits for HA to send back `cmd/play ON`/`OFF`, so a single
gesture can do different things depending on time of day, occupancy,
etc. Long-press still cycles volume locally in both modes (with a
publish for HA logging when online); double-press is a pure HA gesture
for the late-night-lights routine. Offline, short-press toggles audio
locally as a fallback so muscle memory still works without HA.
Every amplitude transition is a 700 ms per-sample fade — start, stop,
and volume changes (preset cycle or HA slider). Constant step rate, so
small adjustments finish in proportionally less time and feel snappy
while big ones feel deliberate. Fade lives inside the noise generator
so the pink filter doesn't see any discontinuity; no clicks.
Cross-task channels are backed by FreeRTOS native queues
(`channels.rs`). `std::sync::mpsc` and `crossbeam-channel` are both
broken on esp-idf-rs because of the `PTHREAD_MUTEX_INITIALIZER`
mismatch (newlib's zeros vs. ESP-IDF's `0xffffffff`); the inner
`Mutex<Waker>` they rely on crashes the moment a multi-producer send
needs to wake a blocked receiver. FreeRTOS queues are the native
primitive on this platform, ISR-safe, and bypass the pthread layer
entirely. Wrapper API is intentionally close to mpsc so the call sites
didn't have to learn anything new.
WiFi creds + MQTT URL come from `cfg.toml` (gitignored, with a
`cfg.toml.example` placeholder) via the toml-cfg crate. Empty values
panic at boot; the public repo never sees real secrets.
Top-level + firmware Makefiles; `make firmware-flash` is the headless
flash entry point and `firmware-flash-monitor` is interactive. Both
auto-create the project-local libxml2.so.2 → libxml2.so.16 symlink
that ESP-IDF's bundled esp-clang needs on Ubuntu Questing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>