From 2f8387a1c8347efe128555b05b6d5304d45516fb Mon Sep 17 00:00:00 2001 From: ewowi Date: Sat, 4 Jul 2026 00:34:55 +0200 Subject: [PATCH 1/3] Add WLED audio sync + MHC-WLED P4 shield line-in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds WLED-compatible audio sync over UDP (broadcast/receive on port 11988) as an AudioModule mode, so a projectMM device can send its analysed audio to the WLED ecosystem or drive its own effects from a peer's audio with automatic fallback to the local mic. Also brings up line-in on the MHC-WLED ESP32-P4 shield (PCM1808 ADC) and fixes a boot crash where a sync-enabled board opened a socket before the network stack was up. KPI: 16384lights | PC:465KB | tick:150/116/159/20/2/367/79/23/28/216/155/22/1/47us(FPS:6666/8620/6289/50000/500000/2724/12658/43478/35714/4629/6451/45454/1000000/21276) | ESP32(P4):tick:1870us(FPS:536) | src:167(32018) | test:116(16553) | lizard:113w Core: - AudioModule: added WLED audio sync — a `sync` control (Off / Send / Receive). Send broadcasts the analysed frame as a WLED v2 packet (~40/s, throttled); Receive binds the port and drives effects from a peer's audio, auto-blending back to the local mic ~1s after the peer goes quiet. `syncPort` is configurable and shown in Send + Receive; floor/gain stay visible (they govern the fallback mic). Added line-in support via a configurable `mclkPin` (the PCM1808 needs a master clock). - AudioModule: fixed a boot crash-loop — setup() opened the sync socket before NetworkModule brought an interface up, so the lwip call asserted on a null core mutex and rebooted forever. Socket opening is now deferred to the tick path via syncEnsureSocket(), gated on platform::networkReady(); syncReinit() only closes + records the mode. Mirrors the existing DevicesModule lazy-open pattern. - platform: added networkReady() — true once an interface has an IP (ESP32: eth/sta/ap connected; desktop: always). The runtime "safe to open a socket" predicate, the counterpart to the compile-time hasNetwork. - platform: added hasNetwork = hasWiFi || hasEthernet (both configs) — "needs an IP stack" (audio sync / UDP), distinct from hasWiFi ("needs the radio": SSID / AP / TX-power / RSSI). - NetworkModule: hide txPowerSetting unless the radio is on (Ethernet-only mode has no TX power to set). Light domain: - WLEDAudioSyncPacket.h: new — the netmindz/WLED-sync v2 wire format in one place (44-byte "00002" packet, port 11988), with build/parse helpers. Parse rejects v1 / short / foreign packets so a receiver ignores them rather than crashing. Tests: - unit_WledAudioSyncPacket: golden-vector + round-trip + rejection tests pinning the 44-byte layout, "00002" header, and exact field offsets (the WLED compatibility contract). Docs / CI: - README: credited wladi (myhome-control) for the MHC-WLED ESP32-P4 shield and the PCM1808 line-in pinout / FMT-pin details. - deviceModels.json: MHC-WLED ESP32-P4 shield (renamed from ABC-WLED) — line-in AudioModule child (ws 26 / sd 33 / sck 32 / mclk 36, gain 100), planned RS-485/DMX, supported LEDs/Ethernet/Audio. Board image + pin/DIP diagram. - Plan-20260703 - WLED audio sync.md: saved approved plan. Co-Authored-By: Claude Opus 4.8 --- README.md | 1 + .../boards/abc-wled-esp32-p4-shield.jpg | Bin 77810 -> 0 bytes .../boards/mhc-wled-esp32-p4-shield.jpg | Bin 0 -> 177694 bytes .../plans/Plan-20260703 - WLED audio sync.md | 129 ++++++++++++ src/core/AudioModule.h | 196 +++++++++++++++++- src/core/NetworkModule.h | 3 + src/light/WLEDAudioSyncPacket.h | 84 ++++++++ src/platform/desktop/platform_config.h | 4 + src/platform/desktop/platform_desktop.cpp | 4 + src/platform/esp32/platform_config.h | 6 + src/platform/esp32/platform_esp32.cpp | 8 + src/platform/platform.h | 8 + test/CMakeLists.txt | 1 + test/scenarios/light/scenario_perf_full.json | 40 ++-- test/unit/light/unit_WledAudioSyncPacket.cpp | 115 ++++++++++ web-installer/deviceModels.json | 20 +- 16 files changed, 584 insertions(+), 35 deletions(-) delete mode 100644 docs/assets/boards/abc-wled-esp32-p4-shield.jpg create mode 100644 docs/assets/boards/mhc-wled-esp32-p4-shield.jpg create mode 100644 docs/history/plans/Plan-20260703 - WLED audio sync.md create mode 100644 src/light/WLEDAudioSyncPacket.h create mode 100644 test/unit/light/unit_WledAudioSyncPacket.cpp diff --git a/README.md b/README.md index 50550342..15fca3fe 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ Specific people whose work directly shaped parts of projectMM. We study their th - **Christophe Gagnier ([@Moustachauve](https://github.com/Moustachauve))** — author of the native [WLED-Android](https://github.com/Moustachauve/WLED-Android) app. Its source let us reverse-engineer exactly what the WLED app reads, so projectMM devices appear in (and are controllable from) the native WLED apps. - **The [Improv Wi-Fi](https://github.com/improv-wifi) project** — the open Improv serial provisioning standard ([sdk-cpp](https://github.com/improv-wifi/sdk-cpp) / [sdk-js](https://github.com/improv-wifi/sdk-js)) that the projectMM web installer uses to provision a freshly-flashed device over USB. - **[FastLED](https://github.com/FastLED/FastLED)** — the canonical LED-effects library whose conventions the LED-effect world shares. projectMM links no part of FastLED, but it carries forward FastLED's recognisable *names and models* for the colour/animation primitives — `scale8`, `sin8`, the gradient-palette model (`CRGBPalette16` / `colorFromPalette`), the `beatsin8` / `inoise8` / `qadd8` family — so a contributor recognises them on sight. The implementations are projectMM's own, integer-only and hot-path-tuned for our render loop; FastLED is the prior art behind the convention, credited here and in each primitive's notes. +- **wladi ([myhome-control](https://shop.myhome-control.de))** — designer of the [MHC-WLED ESP32-P4 shield](https://shop.myhome-control.de/en/ABC-WLED-ESP32-P4-shield/HW10027), and the source of the hardware and the pinout details that got its **line-in audio** working in [AudioModule](docs/moonmodules/core/AudioModule.md): the onboard PCM1808 I2S ADC (WS 26 / SD 33 / SCK 32 / MCLK 36), the PCM1808's stereo wiring, and its `FMT` format-select jumper (open = I2S/Philips, our default; tie to 3V3 for left-justified) — which is what confirmed the standard-I2S path the ADC needs. ## Contributing diff --git a/docs/assets/boards/abc-wled-esp32-p4-shield.jpg b/docs/assets/boards/abc-wled-esp32-p4-shield.jpg deleted file mode 100644 index fffe963716b2b53d266cf49edc331aa73e2d30be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77810 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

v%rIT*V)$Fq|0eJCPXR3n-{rP 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

(tP~Z%hX^jy`3$obH^hWIDxk`t!Yctd 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 Pca@|6B+rE7@67)LhJ^4; literal 0 HcmV?d00001 diff --git a/docs/history/plans/Plan-20260703 - WLED audio sync.md b/docs/history/plans/Plan-20260703 - WLED audio sync.md new file mode 100644 index 00000000..c5559999 --- /dev/null +++ b/docs/history/plans/Plan-20260703 - WLED audio sync.md @@ -0,0 +1,129 @@ +# Plan — WLED-compatible audio sync (send + receive) in AudioModule + +## Context + +projectMM's AudioModule analyses local audio (line-in / mic) into an `AudioFrame` (16 GEQ +bands + level + peak). The product owner wants **WLED audio-sync over UDP** so projectMM can +interoperate with the WLED ecosystem: a projectMM device can **broadcast** its analysed audio +for WLED/MoonLight receivers, and can **receive** a peer's audio to drive its own effects when +it has no local source. MoonLight already *receives* this (`D_WLEDAudio.h`) but nothing in the +family *sends* it — this closes that loop. The wire format is a fixed compatibility contract +(netmindz/WLED-sync), so the packet must be byte-exact. + +The design (product-owner-confirmed): a single **`sync` control: Off / Send / Receive**. +- **Off** — local audio only (today's behaviour). No socket bound → zero overhead. +- **Send** — broadcast the local `AudioFrame` as a WLED v2 packet on UDP 11988. +- **Receive** — bind 11988, and when packets arrive, write the peer's audio *into* `frame_` + (so effects react to it transparently); **auto-blend**: fall back to the local mic/simulate + when no packet for ~1 s. Socket bound only in Receive mode. + +## The wire format (authoritative — netmindz/WLED-sync, header "00002") + +`__attribute__((packed))`, **44 bytes**, UDP port **11988**, broadcast: + +| offset | field | type | projectMM source (AudioFrame) | +|---|---|---|---| +| 0 | `header[6]` | char | `"00002"` (+ NUL) | +| 6 | `gap1[2]` | u8×2 | zero (part of the wire layout) | +| 8 | `sampleRaw` | f32 | `level` | +| 12 | `sampleSmth` | f32 | `levelSmoothed` | +| 16 | `samplePeak` | u8 | derived: 1 when a beat/peak this frame, else 0 | +| 17 | `frameCounter` | u8 | incrementing send counter | +| 18 | `fftResult[16]` | u8×16 | `bands[16]` — direct 1:1 | +| 34 | `gap2[2]` | u8×2 | zero | +| 36 | `FFT_Magnitude` | f32 | `peakMag` | +| 40 | `FFT_MajorPeak` | f32 | `peakHz` | + +The mapping is near-1:1 — `AudioFrame`'s comments already cite the WLED field names +(`volumeRaw`/`volumeSmth`), the struct was built with this in mind. (There's also an 83-byte +v1 packet; we **send v2 only**, and **parse v2 only** — v1 is legacy, out of scope unless a +received v1 shows up, in which case ignore it, don't crash.) + +## Files + +1. **New: `src/light/WLEDAudioSyncPacket.h`** — the format in one place (the `ArtNetPacket.h` / + `DdpPacket.h` convention: constants + inline `build` + inline `parse`, round-trip unit-tested). + - `constexpr uint16_t WLED_SYNC_PORT = 11988;` + - `constexpr char WLED_SYNC_HEADER[6] = "00002";` + - The 44-byte `#pragma pack`ed struct (or a hand-serialised builder writing exact offsets — + hand-serialise is safer than relying on struct packing across compilers, matching how + ArtNet/DDP builders write bytes explicitly; **decide at impl time**, but the *test* pins 44 + bytes + the offsets regardless). + - `size_t buildWledAudioSync(uint8_t out[44], const AudioFrame&, uint8_t frameCounter, bool peak)` + - `bool parseWledAudioSync(const uint8_t* buf, size_t len, AudioFrame& out)` — validates length + (44) + header ("00002"); fills an AudioFrame from the packet (inverse of build); returns false + on a v1/short/foreign packet so the caller ignores it. + +2. **`src/core/AudioModule.h`** — the send/receive plumbing (all guarded `if constexpr (platform::hasWiFi)` + so a `MM_NO_WIFI` build compiles the paths out): + - **Members:** `uint8_t sync = 0;` (0=Off/1=Send/2=Receive), `platform::UdpSocket syncSock_;` + `uint32_t lastSyncSend_ = 0;`, `uint32_t lastSyncRecv_ = 0;` (millis of last received packet, + for the auto-blend fallback), `uint8_t syncFrameCounter_ = 0;`, a `uint8_t syncPkt_[64]` scratch. + - **Control:** in `onBuildControls()`, `controls_.addSelect("sync", sync, {"Off","Send","Receive"}, 3)` + — placed after `simulate`. A read-only `"sync status"` line (e.g. "receiving from 1.2.3.4" / + "sending 33/s" / off) via the existing `addReadOnly` + loop1s idiom. + - **Mode transitions:** `sync` changes must re-bind/unbind the socket, so add it to + `controlChangeTriggersBuildState()` → `onBuildState()` → a small `syncReinit()`: close the + socket; if Send → `open()` + `connect("255.255.255.255", 11988)`; if Receive → `open()` + + `bind(11988)`; if Off → leave closed. (Mirrors NetworkSendDriver's `connectIfDestChanged` + + NetworkReceiveEffect's bind.) + - **Send** (in `loop()`, after `frame_` is refreshed): throttle by an interval (WLED sends + ~real-time; cap ~30–40/s to match, a `syncSendIntervalMs` const — reuse the + `now - lastSyncSend_ < interval` pattern from NetworkSendDriver:106-114). Build the packet + from `frame_` + `syncFrameCounter_++`, `syncSock_.sendTo(...)`. + - **Receive** (in `loop()`): bounded non-blocking drain (mirror NetworkReceiveEffect:103-143, + e.g. ≤8 packets/tick — sync is low-rate). For each, `parseWledAudioSync` into a temp frame; + on success, copy it into `frame_` and stamp `lastSyncRecv_ = millis()`. **Auto-blend:** the + existing local-analysis block in `loop()` should only overwrite `frame_` when NOT in Receive + mode, OR when in Receive mode but `millis() - lastSyncRecv_ > kSyncFallbackMs` (~1000 ms) — + i.e. received audio wins while fresh, local mic resumes when the peer goes quiet. + - **`samplePeak` derivation** for the send: a simple beat flag — set when `level` exceeds a + short-running average by a margin (or reuse whatever peak signal the FFT block already has at + AudioModule.h:264-266). Keep it cheap; it's a hint field. + +3. **New: `test/*` round-trip test** — `test/` C++ unit (ctest) for `WLEDAudioSyncPacket.h`: + build→parse round-trips an AudioFrame; pins the **44-byte size**, the **"00002" header**, the + **exact field offsets** (a golden byte vector — the compatibility contract, same rigor as the + Improv frame golden vector), and that `parse` rejects a wrong-length / wrong-header / v1 packet. + (Follows the ArtNet/DDP packet-test precedent + the Improv golden-vector precedent.) + +4. **`docs/moonmodules/core/AudioModule.md`** (+ the `///` header docs check_specs validates) — + document the `sync` control (Off/Send/Receive, the auto-blend behaviour, port 11988, WLED v2 + compatibility). Keep control-name ↔ doc in sync (check_specs gate). + +## Not doing (scope guards) + +- **No platform change** — `UdpSocket` already has open/connect/sendTo/bind/recvFrom + SO_BROADCAST. +- **No v1 (83-byte) send or parse** — v2 only; a received v1/foreign packet is ignored, not crashed. +- **No new module** — this lives in AudioModule (it owns the AudioFrame, both ends need it). +- **No separate send+receive toggles** — one tri-state `sync` (PO-confirmed), simplest coherent UX. +- **Not a `src/light/` driver/effect** — audio sync is an AudioModule capability, not a light node; + the packet header goes in `src/light/` only because that's the established home for wire formats. + +## Verification + +- **Build:** desktop (`cmake --build build`, -Werror) + `esp32p4-eth` (the MHC shield) + a mic-less + variant (e.g. `esp32-eth`) all clean — the `hasWiFi` guard keeps it compiling everywhere. +- **Unit:** `ctest` — the new round-trip/golden-vector test passes; existing tests unaffected. + `check_specs` green (control ↔ doc). +- **Bench — SEND (the headline interop):** on the MHC-WLED P4 shield (line-in working, at + `192.168.1.139`), set `sync=Send`. Capture the UDP on the Mac (`nc -ul 11988` / a tiny Python + `recvfrom` on 11988) and assert: 44-byte packets, header "00002", `fftResult` matches the live + bands, arriving continuously. **Cross-check with MoonLight** if a MoonLight receiver is available: + its `D_WLEDAudio` should light up from the projectMM broadcast — the real compatibility proof. +- **Bench — RECEIVE + auto-blend:** point a second device (or a Python sender emitting the golden + v2 packet) at the shield with `sync=Receive`; confirm the shield's effects react to the injected + audio (its `level`/`peakHz` readouts track the sent values), then **stop** the sender and confirm + it falls back to the local line-in within ~1 s. +- Save the approved plan to `docs/history/plans/Plan-20260703 - WLED audio sync.md`. + +## Open items (settle during impl, not blockers) + +- **Send rate** — WLED transmits ~per-frame; pick a cap (30–40/s) that's WLED-friendly without + flooding. A `sync fps` control could be added but Off/Send/Receive + a sensible fixed rate is + leaner; add the control only if the PO wants tunability. +- **`samplePeak`** — exact beat-flag source (reuse the FFT peak block vs a small level-vs-average + check). It's a hint field; a simple, cheap derivation is fine. +- **Struct packing vs hand-serialise** for the 44-byte layout — hand-serialised byte writes are + the safer, portable choice (no cross-compiler packing surprises); the golden-vector test pins it + either way. diff --git a/src/core/AudioModule.h b/src/core/AudioModule.h index 54d1eb8b..31d736ec 100644 --- a/src/core/AudioModule.h +++ b/src/core/AudioModule.h @@ -71,6 +71,7 @@ #include "core/AudioLevel.h" #include "core/AudioBands.h" #include "core/math8.h" // beatsin8 / sin8 — the simulated-audio oscillators +#include "light/WLEDAudioSyncPacket.h" // WLED audio-sync wire format (send/receive) #include "platform/platform.h" #include @@ -106,6 +107,11 @@ class AudioModule : public MoonModule { int8_t wsPin = -1; ///< word-select / LRCLK (-1 = unset). Changing it re-creates the I2S channel live (no reboot). int8_t sdPin = -1; ///< serial data in (-1 = unset). Changing it re-creates the I2S channel live. int8_t sckPin = -1; ///< bit clock (-1 = unset). Changing it re-creates the I2S channel live. + int8_t mclkPin = -1; ///< master clock out (-1 = none). Self-clocked MEMS mics (INMP441) leave this + ///< -1; an ADC/codec that needs a master clock (e.g. the MHC-WLED P4 shield's + ///< line-in ADC on GPIO3) sets it so the I2S peripheral drives MCLK. On a codec + ///< board (CodecType != None) the codec's own mclk is used instead (see reinit()). + ///< Changing it re-creates the I2S channel live. /// Sample rate is a discrete choice (the standard audio rates), so it's a /// dropdown over a fixed set, not a free number. sampleRateSel indexes /// kSampleRates; sampleRate() resolves it to Hz. Default index 2 = 22050 @@ -128,6 +134,17 @@ class AudioModule : public MoonModule { /// A real mic always wins: when `mode` is a fill-in ("silence") mode it only runs while there's no real signal. uint8_t simulate = 0; ///< 0 = off, 1 = music (fill silence), 2 = sweep (fill silence), ///< 3 = music (always), 4 = sweep (always) + /// WLED audio-sync over UDP (port 11988, broadcast, WLED v2 wire format — + /// light/WLEDAudioSyncPacket.h). 0 = Off (local audio only, no socket bound); + /// 1 = Send (broadcast this device's AudioFrame for WLED/MoonLight receivers); + /// 2 = Receive (bind 11988; a peer's audio overwrites frame_ so effects react to + /// it, auto-falling-back to the local mic when packets stop for ~1 s). The socket + /// is bound only in Send/Receive — Off costs nothing. Changing it re-binds live. + uint8_t sync = 0; ///< 0 = off, 1 = send, 2 = receive + /// The sync UDP port — the Send destination and the Receive listen port. Defaults + /// to WLED's 11988 (interop with WLED/MoonLight); set it the same on both ends to + /// run a private projectMM-only sync group on a non-WLED port. + uint16_t syncPort = WLED_SYNC_PORT; static constexpr uint16_t kSampleRates[] = {8000, 16000, 22050, 44100}; static constexpr uint8_t kSampleRateCount = 4; @@ -138,13 +155,34 @@ class AudioModule : public MoonModule { controls_.addPin("wsPin", wsPin); controls_.addPin("sdPin", sdPin); controls_.addPin("sckPin", sckPin); + controls_.addPin("mclkPin", mclkPin); static constexpr const char* kRateOptions[] = {"8000", "16000", "22050", "44100"}; controls_.addSelect("sampleRate", sampleRateSel, kRateOptions, kSampleRateCount); + // floor/gain condition the LOCAL FFT/level mapping. They stay visible in every sync + // mode — including Receive, because auto-blend falls back to the local mic when the + // peer goes quiet, and floor/gain govern that fallback. (They're only transiently + // dead while a peer's audio is actively driving the frame — not permanently, so + // hiding them by the sync setting would wrongly hide a control the fallback needs.) controls_.addUint8("floor", floor, 0, 255); controls_.addUint8("gain", gain, 1, 255); static constexpr const char* kSimulateOptions[] = { "off", "music (silence)", "sweep (silence)", "music (always)", "sweep (always)"}; controls_.addSelect("simulate", simulate, kSimulateOptions, 5); + // WLED audio sync — only on builds with an IP stack (WiFi OR Ethernet: the UDP + // send/receive works over either, so an Ethernet-only board like the MHC-WLED + // shield still gets it). A no-network build hides the controls entirely. + if constexpr (platform::hasNetwork) { + static constexpr const char* kSyncOptions[] = {"off", "send", "receive"}; + controls_.addSelect("sync", sync, kSyncOptions, 3); + // The UDP port — the Send destination and the Receive listen port. Defaults to + // WLED's 11988 (interop with WLED/MoonLight); change it on BOTH ends to run a + // private projectMM-only sync group on a non-WLED port. Shown whenever sync is on + // (Send or Receive), hidden in Off. A `sync` change re-runs this method (it's in + // controlChangeTriggersBuildState) so the row toggles live. + controls_.addUint16("syncPort", syncPort, 1, 65535); + controls_.setHidden(controls_.count() - 1, sync == 0); + controls_.addReadOnly("sync status", syncStr_, sizeof(syncStr_)); + } // Read-only live read-outs (formatted in loop1s). Derived every second, // nothing to persist, so ReadOnly (the display-only type) not a flipped // Text — same idiom as SystemModule's uptime/fps. @@ -158,19 +196,25 @@ class AudioModule : public MoonModule { MoonModule::onBuildControls(); } - /// A pin or rate change rebuilds the I2S channel (live, no reboot). + /// A pin or rate change rebuilds the I2S channel (live, no reboot); a `sync` / + /// `syncPort` change re-binds/unbinds the UDP socket AND re-toggles the port row's + /// visibility (all flow through onBuildState → rebuildControls). bool controlChangeTriggersBuildState(const char* name) const override { return std::strcmp(name, "wsPin") == 0 || std::strcmp(name, "sdPin") == 0 - || std::strcmp(name, "sckPin") == 0 || std::strcmp(name, "sampleRate") == 0; + || std::strcmp(name, "sckPin") == 0 || std::strcmp(name, "mclkPin") == 0 + || std::strcmp(name, "sampleRate") == 0 || std::strcmp(name, "sync") == 0 + || std::strcmp(name, "syncPort") == 0; } - void onBuildState() override { reinit(); MoonModule::onBuildState(); } + void onBuildState() override { reinit(); syncReinit(); MoonModule::onBuildState(); } void setup() override { if (active_ == nullptr) active_ = this; // first live mic wins; a 2nd mic is captured but not read reinit(); + syncReinit(); } void teardown() override { deinit(); + if constexpr (platform::hasNetwork) { syncSock_.close(); syncOpen_ = false; } if (active_ == this) active_ = nullptr; // vacate; a surviving module re-elects itself in loop() } @@ -204,6 +248,17 @@ class AudioModule : public MoonModule { // if (active_ == nullptr) active_ = this; + // WLED audio sync. Send broadcasts the current frame_ (throttled). Receive drains + // the socket into frame_ and, while a peer's audio is fresh, RETURNS so the local + // mic analysis below is skipped (the received frame drives the effects); once the + // peer goes quiet (~1 s) it falls through and the local mic resumes (auto-blend). + if constexpr (platform::hasNetwork) { + if (sync != 0 && syncEnsureSocket()) { // lazy-open once the network is up + if (sync == 1) syncSend(); + else if (sync == 2 && syncReceive()) return; + } + } + // Simulated audio (see the `simulate` control): 0=off, 1=music-on-silence, 2=sweep-on-silence, // 3=music-always, 4=sweep-always. Real mic input always wins in the "on-silence" modes — the // mic path below resets realQuietMs_ whenever a block carries signal, and synthesizeFrame() @@ -323,6 +378,20 @@ class AudioModule : public MoonModule { std::snprintf(levelStr_, sizeof(levelStr_), "%u", static_cast(levelPeak_)); std::snprintf(peakStr_, sizeof(peakStr_), "%u Hz", static_cast(frame_.peakHz)); levelPeak_ = 0; // reset for the next window + // Live sync status: "sending" / "receiving" (peer audio fresh) / "listening" + // (bound, no peer) / "off". While the socket isn't open yet, leave the baseline + // syncReinit/syncEnsureSocket set ("waiting for network" / "…failed"); only once + // open do we report the moment-to-moment send/receive state. + if constexpr (platform::hasNetwork) { + if (sync == 0) std::snprintf(syncStr_, sizeof(syncStr_), "off"); + else if (syncOpen_) { + if (sync == 1) std::snprintf(syncStr_, sizeof(syncStr_), "sending"); + else std::snprintf(syncStr_, sizeof(syncStr_), + (lastSyncRecv_ != 0 + && platform::millis() - lastSyncRecv_ < kSyncFallbackMs) + ? "receiving" : "listening"); + } + } MoonModule::loop1s(); } @@ -354,6 +423,17 @@ class AudioModule : public MoonModule { static constexpr uint16_t kSimRealGraceBlocks = 86; // ~2 s at ~23 ms/block before the sim takes over uint16_t realBlocks_ = 0; // grace countdown: >0 = mic was recently live, hold off the sim + // WLED audio sync (light/WLEDAudioSyncPacket.h). One socket, bound only in Send/Receive. + platform::UdpSocket syncSock_; + uint32_t lastSyncSend_ = 0; // millis of the last broadcast (send throttle) + uint32_t lastSyncRecv_ = 0; // millis of the last received packet (receive auto-blend) + uint8_t syncFrameCounter_ = 0; // increments per send (the packet's dup/reorder field) + bool syncOpen_ = false; // socket opened for the current mode (lazy-open latch) + char syncStr_[32] = {}; // "sync status" read-out + static constexpr uint32_t kSyncSendIntervalMs = 25; // ~40/s — WLED-friendly, well under a flood + static constexpr uint32_t kSyncFallbackMs = 1000; // no packet this long → resume local mic + static constexpr int kSyncMaxRecvPerTick = 8; // bounded non-blocking drain (sync is low-rate) + static constexpr const char* kInitFailMsg = "mic init failed — check pins / rate"; /// (Re)create the I2S channel for the current pins + rate. On a codec board the @@ -374,16 +454,19 @@ class AudioModule : public MoonModule { setStatus("mic: set wsPin / sdPin / sckPin", Severity::Status); return; } - // Bring up the I2S channel FIRST. On a codec board (an analog mic behind an - // I2S codec, e.g. the S31's ES8311) the I2S peripheral drives MCLK, and the - // codec won't even answer I2C until that clock runs — so I2S precedes the - // codec config. The MCLK pin comes from the per-target codec config - // (platform::audioCodecPins.mclk); −1 on a direct MEMS mic (self-clocked). - const int16_t mclkPin = platform::audioCodecType == platform::CodecType::None - ? -1 : static_cast(platform::audioCodecPins.mclk); + // Bring up the I2S channel FIRST. Where MCLK comes from depends on the board: + // - Codec board (an analog mic behind an I2S codec, e.g. the S31's ES8311): the + // codec's MCLK pin from the per-target config (platform::audioCodecPins.mclk). + // The codec won't even answer I2C until that clock runs, so I2S precedes the + // codec config below. + // - No codec: the runtime `mclkPin` control — −1 for a self-clocked MEMS mic + // (INMP441), or a real pin for an I2S ADC that needs a master clock (the + // MHC-WLED P4 shield's line-in ADC on GPIO3, WLED's SR_DMTYPE=4). + const int16_t mclk = platform::audioCodecType == platform::CodecType::None + ? mclkPin : static_cast(platform::audioCodecPins.mclk); inited_ = platform::audioMicInit(mic_, static_cast(wsPin), static_cast(sdPin), - static_cast(sckPin), mclkPin, sampleRate()); + static_cast(sckPin), mclk, sampleRate()); if (!inited_) { setStatus(kInitFailMsg, Severity::Error); return; @@ -422,6 +505,97 @@ class AudioModule : public MoonModule { // would leave the last real frame frozen on the LEDs instead of going dark. frame_ = AudioFrame{}; } + + // --- WLED audio sync (guarded: only compiled where platform::hasNetwork) --- + + /// Reset the sync socket to the current mode. Called from setup()/onBuildState() + /// so a `sync` control change applies live (no reboot). This only CLOSES the socket + /// and records the mode — it never opens one, because setup() runs at boot before + /// NetworkModule brings an interface up, and any lwip socket call before then asserts + /// (the core mutex is still null). The actual open() is deferred to syncEnsureSocket(), + /// which runs from the tick path once platform::networkReady() is true. + void syncReinit() { + if constexpr (!platform::hasNetwork) return; + syncSock_.close(); // syncEnsureSocket() re-opens per mode when the net is up + syncOpen_ = false; + lastSyncRecv_ = 0; + std::snprintf(syncStr_, sizeof(syncStr_), + sync == 1 ? "send: waiting for network" + : sync == 2 ? "receive: waiting for network" + : "off"); + } + + /// Lazily open the sync socket for the current mode, once the network stack is up. + /// Idempotent: opens exactly once per mode (syncOpen_ latch), re-armed by syncReinit() + /// on a mode change. Returns true when the socket is ready to use this tick. Off is a + /// no-op (socket stays closed, zero overhead). Send connects to the LAN broadcast; + /// Receive binds the port. Mirrors NetworkSendDriver/NetworkReceiveEffect, but deferred + /// past boot so a boot-present AudioModule can't touch lwip before it exists. + bool syncEnsureSocket() { + if constexpr (!platform::hasNetwork) return false; + if (sync == 0) return false; + if (syncOpen_) return true; + if (!platform::networkReady()) return false; // interface not up yet — try again next tick + if (sync == 1) { // send → broadcast destination (configurable port) + char bcast[16]; formatDottedQuad(bcast, kBroadcast_); + if (syncSock_.open() && syncSock_.connect(bcast, syncPort)) { + syncOpen_ = true; + std::snprintf(syncStr_, sizeof(syncStr_), "sending"); + } else { + syncSock_.close(); + std::snprintf(syncStr_, sizeof(syncStr_), "send: socket failed"); + } + } else { // receive → bind the port (WLED 11988, or a custom group) + if (syncSock_.open() && syncSock_.bind(syncPort)) { + syncOpen_ = true; + std::snprintf(syncStr_, sizeof(syncStr_), "listening"); + } else { + syncSock_.close(); + std::snprintf(syncStr_, sizeof(syncStr_), "receive: bind failed"); + } + } + return syncOpen_; + } + + /// Broadcast the current frame_ as a WLED v2 packet, throttled to ~40/s. Called + /// from loop() in Send mode. Cheap: builds a 44-byte packet, one non-blocking sendTo. + void syncSend() { + if constexpr (!platform::hasNetwork) return; + const uint32_t now = platform::millis(); + if (now - lastSyncSend_ < kSyncSendIntervalMs) return; + lastSyncSend_ = now; + // samplePeak hint: a beat is a raw level notably above the smoothed average. + const bool peak = frame_.level > frame_.levelSmoothed + kSyncPeakMargin; + uint8_t pkt[WLED_SYNC_PACKET_SIZE]; + buildWledAudioSync(pkt, frame_, syncFrameCounter_++, peak); + syncSock_.sendTo(pkt, WLED_SYNC_PACKET_SIZE); + } + + /// Drain the sync socket (bounded, non-blocking) in Receive mode. A valid v2 packet + /// overwrites frame_ and stamps lastSyncRecv_. Returns true while a peer's audio is + /// FRESH (within kSyncFallbackMs) so loop() skips the local mic analysis — false once + /// the peer goes quiet, letting the local mic resume (auto-blend). + bool syncReceive() { + if constexpr (!platform::hasNetwork) return false; + uint8_t pkt[WLED_SYNC_PACKET_SIZE + 8]; // a little slack over the 44-byte v2 + uint8_t srcIp[4] = {}; + for (int i = 0; i < kSyncMaxRecvPerTick; i++) { + const int n = syncSock_.recvFrom(pkt, sizeof(pkt), srcIp); + if (n <= 0) break; // -1 = nothing pending + AudioFrame rf; + if (parseWledAudioSync(pkt, static_cast(n), rf)) { + frame_ = rf; // received audio drives the effects + lastSyncRecv_ = platform::millis(); + } + // else: a v1 / foreign packet — ignore, keep draining. + } + // Fresh received audio → skip local mic. Stale (peer quiet) → fall through. + return lastSyncRecv_ != 0 + && (platform::millis() - lastSyncRecv_) < kSyncFallbackMs; + } + + static constexpr uint16_t kSyncPeakMargin = 8; // level over smoothed = a beat (samplePeak hint) + static constexpr uint8_t kBroadcast_[4] = {255, 255, 255, 255}; // LAN broadcast for send }; } // namespace mm diff --git a/src/core/NetworkModule.h b/src/core/NetworkModule.h index 1af4372d..c63d9022 100644 --- a/src/core/NetworkModule.h +++ b/src/core/NetworkModule.h @@ -263,7 +263,10 @@ class NetworkModule : public MoonModule { // (ESP-IDF's minimum) — write 2 or higher for predictable // behavior. Always bound on radio-capable builds; the // deviceModels.json catalog injects 8 dBm for brown-out-prone boards. + // Hidden with the same radioOn gate as the txPower readout above — a WiFi + // TX-power cap is meaningless on Ethernet / Idle where the radio is off. controls_.addInt16("txPowerSetting", txPowerSetting_, 0, 21); + controls_.setHidden(controls_.count() - 1, !radioOn); } controls_.addBool("mDNS", mdnsEnabled_); diff --git a/src/light/WLEDAudioSyncPacket.h b/src/light/WLEDAudioSyncPacket.h new file mode 100644 index 00000000..64b3bb63 --- /dev/null +++ b/src/light/WLEDAudioSyncPacket.h @@ -0,0 +1,84 @@ +#pragma once + +#include "core/AudioFrame.h" + +#include +#include + +namespace mm { + +// WLED audio-sync wire format — the one place the packet layout lives (the +// ArtNetPacket.h / DdpPacket.h convention). AudioModule builds it to broadcast +// its analysed audio, and parses it to drive effects from a peer's audio; a unit +// test round-trips build↔parse against a golden byte vector so we can never drift +// from WLED. The contract is fixed by netmindz/WLED-sync (the header MoonModules' +// MoonLight receives with, D_WLEDAudio.h), so the bytes must be exact. +// +// v2 packet — 44 bytes, UDP port 11988, broadcast. Matches the "packed" struct in +// WLED-sync.h; we hand-serialise the exact offsets (like ArtNet/DDP) rather than +// rely on cross-compiler struct packing. The two "gap" runs are real wire bytes +// (WLED's struct has explicit gap1/gap2 padding), so they're part of the 44 and +// sent as zero. Floats are little-endian IEEE-754 (WLED memcpy's the struct on a +// little-endian MCU; every projectMM target is little-endian too, so a raw memcpy +// reproduces the exact bytes). +// +// 0-5 header "00002" (+ NUL) +// 6-7 gap1 (zero) +// 8-11 sampleRaw float — AudioFrame.level (WLED volumeRaw) +// 12-15 sampleSmth float — AudioFrame.levelSmoothed (WLED volume/volumeSmth) +// 16 samplePeak u8 — 1 = beat/peak this frame, else 0 +// 17 frameCounter u8 — increments per send, for dup/reorder detection +// 18-33 fftResult[16] u8×16 — AudioFrame.bands[16] (the 16 GEQ channels) +// 34-35 gap2 (zero) +// 36-39 FFT_Magnitude float — AudioFrame.peakMag +// 40-43 FFT_MajorPeak float — AudioFrame.peakHz + +constexpr uint16_t WLED_SYNC_PORT = 11988; +constexpr size_t WLED_SYNC_PACKET_SIZE = 44; +// 6 chars incl NUL: "00002". v1 packets use "00001" (83 bytes) — legacy, ignored. +constexpr char WLED_SYNC_HEADER[6] = "00002"; +constexpr size_t WLED_SYNC_NUM_BANDS = 16; // == AudioFrame bands + WLED NUM_GEQ_CHANNELS + +// Little-endian IEEE-754 store/load. Every projectMM target (Xtensa/RISC-V ESP32, +// desktop x86/arm64) is little-endian, and so is WLED's source MCU — so the raw +// float bytes are the wire bytes. Kept as a helper so the intent is explicit and +// the round-trip test pins it. +inline void wledPutFloatLE(uint8_t* p, float v) { std::memcpy(p, &v, 4); } +inline float wledGetFloatLE(const uint8_t* p) { float v; std::memcpy(&v, p, 4); return v; } + +// Build a v2 audio-sync packet from an AudioFrame into out (>= 44 bytes). +// `frameCounter` and `peak` are the two fields not carried by AudioFrame (the +// caller owns the send counter and the beat flag). Returns the packet size (44). +inline size_t buildWledAudioSync(uint8_t out[WLED_SYNC_PACKET_SIZE], const AudioFrame& f, + uint8_t frameCounter, bool peak) { + std::memset(out, 0, WLED_SYNC_PACKET_SIZE); // zeroes header pad + both gaps + std::memcpy(out, WLED_SYNC_HEADER, 6); // "00002\0" + wledPutFloatLE(out + 8, static_cast(f.level)); // sampleRaw + wledPutFloatLE(out + 12, static_cast(f.levelSmoothed)); // sampleSmth + out[16] = peak ? 1 : 0; // samplePeak + out[17] = frameCounter; // frameCounter + std::memcpy(out + 18, f.bands, WLED_SYNC_NUM_BANDS); // fftResult[16] + wledPutFloatLE(out + 36, static_cast(f.peakMag)); // FFT_Magnitude + wledPutFloatLE(out + 40, static_cast(f.peakHz)); // FFT_MajorPeak + return WLED_SYNC_PACKET_SIZE; +} + +// Parse + validate a v2 audio-sync packet into an AudioFrame (the inverse of +// build). Returns true only for a well-formed v2 datagram: exactly 44 bytes and +// the "00002" header. A v1 (83-byte "00001") packet, a short/foreign datagram, or +// a null buffer returns false so the caller ignores it (be strict on our own +// format, drop everything else — never crash). The float level/peak fields are +// rounded back into the AudioFrame's small-integer fields. +inline bool parseWledAudioSync(const uint8_t* pkt, size_t len, AudioFrame& out) { + if (!pkt || len != WLED_SYNC_PACKET_SIZE) return false; + if (std::memcmp(pkt, WLED_SYNC_HEADER, 6) != 0) return false; + out.level = static_cast(wledGetFloatLE(pkt + 8)); + out.levelSmoothed = static_cast(wledGetFloatLE(pkt + 12)); + // pkt[16] samplePeak and pkt[17] frameCounter are hints the AudioFrame doesn't carry. + std::memcpy(out.bands, pkt + 18, WLED_SYNC_NUM_BANDS); + out.peakMag = static_cast(wledGetFloatLE(pkt + 36)); + out.peakHz = static_cast(wledGetFloatLE(pkt + 40)); + return true; +} + +} // namespace mm diff --git a/src/platform/desktop/platform_config.h b/src/platform/desktop/platform_config.h index f1327927..9484937a 100644 --- a/src/platform/desktop/platform_config.h +++ b/src/platform/desktop/platform_config.h @@ -53,6 +53,10 @@ struct EthPinConfig { // (shared code) `if constexpr (hasEthernet)`s the eth controls off, and seeds its // members from this default; both must exist here for that shared code to compile. constexpr bool hasEthernet = false; +// Some-IP-stack flag (WiFi OR Ethernet) — mirrors the esp32 config so shared code +// (WLED audio sync, UDP interop) gates on "has network" uniformly. True on desktop +// via the WiFi stubs (UdpSocket has a desktop implementation). +constexpr bool hasNetwork = hasWiFi || hasEthernet; // No SPI-Ethernet (W5500) driver on desktop either — NetworkModule's live-reconfigure // path gates on this, so it must exist on every platform (mirrors the esp32 flag). constexpr bool hasEthW5500 = false; diff --git a/src/platform/desktop/platform_desktop.cpp b/src/platform/desktop/platform_desktop.cpp index f3e096af..6f58fb5f 100644 --- a/src/platform/desktop/platform_desktop.cpp +++ b/src/platform/desktop/platform_desktop.cpp @@ -448,6 +448,10 @@ int wifiStaRssi() { return 0; } bool wifiApInit(const char* /*apName*/, const char* /*ip*/) { return false; } bool wifiApConnected() { return false; } void wifiApStop() {} + +// Host sockets work regardless of the (stubbed) link predicates above, and there is +// no lwip-style init race — always socket-safe. +bool networkReady() { return true; } int wifiTxPower() { return 0; } // Match the API contract: 0 is a successful no-op (matches ESP-IDF // MM_NO_WIFI stub semantics). Any non-zero value returns false since diff --git a/src/platform/esp32/platform_config.h b/src/platform/esp32/platform_config.h index 7bff2eb9..7405df5a 100644 --- a/src/platform/esp32/platform_config.h +++ b/src/platform/esp32/platform_config.h @@ -164,6 +164,12 @@ constexpr bool hasEthernet = false; constexpr bool hasEthernet = true; #endif +// True when the firmware carries an IP stack at all — WiFi OR Ethernet. UdpSocket +// (lwIP BSD sockets) is present whenever either is, so features that only need +// "some network" (WLED audio sync, any UDP interop) gate on this rather than +// hasWiFi — an Ethernet-only board (the MHC-WLED P4 shield) still has UDP. +constexpr bool hasNetwork = hasWiFi || hasEthernet; + // Which Ethernet PHY *drivers* this firmware actually carries. The W5500 SPI // driver is compiled in only on chips with no internal EMAC and the SPI-eth // fragment (the S3 — CONFIG_ETH_USE_SPI_ETHERNET set, CONFIG_ETH_USE_ESP32_EMAC diff --git a/src/platform/esp32/platform_esp32.cpp b/src/platform/esp32/platform_esp32.cpp index 807a7786..fee7d1e4 100644 --- a/src/platform/esp32/platform_esp32.cpp +++ b/src/platform/esp32/platform_esp32.cpp @@ -1020,6 +1020,14 @@ bool wifiSetTxPower(int8_t quarterDbm) { return quarterDbm == 0; } #endif // MM_NO_WIFI +// Socket-safe once any interface has an IP: at that point esp_netif_init() has run +// and the lwip core mutex exists, so opening a socket won't assert. Each predicate +// is stubbed to false in the build that lacks its interface, so this OR compiles and +// answers correctly on every firmware. +bool networkReady() { + return ethConnected() || wifiStaConnected() || wifiApConnected(); +} + // Bring the mDNS stack up (idempotent) and ADVERTISE this device as .local. // Advertising is gated by the user's mDNS toggle; the stack init stays — mdnsStop() // removes the services + hostname but keeps the stack up, so toggling mDNS back on diff --git a/src/platform/platform.h b/src/platform/platform.h index 9b3dc795..a9a978f3 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -145,6 +145,14 @@ bool wifiApInit(const char* apName, const char* ip); bool wifiApConnected(); void wifiApStop(); +// True when it is safe to open/use a socket: the TCP/IP stack is initialised and +// an interface has an IP. On ESP32 that means Ethernet or WiFi (STA/AP) is up — +// calling any lwip socket API before then asserts (the core mutex is still null). +// Desktop: always true (host sockets work regardless of link state). Callers that +// open sockets at boot (before NetworkModule brings an interface up) must gate on +// this and open lazily from the tick path once it turns true. +bool networkReady(); + // Current WiFi transmit power, in dBm (ESP-IDF reports quarter-dBm internally // and we round to whole). Returns 0 when WiFi isn't initialised or the call // fails. Same value for STA and AP — WiFi has one radio at one TX power. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e5431805..7035862c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(mm_tests unit/light/unit_AudioBands.cpp unit/light/unit_NetworkSendDriver_no_alloc_in_loop.cpp unit/light/unit_NetworkSendDriver_packet.cpp + unit/light/unit_WledAudioSyncPacket.cpp unit/light/unit_BlendMap.cpp unit/light/unit_draw.cpp unit/light/unit_GameOfLifeEffect.cpp diff --git a/test/scenarios/light/scenario_perf_full.json b/test/scenarios/light/scenario_perf_full.json index 1c434170..35170545 100644 --- a/test/scenarios/light/scenario_perf_full.json +++ b/test/scenarios/light/scenario_perf_full.json @@ -87,7 +87,7 @@ "pc-macos": { "tick_us": [ 0, - 12 + 25 ], "free_heap": [ 0, @@ -99,7 +99,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -176,7 +176,7 @@ "pc-macos": { "tick_us": [ 0, - 8 + 17 ], "free_heap": [ 0, @@ -188,7 +188,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -265,7 +265,7 @@ "pc-macos": { "tick_us": [ 0, - 7 + 20 ], "free_heap": [ 0, @@ -277,7 +277,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -352,7 +352,7 @@ "pc-macos": { "tick_us": [ 0, - 3 + 10 ], "free_heap": [ 0, @@ -364,7 +364,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -1217,7 +1217,7 @@ "pc-macos": { "tick_us": [ 16, - 462 + 490 ], "free_heap": [ 0, @@ -1229,7 +1229,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -1318,7 +1318,7 @@ "pc-macos": { "tick_us": [ 3, - 21 + 28 ], "free_heap": [ 0, @@ -1330,7 +1330,7 @@ ], "at": [ "2026-06-17", - "2026-06-27" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -1411,7 +1411,7 @@ "pc-macos": { "tick_us": [ 14, - 85 + 92 ], "free_heap": [ 0, @@ -1423,7 +1423,7 @@ ], "at": [ "2026-06-17", - "2026-06-27" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -1597,7 +1597,7 @@ "pc-macos": { "tick_us": [ 272, - 1712 + 2452 ], "free_heap": [ 0, @@ -1609,7 +1609,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32s3-n16r8": { @@ -1902,7 +1902,7 @@ "pc-macos": { "tick_us": [ 14, - 106 + 239 ], "free_heap": [ 0, @@ -1914,7 +1914,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32": { @@ -1995,7 +1995,7 @@ "pc-macos": { "tick_us": [ 61, - 296 + 769 ], "free_heap": [ 0, @@ -2007,7 +2007,7 @@ ], "at": [ "2026-06-17", - "2026-07-01" + "2026-07-04" ] }, "esp32": { diff --git a/test/unit/light/unit_WledAudioSyncPacket.cpp b/test/unit/light/unit_WledAudioSyncPacket.cpp new file mode 100644 index 00000000..f4e6ec2a --- /dev/null +++ b/test/unit/light/unit_WledAudioSyncPacket.cpp @@ -0,0 +1,115 @@ +// @module WledAudioSyncPacket + +// Pins the WLED audio-sync wire format — the 44-byte v2 packet projectMM broadcasts on +// UDP 11988 and that WLED / MoonLight (D_WLEDAudio.h) receive. A wire format breaks +// silently, so build → parse is round-tripped AND a golden byte vector fixes the exact +// offsets: the packet is a fixed compatibility contract (netmindz/WLED-sync), not ours to +// drift. (Same rigor as the Improv frame golden vector.) + +#include "doctest.h" +#include "light/WLEDAudioSyncPacket.h" + +#include +#include + +using namespace mm; + +// Build a frame with distinct, checkable values in every field. +static AudioFrame sampleFrame() { + AudioFrame f; + f.level = 200; + f.levelSmoothed = 150; + f.peakHz = 440; + f.peakMag = 77; + for (int i = 0; i < 16; i++) f.bands[i] = static_cast(i * 16); // 0,16,...,240 + return f; +} + +TEST_CASE("build produces a 44-byte v2 packet with the exact WLED layout") { + AudioFrame f = sampleFrame(); + uint8_t pkt[WLED_SYNC_PACKET_SIZE]; + size_t n = buildWledAudioSync(pkt, f, /*frameCounter=*/7, /*peak=*/true); + + CHECK(n == 44); + CHECK(WLED_SYNC_PACKET_SIZE == 44); + CHECK(WLED_SYNC_PORT == 11988); + + // header "00002" (+ NUL) at offset 0 + CHECK(std::memcmp(pkt, "00002", 6) == 0); + // gap1 (offset 6-7) is zero + CHECK(pkt[6] == 0); CHECK(pkt[7] == 0); + // sampleRaw = level (float LE) at offset 8 + CHECK(wledGetFloatLE(pkt + 8) == doctest::Approx(200.0f)); + // sampleSmth = levelSmoothed at offset 12 + CHECK(wledGetFloatLE(pkt + 12) == doctest::Approx(150.0f)); + // samplePeak / frameCounter at 16 / 17 + CHECK(pkt[16] == 1); + CHECK(pkt[17] == 7); + // fftResult[16] = bands at offset 18 + for (int i = 0; i < 16; i++) CHECK(pkt[18 + i] == static_cast(i * 16)); + // gap2 (offset 34-35) is zero + CHECK(pkt[34] == 0); CHECK(pkt[35] == 0); + // FFT_Magnitude = peakMag at 36, FFT_MajorPeak = peakHz at 40 + CHECK(wledGetFloatLE(pkt + 36) == doctest::Approx(77.0f)); + CHECK(wledGetFloatLE(pkt + 40) == doctest::Approx(440.0f)); +} + +TEST_CASE("build -> parse round-trips every AudioFrame field") { + AudioFrame f = sampleFrame(); + uint8_t pkt[WLED_SYNC_PACKET_SIZE]; + buildWledAudioSync(pkt, f, 3, false); + + AudioFrame out; + REQUIRE(parseWledAudioSync(pkt, WLED_SYNC_PACKET_SIZE, out)); + CHECK(out.level == f.level); + CHECK(out.levelSmoothed == f.levelSmoothed); + CHECK(out.peakHz == f.peakHz); + CHECK(out.peakMag == f.peakMag); + for (int i = 0; i < 16; i++) CHECK(out.bands[i] == f.bands[i]); +} + +TEST_CASE("parse rejects wrong length, wrong header, v1, and null") { + AudioFrame f = sampleFrame(); + uint8_t pkt[WLED_SYNC_PACKET_SIZE]; + buildWledAudioSync(pkt, f, 0, false); + AudioFrame out; + + // exact-length valid packet parses + CHECK(parseWledAudioSync(pkt, 44, out)); + // one byte short / one byte long — rejected (WLED sends exactly 44) + CHECK_FALSE(parseWledAudioSync(pkt, 43, out)); + CHECK_FALSE(parseWledAudioSync(pkt, 45, out)); + // wrong header ("00001" = the legacy v1 packet) — rejected, not crashed + uint8_t v1[44]; std::memcpy(v1, pkt, 44); std::memcpy(v1, "00001", 6); + CHECK_FALSE(parseWledAudioSync(v1, 44, out)); + // an 83-byte v1-sized buffer — rejected on length + uint8_t big[83] = {}; std::memcpy(big, "00001", 6); + CHECK_FALSE(parseWledAudioSync(big, sizeof(big), out)); + // null + CHECK_FALSE(parseWledAudioSync(nullptr, 44, out)); +} + +TEST_CASE("golden vector — the exact bytes on the wire (the compatibility contract)") { + // A fixed frame → the exact 44 bytes any WLED receiver must accept. If this changes, + // interop with WLED/MoonLight breaks — the test is the alarm. + AudioFrame f; + f.level = 100; f.levelSmoothed = 50; f.peakHz = 1000; f.peakMag = 25; + for (int i = 0; i < 16; i++) f.bands[i] = static_cast(i); // 0..15 + uint8_t pkt[WLED_SYNC_PACKET_SIZE]; + buildWledAudioSync(pkt, f, /*frameCounter=*/1, /*peak=*/false); + + // Little-endian IEEE-754 for 100.0, 50.0, 25.0, 1000.0 + const uint8_t golden[44] = { + '0','0','0','0','2','\0', // 0-5 header + 0, 0, // 6-7 gap1 + 0x00,0x00,0xC8,0x42, // 8-11 sampleRaw = 100.0f + 0x00,0x00,0x48,0x42, // 12-15 sampleSmth = 50.0f + 0, // 16 samplePeak + 1, // 17 frameCounter + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, // 18-33 fftResult + 0, 0, // 34-35 gap2 + 0x00,0x00,0xC8,0x41, // 36-39 FFT_Magnitude = 25.0f + 0x00,0x00,0x7A,0x44, // 40-43 FFT_MajorPeak = 1000.0f + }; + CHECK(std::memcmp(pkt, golden, 44) == 0); +} diff --git a/web-installer/deviceModels.json b/web-installer/deviceModels.json index 8119bfb1..f3c41251 100644 --- a/web-installer/deviceModels.json +++ b/web-installer/deviceModels.json @@ -847,12 +847,12 @@ ] }, { - "name": "ABC-WLED ESP32-P4 shield", + "name": "MHC-WLED ESP32-P4 shield", "chip": "ESP32-P4", "firmwares": [ "esp32p4-eth" ], - "image": "assets/boards/abc-wled-esp32-p4-shield.jpg", + "image": "assets/boards/mhc-wled-esp32-p4-shield.jpg", "url": "https://shop.myhome-control.de/en/ABC-WLED-ESP32-P4-shield/HW10027", "supported": [ "LEDs", @@ -861,7 +861,7 @@ ], "planned": [ "WiFi", - "RS-485 / DMX", + "RS-485 / DMX (in/out)", "Inputs", "16-ch parallel LEDs (8+ not yet)" ], @@ -870,7 +870,19 @@ "type": "System", "id": "System", "controls": { - "deviceModel": "ABC-WLED ESP32-P4 shield" + "deviceModel": "MHC-WLED ESP32-P4 shield" + } + }, + { + "type": "AudioModule", + "id": "Audio", + "parent_id": "System", + "controls": { + "wsPin": 26, + "sdPin": 33, + "sckPin": 32, + "mclkPin": 36, + "gain": 100 } }, { From 6f51373940857952b94c41e1a87b4df9f2792654 Mon Sep 17 00:00:00 2001 From: ewowi Date: Sat, 4 Jul 2026 12:51:20 +0200 Subject: [PATCH 2/3] Add IrModule (learn-any-remote) + extract Scheduler::setControl; LightCrafter Ethernet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an IR-receiver peripheral that learns any remote: decode a button's NEC code, bind it to an action, and thereafter that button drives the device (brightness, palette) — the physical-remote counterpart of the WLED-app bridge. To do it cleanly, the generic "set any control by name" logic moves out of HttpServer into a Scheduler primitive that IR, HTTP, Improv, and the WLED bridge all share. Also brings up W5500 Ethernet on the LightCrafter (it needs the WIZ850IO reset pin released) and folds in the earlier CodeRabbit findings on the audio-sync packet. KPI: 16384lights | PC:465KB | tick:121/94/120/15/2/299/61/17/20/172/120/17/1/36us(FPS:8264/10638/8333/66666/500000/3344/16393/58823/50000/5813/8333/58823/1000000/27777) | ESP32-S3:tick:1073us(FPS:931) | src:169(32480) | test:118(16934) | lizard:114w Core: - Scheduler: extracted setControl(module, control, value) — the generic control-set primitive (find module, validate + apply, rebuild controls, onUpdate, persist, conditional buildState). It was private to HttpServerModule; now it lives where the module tree does, so any module can drive another's control by composing against one primitive. HttpServer's applySetControl became a thin result→HTTP-status mapper (−54 lines net). Added Scheduler::instance() (the FilesystemModule singleton pattern) so a factory-created module reaches it, and a noteDirty hook so a control set from anywhere schedules the debounced save. - IrModule: new IR-receiver peripheral (factory-registered, catalog-wired, not auto-wired). Decodes an IR remote on `pin` and learns it: a `learn` select arms an action, the next received code binds to it (persisted per action in a readonly Text control, rebuilt into a fast lookup on load), and a bound code runs its action — brightness ±16 (clamped) / palette prev-next — through Scheduler::setControl. Status reports setup state, learning, a binding, a fired action, or an unassigned code. latestCode() is the seam a future LightsControl consumes. - AudioModule: 🐇 CodeRabbit — no functional change here beyond the packet-parse hardening below (kept for the branch). - platform: added irRead(pin, code) — real NEC-over-RMT decode on ESP32 (platform_esp32_ir.cpp: a persistent RX channel, an ISR that only signals a queue, decode + re-arm on the render task — never in interrupt context), a false stub on desktop. NetworkModule W5500 path unchanged from the earlier ISR/poll fix. Light domain: - WLEDAudioSyncPacket: 🐇 CodeRabbit — parse now clamps each wire float into its uint16 field (wledFloatToU16) so a foreign packet carrying NaN or an out-of-range value can't produce an undefined cast; comment corrected from "rounded" to "truncated". UI: - ui.md: IR entry rewritten for the learn workflow (learn select + per-action code read-outs; no action buttons, no last-code control — the status line carries the received code). Embeds the remote photo effects.md-style. Scripts / MoonDeck: - check_devices.py: added "IR" to the supported-capability vocabulary now that IrModule backs it. Tests: - unit_IrModule: learn→bind→fire, clamping, independent multi-action bindings, unassigned-code and missing-target robustness (9 cases, injectCodeForTest seam since irRead stubs on desktop). - unit_AudioModule_sync, unit_WledAudioSyncPacket: 🐇 CodeRabbit — added the sync socket-lifecycle tests and the float-clamp cases. Docs / catalog: - deviceModels.json: SE 16 V1 + LightCrafter 16 — IR moved planned→supported (decode works on hardware); IrModule child (SE16 pin 5, LightCrafter pin 4, parent System); LightCrafter Ethernet gains ethRstGpio 3 (the WIZ850IO nRST — without it the W5500 reads 0x00 "version mismatched"). - IRDriver.jpeg moved from the MoonLight image pool to assets/core/IrModule.jpeg; the stale MoonLight controls screenshot deleted. - Plan-20260704 - IrModule brightness + palette: saved plan. Reviews: - 🐇 WLEDAudioSyncPacket: parse clamps NaN/out-of-range floats before the uint16 cast — fixed. - 🐇 AudioModule: sync socket-lifecycle unit tests added — fixed. - 🐇 deviceModels.json MHC-WLED url still ABC-WLED — accepted as-is: the shop's product path is literally .../ABC-WLED-ESP32-P4-shield/HW10027 (verified live); the rename is internal only, changing it would break the link. - 🐇 scenario_perf_full.json "future timestamps" — accepted: the `at` dates are ≤ the capture date; the finding reflects the reviewer's training-cutoff clock, not real future dates. Co-Authored-By: Claude Opus 4.8 --- .../core/IrModule.jpeg} | Bin .../moonlight/drivers/irdrivercontrols.png | Bin 11395 -> 0 bytes .../Plan-20260704 - IrModule brightness.md | 125 +++++++++++ docs/moonmodules/core/ui/ui.md | 14 ++ esp32/main/CMakeLists.txt | 1 + scripts/check/check_devices.py | 2 +- src/core/AudioModule.h | 22 ++ src/core/FilesystemModule.cpp | 8 +- src/core/HttpServerModule.cpp | 54 ++--- src/core/IrModule.h | 202 ++++++++++++++++++ src/core/Scheduler.cpp | 46 ++++ src/core/Scheduler.h | 36 ++++ src/light/WLEDAudioSyncPacket.h | 22 +- src/main.cpp | 2 + src/platform/desktop/platform_desktop.cpp | 6 + src/platform/esp32/platform_esp32.cpp | 21 +- src/platform/esp32/platform_esp32_ir.cpp | 131 ++++++++++++ src/platform/platform.h | 10 +- test/CMakeLists.txt | 2 + test/scenarios/light/scenario_perf_full.json | 2 +- test/unit/core/unit_AudioModule_sync.cpp | 176 +++++++++++++++ test/unit/core/unit_IrModule.cpp | 174 +++++++++++++++ test/unit/light/unit_WledAudioSyncPacket.cpp | 31 +++ web-installer/deviceModels.json | 36 +++- 24 files changed, 1066 insertions(+), 57 deletions(-) rename docs/{backlog/moonlight_images/moonlight/drivers/IRDriver.jpeg => assets/core/IrModule.jpeg} (100%) delete mode 100644 docs/backlog/moonlight_images/moonlight/drivers/irdrivercontrols.png create mode 100644 docs/history/plans/Plan-20260704 - IrModule brightness.md create mode 100644 src/core/IrModule.h create mode 100644 src/platform/esp32/platform_esp32_ir.cpp create mode 100644 test/unit/core/unit_AudioModule_sync.cpp create mode 100644 test/unit/core/unit_IrModule.cpp diff --git a/docs/backlog/moonlight_images/moonlight/drivers/IRDriver.jpeg b/docs/assets/core/IrModule.jpeg similarity index 100% rename from docs/backlog/moonlight_images/moonlight/drivers/IRDriver.jpeg rename to docs/assets/core/IrModule.jpeg diff --git a/docs/backlog/moonlight_images/moonlight/drivers/irdrivercontrols.png b/docs/backlog/moonlight_images/moonlight/drivers/irdrivercontrols.png deleted file mode 100644 index bbca0d99ac743b3e7bc5c7c6ae32297c21999fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11395 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 R?u7sV002ovPDHLkV1ndg#H;`S diff --git a/docs/history/plans/Plan-20260704 - IrModule brightness.md b/docs/history/plans/Plan-20260704 - IrModule brightness.md new file mode 100644 index 00000000..a011ef28 --- /dev/null +++ b/docs/history/plans/Plan-20260704 - IrModule brightness.md @@ -0,0 +1,125 @@ +# Plan — IrModule: minimal IR receiver peripheral that adjusts brightness + palette + +## Context + +The SE16 / LightCrafter boards carry an IR receiver (SE16: GPIO 5, shared with Ethernet MISO +via the board switch; LightCrafter: GPIO 4). "IR" is currently a `planned` capability. The +product owner wants a **minimal IR service** that actually *does* something — adjust global +**brightness** — rather than only exposing a raw code. The full remote-code → action mapping is +a later step; for now the action plumbing is proven with **`brightness up` / `brightness down` +buttons** in the UI, wired to the same brightness-adjust path a decoded IR code will call later. + +Confirmed with the product owner: +- **Receive only** (no TX). +- **Peripheral, catalog-wired** — factory-registered like AudioModule / I2cScanModule, added + per board via `deviceModels.json`; NOT a hardcoded child of System. +- **Start minimal, grow later** — brightness up/down now; richer remote mapping is a follow-up. + +Backlog alignment ([backlog-mixed.md](../../backlog/backlog-mixed.md)): IR is named as an input +for the eventual **LightsControl** hub. This module is the thin IR *input* peripheral; when +LightsControl is built it consumes IR via the same static seam (the `AudioModule::latestFrame()` +pattern). This does not build LightsControl — it builds the IR input and one concrete action. + +## Files + +1. **New `src/core/IrModule.h`** — shaped on `I2cScanModule` (the minimal peripheral template): + - `class IrModule : public MoonModule`; `role() → Peripheral`; `respectsEnabled()` default + (true — IR is a real feature, not a diagnostic). + - Controls: `addPin("pin", pin_)` (IR receiver GPIO), `addButton("brightness up")`, + `addButton("brightness down")`, `addReadOnly("last code", codeStr_)` (shows the last + decoded code once the RMT decode lands; blank while the seam is a stub). + - `onUpdate(name)`: `"brightness up"` → `Drivers::adjustBrightness(+kStep)`; `"brightness + down"` → `Drivers::adjustBrightness(-kStep)`. `kStep = 16` (a perceptible notch; 16 steps + across the 0–255 range). + - `loop()`: poll `platform::irRead(pin_, code)`; on a fresh code, store it for the readout + (and later: map to an action). Stub returns false today, so loop is a cheap no-op. + - Static `latestCode()` seam for a future LightsControl consumer (mirrors + `AudioModule::latestFrame()`), returning the last decoded code (0 = none yet). + +2. **`src/light/drivers/Drivers.h`** — add a static brightness-adjust seam: + - `static void adjustBrightness(int delta);` — clamps `brightness` to [0,255], rebuilds the + correction LUT, and notifies driver children via `onCorrectionChanged()` — the SAME path + `onUpdate("brightness")` takes, so an IR-driven change behaves exactly like the UI slider. + - Needs a static instance pointer (`static Drivers* active_;` set in `setup()`, cleared in + `teardown()`) — the established single-owner seam pattern. No-op if no Drivers is live. + +3. **`src/platform/platform.h`** — new seam near `i2cScan`: + - `bool irRead(uint16_t pin, uint32_t& codeOut);` — true when a fresh IR frame decodes on + `pin` (self-contained: opens/owns its RMT-RX channel, like `i2cScan` opens its own bus). + Present-tense doc: today ESP32 + desktop both stub to `false`; the RMT-NEC decode is a + focused follow-up (avoids rushing RMT-vs-RmtLedDriver channel contention into this cut). + +4. **`src/platform/esp32/platform_esp32*.cpp`** + **`platform_desktop.cpp`** — `irRead` stubs + returning `false`. (ESP32's real RMT-NEC decode is the follow-up; the RMT-RX machinery + already exists in `platform_esp32_rmt.cpp`.) + +5. **`web-installer/deviceModels.json`** — add an `IrModule` child to SE16 (`pin: 5`) and + LightCrafter (`pin: 4`). Keep "IR" in `planned` (NOT `supported`) until the ESP32 decode + lands — per the vocabulary rule, `supported` requires a working backing capability, and the + *decode* isn't working yet even though the module + brightness action are. The brightness + buttons work now; IR reception is the planned part. + +6. **`src/main.cpp`** — `registerType("IrModule", "core/IrModule.md")`. + +7. **Docs** — the per-module technical page is **moxygen-generated** from `IrModule.h`'s `///` + comments (gitignored, not hand-written); the hand-authored piece is a summary entry in + `docs/moonmodules/core/ui/ui.md` linking to `../moxygen/IrModule.md`, plus embedding the + remote photo effects.md-style (``). *(Superseded the original line below, + which planned a hand-written `IrModule.md` — corrected to the moxygen/ui.md convention.)* The + old line: `docs/moonmodules/core/IrModule.md` — cross-file wiring + the brightness seam + the + "Prior art" note (NEC IR protocol, ESP-IDF RMT RX example). + +8. **Test** `test/unit/core/unit_IrModule.cpp` — the brightness buttons adjust `Drivers` + brightness (up clamps at 255, down clamps at 0, step size); a stub `irRead` yields no code. + Driven through the public control path like `unit_AudioModule_sync`. + +## What actually shipped (grew past the original "brightness buttons" scope) + +The cut ended up delivering the full receive + learn feature, live-proven on the SE16 and +LightCrafter: + +- **Real RMT NEC decode** (`platform_esp32_ir.cpp`): a persistent RX channel on `pin`, an + ISR-minimal done-callback (signals a queue only — no decode/re-arm in interrupt context, the + same discipline as rmtWs2812RxCapture), decode + re-arm on the render task in `irRead`. + Live-proven: 4 distinct remote buttons → 4 stable 32-bit codes on both boards. +- **Learned code → action mapping.** A `learn` select arms an action; the next received code + binds to it (stored per-action in a persistent `code …` Text control, rebuilt into a fast + `learnedCode_` lookup on load). A received code runs its bound action. PO decision + (2026-07-04): learning (any remote, live) over MoonLight's fixed per-remote presets. +- **No UI action buttons** (PO decision 2026-07-04): the remote is the interface once learned, so + brightness/palette up-down buttons would duplicate it — removed. The `learn` select + per-action + `code …` read-outs are the whole UI. +- **No `last code` control** (PO decision 2026-07-04): the received code shows in the status line + ("received 0x…" / "learned … = 0x…"), so a separate read-out would duplicate it. +- **Status feedback** via base `MoonModule::setStatus` (+ a per-module `statusBuf_` for dynamic + text, the I2cScan/Devices pattern): "set pin to receive" / "ready" (setup), "learning: press a + remote button" (armed), "learned = 0x…" (bound), "Drivers.brightness → N" (fired), + "received 0x… (unassigned)" (unbound code). + +## Scope guards (held) + +- **No IR transmit.** +- **No LightsControl hub** — this is the IR input peripheral only; `latestCode()` is the seam it + will consume. + +## Follow-up (next session) + +- **`effect next` / `effect prev`** — change the effect in layer[0] slot[0]. PO asked for it + (2026-07-04); deferred because it is a module *replace* (swap the effect type), not a + `setControl` nudge. It needs: (a) a **replace-by-type primitive extracted to Scheduler** (the + same move as the setControl extraction — currently `applyReplace` is HttpServer-private), (b) + ordered **effect-type enumeration by role** from ModuleFactory, (c) tree navigation to the + layer[0] slot. Then it's two more `kActions`-style rows that call the replace primitive instead + of setControl. Design the primitive first. +- **Move "IR" `planned` → `supported`** in the catalog now that decode works on hardware (a + `supported` capability must have a working backing module — it does now). + +## Verification (done) + +- Desktop + all ESP32 variants build clean (`-Werror`); `ctest` (9 IrModule cases: learn/bind, + fire, clamp, independent bindings, unassigned, robustness); scenarios green; `check_specs` / + `check_devices` valid. +- **Live on SE16 (GPIO 5, IR/Eth switch) and LightCrafter (GPIO 4, IR+Eth simultaneous)**: remote + press decodes to a code, learn binds it, a bound code drives brightness/palette. Boards boot + clean with the decoder (the first ISR-unsafe version crash-looped; the task-side rewrite is + stable). diff --git a/docs/moonmodules/core/ui/ui.md b/docs/moonmodules/core/ui/ui.md index 1d73c02e..ecf3b955 100644 --- a/docs/moonmodules/core/ui/ui.md +++ b/docs/moonmodules/core/ui/ui.md @@ -90,3 +90,17 @@ A System peripheral that probes the I²C bus (default GPIO21/22) on a button pre - read-only — `result` (addresses found). Detail: [technical](../moxygen/I2cScanModule.md) + +### IR + +A System peripheral (added per board, not auto-wired): an IR remote receiver that drives other modules' controls through the shared `Scheduler::setControl` primitive — the device's IR input, the counterpart of the WLED-app bridge. It **learns** any remote: pick an action in `learn`, press a remote button to bind its code, and thereafter that button drives the action. Decoding is real NEC-over-RMT; a received code (bound or not) shows in the status line. + +An IR receiver and the common 21-key RGB remote it decodes + +- `pin` — the IR receiver GPIO (unset until entered; on the SE16 it shares GPIO 5 with the Ethernet MISO via the board switch, on the LightCrafter it is its own GPIO 4 alongside Ethernet). +- `learn` — pick an action (off / brightness up / brightness down / palette next / palette prev); the next received code binds to it, then learning disarms. +- `code brightness up` / `code brightness down` / `code palette next` / `code palette prev` — read-only, the learned code for each action (persisted). + +The actions nudge `Drivers.brightness` (±16, clamped 0–255) and step `Drivers.palette` (next/previous). The status line reports setup state ("set pin to receive" / "ready"), the learn prompt, a binding ("learned … = 0x…"), a fired action ("Drivers.brightness → N"), and an unbound code ("received 0x… (unassigned)"). + +Detail: [technical](../moxygen/IrModule.md) diff --git a/esp32/main/CMakeLists.txt b/esp32/main/CMakeLists.txt index 89bd16f7..53d8cea1 100644 --- a/esp32/main/CMakeLists.txt +++ b/esp32/main/CMakeLists.txt @@ -22,6 +22,7 @@ idf_component_register( "../../src/platform/esp32/platform_esp32_parlio.cpp" "../../src/platform/esp32/platform_esp32_i2s.cpp" "../../src/platform/esp32/platform_esp32_i2c.cpp" + "../../src/platform/esp32/platform_esp32_ir.cpp" "../../src/platform/esp32/platform_esp32_es8311.cpp" INCLUDE_DIRS "../../src" diff --git a/scripts/check/check_devices.py b/scripts/check/check_devices.py index 5961ce0f..bcf398bf 100644 --- a/scripts/check/check_devices.py +++ b/scripts/check/check_devices.py @@ -35,7 +35,7 @@ # in lockstep with the modules that actually exist. planned = peripherals with no # module yet (the backlog seed) — open-ended by design, so it is NOT whitelisted, # only type-checked. Adding a new supported capability means a module backs it. -SUPPORTED_VOCAB = {"LEDs", "WiFi", "Ethernet", "Audio"} +SUPPORTED_VOCAB = {"LEDs", "WiFi", "Ethernet", "Audio", "IR"} # Boot-wired singletons: present on every device, added by code, so the catalog # references them by id without the factory creating them. Their catalog `type` diff --git a/src/core/AudioModule.h b/src/core/AudioModule.h index 31d736ec..c52a026c 100644 --- a/src/core/AudioModule.h +++ b/src/core/AudioModule.h @@ -223,6 +223,16 @@ class AudioModule : public MoonModule { /// mic-less build just sees silence. const AudioFrame* audioFrame() const { return &frame_; } + // --- Test seams (host unit tests only; mirror DevicesModule::injectPacketForTest) --- + // Read-only views of the sync socket lifecycle so unit_AudioModule_sync can assert it + // through the public loop() without befriending the class or exposing internals broadly. + bool syncOpenForTest() const { return syncOpen_; } + uint8_t syncFrameCounterForTest() const { return syncFrameCounter_; } + const char* syncStatusForTest() const { return syncStr_; } + static constexpr uint32_t syncSendIntervalMsForTest() { return kSyncSendIntervalMs; } + static constexpr uint32_t syncFallbackMsForTest() { return kSyncFallbackMs; } + static constexpr uint32_t syncOpenRetryMsForTest() { return kSyncOpenRetryMs; } + /// Process-wide accessor for the consumers (audio effects). There is one mic, /// and an effect can be added/removed via the UI at any time, so it can't rely /// on a boot-time setter — it asks here @@ -429,9 +439,11 @@ class AudioModule : public MoonModule { uint32_t lastSyncRecv_ = 0; // millis of the last received packet (receive auto-blend) uint8_t syncFrameCounter_ = 0; // increments per send (the packet's dup/reorder field) bool syncOpen_ = false; // socket opened for the current mode (lazy-open latch) + uint32_t lastSyncOpenFailMs_ = 0; // millis of the last failed open (0 = none); bring-up backoff char syncStr_[32] = {}; // "sync status" read-out static constexpr uint32_t kSyncSendIntervalMs = 25; // ~40/s — WLED-friendly, well under a flood static constexpr uint32_t kSyncFallbackMs = 1000; // no packet this long → resume local mic + static constexpr uint32_t kSyncOpenRetryMs = 1000; // pause between socket bring-up retries after a failure static constexpr int kSyncMaxRecvPerTick = 8; // bounded non-blocking drain (sync is low-rate) static constexpr const char* kInitFailMsg = "mic init failed — check pins / rate"; @@ -518,6 +530,7 @@ class AudioModule : public MoonModule { if constexpr (!platform::hasNetwork) return; syncSock_.close(); // syncEnsureSocket() re-opens per mode when the net is up syncOpen_ = false; + lastSyncOpenFailMs_ = 0; // a mode change retries bring-up immediately (no stale backoff) lastSyncRecv_ = 0; std::snprintf(syncStr_, sizeof(syncStr_), sync == 1 ? "send: waiting for network" @@ -536,6 +549,12 @@ class AudioModule : public MoonModule { if (sync == 0) return false; if (syncOpen_) return true; if (!platform::networkReady()) return false; // interface not up yet — try again next tick + // Back off between failed bring-ups: loop() runs every tick, so without this a + // persistent open/bind failure (e.g. the port is busy) would retry — one socket() + // syscall per tick — dozens of times a second. lastSyncOpenFailMs_ stamps the last + // failure; hold off until kSyncOpenRetryMs has passed (same throttle form as syncSend). + const uint32_t now = platform::millis(); + if (lastSyncOpenFailMs_ != 0 && now - lastSyncOpenFailMs_ < kSyncOpenRetryMs) return false; if (sync == 1) { // send → broadcast destination (configurable port) char bcast[16]; formatDottedQuad(bcast, kBroadcast_); if (syncSock_.open() && syncSock_.connect(bcast, syncPort)) { @@ -554,6 +573,9 @@ class AudioModule : public MoonModule { std::snprintf(syncStr_, sizeof(syncStr_), "receive: bind failed"); } } + // Stamp a failure (or clear the timer on success). now==0 is nudged to 1 so the + // "!=0 means a failure is pending" sentinel holds even at millis()==0. + lastSyncOpenFailMs_ = syncOpen_ ? 0 : (now == 0 ? 1 : now); return syncOpen_; } diff --git a/src/core/FilesystemModule.cpp b/src/core/FilesystemModule.cpp index 05fb68b0..20624a3e 100644 --- a/src/core/FilesystemModule.cpp +++ b/src/core/FilesystemModule.cpp @@ -20,7 +20,13 @@ FilesystemModule::~FilesystemModule() { void FilesystemModule::setScheduler(Scheduler* s) { scheduler_ = s; instance_ = this; - if (s) s->setLoadAllHook(&loadAllHookTrampoline_); + if (s) { + s->setLoadAllHook(&loadAllHookTrampoline_); + // Scheduler::setControl calls this after a mutation so a control set from anywhere + // (IR, WLED bridge, /api/control) schedules the same debounced save. noteDirty is a + // static, so a plain function pointer suffices — no trampoline needed. + s->setNoteDirtyHook(&FilesystemModule::noteDirty); + } } void FilesystemModule::setup() { diff --git a/src/core/HttpServerModule.cpp b/src/core/HttpServerModule.cpp index 00a4f335..8ff15d7a 100644 --- a/src/core/HttpServerModule.cpp +++ b/src/core/HttpServerModule.cpp @@ -496,47 +496,19 @@ void HttpServerModule::writeControls(JsonSink& sink, MoonModule* mod) { // free: no TcpConnection, returns an OpResult the caller maps to its own reporting. HttpServerModule::OpResult HttpServerModule::applySetControl( const char* moduleName, const char* controlName, const char* valueJson) { - MoonModule* target = findModuleByName(moduleName); - if (!target) return OpResult::ModuleNotFound; - - // Module-level "enabled" pseudo-control. - if (std::strcmp(controlName, "enabled") == 0) { - target->setEnabled(mm::json::parseBool(valueJson, "value")); - target->markDirty(); - FilesystemModule::noteDirty(); - if (scheduler_) scheduler_->buildState(); - return OpResult::Ok; - } - - auto& ctrls = target->controls(); - for (uint8_t i = 0; i < ctrls.count(); i++) { - auto& c = ctrls[i]; - if (std::strcmp(c.name, controlName) != 0) continue; - - // Per-type parse + validate + apply lives in Control.cpp. Non-Ok leaves the - // storage untouched, so no rollback needed. - ApplyResult r = applyControlValue(c, valueJson, "value"); - switch (r) { - case ApplyResult::Ok: break; - case ApplyResult::OutOfRange: return OpResult::OutOfRange; - case ApplyResult::Malformed: return OpResult::Malformed; - case ApplyResult::ReadOnly: return OpResult::ReadOnly; - } - // Rebuild the control list after every change so onBuildControls() can - // re-evaluate which controls are visible for the new value (a Select - // revealing fields, etc.). clear()+onBuildControls(), cheap + idempotent. - target->rebuildControls(); - // Three-tier control-change reaction (see MoonModule::onUpdate): onUpdate - // always; a tree-wide buildState only when the control reshapes dims/mapping. - target->onUpdate(controlName); - target->markDirty(); - FilesystemModule::noteDirty(); - if (target->controlChangeTriggersBuildState(controlName) && scheduler_) { - scheduler_->buildState(); - } - return OpResult::Ok; - } - return OpResult::ControlNotFound; // control name not on this module + // The generic control-set is a Scheduler primitive (it owns the tree + persistence hook), + // shared with every other control writer — Improv, the WLED bridge, IrModule. This wrapper + // only maps its result onto the HTTP OpResult so the response carries the right status code. + if (!scheduler_) return OpResult::ModuleNotFound; + switch (scheduler_->setControl(moduleName, controlName, valueJson)) { + case Scheduler::SetControlResult::Ok: return OpResult::Ok; + case Scheduler::SetControlResult::ModuleNotFound: return OpResult::ModuleNotFound; + case Scheduler::SetControlResult::ControlNotFound: return OpResult::ControlNotFound; + case Scheduler::SetControlResult::OutOfRange: return OpResult::OutOfRange; + case Scheduler::SetControlResult::Malformed: return OpResult::Malformed; + case Scheduler::SetControlResult::ReadOnly: return OpResult::ReadOnly; + } + return OpResult::ModuleNotFound; // unreachable; keeps -Wreturn-type happy } void HttpServerModule::handleSetControl(platform::TcpConnection& conn, const char* body) { diff --git a/src/core/IrModule.h b/src/core/IrModule.h new file mode 100644 index 00000000..357584fd --- /dev/null +++ b/src/core/IrModule.h @@ -0,0 +1,202 @@ +#pragma once + +#include "core/MoonModule.h" +#include "core/Scheduler.h" // setControl — the generic control-set primitive +#include "platform/platform.h" // irRead + +#include +#include +#include // strtoul — rebuild learnedCode_ from persisted hex strings +#include + +namespace mm { + +/// A core, domain-neutral IR-receiver peripheral: it decodes an IR remote on `pin` and drives +/// other modules' controls. It is the device's IR *input* — the role the WLED-app bridge plays +/// for the phone app, expressed for a physical remote. +/// +/// **How it acts.** Every action routes through `Scheduler::setControl(module, control, value)`, +/// the one generic control-set primitive (also used by `/api/control`, Improv, and the WLED +/// bridge). IR never reaches into another module's internals; it composes against that primitive, +/// so adding a new action is one row in `kActions` — not new plumbing. The actions are +/// **brightness up/down** and **palette prev/next**, each a relative nudge of a target control. +/// +/// **Buttons vs the remote.** Each action is a UI button (press it → the nudge happens now) AND a +/// learnable remote binding. The two share one path: a button press and a matching remote code +/// both call `runAction`. +/// +/// **Learning (any remote, no firmware table).** Pick an action in the `learn` select; the next +/// decoded IR code binds to it (stored in that action's `code …` control, which persists like any +/// control). Press that remote button afterwards and its code is looked up → the action runs. This +/// is more flexible than a fixed per-remote preset table (MoonLight's model): it works with any +/// remote, the user teaches it live. A code bound to nothing is shown in `last code` and ignored. +/// +/// **Relative adjust.** An action nudges a target control by a signed delta, clamped to the +/// control's own `[min, max]` — read generically from the target's `ControlDescriptor`, so the +/// same code adjusts a 0–255 brightness slider and an N-option palette Select without knowing +/// either's domain. A future LightsControl hub (docs/backlog) absorbs this as the normalised +/// interface effects read; `latestCode()` is the seam it consumes (the `AudioModule::latestFrame` +/// pattern). +/// +/// **Not auto-wired.** Factory-registered like AudioModule / I2cScanModule, so a board with an IR +/// receiver adds it via the installer device catalog (its `pin` carrying that board's IR GPIO) or +/// the user adds it from the UI. On the SE16 the IR line shares GPIO 5 with the Ethernet MISO +/// through the board's hardware switch; the pin is the receiver input. +/// +/// **Prior art:** consumer IR remotes use the NEC protocol (a 32-bit address+command frame, +/// LSB-first, ~9 ms lead burst); the ESP-IDF RMT peripheral decodes it (the espressif +/// `ir_nec_transceiver` example). The decode itself lives behind `platform::irRead`. +class IrModule : public MoonModule { +public: + ModuleRole role() const override { return ModuleRole::Peripheral; } + + void onBuildControls() override { + controls_.addPin("pin", pin_); + controls_.addSelect("learn", learn_, kLearnOptions, kActionCount + 1); + for (uint8_t i = 0; i < kActionCount; i++) { + // The learned code for this action, shown next to it. A Text control (persistable), + // rendered read-only: the learn flow + persistence write it, the user doesn't type it — + // the same "persist but tooling-set" pattern as SystemModule.deviceModel. (ReadOnly is + // display-only and NOT persisted, so it would lose the binding on reboot; Text persists.) + // No UI button per action: the remote drives the action once learned, so a button would + // duplicate that — the `learn` select + these read-outs are the whole interface. + controls_.addText(kActions[i].codeCtrl, codeStr_[i], sizeof(codeStr_[i])); + controls_.setReadOnly(controls_.count() - 1, true); + } + // No "last code" control — a received code shows in the status line ("received 0x…" / + // "learned … = 0x…"), so a separate read-out would duplicate it. + MoonModule::onBuildControls(); + } + + // Setup state so the module says whether it can receive (a pin is required). Re-run on any + // rebuild so setting the pin updates the status live. Also rebuild the fast uint32 lookup from + // the persisted hex strings — persistence restores the codeStr_ buffers (Text controls), so + // parse each back into learnedCode_ here (post-load) so a learned binding survives a reboot. + void onBuildState() override { + for (uint8_t i = 0; i < kActionCount; i++) + learnedCode_[i] = codeStr_[i][0] ? std::strtoul(codeStr_[i], nullptr, 0) : 0; + reportReady(); + MoonModule::onBuildState(); + } + + void onUpdate(const char* controlName) override { + if (std::strcmp(controlName, "pin") == 0) reportReady(); + else if (std::strcmp(controlName, "learn") == 0) { + if (learn_ != 0) setStatus("learning: press a remote button", Severity::Status); + else reportReady(); + } + } + + void loop() override { + if (pin_ < 0) return; + uint32_t code = 0; + if (platform::irRead(static_cast(pin_), code)) processCode(code); + } + + /// The last decoded IR code (0 = none yet). The seam a future LightsControl consumes. + uint32_t latestCode() const { return lastCode_; } + + /// Feed a decoded code as if it arrived from the receiver — the entry the host unit tests + /// drive (platform::irRead is a stub on desktop). Mirrors DevicesModule::injectPacketForTest. + void injectCodeForTest(uint32_t code) { processCode(code); } + +private: + // One action: a UI button + a learnable remote code, nudging (module, control) by delta, + // clamped to the control's own bounds. Adding an action is one row here. + struct Action { + const char* button; // the UI button name + const char* codeCtrl; // the readonly control showing this action's learned code + const char* module; + const char* control; + int delta; + }; + static constexpr Action kActions[] = { + {"brightness up", "code brightness up", "Drivers", "brightness", +16}, + {"brightness down", "code brightness down", "Drivers", "brightness", -16}, + {"palette next", "code palette next", "Drivers", "palette", +1}, + {"palette prev", "code palette prev", "Drivers", "palette", -1}, + }; + static constexpr uint8_t kActionCount = sizeof(kActions) / sizeof(kActions[0]); + // learn select: index 0 = off, 1..N = arm learning for kActions[index-1]. + static constexpr const char* kLearnOptions[] = { + "off", "brightness up", "brightness down", "palette next", "palette prev", + }; + + // Handle a decoded code: in learn mode bind it to the armed action (and persist); otherwise + // look it up and run the bound action, or report it as unassigned. The shared entry for a real + // receive (loop) and a test injection. + void processCode(uint32_t code) { + lastCode_ = code; // the raw frame; surfaced via latestCode() + the status line below + + if (learn_ != 0) { + const uint8_t idx = learn_ - 1; // learn select is 1-based (0 = off) + learnedCode_[idx] = code; + std::snprintf(codeStr_[idx], sizeof(codeStr_[idx]), "0x%08lX", + static_cast(code)); + std::snprintf(statusBuf_, sizeof(statusBuf_), "learned %s = 0x%08lX", + kActions[idx].button, static_cast(code)); + setStatus(statusBuf_); + learn_ = 0; + markDirty(); + return; + } + for (uint8_t i = 0; i < kActionCount; i++) { + if (learnedCode_[i] != 0 && learnedCode_[i] == code) { runAction(kActions[i]); return; } + } + std::snprintf(statusBuf_, sizeof(statusBuf_), "received 0x%08lX (unassigned)", + static_cast(code)); + setStatus(statusBuf_); + markDirty(); + } + + // Read the target control's current value + bounds, apply the clamped delta, set it through the + // shared primitive — the same path the UI slider takes, so the change rebuilds the correction / + // active palette and persists identically. Reports what it changed. + void runAction(const Action& a) { + Scheduler* sched = Scheduler::instance(); + if (!sched) return; + MoonModule* target = sched->firstByName(a.module); + if (!target) { + std::snprintf(statusBuf_, sizeof(statusBuf_), "%s: no %s module", a.button, a.module); + setStatus(statusBuf_, Severity::Warning); + markDirty(); + return; + } + auto& ctrls = target->controls(); + for (uint8_t i = 0; i < ctrls.count(); i++) { + auto& c = ctrls[i]; + if (std::strcmp(c.name, a.control) != 0) continue; + int next = controlIntValue(c) + a.delta; + if (next < c.min) next = c.min; + if (next > c.max) next = c.max; + char valueJson[32]; + std::snprintf(valueJson, sizeof(valueJson), "{\"value\":%d}", next); + sched->setControl(a.module, a.control, valueJson); + std::snprintf(statusBuf_, sizeof(statusBuf_), "%s.%s → %d", a.module, a.control, next); + setStatus(statusBuf_); + markDirty(); + return; + } + } + + // Report setup readiness (static messages → no format buffer needed). + void reportReady() { + if (pin_ < 0) setStatus("set pin to receive", Severity::Warning); + else setStatus("ready"); + } + + // A 1-byte numeric control (Uint8 / Select) read as int via its descriptor pointer — covers + // every kActions target (brightness Uint8, palette Select both store a uint8_t). + static int controlIntValue(const ControlDescriptor& c) { + return c.ptr ? *static_cast(c.ptr) : 0; + } + + int8_t pin_ = -1; // IR receiver GPIO; -1 until a board/user sets it + uint8_t learn_ = 0; // learn select: 0=off, 1..N=arm kActions[idx-1] + uint32_t learnedCode_[kActionCount] = {}; // bound code per action (0 = unbound) + char codeStr_[kActionCount][12] = {}; // hex read-out per learned code (persisted control) + uint32_t lastCode_ = 0; // last decoded frame (0 = none) + char statusBuf_[48] = ""; // storage for dynamic setStatus() text +}; + +} // namespace mm diff --git a/src/core/Scheduler.cpp b/src/core/Scheduler.cpp index d914e47b..74b4ef1a 100644 --- a/src/core/Scheduler.cpp +++ b/src/core/Scheduler.cpp @@ -1,5 +1,7 @@ #include "core/Scheduler.h" +#include "core/Control.h" // applyControlValue + ApplyResult in setControl +#include "core/JsonUtil.h" // mm::json::parseBool for the "enabled" pseudo-control #include "platform/platform.h" #include // std::snprintf in ensureUniqueName @@ -13,6 +15,7 @@ void Scheduler::addModule(MoonModule* mod) { } void Scheduler::setup() { + instance_ = this; // the one live Scheduler, reachable via Scheduler::instance() startTime_ = platform::millis(); // Phase 1: bind each module's controls. After this, ControlList descriptors hold @@ -127,6 +130,7 @@ void Scheduler::teardown() { deleteTree(modules_[i - 1]); } moduleCount_ = 0; + instance_ = nullptr; } uint32_t Scheduler::elapsed() const { @@ -202,6 +206,48 @@ MoonModule* Scheduler::firstByName(const char* name) { return nullptr; } +Scheduler::SetControlResult Scheduler::setControl(const char* moduleName, + const char* controlName, + const char* valueJson) { + MoonModule* target = firstByName(moduleName); + if (!target) return SetControlResult::ModuleNotFound; + + // Module-level "enabled" pseudo-control — toggles the flag, then a full rebuild so the + // disabled subtree stops/starts ticking. + if (std::strcmp(controlName, "enabled") == 0) { + target->setEnabled(mm::json::parseBool(valueJson, "value")); + target->markDirty(); + if (noteDirtyHook_) noteDirtyHook_(); + buildState(); + return SetControlResult::Ok; + } + + auto& ctrls = target->controls(); + for (uint8_t i = 0; i < ctrls.count(); i++) { + auto& c = ctrls[i]; + if (std::strcmp(c.name, controlName) != 0) continue; + + // Per-type parse + validate + apply lives in Control.cpp. A non-Ok result leaves + // the storage untouched, so there is no rollback to do. + switch (applyControlValue(c, valueJson, "value")) { + case ApplyResult::Ok: break; + case ApplyResult::OutOfRange: return SetControlResult::OutOfRange; + case ApplyResult::Malformed: return SetControlResult::Malformed; + case ApplyResult::ReadOnly: return SetControlResult::ReadOnly; + } + // Rebuild the control list so onBuildControls() re-evaluates conditional visibility + // for the new value; fire the three-tier change reaction (onUpdate always, a + // tree-wide buildState only when the control reshapes dims/mapping); persist. + target->rebuildControls(); + target->onUpdate(controlName); + target->markDirty(); + if (noteDirtyHook_) noteDirtyHook_(); + if (target->controlChangeTriggersBuildState(controlName)) buildState(); + return SetControlResult::Ok; + } + return SetControlResult::ControlNotFound; +} + void Scheduler::walkAndEnsureUnique(MoonModule* mod) { if (!mod) return; ensureUniqueName(mod); diff --git a/src/core/Scheduler.h b/src/core/Scheduler.h index f23d0e74..c89c7480 100644 --- a/src/core/Scheduler.h +++ b/src/core/Scheduler.h @@ -54,6 +54,12 @@ class Scheduler { using LoadAllFn = void(*)(Scheduler*); void setLoadAllHook(LoadAllFn fn) { loadAllHook_ = fn; } + /// Hook invoked after a control mutation so the persistence layer can schedule a + /// debounced save (FilesystemModule::noteDirty). Same decoupling as setLoadAllHook — + /// Scheduler stays independent of FilesystemModule's type. No-op if unset. + using NoteDirtyFn = void(*)(); + void setNoteDirtyHook(NoteDirtyFn fn) { noteDirtyHook_ = fn; } + void addModule(MoonModule* mod); void setup(); void tick(); @@ -83,13 +89,43 @@ class Scheduler { /// First module in tree-walk order with this name, or nullptr if none. MoonModule* firstByName(const char* name); + /// The single live Scheduler, or nullptr before setup() / after teardown(). Mirrors + /// FilesystemModule::instance_ — the one Scheduler is statically reachable so a module + /// created by the factory (IrModule) can call setControl() without a per-module injection. + static Scheduler* instance() { return instance_; } + + /// Outcome of setControl — the generic control-set primitive's result. Transport + /// layers map these to their own status (HTTP → 404 / 400 / 409, …). + enum class SetControlResult : uint8_t { + Ok, + ModuleNotFound, ///< no module with that name in the tree + ControlNotFound, ///< module exists but has no such control + OutOfRange, ///< numeric value outside the control's bounds + Malformed, ///< value didn't parse + ReadOnly, ///< tried to write a display-only control + }; + + /// Set one control by (module name, control name) to a value, applying the full + /// control-change reaction: parse+validate, rebuild the module's control list, fire + /// onUpdate, mark dirty for persistence, and buildState() when the control reshapes + /// dims/mapping. `valueJson` is a small JSON object read for its "value" key + /// (`{"value":128}`) — the same shape /api/control, Improv, and the WLED bridge send. + /// This is THE domain-neutral way for any module (IR, buttons, network bridges) to + /// drive another module's control: they compose against this one primitive instead of + /// reaching into a target's internals. The special control name "enabled" toggles the + /// module's enabled flag. Returns the outcome; a transport maps it to its status codes. + SetControlResult setControl(const char* moduleName, const char* controlName, + const char* valueJson); + private: void walkAndEnsureUnique(MoonModule* mod); static MoonModule* firstInTree(MoonModule* mod, const char* name); + static inline Scheduler* instance_ = nullptr; std::array modules_{}; uint8_t moduleCount_ = 0; LoadAllFn loadAllHook_ = nullptr; + NoteDirtyFn noteDirtyHook_ = nullptr; uint32_t startTime_ = 0; uint32_t lastLoop20ms_ = 0; uint32_t lastLoop1s_ = 0; diff --git a/src/light/WLEDAudioSyncPacket.h b/src/light/WLEDAudioSyncPacket.h index 64b3bb63..48b98850 100644 --- a/src/light/WLEDAudioSyncPacket.h +++ b/src/light/WLEDAudioSyncPacket.h @@ -46,6 +46,16 @@ constexpr size_t WLED_SYNC_NUM_BANDS = 16; // == AudioFrame bands + WLED NUM inline void wledPutFloatLE(uint8_t* p, float v) { std::memcpy(p, &v, 4); } inline float wledGetFloatLE(const uint8_t* p) { float v; std::memcpy(&v, p, 4); return v; } +// Truncate a wire float into an AudioFrame uint16 field, bounded to [0, 65535]. +// A foreign/garbage packet that passed the header check can carry NaN or an +// out-of-range magnitude; casting such a float straight to uint16_t is undefined, +// so clamp first. NaN fails both comparisons and falls through to 0. +inline uint16_t wledFloatToU16(float v) { + if (!(v > 0.0f)) return 0; // <= 0 or NaN + if (v > 65535.0f) return 65535; + return static_cast(v); +} + // Build a v2 audio-sync packet from an AudioFrame into out (>= 44 bytes). // `frameCounter` and `peak` are the two fields not carried by AudioFrame (the // caller owns the send counter and the beat flag). Returns the packet size (44). @@ -68,16 +78,18 @@ inline size_t buildWledAudioSync(uint8_t out[WLED_SYNC_PACKET_SIZE], const Audio // the "00002" header. A v1 (83-byte "00001") packet, a short/foreign datagram, or // a null buffer returns false so the caller ignores it (be strict on our own // format, drop everything else — never crash). The float level/peak fields are -// rounded back into the AudioFrame's small-integer fields. +// truncated into the AudioFrame's small-integer fields, bounded to [0, 65535] +// (wledFloatToU16) so a foreign packet carrying NaN or an out-of-range value +// can't produce an undefined conversion. inline bool parseWledAudioSync(const uint8_t* pkt, size_t len, AudioFrame& out) { if (!pkt || len != WLED_SYNC_PACKET_SIZE) return false; if (std::memcmp(pkt, WLED_SYNC_HEADER, 6) != 0) return false; - out.level = static_cast(wledGetFloatLE(pkt + 8)); - out.levelSmoothed = static_cast(wledGetFloatLE(pkt + 12)); + out.level = wledFloatToU16(wledGetFloatLE(pkt + 8)); + out.levelSmoothed = wledFloatToU16(wledGetFloatLE(pkt + 12)); // pkt[16] samplePeak and pkt[17] frameCounter are hints the AudioFrame doesn't carry. std::memcpy(out.bands, pkt + 18, WLED_SYNC_NUM_BANDS); - out.peakMag = static_cast(wledGetFloatLE(pkt + 36)); - out.peakHz = static_cast(wledGetFloatLE(pkt + 40)); + out.peakMag = wledFloatToU16(wledGetFloatLE(pkt + 36)); + out.peakHz = wledFloatToU16(wledGetFloatLE(pkt + 40)); return true; } diff --git a/src/main.cpp b/src/main.cpp index 33389fc2..5788118e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,6 +97,7 @@ #include "core/SystemModule.h" #include "core/AudioModule.h" #include "core/I2cScanModule.h" +#include "core/IrModule.h" #include "core/FirmwareUpdateModule.h" #include "core/ImprovProvisioningModule.h" #include "core/DevicesModule.h" @@ -210,6 +211,7 @@ static void registerModuleTypes() { mm::ModuleFactory::registerType("SystemModule", "core/SystemModule.md"); mm::ModuleFactory::registerType("AudioModule", "core/AudioModule.md"); mm::ModuleFactory::registerType("I2cScanModule", "core/I2cScanModule.md"); + mm::ModuleFactory::registerType("IrModule", "core/IrModule.md"); mm::ModuleFactory::registerType("FirmwareUpdateModule", "core/FirmwareUpdateModule.md"); mm::ModuleFactory::registerType("ImprovProvisioningModule", "core/ImprovProvisioningModule.md"); mm::ModuleFactory::registerType("DevicesModule", "core/DevicesModule.md"); diff --git a/src/platform/desktop/platform_desktop.cpp b/src/platform/desktop/platform_desktop.cpp index 6f58fb5f..1a5dc347 100644 --- a/src/platform/desktop/platform_desktop.cpp +++ b/src/platform/desktop/platform_desktop.cpp @@ -731,6 +731,8 @@ int TcpConnection::read(uint8_t* buf, size_t maxLen) { bool TcpConnection::write(const uint8_t* data, size_t len) { if (fd_ < 0) return false; + // Send ALL bytes (blocking retry on a full buffer) — an HTTP response must arrive complete. + // See the ESP32 impl: a healthy interface drains in microseconds so the retry rarely spins. size_t sent = 0; while (sent < len) { auto n = ::send(sock(fd_), reinterpret_cast(data + sent), @@ -955,4 +957,8 @@ size_t i2cScan(uint16_t /*sda*/, uint16_t /*scl*/, uint8_t* /*out*/, size_t /*ma return kI2cBusUnavailable; } +// No IR receiver on the host — the seam is a no-op so IrModule runs (its buttons still work +// through Scheduler::setControl); reception is ESP32-only. +bool irRead(uint16_t /*pin*/, uint32_t& /*codeOut*/) { return false; } + } // namespace mm::platform diff --git a/src/platform/esp32/platform_esp32.cpp b/src/platform/esp32/platform_esp32.cpp index fee7d1e4..55eaee14 100644 --- a/src/platform/esp32/platform_esp32.cpp +++ b/src/platform/esp32/platform_esp32.cpp @@ -612,7 +612,21 @@ static bool ethInitSpi() { devcfg.queue_size = 20; eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(kSpiHost, &devcfg); - w5500_config.int_gpio_num = ethConfig_.spiIrq; // -1 → W5500 driver falls back to polling + w5500_config.int_gpio_num = ethConfig_.spiIrq; // wired INT pin (interrupt), or -1 for polling + if (ethConfig_.spiIrq >= 0) { + // Interrupt-driven RX: the W5500 driver registers its handler with gpio_isr_handler_add(), + // which requires the per-pin ISR service to be installed first. Install it once here; + // ESP_ERR_INVALID_STATE means another driver already installed it, which is fine. + esp_err_t isr = gpio_install_isr_service(0); + if (isr != ESP_OK && isr != ESP_ERR_INVALID_STATE) { + ESP_LOGW(NET_TAG, "gpio_install_isr_service failed (%s) — W5500 INT may not fire", + esp_err_to_name(isr)); + } + } else { + // No INT pin: IDF v6's W5500 driver requires a poll period when int_gpio_num < 0, so drive + // the MAC by polling — 10 ms services RX promptly without an interrupt. + w5500_config.poll_period_ms = 10; + } eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); @@ -1339,6 +1353,9 @@ int TcpConnection::read(uint8_t* buf, size_t maxLen) { bool TcpConnection::write(const uint8_t* data, size_t len) { if (fd_ < 0) return false; + // Send every byte, retrying on a full send buffer — an HTTP response must arrive complete. + // Blocks the caller until the peer drains, which suits a one-shot response on a per-request + // connection; a healthy interface drains in microseconds, so the retry rarely spins. size_t sent = 0; while (sent < len) { auto n = lwip_write(fd_, data + sent, len - sent); @@ -1427,4 +1444,6 @@ void TcpServer::close() { } } +// irRead (IR receive) lives in platform_esp32_ir.cpp — an RMT NEC decoder. + } // namespace mm::platform diff --git a/src/platform/esp32/platform_esp32_ir.cpp b/src/platform/esp32/platform_esp32_ir.cpp new file mode 100644 index 00000000..cd4e6c4d --- /dev/null +++ b/src/platform/esp32/platform_esp32_ir.cpp @@ -0,0 +1,131 @@ +// IR receive — the platform::irRead seam (declared in platform.h), decoding the NEC remote +// protocol off the RMT RX peripheral. A persistent RX channel runs on the IR pin; its done ISR +// only records how many symbols arrived and signals a queue (ISR-minimal: no decode, no driver +// call in interrupt context — the same discipline as rmtWs2812RxCapture). irRead(), on the render +// task, drains that signal non-blocking, decodes the captured symbols, and re-arms the channel. +// IrModule is the sole caller. +// +// NEC protocol: a 9 ms lead mark + 4.5 ms space, then 32 bits LSB-first (address, ~address, +// command, ~command), each a 560 µs mark followed by a 560 µs space (0) or a 1690 µs space (1), +// then a final 560 µs stop mark. A repeat frame (9 ms mark + 2.25 ms space) is ignored — we +// surface distinct presses, not auto-repeat. Prior art: the ESP-IDF ir_nec_transceiver example; +// the timing is the published NEC standard. + +#include "platform/platform.h" + +#include "driver/rmt_rx.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "soc/soc_caps.h" // SOC_RMT_MEM_WORDS_PER_CHANNEL + +#include + +namespace mm::platform { + +namespace { + +// NEC timings in µs (RX resolution is 1 µs — see kResolutionHz). ±30 % windows absorb drift. +constexpr uint32_t kLeadMark = 9000; +constexpr uint32_t kLeadSpace = 4500; +constexpr uint32_t kBitMark = 560; +constexpr uint32_t kZeroSpace = 560; +constexpr uint32_t kOneSpace = 1690; +constexpr uint32_t kResolutionHz = 1000000; // 1 tick = 1 µs + +// A symbol duration falls within ±30 % of `target`. Task-context only (called from the decode in +// irRead, never the ISR), so no IRAM requirement. +inline bool nearUs(uint32_t d, uint32_t target) { + const uint32_t tol = target * 3 / 10; + return d + tol >= target && d <= target + tol; +} + +// Decode NEC symbols → 32-bit code. True + code on a well-formed data frame; false on a repeat +// frame or malformed input. Runs on the render task (from irRead), not the ISR. +bool decodeNec(const rmt_symbol_word_t* sym, size_t n, uint32_t& out) { + if (n < 34) return false; // repeat (2 symbols) or truncated + if (!nearUs(sym[0].duration0, kLeadMark)) return false; + if (!nearUs(sym[0].duration1, kLeadSpace)) return false; // 4.5 ms space = data (2.25 = repeat) + uint32_t code = 0; + for (int i = 0; i < 32; i++) { + const rmt_symbol_word_t& s = sym[i + 1]; + if (!nearUs(s.duration0, kBitMark)) return false; + if (nearUs(s.duration1, kOneSpace)) code |= (1u << i); // LSB-first + else if (!nearUs(s.duration1, kZeroSpace)) return false; // neither 0 nor 1 → malformed + } + out = code; + return true; +} + +// Persistent channel + state. Opened lazily on the first irRead for a pin; reopened if the pin +// changes. One IR receiver per device, so a single static channel suffices. +rmt_channel_handle_t rxChan_ = nullptr; +QueueHandle_t doneQueue_ = nullptr; // ISR → task: how many symbols the last frame captured +int currentPin_ = -1; +rmt_symbol_word_t rxBuf_[68]; // NEC = 34 symbols (lead + 32 bits + stop); slack for noise +rmt_receive_config_t rxCfg_ = {}; + +// ISR: record the symbol count and wake the task. No decode, no rmt_* re-arm here — the task +// re-arms after it has copied the buffer, so the DMA target is never overwritten mid-decode. +bool IRAM_ATTR rxDoneCb(rmt_channel_handle_t, const rmt_rx_done_event_data_t* edata, void*) { + size_t n = edata->num_symbols; + BaseType_t high = pdFALSE; + xQueueSendFromISR(doneQueue_, &n, &high); + return high == pdTRUE; +} + +void closeChannel() { + if (rxChan_) { rmt_disable(rxChan_); rmt_del_channel(rxChan_); rxChan_ = nullptr; } + if (doneQueue_) { vQueueDelete(doneQueue_); doneQueue_ = nullptr; } + currentPin_ = -1; +} + +// Arm one receive into rxBuf_. Called from the task (initial arm + re-arm after each frame). +bool arm() { return rmt_receive(rxChan_, rxBuf_, sizeof(rxBuf_), &rxCfg_) == ESP_OK; } + +// (Re)open the RX channel on `pin`. Idempotent for an unchanged pin. Returns true if live after. +bool ensureChannel(int pin) { + if (rxChan_ && currentPin_ == pin) return true; + closeChannel(); + if (pin < 0) return false; + + rmt_rx_channel_config_t cfg = {}; + cfg.gpio_num = static_cast(pin); + cfg.clk_src = RMT_CLK_SRC_DEFAULT; + cfg.resolution_hz = kResolutionHz; + cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; // one hardware block is ample for NEC + if (rmt_new_rx_channel(&cfg, &rxChan_) != ESP_OK) { rxChan_ = nullptr; return false; } + + doneQueue_ = xQueueCreate(1, sizeof(size_t)); + if (!doneQueue_) { rmt_del_channel(rxChan_); rxChan_ = nullptr; return false; } + + rmt_rx_event_callbacks_t cbs = {}; + cbs.on_recv_done = rxDoneCb; + rmt_rx_register_event_callbacks(rxChan_, &cbs, nullptr); + + rxCfg_.signal_range_min_ns = 200; // shorter than a 560 µs mark — filters glitches + rxCfg_.signal_range_max_ns = 12000000; // longer than the 9 ms lead — ends the frame + if (rmt_enable(rxChan_) != ESP_OK || !arm()) { + closeChannel(); + return false; + } + currentPin_ = pin; + return true; +} + +} // namespace + +bool irRead(uint16_t pin, uint32_t& codeOut) { + if (!ensureChannel(static_cast(pin))) return false; + + // Non-blocking: did the ISR signal a completed frame since the last call? + size_t n = 0; + if (xQueueReceive(doneQueue_, &n, 0) != pdTRUE) return false; + + // Decode the captured buffer (task context), then re-arm for the next frame. Re-arming here — + // not in the ISR — guarantees the decode reads a stable buffer the next capture can't clobber. + const bool ok = decodeNec(rxBuf_, n, codeOut); + arm(); + return ok; +} + +} // namespace mm::platform diff --git a/src/platform/platform.h b/src/platform/platform.h index a9a978f3..8f2ba45a 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -319,7 +319,7 @@ class TcpConnection { bool valid() const { return fd_ >= 0; } int read(uint8_t* buf, size_t maxLen); // non-blocking: >0 data, 0 closed, -1 nothing - bool write(const uint8_t* data, size_t len); // blocking — retries until all sent + bool write(const uint8_t* data, size_t len); // blocking — sends all bytes (HTTP responses must complete) // Non-blocking partial write: send as many of `len` bytes as the socket accepts right // now, return the count actually written (0..len). -1 = socket error (caller closes); // 0 = WouldBlock (buffer full, try later) or len==0. The caller advances its own offset @@ -599,4 +599,12 @@ inline constexpr size_t kI2cBusUnavailable = static_cast(-1); // maxOut), or kI2cBusUnavailable if the bus couldn't be opened. size_t i2cScan(uint16_t sda, uint16_t scl, uint8_t* out, size_t maxOut); +// Poll the IR receiver on `pin` for a decoded remote frame. Returns true and writes the +// frame into `codeOut` when a fresh code is available since the last call, false otherwise +// (nothing received, or IR decode unavailable on this target). Self-contained like i2cScan — +// it owns whatever peripheral it needs (an RMT RX channel on ESP32). Non-blocking: safe to +// call every tick. IrModule is the sole caller. ESP32 decodes NEC over RMT; desktop has no IR +// hardware and always returns false. +bool irRead(uint16_t pin, uint32_t& codeOut); + } // namespace mm::platform diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7035862c..f74bd43a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,8 @@ add_executable(mm_tests unit/core/unit_math8.cpp unit/core/unit_noise.cpp unit/core/unit_crc.cpp + unit/core/unit_AudioModule_sync.cpp + unit/core/unit_IrModule.cpp unit/core/unit_Control_apply_absent_key.cpp unit/core/unit_Control_list.cpp unit/core/unit_DeviceIdentify.cpp diff --git a/test/scenarios/light/scenario_perf_full.json b/test/scenarios/light/scenario_perf_full.json index 35170545..6b81aa2f 100644 --- a/test/scenarios/light/scenario_perf_full.json +++ b/test/scenarios/light/scenario_perf_full.json @@ -1596,7 +1596,7 @@ "observed": { "pc-macos": { "tick_us": [ - 272, + 271, 2452 ], "free_heap": [ diff --git a/test/unit/core/unit_AudioModule_sync.cpp b/test/unit/core/unit_AudioModule_sync.cpp new file mode 100644 index 00000000..a1683f05 --- /dev/null +++ b/test/unit/core/unit_AudioModule_sync.cpp @@ -0,0 +1,176 @@ +// @module AudioModule +// @also WledAudioSyncPacket + +// Drives AudioModule's WLED audio-sync socket lifecycle on the host through the public +// loop() — the same entry the scheduler calls on-device. Covers: lazy open once per mode +// (syncEnsureSocket latches), the send path reaching "sending", send throttling, and the +// receive path over a real localhost UDP round-trip (frame replacement + the fresh→stale +// auto-blend fallback). platform::networkReady() is true on desktop, so the lazy open fires +// on the first tick — mirroring a device once its interface is up. +// +// Time is driven deterministically with platform::setTestNowMs() (the animation-test idiom) +// so the throttle/fallback windows are exact and the suite never sleeps a real second; only +// the actual UDP delivery is real, polled with a bounded retry (the NetworkReceiveEffect +// localhost-round-trip pattern). A test port (not 11988) avoids colliding with a running +// projectMM desktop app that would hold the real sync port. + +#include "doctest.h" +#include "core/AudioModule.h" +#include "light/WLEDAudioSyncPacket.h" +#include "platform/platform.h" + +#include +#include + +using namespace mm; + +namespace { +constexpr uint16_t kTestSyncPort = 21988; // a free high port, not the real 11988 + +// The status read-out is published by loop1s(); call it after a loop() so the assertions +// below see the current state string rather than the setup() baseline. +const char* status(AudioModule& a) { a.loop1s(); return a.syncStatusForTest(); } + +// A guard that freezes virtual time for a case and restores the real clock on scope exit, +// so a thrown assertion can't leak frozen time into the next case. +struct FrozenClock { + FrozenClock(uint32_t ms) { platform::setTestNowMs(ms); } + ~FrozenClock() { platform::setTestNowMs(0); } + void advance(uint32_t ms) { now_ += ms; platform::setTestNowMs(now_); } + uint32_t now_ = 1; +}; +} // namespace + +TEST_CASE("AudioModule sync=Send: lazy-opens once and reports sending") { + FrozenClock clk(1); + AudioModule a; + a.sync = 1; + a.syncPort = kTestSyncPort; + a.setup(); // syncReinit() only — socket NOT opened here (boot-safe) + CHECK(std::strstr(status(a), "waiting") != nullptr); // no loop() yet → still waiting + + a.loop(); // networkReady() true on desktop → opens now + CHECK(std::strcmp(status(a), "sending") == 0); + CHECK(a.syncOpenForTest()); + + // Idempotent: a second tick doesn't re-open (the latch holds). + a.loop(); + CHECK(a.syncOpenForTest()); + CHECK(std::strcmp(status(a), "sending") == 0); + + a.teardown(); + CHECK_FALSE(a.syncOpenForTest()); +} + +TEST_CASE("AudioModule sync=Send: broadcasts are throttled to ~kSyncSendIntervalMs") { + FrozenClock clk(1); + AudioModule a; + a.sync = 1; + a.syncPort = kTestSyncPort; + a.setup(); + a.loop(); // opens + first send (frameCounter bumps once) + REQUIRE(a.syncOpenForTest()); + + // More ticks within the same interval must not each emit — the frame counter (bumped + // only on an actual send) does not advance while the throttle window is open. + const uint8_t c0 = a.syncFrameCounterForTest(); + a.loop(); + a.loop(); + CHECK(a.syncFrameCounterForTest() == c0); // throttled: no send per tick + + // After the interval elapses, exactly one more send is allowed. + clk.advance(AudioModule::syncSendIntervalMsForTest() + 5); + a.loop(); + CHECK((uint8_t)(a.syncFrameCounterForTest() - c0) == 1); + + a.teardown(); +} + +TEST_CASE("AudioModule sync=Receive: a localhost WLED packet drives frame_, then auto-blends back") { + FrozenClock clk(1); + AudioModule a; + a.sync = 2; + a.syncPort = kTestSyncPort; + a.setup(); + a.loop(); // binds kTestSyncPort + REQUIRE(a.syncOpenForTest()); + CHECK(std::strcmp(status(a), "listening") == 0); + + // Send a real WLED v2 packet to the bound port over loopback. + AudioFrame peer; + peer.level = 222; peer.levelSmoothed = 111; peer.peakHz = 660; peer.peakMag = 55; + for (int i = 0; i < 16; i++) peer.bands[i] = static_cast(i * 8); + uint8_t pkt[WLED_SYNC_PACKET_SIZE]; + buildWledAudioSync(pkt, peer, /*frameCounter=*/1, /*peak=*/false); + + platform::UdpSocket tx; + REQUIRE(tx.open()); + REQUIRE(tx.connect("127.0.0.1", kTestSyncPort)); + REQUIRE(tx.sendTo(pkt, WLED_SYNC_PACKET_SIZE)); + + // Loopback delivery is async in real time — poll loop() until the peer frame lands + // (bounded, ≤100 iterations). Virtual time stays frozen, so the frame counts as fresh. + bool landed = false; + for (int i = 0; i < 100 && !landed; i++) { + a.loop(); + landed = a.audioFrame()->level == 222 && a.audioFrame()->peakHz == 660; + if (!landed) platform::delayMs(1); // real wait for the datagram, not virtual time + } + CHECK(landed); + CHECK(a.audioFrame()->levelSmoothed == 111); + CHECK(std::strcmp(status(a), "receiving") == 0); // fresh peer audio + + // Auto-blend: advance virtual time past the fallback window with no new packet — the + // peer goes stale and the status falls back to "listening" (the local mic resumes + // on-device). Deterministic: no real sleep. + clk.advance(AudioModule::syncFallbackMsForTest() + 20); + a.loop(); + CHECK(std::strcmp(status(a), "listening") == 0); + + tx.close(); + a.teardown(); +} + +TEST_CASE("AudioModule sync=Receive: a failed bind backs off instead of retrying every tick") { + FrozenClock clk(1); + // Hold the port with an external socket so the module's bind() fails. + platform::UdpSocket hog; + REQUIRE(hog.open()); + REQUIRE(hog.bind(kTestSyncPort)); + + AudioModule a; + a.sync = 2; + a.syncPort = kTestSyncPort; + a.setup(); + a.loop(); // first bring-up attempt → bind fails + CHECK_FALSE(a.syncOpenForTest()); + CHECK(std::strcmp(status(a), "receive: bind failed") == 0); + + // Within the backoff window, further ticks must NOT retry — the socket stays closed and + // the status is unchanged (no per-tick socket() churn). loop1s() only reasserts the + // baseline while syncOpen_ is false, so the string staying put is the observable proof. + a.loop(); + a.loop(); + CHECK_FALSE(a.syncOpenForTest()); + CHECK(std::strcmp(a.syncStatusForTest(), "receive: bind failed") == 0); + + // Release the port and advance past the backoff — the next tick retries and succeeds. + hog.close(); + clk.advance(AudioModule::syncOpenRetryMsForTest() + 5); + a.loop(); + CHECK(a.syncOpenForTest()); + CHECK(std::strcmp(status(a), "listening") == 0); + + a.teardown(); +} + +TEST_CASE("AudioModule sync=Off: no socket, reports off") { + FrozenClock clk(1); + AudioModule a; + a.sync = 0; + a.setup(); + a.loop(); + CHECK_FALSE(a.syncOpenForTest()); + CHECK(std::strcmp(status(a), "off") == 0); + a.teardown(); +} diff --git a/test/unit/core/unit_IrModule.cpp b/test/unit/core/unit_IrModule.cpp new file mode 100644 index 00000000..649fe9ec --- /dev/null +++ b/test/unit/core/unit_IrModule.cpp @@ -0,0 +1,174 @@ +// @module IrModule +// @also Scheduler + +// Pins IrModule's action path: its buttons adjust another module's control through the shared +// Scheduler::setControl primitive, clamped to the control's own bounds. A fake "Drivers" module +// (a Knob with brightness + palette controls) stands in for the real one, so the test needs no +// light-domain modules — it proves the generic relative-adjust behaviour in isolation. The IR +// *reception* side is a platform stub (irRead returns false), so this focuses on the action +// path, which is the working, testable part of this cut. + +#include "doctest.h" +#include "core/IrModule.h" +#include "core/Scheduler.h" +#include "core/MoonModule.h" + +#include + +using namespace mm; + +namespace { + +// Stands in for Drivers: a brightness Uint8 (0–255) and a palette Select (0–3). Named "Drivers" +// so IrModule's kActions ("Drivers"/"brightness", "Drivers"/"palette") resolve to it. +struct FakeDrivers : public MoonModule { + uint8_t brightness = 100; + uint8_t palette = 1; + void onBuildControls() override { + controls_.addUint8("brightness", brightness, 0, 255); + // A Select's max is (optionCount - 1); addSelect binds min 0 / max count-1. + static const char* kPalettes[] = {"A", "B", "C", "D"}; + controls_.addSelect("palette", palette, kPalettes, 4); + } +}; + +// Build Scheduler + FakeDrivers + IrModule, run setup so Scheduler::instance() is live and +// controls are bound. Caller owns teardown via the scheduler (modules are heap-allocated). +struct Rig { + Scheduler scheduler; + FakeDrivers* drivers = new FakeDrivers(); + IrModule* ir = new IrModule(); + Rig() { + drivers->setName("Drivers"); + ir->setName("Ir"); + scheduler.addModule(drivers); + scheduler.addModule(ir); + scheduler.setup(); // binds controls + sets Scheduler::instance() + } + ~Rig() { scheduler.teardown(); } + // Learn `code` to the action at `learnIndex` (1-based: 1=brightness up, 2=down, 3=palette + // next, 4=prev), then that code drives the action on later inject. Mirrors the on-device flow: + // arm the learn select → the next received code binds → subsequent codes fire the action. + void learn(int learnIndex, uint32_t code) { + char v[24]; std::snprintf(v, sizeof(v), "{\"value\":%d}", learnIndex); + Scheduler::instance()->setControl("Ir", "learn", v); + ir->injectCodeForTest(code); // binds + disarms + } + void fire(uint32_t code) { ir->injectCodeForTest(code); } // a received code drives its action +}; + +} // namespace + +TEST_CASE("IrModule: a learned code adjusts Drivers brightness by the step") { + Rig r; + r.drivers->brightness = 100; + r.learn(1, 0xB1); // bind code 0xB1 → brightness up + r.learn(2, 0xB2); // bind code 0xB2 → brightness down + r.fire(0xB1); + CHECK(r.drivers->brightness == 116); // +16 + r.fire(0xB2); + r.fire(0xB2); + CHECK(r.drivers->brightness == 84); // 116 - 32 +} + +TEST_CASE("IrModule: firing a learned code reports what it changed via status") { + Rig r; + r.drivers->brightness = 100; + // Before setup drives anything, the pin is unset in the rig → readiness warns to set it. + CHECK(std::strstr(r.ir->status(), "set pin") != nullptr); + r.learn(1, 0xB1); + r.fire(0xB1); + // The action acknowledges the change it made, naming the target control + new value. + CHECK(std::strcmp(r.ir->status(), "Drivers.brightness → 116") == 0); +} + +TEST_CASE("IrModule: pin state drives readiness status") { + Rig r; + CHECK(std::strstr(r.ir->status(), "set pin") != nullptr); // unset pin → warns to set it + // Set a valid pin through the real path; onBuildState() re-reports readiness. + Scheduler::instance()->setControl("Ir", "pin", "{\"value\":5}"); + CHECK(std::strcmp(r.ir->status(), "ready") == 0); + // Back to unset → warns again. + Scheduler::instance()->setControl("Ir", "pin", "{\"value\":-1}"); + CHECK(std::strstr(r.ir->status(), "set pin") != nullptr); +} + +TEST_CASE("IrModule: a learned brightness code clamps at 0 and 255") { + Rig r; + r.learn(1, 0xB1); // brightness up + r.learn(2, 0xB2); // brightness down + r.drivers->brightness = 250; + r.fire(0xB1); + CHECK(r.drivers->brightness == 255); // 250+16 clamps to 255, not wrap + r.drivers->brightness = 8; + r.fire(0xB2); + CHECK(r.drivers->brightness == 0); // 8-16 clamps to 0, not underflow +} + +TEST_CASE("IrModule: learned palette codes step the Select and clamp at the ends") { + Rig r; + r.learn(3, 0xB3); // palette next + r.learn(4, 0xB4); // palette prev + r.drivers->palette = 1; + r.fire(0xB3); + CHECK(r.drivers->palette == 2); + r.fire(0xB4); + r.fire(0xB4); + CHECK(r.drivers->palette == 0); // 2 → 1 → 0 + r.fire(0xB4); + CHECK(r.drivers->palette == 0); // clamps at the low end (options 0..3) + r.drivers->palette = 3; + r.fire(0xB3); + CHECK(r.drivers->palette == 3); // clamps at the high end +} + +TEST_CASE("IrModule: learn binds a code to an action, then that code drives it") { + Rig r; + r.drivers->brightness = 100; + // Arm learning for "brightness up" (learn select index 1 = first action). + Scheduler::instance()->setControl("Ir", "learn", "{\"value\":1}"); + // A code arrives → it binds to "brightness up" and learning disarms; brightness unchanged yet. + r.ir->injectCodeForTest(0xFA057F80); + CHECK(r.drivers->brightness == 100); + CHECK(std::strstr(r.ir->status(), "learned brightness up") != nullptr); + // The SAME code now drives the bound action. + r.ir->injectCodeForTest(0xFA057F80); + CHECK(r.drivers->brightness == 116); + r.ir->injectCodeForTest(0xFA057F80); + CHECK(r.drivers->brightness == 132); +} + +TEST_CASE("IrModule: an unlearned code is reported as unassigned, drives nothing") { + Rig r; + r.drivers->brightness = 100; + r.ir->injectCodeForTest(0xDEADBEEF); // never learned + CHECK(r.drivers->brightness == 100); + CHECK(std::strstr(r.ir->status(), "unassigned") != nullptr); + CHECK(r.ir->latestCode() == 0xDEADBEEF); +} + +TEST_CASE("IrModule: two codes bind to two actions independently") { + Rig r; + r.drivers->brightness = 100; + r.drivers->palette = 1; + r.learn(1, 0x11111111); // brightness up + r.learn(3, 0x22222222); // palette next + // Each code drives only its own action. + r.fire(0x11111111); + CHECK(r.drivers->brightness == 116); + CHECK(r.drivers->palette == 1); // unchanged + r.fire(0x22222222); + CHECK(r.drivers->palette == 2); + CHECK(r.drivers->brightness == 116); // unchanged +} + +TEST_CASE("IrModule: an unassigned code and a missing target are no-ops, not crashes") { + Rig r; + const uint8_t before = r.drivers->brightness; + r.fire(0xABCDEF01); // no learned binding → nothing happens + CHECK(r.drivers->brightness == before); + // The code is still recorded (for the read-out) even though it drove no action. + CHECK(r.ir->latestCode() == 0xABCDEF01); + // Robustness: the path tolerates any input without crashing (setControl returns a typed + // not-found rather than dereferencing null), and an unassigned code is simply ignored. +} diff --git a/test/unit/light/unit_WledAudioSyncPacket.cpp b/test/unit/light/unit_WledAudioSyncPacket.cpp index f4e6ec2a..d1b08b04 100644 --- a/test/unit/light/unit_WledAudioSyncPacket.cpp +++ b/test/unit/light/unit_WledAudioSyncPacket.cpp @@ -11,6 +11,7 @@ #include #include +#include using namespace mm; @@ -89,6 +90,36 @@ TEST_CASE("parse rejects wrong length, wrong header, v1, and null") { CHECK_FALSE(parseWledAudioSync(nullptr, 44, out)); } +TEST_CASE("parse clamps NaN / out-of-range floats instead of undefined casts") { + // A foreign packet passes the header check but carries hostile float payloads. + // wledFloatToU16 must bound them to [0, 65535] rather than let static_cast + // of a NaN / huge / negative float produce an undefined result. + AudioFrame f = sampleFrame(); + uint8_t pkt[WLED_SYNC_PACKET_SIZE]; + buildWledAudioSync(pkt, f, 0, false); + + const float nan = std::numeric_limits::quiet_NaN(); + wledPutFloatLE(pkt + 8, nan); // level → NaN + wledPutFloatLE(pkt + 12, -5.0f); // levelSmoothed → negative + wledPutFloatLE(pkt + 36, 1e9f); // peakMag → far over 65535 + wledPutFloatLE(pkt + 40, 440.6f); // peakHz → normal, truncates to 440 + + AudioFrame out; + REQUIRE(parseWledAudioSync(pkt, WLED_SYNC_PACKET_SIZE, out)); + CHECK(out.level == 0); // NaN → 0 + CHECK(out.levelSmoothed == 0); // negative → 0 + CHECK(out.peakMag == 65535); // clamped to the u16 ceiling + CHECK(out.peakHz == 440); // in range → truncated + + // The helper directly, at the boundaries. + CHECK(wledFloatToU16(nan) == 0); + CHECK(wledFloatToU16(-1.0f) == 0); + CHECK(wledFloatToU16(0.0f) == 0); + CHECK(wledFloatToU16(65535.0f) == 65535); + CHECK(wledFloatToU16(70000.0f) == 65535); + CHECK(wledFloatToU16(123.9f) == 123); +} + TEST_CASE("golden vector — the exact bytes on the wire (the compatibility contract)") { // A fixed frame → the exact 44 bytes any WLED receiver must accept. If this changes, // interop with WLED/MoonLight breaks — the test is the alarm. diff --git a/web-installer/deviceModels.json b/web-installer/deviceModels.json index f3c41251..93f9807d 100644 --- a/web-installer/deviceModels.json +++ b/web-installer/deviceModels.json @@ -711,13 +711,16 @@ "esp32s3-n8r8" ], "image": "assets/boards/lightcrafter-16.jpg", - "url": "https://lightcrafter.eu/", + "url": "https://www.limpkin.fr/", "supported": [ "LEDs", - "Ethernet" + "WiFi", + "Ethernet", + "IR" ], "planned": [ - "16-ch parallel LEDs (I2S, not yet)" + "16-ch parallel LEDs (I2S, not yet)", + "Power monitoring" ], "modules": [ { @@ -741,7 +744,16 @@ "ethSpiMosi": 11, "ethSpiSck": 12, "ethSpiCs": 10, - "ethSpiIrq": 45 + "ethSpiIrq": 45, + "ethRstGpio": 3 + } + }, + { + "type": "IrModule", + "id": "Ir", + "parent_id": "System", + "controls": { + "pin": 4 } } ] @@ -753,12 +765,16 @@ "esp32s3-n8r8" ], "image": "assets/boards/esp32-s3-stephanelec-16p.jpg", + "url": "https://www.limpkin.fr/", "supported": [ "LEDs", - "Ethernet" + "WiFi", + "Ethernet", + "IR" ], "planned": [ - "16-ch parallel LEDs (I2S, not yet)" + "16-ch parallel LEDs (I2S, not yet)", + "Power monitoring" ], "modules": [ { @@ -784,6 +800,14 @@ "ethSpiCs": 15, "ethSpiIrq": 18 } + }, + { + "type": "IrModule", + "id": "Ir", + "parent_id": "System", + "controls": { + "pin": 5 + } } ] }, From 0054eb9eb5d7b76ce81b3ae49913014b8f75a417 Mon Sep 17 00:00:00 2001 From: ewowi Date: Sat, 4 Jul 2026 13:14:00 +0200 Subject: [PATCH 3/3] Process CodeRabbit findings on PR #35 + pre-merge docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes four CodeRabbit findings on the IR / audio-sync branch (a lost learned-IR binding, a silently-dead IR receiver on re-arm failure, a frozen level read-out under audio sync, and an untested code path) and carries the branch's hard-won lessons into decisions.md before the merge. Core: - IrModule: 🐇 a learned IR binding now schedules the debounced save (FilesystemModule::noteDirty alongside markDirty) — a bound code is written straight to the persistent Text control, not via setControl, so it must trigger persistence itself or the binding could be lost before an unrelated save runs. Dropped a stray markDirty on the transient unassigned-code status path (nothing persistent changes there). - AudioModule: 🐇 syncReceive now feeds the received peer level into the same 1 s peak window the local mic uses, so the "level RMS" read-out reflects peer audio instead of freezing at the last local value while a peer drives the effects. - platform_esp32_ir: 🐇 irRead checks the re-arm result after a decode; on failure it tears the RX channel down (closeChannel) and returns false, so ensureChannel reopens a fresh channel next call rather than leaving the receiver enabled-but-not-armed (silently dead) forever. Tests: - unit_IrModule: 🐇 added a missing-target case — learn a code, rename the target module away, fire the code, and assert the "no Drivers module" status + no crash + unchanged state (the runAction not-found branch was untested; the prior case only covered an unassigned code). Docs / CI: - decisions.md: carried forward this branch's lessons — W5500 SPI bring-up (the ISR service is required for interrupt mode, IDF v6's poll-period pairing, a failed eth init masking the bug behind a WiFi fallback, and a 0x00 "version mismatched" read meaning the chip is held in reset → a per-board reset-release pin); the Scheduler::setControl extraction (put the generic control-set primitive where the tree owner is, not per-input seams); and the RMT/IR ISR-safety rule (signal only in the ISR, decode + re-arm on the task). - backlog-core.md: noted the two module-by-name tree-walkers (HttpServer::findModuleByName vs Scheduler::firstByName) as a consolidation candidate now that firstByName is public (Reviewer finding, deferred). - ui.md: AudioModule entry updated with the sync (Off/Send/Receive), syncPort, mclkPin (line-in), floor/gain controls that the audio-sync feature added (doc-sync gate). Reviews: - 🐇 IrModule learn-path persistence — fixed (noteDirty added). - 🐇 IrModule irRead re-arm failure — fixed (closeChannel + return false on arm() failure). - 🐇 AudioModule level read-out under sync — fixed (levelPeak_ fed from the peer frame). - 🐇 IrModule missing-target test coverage — fixed (new test case). - 🐇 main.cpp IrModule doc path → core/moxygen/IrModule.md — accepted as-is: every core module registers "core/.md" (not the moxygen subpath), and the UI resolves that to the built .html; changing only IrModule would make it the odd one out and break the help link. - 👾 Reviewer (whole-branch, pre-merge): architecturally clean, merge-ready, no must-fix; the one nice-to-have (tree-walker consolidation) recorded in backlog-core.md. Co-Authored-By: Claude Opus 4.8 --- docs/backlog/backlog-core.md | 4 ++++ docs/history/decisions.md | 22 ++++++++++++++++++++++ docs/moonmodules/core/ui/ui.md | 11 +++++++---- src/core/AudioModule.h | 5 +++++ src/core/IrModule.h | 13 +++++++++---- src/platform/esp32/platform_esp32_ir.cpp | 8 +++++++- test/unit/core/unit_IrModule.cpp | 15 ++++++++++++--- 7 files changed, 66 insertions(+), 12 deletions(-) diff --git a/docs/backlog/backlog-core.md b/docs/backlog/backlog-core.md index 87f96757..b8b5d696 100644 --- a/docs/backlog/backlog-core.md +++ b/docs/backlog/backlog-core.md @@ -150,6 +150,10 @@ No FreeRTOS tasks are pinned today. At 16K LEDs the render task takes ~52 ms/tic ## Architecture +### Consolidate the two module-by-name tree-walkers (backlog) + +`HttpServerModule::findModuleByName` (`findInTree` recursion) and `Scheduler::firstByName` (`firstInTree`) are two implementations of the same "find the first module in tree-walk order with this name" operation. The duplication predates the `setControl`-to-Scheduler extraction, but that extraction made `Scheduler::firstByName` public *and* added `Scheduler::instance()`, so HttpServer no longer needs its own copy: its ~9 remaining call sites (identify, addModule, removeModule, clearChildren, the WLED/system shims) can call `scheduler_->firstByName()` and the private `findModuleByName`/`findInTree` pair deletes. Purely a *No duplication* cleanup — no behaviour change — worth doing so the walk order/semantics live in exactly one place. (Reviewer note, IrModule/setControl branch.) + ### Disabling a module should release its resources, not just stop its loop (backlog) Today `setEnabled(false)` only makes the Scheduler skip the module's `loop`/`loop1s`/`loop20ms` callbacks (gated via `respectsEnabled()`/`enabled()` in `MoonModule`/`Scheduler`). The module still **holds whatever it acquired**: AudioModule keeps its I2S channel open, an LED driver keeps its RMT/LCD/Parlio peripheral + DMA buffers, NetworkSendDriver keeps its socket. So a disabled module stops *acting* but doesn't *free* — which is fine for a quick mute (a non-ticking module can't pollute a perf measurement, the use case that surfaced this), but wrong if "disabled" should mean "give the pins/peripheral/memory back so another module can use them, or so a mic-less reconfig works." The mechanism for this already exists — `MoonModule::onEnabledChanged()` (a no-op hook today) is exactly where a module should deinit/reinit its resource on the flip. Work: audit every resource-holding module (AudioModule, the LED drivers, NetworkSend/Receive, anything with a socket/peripheral/large buffer) and implement `onEnabledChanged()` to release on disable + re-acquire on enable, mirroring what `setup()`/`teardown()` do. Decide the contract: does disable free the buffer (cheaper RAM, slower re-enable) or keep it (instant re-enable, holds RAM)? Probably per-module. Pin controls becoming the standard `Pin` type (just landed) is a related enabler — a disabled driver releasing its pins lets the same GPIO be reassigned live. diff --git a/docs/history/decisions.md b/docs/history/decisions.md index ea56101a..3a46b6e7 100644 --- a/docs/history/decisions.md +++ b/docs/history/decisions.md @@ -815,3 +815,25 @@ Bringing up the S31's on-chip 1 Gb RGMII Ethernet took four fixes, and none of t - **A shared clock is a contended resource — the RGMII 125 MHz Tx clock can't come from a PLL that's already spoken for.** The default (AUTO) sourced the Tx clock from the MPLL, but PSRAM already ran the MPLL at 400 MHz (no integer path to 125), and CPLL couldn't synthesise 125 MHz on the 40 MHz XTAL grid either. Only the *fractional* APLL (built for exact frequencies) works. The lesson beyond Ethernet: on an SoC where one PLL feeds multiple peripherals, "pick a clock source" is a *conflict-resolution* decision, not a default — check what already owns each PLL before claiming one. - **Changing a Kconfig *choice* in a defaults fragment needs a clean build.** Editing `CONFIG_ETH_EMAC_RGMII_TX_CLK_SRC_*` in the fragment and doing an incremental build silently kept the *old* choice (the build dir's `sdkconfig` already had a value; defaults don't override an existing one). Two flash cycles were spent "testing APLL" that were actually still CPLL. When a sdkconfig *choice* (not a plain `=y`) changes, `rm -rf` the build dir or the test is a lie. + +## W5500 SPI Ethernet (LightCrafter / SE16): interrupt service, IDF-v6 config, and a per-board reset pin + +Bringing up the two Limpkin ESP32-S3 boards' external W5500 (SPI) Ethernet surfaced three traps between "the driver installs" and "the link is up" — each far from its cause, each a general rule. + +- **Interrupt mode needs `gpio_install_isr_service()` — miss it and the IRQ silently degrades to slow polling, not an error.** The device model gives the W5500 a real INT pin (SE16 GPIO18, LightCrafter GPIO45, per the board schematics). But the IDF W5500 driver registers its handler with `gpio_isr_handler_add()`, which requires the per-pin ISR service already installed — and nothing in the platform layer installed it. The `add` failed with a one-line `E gpio: GPIO isr service is not installed`, the interrupt never fired, and RX was serviced only on the driver's slow fallback → a ~1 s *sawtooth* ping latency (packets serviced in descending batches, the poll-cycle signature). The symptom read like a *network* problem; the cause was a missing one-liner. Rule: a driver that takes a GPIO interrupt needs the ISR service installed first — do it once before the driver init (`ESP_ERR_INVALID_STATE` = already installed = fine). The `int_gpio=-1` polling fallback is a *workaround* that hides this, not the fix. + +- **IDF v6's W5500 driver rejects `int_gpio_num < 0` unless a poll period is also set.** The "no interrupt" fallback isn't just `int_gpio_num = -1` — that alone returns `esp_eth_mac_new_w5500(): invalid configuration argument combination` and the whole eth init fails, cascading the board to WiFi. You must *also* set `w5500_config.poll_period_ms` (e.g. 10). The pair is the API contract; half of it is a config error. + +- **A failed Ethernet init that cascades to WiFi *masks* the eth bug — "why are we on WiFi?" is the tell.** Both bugs above manifested as the board quietly running on WiFi with a healthy HTTP server and no eth log at the default verbosity. The robustness cascade (eth fails → WiFi) is correct behaviour, but it turns a loud init failure into a silent capability loss. When a board that *should* be on Ethernet is on WiFi, that mismatch is the signal to read the eth init log, not a benign state. + +- **A `0x00` "version mismatched" read from an SPI PHY means the chip is held in reset — check for a board reset-release pin.** The LightCrafter's WIZ850IO module holds its own `nRST` until GPIO3 is driven HIGH (it also gates RS485_DE / VBUS_DET on that board). Without releasing it, the ESP32 read `0x00` from the W5500's version register (`expected 0x04, got 0x00`) → "verify chip ID failed". The SE16's W5500 self-resets, so it needed no such pin — which is exactly why this was board-specific and easy to miss. Setting `ethRstGpio` (→ the IDF PHY driver's `reset_gpio_num`, which runs the assert-low/release-high sequence) fixed it. Rule: an all-zeros register read from an SPI peripheral is "the chip isn't talking", and the first suspect after wiring is a reset/enable line the board expects the host to drive. + +## Extract the generic "set a control" primitive to the Scheduler, not per-input seams + +Adding IR remote control raised "how does one module drive another module's control?" The wrong answer is a bespoke seam per target (`Drivers::adjustBrightness`, then `Drivers::setPalette`, … — N one-offs, the "N disparate inputs" the LightsControl backlog warns against). The right answer already existed, half-hidden: the WLED-app bridge sets brightness via `applySetControl(module, control, value)` — the generic find-module → validate → apply → onUpdate → persist → conditional-buildState path. It lived *private on HttpServerModule*. + +The move: **lift `setControl` onto the `Scheduler`** (which owns the module tree + the persistence hook), and make HttpServer's `applySetControl` a thin result→HTTP-status mapper. Now IR, HTTP, Improv, and the WLED bridge all compose against one control-agnostic primitive — brightness, palette, or any future control is the same call with different arguments; adding an input never adds plumbing. HttpServer shrank ~54 lines (dedup, not addition). The reach mechanism is the `FilesystemModule::instance_` singleton pattern (`Scheduler::instance()`), so a factory-created module (IrModule) gets it without a per-module inject. General rule: when a second caller wants a capability that a transport layer implemented privately, the capability belongs in core (the tree owner), and the transport keeps only its status-mapping — the same shape as the DevicesModule/AudioModule "core owns the hard part, the module leans on it" split. + +## An RMT/IR done-callback runs in ISR context: signal only, decode on the task + +The first NEC-decoder version crash-looped the board (USB dropped, port re-enumerated) because the RMT `on_recv_done` callback both **decoded** the frame (calling non-`IRAM_ATTR` helpers) and **re-armed** `rmt_receive()` — from interrupt context. On ESP32, an ISR that jumps to flash-resident code while the cache is disabled, or re-enters a driver call, faults. The existing working RX capture (`rmtWs2812RxCapture`) already showed the safe shape and I didn't follow it at first: **the ISR only records the symbol count and signals a queue; the decode + re-arm happen on the task** (in `irRead`, called from the render loop). Rule: an RMT/GPIO done-callback is ISR context — it may touch only its stack, the passed event data, and an ISR-safe FreeRTOS primitive (`xQueueSendFromISR`); everything else (parsing, logging, re-arming the peripheral) moves to the waking task. When in doubt, copy the codebase's already-proven callback, don't re-derive it. diff --git a/docs/moonmodules/core/ui/ui.md b/docs/moonmodules/core/ui/ui.md index ecf3b955..27468333 100644 --- a/docs/moonmodules/core/ui/ui.md +++ b/docs/moonmodules/core/ui/ui.md @@ -70,12 +70,15 @@ Detail: [technical](../moxygen/FilesystemModule.md) ### Audio -A System peripheral (added by the user, not auto-wired): an I²S microphone feeding the FFT that audio-reactive effects consume via `AudioModule::latestFrame()`. Idle until real GPIOs are entered. +A System peripheral (added by the user, not auto-wired): an I²S microphone (or line-in ADC) feeding the FFT that audio-reactive effects consume via `AudioModule::latestFrame()`. It also syncs audio over UDP, WLED-compatible: broadcast the local analysis for the WLED ecosystem, or receive a peer's audio to drive effects with no local mic. Idle until real GPIOs are entered. -- `wsPin` / `sdPin` / `sckPin` — the I²S microphone GPIOs (unset until entered). -- `sampleRate` — mic sample rate. +- `wsPin` / `sdPin` / `sckPin` — the I²S GPIOs (unset until entered). +- `mclkPin` — master-clock GPIO for a line-in ADC that needs one (e.g. the PCM1808); leave unset for a plain mic. +- `sampleRate` — mic/ADC sample rate. +- `floor` / `gain` — noise floor and input gain for the analysis. - `simulate` — feed a synthetic signal instead of the mic (for testing without hardware). -- read-only — `level` (RMS), `peakHz`. +- `sync` — Off / Send / Receive: broadcast or receive WLED audio-sync packets. `syncPort` sets the UDP port (default 11988, the WLED standard); Receive auto-blends back to the local mic ~1 s after a peer goes quiet. +- read-only — `level` (RMS), `peakHz`, `sync status`. Detail: [technical](../moxygen/AudioModule.md) diff --git a/src/core/AudioModule.h b/src/core/AudioModule.h index c52a026c..387453ca 100644 --- a/src/core/AudioModule.h +++ b/src/core/AudioModule.h @@ -608,6 +608,11 @@ class AudioModule : public MoonModule { if (parseWledAudioSync(pkt, static_cast(n), rf)) { frame_ = rf; // received audio drives the effects lastSyncRecv_ = platform::millis(); + // Feed the peer level into the same 1 s peak window the local mic uses, so the + // "level RMS" read-out (loop1s → levelStr_) reflects received audio too — otherwise + // it freezes at the last local value while a peer is driving the effects. + if (frame_.level > levelPeak_) + levelPeak_ = static_cast(frame_.level > 255 ? 255 : frame_.level); } // else: a v1 / foreign packet — ignore, keep draining. } diff --git a/src/core/IrModule.h b/src/core/IrModule.h index 357584fd..f3183b5f 100644 --- a/src/core/IrModule.h +++ b/src/core/IrModule.h @@ -1,8 +1,9 @@ #pragma once #include "core/MoonModule.h" -#include "core/Scheduler.h" // setControl — the generic control-set primitive -#include "platform/platform.h" // irRead +#include "core/Scheduler.h" // setControl — the generic control-set primitive +#include "core/FilesystemModule.h" // noteDirty — schedule the debounced save on a learned bind +#include "platform/platform.h" // irRead #include #include @@ -137,7 +138,12 @@ class IrModule : public MoonModule { kActions[idx].button, static_cast(code)); setStatus(statusBuf_); learn_ = 0; + // Persist the new binding: markDirty flags the subtree, noteDirty stamps the debounce + // timer loop1s watches. A bound code is written straight to codeStr_ here (not via + // setControl), so it must schedule the save itself — else the binding could be lost + // before an unrelated save happens to run. markDirty(); + FilesystemModule::noteDirty(); return; } for (uint8_t i = 0; i < kActionCount; i++) { @@ -145,8 +151,7 @@ class IrModule : public MoonModule { } std::snprintf(statusBuf_, sizeof(statusBuf_), "received 0x%08lX (unassigned)", static_cast(code)); - setStatus(statusBuf_); - markDirty(); + setStatus(statusBuf_); // status only — nothing persistent changed, so no dirty mark } // Read the target control's current value + bounds, apply the clamped delta, set it through the diff --git a/src/platform/esp32/platform_esp32_ir.cpp b/src/platform/esp32/platform_esp32_ir.cpp index cd4e6c4d..00a46dbf 100644 --- a/src/platform/esp32/platform_esp32_ir.cpp +++ b/src/platform/esp32/platform_esp32_ir.cpp @@ -124,7 +124,13 @@ bool irRead(uint16_t pin, uint32_t& codeOut) { // Decode the captured buffer (task context), then re-arm for the next frame. Re-arming here — // not in the ISR — guarantees the decode reads a stable buffer the next capture can't clobber. const bool ok = decodeNec(rxBuf_, n, codeOut); - arm(); + if (!arm()) { + // Re-arm failed → the channel is enabled but not receiving, and ensureChannel() would + // treat it as still-open (pin unchanged) and never recover it. Tear it down so the next + // irRead reopens a fresh channel on this pin. + closeChannel(); + return false; + } return ok; } diff --git a/test/unit/core/unit_IrModule.cpp b/test/unit/core/unit_IrModule.cpp index 649fe9ec..be49aaec 100644 --- a/test/unit/core/unit_IrModule.cpp +++ b/test/unit/core/unit_IrModule.cpp @@ -162,13 +162,22 @@ TEST_CASE("IrModule: two codes bind to two actions independently") { CHECK(r.drivers->brightness == 116); // unchanged } -TEST_CASE("IrModule: an unassigned code and a missing target are no-ops, not crashes") { +TEST_CASE("IrModule: an unassigned code is a no-op, not a crash") { Rig r; const uint8_t before = r.drivers->brightness; r.fire(0xABCDEF01); // no learned binding → nothing happens CHECK(r.drivers->brightness == before); // The code is still recorded (for the read-out) even though it drove no action. CHECK(r.ir->latestCode() == 0xABCDEF01); - // Robustness: the path tolerates any input without crashing (setControl returns a typed - // not-found rather than dereferencing null), and an unassigned code is simply ignored. +} + +TEST_CASE("IrModule: a learned code whose target module is gone is a no-op, reported") { + Rig r; + const uint8_t before = r.drivers->brightness; + r.learn(1, 0xC0DE); // bind 0xC0DE → brightness up (targets "Drivers") + // Take the target out of reach: rename it so firstByName("Drivers") returns null. + r.drivers->setName("NotDrivers"); + r.fire(0xC0DE); // the action fires but its module is missing + CHECK(r.drivers->brightness == before); // nothing changed + CHECK(std::strstr(r.ir->status(), "no Drivers module") != nullptr); // reported, not crashed }