Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Drive large LED installations and DMX lighting from ESP32, Teensy, Raspberry Pi,

![Web UI](docs/assets/ui/ui_theme.gif)

👉 **Try it now:** flash an ESP32 straight from your browser → <https://moonmodules.org/projectMM/install/> — step-by-step in the [Getting started guide](docs/gettingstarted.md).
👉 **Try it now:** flash an ESP32 straight from your browser → <https://moonmodules.org/projectMM/install/>. Step-by-step in the [Getting started guide](docs/gettingstarted.md).

📦 **Release + downloads:** [latest release](https://github.com/MoonModules/projectMM/releases/latest)

Expand All @@ -22,21 +22,21 @@ If you like projectMM, give it a ⭐️, fork it, or open an issue or pull reque

🎛️ **Pluggable pipeline**: Layouts → Layers (effects + modifiers) → Drivers. Build it visually in the browser, and every change applies live (settings also persist to flash across power cycles).

🔄 **No reboot to apply a configuration change**: edit a pin map, a strand length, an output protocol, or the mic on a running device and it takes effect on the very next frame no init-at-boot step, no restart. Where most LED-controller firmware needs a reboot for a pin or protocol change, projectMM applies it live. (Flashing new *firmware* over OTA still needs the usual power cycle that's a binary swap, not a config change.)
🔄 **No reboot to apply a configuration change**: edit a pin map, a strand length, an output protocol, or the mic on a running device and it takes effect on the very next frame, with no init-at-boot step, no restart. Where most LED-controller firmware needs a reboot for a pin or protocol change, projectMM applies it live. (Flashing new *firmware* over OTA still needs the usual power cycle, since that's a binary swap rather than a config change.)

💡 **DMX *and* addressable LEDs in one setup**: RGB strips, RGBW pixels, par lights, moving heads, all through the same pipeline.

🔌 **Parallel WS2812 output**: drive many strands at once over three ESP32 peripheralsRMT (every chip), the S3's LCD_CAM i80 bus (8 lanes), and the P4's Parlio engine (up to 8 lanes) each with an on-device loopback self-test that bit-verifies the wire signal.
🔌 **Parallel WS2812 output**: drive many strands at once over three ESP32 peripherals: RMT (every chip), the S3's LCD_CAM i80 bus (8 lanes), and the P4's Parlio engine (up to 8 lanes), each with an on-device loopback self-test that bit-verifies the wire signal.

🌐 **Industry protocols, both directions**: send *and* receive [Art-Net](https://art-net.org.uk/), [E1.31/sACN](https://tsp.esta.org/tsp/documents/docs/ANSI_E1-31-2018.pdf), and [DDP](http://www.3waylabs.com/ddp/) over the network interoperable with Falcon, Advatek, xLights, Resolume, LedFx and other industry gear.
🌐 **Industry protocols, both directions**: send *and* receive [Art-Net](https://art-net.org.uk/), [E1.31/sACN](https://tsp.esta.org/tsp/documents/docs/ANSI_E1-31-2018.pdf), and [DDP](http://www.3waylabs.com/ddp/) over the network, interoperable with Falcon, Advatek, xLights, Resolume, LedFx and other industry gear.

🎵 **Audio-reactive**: an I²S microphone drives a 16-band FFT spectrum + sound level, consumed by audio-reactive effects all built fresh from the mic datasheet and textbook DSP.
🎵 **Audio-reactive**: an I²S microphone drives a 16-band FFT spectrum + sound level, consumed by audio-reactive effects, all built fresh from the mic datasheet and textbook DSP.

🏠 **Home-automation control**: a device joins Homebridge (and any MQTT hub) over a dependency-free MQTT 3.1.1 client on/off, brightness, and a HomeKit color wheel that picks the nearest palette. See [the MQTT module docs](docs/moonmodules/core/services.md#mqtt).
🏠 **Home-automation control**: a device joins Homebridge (and any MQTT hub) over a dependency-free MQTT 3.1.1 client: on/off, brightness, and a HomeKit color wheel that picks the nearest palette. See [the MQTT module docs](docs/moonmodules/core/services.md#mqtt).

📁 **On-device File Manager**: browse and edit the device filesystem from the browser a lazy folder tree with an inline editor, drag-drop upload, and create/delete, plus [firmware upload OTA](docs/moonmodules/core/services.md#firmware-update) (flash a `.bin` over the LAN, no USB). See [the File Manager docs](docs/moonmodules/core/services.md#file-manager).
📁 **On-device File Manager**: browse and edit the device filesystem from the browser: a lazy folder tree with an inline editor, drag-drop upload, and create/delete, plus [firmware upload OTA](docs/moonmodules/core/services.md#firmware-update) (flash a `.bin` over the LAN, no USB). See [the File Manager docs](docs/moonmodules/core/services.md#file-manager).

🛡️ **Robust to any input**: add, delete, replace, or reconfigure any module in any order, at any grid size, and the device keeps runningdegraded or idle, never crashed. Every crash that's ever found becomes a regression test, so it stays fixed.
🛡️ **Robust to any input**: add, delete, replace, or reconfigure any module in any order, at any grid size, and the device keeps running, degraded or idle, but never crashed. Every crash that's ever found becomes a regression test, so it stays fixed.

🖥️ **One source tree, many targets**: the same code runs on ESP32, Teensy, Raspberry Pi, and macOS / Windows / Linux.

Expand All @@ -50,7 +50,7 @@ If you like projectMM, give it a ⭐️, fork it, or open an issue or pull reque

📦 **No third-party libraries**: no FastLED, no ESPAsyncWebServer, no ArduinoJson. The color math, the HTTP/WebSocket server, and the control storage are all in-tree. A library, when genuinely needed, lives behind the platform boundary in `src/platform/`, never in core. The full rationale + replacements: [building.md § Third-party libraries](docs/building.md#third-party-libraries).

🔬 **Industry standards, our own code**: we study the prior art hard friend repos, peripheral datasheets, the Art-Net / E1.31 / WS2812 standards carry its *ideas* forward, and credit it by name; but we write our own code rather than copying theirs or tracing their structure. Each feature is spec'd from the primary source, its behaviour pinned with unit + scenario tests, then written fresh against our own architecture, so the result is independent by construction, not a renamed fork. Textbook algorithm, textbook name, our implementation. The method: [CLAUDE.md § Principles](CLAUDE.md#principles).
🔬 **Industry standards, our own code**: we study the prior art hard (friend repos, peripheral datasheets, the Art-Net / E1.31 / WS2812 standards), carry its *ideas* forward, and credit it by name; but we write our own code rather than copying theirs or tracing their structure. Each feature is spec'd from the primary source, its behaviour pinned with unit + scenario tests, then written fresh against our own architecture, so the result is independent by construction, not a renamed fork. Textbook algorithm, textbook name, our implementation. The method: [CLAUDE.md § Principles](CLAUDE.md#principles).

🧱 **One module model**: every effect, modifier, layout, and driver is a `MoonModule`: one base class, a uniform lifecycle, declared controls. That uniformity is why the UI renders any module with zero per-module code, and why a new capability is a new file, not a new framework. See [architecture.md § MoonModules](docs/architecture.md#moonmodules).

Expand Down Expand Up @@ -162,15 +162,17 @@ We built, maintained, and contributed to these projects, so projectMM is grounde

## Credits

Specific people whose work directly shaped parts of projectMM. We study their thinking with respect and write our own code against our architecture rather than tracing theirs — these credits name the prior art behind a feature:

- **Frank ([softhack007](https://github.com/softhack007))** — main author of the WLED-MM audio-reactive usermod, the most-used open-source audio-reactive LED implementation. The ideas behind [AudioService](docs/moonmodules/core/moxygen/AudioService.md) (including the adaptive noise-gate concept, analysed with his permission) descend from years of collaboration on WLED-SR / WLED-MM.
- **[troyhacks](https://github.com/troyhacks/WLED)** — reworked the WLED-MM audio-reactive DSP to run on Espressif's [esp-dsp](https://github.com/espressif/esp-dsp) FFT (a low-latency, "stupid fast" alternative to ArduinoFFT); the same esp-dsp FFT choice [AudioService](docs/moonmodules/core/moxygen/AudioService.md) makes. See its Prior art notes.
- **[hpwit](https://github.com/hpwit) (Yves Bazin)** — the clockless I2S / RMT / Parlio LED-driver techniques and the [ESPLiveScript](https://github.com/hpwit/ESPLiveScript) live-script engine behind the LED drivers and MoonLive.
- **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 color/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 [AudioService](docs/moonmodules/core/moxygen/AudioService.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.
Specific people whose work directly shaped parts of projectMM. We study their thinking with respect and write our own code against our architecture rather than tracing theirs. These credits name the prior art behind a feature:

- **[WLED](https://github.com/wled/WLED) and [WLED-MM](https://github.com/MoonModules/WLED)**: projectMM is born out of WLED, and takes the usermod idea to a new level. Here *everything* is a mod (a MoonModule): effects, drivers, networking, the file system, the system manager. It also integrates tightly with WLED: a projectMM device can act as a WLED device, and it talks to WLED devices (audio sync, discovery, and more).
- **Frank ([softhack007](https://github.com/softhack007))**: main author of the WLED-MM audio-reactive usermod, the most-used open-source audio-reactive LED implementation. The ideas behind [AudioService](docs/moonmodules/core/moxygen/AudioService.md) (including the adaptive noise-gate concept, analysed with his permission) descend from years of collaboration on WLED-SR / WLED-MM.
- **[troyhacks](https://github.com/troyhacks/WLED)**: reworked the WLED-MM audio-reactive DSP to run on Espressif's [esp-dsp](https://github.com/espressif/esp-dsp) FFT (a low-latency, "stupid fast" alternative to ArduinoFFT); the same esp-dsp FFT choice [AudioService](docs/moonmodules/core/moxygen/AudioService.md) makes. See its Prior art notes.
- **[hpwit](https://github.com/hpwit) (Yves Bazin)**: the clockless I2S / RMT / Parlio LED-driver techniques and the [ESPLiveScript](https://github.com/hpwit/ESPLiveScript) live-script engine behind the LED drivers and MoonLive.
- **Christophe Gagnier ([@Moustachauve](https://github.com/Moustachauve))**: author of the native [WLED-Android](https://github.com/Moustachauve/WLED-Android) and [WLED-iOS](https://github.com/Moustachauve/WLED-iOS) apps. Their source let us reverse-engineer exactly what those apps read, 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 color/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.
- **[FPP](https://github.com/FalconChristmas/fpp) (Falcon Player)**: the show player that drives LED panel receiver cards from a Raspberry Pi. Seeing an FPP rig feed a wall of HUB75 panels is what prompted [PanelCardDriver](docs/moonmodules/light/drivers.md#panelcard): if a Linux host can send those frames, so can a board that is already rendering them, which removes the host from the installation entirely. FPP is the inspiration, and the reference point for what good looks like here: it sustains 50 fps.
- **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 [AudioService](docs/moonmodules/core/moxygen/AudioService.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

Expand Down
Binary file modified docs/assets/light/drivers/PanelCardDriver.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions docs/history/lessons.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,20 @@ The leading untested theory is that a short frame gives the DMA no runway to pre
- **Read a control's applicability before drawing conclusions from its value.** `doubleBuffer`, `useRing` and `ringSnapshot` all appear in `/api/state` while being inapplicable to this path (`supportsDoubleBuffer()` is hard `false` here; the ring controls are hidden unless `pinExpanderMode()`, and `wantsRing()` returns false in direct mode outright). Two hypotheses were built on those values and both were dead ends — the UI hides them for a reason, the API does not.
- **A frame-time KPI that does not scale with the frame is measuring a timeout, not a wire.** `frameTime` read ~741 µs flat from 10 to 600 lights. Flat where it should scale is a signal in itself.
- **A fix that works is not a cause that is understood.** The pool swap reliably removes the symptom, which is tempting to write up as a root cause; the arithmetic says the mechanism is still unknown.

## A DMA buffer smaller than the frame multiplies descriptor use, and the symptom looked exactly like a failing cable

Streaming panel-card frames from an S31 at ~5 300 packets/s degraded with uptime: clean for minutes, then refused frames, then a total transmit wedge every ~11 minutes that only a reboot cleared. It read as a hardware fault the whole way, and it was two config lines.

**The arithmetic that caused it.** `CONFIG_ETH_DMA_BUFFER_SIZE` defaults to 512 B. A panel-card frame is 1512 B, so every frame consumed **three** descriptors. A 10-descriptor TX ring therefore held ~3.3 frames while the driver fires 132 back-to-back. Setting the buffer to 1536 B (64-byte aligned) gives one descriptor per frame and the failures stop. **Check DMA buffer size against your actual frame size; the default is sized for IP MTU traffic, not for a burst sender.**

**The second half is a race, not a size.** `CONFIG_ETH_TRANSMIT_MUTEX` defaults to off, and `mac->transmit` advances a shared descriptor pointer with no locking. With the eth netif up, lwIP and the render task both reach it. Bench-isolated: buffer fixed but mutex off → 3 000 refused frames in 4 minutes; mutex on with only 10 descriptors → none in 11. **Ring depth was never the fix**: 30 descriptors ran no cleaner than 10, and a 30-deep TX ring totals ~46 KB of internal RAM against ~15 KB at 10.

**Why it cost hours: two wrong turns worth naming.**
- *"It is not back-pressure."* Halving the send rate left the failure rate at ~13-15%, which reads as ruling out a full ring. It does not: at 3.3 frames of depth each 132-packet burst overruns the ring at any rate. The burst was the problem, never the rate. That misreading sent the investigation toward the cable and the PHY.
- *One counter for two faults.* `esp_eth_transmit` returns `ESP_ERR_INVALID_STATE` for a down link **before** touching the MAC, and `ESP_ERR_NO_MEM` for a full ring. Collapsing both into one bool made "5 million drops" unreadable. Splitting them settled the cause in one build: every failure was `ring`, none was `link`. **When a counter can be incremented by two different faults, split it before theorising.**

**What the physical evidence did and did not prove.** Both link LEDs going dark said the wire genuinely dropped, which is true and correctly killed the "stale software flag" theory. But it does not identify a cause: a wedged MAC stops driving the wire, so the dark LED was a *consequence*. A longer cable was the visible difference from the working setup and looked compelling; it was untouched throughout and is exonerated.

**Recovery still earns its place.** `esp_eth_stop()` + `esp_eth_start()` re-runs negotiation and resets the descriptor rings: the only way back from a wedge short of a reboot, since no ioctl writes the driver's link flag. Bench-verified twice, recovering in ~17 s. Attempted once per wedge, never repeatedly: a restart cannot fix an unplugged cable, and retrying would bounce the interface under the user.

Loading
Loading