diff --git a/CLAUDE.md b/CLAUDE.md index 6171171f..94407d9a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -208,6 +208,10 @@ Each name is a script under `moondeck/`, run through `uv run`; the command and w **`test_host --ui` is never a gate.** The UI scenario runs drive a real browser against a running device and are what the documentation clips are recorded from, so they cost minutes and skip wholesale without a desktop and a Playwright browser. They run on request only, never as part of pre-commit, pre-merge or pre-release, and the `src/ui` trigger above means `--js` alone. +**`check_docgen` is a ratchet.** Errors are resolved before a commit, and warnings may only fall: the committed `docs/reference/metrics/docgen.md` is the number to beat, on the total and on every rule. It fails a run that raises either. Per rule as well as per total, because a total hides one rule paying for another, and because the cheapest way to satisfy a width rule is to split a line, which raises the block count and fixes nothing. A rule whose own limit changed is the one case to say so in the commit. + +**A file with warnings is left better than it was found.** Holding the line is the floor, not the goal: the report is meant to shrink, and it only does so if each change spends a little effort on the warnings in the files it already touches. Reasonable effort, in the spirit of principle 5: the ones a reader would agree with, not a rewrite of every comment in the file. What resists is left with its count unchanged rather than forced, since a comment split to satisfy a width rule is the move the per-rule ratchet exists to refuse. Files the change never opened are a sweep of their own. + Three checks earn their place for a reason worth knowing. **Repo health** is the only place the creeping numbers are visible: flash and DRAM per target, binary size, the tick matrix, line counts, complexity warnings. Its diff belongs in the commit and its deltas in the commit message. It runs when the code changes rather than on every commit, because its timings drift with the host: on a docs-only diff it records a regression that nothing in the diff caused. **The no-backend build** catches a helper left unused outside its guard, fatal under GCC while clang stays silent. **ESP32 firmware fresh** compares the binary against every source in a tenth of a second and catches the edit that was never compiled; compile for real after an sdkconfig or toolchain change. The [provisioning path](moondeck/MoonDeck.md#improv_smoke_test) is the five files MoonDeck names. diff --git a/docs/assets/light/drivers/ParallelLedDriver.png b/docs/assets/light/drivers/ParallelLedDriver.png new file mode 100644 index 00000000..9e179539 Binary files /dev/null and b/docs/assets/light/drivers/ParallelLedDriver.png differ diff --git a/docs/assets/light/drivers/RmtLedDriver.png b/docs/assets/light/drivers/RmtLedDriver.png index 62d3058e..211820a6 100644 Binary files a/docs/assets/light/drivers/RmtLedDriver.png and b/docs/assets/light/drivers/RmtLedDriver.png differ diff --git a/docs/contributing/documentation-standards.md b/docs/contributing/documentation-standards.md index 60becd2d..71eda51d 100644 --- a/docs/contributing/documentation-standards.md +++ b/docs/contributing/documentation-standards.md @@ -140,6 +140,7 @@ Two scales below a page. **A module** has exactly one reference page written and - **A page is read start to finish by one reader**, a **user** or a **developer**. A user brings no coding and no hardware knowledge beyond plugging in a board; a developer brings C++, embedded and this codebase's shape. Where a page serves both, lead with the user and put the depth lower down. - **The headings are the page's table of contents, and they read top to bottom.** A few lines say what the page is and one paragraph says how it is laid out. The sections then follow in the order a reader needs them. A title that makes sense only after reading the body is the order being wrong. - **One tone of voice, everywhere: factual, no nonsense.** State what is true and what to do, addressing the reader as "you". Leave out enthusiasm, apology, and how we felt building it. Only the assumed knowledge changes between pages, never the voice. +- **No sentence whose job is tone.** Every sentence carries a fact the reader needs. Three shapes to cut: a second person used for effect rather than instruction, a flourish before any information arrives, and a rhetorical question the page then answers. Vale checks spelling, sentence length and weasel words, so this one is the writer's judgement and the reviewer's check. - **Follow the [principles](../CLAUDE.md#principles).** Three bear on documentation directly: - **Minimalism**: every fact has one home; history lives in git. - **Present tense only.** "No X anymore" narrates a removal, which is history. Describe the path that exists today. @@ -258,7 +259,7 @@ The first five cut and the last adds, deliberately: the result is a short line o **Depth is homed rather than forbidden.** One line is room to say why. Past that the reasoning belongs in an `@moreinfo` appendix, with an `@xref` back from the line that raised the question. Both kinds of file have one. A header's sits on its class or file lead, and an implementation file's on its own file lead, which 212 headers already carry. A larger cap for implementation files was tried and removed, because it homed depth inline. The reasoning then sits beside one call rather than where a reader goes looking for it. -**A finding in a header is an error; one in an implementation file is a warning.** This follows the same line. A header's comments are the published page, so a defect there ships, while a `.cpp` publishes nothing and its comments are a note to the next reader. Both are counted and both are reported, because a warning nobody sees is a warning nobody fixes. The split stages the sweep rather than ranking the two kinds of comment, so it goes and everything blocks once the warning column reaches zero. That is how Vale's own config promotes a page to error as the sweep finishes it. +**A finding in a header is an error; one in an implementation file is a warning, with one rule in each direction.** No-hard-wrap blocks in both kinds of file: the tree is at zero findings, so there is nothing left to stage. The line-length cap warns in both, because it is new and its findings are lines nobody wrote wrongly. A staged rule joins the others as the tree meets it. A header's comments are the published page, so a defect there ships, while a `.cpp` publishes nothing and its comments are a note to the next reader. Both are counted and both are reported, because a warning nobody sees is a warning nobody fixes. The split stages the sweep rather than ranking the two kinds of comment, so it goes and everything blocks once the warning column reaches zero. That is how Vale's own config promotes a page to error as the sweep finishes it. Enforced by [`check_docgen.py`](../moondeck/check/check_docgen.py) over every header under `src/`, the vendored ones excepted. diff --git a/docs/contributing/principles-and-process.md b/docs/contributing/principles-and-process.md index b0bebbdd..4dd21190 100644 --- a/docs/contributing/principles-and-process.md +++ b/docs/contributing/principles-and-process.md @@ -3,15 +3,8 @@ title: Principles & process --- + The `title:` above names the page in the nav and the browser tab. The embedded text opens with its own `# CLAUDE.md` heading, which is the right name at the repo root but not in a docs menu — front matter overrides it without editing the source file. The doc links inside the embed are rebased at build time (docs/x.md → x.html); see _rebase_repo_root_doc_links in moondeck/docs/mkdocs_hooks.py. --> --8<-- "CLAUDE.md" diff --git a/docs/explanation/architecture/moonbase.md b/docs/explanation/architecture/moonbase.md index 2f5529f6..842a7ff9 100644 --- a/docs/explanation/architecture/moonbase.md +++ b/docs/explanation/architecture/moonbase.md @@ -28,65 +28,28 @@ Neither image can rewrite the partition it is executing from, so each installs t ## What it replaces -Dual-OTA spends half the app area on a second copy of the firmware that is idle except during an -update. **MoonBase** replaces it: a small, rarely-changing image in the partition table's -`factory` slot that owns the device while the application is being replaced, since a board -cannot rewrite the partition it is executing from. One app slot then suffices, and the flash the -second slot held goes elsewhere. +Dual-OTA spends half the app area on a second copy of the firmware that is idle except during an update. **MoonBase** replaces it: a small, rarely-changing image in the partition table's `factory` slot that owns the device while the application is being replaced, since a board cannot rewrite the partition it is executing from. One app slot then suffices, and the flash the second slot held goes elsewhere. -A 4 MB board has no choice, having room for one application and not two, and its app slot grows -by a third in exchange. On a **16 MB** board the choice is deliberate rather than forced, and the -freed 4 MB goes to the filesystem, 11 MB rather than 7. +A 4 MB board has no choice, having room for one application and not two, and its app slot grows by a third in exchange. On a **16 MB** board the choice is deliberate rather than forced, and the freed 4 MB goes to the filesystem, 11 MB rather than 7. -Which boards use MoonBase is a per-variant decision recorded in -`moondeck/build/build_esp32.py` rather than a property of flash size. Today the 4 MB classic, the -S3-Zero and `esp32-16mb` use it, and it may become the default everywhere. +Which boards use MoonBase is a per-variant decision recorded in `moondeck/build/build_esp32.py` rather than a property of flash size. Today the 4 MB classic, the S3-Zero and `esp32-16mb` use it, and it may become the default everywhere. ## The update cycle -The update cycle runs in three moves. The app stages the install URL in NVS, or nothing for a -browser upload, points the bootloader at MoonBase and reboots. MoonBase joins the network with -the app's stored credentials, falling back to an AP at 4.3.2.1, and installs into the single app -slot, either from the staged URL unattended or from an upload. Then it reboots back. The UI covers the whole cycle with one -"updating firmware" overlay, telling the two images apart via `GET /moonbase` (MoonBase answers -with its live status; the app 404s it). Pointing the bootloader at a factory partition *erases* -otadata, so a power cut anywhere mid-install boots MoonBase and the user retries over the -network, a stronger power-fail story than dual-OTA's. A failed install deliberately stays in -MoonBase, visibly, rather than silently reverting to the old app; the way back is its explicit -"Boot the app" action, which only boots an image that validates. +The update cycle runs in three moves. The app stages the install URL in NVS, or nothing for a browser upload, points the bootloader at MoonBase and reboots. MoonBase joins the network with the app's stored credentials, falling back to an AP at 4.3.2.1, and installs into the single app slot, either from the staged URL unattended or from an upload. Then it reboots back. The UI covers the whole cycle with one "updating firmware" overlay, telling the two images apart via `GET /moonbase` (MoonBase answers with its live status; the app 404s it). Pointing the bootloader at a factory partition *erases* otadata, so a power cut anywhere mid-install boots MoonBase and the user retries over the network, a stronger power-fail story than dual-OTA's. A failed install deliberately stays in MoonBase, visibly, rather than silently reverting to the old app; the way back is its explicit "Boot the app" action, which only boots an image that validates. ## Updating MoonBase itself **Updating MoonBase itself** runs the same cycle backwards: the app writes the factory slot while -running from `ota_0`, exactly as MoonBase writes the app slot while running from factory. Neither -image can rewrite the partition it executes from, so each installs the other and the app is the -only thing that can repair a broken recovery image. Without it a bad MoonBase means a cable, which -is the failure MoonBase exists to prevent. - -Two things make that safe enough to offer. `esp_ota_*` refuses a factory partition, so this is a -raw `esp_partition_erase_range` + `esp_partition_write`, which also forfeits the validation -`esp_ota_end` performs: `esp_image_verify` replaces it after the write. A 4 MB board also has nowhere to stage 743 KB before erasing, so the image streams straight in. +running from `ota_0`, exactly as MoonBase writes the app slot while running from factory. Neither image can rewrite the partition it executes from, so each installs the other and the app is the only thing that can repair a broken recovery image. Without it a bad MoonBase means a cable, which is the failure MoonBase exists to prevent. + +Two things make that safe enough to offer. `esp_ota_*` refuses a factory partition, so this is a raw `esp_partition_erase_range` + `esp_partition_write`, which also forfeits the validation `esp_ota_end` performs: `esp_image_verify` replaces it after the write. A 4 MB board also has nowhere to stage 743 KB before erasing, so the image streams straight in. Everything that can reject it is therefore decided from its FIRST CHUNK, before a byte is erased: the image magic, the chip id, and the descriptor naming `projectMM-moonbase` rather than the app. -The chip id matters because there is one MoonBase per chip, one paste apart, and a checksum does -not catch a swap. Those rules live in `src/core/util/FirmwareImage.h` so -a host test can drive them. What remains is a window, during the write, in which the device holds -no recovery image; the app keeps running throughout, so the answer to a failure is to retry. +The chip id matters because there is one MoonBase per chip, one paste apart, and a checksum does not catch a swap. Those rules live in `src/core/util/FirmwareImage.h` so a host test can drive them. What remains is a window, during the write, in which the device holds no recovery image; the app keeps running throughout, so the answer to a failure is to retry. ## Telling the two images apart -Each image reports its version from the app descriptor IDF puts in every binary, with -`PROJECT_VER` set to the same computed version for both. The app can therefore read the factory -partition's version without booting it, and say when the two were built apart. A device that cannot name its -own recovery image cannot be diagnosed: two boards that looked identical, one of which could not -install firmware, took a bisect of the git log to tell apart. - -MoonBase is a standalone ESP-IDF project (`moonbase/`, ~750 KB against an 896 KB slot) sharing -no sources with the app, the deliberate trade for an image that must stay small and, once -working, hardly change. `moondeck/build/build_esp32.py` builds it alongside every variant that opts in, and owns the -flash-layout helpers every consumer uses: serial flash, mooninstaller manifests, release preview -and the QEMU image. IDF's own `flasher_args.json` knows nothing of the two-image scheme and -stages the app at the factory offset, so each of those paths applies the same correction from one -place. Prior art: Tasmota's safeboot scheme and -[MycilaSafeBoot](https://github.com/mathieucarbou/MycilaSafeBoot) proved the single-slot + -recovery-image pattern; MoonBase is our from-scratch, minimal take on it. +Each image reports its version from the app descriptor IDF puts in every binary, with `PROJECT_VER` set to the same computed version for both. The app can therefore read the factory partition's version without booting it, and say when the two were built apart. A device that cannot name its own recovery image cannot be diagnosed: two boards that looked identical, one of which could not install firmware, took a bisect of the git log to tell apart. + +MoonBase is a standalone ESP-IDF project (`moonbase/`, ~750 KB against an 896 KB slot) sharing no sources with the app, the deliberate trade for an image that must stay small and, once working, hardly change. `moondeck/build/build_esp32.py` builds it alongside every variant that opts in, and owns the flash-layout helpers every consumer uses: serial flash, mooninstaller manifests, release preview and the QEMU image. IDF's own `flasher_args.json` knows nothing of the two-image scheme and stages the app at the factory offset, so each of those paths applies the same correction from one place. Prior art: Tasmota's safeboot scheme and [MycilaSafeBoot](https://github.com/mathieucarbou/MycilaSafeBoot) proved the single-slot + recovery-image pattern; MoonBase is our from-scratch, minimal take on it. diff --git a/docs/explanation/architecture/mooncore.md b/docs/explanation/architecture/mooncore.md index 1ada3a18..03b7a489 100644 --- a/docs/explanation/architecture/mooncore.md +++ b/docs/explanation/architecture/mooncore.md @@ -111,14 +111,6 @@ Abstractions are added when a concrete implementation needs them, not pre-design **Platform boundary (hard rule).** All `#ifdef`, `#if defined`, platform-specific `#include`s, and hardware API calls live exclusively in `src/platform/`. Everything outside `src/platform/` compiles on every target without modification. Compile-time platform branching uses `if constexpr` on `platform_config.h` flags, never a preprocessor `#ifdef`. The boundary is enforced by [`moondeck/check/check_platform_boundary.py`](../../moondeck/check/check_platform_boundary.py), a commit gate (see [CLAUDE.md § The Process](../../CLAUDE.md#the-process)). **The desktop build runs everything (hard rule).** Every module, effect and driver in the repo -links and runs on the host, the platform layer has no silicon behind the call. Where a -peripheral is absent the host *emulates* it rather than declaring itself incapable: the parallel -WS2812 buses are backed by heap buffers, `lcdLanes` / `parlioLanes` / `rmtTxChannels` report a -real chip's counts, and `hasLcdCam` is true. Code excluded from the host binary is code that cannot be unit-tested and cannot be seen by any -AST-based check. It only ever runs where it is hardest to debug, which is what the LED drivers -were until they were linked here. - -A capability flag therefore answers *"can this build exercise the path?"*, not *"is this real -hardware?"*. Where a flag must mean the latter (`hasLcdCam` gating the pin expander), that is a -deliberate, commented exception. Timing, wire protocol and pin state are NOT emulated: they need -silicon, and faking them would let a self-test report on hardware it never touched. +links and runs on the host, the platform layer has no silicon behind the call. Where a peripheral is absent the host *emulates* it rather than declaring itself incapable: the parallel WS2812 buses are backed by heap buffers, `lcdLanes` / `parlioLanes` / `rmtTxChannels` report a real chip's counts, and `hasLcdCam` is true. Code excluded from the host binary is code that cannot be unit-tested and cannot be seen by any AST-based check. It only ever runs where it is hardest to debug, which is what the LED drivers were until they were linked here. + +A capability flag therefore answers *"can this build exercise the path?"*, not *"is this real hardware?"*. Where a flag must mean the latter (`hasLcdCam` gating the pin expander), that is a deliberate, commented exception. Timing, wire protocol and pin state are NOT emulated: they need silicon, and faking them would let a self-test report on hardware it never touched. diff --git a/docs/friend-repos/FastLED-FastLED.md b/docs/friend-repos/FastLED-FastLED.md index ceb205ff..b8ceba31 100644 --- a/docs/friend-repos/FastLED-FastLED.md +++ b/docs/friend-repos/FastLED-FastLED.md @@ -1,6 +1,6 @@ -# FastLED — monthly activity digest +# FastLED: monthly activity digest -What landed on [FastLED](https://github.com/FastLED/FastLED)'s main branch, month by month. External-context reference (like the v1/v2/MoonLight inventories) — a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these digests lives in [README.md](index.md). +What landed on [FastLED](https://github.com/FastLED/FastLED)'s main branch, month by month. External-context reference (like the v1/v2/MoonLight inventories), a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these digests lives in [README.md](index.md). ## August 2026 @@ -47,10 +47,10 @@ _Auditability: 195 first-parent commits on `master` with author-date 2026-08-01. No release cut this month (3.10.4, 2026-06-16, remains the latest), so the month is not split. Two big threads: finishing the Raspberry Pi Pico driver family, and cutting the ESP32 platform loose from the Arduino core. **New** -- Raspberry Pi Pico / RP2040: automatic parallel PIO output finally works for real — 2/4/8 strips driven from one PIO program, with a single-lane fallback for mixed layouts. +- Raspberry Pi Pico / RP2040: automatic parallel PIO output finally works for real, 2/4/8 strips driven from one PIO program, with a single-lane fallback for mixed layouts. - Raspberry Pi Pico gains fixed-function SPI+DMA drivers, a UART DMA driver, and a public hardware-SPI routing API. - WS2814 RGBW strips are now a first-class chipset with datasheet timing. -- Classic ESP32 gains a second I2S bank — up to 32 parallel strip outputs — plus a second UART output lane. +- Classic ESP32 gains a second I2S bank, up to 32 parallel strip outputs, plus a second UART output lane. - FastLED can be built as a plain ESP-IDF project with no Arduino core at all: IDF's own time, serial, SPI, LEDC and heap calls are now the default on ESP32, with Arduino only as an opt-in fallback. - Classic ESP32 also gains an I2S-based signal capture backend (reading WS2812 data in), alongside the existing RMT and LPC845 capture paths. - LPC845 now defaults to its UART DMA output path. @@ -69,10 +69,10 @@ No release cut this month (3.10.4, 2026-06-16, remains the latest), so the month **Watching** - Report that RGBW output has been broken since 3.10.3 (#3622, closed) fed the month's RGBW colorimetry cleanups. -- An open thread (#3762) blames an unconditional deep yield in `show()`'s refresh throttle for a long-standing frame-timing regression — no fix shipped yet. +- An open thread (#3762) blames an unconditional deep yield in `show()`'s refresh throttle for a long-standing frame-timing regression, no fix shipped yet. - A port to the WCH CH32V003 (48 MHz, 2 KB RAM) is proposed (#3755). -_Auditability: 212 first-parent commits on `master` with author-date 2026-07-01..2026-07-31. Issues via `search/issues` for `repo:FastLED/FastLED+is:issue+created:2026-07-01..2026-07-31` (110 opened) and `closed:2026-07-01..2026-07-31` (106 closed); the great majority are the project's own phase/meta bring-up trackers for RP2040, LPC845 and the classic-ESP32 I2S driver, plus CI and linter work — only the user-facing ones are surfaced above. No versioned release published in July, so no month split._ +_Auditability: 212 first-parent commits on `master` with author-date 2026-07-01..2026-07-31. Issues via `search/issues` for `repo:FastLED/FastLED+is:issue+created:2026-07-01..2026-07-31` (110 opened) and `closed:2026-07-01..2026-07-31` (106 closed); the great majority are the project's own phase/meta bring-up trackers for RP2040, LPC845 and the classic-ESP32 I2S driver, plus CI and linter work, only the user-facing ones are surfaced above. No versioned release published in July, so no month split._ ## June 2026 (up to 3.10.4) @@ -80,7 +80,7 @@ Released **3.10.4** (2026-06-16), cut from `master`. **New** - STM32: Arduino UNO Q board support. -- New NXP LPC8xx family drivers land (LPC804 PLU, LPC845 bit-bang + PWM/DMA-to-GPIO, LPC11xx) — early bring-up, bench-validated. +- New NXP LPC8xx family drivers land (LPC804 PLU, LPC845 bit-bang + PWM/DMA-to-GPIO, LPC11xx), early bring-up, bench-validated. - Unified `fl::Watchdog` API with real hardware implementations across platforms (ESP32, Teensy 4, AVR, Apollo3, RP2040, STM32) plus a non-allocating reset/crash-classification helper. - Wave simulation: opt-in 9-point isotropic Laplacian (smoother 2D waves), exposed as a UI toggle in several example sketches. - ScreenMap gains a v2 schema (auto-detected), and a new `.fled` container format for video/screenmaps that `FxSdCard` can load. @@ -114,13 +114,13 @@ _Auditability: 481 commits with author-date in 2026-06-01..2026-06-30 on `master **New** -- New **Channels API** for managing multiple LED drivers at once — a `fl::Bus` type, `FastLED.add(...)`, `fl::enableAllDrivers()`, and `FastLED.setExclusiveDriver(...)`, with a diagnostic that warns when a strip's driver doesn't match its bus. (The month's biggest effort.) +- New **Channels API** for managing multiple LED drivers at once, a `fl::Bus` type, `FastLED.add(...)`, `fl::enableAllDrivers()`, and `FastLED.setExclusiveDriver(...)`, with a diagnostic that warns when a strip's driver doesn't match its bus. (The month's biggest effort.) - **RGBW / RGBWW color**: proper colorimetric RGB→RGBW conversion with a lookup table, color-temperature (CCT) control, and an RGB+CCT mode. - ESP32-P4 gains a SIMD (PIE) acceleration backend for faster pixel processing. **Faster** -- Big speedups to the ESP32-P4 **PARLIO** parallel driver — encoding and transmission now overlap, and a chipset-aware encode path is ~5× faster than before. +- Big speedups to the ESP32-P4 **PARLIO** parallel driver, encoding and transmission now overlap, and a chipset-aware encode path is ~5× faster than before. - ESP32-P4 "Wave8" output ~1.2–2× faster via new transpose and lookup-table paths. **Hardware & build** @@ -140,7 +140,7 @@ _Auditability: 481 commits with author-date in 2026-06-01..2026-06-30 on `master **New** -- **Audio "silence gate"** across the audio-reactive features — tempo, spectral metrics, and the Vibe effect now fade out cleanly when the input goes quiet instead of reacting to noise. +- **Audio "silence gate"** across the audio-reactive features, tempo, spectral metrics, and the Vibe effect now fade out cleanly when the input goes quiet instead of reacting to noise. - Audio FFT can run on the ESP-DSP hardware backend (faster spectrum analysis on ESP32). - ESP32-S3 LCD driver gains ISR-driven chunked DMA output (smoother large-strip output); coroutine tasks can be pinned to a chosen core. - RMT receive (reading signals in) gains DMA streaming; a long-strip SPI bug (#2254) fixed. @@ -154,7 +154,7 @@ _Auditability: 481 commits with author-date in 2026-06-01..2026-06-30 on `master *Summarised from 444 first-parent commits on `master`, 2026-03-01 … 2026-03-31.* -- Mostly an internal stability and build-correctness month (sanitizer fixes, WASM build speed, IWYU/PCH hygiene) — little user-facing. +- Mostly an internal stability and build-correctness month (sanitizer fixes, WASM build speed, IWYU/PCH hygiene), little user-facing. - **Fixed:** AVR builds (replaced defaulted `noexcept` with explicit implementations in container types); ESP32-C3/C5 and Teensy build breakages; printf/Arduino-compatibility shim; an audio-path bug. ## February 2026 @@ -168,7 +168,7 @@ _Auditability: 481 commits with author-date in 2026-06-01..2026-06-30 on `master *Summarised from 582 first-parent commits on `master`, 2026-01-01 … 2026-01-31.* -- A heavy **stability-hardening month** — most of the work was fixing memory and initialization bugs surfaced by sanitizers (ASan/LSan/UBSan): use-after-free, memory leaks, static-initialization-order issues, shared-pointer errors. +- A heavy **stability-hardening month**, most of the work was fixing memory and initialization bugs surfaced by sanitizers (ASan/LSan/UBSan): use-after-free, memory leaks, static-initialization-order issues, shared-pointer errors. - **Fixed (user-visible):** a crash in power management; RP2350 system defines not being included; an ISR error where an int was read as a bool; i2s/LCD-CAM. ## December 2025 @@ -177,7 +177,7 @@ _Auditability: 481 commits with author-date in 2026-06-01..2026-06-30 on `master **New** -- **PARLIO driver maturation** (ESP32 parallel output): streaming support, up to 16 lanes, larger per-channel LED counts, background DMA buffer worker, and a low-level hardware abstraction layer — plus many alignment/timing fixes (the "1-bit shift" buffer-boundary bug). +- **PARLIO driver maturation** (ESP32 parallel output): streaming support, up to 16 lanes, larger per-channel LED counts, background DMA buffer worker, and a low-level hardware abstraction layer, plus many alignment/timing fixes (the "1-bit shift" buffer-boundary bug). - **Validation / proof-of-life framework**: hardware-in-the-loop validation, an ESP32 watchdog (with a USB-disconnect fix), and a result banner. - New `Potentiometer` class (hysteresis + calibration); 16-bit PWM pin support (`setPwm16`); per-channel gain on the HD108 chipset; `Serial` gains `printf`. - Signal **receive (RX)** gains raw edge-time capture and a safe sketch-halt. @@ -188,10 +188,10 @@ _Auditability: 481 commits with author-date in 2026-06-01..2026-06-30 on `master **New** -- **Channels / ChannelBusManager foundation** — a unified, priority-based driver manager with fallback, centralized SPI driver registration, and a new ChannelEngine-based SPI driver + RMT4 driver for ESP32 IDF 4.x. (Start of the multi-driver architecture that continues through to May.) +- **Channels / ChannelBusManager foundation**: a unified, priority-based driver manager with fallback, centralized SPI driver registration, and a new ChannelEngine-based SPI driver + RMT4 driver for ESP32 IDF 4.x. (Start of the multi-driver architecture that continues through to May.) - **PARLIO** gains a runtime-configurable multi-channel driver with auto-select (and dropped ESP32-S3, which uses LCD instead). - New **UCS7604** controller; a generic clockless waveform generator; video playback support. -- **Audio-reactive** effects expand — downbeat-darkness effect, AnimartrixRing audio reactivity, configurable UI audio. +- **Audio-reactive** effects expand, downbeat-darkness effect, AnimartrixRing audio reactivity, configurable UI audio. - WASM web preview moves to dedicated worker threads (drops Asyncify) with incremental/PCH build speedups. - Experimental RISC-V interrupt support. @@ -214,9 +214,9 @@ _Auditability: 481 commits with author-date in 2026-06-01..2026-06-30 on `master **New** -- **WASM web-preview overhaul** — Three.js-based tile rendering, instanced LED rendering, SharedArrayBuffer zero-copy frames, a background-worker async controller, and an improved video recorder (native `captureStream`, 60 FPS, better MP4 compatibility). +- **WASM web-preview overhaul**: Three.js-based tile rendering, instanced LED rendering, SharedArrayBuffer zero-copy frames, a background-worker async controller, and an improved video recorder (native `captureStream`, 60 FPS, better MP4 compatibility). - New hardware-accelerated **ezWS2812** GPIO + SPI drivers for Silicon Labs MGM240 / EFR32MG24 (MG24) boards. -- **Codec support** — progressive JPEG decoding (4 ms time budget), and metadata parsing for GIF/JPEG/MPEG1. +- **Codec support**: progressive JPEG decoding (4 ms time budget), and metadata parsing for GIF/JPEG/MPEG1. - Bilinear interpolation for upscaling effects; `FxNoiseRing` low-memory mode. - README/wiring guidance for high-parallel LED setups (incl. ObjectFLED parallel capacity). diff --git a/docs/friend-repos/MoonModules-WLED-MM.md b/docs/friend-repos/MoonModules-WLED-MM.md index ab14d3fa..d646b689 100644 --- a/docs/friend-repos/MoonModules-WLED-MM.md +++ b/docs/friend-repos/MoonModules-WLED-MM.md @@ -1,6 +1,6 @@ -# WLED-MM — monthly activity digest +# WLED-MM: monthly activity digest -What landed on [WLED-MM](https://github.com/MoonModules/WLED-MM)'s `mdev` (default) branch, month by month. External-context reference — a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). Months are split at versioned-release boundaries (the rolling `nightly` tag is not a release). +What landed on [WLED-MM](https://github.com/MoonModules/WLED-MM)'s `mdev` (default) branch, month by month. External-context reference, a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). Months are split at versioned-release boundaries (the rolling `nightly` tag is not a release). ## August 2026 @@ -14,11 +14,11 @@ _Auditability: 1 commit on `mdev`, author-date 2026-08-01..2026-08-31 (f2d32c9c, *Summarised from 2 commits on `mdev`, both 2026-07-01 (no versioned release cut this month; the `nightly` prerelease republished on 2026-07-02 packages June's work).* -Near-dormant month — a single small change and a build-number bump. +Near-dormant month, a single small change and a build-number bump. - The instance list now shows which repo (WLED-MM or upstream WLED) each discovered instance runs, and reports the right release for upstream WLED instances. -*Auditability: 2 commits on `mdev`, author-date 2026-07-01..2026-07-31 (range 12b0238 … 7c55f91; 7c55f91 is a build-number bump, omitted as not user-facing). Issues checked: `repo:MoonModules/WLED-MM is:issue created:2026-07-01..2026-07-31` (0) and `closed:2026-07-01..2026-07-31` (0), and `updated:2026-07-01..2026-07-31` (0) — no issues opened, closed, or even commented on all month. (The June window returns 2 on the same query form, so the zeros are real rather than a mistyped repo name: the search API needs `MoonModules/WLED-MM`, not `MoonModules/WLED`.)* +*Auditability: 2 commits on `mdev`, author-date 2026-07-01..2026-07-31 (range 12b0238 … 7c55f91; 7c55f91 is a build-number bump, omitted as not user-facing). Issues checked: `repo:MoonModules/WLED-MM is:issue created:2026-07-01..2026-07-31` (0) and `closed:2026-07-01..2026-07-31` (0), and `updated:2026-07-01..2026-07-31` (0), no issues opened, closed, or even commented on all month. (The June window returns 2 on the same query form, so the zeros are real rather than a mistyped repo name: the search API needs `MoonModules/WLED-MM`, not `MoonModules/WLED`.)* ## June 2026 @@ -26,7 +26,7 @@ Near-dormant month — a single small change and a build-number bump. **New** - Waveshare ESP32-S3 Matrix Driver board profile added. -- Audio-reactive is now an out-of-tree usermod (pulled in as a dependency) rather than baked into the tree — no user-facing change to how it works, but a cleaner build. +- Audio-reactive is now an out-of-tree usermod (pulled in as a dependency) rather than baked into the tree, no user-facing change to how it works, but a cleaner build. **Fixed** - Output settings no longer revert to defaults after a reboot (regression in 14.7.2 nightly, reported on ESP32-D0WDQ5 and ESP32-S3; issue #367). @@ -36,13 +36,13 @@ Near-dormant month — a single small change and a build-number bump. - ArtiFX (ARTI effect engine) robustness: call-stack bounds checks, safer string/lexer handling, and fixes for glitches seen only in speed-optimised builds (relates to issue #295); ARTI status now shown in the Info panel. - Upstream compatibility: accepts `I2CSDAPIN`/`I2CSCLPIN` as alternatives to the older I2C pin defines; fixed the arduinoFFT dependency; brown-out restart info now checked on both cores. -*Auditability: 38 commits on `mdev`, author-date 2026-06-01..2026-06-30 (range 84669c3 … 70fe1b8; several are CHANGELOG/version-bump/CodeRabbit-config/internal-refactor commits, omitted as not user-facing). Issues checked: created 2026-06-01..2026-06-30 and closed 2026-06-01..2026-06-30 — 4 relevant surfaced (#367, #371 fixed this month; #105, #295 long-standing, closed/addressed this month).* +*Auditability: 38 commits on `mdev`, author-date 2026-06-01..2026-06-30 (range 84669c3 … 70fe1b8; several are CHANGELOG/version-bump/CodeRabbit-config/internal-refactor commits, omitted as not user-facing). Issues checked: created 2026-06-01..2026-06-30 and closed 2026-06-01..2026-06-30, 4 relevant surfaced (#367, #371 fixed this month; #105, #295 long-standing, closed/addressed this month).* ## May 2026 *Summarised from 34 first-parent commits on `mdev`, 2026-05-01 … 2026-05-30.* -- **Ethernet board support:** added QuinLED v4 Ethernet profiles, a legacy Olimex ETH-Gateway option, and fixed the KIT-VE PHY address. **Breaking:** a duplicate Ethernet option was removed — re-select your board if you used the previous Olimex-ESP32-Gateway entry. +- **Ethernet board support:** added QuinLED v4 Ethernet profiles, a legacy Olimex ETH-Gateway option, and fixed the KIT-VE PHY address. **Breaking:** a duplicate Ethernet option was removed, re-select your board if you used the previous Olimex-ESP32-Gateway entry. - Audio-reactive auto-disables during DDP / DMX / Art-Net input (avoids the two fighting over the LEDs). - Persistent on-screen error display when a restart is needed (errors no longer scroll away unseen); Improv and MQTT input hardened against malformed data. - Web UI accessibility improvements. @@ -51,7 +51,7 @@ Near-dormant month — a single small change and a build-number bump. *Summarised from 47 first-parent commits on `mdev`, 2026-04-01 … 2026-04-30.* -- **DDP input** hardened — rejects malformed / unsupported / "control" packets, relaxed header checks for compatibility. +- **DDP input** hardened, rejects malformed / unsupported / "control" packets, relaxed header checks for compatibility. - Robustness: recovers gracefully from an empty `{}` config file (`cfg.json` / `wsec.json`) instead of misbehaving; steadier serial on ESP32; ESP8266 build fixes. - Otherwise a documentation / AI-contributor-guideline month (little user-facing). @@ -73,7 +73,7 @@ Near-dormant month — a single small change and a build-number bump. *Summarised from 42 first-parent commits on `mdev`, 2026-02-01 … 2026-02-28.* -- **Memory / "Heap too low" work:** moved the WS-LED preview buffer into PSRAM, PSRAM-aware allocation, reduced JSON buffers on S3-without-PSRAM — fewer out-of-memory failures on tight boards. +- **Memory / "Heap too low" work:** moved the WS-LED preview buffer into PSRAM, PSRAM-aware allocation, reduced JSON buffers on S3-without-PSRAM, fewer out-of-memory failures on tight boards. - **Board support:** ESP32-S3 QSPI build, builds without the HUB75 driver (4 MB / 16 MB variants), better handling of ESP32 PICO-D2/V3 and D0WDR2-V3 (frees GPIO17), startup serial now prints HUB75 pins + full chip revision. - Audio: disabled broken I2S 16-bit sampling; fixed Ethernet errors when using I2S audio. - Spots effect fixes; fixed a short black-out when a playlist advances. @@ -82,14 +82,14 @@ Near-dormant month — a single small change and a build-number bump. *Summarised from 48 first-parent commits on `mdev`, 2026-01-13 (after v14.7.1) … 2026-01-31.* -- **Animartrix** overhauled — optional gamma correction, always paints in 2D, big math speedups, dependency upgrade, and several bugfixes (segment-option changes now respected). +- **Animartrix** overhauled, optional gamma correction, always paints in 2D, big math speedups, dependency upgrade, and several bugfixes (segment-option changes now respected). - **New ESP32 node types** for ESP-NOW (WizMote data); Philips Hue robustness; PixelForge GIF tool gains image rotation. - **Fixed:** DMX-output now rate-limited to prevent watchdog resets; "relay does not turn on" sporadic issue; stack-smashing crash risk from `notify()`; better 2D preview color accuracy and PS Fireworks trails. - New V4 build environments incl. `esp32_16MB_V4_M_eth` (16 MB ESP32 with Ethernet); IR re-enabled for the Athom Music build. ## January 2026 (up to v14.7.1) -*Summarised from 32 first-parent commits on `mdev`, 2026-01-01 … 2026-01-13 — released as **v14.7.1**.* +*Summarised from 32 first-parent commits on `mdev`, 2026-01-01 … 2026-01-13, released as **v14.7.1**.* - **Release v14.7.1.** - Random per-LED colors via the JSON API (`"col":["r","r","r"]`); manual/dual auto-white modes work with palettes; segment-palette functions inlined for speed. @@ -144,4 +144,4 @@ Near-dormant month — a single small change and a build-number bump. *Summarised from 3 first-parent commits on `mdev`, 2025-09-01 … 2025-09-30.* -- Quiet month on `mdev` — build instructions, npm `ci` for dependencies, and GitHub Copilot contributor instructions. Nothing user-facing. +- Quiet month on `mdev`, build instructions, npm `ci` for dependencies, and GitHub Copilot contributor instructions. Nothing user-facing. diff --git a/docs/friend-repos/PlummersSoftwareLLC-NightDriverStrip.md b/docs/friend-repos/PlummersSoftwareLLC-NightDriverStrip.md index 0abd95ab..fadf06dc 100644 --- a/docs/friend-repos/PlummersSoftwareLLC-NightDriverStrip.md +++ b/docs/friend-repos/PlummersSoftwareLLC-NightDriverStrip.md @@ -1,8 +1,8 @@ -# NightDriverStrip — monthly activity digest +# NightDriverStrip: monthly activity digest -What landed on [NightDriverStrip](https://github.com/PlummersSoftwareLLC/NightDriverStrip)'s `main` branch, month by month. External-context reference — a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). +What landed on [NightDriverStrip](https://github.com/PlummersSoftwareLLC/NightDriverStrip)'s `main` branch, month by month. External-context reference, a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). -Summarised via the GitHub commits API (no local clone), so counts are all commits on `main`, not first-parent merges — the bullets filter out dependency bumps, whitespace, and pure refactors. Releases are noted as context rather than used as month boundaries: **v1.3.0** (published 2026-01-10) was tagged from a late-November commit, and the latest are **v2.0.0** and **v2.0.1**, both published 2026-06-14. v1.3.0 is not a clean month boundary so its month is kept whole; June IS split at v2.0.0, which was cut from `main` mid-month (see the two June sections below). +Summarised via the GitHub commits API (no local clone), so counts are all commits on `main`, not first-parent merges, the bullets filter out dependency bumps, whitespace, and pure refactors. Releases are noted as context rather than used as month boundaries: **v1.3.0** (published 2026-01-10) was tagged from a late-November commit, and the latest are **v2.0.0** and **v2.0.1**, both published 2026-06-14. v1.3.0 is not a clean month boundary so its month is kept whole; June IS split at v2.0.0, which was cut from `main` mid-month (see the two June sections below). ## August 2026 @@ -30,11 +30,11 @@ A quiet month: one feature merge, no release, no issues. - Better behaviour on low-memory boards: the firmware degrades gracefully instead of failing when memory runs short. - Fixed the JPEG decoder not being ready in time for the startup splash, and corrected serial status output on S3 boards. -_Auditability: 3 commits on `main` author-dated 2026-07-01..2026-07-31 (1 first-parent merge — PR #901, merged July 18 — plus a whitespace commit); `commits?sha=main&since=…&until=…`. Issues checked via `search/issues` for `created:2026-07-01..2026-07-31` (0), `closed:` (0) and `updated:` (0) in the same range. No versioned release published in July (latest are v2.0.0/v2.0.1, both June 14), so the month is kept whole._ +_Auditability: 3 commits on `main` author-dated 2026-07-01..2026-07-31 (1 first-parent merge, PR #901, merged July 18, plus a whitespace commit); `commits?sha=main&since=…&until=…`. Issues checked via `search/issues` for `created:2026-07-01..2026-07-31` (0), `closed:` (0) and `updated:` (0) in the same range. No versioned release published in July (latest are v2.0.0/v2.0.1, both June 14), so the month is kept whole._ ## June 2026 (up to v2.0.0) -The big one: **NightDriverStrip 2.0.0** shipped on June 14 — a major release cut from `main`. +The big one: **NightDriverStrip 2.0.0** shipped on June 14, a major release cut from `main`. **New** - Brand-new Web UI replacing the old one, plus a new browser-based web installer for flashing devices. @@ -57,12 +57,12 @@ The big one: **NightDriverStrip 2.0.0** shipped on June 14 — a major release c - **v2.0.1** (also June 14): patch that fixes the web installer failing to build for the 2.0.0 release (removed a stale project entry). - Stock-ticker effect now shows correct live data, fetched through the new V2 API. -- Fixed 64x32 (wide-and-short) displays that were rotating and doubling their content instead of scaling — effect previews in the Web UI and CLI now match the active output driver's pixel mapping (issue #878). +- Fixed 64x32 (wide-and-short) displays that were rotating and doubling their content instead of scaling, effect previews in the Web UI and CLI now match the active output driver's pixel mapping (issue #878). **Watching** - Issue #877 ("networking seems broken") drew heavy discussion (38 comments) around the WebUI being unreachable after a recent merge on some setups; closed in June. -Auditability: ~40 first-parent merges/commits on `main` with author-date in 2026-06-01..2026-06-30 (`commits?sha=main&since=…until=…`); two versioned releases published June 14 — v2.0.0 (commit ce00eaa) and v2.0.1 (commit 835015b), both ancestors of `main`, so the month is split at v2.0.0. Issues checked via `search/issues` for `created:2026-06-01..2026-06-30` (0 opened in range) and `closed:2026-06-01..2026-06-30` (#877, #878, #825 closed; #878 and #877 user-facing). +Auditability: ~40 first-parent merges/commits on `main` with author-date in 2026-06-01..2026-06-30 (`commits?sha=main&since=…until=…`); two versioned releases published June 14, v2.0.0 (commit ce00eaa) and v2.0.1 (commit 835015b), both ancestors of `main`, so the month is split at v2.0.0. Issues checked via `search/issues` for `created:2026-06-01..2026-06-30` (0 opened in range) and `closed:2026-06-01..2026-06-30` (#877, #878, #825 closed; #878 and #877 user-facing). ## May 2026 @@ -70,13 +70,13 @@ Auditability: ~40 first-parent merges/commits on `main` with author-date in 2026 **New** -- **RGBWW / SK6812 white-channel support** — W/WW helpers, a WarmGlow test effect, configurable SK6812 white extraction; color-temperature naming cleanup. -- **New Setup Wizard / guided-installer WebUI** — and a push to make the UI a "non-special consumer": everything the official UI needs now comes from the firmware over the wire (spec/schema), not baked into `app.js`. +- **RGBWW / SK6812 white-channel support**: W/WW helpers, a WarmGlow test effect, configurable SK6812 white extraction; color-temperature naming cleanup. +- **New Setup Wizard / guided-installer WebUI**: and a push to make the UI a "non-special consumer": everything the official UI needs now comes from the firmware over the wire (spec/schema), not baked into `app.js`. - M5 Stick S3 support (IR + WS2812B); optimised WS2812B draw path; `ACTIVITY_PIN` support; allow zero effects in the table. **Fixed / hardened** -- Major **PSRAM strategy reversal** — switched to PSRAM-default routing (threshold 96, Mesmerizer's proven value) and removed the bespoke `psram_allocator` family; JSON save-path now uses internal RAM to avoid touching PSRAM during flash/cache-disabled windows. +- Major **PSRAM strategy reversal**, switched to PSRAM-default routing (threshold 96, Mesmerizer's proven value) and removed the bespoke `psram_allocator` family; JSON save-path now uses internal RAM to avoid touching PSRAM during flash/cache-disabled windows. - Several concurrency and memory-safety fixes across buffer / drawing / network / task paths; PolarMap and noise-generation bugs; weather-data state protection. ## April 2026 @@ -96,7 +96,7 @@ Auditability: ~40 first-parent merges/commits on `main` with author-date in 2026 *~32 commits on `main`, 2026-03-01 … 2026-03-31.* -- **Replaced the RemoteDebug dependency** with a custom Logger + Telnet server — plus WiFi-stability and hardware-safety improvements; fixed a DebugCLI use-after-free for active telnet sessions and a stack-corruption (FD_SETSIZE) in the telnet sink. +- **Replaced the RemoteDebug dependency** with a custom Logger + Telnet server, plus WiFi-stability and hardware-safety improvements; fixed a DebugCLI use-after-free for active telnet sessions and a stack-corruption (FD_SETSIZE) in the telnet sink. - New Arduino-V3 partition layouts (standard, NOOTA, 8MB). - Otherwise dependency bumps and include hygiene. @@ -139,13 +139,13 @@ Auditability: ~40 first-parent merges/commits on `main` with author-date in 2026 *2 commits on `main`, 2025-11-01 … 2025-11-30.* -- Quiet month — a dependency bump (js-yaml). (The v1.3.0 tag was cut from a late-November commit but published in January.) +- Quiet month, a dependency bump (js-yaml). (The v1.3.0 tag was cut from a late-November commit but published in January.) ## October 2025 *2 commits on `main`, 2025-10-01 … 2025-10-31.* -- Quiet month — a dependency bump (vite). +- Quiet month, a dependency bump (vite). ## September 2025 diff --git a/docs/friend-repos/hpwit-ESPLiveScript.md b/docs/friend-repos/hpwit-ESPLiveScript.md index be00870a..26a9d2aa 100644 --- a/docs/friend-repos/hpwit-ESPLiveScript.md +++ b/docs/friend-repos/hpwit-ESPLiveScript.md @@ -1,8 +1,8 @@ -# hpwit/ESPLiveScript — monthly activity digest +# hpwit/ESPLiveScript: monthly activity digest -What landed on [hpwit/ESPLiveScript](https://github.com/hpwit/ESPLiveScript), month by month. External-context reference — a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). +What landed on [hpwit/ESPLiveScript](https://github.com/hpwit/ESPLiveScript), month by month. External-context reference, a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). -The library: Yves Bazin's (hpwit) C-like compiler/interpreter for the ESP32 — small scripts (e.g. LED effects) compiled and run live on-device without a full recompile-and-flash cycle. Summarised via the GitHub commits API. +The library: Yves Bazin's (hpwit) C-like compiler/interpreter for the ESP32, small scripts (e.g. LED effects) compiled and run live on-device without a full recompile-and-flash cycle. Summarised via the GitHub commits API. **Branch note:** `main` is quiet (last touched June 2025), but this repo develops on a long series of **version branches** (`v2`…`v4.3`, plus `vjson`/`vjson2`/`vdrop`/`memory*`), and that's where the recent work is. The activity below is read across those branches, not just `main`. @@ -16,21 +16,21 @@ _Checked: `repos/hpwit/ESPLiveScript/commits?sha=main` for 2026-08-01..2026-09-0 ## July 2026 -No user-facing activity: no commits on `main` **or any of the 38 version branches** (v2.x/v3.x/v4.x, `vjson`/`vjson2`/`vdrop`, `dev`, `mem*`) in July 2026, and no notable issues. (Latest commit on `main` predates the window — June 2025; the newest commit anywhere is `vjson2`, February 2026.) +No user-facing activity: no commits on `main` **or any of the 38 version branches** (v2.x/v3.x/v4.x, `vjson`/`vjson2`/`vdrop`, `dev`, `mem*`) in July 2026, and no notable issues. (Latest commit on `main` predates the window, June 2025; the newest commit anywhere is `vjson2`, February 2026.) -_Checked: commits author-dated 2026-07-01..2026-07-31 on `main` and every one of the 38 branches — 0 on each; issues created / closed / updated 2026-07-01..2026-07-31 (0 each); PRs created in-window (0); no versioned release published in July 2026._ +_Checked: commits author-dated 2026-07-01..2026-07-31 on `main` and every one of the 38 branches, 0 on each; issues created / closed / updated 2026-07-01..2026-07-31 (0 each); PRs created in-window (0); no versioned release published in July 2026._ ## June 2026 -No user-facing activity: no commits on `main` **or any of the ~30 version branches** (v2.x/v3.x, dev, mem*) in June 2026, and no notable issues. (Latest commit on `main` predates the window — June 2025.) +No user-facing activity: no commits on `main` **or any of the ~30 version branches** (v2.x/v3.x, dev, mem*) in June 2026, and no notable issues. (Latest commit on `main` predates the window, June 2025.) -_Checked: commits author-dated 2026-06-01..2026-06-30 on `main` and every version branch (`v2`…`v3.3`, `dev`, `mem2`…`mem4`, `memory`) — 0 on each; issues created / closed / updated 2026-06-01..2026-06-30 (0 each); PRs created in-window (0); no versioned release published in June 2026._ +_Checked: commits author-dated 2026-06-01..2026-06-30 on `main` and every version branch (`v2`…`v3.3`, `dev`, `mem2`…`mem4`, `memory`), 0 on each; issues created / closed / updated 2026-06-01..2026-06-30 (0 each); PRs created in-window (0); no versioned release published in June 2026._ ## February 2026 *Latest in-window activity, on the `vjson2` branch.* -- **JSON exchange refinements** (`vjson2`) — continued work on the script↔host JSON path begun in mid-2025, plus a code-refactoring cleanup pass. +- **JSON exchange refinements** (`vjson2`), continued work on the script↔host JSON path begun in mid-2025, plus a code-refactoring cleanup pass. ## March 2025 (and earlier 2025, on the `v4.x` branches) diff --git a/docs/friend-repos/hpwit-I2SClocklessLedDriver.md b/docs/friend-repos/hpwit-I2SClocklessLedDriver.md index 7469d77e..970ebcd9 100644 --- a/docs/friend-repos/hpwit-I2SClocklessLedDriver.md +++ b/docs/friend-repos/hpwit-I2SClocklessLedDriver.md @@ -1,10 +1,10 @@ -# hpwit/I2SClocklessLedDriver — monthly activity digest +# hpwit/I2SClocklessLedDriver: monthly activity digest -What landed on [hpwit/I2SClocklessLedDriver](https://github.com/hpwit/I2SClocklessLedDriver)'s `main` branch, month by month. External-context reference — a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). +What landed on [hpwit/I2SClocklessLedDriver](https://github.com/hpwit/I2SClocklessLedDriver)'s `main` branch, month by month. External-context reference, a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). -The library: Yves Bazin's (hpwit) clockless-LED driver that clocks WS2812-class strips out over the ESP32's I2S/LCD peripheral with DMA — the parallel-output technique projectMM's LED-driver analysis studies. Summarised via the GitHub commits API (all commits on `main`), filtering out merge commits, lint/format churn, and 🐰-review fixups. No versioned release is cut from `main` in this window (latest tag is 1.4), so months are kept whole. +The library: Yves Bazin's (hpwit) clockless-LED driver that clocks WS2812-class strips out over the ESP32's I2S/LCD peripheral with DMA, the parallel-output technique projectMM's LED-driver analysis studies. Summarised via the GitHub commits API (all commits on `main`), filtering out merge commits, lint/format churn, and 🐰-review fixups. No versioned release is cut from `main` in this window (latest tag is 1.4), so months are kept whole. -> **Authorship note.** Most of the activity in this window is projectMM's own — `ewowi` authored ~53 of the in-window commits, with the rest from the maintainer (Yves Bazin / hpwit) and a couple of others. The IDF 5.5 / arduino-less ESP-IDF / RGBCCT / >65K-LED work below is largely projectMM upstreaming its driver needs into hpwit's library, then tracking the result here. +> **Authorship note.** Most of the activity in this window is projectMM's own, `ewowi` authored ~53 of the in-window commits, with the rest from the maintainer (Yves Bazin / hpwit) and a couple of others. The IDF 5.5 / arduino-less ESP-IDF / RGBCCT / >65K-LED work below is largely projectMM upstreaming its driver needs into hpwit's library, then tracking the result here. ## August 2026 @@ -16,7 +16,7 @@ _Checked: `repos/hpwit/I2SClocklessLedDriver/commits?sha=main` for 2026-08-01..2 ## July 2026 -No user-facing activity: no commits merged to `main` (latest activity is April 6, 2026) and no notable issues. No branch saw commits either — the newest work anywhere is the `esp32-p4-support` branch, last touched April 11, 2026. +No user-facing activity: no commits merged to `main` (latest activity is April 6, 2026) and no notable issues. No branch saw commits either, the newest work anywhere is the `esp32-p4-support` branch, last touched April 11, 2026. _Auditability: commits on `main` author-dated 2026-07-01..2026-07-31 = 0 (0 merged), and 0 on every other branch; issues created/closed/updated in July 2026 = 0; PRs created = 0. No versioned release published in July (latest tag `1.4`, 2026-04-06)._ @@ -44,7 +44,7 @@ _Auditability: commits on `main` author-dated 2026-06-01..2026-06-30 = 0 (0 merg *~4 commits on `main`, 2026-01-01 … 2026-01-31.* -- **>65K-LED support** — `total_leds` widened to 32 bits; added `extractWhiteFromRGB`. +- **>65K-LED support**: `total_leds` widened to 32 bits; added `extractWhiteFromRGB`. - ESP32-D0: removed `ESP_INTR_FLAG_IRAM` from `esp_intr_alloc`; `deleteDriver` checks on the DMA tampon buffers. ## December 2025 @@ -65,7 +65,7 @@ _Auditability: commits on `main` author-dated 2026-06-01..2026-06-30 = 0 (0 merg **New** -- **IDF 5.5 support** — version checks, dynamic DMA-buffer allocation (PSRAM-preferred), `NUM_STRIPS` as a global, `IRAM_ATTR` removed from forwards to compile warning-free. +- **IDF 5.5 support**: version checks, dynamic DMA-buffer allocation (PSRAM-preferred), `NUM_STRIPS` as a global, `IRAM_ATTR` removed from forwards to compile warning-free. - `updateDriver` / `deleteDriver` gained length/size and per-strip offset parameters; `initled` with custom color arrangement; `isVirtualDriver` flag; `setDelay()`. - Split a `Driver.cpp` out of the header; added clang-format and removed the `COLOR_ORDER_` compiler directives. diff --git a/docs/friend-repos/hpwit-I2SClocklessVirtualLedDriver.md b/docs/friend-repos/hpwit-I2SClocklessVirtualLedDriver.md index 062b7331..89bc4b89 100644 --- a/docs/friend-repos/hpwit-I2SClocklessVirtualLedDriver.md +++ b/docs/friend-repos/hpwit-I2SClocklessVirtualLedDriver.md @@ -1,8 +1,8 @@ -# hpwit/I2SClocklessVirtualLedDriver — monthly activity digest +# hpwit/I2SClocklessVirtualLedDriver: monthly activity digest -What landed on [hpwit/I2SClocklessVirtualLedDriver](https://github.com/hpwit/I2SClocklessVirtualLedDriver), month by month. External-context reference — a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). +What landed on [hpwit/I2SClocklessVirtualLedDriver](https://github.com/hpwit/I2SClocklessVirtualLedDriver), month by month. External-context reference, a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). -The library: Yves Bazin's (hpwit) "virtual pins" variant of the I2S clockless driver — drives far more strips than the chip has usable pins by fanning the I2S output through external shift registers. This multiplex technique is the load-bearing idea projectMM's LED-driver analysis singles out (factoring the shift-register multiplex out of the I2S/LCD peripheral code). Summarised via the GitHub commits API, read across all branches (`main`, `integration`, `int2`, `variable`, `hpwit-patch-1`, `dev`, `optomize`), not just `main`. +The library: Yves Bazin's (hpwit) "virtual pins" variant of the I2S clockless driver, drives far more strips than the chip has usable pins by fanning the I2S output through external shift registers. This multiplex technique is the load-bearing idea projectMM's LED-driver analysis singles out (factoring the shift-register multiplex out of the I2S/LCD peripheral code). Summarised via the GitHub commits API, read across all branches (`main`, `integration`, `int2`, `variable`, `hpwit-patch-1`, `dev`, `optomize`), not just `main`. ## August 2026 @@ -16,20 +16,20 @@ _Checked: `repos/hpwit/I2SClocklessVirtualLedDriver/commits?sha=main` for 2026-0 No user-facing activity this month: no commits merged to `main` (latest commit on `main` dates to November 2024), no commits on any other branch (newest anywhere is `variable`, December 2024), no releases published, and no issues opened, closed, or updated. -_Checked: commits with author-date 2026-07-01..2026-07-31 on `main` and every branch (`integration`, `int2`, `variable`, `hpwit-patch-1`, `dev`, `optomize`) — 0 on each; issues created 2026-07-01..2026-07-31 (0), closed in that range (0), and updated in that range (0); PRs created (0); releases (none in July — latest versioned tag is 2.1, Jan 2024)._ +_Checked: commits with author-date 2026-07-01..2026-07-31 on `main` and every branch (`integration`, `int2`, `variable`, `hpwit-patch-1`, `dev`, `optomize`), 0 on each; issues created 2026-07-01..2026-07-31 (0), closed in that range (0), and updated in that range (0); PRs created (0); releases (none in July, latest versioned tag is 2.1, Jan 2024)._ ## June 2026 No user-facing activity this month: no commits merged to `main` (latest commit on `main` dates to November 2024), no releases published, and no issues opened, closed, or updated. -_Checked: commits on `main` with author-date 2026-06-01..2026-06-30 (0 commits); issues created 2026-06-01..2026-06-30 (0), closed in that range (0), and updated in that range (0); releases (none in June — latest versioned tag is 2.1, Jan 2024)._ +_Checked: commits on `main` with author-date 2026-06-01..2026-06-30 (0 commits); issues created 2026-06-01..2026-06-30 (0), closed in that range (0), and updated in that range (0); releases (none in June, latest versioned tag is 2.1, Jan 2024)._ ## No activity in the digest window (Sept 2025 – May 2026) No branch has any commits in the window these digests cover; the project went quiet at the end of 2024. Its last active stretch: -- **December 2024** (`variable` / `int2` branches) — variable-driver and integration work; this is the newest commit anywhere in the repo (`variable`, 2024-12-17). -- **November 2024** (`main`, tag `2.1`) — **ESP32-S3 support** added and merged to `main` (2024-11-20). -- **Earlier 2024** — `i2sStop` moved into IRAM (June); ESP-IDF 5 corrections (June); shift-array support and examples (Jan–Oct). +- **December 2024** (`variable` / `int2` branches), variable-driver and integration work; this is the newest commit anywhere in the repo (`variable`, 2024-12-17). +- **November 2024** (`main`, tag `2.1`), **ESP32-S3 support** added and merged to `main` (2024-11-20). +- **Earlier 2024**: `i2sStop` moved into IRAM (June); ESP-IDF 5 corrections (June); shift-array support and examples (Jan–Oct). -*Nothing newer to report. This file gets a new `## ` section the next time any branch sees in-window commits — watch `integration`/`variable`, where the development historically happened before merging to `main`.* +*Nothing newer to report. This file gets a new `## ` section the next time any branch sees in-window commits, watch `integration`/`variable`, where the development historically happened before merging to `main`.* diff --git a/docs/friend-repos/hpwit-new-parser.md b/docs/friend-repos/hpwit-new-parser.md index 201d1a9b..3dc14778 100644 --- a/docs/friend-repos/hpwit-new-parser.md +++ b/docs/friend-repos/hpwit-new-parser.md @@ -1,10 +1,10 @@ -# hpwit/new-parser (ESPLiveScript2) — monthly activity digest +# hpwit/new-parser (ESPLiveScript2): monthly activity digest -What landed on [hpwit/new-parser](https://github.com/hpwit/new-parser), month by month. External-context reference — a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). +What landed on [hpwit/new-parser](https://github.com/hpwit/new-parser), month by month. External-context reference, a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). -The library: **ESPLiveScript2**, Yves Bazin's (hpwit) from-scratch C++ rewrite of [ESPLiveScript](https://github.com/hpwit/ESPLiveScript) — the same idea (a small C-like language compiled on-device to real Xtensa machine code, no interpreter, so a script runs at near-native speed) reimplemented independently rather than refactored. The library ships inside the repo as `asmparser2/` (PlatformIO name `ESPLiveScript2`, at v1.3.0). Summarised via the GitHub commits API. +The library: **ESPLiveScript2**, Yves Bazin's (hpwit) from-scratch C++ rewrite of [ESPLiveScript](https://github.com/hpwit/ESPLiveScript), the same idea (a small C-like language compiled on-device to real Xtensa machine code, no interpreter, so a script runs at near-native speed) reimplemented independently rather than refactored. The library ships inside the repo as `asmparser2/` (PlatformIO name `ESPLiveScript2`, at v1.3.0). Summarised via the GitHub commits API. -**Repo note:** the repository name is `new-parser`, but the library and its README call it **ESPLiveScript2** — the name to search for. Sibling digest for v1: [hpwit-ESPLiveScript.md](hpwit-ESPLiveScript.md). +**Repo note:** the repository name is `new-parser`, but the library and its README call it **ESPLiveScript2**, the name to search for. Sibling digest for v1: [hpwit-ESPLiveScript.md](hpwit-ESPLiveScript.md). ## August 2026 @@ -64,7 +64,7 @@ _Checked: 38 commits author-dated 2026-08-01..2026-08-31 on `main` (0b6aa6b..d84 ## Timeline note (added 2026-08-06) -Added to the digest set on 2026-08-06, after the product owner flagged the rewrite. History to date, from the commit log: created March 2025, six commits across March–May 2025, then **dormant for over a year**, then **12 commits in the first days of August 2026** — the rewrite as it now stands is days old at the time of writing. July 2026 is therefore empty, and the August work is summarised above. +Added to the digest set on 2026-08-06, after the product owner flagged the rewrite. History to date, from the commit log: created March 2025, six commits across March–May 2025, then **dormant for over a year**, then **12 commits in the first days of August 2026**, the rewrite as it now stands is days old at the time of writing. July 2026 is therefore empty, and the August work is summarised above. What the rewrite is, from its README (context for future months, not an endorsement): @@ -74,6 +74,6 @@ What the rewrite is, from its README (context for future months, not an endorsem ## July 2026 -No activity: no commits on `main` in July 2026, and no issues. (The repo was dormant between May 2025 and August 2026 — the current rewrite work begins 2026-08-01, outside this window.) +No activity: no commits on `main` in July 2026, and no issues. (The repo was dormant between May 2025 and August 2026, the current rewrite work begins 2026-08-01, outside this window.) _Checked: commits author-dated 2026-07-01..2026-07-31 on `main` (0); issues created 2026-07-01..2026-07-31 (0) and closed in the same window (0); no versioned release published in July 2026._ diff --git a/docs/friend-repos/index.md b/docs/friend-repos/index.md index f225786f..37b2d963 100644 --- a/docs/friend-repos/index.md +++ b/docs/friend-repos/index.md @@ -1,40 +1,40 @@ # Friend repos, monthly activity digests -Monthly logs of what shipped on related open-source LED projects — the live landscape projectMM watches to sharpen its own designs under the *Industry standards, our own code* principle ([CLAUDE.md § Principles](../../CLAUDE.md#principles)): study to think, write fresh, never copy. Generated by the [digest prompt](#digest-prompt-reusable) below. - -- [FastLED-FastLED.md](FastLED-FastLED.md) — the LED-animation library; ESP32/Arduino driver + color math. -- [wled-WLED.md](wled-WLED.md) — upstream WLED firmware. -- [MoonModules-WLED-MM.md](MoonModules-WLED-MM.md) — MoonModules' WLED fork (the direct lineage). -- [troyhacks-WLED.md](troyhacks-WLED.md) — troyhacks' personal fork of WLED-MM (PixelForge, RMTHI, audio-reactive hardening). -- [Funkelfetisch-projectMM.md](Funkelfetisch-projectMM.md) — a fork of THIS project building a commercial product on it (HELIO, a physical infinity-sphere lamp); the work lives in feature branches, not on its default branch. -- [PlummersSoftwareLLC-NightDriverStrip.md](PlummersSoftwareLLC-NightDriverStrip.md) — Dave Plummer's LED matrix/strip firmware. -- [hpwit-I2SClocklessLedDriver.md](hpwit-I2SClocklessLedDriver.md) — hpwit's I2S/LCD DMA clockless LED driver (parallel multi-strip output). -- [hpwit-I2SClocklessVirtualLedDriver.md](hpwit-I2SClocklessVirtualLedDriver.md) — the shift-register "virtual pins" variant of the above (dormant since 2024). -- [hpwit-ESPLiveScript.md](hpwit-ESPLiveScript.md) — hpwit's live C-like script compiler for the ESP32 (main quiet; work moved to version branches). -- [hpwit-new-parser.md](hpwit-new-parser.md) — **ESPLiveScript2**, hpwit's from-scratch rewrite of the above (repo is named `new-parser`; the library lives in `asmparser2/`). Dormant May 2025 → August 2026, then an active rewrite whose stated goal is a *verifiable* compiler: host builds plus QEMU running the actual compiled Xtensa bytes. +Monthly logs of what shipped on related open-source LED projects, the live landscape projectMM watches to sharpen its own designs under the *Industry standards, our own code* principle ([CLAUDE.md § Principles](../../CLAUDE.md#principles)): study to think, write fresh, never copy. Generated by the [digest prompt](#digest-prompt-reusable) below. + +- [FastLED-FastLED.md](FastLED-FastLED.md): the LED-animation library; ESP32/Arduino driver + color math. +- [wled-WLED.md](wled-WLED.md): upstream WLED firmware. +- [MoonModules-WLED-MM.md](MoonModules-WLED-MM.md): MoonModules' WLED fork (the direct lineage). +- [troyhacks-WLED.md](troyhacks-WLED.md): troyhacks' personal fork of WLED-MM (PixelForge, RMTHI, audio-reactive hardening). +- [Funkelfetisch-projectMM.md](Funkelfetisch-projectMM.md): a fork of THIS project building a commercial product on it (HELIO, a physical infinity-sphere lamp); the work lives in feature branches, not on its default branch. +- [PlummersSoftwareLLC-NightDriverStrip.md](PlummersSoftwareLLC-NightDriverStrip.md): Dave Plummer's LED matrix/strip firmware. +- [hpwit-I2SClocklessLedDriver.md](hpwit-I2SClocklessLedDriver.md): hpwit's I2S/LCD DMA clockless LED driver (parallel multi-strip output). +- [hpwit-I2SClocklessVirtualLedDriver.md](hpwit-I2SClocklessVirtualLedDriver.md): the shift-register "virtual pins" variant of the above (dormant since 2024). +- [hpwit-ESPLiveScript.md](hpwit-ESPLiveScript.md): hpwit's live C-like script compiler for the ESP32 (main quiet; work moved to version branches). +- [hpwit-new-parser.md](hpwit-new-parser.md): **ESPLiveScript2**, hpwit's from-scratch rewrite of the above (repo is named `new-parser`; the library lives in `asmparser2/`). Dormant May 2025 → August 2026, then an active rewrite whose stated goal is a *verifiable* compiler: host builds plus QEMU running the actual compiled Xtensa bytes. ## Cross-repo trends Reading across the friend-repo digests, the themes the wider ESP32-LED ecosystem converged on over this release cycle (Sept 2025 → June 2026): - **ESP32-P4 / S3 parallel output.** FastLED poured effort into the PARLIO and LCD_CAM drivers (P4/S3 parallel LED output, big encode speedups); NightDriverStrip added custom RMT output; hpwit's I2SClocklessLedDriver pushed IDF-5.5 + arduino-less-ESP-IDF support for its I2S/LCD DMA driver (the canonical implementation of this technique), and troyhacks ran ESP32-P4 bring-up branches. The frontier is parallel, DMA-driven output on the newer chips. -- **PSRAM strategy is unsettled everywhere.** All four wrestled with PSRAM this cycle — WLED-MM moved preview buffers into PSRAM, NightDriverStrip did a full PSRAM-default reversal (then tuned the threshold), WLED added S3-no-PSRAM builds. Nobody has a clean answer; the cache-disabled-during-flash hazard recurs across repos. +- **PSRAM strategy is unsettled everywhere.** All four wrestled with PSRAM this cycle, WLED-MM moved preview buffers into PSRAM, NightDriverStrip did a full PSRAM-default reversal (then tuned the threshold), WLED added S3-no-PSRAM builds. Nobody has a clean answer; the cache-disabled-during-flash hazard recurs across repos. - **Audio-reactive maturing.** FastLED added a silence-gate + ESP-DSP FFT backend; WLED-MM and WLED both refined audio sync and auto-disable-during-realtime; NightDriverStrip modernised its SoundAnalyzer/FFT. Audio-reactive is table stakes now, and the polish is in *not* reacting to noise/silence. - **The FastLED dependency question.** WLED merged a *full FastLED replacement* (its own color/math); projectMM already made the same call (own color math, no FastLED in core). Two independent projects concluded the dependency wasn't worth it. -- **UI as a firmware-driven consumer.** Both WLED and NightDriverStrip pushed toward "the official UI knows nothing the firmware doesn't publish over the wire" — exactly projectMM's MoonModule-driven, no-hardcoded-knowledge UI principle. Convergent design. NightDriverStrip's **2.0.0** (June 2026) crystallised this: a brand-new web UI, a browser-based installer, and settings (like strip type) moved from compile-time to *runtime-selectable* on the device — the same "reconfigure live, no reflash" direction projectMM builds around. +- **UI as a firmware-driven consumer.** Both WLED and NightDriverStrip pushed toward "the official UI knows nothing the firmware doesn't publish over the wire", exactly projectMM's MoonModule-driven, no-hardcoded-knowledge UI principle. Convergent design. NightDriverStrip's **2.0.0** (June 2026) crystallised this: a brand-new web UI, a browser-based installer, and settings (like strip type) moved from compile-time to *runtime-selectable* on the device, the same "reconfigure live, no reflash" direction projectMM builds around. - **Effect velocity.** WLED and WLED-MM shipped many new effects (PacMan, Color Clouds, Shimmer, the user_fx pack); new effects remain the most visible user-facing output. -- **Display / HDMI output beyond LED strips.** troyhacks ran a cluster of branches probing HDMI video output and large hardware panels (WaveShare 10.1″, M5Stack, ESP32-P4 panels) — driving *displays*, not just addressable strips, off the same firmware. -- **On-device live scripting.** hpwit's ESPLiveScript compiles small C-like effect scripts that run live on the ESP32 with no reflash — a different answer to effect authoring than C++ recompilation or a fixed effect table. +- **Display / HDMI output beyond LED strips.** troyhacks ran a cluster of branches probing HDMI video output and large hardware panels (WaveShare 10.1″, M5Stack, ESP32-P4 panels), driving *displays*, not just addressable strips, off the same firmware. +- **On-device live scripting.** hpwit's ESPLiveScript compiles small C-like effect scripts that run live on the ESP32 with no reflash, a different answer to effect authoring than C++ recompilation or a fixed effect table. ## What these projects do that projectMM doesn't (yet) Observational: where the landscape is ahead of projectMM. These are *not* commitments; real adoption decisions live in the [`../backlog/`](../work/future/index.md), cross-referenced where one already exists. -- **Parallel multi-strip output on S3/P4** (PARLIO/LCD_CAM, and hpwit's I2S/shift-register drivers) — the direct parallel drivers ship (MultiPin/Moon on LCD_CAM, Parlio on P4, driving up to 16 strands and 12,288+ lights). The shift-register/'595 expander path also ships but is dormant: it works at prime-only geometries yet has a known lapping-ring sparkle at the largest configs, so it stays off by default. See the [LED-driver analysis](../work/future/leddriver-analysis-top-down.md). -- **Audio-reactive input** — none of projectMM's effects are audio- or motion-reactive yet. The Peripheral role + the Pi-sensor backlog entry are the foundation; the producer→effect wiring is backlog. -- **A guided setup/installer wizard on-device** (NightDriverStrip's Setup Wizard, WLED's installer) — projectMM has the web installer + Improv, but no on-device first-run wizard. -- **A large built-in effect library** — projectMM ships a focused set (concrete-first); the WLED family ships dozens. Breadth is a deliberate non-goal until the core is proven. -- **On-device live effect scripting** (hpwit's ESPLiveScript) — projectMM effects are compiled C++; there's no runtime script path. Not a goal today, noted as a landscape contrast. +- **Parallel multi-strip output on S3/P4** (PARLIO/LCD_CAM, and hpwit's I2S/shift-register drivers), the direct parallel drivers ship (MultiPin/Moon on LCD_CAM, Parlio on P4, driving up to 16 strands and 12,288+ lights). The shift-register/'595 expander path also ships but is dormant: it works at prime-only geometries yet has a known lapping-ring sparkle at the largest configs, so it stays off by default. See the [LED-driver analysis](../work/future/leddriver-analysis-top-down.md). +- **Audio-reactive input**: none of projectMM's effects are audio- or motion-reactive yet. The Peripheral role + the Pi-sensor backlog entry are the foundation; the producer→effect wiring is backlog. +- **A guided setup/installer wizard on-device** (NightDriverStrip's Setup Wizard, WLED's installer), projectMM has the web installer + Improv, but no on-device first-run wizard. +- **A large built-in effect library**: projectMM ships a focused set (concrete-first); the WLED family ships dozens. Breadth is a deliberate non-goal until the core is proven. +- **On-device live effect scripting** (hpwit's ESPLiveScript), projectMM effects are compiled C++; there's no runtime script path. Not a goal today, noted as a landscape contrast. ## Refreshing @@ -44,12 +44,13 @@ Adding a month or a new friend repo is the [friend-repos](index.md) workflow, an > **Friend-repo monthly digest.** For the repo `` (local clone at ``, or via `gh api repos//`), summarise what landed on its **main/default branch** during ``. > -> 1. Read the merged commits on the default branch with author-date in that calendar month (`git log --first-parent --since/--until` on the local clone, or the GitHub API). Use `--first-parent` so it's the merged-feature view, not every squashed sub-commit. The default branch isn't always `main`/`master` — check (`git remote show origin`); e.g. WLED-MM's is `mdev`. -> 1b. **Also investigate the issues over that month.** The REST `gh api repos///issues` endpoint returns **pull requests too** — filter them out (`--jq '.[] | select(.pull_request == null)'`) or use the GitHub **search** API, which already excludes them: `search/issues?q=repo:/+is:issue+created:` (and the same with `closed:`). Only real issues, not PRs. The commit log shows what shipped; the issues show what users *hit* and what the maintainers are prioritising — the two together are the real activity picture. Skim: notable bugs opened (recurring pain points, hardware quirks), fixes closed that map to a commit, and any heavily-discussed feature request or design thread. Fold the user-facing ones into the summary below (a widely-reported bug that got fixed, a feature the community is pushing for); an issue with no user-facing outcome yet is still worth a one-line "watching:" note if it signals a direction. Don't list every issue — surface the few that matter, the same bar as the commit summary. -> 2. **Split a month at any release boundary — but only if the release was cut from the branch you're summarising.** If a *versioned* release was published mid-month (check `git for-each-ref refs/tags` / the GitHub releases API; ignore rolling tags like `nightly` and prereleases), AND the tag is an ancestor of the digest branch (`git merge-base --is-ancestor `), split that month at the release date into `## (up to v)` / `## (post-v)`. If the tag is NOT an ancestor (the project cuts releases from a separate release branch — e.g. upstream WLED tags off `0_15`/release branches, not `main`), do NOT split: keep the month whole and just note which release shipped that month as context, since the trunk you're summarising feeds future releases rather than being the release line. Whole months with no in-branch release stay one section. -> 3. Write an **end-user-readable** summary: what changed that a *user of the library* would notice or care about — new features, new hardware/platform support, notable fixes, breaking changes. Skip internal refactors, CI, test-only, and dependency bumps unless they affect users. +> 1. Read the merged commits on the default branch with author-date in that calendar month (`git log --first-parent --since/--until` on the local clone, or the GitHub API). Use `--first-parent` so it's the merged-feature view, not every squashed sub-commit. The default branch isn't always `main`/`master`, check (`git remote show origin`); e.g. WLED-MM's is `mdev`. +> 1b. **Also investigate the issues over that month.** The REST `gh api repos///issues` endpoint returns **pull requests too**, filter them out (`--jq '.[] | select(.pull_request == null)'`) or use the GitHub **search** API, which already excludes them: `search/issues?q=repo:/+is:issue+created:` (and the same with `closed:`). Only real issues, not PRs. The commit log shows what shipped; the issues show what users *hit* and what the maintainers are prioritising, the two together are the real activity picture. Skim: notable bugs opened (recurring pain points, hardware quirks), fixes closed that map to a commit, and any heavily-discussed feature request or design thread. Fold the user-facing ones into the summary below (a widely-reported bug that got fixed, a feature the community is pushing for); an issue with no user-facing outcome yet is still worth a one-line "watching:" note if it signals a direction. Don't list every issue, surface the few that matter, the same bar as the commit summary. +> 2. **Split a month at any release boundary, but only if the release was cut from the branch you're summarising.** If a *versioned* release was published mid-month (check `git for-each-ref refs/tags` / the GitHub releases API; ignore rolling tags like `nightly` and prereleases), AND the tag is an ancestor of the digest branch (`git merge-base --is-ancestor `), split that month at the release date into `## (up to v)` / `## (post-v)`. If the tag is NOT an ancestor (the project cuts releases from a separate release branch, e.g. upstream WLED tags off `0_15`/release branches, not `main`), do NOT split: keep the month whole and just note which release shipped that month as context, since the trunk you're summarising feeds future releases rather than being the release line. Whole months with no in-branch release stay one section. +> 3. Write an **end-user-readable** summary: what changed that a *user of the library* would notice or care about, new features, new hardware/platform support, notable fixes, breaking changes. Skip internal refactors, CI, test-only, and dependency bumps unless they affect users. > 4. Format as **short bullet points**, each one line, plainest language, minimal jargon. Group only if there's a natural split (e.g. "New" / "Fixed"); otherwise a flat list. -> 5. Add it as a `## ` section to `docs/friend-repos/.md`, newest month on top. Don't editorialise or compare to projectMM — just report what they shipped. +> 4b. Follow [the documentation standards](../contributing/documentation-standards.md): American spelling, **no em-dashes** (use a comma, colon, parentheses or a full stop), and one line per paragraph with no hard wraps, since `check_docgen` reports all three as errors on the file you write. +> 5. Add it as a `## ` section to `docs/friend-repos/.md`, newest month on top. Don't editorialise or compare to projectMM, just report what they shipped. > 6. State the commit range / count **and the issue query** summarised so the digest is auditable. > -> When backfilling several months (e.g. since the last release), run this once per month for a consistent timeline, then optionally add a `## Since v — overview` intro at the top with 3–5 bullets naming the multi-month threads the per-month slices can't show on their own. +> When backfilling several months (e.g. since the last release), run this once per month for a consistent timeline, then optionally add a `## Since v, overview` intro at the top with 3–5 bullets naming the multi-month threads the per-month slices can't show on their own. diff --git a/docs/friend-repos/troyhacks-WLED.md b/docs/friend-repos/troyhacks-WLED.md index b1fbcb4b..67930c43 100644 --- a/docs/friend-repos/troyhacks-WLED.md +++ b/docs/friend-repos/troyhacks-WLED.md @@ -1,10 +1,10 @@ -# troyhacks/WLED — monthly activity digest +# troyhacks/WLED: monthly activity digest -What landed on [troyhacks/WLED](https://github.com/troyhacks/WLED)'s `mdev` branch, month by month. External-context reference — a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). +What landed on [troyhacks/WLED](https://github.com/troyhacks/WLED)'s `mdev` branch, month by month. External-context reference, a factual log of a friend repo's activity, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). This is a personal fork of [MoonModules/WLED-MM](https://github.com/MoonModules/WLED-MM) (the `mdev` branch), so much of the `mdev` traffic is merges from and alignments with the MM and upstream WLED lines; the bullets below pick out what a *user* of this fork would notice. Summarised via the GitHub commits API (all commits on `mdev`, not first-parent merges), filtering out build-number bumps, merge commits, typo/comment churn, and pure refactors. No versioned release is cut from this branch (it tracks `mdev` and ships nightly `mdev` builds), so months are kept whole. -**Branch note — the experiments live off `mdev`.** troyhacks branches heavily: `mdev` is the merge/alignment stream, but the distinctive work happens in named experimental branches (HDMI output, ESP32-P4, W5500 Ethernet, hardware-panel ports, voice control, a pure-IDFv5 port, a new settings subsystem). Those are *experiments*, not necessarily destined for `mdev`, so each month below carries a separate **Experimental branches** line for what moved on them — the frontier of what this fork is probing. +**Branch note, the experiments live off `mdev`.** troyhacks branches heavily: `mdev` is the merge/alignment stream, but the distinctive work happens in named experimental branches (HDMI output, ESP32-P4, W5500 Ethernet, hardware-panel ports, voice control, a pure-IDFv5 port, a new settings subsystem). Those are *experiments*, not necessarily destined for `mdev`, so each month below carries a separate **Experimental branches** line for what moved on them, the frontier of what this fork is probing. ## August 2026 @@ -18,22 +18,22 @@ _Checked: commits on `mdev` for author-date 2026-08-01..2026-08-31 (2: b537e0c9 No user-facing activity: no commits were merged to `mdev` in July 2026 (the branch's most recent commit is still dated 2026-05-20), and no versioned release was published. The repository's issue tracker is disabled, so no issues were opened or closed. -- **Experimental branches:** nothing moved in July either — the most-recently-touched branch, `P4_experimental` (ESP32-P4), had no July commit either, and neither did any other branch. +- **Experimental branches:** nothing moved in July either, the most-recently-touched branch, `P4_experimental` (ESP32-P4), had no July commit either, and neither did any other branch. -_Checked: merged commits on `mdev` for author-date 2026-07-01..2026-08-01 (0 commits); commits on `P4_experimental` for the same window (0); releases published in July 2026 (none); issue search `repo:troyhacks/WLED is:issue created:2026-07-01..2026-07-31` and `closed:2026-07-01..2026-07-31` (0 results — issues disabled on this fork)._ +_Checked: merged commits on `mdev` for author-date 2026-07-01..2026-08-01 (0 commits); commits on `P4_experimental` for the same window (0); releases published in July 2026 (none); issue search `repo:troyhacks/WLED is:issue created:2026-07-01..2026-07-31` and `closed:2026-07-01..2026-07-31` (0 results, issues disabled on this fork)._ ## June 2026 No user-facing activity: no commits were merged to `mdev` in June 2026 (the branch's most recent commit is dated 2026-05-20), and no versioned release was published. The repository's issue tracker is disabled, so no issues were opened or closed. -_Checked: merged commits on `mdev` for author-date 2026-06-01..2026-06-30 (0 commits); releases published in June 2026 (none); issue search `repo:troyhacks/WLED is:issue created:2026-06-01..2026-06-30` and `closed:2026-06-01..2026-06-30` (0 results — issues disabled on this fork)._ +_Checked: merged commits on `mdev` for author-date 2026-06-01..2026-06-30 (0 commits); releases published in June 2026 (none); issue search `repo:troyhacks/WLED is:issue created:2026-06-01..2026-06-30` and `closed:2026-06-01..2026-06-30` (0 results, issues disabled on this fork)._ ## May 2026 *~18 commits on `mdev`, 2026-05-01 … 2026-05-31.* -- Quieter month on `mdev` — alignment with upstream/MM, smaller effect and build-flag fixes; the heavy lifting was in the Nov–Jan window. -- **Experimental branches:** `P4_experimental` (ESP32-P4) and `M5Stack_Core_S3_Display` both saw work — the two most-recently-touched branches in the repo. +- Quieter month on `mdev`, alignment with upstream/MM, smaller effect and build-flag fixes; the heavy lifting was in the Nov–Jan window. +- **Experimental branches:** `P4_experimental` (ESP32-P4) and `M5Stack_Core_S3_Display` both saw work, the two most-recently-touched branches in the repo. ## April 2026 @@ -49,7 +49,7 @@ _Checked: merged commits on `mdev` for author-date 2026-06-01..2026-06-30 (0 com - Effect and 2D-matrix fixes; build-target and partition adjustments. - More upstream-compatibility alignment in the segment / bus drawing code. -- **Experimental branches:** `Pure_IDFv5_Port` (a from-scratch ESP-IDF v5 port — no Arduino) and `New-Settings-Subsystem` (settings rework with usermod auto-detection) both started. +- **Experimental branches:** `Pure_IDFv5_Port` (a from-scratch ESP-IDF v5 port, no Arduino) and `New-Settings-Subsystem` (settings rework with usermod auto-detection) both started. ## February 2026 @@ -86,14 +86,14 @@ _Checked: merged commits on `mdev` for author-date 2026-06-01..2026-06-30 (0 com **New** -- **WLEDPixelForge** — a new image and scrolling-text interface (`pxmagic`), with 1D GIF support, blur option, and version-14.x adaptations. +- **WLEDPixelForge**: a new image and scrolling-text interface (`pxmagic`), with 1D GIF support, blur option, and version-14.x adaptations. - Effect math sped up (up to ~3× faster); inlined hot-path color/segment functions; more segment/effect data allowed on PSRAM boards. - DDP-over-websockets / DDP-over-WS stability; E1.31 kill switch; `dnrgbw` realtime mode. **Fixed / hardened** - Large **preset/ledmap robustness pass**: fixed `presets.json` corruption (mutex protection on the write path), ledmap-parser robustness, reduced UI freeze when updating presets. -- Extensive **mutex / critical-section redesign** across segment and `bus.show` paths — fixes for semaphore leaks, "giving a semaphore never taken", and realtime-lock race conditions. +- Extensive **mutex / critical-section redesign** across segment and `bus.show` paths, fixes for semaphore leaks, "giving a semaphore never taken", and realtime-lock race conditions. ## November 2025 diff --git a/docs/friend-repos/wled-WLED.md b/docs/friend-repos/wled-WLED.md index f24a586f..9bb43201 100644 --- a/docs/friend-repos/wled-WLED.md +++ b/docs/friend-repos/wled-WLED.md @@ -1,8 +1,8 @@ -# WLED (upstream) — monthly activity digest +# WLED (upstream): monthly activity digest -What landed on [wled/WLED](https://github.com/wled/WLED)'s `main` branch, month by month. External-context reference — a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). +What landed on [wled/WLED](https://github.com/wled/WLED)'s `main` branch, month by month. External-context reference, a factual log of a friend repo's releases, not projectMM's own history or roadmap. Newest month on top. The reusable prompt that generates these lives in [README.md](index.md). -Months are **not** split at release dates: upstream WLED cuts releases from separate release branches (`0_15`, `16_x`), so the version tags aren't on `main` — `main` is the development trunk that feeds future releases. Each month notes which release shipped, as context. +Months are **not** split at release dates: upstream WLED cuts releases from separate release branches (`0_15`, `16_x`), so the version tags aren't on `main`, `main` is the development trunk that feeds future releases. Each month notes which release shipped, as context. ## August 2026 @@ -41,7 +41,7 @@ The month `main` switched to the **V5** platform: WLED's trunk moved from the ES **New** - Trunk builds move to ESP-IDF 5.3 / arduino-esp32 v3, opening the door to the newer chips (ESP32-C5, C6 and P4 build targets ride along). -- ESP-NOW now uses WLED's own code instead of the QuickESPNow library — faster, and roughly 10 KB more free memory on ESP32 (1.5 KB on ESP8266). +- ESP-NOW now uses WLED's own code instead of the QuickESPNow library, faster, and roughly 10 KB more free memory on ESP32 (1.5 KB on ESP8266). - New `esp32_eth_V4` build for Ethernet boards; ESP32-C6 boards get 4 MB and 8 MB builds. - Nightly builds renamed, and the web UI warns when you're running a development build. - Audio-reactive now compiles on all the newer chips. @@ -81,7 +81,7 @@ Post-16.0 stabilisation month: no new version tag (v16.0.0 shipped 2026-05-03 of - Better brownout detection and extended error codes aligned with WLED-MM. **Watching** -- Discussion opened on switching from plain gamma to an sRGB transfer function for better low-brightness accuracy (#5707), and on improving the Nodes/Instances page (#5711) — no shipped outcome yet. +- Discussion opened on switching from plain gamma to an sRGB transfer function for better low-brightness accuracy (#5707), and on improving the Nodes/Instances page (#5711), no shipped outcome yet. - Several v16.0 field reports still open: multi-controller sync losing color (#5705), UDP sync failing in AP mode (#5709), and OTA-update trouble on some boards (#5682, #5702). _Auditability: 43 commits on `main` with author-date 2026-06-01..2026-06-30 (`repos/wled/WLED/commits?sha=main`, first-line view; a few older-dated cherry-picks appear in-range and were excluded as non-June). Issues via `search/issues` for repo:wled/WLED created:2026-06-01..2026-06-30 (18 opened) and closed:2026-06-01..2026-06-30 (25 closed); only user-facing ones surfaced. No versioned release published in June (v16.0.0 was 2026-05-03), so no month split._ @@ -121,7 +121,7 @@ _Auditability: 43 commits on `main` with author-date 2026-06-01..2026-06-30 (`re **New / effects** -- **Full FastLED replacement** merged (#4615) — WLED's own color/math instead of the FastLED dependency. +- **Full FastLED replacement** merged (#4615), WLED's own color/math instead of the FastLED dependency. - Many new user_fx effects: Spinning Wheel, Color Clouds, Lava Lamp, Magma, Ants, Morse Code, Comet (fire particle system), a slow >4-hour transition FX, Tetris line-clear flash. - Scrolling-text FX gains custom fonts + international UTF-8; stencil blending mode; ESP32-C3 audio-reactive (DSP FFT + integer math); more macro/timer slots; longer max playlist duration. - OTA update page restyled (auto-sets download URL from `info.repo`); clearer UI tool icons. @@ -138,7 +138,7 @@ _Auditability: 43 commits on `main` with author-date 2026-06-01..2026-06-30 (`re - **Version scheme changed to Major.minor** (dropped the leading "0."), heading toward v16; bumped to 16.0.0-alpha. - New **Pin Info** page (used/available pins overview); UI settings readability improvements. -- Improved bus handling — free choice of bus driver in any order, better memory calculations; gamma lower-limit removed (enables inverse gamma correction, applied to segment brightness too). +- Improved bus handling, free choice of bus driver in any order, better memory calculations; gamma lower-limit removed (enables inverse gamma correction, applied to segment brightness too). - Extended CCT blending (exclusive blend, color-jump fix); full WiFi scan with BSSID apply; new ESP32-S3 8MB QSPI build; experimental ESP32-C5/C6 in the node list. **Fixed** diff --git a/docs/gettingstarted.md b/docs/gettingstarted.md index 324ae774..764dfffd 100644 --- a/docs/gettingstarted.md +++ b/docs/gettingstarted.md @@ -1,18 +1,13 @@ # Getting started -New to ESP32 or flashing firmware? You don't need to be. projectMM installs -straight from your web browser: no software to download, no command line. In a -few minutes you'll have lights running and the device on your network, and the -device's own web interface open in your browser ready to play with. +New to ESP32 or flashing firmware? You don't need to be. projectMM installs straight from your web browser: no software to download, no command line. In a few minutes you'll have lights running and the device on your network, and the device's own web interface open in your browser ready to play with. This guide has two chapters. **Chapter 1** gets projectMM onto your device. **Chapter 2** is a tour of the interface you land in afterwards, so you know what every part does and where to start building your own light show. **You need:** an ESP32 board, a USB cable that carries data (not charge-only), -and a **Chromium-based browser** on a computer (Google Chrome, Microsoft Edge, -or Opera (the installer uses the Web Serial API, which Safari and Firefox don't -support). +and a **Chromium-based browser** on a computer (Google Chrome, Microsoft Edge, or Opera (the installer uses the Web Serial API, which Safari and Firefox don't support). > Want the bigger picture of what projectMM is first? See the > [project overview](../README.md). @@ -23,8 +18,7 @@ support). ### 1. Open the installer and plug in -Open the **[web installer](https://moonmodules.org/projectMM/install/)** in -Chrome or Edge, then plug your ESP32 into a USB port. +Open the **[web installer](https://moonmodules.org/projectMM/install/)** in Chrome or Edge, then plug your ESP32 into a USB port. ![The web installer](assets/gettingstarted/01-01-installer-start.png) @@ -32,70 +26,49 @@ Chrome or Edge, then plug your ESP32 into a USB port. ### 2. Pick the USB port -Click **USB Port → Pick a port…**. Your browser shows a small list of connected -devices — choose the one that appeared when you plugged in the ESP32. (Not sure -which? Unplug, look at the list, plug back in — the new entry is your device.) +Click **USB Port → Pick a port…**. Your browser shows a small list of connected devices, choose the one that appeared when you plugged in the ESP32. (Not sure which? Unplug, look at the list, plug back in, the new entry is your device.) ![Selecting the USB port](assets/gettingstarted/01-02-select-port.png) -**Windows users — dialog says "No serial ports available"?** Windows doesn't -ship drivers for the USB-serial chips most ESP32 boards use (WCH CH340, Silicon -Labs CP2102). One-time install fixes it for every future flash — full -step-by-step + the download link is in -[building.md § Windows: USB-serial drivers](how-to/building.md#windows-usb-serial-drivers). +**Windows users, dialog says "No serial ports available"?** Windows doesn't +ship drivers for the USB-serial chips most ESP32 boards use (WCH CH340, Silicon Labs CP2102). One-time install fixes it for every future flash, full step-by-step + the download link is in [building.md § Windows: USB-serial drivers](how-to/building.md#windows-usb-serial-drivers). macOS and Linux ship these drivers built in, so it's a Windows-only step. -Once a port is chosen, the installer recognises the chip and tells you how many -devices match it, so you know you're on the right track before you pick one. +Once a port is chosen, the installer recognizes the chip and tells you how many devices match it, so you know you're on the right track before you pick one. ![Port selected, chip detected](assets/gettingstarted/01-03-port-selected.png) ### 3. Pick your device -Choose your device from the **Device** picker. Each card shows a picture, the -chip, and what the device can do (LEDs, WiFi, a button, a microphone…); click +Choose your device from the **Device** picker. Each card shows a picture, the chip, and what the device can do (LEDs, WiFi, a button, a microphone…); click **details** on any card to see exactly what it is and a link to its product page. ![Picking a device](assets/gettingstarted/01-04-pick-device.png) ![A device card with its details](assets/gettingstarted/01-05-device-details.png) -The little colored pills are the device's capabilities, and the color tells you -how ready each one is: +The little colored pills are the device's capabilities, and the color tells you how ready each one is: -- 🟢 **Green** — set up and working the moment you install. This capability is +- 🟢 **Green**, set up and working the moment you install. This capability is supported *and* already wired into the device's configuration. -- 🟡 **Yellow** — the firmware supports it, but it isn't pre-configured. It works +- 🟡 **Yellow**, the firmware supports it, but it isn't pre-configured. It works once you add and set up the matching module yourself in the UI (Chapter 2). -- 🟠 **Amber** — planned. The hardware has it, but there's no module for it yet — - it's on the to-do list. (Want to help? Building one is our usual loop: read the - product page and datasheet, pin the behavior as tests, then write the code to - pass them — [see how we work](../CLAUDE.md#principles).) +- 🟠 **Amber**, planned. The hardware has it, but there's no module for it yet: it's on the to-do list. (Want to help? Building one is our usual loop: read the product page and datasheet, pin the behavior as tests, then write the code to pass them, [see how we work](../CLAUDE.md#principles).) -So a green pill is "just works", a yellow one is "works, with a bit of setup", and -an amber one is "coming later". +So a green pill is "just works", a yellow one is "works, with a bit of setup", and an amber one is "coming later". -The setup panel then shows how your device is configured out of the box — the -modules and settings applied automatically when you install. +The setup panel then shows how your device is configured out of the box, the modules and settings applied automatically when you install. ![Device setup](assets/gettingstarted/01-06-device-setup.png) -Nothing is locked in: once the device is running you can change any of it later -in the UI (that's what Chapter 2 is all about). +Nothing is locked in: once the device is running you can change any of it later in the UI (that's what Chapter 2 is all about). -Leave **Release** and **Firmware** at their suggested values (the newest stable -build, and the firmware that matches your device). Tick **Erase chip first** only -if you're starting clean, switching firmware, or updating a 4 MB classic board -(esp32 / wrover / eth) from a release before v4.0. That last update must erase: -its partition layout changed ([MIGRATING](reference/MIGRATING.md)), and if the device already holds -config you care about, back it up first ("Back up a device's config first" on the -installer page): erasing wipes WiFi credentials and all settings, and the backup -brings them back after the flash (its report lists anything it could not carry). +Leave **Release** and **Firmware** at their suggested values (the newest stable build, and the firmware that matches your device). Tick **Erase chip first** only if you're starting clean, switching firmware, or updating a 4 MB classic board (esp32 / wrover / eth) from a release before v4.0. That last update must erase: +its partition layout changed ([MIGRATING](reference/MIGRATING.md)), and if the device already holds config you care about, back it up first ("Back up a device's config first" on the installer page): erasing wipes WiFi credentials and all settings, and the backup brings them back after the flash (its report lists anything it could not carry). ### 4. Click Install -The installer erases (if you asked it to) and writes the firmware. Just watch — -it takes under a minute. +The installer erases (if you asked it to) and writes the firmware. Just watch, it takes under a minute. ![Erasing](assets/gettingstarted/01-07-erasing.png) ![Installing](assets/gettingstarted/01-08-installing.png) @@ -105,40 +78,30 @@ it takes under a minute. What happens next depends on your device: - **WiFi:** enter your network name and password when prompted, then **Connect**. - (Click **Skip** to set WiFi up later from the device itself.) Restoring a config - backup? You can skip this step: join the device's `MM-XXXX` access point, open - `http://4.3.2.1`, and restore the backup in the File Manager (⟲), then take the - offered restart: the bundle carries the WiFi credentials, so the device joins - your network by itself. + (Click **Skip** to set WiFi up later from the device itself.) Restoring a config backup? You can skip this step: join the device's `MM-XXXX` access point, open `http://4.3.2.1`, and restore the backup in the File Manager (⟲), then take the offered restart: the bundle carries the WiFi credentials, so the device joins your network by itself. ![Entering WiFi credentials](assets/gettingstarted/01-09-wifi-credentials.png) -- **Ethernet:** plug in the cable — it connects on its own, no password needed. +- **Ethernet:** plug in the cable, it connects on its own, no password needed. ### 6. Open your device -When it's online, the installer shows a link — your device's address on your -network. Click it. +When it's online, the installer shows a link, your device's address on your network. Click it. ![Device is online over WiFi](assets/gettingstarted/01-10-online-wifi.png) -You'll see this same "Device is online!" box however your device connected — over -Ethernet, or when it rejoins a network it already knows: +You'll see this same "Device is online!" box however your device connected, over Ethernet, or when it rejoins a network it already knows: ![Online over Ethernet](assets/gettingstarted/01-11-online-ethernet.png) ![Online on an address it already had](assets/gettingstarted/01-12-online-existing-ip.png) -That's it — projectMM is installed and on your network. The link opens the -device's own web interface, served straight from the ESP32. Let's look around. +That's it, projectMM is installed and on your network. The link opens the device's own web interface, served straight from the ESP32. Let's look around. --- -## Chapter 2 — Your projectMM interface +## Chapter 2: Your projectMM interface -Everything below runs **in your browser, live from the device**. There's no app, -no account, no cloud — the ESP32 itself serves this page, and every change you -make takes effect on the lights immediately. Open the link from step 6 and follow -along; you can't break anything by exploring. +Everything below runs **in your browser, live from the device**. There's no app, no account, no cloud, the ESP32 itself serves this page, and every change you make takes effect on the lights immediately. Open the link from step 6 and follow along; you can't break anything by exploring. ### The layout: list, preview, controls @@ -146,86 +109,68 @@ along; you can't break anything by exploring. Three regions, left to right: -- **The module list** (left) — every part of your device, from system info at the +- **The module list** (left), every part of your device, from system info at the top to your light setup at the bottom. Click a name to jump to it. - **The 3D preview** (center): a live picture of your lights in their real shape, updating as the effects run. This is what your physical LEDs are doing, right now. -- **The controls** (right) — the settings for each module. Drag a slider or pick an +- **The controls** (right), the settings for each module. Drag a slider or pick an option and the lights react instantly. -Every module header carries a **⏻ power button** — it turns that module on or off. -Bright (accent-colored) means on; dimmed means off. A switched-off module simply -stops running — it stays in place with all its settings, so flicking it back on -picks up right where it left off. It's the quick way to mute an effect or an output -for a moment without deleting anything. +Every module header carries a **⏻ power button**, it turns that module on or off. +Bright (accent-colored) means on; dimmed means off. A switched-off module simply stops running, it stays in place with all its settings, so flicking it back on picks up right where it left off. It's the quick way to mute an effect or an output for a moment without deleting anything. -You'll also spot two little read-outs in each header: **🕒** is how fast that module -runs (its loop speed — click it to flip between fps and microseconds), and **🧠** is -how much memory it uses. They let you see at a glance what each part is costing. +You'll also spot two little read-outs in each header: **🕒** is how fast that module runs (its loop speed, click it to flip between fps and microseconds), and **🧠** is how much memory it uses. They let you see at a glance what each part is costing. -The interface adapts to your window. On a narrower screen the controls take the -full width and the preview tucks into a floating thumbnail you can move around: +The interface adapts to your window. On a narrower screen the controls take the full width and the preview tucks into a floating thumbnail you can move around: -![Medium width — preview as a floating thumbnail](assets/gettingstarted/02-02-UI-mid.png) +![Medium width, preview as a floating thumbnail](assets/gettingstarted/02-02-UI-mid.png) -Narrower still, it stacks into a single scrollable column — so it works on a -phone, standing next to your lights: +Narrower still, it stacks into a single scrollable column, so it works on a phone, standing next to your lights: -![Small width — single column](assets/gettingstarted/02-03-UI-small.png) +![Small width, single column](assets/gettingstarted/02-03-UI-small.png) ### The 3D preview ![The 3D preview, lights numbered](assets/gettingstarted/02-04-UI-Preview.png) -Drag to rotate, scroll to zoom. Each dot is one light at its real position, lit -with the color it's showing this instant. Turn on the numbers to see each light's -index — handy when you're wiring or mapping a layout. The preview is a *view* of -the device; it never slows the lights down, and it gracefully eases off (fewer -updates, then fewer points) on a slow connection rather than stalling. +Drag to rotate, scroll to zoom. Each dot is one light at its real position, lit with the color it's showing this instant. Turn on the numbers to see each light's index, handy when you're wiring or mapping a layout. The preview is a *view* of the device; it never slows the lights down, and it gracefully eases off (fewer updates, then fewer points) on a slow connection rather than stalling. > More on how the preview streams from the device: > [PreviewDriver](moonmodules/light/moxygen/PreviewDriver.md). ### The system modules -The top of the list is your device's "about" section — read-outs and connection -settings. You rarely need to touch these, but they're the first place to look if -something seems off. +The top of the list is your device's "about" section, read-outs and connection settings. You rarely need to touch these, but they're the first place to look if something seems off. -**System** — who this device is and how it's doing: its name, the device model, -uptime, frame rate, and live memory / storage bars. You may also see an **Audio** -module here — devices with a built-in mic come with it set up for you, and on any -device you can add it yourself (it's how audio-reactive effects hear the music). -Audio is just the first of many: any sensor or input — from hardware or over the -network — lives here as its own module, and we're adding more all the time. +**System**, who this device is and how it's doing: its name, the device model, +uptime, frame rate, and live memory / storage bars. You may also see an **Audio** module here, devices with a built-in mic come with it set up for you, and on any device you can add it yourself (it's how audio-reactive effects hear the music). +Audio is just the first of many: any sensor or input, from hardware or over the network, lives here as its own module, and we're adding more all the time. ![The System module](assets/gettingstarted/02-05-UI-System.png) > [SystemModule](moonmodules/core/system.md#system) · > [Audio](moonmodules/core/services.md#audio) -**Firmware** — which build you're running, and where you update it. The -**Install** button here does an over-the-air update straight from the device — no +**Firmware**, which build you're running, and where you update it. The +**Install** button here does an over-the-air update straight from the device, no USB cable needed once it's on your network. ![The Firmware module](assets/gettingstarted/02-06-UI-Firmware.png) -**Updating from an older build?** Skim the [migration notes](reference/MIGRATING.md) first. Most updates need nothing — the device keeps your settings — but a breaking change is listed there with the one action it costs you (usually re-setting or re-adding a control). +**Updating from an older build?** Skim the [migration notes](reference/MIGRATING.md) first. Most updates need nothing, the device keeps your settings, but a breaking change is listed there with the one action it costs you (usually re-setting or re-adding a control). > [FirmwareUpdateModule](moonmodules/core/system.md#firmware-update) -**Network** — your connection: WiFi or Ethernet, signal strength, and the -address others reach it at. The **Devices** section underneath finds other -projectMM devices on the same network, so a roomful of them can discover each -other. +**Network**, your connection: WiFi or Ethernet, signal strength, and the +address others reach it at. The **Devices** section underneath finds other projectMM devices on the same network, so a roomful of them can discover each other. ![The Network module](assets/gettingstarted/02-07-UI-Network.png) > [NetworkModule](moonmodules/core/system.md#network) · > [DevicesModule](moonmodules/core/system.md#devices) -> **Lights are just one use.** Everything above — the modules, the live controls, the -> 3D view, the web UI, the networking — is a general-purpose engine that knows nothing +> **Lights are just one use.** Everything above, the modules, the live controls, the +> 3D view, the web UI, the networking, is a general-purpose engine that knows nothing > about LEDs. The light show below is one *domain* built on top of it; you could build > a different one and reuse all the same machinery. [FastLED-MM](https://github.com/MoonModules/FastLED-MM) > is an example, driving its LEDs with [FastLED](https://github.com/FastLED/FastLED) (on @@ -233,12 +178,7 @@ other. ### Control it from your phone with WLED Native -The device's own web UI works on a phone, but for quick on/off and brightness from -your pocket there's a nicer option: **WLED Native**, the open-source mobile app for -the WLED ecosystem. projectMM speaks the WLED JSON API and announces itself over the -network the same way a WLED device does, so the app finds your projectMM controllers -automatically — no setup, no pairing. Each one shows up as a card with a power toggle -and a brightness slider, so a roomful of controllers is a scroll and a tap away. +The device's own web UI works on a phone, but for quick on/off and brightness from your pocket there's a nicer option: **WLED Native**, the open-source mobile app for the WLED ecosystem. projectMM speaks the WLED JSON API and announces itself over the network the same way a WLED device does, so the app finds your projectMM controllers automatically, no setup, no pairing. Each one shows up as a card with a power toggle and a brightness slider, so a roomful of controllers is a scroll and a tap away. ![projectMM devices discovered in WLED Native](assets/core/WLED%20Native%20discovers%20projectMM.jpeg){ width="300" } @@ -249,110 +189,81 @@ Get it free for your phone: WLED Native is by **Christophe Gagnier ([@Moustachauve](https://github.com/Moustachauve))**, who wrote both the [Android](https://github.com/Moustachauve/WLED-Android) and [iOS](https://github.com/Moustachauve/WLED-iOS) apps. Their open source is what let us work out exactly what those apps read, so a projectMM device appears in them without either side needing to know about the other. -For the full picture and controls, the device's web interface is always there at -`http://.local` — WLED Native is the fast everyday remote alongside it. +For the full picture and controls, the device's web interface is always there at `http://.local`, WLED Native is the fast everyday remote alongside it. ### Bring it into your smart home with Home Assistant -Want your lights in the same dashboard as the rest of your house — and in -automations, voice assistants, and Apple Home? projectMM adopts into **Home -Assistant** like any other light: point the device at your HA setup and it appears -as a light entity with on/off and brightness, alongside a floor of other devices. +Want your lights in the same dashboard as the rest of your house, and in automations, voice assistants, and Apple Home? projectMM adopts into **Home Assistant** like any other light: point the device at your HA setup and it appears as a light entity with on/off and brightness, alongside a floor of other devices. ![projectMM devices as lights in a Home Assistant dashboard](assets/core/ha-integration.png){ width="600" } -There are two ways in — zeroconf (HA finds the device on its own) or MQTT -auto-discovery (for a broker-only or cross-subnet setup) — and from there you can -bridge the entity into Apple Home too. The step-by-step, including installing HA -and the MQTT broker if you don't have them, is in the -[home automation guide](how-to/home-automation.md). +There are two ways in, zeroconf (HA finds the device on its own) or MQTT auto-discovery (for a broker-only or cross-subnet setup), and from there you can bridge the entity into Apple Home too. The step-by-step, including installing HA and the MQTT broker if you don't have them, is in the [home automation guide](how-to/home-automation.md). ### Building a light show: layouts → layers → drivers The bottom three modules are where the fun is. They form a simple pipeline: a **layout** says where your lights are, **layers** decide what colors play on -them, and **drivers** send the result out to the real world. Add modules with the -dashed **+ add module** button under each one. +them, and **drivers** send the result out to the real world. Add modules with the dashed **+ add module** button under each one. -**Layouts** — the shape of your lights. The default **Grid** is a width × height -(× depth) of pixels; change the numbers and the preview reshapes instantly. Turn -on **serpentine** if your strip zig-zags back and forth. +**Layouts**, the shape of your lights. The default **Grid** is a width × height +(× depth) of pixels; change the numbers and the preview reshapes instantly. Turn on **serpentine** if your strip zig-zags back and forth. ![The Layouts module](assets/gettingstarted/02-08-UI-Layouts.png) > [Layouts](moonmodules/light/supporting.md) -**Effects** — what plays on the lights. Add an **effect** (a moving pattern), stack -several to blend them, and reshape them with **modifiers** (mirror, rotate, and -more). Each effect has its own controls — speed, color mode, and so on — that you -tweak live. +**Effects**, what plays on the lights. Add an **effect** (a moving pattern), stack +several to blend them, and reshape them with **modifiers** (mirror, rotate, and more). Each effect has its own controls, speed, color mode, and so on, that you tweak live. ![The Effects module](assets/gettingstarted/02-09-UI-Layers.png) > [Effects](moonmodules/light/supporting.md) · [Layer](moonmodules/light/supporting.md) -**Drivers** — where the colors go. Set overall **brightness** and color order, -then add an output: real LED strips on a pin, or send the frame over the network -(ArtNet, E1.31/sACN, DDP) to other devices or lighting software. +**Drivers**, where the colors go. Set overall **brightness** and color order, +then add an output: real LED strips on a pin, or send the frame over the network (ArtNet, E1.31/sACN, DDP) to other devices or lighting software. ![The Drivers module](assets/gettingstarted/02-10-UI-Drivers.png) > [Drivers](moonmodules/light/supporting.md) · > [NetworkSendDriver](moonmodules/light/moxygen/NetworkSendDriver.md) -That's the whole picture: **layout → layers → drivers**, previewed in 3D, all -tuned live in your browser. Pick an effect, drag a slider, watch the lights — then -keep going. +That's the whole picture: **layout → layers → drivers**, previewed in 3D, all tuned live in your browser. Pick an effect, drag a slider, watch the lights, then keep going. --- ### If your device shows MoonBase **MoonBase** is a small recovery image built into your device. If a firmware update -is interrupted, or an installed firmware does not start, your device boots MoonBase -instead of going dark, and its page offers you three ways out: +is interrupted, or an installed firmware does not start, your device boots MoonBase instead of going dark, and its page offers you three ways out: - **Boot the app** puts you straight back if the firmware is still fine. Try this first: it changes nothing on the device. - **From a file** installs a firmware you have already downloaded. Get the - `firmware-...bin` matching your device from the - [releases page](https://github.com/MoonModules/projectMM/releases). + `firmware-...bin` matching your device from the [releases page](https://github.com/MoonModules/projectMM/releases). - **From a URL** downloads and installs in one step. The releases page gives you a link to each file; paste it here and your device fetches it directly. Installing takes a few minutes, and the page reports its progress as it downloads. Your device reboots into the new firmware on its own when it finishes. -Two things worth knowing. A failed install **stays** in MoonBase rather than -pretending to have worked, so you can simply try again. And you cannot break a -device this way: MoonBase is never overwritten by an update, so it is still there -for the next attempt, including after a power cut in the middle of one. +Two things worth knowing. A failed install **stays** in MoonBase rather than pretending to have worked, so you can simply try again. And you cannot break a device this way: MoonBase is never overwritten by an update, so it is still there for the next attempt, including after a power cut in the middle of one. -If your device is not on your network at all, MoonBase opens its own WiFi access -point and is reachable at **4.3.2.1** once you join it. +If your device is not on your network at all, MoonBase opens its own WiFi access point and is reachable at **4.3.2.1** once you join it. -MoonBase shows its own version on its page, and your device's Firmware card shows -which MoonBase it carries. If that version is marked outdated, the same card installs -a newer one over the network, so keeping the recovery image current needs no cable. +MoonBase shows its own version on its page, and your device's Firmware card shows which MoonBase it carries. If that version is marked outdated, the same card installs a newer one over the network, so keeping the recovery image current needs no cable. -That update runs from the app, because only the running app can write the partition -MoonBase lives in. So it is a way to keep MoonBase fresh, not a way back from a -device that will not start: if the app cannot run, or MoonBase itself will not boot, -that still takes a cable. Your device checks the image first, refusing anything whose -magic bytes, chip or description say it is not a MoonBase image for this chip. +That update runs from the app, because only the running app can write the partition MoonBase lives in. So it is a way to keep MoonBase fresh, not a way back from a device that will not start: if the app cannot run, or MoonBase itself will not boot, that still takes a cable. Your device checks the image first, refusing anything whose magic bytes, chip or description say it is not a MoonBase image for this chip. --- ### Where to go next -- **Understand the pipeline** — how layouts, layers, effects, modifiers and +- **Understand the pipeline**: how layouts, layers, effects, modifiers and drivers fit together: [architecture overview](explanation/architecture/moonlight.md#the-pipeline). -- **Run it on your computer** instead of (or alongside) an ESP32 — macOS, Windows, +- **Run it on your computer** instead of (or alongside) an ESP32, macOS, Windows, Linux: [project overview → Getting started](../README.md#getting-started). - **Manage several devices, build, and flash from one console** with MoonDeck, our developer tool: [MoonDeck guide](../moondeck/MoonDeck.md). - **Build from source** or target Teensy / Raspberry Pi: [building.md](how-to/building.md). -Stuck, or something didn't work? Open an -[issue](https://github.com/MoonModules/projectMM/issues) — and tell us what device -you used and where it stopped. +Stuck, or something didn't work? Open an [issue](https://github.com/MoonModules/projectMM/issues), and tell us what device you used and where it stopped. diff --git a/docs/how-to/building.md b/docs/how-to/building.md index 6b3a6bbc..968d9f08 100644 --- a/docs/how-to/building.md +++ b/docs/how-to/building.md @@ -55,25 +55,16 @@ The distinction matters because a shipped binary is launched from a download fol ### Editor setup (clangd) -Diagnostics appear **as you type**, from the same [`.clang-tidy`](../.clang-tidy) config CI -uses, so a finding shows up while the code is still in your head, not ten minutes later in a -pipeline. +Diagnostics appear **as you type**, from the same [`.clang-tidy`](../.clang-tidy) config CI uses, so a finding shows up while the code is still in your head, not ten minutes later in a pipeline. -Once per machine: install the **clangd** extension (`llvm-vs-code-extensions.vscode-clangd`) -and **disable Microsoft's C/C++ IntelliSense**, running both produces duplicated and -contradictory diagnostics. Nothing else to configure: [`.clangd`](../.clangd) at the repo root -points at the compilation database, and `CMAKE_EXPORT_COMPILE_COMMANDS` (set in -`CMakeLists.txt`) means any normal build refreshes it. +Once per machine: install the **clangd** extension (`llvm-vs-code-extensions.vscode-clangd`) and **disable Microsoft's C/C++ IntelliSense**, running both produces duplicated and contradictory diagnostics. Nothing else to configure: [`.clangd`](../.clangd) at the repo root points at the compilation database, and `CMAKE_EXPORT_COMPILE_COMMANDS` (set in `CMakeLists.txt`) means any normal build refreshes it. Two things worth knowing: - **If every file reports `'cstdint' file not found`**: the build directory was configured - with a different compiler than clangd is. `.clangd`'s `--query-driver` handles the usual - cases; if a new toolchain appears, add it there. This failure is loud and total, real - diagnostics disappear behind it, so it is worth recognizing on sight. + with a different compiler than clangd is. `.clangd`'s `--query-driver` handles the usual cases; if a new toolchain appears, add it there. This failure is loud and total, real diagnostics disappear behind it, so it is worth recognizing on sight. - **clangd runs a subset of the CI check set**: skipping checks it considers slow (>10% - AST-build cost). That is deliberate and means the same config file is safe to share: CI - remains the authority. + AST-build cost). That is deliberate and means the same config file is safe to share: CI remains the authority. ### Packaging @@ -100,8 +91,7 @@ Every host needs [uv](https://docs.astral.sh/uv/), CMake 3.20+, and a C++20 comp ### Docker -The desktop build runs in a container, which is the whole system without an ESP32: same effect -pipeline, same web UI, same driver stack, driving real fixtures over Art-Net, DDP and E1.31. +The desktop build runs in a container, which is the whole system without an ESP32: same effect pipeline, same web UI, same driver stack, driving real fixtures over Art-Net, DDP and E1.31. ```sh docker compose up -d # then open http://localhost:8081/ @@ -116,20 +106,12 @@ docker run -d --name projectmm -p 8081:8080 -v projectmm:/data \ ghcr.io/moonmodules/projectmm:latest --no-browser ``` -`--no-browser` because a container has no browser to open: without it the start prints a line -saying it could not open one, which is noise rather than a failure. The Compose service passes it -already. +`--no-browser` because a container has no browser to open: without it the start prints a line saying it could not open one, which is noise rather than a failure. The Compose service passes it already. -`:latest` follows the rolling prerelease, the same build the installer page offers; a version tag -like `:4.0.0` pins one. Images are published by the release workflow from the same `.deb` that -release ships, so the image and the binary are the same build. +`:latest` follows the rolling prerelease, the same build the installer page offers; a version tag like `:4.0.0` pins one. Images are published by the release workflow from the same `.deb` that release ships, so the image and the binary are the same build. **Upgrading preserves everything.** The image holds only the binary and all state lives in the -volume, so an upgrade keeps settings, presets, scripts and the device's identity. Which command -depends on which source the compose file names: `docker compose pull && docker compose up -d` for -a published `image:`, and `docker compose build --pull && docker compose up -d` for the shipped -`build: .`, where `pull` alone would fetch nothing and `up` would rebuild from the checkout. Only `docker compose down -v` wipes it, and only a mounted volume is preserved -at all: a bare `docker run` with no `-v` loses its state when the container goes. +volume, so an upgrade keeps settings, presets, scripts and the device's identity. Which command depends on which source the compose file names: `docker compose pull && docker compose up -d` for a published `image:`, and `docker compose build --pull && docker compose up -d` for the shipped `build: .`, where `pull` alone would fetch nothing and `up` would rebuild from the checkout. Only `docker compose down -v` wipes it, and only a mounted volume is preserved at all: a bare `docker run` with no `-v` loses its state when the container goes. | | | |---|---| @@ -141,19 +123,13 @@ at all: a bare `docker run` with no `-v` loses its state when the container goes | **Capabilities** | none; it binds its port as an ordinary process | **When host networking is needed.** Unicast output to a fixture works over ordinary bridge -networking. Discovery and the broadcast or multicast output modes do not cross a bridge, so those -want `network_mode: host` (or an L2 CNI on Kubernetes). With host networking there is no port -mapping, so pass `--port 8081` in `command:` to stay clear of anything already on 8080. +networking. Discovery and the broadcast or multicast output modes do not cross a bridge, so those want `network_mode: host` (or an L2 CNI on Kubernetes). With host networking there is no port mapping, so pass `--port 8081` in `command:` to stay clear of anything already on 8080. **Several instances** run side by side with no conflict: each container has its own port space, so -they all listen on 8080 internally with different published ports, and each generates its own -identity so they are distinguishable on the network. CPU is the practical limit rather than memory -(measured at ~5 MB and about one core each, since the desktop build renders as fast as it is -allowed); cap it with `cpus:` in compose when running a fleet. +they all listen on 8080 internally with different published ports, and each generates its own identity so they are distinguishable on the network. CPU is the practical limit rather than memory (measured at ~5 MB and about one core each, since the desktop build renders as fast as it is allowed); cap it with `cpus:` in compose when running a fleet. **amd64 only** for now: the release ships no arm64 Linux binary. On an Apple-silicon Mac or an ARM -server the compose file's `platform: linux/amd64` runs it under emulation, which works but is -slower than native. +server the compose file's `platform: linux/amd64` runs it under emulation, which works but is slower than native. ## ESP32 @@ -189,13 +165,9 @@ uv run moondeck/build/flash_esp32.py --firmware esp32 --port /dev/tty.usbserial- uv run moondeck/run/monitor_esp32.py --port /dev/tty.usbserial-XXXX ``` -On the variants that opt into it (`esp32`, `esp32-16mb`, `esp32-wrover`, `esp32-eth`, -`esp32s3-zero`, and `qemu`, which is emulated rather than installable) the build also produces +On the variants that opt into it (`esp32`, `esp32-16mb`, `esp32-wrover`, `esp32-eth`, `esp32s3-zero`, and `qemu`, which is emulated rather than installable) the build also produces **MoonBase**, the second boot image ([MoonBase](../explanation/architecture/moonbase.md)), -and `flash_esp32.py` writes the corrected layout in one pass: app in the big `ota_0` slot, -MoonBase in `factory`, and an otadata that boots the app directly. A device on the older -dual-OTA table adopts this layout only through such a full serial flash, OTA never rewrites -the partition table. +and `flash_esp32.py` writes the corrected layout in one pass: app in the big `ota_0` slot, MoonBase in `factory`, and an otadata that boots the app directly. A device on the older dual-OTA table adopts this layout only through such a full serial flash, OTA never rewrites the partition table. `setup_esp_idf.py` runs the upstream installer for the host: `install.sh` on macOS/Linux, `install.bat` on Windows. Both create the same `~/.espressif/python_env/...` venv and download the same toolchains (~1.5 GB more) — only the wrapper differs. The Windows installer needs roughly 5 minutes on a fast link. It also offers to move a drifted checkout onto the pinned commit (see [ESP-IDF version](#esp-idf-version)); pass `--no-checkout` to keep it warn-only. @@ -205,7 +177,7 @@ the partition table. (cd ~/esp/esp-idf && ./install.sh esp32s31) # one-time, adds the S31 RISC-V toolchain ``` -Flash the S31 over USB with the CLI (`flash_esp32.py --firmware esp32s31 --port `), **not** the web installer: the browser flasher (`esptool-js`) has no S31 chip definition, so a browser flash fails, but the CLI's `esptool.py` supports it. The web installer surfaces the same guidance if you try. (Status + the condition to enable web flashing: [backlog](../work/future/index.md).) +Flash the S31 from the web installer or with the CLI (`flash_esp32.py --firmware esp32s31 --port `), whichever suits. Browser flashing works from esptool-js 0.7.0, which identifies the chip by its id rather than by a ROM magic value the S31 shares with the classic ESP32. On Windows, the `--port` argument is a `COM*` name (e.g. `COM3`) instead of `/dev/tty.usbserial-XXXX`. MoonDeck's port picker enumerates `COM*` automatically. @@ -320,10 +292,7 @@ The Ethernet PHY type and pin map are runtime config, not baked into the build: ### Flashing a running device over the network -A board already on the network is updated over HTTP, with no cable. Which route to use depends on -whether the variant carries [MoonBase](../explanation/architecture/moonbase.md): a board -cannot rewrite the partition it is executing from, so on a MoonBase variant the app hands over to -MoonBase and MoonBase does the writing. +A board already on the network is updated over HTTP, with no cable. Which route to use depends on whether the variant carries [MoonBase](../explanation/architecture/moonbase.md): a board cannot rewrite the partition it is executing from, so on a MoonBase variant the app hands over to MoonBase and MoonBase does the writing. **On a MoonBase variant** (`esp32`, `esp32-16mb`, `esp32s3-zero`, and any variant `build_esp32.py` builds MoonBase alongside), it is two requests: @@ -337,37 +306,26 @@ curl --http1.1 -H "Expect:" --data-binary @build/esp32-/projectMM.bin http:///api/firmware/upload ``` -The second request ends with **no HTTP status** (curl reports 000): the device reboots into the new -image as the write completes, so the socket closes before a response arrives. That is success, not -failure. Confirm by reading the build back: +The second request ends with **no HTTP status** (curl reports 000): the device reboots into the new image as the write completes, so the socket closes before a response arrives. That is success, not failure. Confirm by reading the build back: ```sh curl -s http:///api/modules/Firmware # the `build` control names the commit and date ``` -MoonBase serves the same route names as the application, so a page driving an update keeps calling -the same paths after the handover. It also installs unattended from a URL, which is what the UI's -update button uses: `POST /api/firmware/url` with the URL as the body. `POST /api/firmware/boot-app` -returns to the application without installing anything, and only boots an image that validates. +MoonBase serves the same route names as the application, so a page driving an update keeps calling the same paths after the handover. It also installs unattended from a URL, which is what the UI's update button uses: `POST /api/firmware/url` with the URL as the body. `POST /api/firmware/boot-app` returns to the application without installing anything, and only boots an image that validates. **Without MoonBase**, the application takes the image directly on the same route, -`POST /api/firmware/upload`. It is one of only two streaming routes (`/api/file` is the other), so -the body may exceed the request buffer; every other route rejects an oversized body with 413. +`POST /api/firmware/upload`. It is one of only two streaming routes (`/api/file` is the other), so the body may exceed the request buffer; every other route rejects an oversized body with 413. Two failure modes are worth recognizing, because both look like something else: - **413 from `/api/firmware/upload`** on a MoonBase variant means the request reached the - APPLICATION rather than MoonBase, and the app rejected an oversized body on a route it does not - stream. The device did not reboot into MoonBase, or booted back before the upload. Check with - `GET /moonbase`, which MoonBase answers and the app 404s. + APPLICATION rather than MoonBase, and the app rejected an oversized body on a route it does not stream. The device did not reboot into MoonBase, or booted back before the upload. Check with `GET /moonbase`, which MoonBase answers and the app 404s. - **`{"error":"incomplete request body"}`** from `/api/firmware/upload` means the body did not - arrive within the read window. Send with `--http1.1 -H "Expect:"` so the transfer starts - immediately instead of waiting for a `100 Continue` the device does not send. + arrive within the read window. Send with `--http1.1 -H "Expect:"` so the transfer starts immediately instead of waiting for a `100 Continue` the device does not send. **A partition-table change needs a cable.** OTA writes the app, never the table, so a device on an -older layout adopts a new one only through a full serial flash (see the note under -[Firmware variants](#firmware-variants)). On the 4 MB classic that migration also moves the -filesystem, so the device comes back unprovisioned. +older layout adopts a new one only through a full serial flash (see the note under [Firmware variants](#firmware-variants)). On the 4 MB classic that migration also moves the filesystem, so the device comes back unprovisioned. ## Teensy diff --git a/docs/how-to/faq.md b/docs/how-to/faq.md new file mode 100644 index 00000000..38d861ca --- /dev/null +++ b/docs/how-to/faq.md @@ -0,0 +1,52 @@ +# FAQ + +Questions people ask on a first run, in the order they meet them: installing, then first light, then the interface they are looking at, then effects, then the driver that reaches real hardware. + +A symptom rather than a question belongs in [Troubleshooting](troubleshooting.md), which starts from what you can see. + +## Installing + +- **Which firmware does my board take?** The web installer picks it from the board you choose. See [Install and first light](../gettingstarted.md). +- **Can I run it without a board?** Yes, on macOS, Linux and Windows. See [Installing to desktop](installing-to-desktop.md). +- **Which hardware do the heavy effects want?** The ones computing a field per light cost CPU, so a desktop or a small always-on machine runs them where a microcontroller cannot. See [Installing on Linux](installing-on-linux.md). +- **How do I update?** Over the air from the Firmware card, or by URL. See [Updating firmware](updating-firmware.md). + +## First light + +- **Where do I set the number of lights?** On a Layout, not on the driver. See [Layouts](../moonmodules/light/layouts.md). +- **Where do I add an effect?** Expand a Layer under Effects, then **add module**: a layer is what composites effects and holds the blend mode. See [Build your first light show](../tutorials/first-light-show.md). +- **Nothing lights up.** See [Troubleshooting](troubleshooting.md#the-lights-are-dark). + +## The interface + +- **What is the number on a module card?** That module's own tick time, shown from expert mode up. Hold it to peek at the rate, which inverts it: a module ticking in 22 µs reads as 45K fps. The device's frame rate is `fps` on the [System module](../moonmodules/core/system.md#system). +- **The card is busy, and I do not need half of it.** Set `mode` on the [System module](../moonmodules/core/system.md#system) to `user`, `expert` or `developer`: each control names the mode it needs. +- **The preview changed resolution.** It follows the layout, so a geometry change moves it. See [Preview](../moonmodules/light/drivers.md#preview). + +## Effects and scripting + +- **Can I write my own effect?** Yes, in MoonLive, on the device, live. See [Write your first script](../tutorials/first-script.md). +- **How do I save a look?** Presets. See [Presets](presets.md). +- **Can I drive it from a control surface or a DAW?** Over MIDI, OSC and DMX. See [Control surface](control-surface.md). + +## Drivers and panels + +- **Which driver do I need?** One per output kind: LEDs on pins, a panel card over Ethernet, HUB75 direct, or a network protocol. See [Drivers](../moonmodules/light/drivers.md). +- **Which board drives a panel card?** One with gigabit Ethernet, so an S31 or a desktop. An S3 or P4 is 100 Mbit and wants a gigabit switch between it and the card. See [Panel cards](panel-cards.md#the-one-hardware-fact-that-decides-everything). +- **My ColorLight card does almost nothing.** These cards need a 1000 Mbps link, and the driver reports the negotiated speed in its status line. See [Panel cards](panel-cards.md). +- **My HUB75 panel is one column out.** Set `clockEdge` to `falling`: some panel chips sample the shift clock on the other edge. See [Drivers](../moonmodules/light/drivers.md). +- **The HLS stream lags by seconds.** HLS buffers whole segments, so the delay is the format. For a live view use [Preview](../moonmodules/light/drivers.md#preview) instead of [HLS](../moonmodules/light/drivers.md#hls). + +## Network + +- **Ethernet and WiFi fight.** Pick one in the [Network module](../moonmodules/core/system.md#network)'s `mode`, because both leaves two default routes. + +## Developing + +- **What is MoonDeck, and do I need it?** A browser console that builds, flashes, runs, tests and checks the project, and drives the boards on your bench. Everything it offers is a script under `moondeck/`, so the CLI and the console run the same code and neither is required. See [MoonDeck](../explanation/architecture/moondeck.md). +- **Why not PlatformIO or pioarduino?** The ESP32 build is ESP-IDF-native, tracking IDF pre-releases against a pinned commit for chips like the P4 and S31, and the hot-path drivers use the vendor APIs directly. The tooling also covers far more than compile-upload-monitor. See [Building](building.md#moondeck-the-dev-console). +- **How do I start?** `uv run moondeck/moondeck.py`, then open . Build and run the desktop first: it needs no board and proves the toolchain. See [Building](building.md). + +## Still stuck + +Name the geometry, the driver, the peripheral and the measured refresh. Those four decide which path you are on. See [Log an issue](logging-an-issue.md). diff --git a/docs/how-to/logging-an-issue.md b/docs/how-to/logging-an-issue.md index 42a4e256..8a47a125 100644 --- a/docs/how-to/logging-an-issue.md +++ b/docs/how-to/logging-an-issue.md @@ -1,9 +1,6 @@ # Log an issue -Something not working? Tell us. You do not need to diagnose it — describe what -you saw, and hand us the state of the device it happened on. The **`{ }`** link -on every module card gives you that in one click, and it is the single most -useful thing you can attach. +Something not working? Tell us. You do not need to diagnose it, describe what you saw, and hand us the state of the device it happened on. The **`{ }`** link on every module card gives you that in one click, and it is the single most useful thing you can attach. 👉 **[Open an issue on GitHub](https://github.com/MoonModules/projectMM/issues)** @@ -16,10 +13,10 @@ supposed to work like this?". ## Checklist - Search the [existing issues](https://github.com/MoonModules/projectMM/issues) - first — it may already be reported, or already fixed + first, it may already be reported, or already fixed - Check the Firmware card: are you on the current version? - For bugs, include the `{ }` output (below) -- For feature requests, describe the use case — no template needed +- For feature requests, describe the use case, no template needed --- @@ -41,13 +38,11 @@ Actual behavior: ### 2. Attach the `{ }` output -⚠️ **This is the most useful diagnostic data.** It is a JSON snapshot of the -module's live state — its type, every control value, its status line and its -timing — exactly as it was when things went wrong. +⚠️ **This is the most useful diagnostic data.** It is a JSON snapshot of the module's live state, its type, every control value, its status line and its timing, exactly as it was when things went wrong. **How to get it:** -1. Find the card that misbehaved — the effect that went dark, the driver that +1. Find the card that misbehaved, the effect that went dark, the driver that isn't lighting, the network card that won't connect 2. Click the **`{ }`** link in its title row, next to the `?` 3. Select all in the new tab (Ctrl/Cmd+A), copy, and paste it into the issue @@ -55,9 +50,9 @@ timing — exactly as it was when things went wrong. **Which cards to include:** 1. **Always: System.** Names the chip, firmware variant, build, SDK version and - the last boot reason — how we tell a board problem from a firmware one + the last boot reason, how we tell a board problem from a firmware one 2. **Always: Firmware.** The exact version and build you are running -3. **The affected card itself** — Effects, Drivers, Layouts, Network, whichever +3. **The affected card itself**, Effects, Drivers, Layouts, Network, whichever one went wrong 4. **If it is about lights:** the Layer card, which carries the geometry 5. **If it is about pins or wiring:** the Drivers card @@ -80,32 +75,27 @@ API output: ### 3. Add a photo -If it is something you can see — a fixture showing the wrong colours, a UI in a -state that looks wrong — a photo or screenshot says it faster than a paragraph. +If it is something you can see, a fixture showing the wrong colors, a UI in a state that looks wrong, a photo or screenshot says it faster than a paragraph. --- ## If the device crashed or won't boot -The `{ }` link needs a device that still serves its interface. When it doesn't, -tell us instead: +The `{ }` link needs a device that still serves its interface. When it doesn't, tell us instead: -- **What the LEDs did** — nothing at all, a brief flash, a repeating pattern +- **What the LEDs did**: nothing at all, a brief flash, a repeating pattern - **What changed** just before it started, even if it seems unrelated: a setting, a script edit, a firmware update, a cable moved - **Does it repeat?** Power-cycle it. A device that boots once and fails the next time is a different problem from one that never boots - **The serial boot log**, if you have the device on USB and can run a monitor. - If you can't, say so — we will not ask you to set up a toolchain to file a bug + If you can't, say so, we will not ask you to set up a toolchain to file a bug -One more thing worth checking yourself: `bootReason` on the System card. `PANIC`, -`TASK_WDT` or `BROWNOUT` means the device crashed or lost power rather than -merely misbehaving, and saying so in the first line of the report saves a round -trip. +One more thing worth checking yourself: `bootReason` on the System card. `PANIC`, `TASK_WDT` or `BROWNOUT` means the device crashed or lost power rather than merely misbehaving, and saying so in the first line of the report saves a round trip. --- ## Feature requests Describe the use case, what you expect it to do, and what you have tried instead. -No template, no diagnostics — just tell us what you want to build. +No template, no diagnostics, just tell us what you want to build. diff --git a/docs/how-to/panel-cards.md b/docs/how-to/panel-cards.md index 86939d37..fbc02209 100644 --- a/docs/how-to/panel-cards.md +++ b/docs/how-to/panel-cards.md @@ -55,9 +55,7 @@ The cards have no buffering and no flow control. They latch the image when the s The failure mode is the confusing part: **nothing errors**. The link is up, frames go out, and the panels tear, show wrong rows, or never latch. That is why projectMM reads the *negotiated* speed and warns you, rather than letting a slow link look like a format bug. It still sends, since a small wall on 100 Mbit is often fine, but if your picture is unstable, check this first. -The cards can also be picky about negotiating a gigabit link with a 100 Mbit controller. A gigabit -switch in between is the remedy: the card negotiates gigabit with the switch, the switch buffers, and -the controller's slower link stops being the card's problem. This applies to the **P4 and the S3**. +The cards can also be picky about negotiating a gigabit link with a 100 Mbit controller. A gigabit switch in between is the remedy: the card negotiates gigabit with the switch, the switch buffers, and the controller's slower link stops being the card's problem. This applies to the **P4 and the S3**. An **S31 is gigabit on its own** and connects directly. --- @@ -72,23 +70,16 @@ An **S31 is gigabit on its own** and connects directly. | [7. Card firmware, and the flicker](#7-card-firmware-and-the-flicker) | The v13 defect, and the LED Upgrade 4.0 downgrade that clears it | | [8. When it does not light up](#8-when-it-does-not-light-up) | Symptom to cause | -The two halves are independent: an ESP32 and a desktop drive the same card the same way, and neither -is a prerequisite for the other. Pick whichever hardware you have. +The two halves are independent: an ESP32 and a desktop drive the same card the same way, and neither is a prerequisite for the other. Pick whichever hardware you have. --- ## 4. Set the panels up in LED Vision -The receiving card has to know what it is driving before projectMM sends it anything: how big each -panel is, how many there are, and which driver IC they use. That configuration lives **on the card**, -written once with ColorLight's own **[LEDVision](https://en.colorlightinside.com/product/download/380)**, -and it is why projectMM itself needs no panel wiring settings at all (see -[§5.4](#54-describe-the-wall)). +The receiving card has to know what it is driving before projectMM sends it anything: how big each panel is, how many there are, and which driver IC they use. That configuration lives **on the card**, written once with ColorLight's own **[LEDVision](https://en.colorlightinside.com/product/download/380)**, and it is why projectMM itself needs no panel wiring settings at all (see [§5.4](#54-describe-the-wall)). **Which version.** An **8.x** build is what people running these cards in this scene actually use: -this project's own wall is set up with **8.8**, and the walkthrough linked below uses **8.5**. Newer -releases exist, and whether they are equally suitable here has not been established, so the safe -advice is to take an 8.x build and only move if you have a reason to. +this project's own wall is set up with **8.8**, and the walkthrough linked below uses **8.5**. Newer releases exist, and whether they are equally suitable here has not been established, so the safe advice is to take an 8.x build and only move if you have a reason to. > **Worth watching first:** [Setting up a Colorlight Card with FPP v6.3 and LED Vision 8.5](https://www.youtube.com/watch?v=L4lHbwUszAs) > walks through the whole card-and-panel setup on video. It drives the card from FPP rather than @@ -103,17 +94,14 @@ advice is to take an 8.x build and only move if you have a reason to. 2. Open LEDVision and go to the **receiving-card** setup (usually `Settings` then a receiving-card or `Screen` panel; some versions ask for a password, commonly `168`). 3. Load the panel definition. Either pick your panel from the built-in module list, or load the - `.rcfgx` / `.rcvx` file the panel supplier provided, which is the reliable route for a panel that - is not a well-known model. + `.rcfgx` / `.rcvx` file the panel supplier provided, which is the reliable route for a panel that is not a well-known model. 4. Set the **cabinet** size: how many pixels one card drives, across and down. 5. Set the **panel arrangement**: how the HUB75 ribbons chain, and which physical panel is first. This is the step that makes the card, not projectMM, responsible for panel order. 6. **Send to the receiving card**, then **Save** so the configuration survives a power cycle. Saving - is a separate action from sending in most versions, and skipping it is the usual reason a wall - comes back wrong after being unplugged. + is a separate action from sending in most versions, and skipping it is the usual reason a wall comes back wrong after being unplugged. -When this is right, a test pattern from LEDVision fills the wall correctly. Get to that point before -introducing projectMM: it separates "the panels are wired and configured" from "the sender works". +When this is right, a test pattern from LEDVision fills the wall correctly. Get to that point before introducing projectMM: it separates "the panels are wired and configured" from "the sender works". --- @@ -129,10 +117,7 @@ introducing projectMM: it separates "the panels are wired and configured" from " Other ESP32 variants do not ship panel-card support: their Ethernet is 100 Mbit at best, and most have none at all. -The S3 needs a **W5500 Ethernet module** wired to its SPI pins, which the S3 firmwares already -support; its pins come from the board entry in `deviceModels.json`. Because W5500 is 100 Mbit and -sits behind SPI, it is the configuration most likely to need the gigabit switch described in -[§2](#the-one-hardware-fact-that-decides-everything). +The S3 needs a **W5500 Ethernet module** wired to its SPI pins, which the S3 firmwares already support; its pins come from the board entry in `deviceModels.json`. Because W5500 is 100 Mbit and sits behind SPI, it is the configuration most likely to need the gigabit switch described in [§2](#the-one-hardware-fact-that-decides-everything). ### 5.2 Flash it @@ -153,47 +138,26 @@ Panel-card support is compiled in per firmware, and it is already on for the boa **On powering panels from the same supply as the board.** This works, and plenty of small setups run that way. What it costs you is headroom. -HUB75 panels draw far less than a naive count suggests, and the reason is **multiplexing**: the panel -lights one group of scan rows at a time, cycling fast enough to look continuous, so at 1/16 or 1/32 -scan only a fraction of the LEDs are on at any instant. This is why these panels have a reputation -for modest consumption. Size the supply from the rating on your panel's own datasheet rather than -from pixels multiplied by LED current. +HUB75 panels draw far less than a naive count suggests, and the reason is **multiplexing**: the panel lights one group of scan rows at a time, cycling fast enough to look continuous, so at 1/16 or 1/32 scan only a fraction of the LEDs are on at any instant. This is why these panels have a reputation for modest consumption. Size the supply from the rating on your panel's own datasheet rather than from pixels multiplied by LED current. -The failure when you do run short is not a clean one: the 5 V rail sags, and a sagging rail shows up -as flicker, color shifts, or the controller resetting mid-frame. None of those look like a power -problem, which is why they cost an evening. A wall that is stable at 30% brightness and misbehaves at -100% is telling you this is the problem, not the network. +The failure when you do run short is not a clean one: the 5 V rail sags, and a sagging rail shows up as flicker, color shifts, or the controller resetting mid-frame. None of those look like a power problem, which is why they cost an evening. A wall that is stable at 30% brightness and misbehaves at 100% is telling you this is the problem, not the network. Nothing needs an IP address: the panel link is below IP entirely. **That is also how you reach the UI.** The board's Ethernet port is now carrying panel data, so -WiFi is what serves the web interface. Leave WiFi configured as normal; it is unrelated to the panel -link and the two do not interfere. On the P4 this decides which firmware to flash, because the -`-eth` variant has no WiFi compiled in at all: use `-eth-wifi`. The S31 and S3 firmwares carry both -already. +WiFi is what serves the web interface. Leave WiFi configured as normal; it is unrelated to the panel link and the two do not interfere. On the P4 this decides which firmware to flash, because the `-eth` variant has no WiFi compiled in at all: use `-eth-wifi`. The S31 and S3 firmwares carry both already. ### 5.4 Describe the wall The driver has **no geometry controls**. The wall's shape lives in the Layout, once, so that everything else (effects, modifiers, the preview) sees the same picture. **A plain Grid is usually all you need.** Two 128x64 panels stacked is a 128x128 grid, and that is -the whole configuration. The reason it is that simple is worth knowing: the driver reads only the -wall's width and height and sends the image row by row. Which physical panel a row lands on, and in -what order the HUB75 ribbons chain, was already settled on the card in -[§4](#4-set-the-panels-up-in-led-vision). The card owns panel arrangement; projectMM owns the -picture. - -That is also why this needs none of the physical detail you may have filled in elsewhere. An output -page that asks for scan rate, address lines and chain order is describing panels driven *directly*, -where the software has to generate the HUB75 timing itself. Through a receiving card, none of that is -the sender's business: the card generates the timing, and the sender hands it an image. That holds -for any sender, [FPP](https://github.com/FalconChristmas/fpp) included, which reaches these cards -over Ethernet exactly as projectMM does. +the whole configuration. The reason it is that simple is worth knowing: the driver reads only the wall's width and height and sends the image row by row. Which physical panel a row lands on, and in what order the HUB75 ribbons chain, was already settled on the card in [§4](#4-set-the-panels-up-in-led-vision). The card owns panel arrangement; projectMM owns the picture. + +That is also why this needs none of the physical detail you may have filled in elsewhere. An output page that asks for scan rate, address lines and chain order is describing panels driven *directly*, where the software has to generate the HUB75 timing itself. Through a receiving card, none of that is the sender's business: the card generates the timing, and the sender hands it an image. That holds for any sender, [FPP](https://github.com/FalconChristmas/fpp) included, which reaches these cards over Ethernet exactly as projectMM does. **When you need the Panels layout instead.** It exists for walls where projectMM, not a card, owns -the ordering: addressable panels wired as one long pixel strip, where the strip snakes from panel to -panel and the layout has to undo that. Its controls are about **wiring order**, which a HUB75 ribbon -does not have. +the ordering: addressable panels wired as one long pixel strip, where the strip snakes from panel to panel and the layout has to undo that. Its controls are about **wiring order**, which a HUB75 ribbon does not have. | Control | Meaning | |---|---| @@ -203,10 +167,7 @@ does not have. | `wiringOrderP`, `X++P`, `Y++P`, `snakeP` | How the panels themselves are ordered | **Do HUB75 panels snake?** Not in the sense these controls mean. A HUB75 panel is addressed by row -and column over the ribbon, so its internal pixel order is fixed by the panel's own driver ICs and -is not something a layout re-maps. A *chain* of panels can be arranged in any order, including a -serpentine one, but that is configured on the card, not here. So if you are driving panels through a -receiving card and every other row looks reversed, the setting to revisit is in LEDVision. +and column over the ribbon, so its internal pixel order is fixed by the panel's own driver ICs and is not something a layout re-maps. A *chain* of panels can be arranged in any order, including a serpentine one, but that is configured on the card, not here. So if you are driving panels through a receiving card and every other row looks reversed, the setting to revisit is in LEDVision. ### 5.5 Add the driver @@ -306,16 +267,13 @@ The second row is worth taking seriously, because projectMM sends a full frame e ### Reading and changing the version **Use [LEDUpgrade](https://en.colorlightinside.com/product/download/383) 4.0 and firmware 11.09.** -That is the proven combination, and the easiest one, because 11.09 ships inside LEDUpgrade 4.0: it is -in the preset list, so there is no firmware file to find. Treat any other pairing as a detour. +That is the proven combination, and the easiest one, because 11.09 ships inside LEDUpgrade 4.0: it is in the preset list, so there is no firmware file to find. Treat any other pairing as a detour. **Why not 5.0.** Version 5.0 ships no pre-v12 firmware at all, so it cannot do this downgrade from its preset list however long you fight it. **Why 11.09 rather than something older.** Anything before v12 clears the flicker, but older is not -automatically safer: cards on 11.08 were reported strobing white, which 11.09 fixes. 11.09 is the -newest build on the safe side of the defect, so it carries the most fixes while carrying none of the -flicker. +automatically safer: cards on 11.08 were reported strobing white, which 11.09 fixes. 11.09 is the newest build on the safe side of the defect, so it carries the most fixes while carrying none of the flicker. > **On Windows, if LEDUpgrade cannot find the card.** A built-in Ethernet port can be held by > something else in the stack, and Hyper-V's virtual switch is the usual culprit: it binds the @@ -341,8 +299,7 @@ Set the driver's `firmware` control to match the card: | `v12 and older` | **The default**, and a downgraded card. Brightness and sync go out once. | | `v13 and newer` | A stock card. Both go out twice, which is the copy this firmware acts on. | -The default is the downgraded generation on purpose: this page's own advice is to move a v13 card -off it, so the setting is already right when you finish rather than being one last unexplained step. +The default is the downgraded generation on purpose: this page's own advice is to move a v13 card off it, so the setting is already right when you finish rather than being one last unexplained step. The mismatch is not subtle in one direction: leave a downgraded card on `v13 and newer` and it receives a second sync, treats it as another latch, aborts the refresh already running, and the wall updates once every few seconds. diff --git a/docs/index.md b/docs/index.md index 51e5663d..46aede99 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,9 +2,7 @@ High-performance LED & DMX lighting control for ESP32 and beyond. -[:material-flash: Flash an ESP32 from your browser](/projectMM/install/){ .md-button .md-button--primary } -  -[:material-github: GitHub](https://github.com/MoonModules/projectMM){ .md-button } +[:material-flash: Flash an ESP32 from your browser](/projectMM/install/){ .md-button .md-button--primary }   [:material-github: GitHub](https://github.com/MoonModules/projectMM){ .md-button } !!! tip "New here?" The [Getting started](gettingstarted.md) guide walks you from a blank ESP32 to your first running light show, step by step, with no build tools required. diff --git a/docs/moonmodules/core/services.md b/docs/moonmodules/core/services.md index 25b18e19..46261672 100644 --- a/docs/moonmodules/core/services.md +++ b/docs/moonmodules/core/services.md @@ -47,8 +47,7 @@ Detail: [technical](moxygen/AudioService.md) · [the sync packet](../light/moxyg Receives [OSC](https://opensoundcontrol.stanford.edu/) over UDP and writes it onto this device's controls, so a fader in Resolume, TouchDesigner or TouchOSC drives projectMM directly. It owns no surface of its own: everything lands in the same control writes the HTTP API and the UI use, so every validator still runs. Addresses, feedback and setup: ⌄ details. - `listen` — receive OSC (default **off**). This opens an unauthenticated UDP port that writes - controls, on the same LAN-trust basis as the Art-Net and audio-sync receivers, so it is a - capability you turn on rather than one every device carries. + controls, on the same LAN-trust basis as the Art-Net and audio-sync receivers, so it is a capability you turn on rather than one every device carries. - `port` — the UDP port (default 9000, what TouchOSC uses). Applies live. - `status` — listening, off, or why the port could not be opened. @@ -111,8 +110,7 @@ A Service added per board: **a MoonLive script that reads hardware and drives co Why a script rather than another module: a mapping row is right for a button and wrong for anything with a condition in it. A script holds state and chooses between outcomes. - `script`: which `.mls` file to run, picked from the script library. Naming a different one - recompiles live; a compile error shows on the status line and the service does nothing until it is - fixed. + recompiles live; a compile error shows on the status line and the service does nothing until it is fixed. - Everything the script declares appears as a real control on the card, so a slider move lands without a recompile. @@ -159,21 +157,12 @@ A desktop device is picked by list position, so re-pick if the OS reorders them; #### WLED audio sync: what is on the wire -Sending and receiving both use the **multicast address 239.0.0.1**, which is what WLED's own -usermod does (`beginMulticast` on both ends). It never uses broadcast, so a broadcast sender is -inaudible to WLED and a receiver that only binds the port never hears WLED. This is a -network-layer address, unrelated to any device grouping. +Sending and receiving both use the **multicast address 239.0.0.1**, which is what WLED's own usermod does (`beginMulticast` on both ends). It never uses broadcast, so a broadcast sender is inaudible to WLED and a receiver that only binds the port never hears WLED. This is a network-layer address, unrelated to any device grouping. **Port 11988 is the WLED contract**, and `syncPort` defaults to it. The port is configurable for -projectMM peers that want a private stream, but a custom port is no longer WLED-compatible: the -endpoint WLED speaks is 239.0.0.1:11988 specifically. +projectMM peers that want a private stream, but a custom port is no longer WLED-compatible: the endpoint WLED speaks is 239.0.0.1:11988 specifically. -Multicast is also the better neighbour, with a caveat worth knowing: a switch or access point that -does **IGMP snooping** forwards the group only to the ports that joined it, so the other hosts -never see the traffic at all. Without snooping the switch floods it exactly like broadcast, and on -WiFi it goes out at the lowest basic rate to every station. So multicast can reduce how many hosts -have to process ~40 packets a second, but it does not guarantee it. See -[multicast and IGMP snooping](../../explanation/architecture/moonlight.md#multicast-and-igmp-snooping). +Multicast is also the better neighbor, with a caveat worth knowing: a switch or access point that does **IGMP snooping** forwards the group only to the ports that joined it, so the other hosts never see the traffic at all. Without snooping the switch floods it exactly like broadcast, and on WiFi it goes out at the lowest basic rate to every station. So multicast can reduce how many hosts have to process ~40 packets a second, but it does not guarantee it. See [multicast and IGMP snooping](../../explanation/architecture/moonlight.md#multicast-and-igmp-snooping). The 44-byte v2 packet is byte-compatible with WLED, with one field that is not yet equivalent: @@ -187,43 +176,24 @@ The 44-byte v2 packet is byte-compatible with WLED, with one field that is not y | `FFT_Magnitude` | 0..255 internally, x16 on the wire | ~0..4096 | compatible | **The magnitude scale differs, so it is converted at the wire.** WLED sends the raw magnitude of -its FFT's dominant bin, scaled so that "the end result is linear and ~4096 max" (its own comment -where it divides the input samples by 16). Its effects then divide that by 4, 8 or 16 depending on -the effect and treat the result as a byte, which is why their thresholds read `< 48` squelch and -`> 144` full brightness. projectMM byte-scales the peak magnitude to 0..255 instead, through the -same noise floor and gain conditioning as the 16 bands, so one pair of knobs governs the whole -spectrum. +its FFT's dominant bin, scaled so that "the end result is linear and ~4096 max" (its own comment where it divides the input samples by 16). Its effects then divide that by 4, 8 or 16 depending on the effect and treat the result as a byte, which is why their thresholds read `< 48` squelch and `> 144` full brightness. projectMM byte-scales the peak magnitude to 0..255 instead, through the same noise floor and gain conditioning as the 16 bands, so one pair of knobs governs the whole spectrum. projectMM keeps its own units internally and multiplies by 16 on send, dividing by 16 on receive. -The factor is exact rather than a fudge: it is the divisor WLED's effects apply, so our full-scale -255 arrives as 4080, right on WLED's own ~4096 design target, and every effect's thresholds land -where they were tuned to. Adopting WLED's range internally was the alternative, and was rejected -because that range is an artifact of FFT size and input scaling rather than a specification (WLED's -own fallback path admits "no idea if 10000 is a good value"), and importing it would cost the -property that one floor/gain pair conditions every value the service publishes, in exchange for -resolution the receiving effects discard anyway when they divide back down to a byte. +The factor is exact rather than a fudge: it is the divisor WLED's effects apply, so our full-scale 255 arrives as 4080, right on WLED's own ~4096 design target, and every effect's thresholds land where they were tuned to. Adopting WLED's range internally was the alternative, and was rejected because that range is an artifact of FFT size and input scaling rather than a specification (WLED's own fallback path admits "no idea if 10000 is a good value"), and importing it would cost the property that one floor/gain pair conditions every value the service publishes, in exchange for resolution the receiving effects discard anyway when they divide back down to a byte. -A received magnitude is clamped to 255, since a real WLED source reaches ~9500 and an unclamped -value would drive effects harder than locally analyzed audio ever could. +A received magnitude is clamped to 255, since a real WLED source reaches ~9500 and an unclamped value would drive effects harder than locally analyzed audio ever could. Prior art: the WLED-MM audio-reactive usermod by **Frank ([@softhack007](https://github.com/softhack007))**, the most-used open-source audio-reactive LED implementation, whose adaptive noise-gate concept the analysis here descends from (analyzed with his permission); and **[@troyhacks](https://github.com/troyhacks/WLED)**, who reworked that DSP onto Espressif's [esp-dsp](https://github.com/espressif/esp-dsp) FFT, the same choice this service makes. The line-in path exists because **wladi ([myhome-control](https://shop.myhome-control.de))** supplied the hardware and pinout for the [MHC-WLED ESP32-P4 shield](../../reference/hardware/mhc-wled-esp32-p4-shield.md): its onboard PCM1808 I2S ADC is what `mclkPin` is for. ## OSC, details **Feedback: the device answers.** With `feedback` on, a control that changes anywhere (the web UI, a -preset recall, an audio-reactive effect) is mirrored back to the surface, which is what keeps a -client honest and what moves a motorised fader. `feedbackTo` names the receiver, or is left empty to -answer whoever last wrote to us; `feedbackPort` is where that client LISTENS, which is not the port -we listen on (Open Stage Control calls its own `osc-port`). - -A client learns the current state three ways: when it first writes to us from a new address, when -its address changes, and whenever it sends **`/mm/hello`**. The last one exists because a client -restarting on the SAME address is invisible to the other two, and most controllers send nothing of -their own on load, so every widget would show its layout file's defaults until the user moved one. +preset recall, an audio-reactive effect) is mirrored back to the surface, which is what keeps a client honest and what moves a motorised fader. `feedbackTo` names the receiver, or is left empty to answer whoever last wrote to us; `feedbackPort` is where that client LISTENS, which is not the port we listen on (Open Stage Control calls its own `osc-port`). + +A client learns the current state three ways: when it first writes to us from a new address, when its address changes, and whenever it sends **`/mm/hello`**. The last one exists because a client restarting on the SAME address is invisible to the other two, and most controllers send nothing of their own on load, so every widget would show its layout file's defaults until the user moved one. The shipped session has a `sync from device` button for exactly this. **Setting one up**, from installing the app to using it from a phone, is its own page: -[Driving projectMM from a phone or tablet](../../how-to/control-surface.md). It needs no -checkout and no tooling, just the app and the session file from the latest release. +[Driving projectMM from a phone or tablet](../../how-to/control-surface.md). It needs no checkout and no tooling, just the app and the session file from the latest release. **Addresses.** These are a public contract: a TouchOSC layout built against them keeps working, so they stay small and boring. @@ -236,9 +206,7 @@ they stay small and boring. | `/mm/hello` | anything, or nothing | resend every value to the sender | | `/mm/control//` | float 0..1 or int 0..255 | any control directly | -Both argument forms are accepted because controllers disagree: apps send a float in 0..1, hardware -bridges send an int in the target's range. Out-of-range values are clamped rather than ignored, so -a controller sending 0..127 does something sensible instead of appearing dead. +Both argument forms are accepted because controllers disagree: apps send a float in 0..1, hardware bridges send an int in the target's range. Out-of-range values are clamped rather than ignored, so a controller sending 0..127 does something sensible instead of appearing dead. Send one from the bench with `uv run moondeck/check/send_osc.py /mm/fader/1 0.75`. @@ -252,15 +220,9 @@ uv run moondeck/run/run_open_stage_control.py # device on this uv run moondeck/run/run_open_stage_control.py --host 192.168.1.42 ``` -Open **http://127.0.0.1:8088** and the surface is there. On the device, turn `listen` and `feedback` -on; nothing else needs configuring, because the launcher passes the session, the send address and -the listen port as arguments rather than leaving them to be typed into a settings panel. The session -itself sends `/mm/hello` when the page loads, so every widget shows the device's real values straight -away instead of its layout file's defaults, and a browser refresh re-reads them. +Open **http://127.0.0.1:8088** and the surface is there. On the device, turn `listen` and `feedback` on; nothing else needs configuring, because the launcher passes the session, the send address and the listen port as arguments rather than leaving them to be typed into a settings panel. The session itself sends `/mm/hello` when the page loads, so every widget shows the device's real values straight away instead of its layout file's defaults, and a browser refresh re-reads them. -It runs **headless**: a web server rather than a desktop window. That is deliberate. The surface is -then reachable from a phone or another laptop on the same network (the launcher prints those URLs), -and on macOS it sidesteps the quarantine dialog an unsigned download otherwise raises. +It runs **headless**: a web server rather than a desktop window. That is deliberate. The surface is then reachable from a phone or another laptop on the same network (the launcher prints those URLs), and on macOS it sidesteps the quarantine dialog an unsigned download otherwise raises. | | | |---|---| @@ -270,30 +232,19 @@ and on macOS it sidesteps the quarantine dialog an unsigned download otherwise r | `--app` | the Open Stage Control binary, when it is not on PATH or in the usual place | | `--gui` | also open the desktop window; by default it is the server alone | -The launcher looks on PATH first, then in each platform's default install location. **Windows and -Linux are untested**: the paths are the ones those installers use, but only macOS has been run. If -it cannot find the app, `--app` takes the full path and that always works. +The launcher looks on PATH first, then in each platform's default install location. **Windows and Linux are untested**: the paths are the ones those installers use, but only macOS has been run. If it cannot find the app, `--app` takes the full path and that always works. **A ready-made control surface.** A session of the switches, encoders and faders ships as a release -asset (`projectMM-control-surface.json`) and lives in the repo at -[`docs/reference/examples/open-stage-control.json`](../../reference/examples/open-stage-control.json). +asset (`projectMM-control-surface.json`) and lives in the repo at [`docs/reference/examples/open-stage-control.json`](../../reference/examples/open-stage-control.json). Editing the layout needs `read-only` off in the launcher. The shipped Open Stage Control session beside projectMM's own Control card: eight switches, eight encoders and eight faders in both Driving the device from that session, beside the Control card it mirrors. -It binds only to `/mm/switch/N`, `/mm/encoder/N` and `/mm/fader/N`, N being 1 to 8, on purpose. A surface should -address the SURFACE, and [Control](system.md#control) decides what each one drives, so one layout keeps -working as assignments change and a hardware desk lands on the same bindings. Reaching past it to -`/mm/control//` also works and is the right answer for a one-off, but it hard-codes -into the layout a mapping that belongs on the device. Two have targets today, `switch1` -(`Drivers.on`) and `fader1` (`Drivers.brightness`); the rest wait for a target picker. +It binds only to `/mm/switch/N`, `/mm/encoder/N` and `/mm/fader/N`, N being 1 to 8, on purpose. A surface should address the SURFACE, and [Control](system.md#control) decides what each one drives, so one layout keeps working as assignments change and a hardware desk lands on the same bindings. Reaching past it to `/mm/control//` also works and is the right answer for a one-off, but it hard-codes into the layout a mapping that belongs on the device. Two have targets today, `switch1` (`Drivers.on`) and `fader1` (`Drivers.brightness`); the rest wait for a target picker. -The session also carries a **pad grid**, and those pads are inert: `/mm/pad/N` has no route in the -OSC module yet, so pressing one sends a message nothing reads. It ships anyway because the grid is -the layout a preset launcher wants and the addresses are the ones it will use; treat it as a -placeholder rather than as part of the contract above. +The session also carries a **pad grid**, and those pads are inert: `/mm/pad/N` has no route in the OSC module yet, so pressing one sends a message nothing reads. It ships anyway because the grid is the layout a preset launcher wants and the addresses are the ones it will use; treat it as a placeholder rather than as part of the contract above. **It does not reach a Mackie desk.** The X-Touch and QCon Pro G2 speak Mackie Control over MIDI, not OSC: see [control surfaces](../../reference/hardware/control-surfaces.md) for what would. @@ -301,16 +252,9 @@ not OSC: see [control surfaces](../../reference/hardware/control-surfaces.md) fo ## Infrared, details Set a row's `learn` and the next code received binds to it, which is how any remote works without a shipped code table. Arming one row disarms any other, so a code cannot bind twice. A fresh service starts with no rows: add one, learn a key, pick a target. The status line reports whether the channel actually opened, not merely that a pin is set; on some boards the receiver shares its pin with another peripheral through a board switch. -Nothing is fixed in firmware. A row IS the binding: learn a key onto it, pick what it drives from -the target dropdown, and pick whether the press toggles that control, or nudges it by a value. A -handset with twenty keys is twenty rows. `set` is offered only where an input reports a release, so -it is unavailable here: a remote code is a single event, and a `set` row would latch the control -with nothing able to clear it. +Nothing is fixed in firmware. A row IS the binding: learn a key onto it, pick what it drives from the target dropdown, and pick whether the press toggles that control, or nudges it by a value. A handset with twenty keys is twenty rows. `set` is offered only where an input reports a release, so it is unavailable here: a remote code is a single event, and a `set` row would latch the control with nothing able to clear it. -One key binds to one row. Learning a key that another row already holds moves the binding rather -than duplicating it, because dispatch fires the first row holding a code and a duplicate could -never run. +One key binds to one row. Learning a key that another row already holds moves the binding rather than duplicating it, because dispatch fires the first row holding a code and a duplicate could never run. -The status line reports setup state ("set pin to receive" / "ready"), the learn prompt, a binding -("learned 0x..."), what a press did or why it did not, and an unbound code ("received 0x... +The status line reports setup state ("set pin to receive" / "ready"), the learn prompt, a binding ("learned 0x..."), what a press did or why it did not, and an unbound code ("received 0x... (unassigned)"). diff --git a/docs/moonmodules/core/system.md b/docs/moonmodules/core/system.md index 3dde5c47..b804e01a 100644 --- a/docs/moonmodules/core/system.md +++ b/docs/moonmodules/core/system.md @@ -1,6 +1,6 @@ # Core system -The device's fixed infrastructure — identity, network, provisioning, firmware, and the inspection tools. These modules are **always present and wired by code**, not user-added; the user does not add or delete them. User-added capability modules (Audio, IR) live in the `Services` container instead — see [core/services.md](services.md). Every row links to its generated technical page (the full API, from the `.h`) and its tests. Cross-cutting rationale that no single `.h` owns lives in the prose sections below the table. +The device's fixed infrastructure: identity, network, provisioning, firmware, and the inspection tools. These modules are **always present and wired by code**, not user-added; the user does not add or delete them. User-added capability modules (Audio, IR) live in the `Services` container instead, see [core/services.md](services.md). Every row links to its generated technical page (the full API, from the `.h`) and its tests. Cross-cutting rationale that no single `.h` owns lives in the prose sections below the table. ## System modules @@ -8,15 +8,17 @@ The device's fixed infrastructure — identity, network, provisioning, firmware, ### System -The device's identity and vitals — name (behind mDNS `.local`, the SoftAP SSID, the DHCP hostname), uptime, heap, and per-module footprint reporting. Its fixed inspection children (Tasks, I2C scan) hang beneath it. +The device's identity and vitals: name (behind mDNS `.local`, the SoftAP SSID, the DHCP hostname), uptime, heap and the tick it renders at. Its fixed inspection children (Tasks, I2C scan) hang beneath it. System module controls -- `deviceName` — the identity behind mDNS, the SoftAP SSID and the DHCP hostname. -- `deviceModel` — the board model (drives the installer catalog entry). -- `expertMode` — reveals advanced controls (marked 🔧) across the UI; off by default. -- `logLevel` — serial verbosity, defaulting to Warn. The first 60 s always logs at Info. -- read-only vitals — `uptime`, `fps`, `heap`, `psram`, `flash`, `chip`, and per-module footprint. +- `deviceName`: the identity behind mDNS, the SoftAP SSID and the DHCP hostname. +- `deviceModel`: the board model (drives the installer catalog entry). +- `mode`: how much of the UI is shown, `user`, `expert` (🎚️) or `developer` (🔧), cumulative. +- `logLevel`: serial verbosity, defaulting to Warn. The first 60 s always logs at Info. +- read-only rate: `uptime`, `fps`, `tickTimeUs`. +- read-only memory: `heap`, `psram`, `maxBlock`, `flash`. +- read-only identity: `mac`, `chip`, `cpu`, `sdk`, `bootReason`. Detail: [technical](moxygen/SystemModule.md) @@ -30,12 +32,12 @@ WiFi / Ethernet connectivity, static-IP configuration, RSSI and TX-power reporti Network module controls -- `mode` — WiFi / Ethernet / off. -- `ssid` / `password` — WiFi credentials. -- `mDNS` — the `.local` hostname. -- `addressing` — DHCP or static; static exposes IP / gateway / subnet / DNS fields. -- `ethType` / `ethPhyAddr` / `ethRstGpio` / … — Ethernet PHY configuration. -- read-only — `rssi` (dBm), `txPower` (dBm). +- `ssid` / `password`: WiFi credentials. +- `mDNS`: the `.local` hostname. +- `addressing`: DHCP or static; static reveals `ip`, `gateway`, `subnet` and `dns`. +- `ethBoard`: the board's Ethernet wiring by name; `Custom` exposes every pin. +- `txPowerSetting`: caps the radio's transmit power for a board that browns out; 0 lifts it. +- read-only: `mode`, the interface in use, with `rssi` / `txPower` (dBm) on a live radio. Detail: [technical](moxygen/NetworkModule.md) @@ -49,7 +51,7 @@ Serial/BLE Improv Wi-Fi provisioning: the web installer hands credentials to a f Improv provisioning module controls -- `provision_status` — read-only provisioning state. +- `provision_status`: read-only provisioning state. Detail: [technical](moxygen/ImprovProvisioningModule.md) · [frame format](moxygen/ImprovFrame.md) · [chunk reassembly](moxygen/ImprovOpReassembler.md) @@ -59,10 +61,10 @@ Detail: [technical](moxygen/ImprovProvisioningModule.md) · [frame format](moxyg Discovers other projectMM devices on the LAN and lists them, persisting the last-known list across a reboot. A wired-by-code child of Network. -Devices module — discovered LAN devices +Devices module, discovered LAN devices -- `devices` — a List of discovered devices; each row expands to a detail panel. Persistable. -- `wledCompatible` — also announce on WLED's broadcast address, off by default. +- `devices`: a List of discovered devices; each row expands to a detail panel. Persistable. +- `wledCompatible`: also announce on WLED's broadcast address, off by default. WLED apps browse on broadcast, so a device appears in them only with this on. Off is the better neighbour, since a broadcast wakes every device on the LAN to parse a packet none of them want. Presence always goes to the projectMM group regardless, so peers find each other either way. See [multicast and IGMP snooping](../../explanation/architecture/moonlight.md#multicast-and-igmp-snooping). @@ -78,13 +80,13 @@ Bridges the light to an MQTT broker so a home-automation hub can control it, as MQTT module controls -- `broker` — the broker hostname (e.g. `homeassistant.lan`) or IP. A hostname is resolved via DNS. -- `port` — broker port (default 1883). -- `username` / `password` — broker credentials, optional, the password stored obfuscated. -- `haDiscovery` — announce a Home Assistant discovery light, off by default. +- `broker`: the broker hostname (e.g. `homeassistant.lan`) or IP. A hostname is resolved via DNS. +- `port`: broker port (default 1883). +- `username` / `password`: broker credentials, optional, the password stored obfuscated. +- `haDiscovery`: announce a Home Assistant discovery light, off by default. HA already discovers the device over the WLED shim with no broker, so this stays off to avoid a duplicate entity. Turn it on for broker-only or cross-subnet setups. See the [home-automation guide](../../how-to/home-automation.md). -- read-only — `mqtt_status`, from `disabled` and `idle` through to `connected`, or an error. +- read-only: `mqtt_status`, from `disabled` and `idle` through to `connected`, or an error. Detail: [technical](moxygen/MqttModule.md) @@ -94,13 +96,13 @@ Detail: [technical](moxygen/MqttModule.md) ### Firmware update -Over-the-air firmware flashing — the one operation that swaps the binary and needs a power cycle (every *config* change applies live; a firmware OTA does not). +Over-the-air firmware flashing, the one operation that swaps the binary and needs a power cycle (every *config* change applies live; a firmware OTA does not). Firmware update module controls -- `firmware` — the OTA image to flash. -- read-only: `version`, `build`, `partition`. -- `image` — on a device carrying two images, which one those describe and an install writes. +- `firmware`: the OTA image to flash. +- read-only: `version`, `build` and `partition`. +- `image`: on a device carrying two images, which one those describe and an install writes. The choice is the app it runs, or MoonBase in the factory slot. This control's presence is also what tells the UI that installs run through the reboot-into-MoonBase cycle, behind one "updating firmware" overlay, and that a **Restart in MoonBase** button belongs on the card ([MoonBase](../../explanation/architecture/moonbase.md)). @@ -130,9 +132,9 @@ One opt-in report about this install, sent once per install or upgrade, so devel Stats module controls -- `consent` — a checkbox, off by default. Nothing is sent and no identifier computed while off. +- `consent`: a checkbox, off by default. Nothing is sent and no identifier computed while off. - read-only: `version` and `reportedVersion`, which differ exactly when a report is due. -- `send update` — reports again now, for a setup that changed without a version change. +- `send update`: reports again now, for a setup that changed without a version change. The two versions differing is what makes an upgrade send one report and a reboot send nothing. The button replaces this install's row rather than adding one. @@ -150,10 +152,10 @@ A public message board between projectMM devices, in the shape Meshtastic's chan Talk module controls -- `consent` — a checkbox, off by default. Nothing is published or read while it is off. -- `shareName` — whether your device name rides along, **off by default and a separate decision**. -- `message` — what to say, up to 280 characters. Typing changes nothing on its own. -- `send` — publishes the message and clears the box, as does Enter in the message field. +- `consent`: a checkbox, off by default. Nothing is published or read while it is off. +- `shareName`: whether your device name rides along, **off by default and a separate decision**. +- `message`: what to say, up to 280 characters. Typing changes nothing on its own. +- `send`: publishes the message and clears the box, as does Enter in the message field. A device name identifies a person rather than a machine. Without it your messages carry the first 8 characters of your installation id, which groups them without naming you. @@ -169,18 +171,18 @@ Detail: [technical](moxygen/MoonTalkModule.md) Browse and manage the device filesystem: a folder tree with an inline text editor. Distinct from Filesystem, the persistence engine. Behaviour: ⌄ details. -File Manager panel — folder tree + toolbar +File Manager panel, folder tree + toolbar -- `file browser` — the panel itself: a folder tree, a toolbar and an inline text editor. +- `file browser`: the panel itself: a folder tree, a toolbar and an inline text editor. - **Backup (⤓)**, download the device's files as one `.json` bundle. **Keep it private: it contains the WiFi password.** Every file is byte-verified against the listing, and an unreadable one is skipped and named. - **Restore (⟲)**, upload a backup bundle, pressing twice since it overwrites the device's files. Known renames from [MIGRATING.md](../../reference/MIGRATING.md) apply before upload, then a report lists what needs an eye. Every file applies as it lands, bar network settings and the web server's `port`, which the dialog names. -- `show hidden` — reveal dot-prefixed files and folders, such as `.config`. -- `filesystem` — read-only usage bar (used / total bytes, from the platform). -- `lastSaved` — read-only; how long ago config was persisted (read from the Filesystem engine). +- `show hidden`: reveal dot-prefixed files and folders, such as `.config`. +- `filesystem`: read-only usage bar (used / total bytes, from the platform). +- `lastSaved`: read-only; how long ago config was persisted (read from the Filesystem engine). Detail: [technical](moxygen/FileManagerModule.md) @@ -188,15 +190,15 @@ Detail: [technical](moxygen/FileManagerModule.md) ### I2C scan -A fixed System module (wired-by-code, always present) that probes the I²C bus on a button press and reports the addresses found — a hardware bring-up tool. The bus pins default to unused (−1), so a board without an I²C device claims no GPIO for it; a board with a bus sets its pins via the catalog, or you type them for an ad-hoc scan. Passive until the scan button is pressed. +A fixed System module (wired-by-code, always present) that probes the I²C bus on a button press and reports the addresses found, a hardware bring-up tool. The bus pins default to unused (−1), so a board without an I²C device claims no GPIO for it; a board with a bus sets its pins via the catalog, or you type them for an ad-hoc scan. Passive until the scan button is pressed. I2C scan module controls -- `sda` / `scl` — the bus GPIOs, defaulting to −1 for unused. +- `sda` / `scl`: the bus GPIOs, defaulting to −1 for unused. A board with a fixed bus injects its own through the catalog, or you type the pins for an ad-hoc scan. The classic Arduino-ESP32 pair is 21/22. -- `scan` — a button; press to probe the bus now. -- read-only — `result` (addresses found). +- `scan`: a button; press to probe the bus now. +- read-only: `result` (addresses found). Detail: [technical](moxygen/I2cScanModule.md) @@ -206,10 +208,10 @@ Detail: [technical](moxygen/I2cScanModule.md) A read-only diagnostic showing **what runs where**: you cannot optimise which module runs on which core until you can see it. A fixed System module, wired-by-code, with each task's MoonModules nested beneath it. -Tasks module — a row per FreeRTOS task +Tasks module, a row per FreeRTOS task -- read-only — `tasks`, a row per FreeRTOS task. -- read-only — `core0` / `core1`, what executes on each core, empty on a single-core chip. +- read-only: `tasks`, a row per FreeRTOS task. +- read-only: `core0` / `core1`, what executes on each core, empty on a single-core chip. Each row carries `name`, `state`, `core`, `prio` and `stack`, the minimum free stack seen. A `cpu` percentage appears only in a profiling build, off by default because the run-time counter costs about 5% of the tick. @@ -221,13 +223,13 @@ Detail: [technical](moxygen/TasksModule.md) ### Pins -A read-only diagnostic showing **which module owns each GPIO, for what role, and whether that pin is safe for it** — the device's pin ownership map, keyed by physical GPIO. A fixed System module, wired-by-code. +A read-only diagnostic showing **which module owns each GPIO, for what role, and whether that pin is safe for it**, the device's pin ownership map, keyed by physical GPIO. A fixed System module, wired-by-code. It walks the live tree for every claimed pin, holding no state, and flags double claims. -Pins module — the GPIO ownership map +Pins module, the GPIO ownership map -- read-only — `pins`, a row per claimed GPIO. +- read-only: `pins`, a row per claimed GPIO. Each row carries `gpio`, `owner` and `role`, plus live `dir`, `level` and `drive`. A row takes a coloured edge when unsafe: red for a reserved or double-claimed pin, yellow for a driven role on a strap, per [gpio-usage.md](../../reference/hardware/gpio-usage.md). @@ -237,14 +239,14 @@ Detail: [technical](moxygen/PinsModule.md) ### Control -A grid of preset pads, a row of rotary encoders above them, a row of on/off switches above those, and a bank of faders below — the layout of a Mackie-style control desk ([X-Touch](https://www.behringer.com/product.html?modelCode=0808-AAF), [QCon Pro G2](https://www.iconproaudio.com/product/qcon-pro-g2/)), so a physical surface maps onto it without a translation layer. +A grid of preset pads, a row of rotary encoders above them, a row of on/off switches above those, and a bank of faders below, the layout of a Mackie-style control desk ([X-Touch](https://www.behringer.com/product.html?modelCode=0808-AAF), [QCon Pro G2](https://www.iconproaudio.com/product/qcon-pro-g2/)), so a physical surface maps onto it without a translation layer. Control module surface: encoders, preset pads, faders -- `presets` — one pad per preset file. Click applies, right-click names, drag rearranges. -- `switch1` … `switch8` — the switch row. `switch1` drives `Drivers.on`, the rest unbound. -- `encoder1` … `encoder8` — rotary encoders. Drag or scroll to turn, right-click to see the binding. -- `fader1` … `fader8` — faders. `fader1` drives `Drivers.brightness`, the rest unbound. +- `presets`: one pad per preset file. Click applies, right-click names, drag rearranges. +- `switch1` … `switch8`, the switch row. `switch1` drives `Drivers.on`, the rest unbound. +- `encoder1` … `encoder8`, rotary encoders. Drag or scroll to turn, right-click to see the binding. +- `fader1` … `fader8`, faders. `fader1` drives `Drivers.brightness`, the rest unbound. Detail: [technical](moxygen/ControlModule.md) @@ -290,9 +292,9 @@ Detail: [technical](moxygen/ControlSurface.md) · [InputMapping](moxygen/InputMa #### Presets -A preset is a file: `/.config/presets/.json`. Saving writes one, applying reads one, deleting removes one. Nothing else holds preset state, so there is no second copy to keep in step: the list is rebuilt from the folder rather than persisted alongside it. That rescan runs at startup and after every save, rename and delete — a reorder only rewrites the affected files and re-sorts the rows in place, since the folder's contents have not changed. So a preset added or removed through the File Manager appears once the module next rescans (a reboot, or a save, rename or delete on the surface), not the instant the file lands. +A preset is a file: `/.config/presets/.json`. Saving writes one, applying reads one, deleting removes one. Nothing else holds preset state, so there is no second copy to keep in step: the list is rebuilt from the folder rather than persisted alongside it. That rescan runs at startup and after every save, rename and delete, a reorder only rewrites the affected files and re-sorts the rows in place, since the folder's contents have not changed. So a preset added or removed through the File Manager appears once the module next rescans (a reboot, or a save, rename or delete on the surface), not the instant the file lands. -The name becomes the file name, so it is restricted to printable ASCII without `/`, `\` or `.` — a validator on the control, which every write path runs. `slot` records which pad the preset occupies, so a surface arranged to match a physical desk survives a reboot. +The name becomes the file name, so it is restricted to printable ASCII without `/`, `\` or `.`, a validator on the control, which every write path runs. `slot` records which pad the preset occupies, so a surface arranged to match a physical desk survives a reboot. ##### What a preset carries @@ -320,7 +322,7 @@ A pad is tinted by its role: layout blue, effects violet, driver green, service ##### Applying is a rebuild -Applying a preset creates, replaces and destroys modules to match what the file describes — it is a restore, not a value overlay: a preset carrying more than the device has adds it, and one describing less removes what it omits. +Applying a preset creates, replaces and destroys modules to match what the file describes, it is a restore, not a value overlay: a preset carrying more than the device has adds it, and one describing less removes what it omits. Structural mutation quiesces the render worker, and mutations run inline on the render tick, so a large restore stalls rendering for its duration. The captured subtree is applied and `prepareTree()` runs once at the end. Presets are a cold-path feature; the tick path is untouched. @@ -330,11 +332,11 @@ Looks reach Home Assistant two ways, and only `Effects` presets travel either of **The WLED integration** (`/presets.json`) is the native path: HA renders looks in its own preset dropdown, shows which one is applied, and applies one when it is chosen. This is what HA calls a preset. -**MQTT discovery** publishes the same looks as the light entity's **effect list**. HA has no preset concept over MQTT, so they arrive as effects — the same result from the user's side, reached through a different mechanism. +**MQTT discovery** publishes the same looks as the light entity's **effect list**. HA has no preset concept over MQTT, so they arrive as effects, the same result from the user's side, reached through a different mechanism. -HA caches the preset list and re-fetches only when the device's `info.fs.pmt` value changes, so the device reports a revision counter there that bumps on every preset save, rename and delete — a counter rather than a timestamp, so two changes inside one second still read as two. A constant there leaves HA showing the list it read at setup forever; over MQTT the same revision re-announces the effect list mid-session. +HA caches the preset list and re-fetches only when the device's `info.fs.pmt` value changes, so the device reports a revision counter there that bumps on every preset save, rename and delete, a counter rather than a timestamp, so two changes inside one second still read as two. A constant there leaves HA showing the list it read at setup forever; over MQTT the same revision re-announces the effect list mid-session. -Only looks are exposed, on both paths. A `Drivers` or `Layouts` preset rewires pins or geometry, which must not be reachable from something that believes it is choosing a color scheme — the restriction is enforced at the apply entry point, not merely by omitting them from the list. +Only looks are exposed, on both paths. A `Drivers` or `Layouts` preset rewires pins or geometry, which must not be reachable from something that believes it is choosing a color scheme, the restriction is enforced at the apply entry point, not merely by omitting them from the list. Home Assistant's WLED integration connects on **port 80 only**: its host field rejects a port, so a desktop build (which defaults to 8080) needs `--port 80`, and that needs root: @@ -345,7 +347,7 @@ sudo uv run moondeck/run/run_desktop.py --port 80 The discovery buffers are sized to the looks this device actually has, and grow or shrink as presets are added and removed. There is no cap on the number: a fixed one would either reserve memory a small setup never uses, or silently publish nothing once the list outgrew it. ## MQTT, details -The topic prefix is `projectMM/` — a **stable** identifier (the last 6 hex of the device's MAC), fixed for the device's life. Renaming the device does **not** change its topics, so a hub's config never breaks on a rename (the WLED/Tasmota/Home-Assistant convention). It's derived, not a stored control. +The topic prefix is `projectMM/`, a **stable** identifier (the last 6 hex of the device's MAC), fixed for the device's life. Renaming the device does **not** change its topics, so a hub's config never breaks on a rename (the WLED/Tasmota/Home-Assistant convention). It's derived, not a stored control. **Topics** (for a device whose MAC ends `563cfe`): the device SUBSCRIBEs to the `set` topics and PUBLISHes the `get` topics on change (and on connect, so a controller never reads "No Response"). It also publishes its friendly `deviceName` on the retained `name` topic, so a hub can show the human name while the topics stay MAC-stable: @@ -361,9 +363,9 @@ The topic prefix is `projectMM/` — a **stable** identifier (the last 6 he | device → get | `projectMM/563cfe/update/state` | `{"installed_version":…,"latest_version":…,"release_url":…,"title":…}` (retained; HA update entity) | | set → device | `projectMM/563cfe/update/set` | target version string (empty = install latest); triggers OTA against the matching GitHub release asset | -The HomeKit color wheel has no "palette" concept, so `hsv/set`'s hue+saturation pick the **nearest palette** (each built-in palette has a representative color; the closest one is selected) and the value drives brightness — the color wheel becomes a natural palette selector. +The HomeKit color wheel has no "palette" concept, so `hsv/set`'s hue+saturation pick the **nearest palette** (each built-in palette has a representative color; the closest one is selected) and the value drives brightness, the color wheel becomes a natural palette selector. -**Homebridge** — install [`homebridge-mqttthing`](https://github.com/arachnetech/homebridge-mqttthing) and add a `lightbulb` accessory. Use the device's own MAC suffix (read it from the `mqtt_status`/topics, or `mosquitto_sub -t 'projectMM/#'`) in place of `563cfe`: +**Homebridge**, install [`homebridge-mqttthing`](https://github.com/arachnetech/homebridge-mqttthing) and add a `lightbulb` accessory. Use the device's own MAC suffix (read it from the `mqtt_status`/topics, or `mosquitto_sub -t 'projectMM/#'`) in place of `563cfe`: ```json { @@ -387,8 +389,8 @@ The HomeKit color wheel has no "palette" concept, so `hsv/set`'s hue+saturation ``` Home Assistant adopts the device two ways, both zero-config: -- **MQTT auto-discovery** — with `haDiscovery` on (opt-in; off by default) and a broker set, the device announces itself on `homeassistant/light/projectMM_/config` and HA auto-creates a wired entity with **on/off + brightness** (the config declares `brightness` only; color isn't in it, so the entity has no color control). Retained across reboots. Color/palette stays on the separate `hsv/set` topic above, not this entity. Off by default because the WLED `/json` shim already gives HA a richer light (color + palette + sensors) over mDNS with no broker — leaving both on lists the device twice; enable this only for broker-only / cross-subnet setups. -- **WLED integration** — HA's built-in WLED integration discovers the device over the WLED `/json` API projectMM already serves; on/off + brightness work with no broker. +- **MQTT auto-discovery**: with `haDiscovery` on (opt-in; off by default) and a broker set, the device announces itself on `homeassistant/light/projectMM_/config` and HA auto-creates a wired entity with **on/off + brightness** (the config declares `brightness` only; color isn't in it, so the entity has no color control). Retained across reboots. Color/palette stays on the separate `hsv/set` topic above, not this entity. Off by default because the WLED `/json` shim already gives HA a richer light (color + palette + sensors) over mDNS with no broker, leaving both on lists the device twice; enable this only for broker-only / cross-subnet setups. +- **WLED integration**: HA's built-in WLED integration discovers the device over the WLED `/json` API projectMM already serves; on/off + brightness work with no broker. Both can be on at once. Setup walkthrough (including exposing HA to Apple Home via HA's HomeKit Bridge, no Homebridge needed) in the [Home Assistant recipe](../../how-to/home-automation.md#adopt-in-home-assistant). @@ -397,8 +399,8 @@ The panel is a lazy folder **tree** (each folder loads its children on first exp - Click a folder's row to select it and toggle its expansion (▸/▾); click a selected file to open the editor. - The toolbar acts on the selected node: **+ folder** creates a folder inside it, **+ file** creates an empty file (click it to edit), **🗑 delete** removes the selected file, or a folder and everything inside it (press-twice to confirm), **⟳** refreshes. -- **Drag files from the desktop** onto a folder (or the tree) to upload them — the body streams straight to the file (any size, binary-safe; capped only by a sanity limit and the free space, which it reports if short); a per-file **⤓** streams it back to the desktop. +- **Drag files from the desktop** onto a folder (or the tree) to upload them, the body streams straight to the file (any size, binary-safe; capped only by a sanity limit and the free space, which it reports if short); a per-file **⤓** streams it back to the desktop. - The editor loads a file's text, pretty-prints JSON on open, and saves atomically; a binary file (contains a NUL) loads read-only (use ⤓ to fetch it intact). Upload and download both stream, so neither truncates. -- Create / delete are HTTP calls (`POST` / `DELETE /api/dir?path=`), not controls — the path rides the request, so nothing is stored on the device per op. +- Create / delete are HTTP calls (`POST` / `DELETE /api/dir?path=`), not controls, the path rides the request, so nothing is stored on the device per op. Last-modified dates (needs an NTP time source + LittleFS mtime), binary/large + folder upload, folder-as-zip download, and `.ml` syntax highlighting are backlogged ([backlog-core § File Manager follow-ups](../../work/future/backlog-core.md#file-manager-follow-ups)). diff --git a/docs/moonmodules/core/ui.md b/docs/moonmodules/core/ui.md index 9c070cb8..cbafa7fb 100644 --- a/docs/moonmodules/core/ui.md +++ b/docs/moonmodules/core/ui.md @@ -1,19 +1,12 @@ # Web UI -The projectMM web UI as shipped — the render layer over the MoonModule tree. This page is the UI's -own implementation spec (status bar, cards, control rendering, styling, the no-rebuild update -contract). The **high-level architecture** — hand-maintained files, MoonModule-driven rendering, the -light-domain plug-in points — lives in [MoonCore, the web UI](../../explanation/architecture/mooncore.md#web-ui); -the **backend contract** it consumes (every `/api/*` endpoint, the `/ws` frame shape, the control -descriptors) is owned by [HttpServerModule](moxygen/HttpServerModule.md); the **emoji legend** the -cards and picker render is [the tag emoji legend](../../explanation/architecture/index.md#tag-emoji-legend). +The projectMM web UI as shipped, the render layer over the MoonModule tree. This page is the UI's own implementation spec (status bar, cards, control rendering, styling, the no-rebuild update contract). The **high-level architecture**, hand-maintained files, MoonModule-driven rendering, the light-domain plug-in points, lives in [MoonCore, the web UI](../../explanation/architecture/mooncore.md#web-ui); the **backend contract** it consumes (every `/api/*` endpoint, the `/ws` frame shape, the control descriptors) is owned by [HttpServerModule](moxygen/HttpServerModule.md); the **emoji legend** the cards and picker render is [the tag emoji legend](../../explanation/architecture/index.md#tag-emoji-legend). This page covers only what those don't: the browser-side rendering behavior. ## Interaction principles - **Two timescales for inputs.** Local UI feedback (a slider's value label, a toggle's state) - updates within ~20 ms of the input event; network sends are debounced (150 ms slider, 500 ms text), - so a slider feels instant even when the server is busy. + updates within ~20 ms of the input event; network sends are debounced (150 ms slider, 500 ms text), so a slider feels instant even when the server is busy. - **No write-back races.** A `dragTs[moduleId:key]` cooldown of 1 s stops an incoming WS push from overwriting a control the user is actively manipulating. - **No DOM rebuilds on state updates.** The initial render builds the tree; state updates patch values @@ -24,7 +17,7 @@ This page covers only what those don't: the browser-side rendering behavior. ```text ┌─────────────────────────────────────────────────────────────┐ │ Status bar: ☰ · logo · brand · device name · sys stats │ -│ · WS dot · reconnect · reboot · theme toggle │ +│ · WS dot · reboot · theme toggle │ ├──────────────┬──────────────────────────────────────────────┤ │ Side nav │ Sticky preview canvas (shrinks on scroll) │ │ (root list) ├──────────────────────────────────────────────┤ @@ -36,11 +29,11 @@ This page covers only what those don't: the browser-side rendering behavior. └──────────────┴──────────────────────────────────────────────┘ ``` -- Fixed status bar at top (44 px) — hamburger, MoonLight logo, then the rest. +- Fixed status bar at top (44 px), hamburger, MoonLight logo, then the rest. - Side nav: a left column listing root modules; the selected root's card subtree fills the main area (one root visible at a time). - Module-card column max-width 500 px, centered (single-column, easier to read on phones). The preview - canvas is **not** capped — it spans the full content width. + canvas is **not** capped, it spans the full content width. - Preview canvas is sticky just below the status bar and shrinks 0 % → 50 % as the user scrolls 0 → 300 px. @@ -48,57 +41,42 @@ This page covers only what those don't: the browser-side rendering behavior. Fixed top, 44 px, left-to-right: -1. **Hamburger (☰)** — toggles the side nav (`body.nav-open`). See [§ Side navigation](#side-navigation). -2. **MoonLight logo** — 28 px PNG from `/moonlight-logo.png`; the same image is the page favicon. -3. **Brand wordmark** — "projectMM". -4. **Device name** — from the `System.deviceName` control. -5. **System stats** — `uptime · NN K free` (uptime `Xd Yh Zm Ws`, free heap KB), read from the +1. **Hamburger (☰)**, toggles the side nav (`body.nav-open`). See [§ Side navigation](#side-navigation). +2. **MoonLight logo**, 28 px PNG from `/moonlight-logo.png`; the same image is the page favicon. +3. **Brand wordmark**, "projectMM". +4. **Device name**, from the `System.deviceName` control. +5. **System stats**, `uptime · NN K free` (uptime `Xd Yh Zm Ws`, free heap KB), read from the SystemModule controls in the WS state push; no separate polling endpoint. -6. **WebSocket dot** — green = connected, gray = disconnected. The socket auto-reconnects with - exponential backoff (500 ms → 5 s) on close; no manual reconnect button (a page reload covers the - rare half-dead socket). -7. **Reboot button (⏻)** — red border via `data-crashed="true"` when `SystemModule.bootReason` is - PANIC / INT_WDT / TASK_WDT / BROWNOUT. Press-twice to confirm: the first click arms it (solid red), - a second sends `POST /api/reboot`. Disarms after 3 s or on pointer-leave — no `confirm()` popup. -8. **Theme toggle (☀/🌙)** — flips `[data-theme]` on ``; preference in `localStorage['mm_theme']`. +6. **WebSocket dot**, green = connected, gray = disconnected. The socket auto-reconnects with + exponential backoff (500 ms → 5 s) on close, and a page reload covers the rare half-dead socket. +7. **Reboot button (⏻)**, red border via `data-crashed="true"` when `SystemModule.bootReason` is + PANIC / INT_WDT / TASK_WDT / BROWNOUT. Press-twice to confirm: the first click arms it (solid red), a second sends `POST /api/reboot`. Disarms after 3 s or on pointer-leave, no `confirm()` popup. +8. **Theme toggle (☀/🌙)**, flips `[data-theme]` on ``; preference in `localStorage['mm_theme']`. ## Side navigation A left column listing the root modules, one entry per top-level MoonModule. - **One root visible at a time.** Clicking a nav entry sets the selected root; `renderCards()` renders - only that root's card subtree. The selection persists in `localStorage['mm_selectedRoot']`; the - active entry is highlighted. + only that root's card subtree. The selection persists in `localStorage['mm_selectedRoot']`; the active entry is highlighted. - **Hamburger toggle.** ☰ toggles `body.nav-open`. On wide screens (≥ 820 px) the nav is a static - column the hamburger collapses/expands; on narrow screens (< 820 px) it's a slide-in drawer over a - dimming overlay (click the overlay or press Esc to close). + column the hamburger collapses/expands; on narrow screens (< 820 px) it's a slide-in drawer over a dimming overlay (click the overlay or press Esc to close). - **The nav states its own order** (`NAV_ORDER` in app.js), which is deliberately NOT the order the - roots run in. `main.cpp` orders by dependency — Filesystem before anything that writes a file, - System before the modules that read its identity — and that is load-bearing, so it cannot be - reshuffled to suit a menu. It also reads as an implementation detail to a user: it puts System - first and the lights last. The nav instead groups by what someone is looking for: **Control**, - then the light pipeline in pipeline order (**Layouts → Effects → Drivers**), then the device - (**System, File Manager, Network, Services, Firmware**). A root the list does not name still - appears, after them, in scheduler order — adding a module never makes it invisible. + roots run in. `main.cpp` orders by dependency, Filesystem before anything that writes a file, System before the modules that read its identity, and that is load-bearing, so it cannot be reshuffled to suit a menu. It also reads as an implementation detail to a user: it puts System first and the lights last. The nav instead groups by what someone is looking for: **Control**, then the light pipeline in pipeline order (**Layouts → Effects → Drivers**), then the device (**System, File Manager, Network, Services, Firmware**). A root the list does not name still appears, after them, in scheduler order, adding a module never makes it invisible. - **No root reorder.** The order is fixed in that list; the nav does not drag-reorder. -- **Footer** pinned to the bottom of the nav: social links (GitHub, Discord, Reddit, YouTube — inline +- **Footer** pinned to the bottom of the nav: social links (GitHub, Discord, Reddit, YouTube, inline SVG) and a `© MoonLight` line. -The WS state push carries the full module tree; only the selected root is rendered, and -`updateValues()` patches just the visible cards (non-rendered roots have no DOM nodes, so their data -is silently ignored). Per-root server-side filtering was evaluated and deferred — the 1 Hz push is -cheap and the JSON is built through a streaming sink, so tree size is not a buffer-limit concern. +The WS state push carries the whole module tree; only the selected root is rendered, and `updateValues()` patches just the visible cards (non-rendered roots have no DOM nodes, so their data is silently ignored). Filtering per root server-side buys little: the full tree arrives once on connect and each second after that carries only what changed, built through a streaming sink, so tree size is not a buffer-limit concern. ## Module card -Each MoonModule renders as a card, with **child cards nested inside their parent card's box** — the -parent's border encloses its children, so the tree shape is visible structurally, not just by -indentation. Nesting depth shows as progressively lighter backgrounds and a left-margin indent. +Each MoonModule renders as a card, with **child cards nested inside their parent card's box**, the parent's border encloses its children, so the tree shape is visible structurally, not just by indentation. Nesting depth shows as progressively lighter backgrounds and a left-margin indent. ```text ┌─ card ──────────────────────────────────┐ │ [name] [emoji] [timing · 🧠 mem] [enabled toggle] [✎ × ☰] [? help] [{ } api] │ -│ [control rows — one per control] │ +│ [control rows, one per control] │ │ ┌─ child card ────────────────────────┐ │ │ │ … │ │ │ └─────────────────────────────────────┘ │ @@ -107,43 +85,28 @@ indentation. Nesting depth shows as progressively lighter backgrounds and a left ``` - The parent's own controls render **above** its children; `+ add child` renders **below** them. - Child cards live in a `.card-children` wrapper appended into the parent card's DOM node (not flat - siblings); `renderModuleTree` recurses into the parent card, not into `main`. -- **`{ }`** opens `GET /api/modules/{name}` in a new tab — that one module's live JSON, for issue - reports (see [Log an issue](../../how-to/logging-an-issue.md)). On EVERY card, unlike `✎`/`×` (user-editable - children only) and `?` (types that have a doc page). -- **Enabled toggle** in the right-hand action cluster mirrors `MoonModule::enabled()` — a styled - `