-
-
Notifications
You must be signed in to change notification settings - Fork 9
Prepare for 0.7.1 #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Docs ===== - Architecture: back to Effects at Core 0 (prio3) and Drivers and Sveltekit on Core1 - IO: Dig2Go LED strip details. - Layouts: Single Row/Column Backend ======= - main: back to Effects at Core 0 (prio3) and Drivers and Sveltekit on Core1 - Utilities: add addYield for task management - Art-Net out: replace lastMillis delay by vTaskDelayUntil, code efficiency, addYield - Effects: 1D / y-axis: WaveEffect, FreqMatrixEffect, NoiseMeterEffect, PopCornEffect - Layouts: Single row -> single column, Single line -> single row
|
Note
|
| Cohort / File(s) | Change Summary |
|---|---|
CPU Core Scheduling & Watchdog platformio.ini, src/main.cpp, lib/framework/ESP32SvelteKit.cpp, lib/framework/DownloadFirmwareService.cpp |
Re-pin tasks to different cores (unicore guard), add esp_task_wdt_reset() in task loops, adjust task creation core parameter. |
Yielding Utilities & Usage src/MoonBase/Utilities.h, src/MoonLight/Layers/PhysicalLayer.cpp, src/MoonLight/Layers/VirtualLayer.cpp |
Add global yield counters (yieldCallCount, yieldCounter) and helpers (addYield, logYield); replace direct taskYIELD() with addYield(...). |
ArtNet Driver & Timing src/MoonLight/Nodes/Drivers/D_ArtnetOut.h |
Change default usedChannelsPerUniverse, switch to per‑light pointer writes, replace throttle with vTaskDelayUntil using xLastWakeTime, add yields between package writes, expose timing member. |
Effects Axis & Layout Renames src/MoonLight/Nodes/Effects/E_MoonLight.h, src/MoonLight/Nodes/Effects/E_WLED.h, src/MoonLight/Nodes/Layouts/L_MoonLight.h, src/MoonLight/Modules/ModuleDrivers.h |
Reorient many effects from x‑centric to y/z‑centric indexing (use Coord3D), rename layouts (SingleLineLayout→SingleRowLayout, SingleRowLayout→SingleColumnLayout) and update call sites. |
EventSocket & Client Visibility lib/framework/EventSocket.h, lib/framework/EventSocket.cpp, lib/framework/EventEndpoint.h |
Add EVENT_CLIENT_INFO, track _clientVisibility map, implement handleClientInfo(...) and getActiveClients(), enhanced logging. |
Origin Propagation / Stateful Reads lib/framework/StatefulService.h, lib/framework/*Endpoint.h, lib/framework/FSPersistence.h, lib/framework/WebSocketServer.h |
Thread origin/module IDs through transactions and reads: beginTransaction(const String &originId) and extend read(...) signatures to accept originId; update callers across endpoints and persistence. |
Module IO / Monitor Emits src/MoonBase/Modules/ModuleIO.h, src/MoonLight/Modules/ModuleLightsControl.h, src/MoonLight/Modules/ModuleLiveScripts.h, src/MoonLight/Modules/ModuleDrivers.h |
Pass originId/_moduleName through read callbacks, gate monitor emits on getActiveClients() and include module name in emitEvent payloads. |
Shared Data Exposure src/MoonBase/Nodes.h, src/main.cpp |
Add SharedData.activeClients and update it periodically from EventSocket active client count. |
UI Visibility & Interface Changes interface/src/routes/+layout.svelte, interface/src/routes/demo/*, interface/src/routes/+page.svelte, interface/src/routes/+page.ts, interface/src/routes/+layout.svelte |
Track page visibility, emit client_info on visibility changes; remove demo route/component and its load hook; update Help link. |
Docs / Installer / Build Renames docs/..., firmware/..., firmware/installer/..., mkdocs.yml, misc/misc.txt, docs/gettingstarted/* |
Bulk rename esp32 env/build identifiers and firmware filenames (esp32-s3-devkitc-1-* → esp32-s3-*), reorganize Getting Started/installer docs, update manifest images/paths, remove docs/gettingstarted/installation.md. |
Miscellaneous / Minor Edits src/MoonLight/Nodes/Drivers/D_Infrared.h, assorted docs/..., platformio.ini, interface/vite.config.ts |
Lambda/formatting changes in infrared module (no behavior change), bump APP_DATE, update dev proxy targets and minor docs/link fixes. |
Sequence Diagram(s)
sequenceDiagram
autonumber
participant Browser
participant Frontend as SvelteKit
participant WebSocketServer
participant EventSocket
participant SharedData
participant EffectTask
participant VirtualLayer
participant PhysicalLayer
participant ArtNetDriver
participant Watchdog
Browser->>Frontend: page visible / hidden (visibilitychange)
Frontend->>WebSocketServer: WS connect / send client_info {visible}
WebSocketServer->>EventSocket: forward client_info (originId)
EventSocket->>EventSocket: handleClientInfo -> update _clientVisibility
EventSocket->>SharedData: update activeClients
SharedData->>EffectTask: periodic read of activeClients
Note over EffectTask,VirtualLayer: Frame generation loop
EffectTask->>VirtualLayer: loop() -> node->loop()
VirtualLayer->>VirtualLayer: addYield(10)
VirtualLayer->>PhysicalLayer: deliver frame
PhysicalLayer->>PhysicalLayer: addYield(10)
PhysicalLayer->>ArtNetDriver: prepare/writePackage()
ArtNetDriver->>ArtNetDriver: addYield(10) between packages
ArtNetDriver->>ArtNetDriver: vTaskDelayUntil(xLastWakeTime + framePeriod)
Watchdog->>EffectTask: expects esp_task_wdt_reset() each loop
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
- Drive lots of LEDs (130K max) #93 — Overlaps task/core affinity, watchdog resets, and yield replacements.
- add heap-optimization: request heap optimization review #62 — Related EventSocket client-info/visibility handling and endpoint origin propagation.
- 1D effects x to y-axis conversion #81 — Related axis-orientation and coordinate conversions in effects and layers.
Suggested labels
enhancement
Poem
🐇 I hopped through cores and swapped their beds,
I learned to yield so watchdogs kept their threads,
Packets paced in pulses, rows became columns bright,
I count the peeking clients glowing in the night,
A tiny rabbit cheers — the LEDs dance right.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 17.07% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title 'Prepare for 0.7.1' accurately summarizes the main objective of the PR—a release preparation for version 0.7.1. It is concise, clear, and directly reflects the changeset's purpose. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @platformio.ini:
- Line 66: The build flag ESP32SVELTEKIT_RUNNING_CORE is currently set to 1
which pins ESP32SvelteKit to APP_CPU and conflicts with timing-critical
AppDrivers on Core 1; change the definition of ESP32SVELTEKIT_RUNNING_CORE (the
-D ESP32SVELTEKIT_RUNNING_CORE=1 entry) to 0 so SvelteKit runs on PRO_CPU (core
0), leaving core 1 exclusively for AppDrivers, and update the accompanying
comment to reflect this core assignment.
🧹 Nitpick comments (3)
src/MoonBase/Utilities.h (2)
274-275: Initialize global yield tracking variables.The inline global variables
yieldFrequencyandyieldCounterare declared but not explicitly initialized. While inline variables in C++17 are zero-initialized by default, explicit initialization improves clarity and prevents potential issues if usage context changes.✨ Proposed fix
-inline uint16_t yieldFrequency; -inline uint16_t yieldCounter; +inline uint16_t yieldFrequency = 0; +inline uint16_t yieldCounter = 0;
277-282: Misleading variable name:yieldFrequencyis actually a counter.The variable
yieldFrequencyis incremented on every call and used as a counter (++yieldFrequency % frequency), but its name suggests it represents a frequency value. This creates confusion when reading the code.Consider renaming to
yieldCallCountoryieldCheckCounterfor clarity.♻️ Proposed refactor
-inline uint16_t yieldFrequency; +inline uint16_t yieldCallCount = 0; inline uint16_t yieldCounter; inline void addYield(uint8_t frequency) { - if (++yieldFrequency % frequency == 0) { + if (++yieldCallCount % frequency == 0) { yieldCounter++; vTaskDelay(1); // taskYIELD() is not good enough as it does not give back control to idle tasks } } inline void logYield() { - EXT_LOGD(ML_TAG, "yieldCounter %d (%d)", yieldFrequency, yieldCounter); + EXT_LOGD(ML_TAG, "yieldCounter %d (%d)", yieldCallCount, yieldCounter); yieldCounter = 0; }src/MoonLight/Nodes/Drivers/D_ArtnetOut.h (1)
160-170: Consider flattening nested RGB channel conditionals.The current nested structure works correctly but could be simplified for readability. Since each RGB offset is independent, the conditionals can be flattened:
♻️ Proposed refactor
- // correct the RGB channels for color order and brightness reOrderAndDimRGBW(p + header->offsetRGB, c + header->offsetRGB); - - if (header->offsetRGB1 != UINT8_MAX) { + if (header->offsetRGB1 != UINT8_MAX) reOrderAndDimRGBW(p + header->offsetRGB1, c + header->offsetRGB1); - if (header->offsetRGB2 != UINT8_MAX) { + if (header->offsetRGB2 != UINT8_MAX) reOrderAndDimRGBW(p + header->offsetRGB2, c + header->offsetRGB2); - if (header->offsetRGB3 != UINT8_MAX) { + if (header->offsetRGB3 != UINT8_MAX) reOrderAndDimRGBW(p + header->offsetRGB3, c + header->offsetRGB3); - } - } - }This maintains the same logic while improving readability.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
docs/develop/architecture.mddocs/moonbase/inputoutput.mddocs/moonlight/layouts.mdlib/framework/EventSocket.cppplatformio.inisrc/MoonBase/Modules/ModuleIO.hsrc/MoonBase/Utilities.hsrc/MoonLight/Layers/PhysicalLayer.cppsrc/MoonLight/Layers/VirtualLayer.cppsrc/MoonLight/Modules/ModuleDrivers.hsrc/MoonLight/Nodes/Drivers/D_ArtnetOut.hsrc/MoonLight/Nodes/Effects/E_MoonLight.hsrc/MoonLight/Nodes/Effects/E_WLED.hsrc/MoonLight/Nodes/Layouts/L_MoonLight.hsrc/main.cpp
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
src/MoonLight/Layers/VirtualLayer.cppdocs/develop/architecture.mdplatformio.inisrc/MoonBase/Utilities.hsrc/main.cppsrc/MoonLight/Layers/PhysicalLayer.cppsrc/MoonLight/Nodes/Drivers/D_ArtnetOut.h
📚 Learning: 2026-01-06T15:24:11.096Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:164-171
Timestamp: 2026-01-06T15:24:11.096Z
Learning: In VirtualLayer.cpp (setLight and getLight), the unmapped path intentionally disables bounds checking when indexV >= mappingTableSize or oneToOneMapping is true to optimize performance for very large LED counts (100K+). This is a deliberate design decision; only rely on this behavior if you can guarantee calls stay within bounds. Document this contract and avoid adding defensive checks in the hot path. Use explicit bounds checks in all other code paths or when the input cannot be guaranteed to be in-range.
Applied to files:
src/MoonLight/Layers/VirtualLayer.cpp
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
src/MoonLight/Layers/VirtualLayer.cppsrc/MoonLight/Nodes/Effects/E_WLED.hsrc/MoonLight/Nodes/Effects/E_MoonLight.hdocs/develop/architecture.mdsrc/MoonLight/Layers/PhysicalLayer.cpp
📚 Learning: 2026-01-06T15:24:29.770Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:212-221
Timestamp: 2026-01-06T15:24:29.770Z
Learning: In VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking for unmapped lights (the else branch around lines 212-221) is intentionally disabled for performance when driving 100K+ LEDs. The design places responsibility on effect developers to stay within maxChannels. This is an explicit performance vs safety tradeoff for extreme LED counts.
Applied to files:
src/MoonLight/Layers/VirtualLayer.cppsrc/MoonLight/Nodes/Effects/E_WLED.hsrc/MoonLight/Nodes/Effects/E_MoonLight.hdocs/develop/architecture.mdsrc/MoonLight/Nodes/Drivers/D_ArtnetOut.h
📚 Learning: 2025-12-18T15:15:57.828Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-18T15:15:57.828Z
Learning: In DripEffect (E_WLED.h), dropsSize must be set to layer->size.y (not layer->size.x) in onSizeChanged(), because the drops array is allocated as [layer->size.y][maxNumDrops]. Using layer->size.x causes out-of-bounds access when iterating over y-coordinates.
Applied to files:
src/MoonLight/Nodes/Effects/E_WLED.hsrc/MoonLight/Nodes/Effects/E_MoonLight.h
📚 Learning: 2026-01-06T15:24:17.118Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:164-171
Timestamp: 2026-01-06T15:24:17.118Z
Learning: In VirtualLayer::setLight and VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking in the unmapped path (when indexV >= mappingTableSize or oneToOneMapping is true) is intentionally disabled for performance when driving very large LED counts (100K+). Effect developers are responsible for ensuring their code stays within bounds. This is an explicit design decision prioritizing performance over defensive checks in the hot path.
Applied to files:
src/MoonLight/Nodes/Effects/E_WLED.hsrc/MoonLight/Nodes/Effects/E_MoonLight.h
📚 Learning: 2025-12-07T14:16:03.228Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-07T14:16:03.228Z
Learning: VirtualLayer mappingTableSize is uint16_t with a maximum of 65535 LEDs. If a user defines a space where size.x * size.y * size.z exceeds 65535, the code accepts the overflow and performs mapping partially. This is an explicit design decision balancing performance and memory constraints.
Applied to files:
src/MoonLight/Nodes/Effects/E_WLED.h
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
docs/develop/architecture.mdsrc/main.cpp
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
docs/develop/architecture.mdsrc/main.cpp
📚 Learning: 2026-01-04T09:31:07.533Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:07.533Z
Learning: In lib/framework/ESP32SvelteKit.cpp, the SvelteKit loop task stack size of 6KB (6 * 1024 bytes) for non-PSRAM devices has been validated through testing, showing approximately 1500 bytes of free stack remaining under load, providing adequate ~24% headroom.
Applied to files:
docs/develop/architecture.mdsrc/main.cpp
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/**/platformio.ini : Use PlatformIO to build and upload the ESP32 backend C++ code
Applied to files:
platformio.ini
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/src/**/*.{h,cpp} : Modify C++ backend code in the `src/` and `lib/` directories for ESP32 development
Applied to files:
platformio.ini
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Be aware of the integrated build process where the PlatformIO build automatically builds and embeds the SvelteKit frontend into the ESP32 firmware
Applied to files:
platformio.ini
🧬 Code graph analysis (6)
src/MoonLight/Layers/VirtualLayer.cpp (1)
src/MoonBase/Utilities.h (1)
addYield(277-282)
src/MoonLight/Modules/ModuleDrivers.h (1)
src/MoonLight/Nodes/Layouts/L_MoonLight.h (1)
SingleColumnLayout(398-437)
src/MoonLight/Nodes/Effects/E_WLED.h (1)
src/MoonBase/Utilities.h (1)
Coord3D(151-163)
src/MoonLight/Nodes/Effects/E_MoonLight.h (1)
src/MoonBase/Utilities.h (1)
Coord3D(151-163)
src/MoonLight/Layers/PhysicalLayer.cpp (1)
src/MoonBase/Utilities.h (1)
addYield(277-282)
src/MoonLight/Nodes/Drivers/D_ArtnetOut.h (3)
src/MoonBase/Nodes.cpp (6)
setup(140-204)setup(140-140)setup(346-363)setup(346-346)reOrderAndDimRGBW(567-575)reOrderAndDimRGBW(567-567)src/MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h (1)
setup(38-46)src/MoonBase/Utilities.h (1)
addYield(277-282)
🪛 markdownlint-cli2 (0.18.1)
docs/moonlight/layouts.md
26-26: Table pipe style
Expected: leading_only; Actual: leading_and_trailing; Unexpected trailing pipe
(MD055, table-pipe-style)
27-27: Table pipe style
Expected: leading_only; Actual: leading_and_trailing; Unexpected trailing pipe
(MD055, table-pipe-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (28)
lib/framework/EventSocket.cpp (3)
160-160: Enhanced logging improves debugging.Adding
originIdandlento the warning message provides valuable context for tracing invalid event emissions.
193-195: Verify the rationale for disabling subscription removal on send failures.The enhanced logging at line 193 is excellent—it provides full context for debugging failed deliveries.
However, commenting out the subscription removal at line 194 means that failed clients remain in the subscription list and will be retried on every subsequent event emission. While this may be intentional for handling transient network issues, it could also lead to:
- Repeated failed send attempts consuming resources
- Degraded performance over time if failures persist
- Memory overhead from stale subscriptions
Note that null clients are still removed at line 213, so this only affects cases where
getClient()succeeds butsendMessage()fails.Could you clarify the reasoning behind keeping subscriptions active on send failures? Is there a retry/recovery strategy in place, or should persistent failures eventually trigger removal?
224-228: Same concern: verify disabling subscription removal in broadcast path.This follows the same pattern as lines 193-195, but for the broadcast delivery path. The enhanced logging at line 226 is valuable, but commenting out the iterator-based removal at line 227 means:
- The iterator still advances correctly on success (line 231)
- Failed clients remain in the subscription list for future attempts
- The for-loop with iterator pattern (line 202) was designed to support safe removal during iteration
Same question as the previous segment: is there a deliberate strategy for handling persistent send failures without removal, or should subscriptions be cleaned up after repeated failures?
platformio.ini (1)
59-59: Version date updated to 20260107.Routine version tracking for the 0.7.1 release preparation.
src/MoonLight/Nodes/Layouts/L_MoonLight.h (2)
357-396: Layout class renamed from SingleLineLayout to SingleRowLayout.The rename clarifies that this layout creates a horizontal row of LEDs. The implementation logic remains unchanged—only the class name and user-facing display name are updated.
398-437: Layout class renamed from SingleRowLayout to SingleColumnLayout.The rename clarifies that this layout creates a vertical column of LEDs. The implementation logic remains unchanged—only the class name and user-facing display name are updated. This prevents confusion between the two 1D layout options.
src/MoonLight/Modules/ModuleDrivers.h (2)
79-109: Layout references updated to match renamed classes.The reference to
SingleColumnLayout(line 92) correctly reflects the layout class rename fromSingleLineLayout. The ordering now shows SingleRowLayout followed by SingleColumnLayout, providing clearer horizontal/vertical layout options.
111-143: Node allocation updated for renamed layout class.The
checkAndAlloc<SingleColumnLayout>call (line 126) correctly aligns with the layout class rename, ensuring proper instantiation when users select this layout.src/MoonLight/Nodes/Effects/E_WLED.h (3)
234-306: Freq Matrix axis reorientation from x-shift to y-shift.The pixel shifting logic has been completely rewritten to shift along the y-axis instead of x-axis:
- Line 298: New pixel placed at
Coord3D(0, 0)(top position)- Line 299: Outer loop iterates downward through y-coordinates
- Lines 301-302: Inner loops propagate the color across x and z dimensions
This changes the visual direction of the frequency-driven pixel trail. The effect now shifts downward (along y) while maintaining the same color across each horizontal slice.
Verify this produces the intended visualization—particularly for 1D fixtures where this axis swap may significantly alter the appearance.
482-516: Noise Meter reoriented to grow along y-axis.Lines 503, 509-510 change the meter visualization:
maxLennow maps audio level tolayer->size.y(wassize.x)- Inner loops propagate each bar position across x and z dimensions
- Coordinates changed from
Coord3D(i, x, z)toCoord3D(x, i, z)This reorients the audio meter bars to grow vertically (along y-axis) instead of horizontally. The effect now visualizes volume as vertical bars, which is a more conventional VU meter orientation.
1079-1153: PopCorn effect reoriented along y-axis.The popcorn kernels now move along the y-axis instead of x-axis:
- Line 1103: Gravity scaling adjusted for
layer->size.y(wassize.x)- Line 1127: Peak height calculation uses
(layer->size.y - 1)(wassize.x - 1)- Lines 1145-1148: Rendering uses
ledIndexas y-coordinate and propagates across x and z dimensions viaCoord3D(x, ledIndex, z)This changes kernels to "pop" vertically, which is more physically intuitive (gravity acts along vertical axis). The propagation logic correctly extends each kernel position across the x and z dimensions for multi-dimensional fixtures.
src/MoonLight/Nodes/Effects/E_MoonLight.h (1)
519-564: WaveEffect axis swap logic is internally consistent; visual verification across fixture types required.The code correctly implements a horizontal wave pattern with x-axis iteration and y-position amplitude. All temporal offsets use x-based indexing, position calculations scale to
layer->size.y, and rendering coordinates match this orientation withCoord3D(x, pos). The connecting line logic (drawing vertical lines on discontinuities) aligns with y-axis rendering.However, visual verification across different fixture geometries (2D panels, cubes, 1D strips) cannot be validated statically and should be tested with actual hardware or simulator.
src/MoonLight/Layers/PhysicalLayer.cpp (1)
112-112: LGTM! Centralized yield mechanism correctly implemented.The addition of
addYield(10)afternode->loop()aligns with the centralized yield tracking introduced insrc/MoonBase/Utilities.h. This addresses the watchdog timeout issue at extreme loads by ensuring the IDLE task gets CPU time during node iteration loops.Based on learnings, this is the correct solution for preventing task watchdog timeouts when processing many nodes without yielding.
src/MoonLight/Layers/VirtualLayer.cpp (1)
65-66: LGTM! Consistent yield mechanism across layers.The replacement of
taskYIELD()withaddYield(10)mirrors the identical change inPhysicalLayer.cpp(line 112), ensuring consistent yielding behavior across both Physical and Virtual layers during node processing loops.Based on learnings,
vTaskDelay(1)(called byaddYield) is superior totaskYIELD()as it properly returns control to the IDLE task.docs/moonbase/inputoutput.md (1)
60-61: LGTM! Improved documentation and naming consistency.The naming change from "Dig 2Go" to "Dig2Go" aligns with the corresponding update in
src/MoonBase/Modules/ModuleIO.h(line 120). The added GRBW configuration guidance for the shipped 300 LED strip helps users configure their hardware correctly.src/MoonBase/Modules/ModuleIO.h (1)
120-120: LGTM! Naming consistency improvement.The removal of the space in "QuinLED Dig2Go" aligns with the documentation update in
docs/moonbase/inputoutput.md(line 60).src/main.cpp (2)
334-350: LGTM! Core assignment swap aligns with architecture requirements.The core reassignment correctly implements the learned architecture pattern:
- effectTask → Core 0: Computation-only task (calculating colors, writing to channels array) can tolerate WiFi preemption
- driverTask → Core 1: Timing-critical hardware operations (DMA, I2S, LCD, PARLIO) require uninterrupted execution without WiFi interference
Based on learnings, ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference that runs at priority ~23 on Core 0.
156-156: Minor non-functional cleanup.These changes are non-functional:
- Lines 156, 192: Removed trailing comments from
vTaskDelay(1)calls- Line 366: Added commented
logYield()call, showing awareness of the new yield logging utility fromsrc/MoonBase/Utilities.hwithout activating itAlso applies to: 192-192, 366-366
docs/moonlight/layouts.md (1)
26-27: LGTM! Documentation reflects layout naming changes.The documentation correctly reflects the layout class renames (SingleLineLayout → SingleColumnLayout/SingleRowLayout) mentioned in the AI summary. The description "Choose Single Column for LED strips" provides clear guidance for users.
src/MoonBase/Utilities.h (2)
277-282: LGTM! Yield mechanism addresses watchdog starvation.The implementation correctly uses
vTaskDelay(1)instead oftaskYIELD()to give idle tasks execution time, directly addressing the retrieved learning about task watchdog firing at extreme loads when node iteration loops don't yield.The frequency-based yielding (every Nth call) provides a good balance between responsiveness and overhead.
Based on learnings, this solution prevents IDLE task starvation by yielding inside iteration loops.
274-287: Global yield counters have unprotected concurrent access, but acceptable for diagnostic use.
yieldFrequencyandyieldCounterare modified byaddYield()from two concurrent tasks—effectTask callsVirtualLayer::loop()(which callsaddYield()at line 65 in VirtualLayer.cpp) and driverTask callsPhysicalLayer::loopDrivers()(which callsaddYield()at line 112 in PhysicalLayer.cpp). The read-modify-write operations lack synchronization, creating race conditions with lost updates. However, since these counters are diagnostic only and approximate counts are sufficient for monitoring, the current unsynchronized approach is acceptable—the performance cost of synchronization would defeat the purpose of lightweight yielding.docs/develop/architecture.md (1)
14-36: LGTM! Architecture documentation accurately reflects core reassignments.The updated task assignments correctly document:
- Effect Task on Core 0 (PRO_CPU): Computation-only workload that tolerates WiFi preemption
- Driver Task on Core 1 (APP_CPU): Timing-critical hardware operations isolated from WiFi interference
- ESP32SvelteKit on Core 1 (APP_CPU): Lower priority ensures LED timing is never compromised
These changes align perfectly with the retrieved learnings about ESP32 core assignments for LED systems.
Based on learnings: Effects task can run on Core 0 since it only performs computation; Driver task requires Core 1 for uninterrupted timing-critical hardware operations.
src/MoonLight/Nodes/Drivers/D_ArtnetOut.h (6)
31-31: LGTM! Improved default forusedChannelsPerUniverse.The new default value of 510 is sensible for Art-Net (512-byte universe / 3 RGB channels = 170 LEDs = 510 channels). This prevents the previous invalid 0 default before
onUpdate()is called.
127-127: LGTM! Improved frame pacing withvTaskDelayUntil.The switch from delay-based throttling to wake-time-based scheduling using
vTaskDelayUntilprevents frame drift and provides more consistent Art-Net output timing. ThexLastWakeTimemember is properly initialized withxTaskGetTickCount().This is the correct FreeRTOS pattern for periodic tasks.
Also applies to: 210-210
160-170: LGTM! RGB channel handling with proper bounds checks.The nested conditionals correctly verify that each optional RGB channel offset is valid (not
UINT8_MAX) before processing. The use of pointer arithmetic with offsets is safe since the bulkmemcpy(line 157) already ensures all channels are within bounds.
185-185: LGTM! Yield during Art-Net streaming prevents watchdog timeout.The
addYield(10)call afterwritePackage()addresses the retrieved learning about task watchdog firing at extreme loads. Yielding every 10 packets provides opportunities for the IDLE task to run and reset the watchdog without excessive overhead.This is particularly important when streaming large numbers of universes.
Based on learnings, adding yields inside iteration loops prevents IDLE task starvation and watchdog timeouts.
82-94: LGTM! Proper UI synchronization inonUpdate.The recalculation and UI update logic correctly:
- Derives
totalChannelsfrom header configuration- Calculates
usedChannelsPerUniverseto align with light channel boundaries- Computes
totalUniversesusing ceiling division to ensure sufficient capacity- Updates UI controls to reflect calculated values
- Triggers UI refresh via
requestUIUpdateThis ensures the UI always displays accurate Art-Net configuration.
154-158: Bounds checking is intentionally disabled for performance; developers must ensurenrOfLights * channelsPerLightdoes not exceed allocatedmaxChannels.The loop condition properly bounds
indexP < header->nrOfLights, but the pointer accesschannelsD[indexP * header->channelsPerLight]relies on an invariant that is not explicitly enforced at runtime. The UI constrainsnrOfLights ≤ maxChannels / 3(assuming 3 channels per light), but sincechannelsPerLightis configurable, changing it afternrOfLightsis set could violate the allocation bounds. This is an accepted tradeoff for performance when handling very large LED counts (100K+); effect and configuration developers are responsible for maintaining the allocation invariant.
Docs ==== - Architecture: add idle watchdog - Develop installation: move Installer troubleshooting here - Hardware and installer: restructure Firmware ======== - remove devkit in naming Backend ======= - Event socket: add vTaskDelay(1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
firmware/installer/manifest.json (1)
75-75: Fix typo in name field.There's an extra space in the name: "esp32-p4- olimex" should be "esp32-p4-olimex" to maintain consistency with other build names.
📝 Proposed fix
- "name": "esp32-p4- olimex", + "name": "esp32-p4-olimex",
🤖 Fix all issues with AI agents
In @docs/develop/architecture.md:
- Line 275: In the sentence that reads "Mostly in the effects and drivers task
but also in other tasks like WiFi occassionally." in
docs/develop/architecture.md, fix the spelling mistake by replacing
"occassionally" with "occasionally" so it reads "...WiFi occasionally."; update
that single word in the paragraph describing task watchdog crashes.
In @docs/develop/documentation.md:
- Around line 43-46: Replace hard tab characters used for nested list
indentation in docs/develop/documentation.md with two space characters so
Markdown renders correctly: update the four list lines beginning with "For
drivers..." through "For WLED effects..." to use 2-space indentation for any
nested bullets and also replace the hard tab at line 100 with two spaces; ensure
no tabs remain in the file.
In @docs/develop/installation.md:
- Line 243: The markdown list items around the installation docs have incorrect
two-space indentation and stray HTML closing tags; convert those lines to proper
Markdown list syntax (remove the extra leading spaces and any </li> tags) so
they render as standard bullets, and add meaningful alt text to the image
markdown entries that are missing alt attributes so images are accessible
(replace image syntax like  with something like ).
In @docs/gettingstarted/hardware.md:
- Line 55: Correct the two typos in the hardware documentation sentence that
mentions Art-Net: change "uased" to "used" and "off-the-shelve" to
"off-the-shelf" in the sentence starting "A few examples are shown below. Note
that MoonLight also supports Art-Net..." so the sentence reads correctly about
Art-Net being usable as an alternative to off-the-shelf controllers.
- Line 28: The <img> tag in the documentation is missing alt text; update the
<img width="350"
src="https://github.com/user-attachments/assets/1623a751-5f4b-463b-a6f3-a642c2bc52bf"/>
element by adding a descriptive alt attribute (e.g., alt="Diagram of hardware
setup showing X, Y, and Z") that succinctly describes the image content for
accessibility and screen readers.
- Around line 7-17: Use a consistent "premade" spelling and fix hyphenation:
change "pre-made" in the sentence before the "Premade ESP32 devices" heading to
"premade", change the heading "Premade ESP32 devices" to match that same
"premade" form if needed, and update the bullet "* [QuinLed LED pre assembled
boards]" to "* [QuinLed LED pre-assembled boards]" (hyphenate "pre-assembled")
so all occurrences use "premade" and "pre-assembled" consistently.
In @docs/gettingstarted/installer.md:
- Line 27: Images in docs/gettingstarted/installer.md lack alt attributes;
update each <img> tag to include a descriptive alt attribute per WCAG (e.g.,
replace `<img width="250" src="...">` with `<img width="250" src="..."
alt="ESPConnect device selection interface">`), applying the suggested alt texts
for the specific images referenced (lines ~27, 32, 36, 61, 65, 73, 95, 101, 107,
130, 140) and ensure all other image tags in this file have similarly concise,
descriptive alt values matching the screenshot content.
In @docs/gettingstarted/overview.md:
- Around line 6-8: The nested list under the parent item needs MD007-compliant
indentation: change the four-space indents before the child bullets for "Lights
control", "Effects", and "Drivers" to two spaces each so they are nested two
spaces from the parent item; update those list lines to use two spaces before
the leading asterisk for consistent Markdown nesting.
In @docs/moonbase/inputoutput.md:
- Line 61: The list sub-bullet line starting with "Dig2Go: Shipped with a 300
LED, GRBW led strip: Choose layout with 300 lights (e.g. Single Column for 1D,
Panel 15x20 for 2D). Select Light preset GRBW in the LED Driver." has 4 leading
spaces; reduce the indentation to 2 spaces so it aligns as a proper Markdown
sub-bullet (i.e., change the leading whitespace from four spaces to two while
keeping the text unchanged).
In @docs/moonlight/drivers.md:
- Line 9: The Layouts link text uses '../../moonlight/layouts' without a
trailing slash while other links use trailing slashes; update the link string
'../../moonlight/layouts' to '../../moonlight/layouts/' so it matches the
pattern used for livescripts/, lightscontrol/, modifiers/, and effects/ (or
alternatively normalize all similar links by removing trailing slashes
consistently).
In @firmware/installer/manifest_esp32-c3.json:
- Line 2: The manifest's version and binary path are out of sync with the build
configuration; update the "version" field in the manifest from "0.7.0" to
"0.7.1" and change the binary path to "./MoonLight_esp32-c3_0-7-1_webflash.bin"
to match the APP_VERSION in platformio.ini (or alternatively set APP_VERSION to
"0.7.0" in platformio.ini if you intend to keep the manifest as-is); ensure the
filename pattern produced by merge_bin.py (e.g.,
MoonLight_esp32-c3_0-7-1_webflash.bin) matches the manifest binary reference and
that the image reference "./images/esp32-c3.jpg" remains unchanged.
In @firmware/installer/manifest.json:
- Around line 35-38: The manifest references binary files that don't exist:
update the "parts" entries for the devices named esp32-s3-n8r8v and
esp32-s3-n16r8v so the "path" values match the actual filenames, or rename the
actual binaries to match the manifest; specifically either rename
MoonLight_esp32-s3-devkitc-1-n8r8v_0-7-0_webflash.bin and
MoonLight_esp32-s3-devkitc-1-n16r8v_0-7-0_webflash.bin to
MoonLight_esp32-s3-n8r8v_0-7-0_webflash.bin and
MoonLight_esp32-s3-n16r8v_0-7-0_webflash.bin respectively, or change the
manifest entries for the "parts" array to reference the existing devkitc-1
filenames.
- Around line 43-46: The manifest entry for device "esp32-s3-n16r8v" references
a non-existent binary at "./MoonLight_esp32-s3-n16r8v_0-7-0_webflash.bin" under
the "parts" array; fix it by either renaming the actual binary file to that
exact filename or update the "path" value in the manifest to the existing binary
name (e.g., the repo's MoonLight_esp32-s3-devkitc-1-n16r8v_0-7-0_webflash.bin)
so the "parts" -> "path" string matches an actual file. Ensure the manifest
"name" and "parts"->"path" remain consistent and that the referenced file is
committed.
In @platformio.ini:
- Around line 65-66: The comment above the ESP32SVELTEKIT_RUNNING_CORE build
define is contradictory: it says networking is moved to "protocol core 0" but
the define ESP32SVELTEKIT_RUNNING_CORE=1 assigns tasks to Core 1 (APP_CPU).
Update the comment to accurately describe that setting Core 1 (APP_CPU) places
the ESP32SvelteKit tasks on the application core to avoid contention with WiFi
on Core 0 (PRO_CPU), or change the define if you really intend Core 0; ensure
the comment references ESP32SVELTEKIT_RUNNING_CORE and clearly states which core
(PRO_CPU/Core 0 vs APP_CPU/Core 1) is used and why.
In @src/MoonBase/Utilities.h:
- Around line 284-287: logYield() currently reads and then resets the global
yieldCounter without synchronization, causing races with concurrent increments;
fix by performing an atomic read-and-reset (e.g., use std::atomic for
yieldCounter and call yieldCounter.exchange(0) to capture the current value
atomically) or protect the read/reset with the existing mutex used for
yieldCounter updates, then log the captured value with EXT_LOGD(ML_TAG, ...) and
avoid directly resetting the non-atomic variable.
- Around line 274-282: The global diagnostic counters yieldCallCount and
yieldCounter and the addYield function have data-race risks because ++ on
uint16_t is non-atomic; change yieldCallCount and yieldCounter to
std::atomic<uint16_t> (include <atomic>) and replace ++/++ usage with atomic
operations (e.g., fetch_add(1) and use the returned value for the modulus check)
inside addYield so increments and reads are safe across tasks while preserving
the vTaskDelay(1) call; alternatively, if counts only need per-task accuracy,
make them task-local variables instead of globals.
🧹 Nitpick comments (4)
docs/network/ethernet.md (1)
3-3: Consider adding alt text for accessibility.The image lacks alt text, which impacts screen reader users and SEO. As per the static analysis hint, this is a recommended accessibility improvement.
♻️ Proposed fix
-<img width="320" src="../../media/network/MB-ethernet.png" /> +<img width="320" src="../../media/network/MB-ethernet.png" alt="MoonBase Ethernet configuration interface" />docs/network/sta.md (1)
3-3: Consider adding alt text for accessibility.The image lacks alt text, which impacts screen reader users and SEO. As per the static analysis hint, this is a recommended accessibility improvement.
♻️ Proposed fix
-<img width="320" src="../../media/network/MB-WiFi1.png" /> +<img width="320" src="../../media/network/MB-WiFi1.png" alt="MoonBase WiFi Station configuration interface" />docs/gettingstarted/installer.md (1)
142-142: Fix line continuation formatting on line 142.Line 142 ends with whitespace before a continuation, creating an awkward line break. Reflow to clean up:
-Click Connect and choose your device. Select Flash Tools, go to Flash Firmware, open a firmware.bin file from your file system, select Flash Offset App0 and Flash +Click Connect and choose your device. Select Flash Tools, go to Flash Firmware, open a firmware.bin file from your file system, select Flash Offset App0 and Flash.src/main.cpp (1)
366-367: Commented debug hook is fine for now.The commented
logYield()call provides a hook for future debugging of yield behavior. Consider removing or uncommenting based on whether this diagnostic capability is needed.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (8)
docs/media/moonbase/inputoutput.pngis excluded by!**/*.pngdocs/media/moonlight/ML-Start.pngis excluded by!**/*.pngdocs/media/network/MB-WiFi1.pngis excluded by!**/*.pngdocs/media/network/MB-WiFi2.pngis excluded by!**/*.pngdocs/media/network/MB-ethernet.pngis excluded by!**/*.pngfirmware/installer/images/esp32-c3.jpgis excluded by!**/*.jpgfirmware/installer/images/esp32-s3-n16r8v.jpgis excluded by!**/*.jpgfirmware/installer/images/esp32-s3-n8r8v.jpgis excluded by!**/*.jpg
📒 Files selected for processing (32)
docs/develop/architecture.mddocs/develop/development.mddocs/develop/documentation.mddocs/develop/installation.mddocs/develop/nodes.mddocs/develop/sveltekit.mddocs/gettingstarted.mddocs/gettingstarted/hardware.mddocs/gettingstarted/installation.mddocs/gettingstarted/installer.mddocs/gettingstarted/overview.mddocs/moonbase/inputoutput.mddocs/moonlight/drivers.mddocs/network/ethernet.mddocs/network/sta.mdfirmware/esp32-c3.inifirmware/esp32-s3.inifirmware/installer/manifest.jsonfirmware/installer/manifest_esp32-c3.jsonfirmware/installer/manifest_esp32-s3-n16r8v.jsonfirmware/installer/manifest_esp32-s3-n8r8v.jsoninterface/src/routes/+page.sveltelib/framework/EventSocket.cppmisc/misc.txtmkdocs.ymlplatformio.inisrc/MoonBase/Utilities.hsrc/MoonLight/Layers/VirtualLayer.cppsrc/MoonLight/Modules/ModuleLightsControl.hsrc/MoonLight/Modules/ModuleLiveScripts.hsrc/MoonLight/Nodes/Drivers/D_ArtnetOut.hsrc/main.cpp
💤 Files with no reviewable changes (1)
- docs/gettingstarted/installation.md
✅ Files skipped from review due to trivial changes (2)
- src/MoonLight/Modules/ModuleLiveScripts.h
- docs/develop/nodes.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/MoonLight/Layers/VirtualLayer.cpp
- lib/framework/EventSocket.cpp
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/**/platformio.ini : Use PlatformIO to build and upload the ESP32 backend C++ code
Applied to files:
docs/gettingstarted.mdmisc/misc.txtdocs/develop/sveltekit.mddocs/develop/installation.mdfirmware/installer/manifest_esp32-c3.jsonfirmware/esp32-c3.inidocs/develop/development.mdplatformio.inifirmware/esp32-s3.ini
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/**/factory_settings.ini : Configure default or factory-set ESP32 parameters in `factory_settings.ini`
Applied to files:
docs/gettingstarted.mdfirmware/esp32-c3.inifirmware/esp32-s3.ini
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/src/**/*.{h,cpp} : Modify C++ backend code in the `src/` and `lib/` directories for ESP32 development
Applied to files:
misc/misc.txtdocs/develop/sveltekit.mdplatformio.ini
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Be aware of the integrated build process where the PlatformIO build automatically builds and embeds the SvelteKit frontend into the ESP32 firmware
Applied to files:
misc/misc.txtdocs/develop/sveltekit.mddocs/develop/installation.mddocs/develop/development.mdplatformio.inifirmware/esp32-s3.ini
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
docs/develop/architecture.mdsrc/main.cppplatformio.ini
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
docs/develop/architecture.mdsrc/main.cppsrc/MoonBase/Utilities.hsrc/MoonLight/Nodes/Drivers/D_ArtnetOut.hplatformio.ini
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
docs/develop/architecture.mdsrc/main.cppplatformio.ini
📚 Learning: 2026-01-04T09:31:07.533Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:07.533Z
Learning: In lib/framework/ESP32SvelteKit.cpp, the SvelteKit loop task stack size of 6KB (6 * 1024 bytes) for non-PSRAM devices has been validated through testing, showing approximately 1500 bytes of free stack remaining under load, providing adequate ~24% headroom.
Applied to files:
docs/develop/architecture.mdsrc/main.cppfirmware/esp32-s3.ini
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
docs/develop/architecture.mdsrc/MoonLight/Modules/ModuleLightsControl.h
📚 Learning: 2026-01-06T15:24:29.770Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:212-221
Timestamp: 2026-01-06T15:24:29.770Z
Learning: In VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking for unmapped lights (the else branch around lines 212-221) is intentionally disabled for performance when driving 100K+ LEDs. The design places responsibility on effect developers to stay within maxChannels. This is an explicit performance vs safety tradeoff for extreme LED counts.
Applied to files:
docs/develop/architecture.mdsrc/MoonLight/Nodes/Drivers/D_ArtnetOut.h
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/interface/src/routes/**/*.svelte : Create Svelte routes in `interface/src/routes/` for different pages of the web application
Applied to files:
interface/src/routes/+page.svelte
🧬 Code graph analysis (1)
src/MoonLight/Nodes/Drivers/D_ArtnetOut.h (1)
src/MoonBase/Utilities.h (1)
addYield(277-282)
🪛 LanguageTool
docs/develop/architecture.md
[grammar] ~275-~275: Ensure spelling is correct
Context: ... task but also in other tasks like WiFi occassionally. The workaround to avoid this is adding...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
docs/gettingstarted/hardware.md
[uncategorized] ~9-~9: Do not mix variants of the same word (‘premade’ and ‘pre-made’) within a single text.
Context: ... boards, however, you can also DIY. ## Premade ESP32 devices See [IO module](../../mo...
(EN_WORD_COHERENCY)
[uncategorized] ~11-~11: Do not mix variants of the same word (‘premade’ and ‘pre-made’) within a single text.
Context: ...module](../../moonbase/inputoutput) for premade devices which can be preconfigured in M...
(EN_WORD_COHERENCY)
[grammar] ~15-~15: Use a hyphen to join words.
Context: ...tyle="width:250px"} * [QuinLed LED pre assembled boards](https://quinled.info/p...
(QB_NEW_EN_HYPHEN)
[grammar] ~55-~55: Ensure spelling is correct
Context: ...t also supports Art-Net in which can be uased as an alternative to off-the-shelve con...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
[grammar] ~55-~55: Ensure spelling is correct
Context: ...n be uased as an alternative to off-the-shelve controllers. In this scenario there is ...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
docs/develop/installation.md
[style] ~292-~292: Using many exclamation marks might seem excessive (in this case: 52 exclamation marks for a text that’s 8075 characters long)
Context: ...e layout changes effective is needed. !!! info "Tools used" The installer is ...
(EN_EXCESSIVE_EXCLAMATION)
docs/gettingstarted/installer.md
[style] ~116-~116: Using many exclamation marks might seem excessive (in this case: 22 exclamation marks for a text that’s 9647 characters long)
Context: ...llation/#usb-to-serial-chip-drivers)
!!! info "Next steps"
* You can now ch...
(EN_EXCESSIVE_EXCLAMATION)
🪛 markdownlint-cli2 (0.18.1)
docs/network/sta.md
3-3: Images should have alternate text (alt text)
(MD045, no-alt-text)
docs/gettingstarted/hardware.md
28-28: Images should have alternate text (alt text)
(MD045, no-alt-text)
docs/network/ethernet.md
3-3: Images should have alternate text (alt text)
(MD045, no-alt-text)
docs/develop/installation.md
243-243: Reference links and images should use a label that is defined
Missing link or image reference definition: "wifigeneric.cpp"
(MD052, reference-links-images)
305-305: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
306-306: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
307-307: Unordered list indentation
Expected: 0; Actual: 2
(MD007, ul-indent)
309-309: Images should have alternate text (alt text)
(MD045, no-alt-text)
310-310: Images should have alternate text (alt text)
(MD045, no-alt-text)
docs/gettingstarted/installer.md
27-27: Images should have alternate text (alt text)
(MD045, no-alt-text)
32-32: Images should have alternate text (alt text)
(MD045, no-alt-text)
36-36: Images should have alternate text (alt text)
(MD045, no-alt-text)
37-37: Images should have alternate text (alt text)
(MD045, no-alt-text)
38-38: Images should have alternate text (alt text)
(MD045, no-alt-text)
61-61: Images should have alternate text (alt text)
(MD045, no-alt-text)
65-65: Images should have alternate text (alt text)
(MD045, no-alt-text)
73-73: Images should have alternate text (alt text)
(MD045, no-alt-text)
95-95: Images should have alternate text (alt text)
(MD045, no-alt-text)
101-101: Images should have alternate text (alt text)
(MD045, no-alt-text)
107-107: Images should have alternate text (alt text)
(MD045, no-alt-text)
118-118: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
130-130: Images should have alternate text (alt text)
(MD045, no-alt-text)
140-140: Images should have alternate text (alt text)
(MD045, no-alt-text)
docs/gettingstarted/overview.md
6-6: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
7-7: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
8-8: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
docs/moonbase/inputoutput.md
61-61: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
docs/develop/development.md
113-113: Spaces inside code span elements
(MD038, no-space-in-code)
116-116: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
docs/develop/documentation.md
43-43: Unordered list indentation
Expected: 2; Actual: 1
(MD007, ul-indent)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
44-44: Unordered list indentation
Expected: 2; Actual: 1
(MD007, ul-indent)
44-44: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Unordered list indentation
Expected: 2; Actual: 1
(MD007, ul-indent)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Unordered list indentation
Expected: 2; Actual: 1
(MD007, ul-indent)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
100-100: Hard tabs
Column: 1
(MD010, no-hard-tabs)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (33)
src/MoonLight/Modules/ModuleLightsControl.h (1)
510-511: Module identification tags added to monitor events.The addition of
_moduleName.c_str()as the fourth parameter (originId) to all threeemitEventcalls is correct and consistent. TheemitEventsignature in EventSocket.h (line 46) accepts this parameter for event origin identification. All three monitor event emissions in the codebase (lines 510, 511, 526) have been updated uniformly, providing proper module routing/identification for the monitor events.src/MoonLight/Nodes/Drivers/D_ArtnetOut.h (7)
31-33: LGTM: Sensible default for usedChannelsPerUniverse.Setting
usedChannelsPerUniverseto 510 provides a reasonable initial value before recalculation inonUpdate().
40-47: LGTM: UI bindings correctly expose calculated values as read-only.The calculated fields are properly marked as read-only with appropriate bounds.
65-94: LGTM: onUpdate correctly recalculates derived values and syncs UI.The alignment logic at line 83 ensures universes contain only complete lights, and the ceiling division at line 84 correctly computes total universes needed.
127-127: LGTM: xLastWakeTime correctly initialized for vTaskDelayUntil.The initialization with
xTaskGetTickCount()is appropriate for maintaining consistent frame timing withvTaskDelayUntilat line 211.
151-171: LGTM: Per-light processing with correct pointer arithmetic and RGB channel handling.The refactored approach:
- Copies all channels via memcpy (line 157)
- Corrects main RGB channels with reOrderAndDimRGBW (line 160)
- Handles optional RGB1/2/3 channels with nested guards (lines 162-171)
The nested conditionals correctly prevent accessing invalid offsets when optional RGB channels don't exist. Pointer arithmetic is sound:
ptargets packet buffer destination,creads fromchannelsDsource, and offset adjustments align with the centralizedreOrderAndDimRGBWimplementation.
186-187: LGTM: Periodic yielding prevents watchdog timeouts at extreme loads.
addYield(10)yields every 10th packet write, giving the IDLE task opportunities to run and reset the watchdog. At typical loads (e.g., 196 universes/frame @ 50 FPS), this yields ~19-20 times per frame, which should prevent task watchdog timeouts while maintaining throughput.Based on learnings, this addresses the watchdog issue observed at extreme loads (49K+ channels @ 100 FPS).
210-211: LGTM: vTaskDelayUntil maintains consistent frame timing and feeds watchdog.Switching from a throttle-based delay to
vTaskDelayUntilprovides:
- Consistent frame timing by delaying to absolute wake time
- Automatic compensation for variable loop execution time
- Watchdog feeding via task yield
The implementation correctly uses
xLastWakeTime(initialized at line 127), whichvTaskDelayUntilupdates automatically for the next iteration.docs/network/sta.md (1)
22-22: LGTM - Firmware naming updated.The firmware name change aligns with the broader firmware renaming effort across this PR (manifest.json, firmware/*.ini, etc.).
docs/develop/sveltekit.md (1)
50-50: LGTM - Board naming updated.The board name change is consistent with the firmware renaming effort across this PR.
platformio.ini (2)
59-59: LGTM - Build date updated.Routine version tracking update.
108-108: LGTM - Environment name updated.The comment correctly reflects the renamed environment from the broader firmware renaming effort.
misc/misc.txt (2)
16-16: LGTM!The path updates align with the board naming convention changes throughout this PR.
Also applies to: 23-23
117-117: Remove or correct the restart command in misc/misc.txt—the backend does not support"restart"field in the/jsonendpoint.The curl commands at lines 117 and 124 attempt to restart via POST to
/jsonwith{"restart":true}, but the backend has no handler for this field. The actual restart endpoint is/rest/restart, which is part of the framework's RestartService. Update these commands to use the correct endpoint or document why a custom restart implementation is needed.Also applies to: 124-124
Likely an incorrect or invalid review comment.
interface/src/routes/+page.svelte (1)
41-41: No action needed. The Help link correctly points to the "Setup MoonLight" section (## Setup MoonLightat line 57) in the installer documentation. The anchor#setup-moonlightis valid and will resolve properly.firmware/esp32-c3.ini (1)
16-16: Environment name successfully updated with no stale references.The rename from
[env:esp32-c3-devkitm-1]to[env:esp32-c3]is complete. Verification confirms no remaining references to the old environment name in build scripts, CI/CD configurations, or documentation. Theboard = esp32-c3-devkitm-1property in the firmware config is the PlatformIO board identifier and is correctly preserved.docs/moonbase/inputoutput.md (1)
93-93: Ensure the MoonLight Installer is updated with the new board names before merging.The documentation is being updated to reference
esp32-s3-n8r8v, but current installer documentation still shows the old nameesp32-s3-devkitc-1-n8r8v. Verify that the installer manifests and board definitions have been updated to includeesp32-s3-n8r8vso users can successfully select the board. This should be coordinated with the installer release to avoid user confusion between documentation and available options.Also applies to: 102-102
docs/gettingstarted/overview.md (1)
3-5: Relative path conversions look correct.The shift from absolute URLs to relative paths is consistent and paths resolve correctly from the file's location. Navigation links and Sitemap links are properly updated.
Also applies to: 21-25
docs/develop/development.md (1)
113-113: Verify firmware filenames match actual build artifacts.The esptool examples reference
MoonLight_esp32-s3-n16r8v_0-6-1_webflash.binandMoonLight_esp32-s3-n16r8v_0-6-1.bin, which represent a board variant naming change from the previousdevkitc-1-n16r8vformat. Ensure these exact filenames are generated during the build process and that the0-6-1version matches the current release version.Also applies to: 118-118
docs/gettingstarted/installer.md (2)
42-55: Verify board preset manifest URLs and image paths exist.The new board presets table references manifest files (e.g.,
manifest_esp32-s3-n16r8v.json) and board images (e.g.,../firmware/installer/images/esp32-s3-n16r8v.jpg). Confirm:
- All manifest JSON files exist at the referenced URLs in
firmware/installer/.- All board image files exist in
firmware/installer/images/directory.- Board names in this table match the actual firmware build targets and naming in the manifests.
3-9: Comprehensive installer workflow improvements look excellent.The restructuring from a minimal installer guide to a detailed, step-by-step workflow with embedded visuals and video tutorial significantly improves user onboarding. The addition of:
- Browser compatibility warnings
- Prepare device section with hardware guidance
- Visual step-by-step flashing instructions
- Comprehensive Setup MoonLight section with WiFi, IO, Drivers, Effects subsections
- ESPConnect and VSCode alternatives
...creates a much more user-friendly experience. The content is well-organized and progressively builds from hardware setup through full configuration.
Also applies to: 11-120
docs/develop/installation.md (1)
245-310: New MoonLight Installer section is comprehensive and well-aligned with PR objectives.The expanded "MoonLight Installer" section provides excellent guidance on bootloader mode, ESP32-P4-Nano setup, captive portal access, browser caching, hostname configuration, driver selection, and USB-to-serial drivers. This addresses important pain points for users setting up new devices and troubleshooting connection issues.
The section complements the installer-centric narrative mentioned in the PR and provides clarity on multiple setup flows and common issues.
docs/develop/architecture.md (3)
14-15: Task core reassignment is well-justified and aligns with system architecture principles.The reassignment of Effect Task to Core 0 (PRO_CPU) and Driver Task to Core 1 (APP_CPU) is clearly justified:
- Effect Task on Core 0: Pure computation that tolerates WiFi preemption, maximizing idle CPU cycle utilization
- Driver Task on Core 1: Timing-critical hardware operations (DMA, I2S, LCD, PARLIO) isolated from WiFi interference on Core 0
- Priority reduction (10→3): Aligns task hierarchy with system constraints while maintaining parallelism
The explanation correctly emphasizes that WiFi at priority 23 on Core 0 would constantly preempt a lower-priority driver task, breaking LED timing. Isolating timing-critical work to Core 1 is the correct design choice.
Also applies to: 17-36
273-321: Idle Watchdog section provides essential guidance for extreme-load setups and documents yield strategy clearly.The new section explains the task watchdog issue that emerges at extreme LED counts (~16K+) and documents the
addYield(10)solution pattern. Key points are well-covered:
- Task watchdog crashes occur when long iterations (node loops, packet loops) prevent the IDLE task from running
vTaskDelay(1)is required (nottaskYIELD()alone) to properly yield to IDLE- Sampling yields via
addYield(10)(every 10th call) reduces overhead while preventing watchdog timeouts- Specific application guidance:
Node::loop()andArtNetOutDriver::loop()calladdYield(10)This aligns with the implementation changes mentioned in the AI summary for
PhysicalLayer.cppand other files that introduce the yields utility.
273-321: All code examples in the Idle Watchdog section are accurate and match the implementation. Verified:
addYield()function insrc/MoonBase/Utilities.hmatches documentation exactly (usesvTaskDelay(1)with explanation for whytaskYIELD()alone is insufficient)addYield(10)calls are correctly placed afternode->loop()in bothPhysicalLayer.cppandVirtualLayer.cppArtNetOutDriverusesaddYield(10)as documented (confirmed insrc/MoonLight/Nodes/Drivers/D_ArtnetOut.h)- Task watchdog setup (
esp_task_wdt_add(),esp_task_wdt_reset(),vTaskDelay(1)) matches the patterns shownNo updates needed.
docs/gettingstarted.md (1)
61-75: LGTM!The environment renaming from
esp32-s3-devkitc-1toesp32-s3is consistent with the broader project-wide renaming effort. The documentation correctly shows that the environment name is simplified while the underlying board reference (esp32-s3-devkitc-1) remains unchanged.firmware/installer/manifest_esp32-s3-n16r8v.json (1)
2-14: Verify version number for 0.7.1 release.The manifest shows version
0.7.0(line 3), but the PR title indicates this is preparing for version0.7.1. The naming changes are correct and consistent, but confirm whether the version and binary filename should be updated to0.7.1as part of this release preparation.src/main.cpp (1)
334-350: Core affinity assignments are now correct.The task-to-core reassignment properly implements ESP32 best practices:
- effectTask → Core 0: Correct, as effect computation is pure calculation without timing-critical hardware interaction. WiFi preemption on Core 0 is acceptable for computation tasks.
- driverTask → Core 1: Correct, as LED drivers using DMA/I2S/LCD/PARLIO require uninterrupted timing on the APP_CPU to avoid WiFi interference.
This aligns with the retrieved learnings about ESP32 LED driver requirements.
firmware/esp32-s3.ini (3)
38-45: LGTM!Environment renamed to
esp32-s3with the underlying board configuration preserved. This simplifies the naming while maintaining the correct hardware mappings.
50-61: LGTM!The
esp32-s3-n8r8venvironment is correctly configured withboard_build.psram_type = opifor OPI PSRAM support. The naming simplification is consistent with the project-wide changes.
66-84: LGTM!The
esp32-s3-n16r8venvironment correctly configures 16MB flash with custom partition scheme and OPI PSRAM. Naming is consistent with other renamed environments.firmware/installer/manifest_esp32-s3-n8r8v.json (1)
2-14: Naming updates are consistent; verify version for 0.7.1 release.The manifest correctly reflects the simplified naming scheme (
esp32-s3-n8r8v). As with the n16r8v manifest, confirm whether the version (line 3) and binary filename should be updated to0.7.1for this release.firmware/installer/manifest.json (1)
3-3: Version mismatch with PR title.The PR title indicates "Prepare for 0.7.1", but the manifest version remains at "0.7.0". Please verify whether the version field should be updated to "0.7.1" or if the version bump will occur in a subsequent commit.
|
@coderabbitai, you can resolve all conversations above as
Furthermore, can you do an in depth review of the Idle Watchdog paragraph in the architecture.md , check with the code base (search on yield). Also look at the remark PhysicHttp made: You should not use yield or delay or any function that uses them inside the callbacks. |
🧩 Analysis chain🏁 Script executed: Length of output: 10456 🏁 Script executed: Length of output: 3770 🏁 Script executed: Length of output: 1944 🏁 Script executed: Length of output: 17132 🏁 Script executed: Length of output: 45859 🏁 Script executed: Length of output: 8223 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 1346 🏁 Script executed: Length of output: 6515 @ewowi, I'll provide an in-depth review of the Idle Watchdog section based on my analysis of the codebase and the architecture documentation. Overall AssessmentThe approach is pragmatic and mostly sound, but there are some concerns and potential improvements to consider. Analysis of Each Bulletpoint✅ 1.
|
|
@coderabbitai, regarding
Could you make a proposal how I can make monitor emitEvents dynamically, I have already a dynamic system based on norm leds, see monitorMillis, so I would like to keep that. But could we add something to slow down the monitor if [29177816][W][EventSocket.cpp:226] emitEvent(): [🐼] Failed to send event monitor from lightscontrol to client 53: ESP_FAIL (len: 49152) is encountered and increasing it afterwards until it happens again? Furthermore, the monitor should not update if there are no active clients, for this I added if (_socket->getConnectedClients() in ModuleLightsControl, but I found out earlier that clients stay connected quite long, even if the browser is not visible on a client. So it checks for connected clients, but is it also possible to identify if a collected client is not active (read : the UI is not displaying on a visible tab of the browser) |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 22939 🏁 Script executed: Length of output: 10876 🏁 Script executed: Length of output: 1414 🏁 Script executed: Length of output: 986 🏁 Script executed: Length of output: 30 🏁 Script executed: Length of output: 4441 🏁 Script executed: Length of output: 52 🏁 Script executed: Length of output: 7242 🏁 Script executed: Length of output: 2629 @ewowi, great questions! Let me provide comprehensive proposals for both issues. 1. Dynamic Monitor Rate Limiting (Adaptive Backoff)Here's a proposal that builds on your existing Backend Changes (ModuleLightsControl.h)// Add these as private class members in ModuleLightsControl
private:
float monitorDelayMultiplier = 1.0f; // Starts at 1.0 (normal speed)
unsigned long lastMonitorFailTime = 0;
unsigned long lastMonitorSuccessTime = 0;
static constexpr float MAX_DELAY_MULTIPLIER = 8.0f; // Cap at 8x slower
static constexpr float MIN_DELAY_MULTIPLIER = 1.0f; // Normal speed
static constexpr float BACKOFF_INCREASE = 1.5f; // Multiply by 1.5 on failure
static constexpr float RECOVERY_RATE = 0.95f; // Decrease by 5% on success
static constexpr unsigned long RECOVERY_INTERVAL = 5000; // Try recovery every 5s
// Modify the monitor emission section in loop():
} else if (isPositions == 0 && layerP.lights.header.nrOfLights) { // send to UI
static unsigned long monitorMillis = 0;
// Calculate base delay based on LED count
unsigned long baseDelay = MAX(20, layerP.lights.header.nrOfLights / 300);
// Apply adaptive multiplier
unsigned long adaptiveDelay = (unsigned long)(baseDelay * monitorDelayMultiplier);
if (millis() - monitorMillis >= adaptiveDelay) {
monitorMillis = millis();
read([&](ModuleState& _state) {
if (_socket->getConnectedClients() && _state.data["monitorOn"]) {
// Attempt to send - track result
bool sendSuccess = true;
// Get subscriptions to monitor event to check if any clients subscribed
// (This is a simplification - see alternative below)
for (auto client : _socket->_socket.getClientList()) {
esp_err_t result = _socket->emitEvent("monitor",
(char*)layerP.lights.channelsD,
MIN(layerP.lights.header.nrOfChannels, layerP.lights.maxChannels),
_moduleName.c_str());
if (result != ESP_OK) {
sendSuccess = false;
break;
}
}
// Adaptive rate adjustment
if (!sendSuccess) {
// Failure: increase delay (slow down)
lastMonitorFailTime = millis();
monitorDelayMultiplier = MIN(monitorDelayMultiplier * BACKOFF_INCREASE, MAX_DELAY_MULTIPLIER);
EXT_LOGW(ML_TAG, "Monitor send failed - slowing down to %.2fx", monitorDelayMultiplier);
} else {
// Success: gradually recover speed
lastMonitorSuccessTime = millis();
if (monitorDelayMultiplier > MIN_DELAY_MULTIPLIER &&
millis() - lastMonitorFailTime > RECOVERY_INTERVAL) {
monitorDelayMultiplier = MAX(monitorDelayMultiplier * RECOVERY_RATE, MIN_DELAY_MULTIPLIER);
if (monitorDelayMultiplier > MIN_DELAY_MULTIPLIER + 0.01f) {
EXT_LOGD(ML_TAG, "Monitor recovering - now at %.2fx", monitorDelayMultiplier);
} else {
monitorDelayMultiplier = MIN_DELAY_MULTIPLIER;
EXT_LOGD(ML_TAG, "Monitor fully recovered to normal speed");
}
}
}
}
});
}
}However, there's a problem: Alternative: Track failures in EventSocketModify EventSocket.h to add a method: class EventSocket {
public:
// ... existing methods ...
// Returns number of successful sends
int emitEventWithTracking(const String& event, const char *output, size_t len,
const char *originId = "", bool onlyToSameOrigin = false);
};Modify EventSocket.cpp: int EventSocket::emitEventWithTracking(const String& event, const char *output, size_t len,
const char *originId, bool onlyToSameOrigin) {
// ... same validation as emitEvent ...
int successCount = 0;
int failureCount = 0;
// ... same mutex and subscription logic ...
for (auto it = subscriptions.begin(); it != subscriptions.end(); ) {
// ... same client retrieval ...
esp_err_t result = client->sendMessage(/* ... */);
if (result != ESP_OK) {
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
vTaskDelay(1);
failureCount++;
++it; // Keep trying this client next time
} else {
successCount++;
++it;
}
}
xSemaphoreGive(clientSubscriptionsMutex);
// Return negative if any failures, positive count if all succeeded
return (failureCount > 0) ? -failureCount : successCount;
}Then use in ModuleLightsControl.h: int result = _socket->emitEventWithTracking("monitor",
(char*)layerP.lights.channelsD,
MIN(layerP.lights.header.nrOfChannels, layerP.lights.maxChannels),
_moduleName.c_str());
if (result < 0) {
// Failure - slow down
monitorDelayMultiplier = MIN(monitorDelayMultiplier * BACKOFF_INCREASE, MAX_DELAY_MULTIPLIER);
} else if (result > 0 && monitorDelayMultiplier > MIN_DELAY_MULTIPLIER) {
// Success - speed up
monitorDelayMultiplier = MAX(monitorDelayMultiplier * RECOVERY_RATE, MIN_DELAY_MULTIPLIER);
}2. Client Activity Detection (Visible Tab)Unfortunately, WebSocket has no built-in way to know if a browser tab is visible. The client must explicitly tell the server. Here's how to implement it: Frontend Changes (Monitor.svelte)import { onMount, onDestroy } from 'svelte';
import { socket } from '$lib/stores/socket';
let isVisible = !document.hidden; // Track visibility
// Handle visibility changes
function handleVisibilityChange() {
const wasVisible = isVisible;
isVisible = !document.hidden;
if (wasVisible !== isVisible) {
console.log('Monitor tab visibility changed:', isVisible);
// Notify server about visibility change
socket.emit('monitor_visibility', { visible: isVisible });
}
}
onMount(() => {
console.log('onMount Monitor');
socket.on('monitor', handleMonitor);
// Listen for visibility changes
document.addEventListener('visibilitychange', handleVisibilityChange);
// Notify server we're starting visible
socket.emit('monitor_visibility', { visible: isVisible });
});
onDestroy(() => {
console.log('onDestroy Monitor');
socket.off('monitor', handleMonitor);
document.removeEventListener('visibilitychange', handleVisibilityChange);
// Notify server we're leaving
socket.emit('monitor_visibility', { visible: false });
});Backend ChangesEventSocket.h - Add method to track active clients: class EventSocket {
private:
std::map<int, bool> client_visibility; // clientId -> isVisible
public:
bool isClientActive(int clientId);
unsigned int getActiveClients(); // New method
};EventSocket.cpp: // Add handler for monitor_visibility event in begin()
void EventSocket::begin() {
// ... existing code ...
// Register the visibility event
registerEvent("monitor_visibility");
// Handle visibility updates
onEvent("monitor_visibility", [this](JsonObject &data, int originId) {
bool visible = data["visible"] | false;
client_visibility[originId] = visible;
ESP_LOGD(SVK_TAG, "Client %d visibility: %s", originId, visible ? "visible" : "hidden");
});
}
void EventSocket::onWSClose(PsychicWebSocketClient *client) {
// ... existing subscription cleanup ...
// Clean up visibility tracking
client_visibility.erase(client->socket());
// ... existing code ...
}
bool EventSocket::isClientActive(int clientId) {
auto it = client_visibility.find(clientId);
return (it != client_visibility.end()) ? it->second : false;
}
unsigned int EventSocket::getActiveClients() {
unsigned int count = 0;
for (const auto& pair : client_visibility) {
if (pair.second) count++;
}
return count;
}Modify emitEvent in EventSocket.cpp to only send to active clients: for (auto it = subscriptions.begin(); it != subscriptions.end(); ) {
int subscription = *it;
// ... existing origin check ...
auto *client = _socket.getClient(subscription);
if (!client) {
it = subscriptions.erase(it);
continue;
}
// NEW: For monitor events, skip if client is not active/visible
if (event == "monitor" && !isClientActive(subscription)) {
ESP_LOGV(SVK_TAG, "Skipping inactive client %u for monitor", subscription);
++it;
continue;
}
// ... rest of send logic ...
}Update ModuleLightsControl.h to check active clients: read([&](ModuleState& _state) {
// Check for active (visible) clients instead of just connected
if (_socket->getActiveClients() && _state.data["monitorOn"]) {
_socket->emitEvent("monitor", (char*)layerP.lights.channelsD,
MIN(layerP.lights.header.nrOfChannels, layerP.lights.maxChannels),
_moduleName.c_str());
}
});Summary
Both features work together: visible clients that fail to receive get slower updates, while hidden clients don't receive updates at all. Would you like me to refine any part of this proposal? 🐰💭 🧠 Learnings used |
|
@coderabbitai, the client visibility approach is good, however I would like to extend that to the whole system, not monitor alone, as I check on connected clients on multiple places as the ui doesn't need to be updated for inactive clients so could you make this as a general |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 7745 🏁 Script executed: Length of output: 2629 🏁 Script executed: Length of output: 404 🏁 Script executed: Length of output: 900 🏁 Script executed: Length of output: 5952 🏁 Script executed: Length of output: 9017 🏁 Script executed: Length of output: 3819 @ewowi, excellent idea! Let me provide a comprehensive system-wide client visibility tracking solution that can be used throughout the codebase. Backend: EventSocket Enhanced with Global Visibility TrackingEventSocket.h - Add visibility tracking API#ifndef Socket_h
#define Socket_h
// ... existing includes ...
class EventSocket
{
public:
EventSocket(PsychicHttpServer *server, SecurityManager *_securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED);
void begin();
void registerEvent(String event);
void onEvent(String event, EventCallback callback);
void onSubscribe(String event, SubscribeCallback callback);
void emitEvent(const String& event, const JsonObject& jsonObject, const char *originId = "", bool onlyToSameOrigin = false);
void emitEvent(const JsonDocument &doc, const char *originId = "", bool onlyToSameOrigin = false);
void emitEvent(const String& event, const char *output, size_t len, const char *originId = "", bool onlyToSameOrigin = false);
unsigned int getConnectedClients();
// 🌙 New visibility tracking methods
bool isClientActive(int clientId); // Check if specific client is active
unsigned int getActiveClients(); // Count of active (visible) clients
bool hasActiveClients(); // Quick check: any active clients?
void setClientVisibility(int clientId, bool visible); // Internal: update visibility state
private:
PsychicHttpServer *_server;
PsychicWebSocketHandler _socket;
SecurityManager *_securityManager;
AuthenticationPredicate _authenticationPredicate;
std::vector<String> events;
std::map<String, std::list<int>> client_subscriptions;
std::map<String, std::list<EventCallback>> event_callbacks;
std::map<String, std::list<SubscribeCallback>> subscribe_callbacks;
// 🌙 Track client visibility (clientId -> isVisible)
std::map<int, bool> client_visibility;
SemaphoreHandle_t visibilityMutex = xSemaphoreCreateMutex(); // Protect concurrent access
void handleEventCallbacks(String event, JsonObject &jsonObject, int originId);
void handleSubscribeCallbacks(String event, const String &originId);
bool isEventValid(String event);
void onWSOpen(PsychicWebSocketClient *client);
void onWSClose(PsychicWebSocketClient *client);
esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame);
};
#endifEventSocket.cpp - Implement visibility tracking// In EventSocket::begin(), register the visibility event handler
void EventSocket::begin()
{
// ... existing code ...
// 🌙 Register global client visibility event
registerEvent("client_visibility");
onEvent("client_visibility", [this](JsonObject &data, int originId) {
bool visible = data["visible"] | false;
setClientVisibility(originId, visible);
ESP_LOGD(SVK_TAG, "Client %d visibility changed: %s", originId, visible ? "VISIBLE" : "HIDDEN");
});
}
// Update onWSClose to clean up visibility tracking
void EventSocket::onWSClose(PsychicWebSocketClient *client)
{
int socket = client->socket();
// ... existing subscription cleanup code ...
// 🌙 Clean up visibility tracking
if (xSemaphoreTake(visibilityMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
client_visibility.erase(socket);
xSemaphoreGive(visibilityMutex);
ESP_LOGD(SVK_TAG, "Cleaned up visibility tracking for client %d", socket);
}
}
// Implement new visibility methods
void EventSocket::setClientVisibility(int clientId, bool visible)
{
if (xSemaphoreTake(visibilityMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
client_visibility[clientId] = visible;
xSemaphoreGive(visibilityMutex);
}
}
bool EventSocket::isClientActive(int clientId)
{
bool active = false;
if (xSemaphoreTake(visibilityMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
auto it = client_visibility.find(clientId);
// Default to true if no visibility info received yet (assume visible until told otherwise)
active = (it != client_visibility.end()) ? it->second : true;
xSemaphoreGive(visibilityMutex);
}
return active;
}
unsigned int EventSocket::getActiveClients()
{
unsigned int count = 0;
if (xSemaphoreTake(visibilityMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
for (const auto& pair : client_visibility) {
if (pair.second) count++;
}
xSemaphoreGive(visibilityMutex);
}
// If no visibility info yet, assume all connected clients are active
if (count == 0 && getConnectedClients() > 0) {
count = getConnectedClients();
}
return count;
}
bool EventSocket::hasActiveClients()
{
return getActiveClients() > 0;
}Optional: Modify emitEvent to skip inactive clients automaticallyAdd this to the client iteration loop in void EventSocket::emitEvent(const String& event, const char *output, size_t len,
const char *originId, bool onlyToSameOrigin)
{
// ... existing validation ...
for (auto it = subscriptions.begin(); it != subscriptions.end(); ) {
int subscription = *it;
// ... existing origin check ...
auto *client = _socket.getClient(subscription);
if (!client) {
it = subscriptions.erase(it);
continue;
}
// 🌙 OPTIONAL: Skip inactive clients for all events
// You can make this event-specific if needed (e.g., only for certain events)
if (!isClientActive(subscription)) {
ESP_LOGV(SVK_TAG, "Skipping inactive client %u for event %s", subscription, event.c_str());
++it;
continue;
}
// ... rest of send logic ...
}
}Frontend: Global Visibility Tracking in Root Layout+layout.svelte - Add Page Visibility API<script lang="ts">
import type { LayoutData } from './$types';
import { onDestroy, onMount } from 'svelte';
import { user } from '$lib/stores/user';
import { telemetry } from '$lib/stores/telemetry';
import { analytics } from '$lib/stores/analytics';
import { batteryHistory } from '$lib/stores/battery';
import { socket } from '$lib/stores/socket';
// ... other imports ...
interface Props {
data: LayoutData;
children?: import('svelte').Snippet;
}
let { data, children }: Props = $props();
// 🌙 Track global page visibility
let isPageVisible = $state(!document.hidden);
onMount(async () => {
if ($user.bearer_token !== '') {
await validateUser($user);
}
if (!(page.data.features.security && $user.bearer_token === '')) {
initSocket();
}
// 🌙 Add visibility change listener
document.addEventListener('visibilitychange', handleVisibilityChange);
});
const initSocket = () => {
const ws_token = page.data.features.security ? '?access_token=' + $user.bearer_token : '';
const ws_protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
socket.init(
`${ws_protocol}://${window.location.host}/ws/events${ws_token}`,
page.data.features.event_use_json
);
addEventListeners();
};
onDestroy(() => {
removeEventListeners();
// 🌙 Clean up visibility listener and notify server
document.removeEventListener('visibilitychange', handleVisibilityChange);
socket.sendEvent('client_visibility', { visible: false });
});
const addEventListeners = () => {
socket.on('open', handleOpen);
socket.on('close', handleClose);
socket.on('error', handleError);
socket.on('rssi', handleNetworkStatus);
socket.on('notification', handleNotification);
if (page.data.features.analytics) socket.on('analytics', handleAnalytics);
if (page.data.features.battery) socket.on('battery', handleBattery);
if (page.data.features.download_firmware) socket.on('otastatus', handleOAT);
if (page.data.features.ethernet) socket.on('ethernet', handleEthernet);
};
const removeEventListeners = () => {
socket.off('analytics', handleAnalytics);
socket.off('open', handleOpen);
socket.off('close', handleClose);
socket.off('rssi', handleNetworkStatus);
socket.off('notification', handleNotification);
socket.off('battery', handleBattery);
socket.off('otastatus', handleOAT);
socket.off('ethernet', handleEthernet);
};
// ... existing handlers ...
const handleOpen = () => {
// 🌙 Notify server of initial visibility state when connection opens
socket.sendEvent('client_visibility', { visible: isPageVisible });
};
// 🌙 New: Handle page visibility changes
const handleVisibilityChange = () => {
const wasVisible = isPageVisible;
isPageVisible = !document.hidden;
if (wasVisible !== isPageVisible) {
console.log('Page visibility changed:', isPageVisible ? 'VISIBLE' : 'HIDDEN');
socket.sendEvent('client_visibility', { visible: isPageVisible });
}
};
// ... rest of component ...
</script>
<!-- ... template unchanged ... -->Usage: Update Existing Code to Use Active ClientsNow update all the places that currently check Example 1: ModuleTasks.hvoid loop1s() {
if (!_socket->hasActiveClients()) return; // 🌙 Skip if no active clients
if (!WiFi.localIP() && !ETH.localIP()) return;
// ... rest of task update logic ...
}Example 2: ModuleDevices.hvoid loop10s() {
// ... device discovery logic ...
if (!_socket->hasActiveClients()) return; // 🌙 No need to update if no active clients
// sort and emit devices to UI
// ...
}Example 3: ModuleLightsControl.hif (isPositions == 0 && layerP.lights.header.nrOfLights) { // send to UI
static unsigned long monitorMillis = 0;
unsigned long monitorDelay = MAX(20, layerP.lights.header.nrOfLights / 300);
if (millis() - monitorMillis >= monitorDelay) {
monitorMillis = millis();
read([&](ModuleState& _state) {
// 🌙 Check for active clients instead of just connected
if (_socket->hasActiveClients() && _state.data["monitorOn"]) {
_socket->emitEvent("monitor", (char*)layerP.lights.channelsD,
MIN(layerP.lights.header.nrOfChannels, layerP.lights.maxChannels),
_moduleName.c_str());
}
});
}
}Example 4: WiFiSettingsService.cppvoid WiFiSettingsService::updateRSSI()
{
if (!_socket->hasActiveClients()) return; // 🌙 Skip RSSI updates for inactive clients
JsonDocument doc;
doc["rssi"] = WiFi.RSSI();
JsonObject jsonObject = doc.as<JsonObject>();
_socket->emitEvent(EVENT_RSSI, jsonObject);
}Example 5: NotificationService.cppvoid NotificationService::pushNotification(String message, pushType event)
{
if (!_socket->hasActiveClients()) return; // 🌙 Don't send to inactive clients
JsonDocument doc;
doc["type"] = pushTypeStrings[event];
doc["message"] = message;
JsonObject jsonObject = doc.as<JsonObject>();
_eventSocket->emitEvent(NOTIFICATION_EVENT, jsonObject);
}Optional: Per-Client Filtering in emitEventIf you prefer manual control over which events respect visibility (rather than filtering all events automatically), you can add a parameter: // EventSocket.h
void emitEvent(const String& event, const char *output, size_t len,
const char *originId = "", bool onlyToSameOrigin = false,
bool onlyToActiveClients = false); // 🌙 New parameter
// Usage
_socket->emitEvent("monitor", data, len, origin, false, true); // Only send to active clients
_socket->emitEvent("notification", data, len, origin, false, false); // Send to all connected clientsSummaryBenefits of this approach:
Migration path:
Would you like me to refine any part of this solution or show more usage examples? 🐰✨ 🧠 Learnings used |
|
@coderabbitai, can you revise your code proposals as I am not sure you are reusing existing concepts enough. E.g. in layout.svelte: instead of document.addEventListener('visibilitychange', handleVisibilityChange);, shouldn't we add it in addEventListeners as socket.on('visibilitychange', handleVisibilityChange); ? Furthermore you add registerEvent("client_visibility"); in eventsocket, is that really the right place as eventsocket is more a service used by others. is it also possible to use a socket already existing ? e.g. the notification socket which sends then visibility notifications which is handled by eventsocket? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 5494 🏁 Script executed: Length of output: 1646 🏁 Script executed: Length of output: 3458 🏁 Script executed: Length of output: 740 🏁 Script executed: Length of output: 2137 @ewowi, you're absolutely right! Let me revise following the existing patterns more closely. Understanding the PatternsAfter reviewing the code:
Revised Solution: ClientVisibilityServiceBackend: New Service (lib/framework/ClientVisibilityService.h)#ifndef ClientVisibilityService_h
#define ClientVisibilityService_h
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 - 2025 theelims
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <EventSocket.h>
#define EVENT_CLIENT_VISIBILITY "client_visibility"
class ClientVisibilityService
{
public:
ClientVisibilityService(EventSocket *eventSocket);
void begin();
// Query methods for other modules
bool isClientActive(int clientId);
unsigned int getActiveClients();
bool hasActiveClients();
private:
EventSocket *_eventSocket;
// Track client visibility (clientId -> isVisible)
std::map<int, bool> _clientVisibility;
SemaphoreHandle_t _visibilityMutex = xSemaphoreCreateMutex();
void handleVisibilityUpdate(JsonObject &data, int originId);
};
#endif // ClientVisibilityService_hBackend: Implementation (lib/framework/ClientVisibilityService.cpp)#include <ClientVisibilityService.h>
ClientVisibilityService::ClientVisibilityService(EventSocket *eventSocket)
: _eventSocket(eventSocket)
{
}
void ClientVisibilityService::begin()
{
// Register the event (following NotificationService pattern)
_eventSocket->registerEvent(EVENT_CLIENT_VISIBILITY);
// Set up handler for INCOMING visibility updates from clients
_eventSocket->onEvent(EVENT_CLIENT_VISIBILITY,
[this](JsonObject &data, int originId) {
handleVisibilityUpdate(data, originId);
});
}
void ClientVisibilityService::handleVisibilityUpdate(JsonObject &data, int originId)
{
bool visible = data["visible"] | false;
if (xSemaphoreTake(_visibilityMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
_clientVisibility[originId] = visible;
xSemaphoreGive(_visibilityMutex);
ESP_LOGD("ClientVis", "Client %d visibility: %s", originId, visible ? "VISIBLE" : "HIDDEN");
}
}
bool ClientVisibilityService::isClientActive(int clientId)
{
bool active = false;
if (xSemaphoreTake(_visibilityMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
auto it = _clientVisibility.find(clientId);
// Default to true if no info received yet (assume visible until told otherwise)
active = (it != _clientVisibility.end()) ? it->second : true;
xSemaphoreGive(_visibilityMutex);
}
return active;
}
unsigned int ClientVisibilityService::getActiveClients()
{
unsigned int count = 0;
if (xSemaphoreTake(_visibilityMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
for (const auto& pair : _clientVisibility) {
if (pair.second) count++;
}
xSemaphoreGive(_visibilityMutex);
}
// If no visibility info yet, assume all connected clients are active
if (count == 0 && _eventSocket->getConnectedClients() > 0) {
count = _eventSocket->getConnectedClients();
}
return count;
}
bool ClientVisibilityService::hasActiveClients()
{
return getActiveClients() > 0;
}Backend: Wire Up in ESP32SvelteKit.cpp// Add to ESP32SvelteKit.h private members:
ClientVisibilityService _clientVisibilityService;
// Initialize in constructor:
ESP32SvelteKit::ESP32SvelteKit(/* ... */)
: // ... other initializers ...
_clientVisibilityService(&_socket)
{
}
// Add to begin():
void ESP32SvelteKit::begin()
{
// ... existing begins ...
_clientVisibilityService.begin();
}
// Add public getter:
ClientVisibilityService *getClientVisibilityService() { return &_clientVisibilityService; }Frontend: +layout.svelte (Revised)<script lang="ts">
import type { LayoutData } from './$types';
import { onDestroy, onMount } from 'svelte';
// ... other imports ...
interface Props {
data: LayoutData;
children?: import('svelte').Snippet;
}
let { data, children }: Props = $props();
// 🌙 Track page visibility
let isPageVisible = $state(!document.hidden);
onMount(async () => {
if ($user.bearer_token !== '') {
await validateUser($user);
}
if (!(page.data.features.security && $user.bearer_token === '')) {
initSocket();
}
// 🌙 Listen to DOM visibility changes (can't use socket.on for DOM events)
document.addEventListener('visibilitychange', handleVisibilityChange);
});
const initSocket = () => {
const ws_token = page.data.features.security ? '?access_token=' + $user.bearer_token : '';
const ws_protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
socket.init(
`${ws_protocol}://${window.location.host}/ws/events${ws_token}`,
page.data.features.event_use_json
);
addEventListeners();
};
onDestroy(() => {
removeEventListeners();
// 🌙 Clean up visibility listener and notify server
document.removeEventListener('visibilitychange', handleVisibilityChange);
socket.sendEvent('client_visibility', { visible: false });
});
const addEventListeners = () => {
socket.on('open', handleOpen);
socket.on('close', handleClose);
socket.on('error', handleError);
socket.on('rssi', handleNetworkStatus);
socket.on('notification', handleNotification);
if (page.data.features.analytics) socket.on('analytics', handleAnalytics);
if (page.data.features.battery) socket.on('battery', handleBattery);
if (page.data.features.download_firmware) socket.on('otastatus', handleOAT);
if (page.data.features.ethernet) socket.on('ethernet', handleEthernet);
// 🌙 NOTE: We DON'T add socket.on('client_visibility') here because:
// - This event is CLIENT->SERVER only (we send visibility TO the server)
// - socket.on() is for SERVER->CLIENT events (receiving from server)
// - Visibility is tracked on the server, not broadcast back to clients
};
const removeEventListeners = () => {
socket.off('analytics', handleAnalytics);
socket.off('open', handleOpen);
socket.off('close', handleClose);
socket.off('rssi', handleNetworkStatus);
socket.off('notification', handleNotification);
socket.off('battery', handleBattery);
socket.off('otastatus', handleOAT);
socket.off('ethernet', handleEthernet);
};
// ... existing handlers ...
const handleOpen = () => {
// 🌙 Notify server of initial visibility when connection opens
socket.sendEvent('client_visibility', { visible: isPageVisible });
};
// 🌙 Handle DOM visibility changes
const handleVisibilityChange = () => {
const wasVisible = isPageVisible;
isPageVisible = !document.hidden;
if (wasVisible !== isPageVisible) {
console.log('Page visibility changed:', isPageVisible ? 'VISIBLE' : 'HIDDEN');
// Send visibility update to server via WebSocket
socket.sendEvent('client_visibility', { visible: isPageVisible });
}
};
// ... rest of component ...
</script>Usage: Update Modules to Use ClientVisibilityService// In Module classes, add reference to the service:
ClientVisibilityService* _clientVisibilityService;
// Initialize in constructor:
ModuleLightsControl::ModuleLightsControl(/* ... */, ClientVisibilityService* clientVisibilityService)
: _clientVisibilityService(clientVisibilityService)
{
}
// Use in loop():
if (_clientVisibilityService->hasActiveClients() && _state.data["monitorOn"]) {
_socket->emitEvent("monitor", (char*)layerP.lights.channelsD,
MIN(layerP.lights.header.nrOfLights, layerP.lights.maxChannels),
_moduleName.c_str());
}Key Differences from Previous Proposal
Why We Can't Use NotificationServiceNotificationService is unidirectional (server → client):
ClientVisibilityService is unidirectional (client → server):
They serve opposite purposes, so we need a separate service. Does this better align with your existing architecture? 🐰💭 🧠 Learnings used |
|
@coderabbitai, coming back to the issue of monitor creating multiple messages: [13039743][W][EventSocket.cpp:226] emitEvent(): [🐼] Failed to send event monitor from lightscontrol to client 55: ESP_FAIL (len: 49152) It looks this occurs especially when the client is still connected but not active. Because of this occurring, when I open the browser (and the client gets active again) it is not responding anymore Does that make sense? And if so, why is it only happening when the client is not active? are the emitted monitor events not processed when the client is inactive? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1311 🏁 Script executed: Length of output: 10325 🏁 Script executed: Length of output: 290 🏁 Script executed: Length of output: 1632 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 6699 🏁 Script executed: Length of output: 2515 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 9545 🏁 Script executed: Length of output: 278 🌐 Web query: 💡 Result: Short summary — what happens and why:
Practical effects
Mitigations and patterns
If you want, I can:
Sources @ewowi, YES, that makes perfect sense! Your observation is spot-on, and it reveals exactly why the ClientVisibilityService I proposed is necessary. Let me explain what's happening: The Root CauseWhat Happens When a Tab is Hidden
Why the Client Becomes UnresponsiveWhen you make the tab visible again:
Why This Only Happens for Inactive ClientsActive (visible) clients: Browser processes WebSocket messages immediately → TCP ACKs sent → ESP32 buffer drains → no ESP_FAIL Inactive (hidden) clients: Browser delays processing → no TCP ACKs → ESP32 buffer fills → ESP_FAIL The Solution: ClientVisibilityServiceThis validates the exact use case for the ClientVisibilityService I proposed earlier:
Short-Term Workaround (Before Implementing ClientVisibilityService)Add a simple check in ModuleLightsControl.h to skip monitor entirely if last send failed: static bool monitorSendFailed = false;
static unsigned long lastFailTime = 0;
// In monitor emission block:
if (_socket->getConnectedClients() && _state.data["monitorOn"]) {
// Skip sending for 5 seconds after a failure
if (monitorSendFailed && millis() - lastFailTime < 5000) {
return; // Give client time to catch up
}
// Try to send
// Note: We need to modify EventSocket.cpp to return the result
// For now, you could check if failure logs increased
_socket->emitEvent("monitor", (char*)layerP.lights.channelsD,
MIN(layerP.lights.header.nrOfChannels, layerP.lights.maxChannels),
_moduleName.c_str());
}However, this is a band-aid. The proper solution is ClientVisibilityService, which:
Additional Optimization: Reduce Monitor Frame Size48KB is quite large for WebSocket frames. Consider: // Only send visible channels (monitor might not need all channels)
unsigned int monitorChannels = MIN(
layerP.lights.header.nrOfLights * 3, // RGB only, skip alpha/W?
layerP.lights.maxChannels
);Or implement frame skipping when send fails: static uint8_t frameSkipCounter = 0;
if (monitorSendFailed) {
frameSkipCounter++;
if (frameSkipCounter % 10 != 0) return; // Only send every 10th frame
} else {
frameSkipCounter = 0; // Reset when healthy
}ConclusionYour diagnosis is 100% correct: inactive clients cause buffer overflow, and reactivation causes UI freeze from the backlog. The ClientVisibilityService I proposed earlier is the architectural solution to this exact problem. Would you like me to prioritize implementing ClientVisibilityService, or would you prefer to try the workarounds first? 🐰💡 ✏️ Learnings added
🧠 Learnings used |
|
@coderabbitai, could this also be part of the problem: // 🌙 error check => I removed the erase because I don't want to cancel a subscription because it failed once (too drastically), but in that case ++it should not be in the else clause but implemented as well isn't it? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1920 @ewowi, YES! This is absolutely a critical bug and definitely part (maybe most) of the problem! 🎯 The BugYour iterator logic has a classic mistake: if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)", ...);
vTaskDelay(1);
// it = subscriptions.erase(it); // Commented out
// ❌ NO ++it HERE - iterator doesn't advance!
}
else
{
++it; // ✅ Only increments on success
}What happens:
The Cascading FailureThis compounds the browser throttling issue:
The The FixAlways increment the iterator, regardless of result: // 🌙 error check
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
vTaskDelay(1); // Task watchdog got triggered.
// Don't erase subscription - give it another chance next time
}
// ✅ ALWAYS increment, whether success or failure
++it;Or if you want to be explicit: if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
vTaskDelay(1);
++it; // ✅ Move to next client even on failure
}
else
{
++it; // ✅ Move to next client on success
}Even better - factor out the common increment: esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
vTaskDelay(1); // Prevent watchdog on retry
}
// Always advance to next subscription
++it;Optional: Add Failure CounterIf you want to eventually give up on persistently failing clients: // Add to EventSocket.h private members:
std::map<int, uint8_t> client_failure_counts;
// In the loop:
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
// Track consecutive failures
client_failure_counts[subscription]++;
// After 10 consecutive failures, unsubscribe
if (client_failure_counts[subscription] >= 10)
{
ESP_LOGE(SVK_TAG, "Client %u failed 10 times, removing subscription to %s",
client->socket(), event.c_str());
it = subscriptions.erase(it);
client_failure_counts.erase(subscription);
continue; // erase already advanced iterator
}
vTaskDelay(1);
}
else
{
// Reset failure counter on success
client_failure_counts[subscription] = 0;
}
++it; // Always advanceImmediate ActionThis should be fixed immediately - it's causing the system to lock up. The simple fix (always Combined with ClientVisibilityService (to prevent failures from inactive clients), this will solve both issues:
Great catch! This explains a lot of the unresponsiveness you've been seeing. 🐰🔧 🧠 Learnings used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In @docs/develop/architecture.md:
- Line 13: The table entry for ESP32SvelteKit incorrectly lists Core 0
(PRO_CPU); update that table row so ESP32SvelteKit shows Core 1 (APP_CPU) to
match the section header and design principles (which require SvelteKit on
APP_CPU to avoid HTTP requests preempting LED Driver Task). Ensure the Core
column for the ESP32SvelteKit row reads "1 (APP_CPU)" and keep the rest of the
row (priority, task name, period, description) unchanged.
In @docs/develop/installation.md:
- Around line 295-307: Fix the malformed Markdown links by removing the
extraneous quotes wrapping the URLs in the link definitions for
"ESP-Web-Tools-Tutorial", "CP210x drivers", "CH342, CH343, CH9102 drivers", and
"CH340, CH341 drivers" so they become standard Markdown links like
[text](https://example.com); also remove the stray HTML closing tags "</li>"
after the CH342/CH343 and CH340/CH341 driver list items so the list uses plain
Markdown syntax.
In @docs/gettingstarted/hardware.md:
- Line 35: Replace the misspelled word "perfactly" with "perfectly" in the
sentence "It is perfactly usable to drive Art-Net." so it reads "It is perfectly
usable to drive Art-Net. To drive LEDs a few more components are needed:";
ensure only the typo is changed and punctuation/spaces remain unchanged.
- Line 55: Fix the two typos in the sentence "A few examples are shown below.
Note that MoonLight also supports Art-Net in which can be uased as an
alternative to off-the-shelve controllers." by changing "uased" to "used" and
"off-the-shelve" to "off-the-shelf" so the sentence reads correctly and clearly.
In @docs/gettingstarted/installer.md:
- Line 27: The installer docs include <img> tags without alt attributes (e.g.,
the image with src containing f092743f-a362-40da-b932-d31b203d966d and the other
screenshots referenced in the comment), so update each <img> element to include
concise, descriptive alt text that explains what the screenshot shows and its
purpose in the installation flow; apply this change to all images noted (lines
27, 32, 36, 37, 38, 61, 65, 73, 95, 101, 107, 130, 140) by editing the
corresponding <img> tags to add an appropriate alt="…" string.
🧹 Nitpick comments (1)
docs/develop/architecture.md (1)
273-321: Idle Watchdog section is comprehensive and well-documented.The explanation correctly describes the watchdog mechanism, the critical insight that
vTaskDelay(1)is needed to unblock and yield to the Idle task (not justtaskYIELD()), and the frequency-based yield strategy. The code examples and commentary are clear and match the learnings provided.One minor note: The inline
yieldCallCountcounter is shared and non-atomic across tasks, which introduces imprecision and potential cross-task interference (though not memory corruption). If desired, this could be mentioned as a known trade-off for simplicity on large setups.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
docs/develop/architecture.mddocs/develop/documentation.mddocs/develop/installation.mddocs/gettingstarted/hardware.mddocs/gettingstarted/installer.mdfirmware/installer/manifest.jsoninterface/src/routes/demo/+page.svelteinterface/src/routes/demo/+page.tsinterface/src/routes/demo/Demo.sveltelib/framework/EventSocket.cpplib/framework/WWWData.hplatformio.ini
💤 Files with no reviewable changes (3)
- interface/src/routes/demo/+page.ts
- interface/src/routes/demo/Demo.svelte
- interface/src/routes/demo/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
- docs/develop/documentation.md
- platformio.ini
- lib/framework/EventSocket.cpp
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Be aware of the integrated build process where the PlatformIO build automatically builds and embeds the SvelteKit frontend into the ESP32 firmware
Applied to files:
docs/develop/installation.md
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/**/platformio.ini : Use PlatformIO to build and upload the ESP32 backend C++ code
Applied to files:
docs/develop/installation.md
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
docs/develop/architecture.md
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
docs/develop/architecture.md
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
docs/develop/architecture.md
📚 Learning: 2026-01-04T09:31:07.533Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:07.533Z
Learning: In lib/framework/ESP32SvelteKit.cpp, the SvelteKit loop task stack size of 6KB (6 * 1024 bytes) for non-PSRAM devices has been validated through testing, showing approximately 1500 bytes of free stack remaining under load, providing adequate ~24% headroom.
Applied to files:
docs/develop/architecture.md
📚 Learning: 2026-01-08T10:20:09.363Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
Applied to files:
docs/develop/architecture.md
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
docs/develop/architecture.md
📚 Learning: 2026-01-06T15:24:29.770Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:212-221
Timestamp: 2026-01-06T15:24:29.770Z
Learning: In VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking for unmapped lights (the else branch around lines 212-221) is intentionally disabled for performance when driving 100K+ LEDs. The design places responsibility on effect developers to stay within maxChannels. This is an explicit performance vs safety tradeoff for extreme LED counts.
Applied to files:
docs/develop/architecture.md
📚 Learning: 2026-01-08T11:39:52.862Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Applied to files:
docs/develop/architecture.md
🪛 LanguageTool
docs/gettingstarted/hardware.md
[grammar] ~55-~55: Ensure spelling is correct
Context: ...t also supports Art-Net in which can be uased as an alternative to off-the-shelve con...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
[grammar] ~55-~55: Ensure spelling is correct
Context: ...n be uased as an alternative to off-the-shelve controllers. In this scenario there is ...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
docs/gettingstarted/installer.md
[style] ~116-~116: Using many exclamation marks might seem excessive (in this case: 22 exclamation marks for a text that’s 9648 characters long)
Context: ...llation/#usb-to-serial-chip-drivers)
!!! info "Next steps"
* You can now ch...
(EN_EXCESSIVE_EXCLAMATION)
🪛 markdownlint-cli2 (0.18.1)
docs/gettingstarted/installer.md
27-27: Images should have alternate text (alt text)
(MD045, no-alt-text)
32-32: Images should have alternate text (alt text)
(MD045, no-alt-text)
36-36: Images should have alternate text (alt text)
(MD045, no-alt-text)
37-37: Images should have alternate text (alt text)
(MD045, no-alt-text)
38-38: Images should have alternate text (alt text)
(MD045, no-alt-text)
61-61: Images should have alternate text (alt text)
(MD045, no-alt-text)
65-65: Images should have alternate text (alt text)
(MD045, no-alt-text)
73-73: Images should have alternate text (alt text)
(MD045, no-alt-text)
95-95: Images should have alternate text (alt text)
(MD045, no-alt-text)
101-101: Images should have alternate text (alt text)
(MD045, no-alt-text)
107-107: Images should have alternate text (alt text)
(MD045, no-alt-text)
118-118: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
130-130: Images should have alternate text (alt text)
(MD045, no-alt-text)
140-140: Images should have alternate text (alt text)
(MD045, no-alt-text)
docs/develop/architecture.md
28-28: Images should have alternate text (alt text)
(MD045, no-alt-text)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (7)
docs/develop/architecture.md (3)
17-36: Task descriptions are clear and architecturally sound.The updated descriptions for Effect Task, Driver Task, and ESP32SvelteKit correctly explain the rationale for core assignments and priorities. The core reasoning aligns well with the learnings: Effect Task (Core 0) tolerates WiFi preemption for computation; Driver Task (Core 1) requires timing isolation for DMA/I2S/LCD/PARLIO; ESP32SvelteKit (Core 1, lower priority) processes UI without blocking LED output.
40-100: Diagrams correctly reflect updated core assignments.Both the sequence diagram and the core assignment graph accurately depict the new configuration: Effect Task on Core 0 with WiFi/lwIP, Driver Task on Core 1 isolated from WiFi, and SvelteKit on Core 1 with lower priority. The visual hierarchy makes the design intent clear.
1-321: Overall documentation update is thorough and consistent with learnings.The architecture documentation has been updated to reflect the new task core assignments (Effects on Core 0, Driver on Core 1, SvelteKit on Core 1) with clear explanations and updated diagrams. The new Idle Watchdog section provides valuable guidance on managing the task watchdog and yield mechanisms for large LED setups. The documentation aligns well with the learnings about task priorities, core isolation, and the importance of
vTaskDelay(1)overtaskYIELD()for reaching the Idle task.One outstanding issue: The table entry for ESP32SvelteKit (line 13) shows Core 0, which contradicts the section header (line 31) and the enhanced summary. Correct the table to show Core 1 and mark it as changed.
docs/develop/installation.md (1)
100-100: LGTM: Terminology improvement.The change from "reboot" to "restart" is more user-friendly and consistent with common documentation practices.
firmware/installer/manifest.json (2)
35-46: LGTM: Board naming simplification.The ESP32-S3 variant names have been simplified by removing the
-devkitc-1-prefix, making them more concise while maintaining clarity. The corresponding image and binary paths are consistently updated.
75-75: Good catch: Typo fix.The extra space in "esp32-p4- olimex" has been correctly removed.
docs/gettingstarted/installer.md (1)
46-47: Board naming consistency verified.The ESP32-S3 variant names (esp32-s3-n8r8v, esp32-s3-n16r8v) and the corrected ESP32-P4-Olimex name align correctly with the updated manifest.json entries.
Also applies to: 52-52
| |------|------|----------|------------|-----------|---------| | ||
| | **WiFi/BT** | 0 (PRO_CPU) | 23 | System | Event-driven | System networking stack | | ||
| | **lwIP TCP/IP** | 0 (PRO_CPU) | 18 | System | Event-driven | TCP/IP protocol processing | | ||
| | **ESP32SvelteKit** | 0 (PRO_CPU) | 2 | System | 10ms | HTTP/WebSocket UI framework | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n docs/develop/architecture.md | head -40Repository: MoonModules/MoonLight
Length of output: 2327
🏁 Script executed:
git diff docs/develop/architecture.md | head -100Repository: MoonModules/MoonLight
Length of output: 47
🏁 Script executed:
git status docs/develop/architecture.md && echo "---" && git log --oneline -5 -- docs/develop/architecture.mdRepository: MoonModules/MoonLight
Length of output: 175
🏁 Script executed:
git show 16d44d6 -- docs/develop/architecture.md | head -150Repository: MoonModules/MoonLight
Length of output: 5711
ESP32SvelteKit table entry shows Core 0 but section explains Core 1 placement.
Line 13 shows ESP32SvelteKit on Core 0 (PRO_CPU), which contradicts the section header at line 31 that correctly places it on Core 1 (APP_CPU). The design principles section (lines 131–136) explicitly explains why ESP32SvelteKit must run on Core 1: to prevent HTTP requests from stalling LED output, since Driver Task (priority 3) preempts SvelteKit (priority 2). The table entry must be corrected for consistency.
Proposed correction for table consistency
-| **ESP32SvelteKit** | 0 (PRO_CPU) | 2 | System | 10ms | HTTP/WebSocket UI framework |
+| **ESP32SvelteKit** | 1 (APP_CPU) | 2 | System | 10ms | HTTP/WebSocket UI framework |📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| | **ESP32SvelteKit** | 0 (PRO_CPU) | 2 | System | 10ms | HTTP/WebSocket UI framework | | |
| | **ESP32SvelteKit** | 1 (APP_CPU) | 2 | System | 10ms | HTTP/WebSocket UI framework | |
🤖 Prompt for AI Agents
In @docs/develop/architecture.md at line 13, The table entry for ESP32SvelteKit
incorrectly lists Core 0 (PRO_CPU); update that table row so ESP32SvelteKit
shows Core 1 (APP_CPU) to match the section header and design principles (which
require SvelteKit on APP_CPU to avoid HTTP requests preempting LED Driver Task).
Ensure the Core column for the ESP32SvelteKit row reads "1 (APP_CPU)" and keep
the rest of the row (priority, task name, period, description) unchanged.
docs/develop/installation.md
Outdated
| See also [ESP-Web-Tools-Tutorial]("https://github.com/witnessmenow/ESP-Web-Tools-Tutorial/blob/main/README.md") | ||
|
|
||
| [ESPConnect on GitHub](https://github.com/thelastoutpostworkshop/ESPConnect) | ||
| ESPConnect is based on [Jason2866](https://github.com/Jason2866)'s [WebSerial ESPTool](https://github.com/Jason2866/WebSerial_ESPTool/tree/development). | ||
|
|
||
| ### USB-to-serial chip drivers | ||
|
|
||
| In case your computer does not recognise your connected ESP32, you might need to install the right USB-to-serial chip drivers. | ||
| Below are the drivers for common chips used in ESP devices. See the images below to see what chip your ESP32 has. | ||
|
|
||
| * CP210x drivers: [Windows & Mac]("https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers") | ||
| * CH342, CH343, CH9102 drivers: [Windows]("https://www.wch.cn/downloads/CH343SER_ZIP.html"), [Mac]("https://www.wch.cn/downloads/CH34XSER_MAC_ZIP.html") (download via blue button with download icon)</li> | ||
| * CH340, CH341 drivers: [Windows]("https://www.wch.cn/downloads/CH341SER_ZIP.html"), [Mac]("https://www.wch.cn/downloads/CH341SER_MAC_ZIP.html") (download via blue button with download icon)</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix markdown link syntax errors.
Multiple links incorrectly wrap URLs in quotes within parentheses. In markdown, URLs should not be quoted unless adding a title attribute.
🔗 Proposed fix for link syntax
!!! info "Tools used"
- The installer is powered by [ESP Web Tools]("https://esphome.github.io/esp-web-tools")
- See also [ESP-Web-Tools-Tutorial]("https://github.com/witnessmenow/ESP-Web-Tools-Tutorial/blob/main/README.md")
+ The installer is powered by [ESP Web Tools](https://esphome.github.io/esp-web-tools)
+ See also [ESP-Web-Tools-Tutorial](https://github.com/witnessmenow/ESP-Web-Tools-Tutorial/blob/main/README.md)
[ESPConnect on GitHub](https://github.com/thelastoutpostworkshop/ESPConnect)
ESPConnect is based on [Jason2866](https://github.com/Jason2866)'s [WebSerial ESPTool](https://github.com/Jason2866/WebSerial_ESPTool/tree/development).
### USB-to-serial chip drivers
In case your computer does not recognise your connected ESP32, you might need to install the right USB-to-serial chip drivers.
Below are the drivers for common chips used in ESP devices. See the images below to see what chip your ESP32 has.
-* CP210x drivers: [Windows & Mac]("https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers")
-* CH342, CH343, CH9102 drivers: [Windows]("https://www.wch.cn/downloads/CH343SER_ZIP.html"), [Mac]("https://www.wch.cn/downloads/CH34XSER_MAC_ZIP.html") (download via blue button with download icon)</li>
-* CH340, CH341 drivers: [Windows]("https://www.wch.cn/downloads/CH341SER_ZIP.html"), [Mac]("https://www.wch.cn/downloads/CH341SER_MAC_ZIP.html") (download via blue button with download icon)</li>
+* CP210x drivers: [Windows & Mac](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers)
+* CH342, CH343, CH9102 drivers: [Windows](https://www.wch.cn/downloads/CH343SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH34XSER_MAC_ZIP.html) (download via blue button with download icon)
+* CH340, CH341 drivers: [Windows](https://www.wch.cn/downloads/CH341SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH341SER_MAC_ZIP.html) (download via blue button with download icon)Also note: The </li> closing tags on lines 306-307 should be removed as they're not needed in markdown lists.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @docs/develop/installation.md around lines 295 - 307, Fix the malformed
Markdown links by removing the extraneous quotes wrapping the URLs in the link
definitions for "ESP-Web-Tools-Tutorial", "CP210x drivers", "CH342, CH343,
CH9102 drivers", and "CH340, CH341 drivers" so they become standard Markdown
links like [text](https://example.com); also remove the stray HTML closing tags
"</li>" after the CH342/CH343 and CH340/CH341 driver list items so the list uses
plain Markdown syntax.
|
|
||
| The [M5Stack AtomS3R](https://docs.m5stack.com/en/core/AtomS3R) will be used to offer 'MoonLight in a box'. | ||
| The [M5Stack AtomS3R](https://docs.m5stack.com/en/core/AtomS3R) can be used to offer 'MoonLight in a box'. | ||
| It is perfactly usable to drive Art-Net. To drive LEDs a few more components are needed: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix typo: "perfactly" → "perfectly"
Line 35 has a spelling error.
✏️ Proposed fix
-It is perfactly usable to drive Art-Net. To drive LEDs a few more components are needed:
+It is perfectly usable to drive Art-Net. To drive LEDs a few more components are needed:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| It is perfactly usable to drive Art-Net. To drive LEDs a few more components are needed: | |
| It is perfectly usable to drive Art-Net. To drive LEDs a few more components are needed: |
🧰 Tools
🪛 LanguageTool
[grammar] ~35-~35: Ensure spelling is correct
Context: ...d to offer 'MoonLight in a box'. It is perfactly usable to drive Art-Net. To drive LEDs ...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
In @docs/gettingstarted/hardware.md at line 35, Replace the misspelled word
"perfactly" with "perfectly" in the sentence "It is perfactly usable to drive
Art-Net." so it reads "It is perfectly usable to drive Art-Net. To drive LEDs a
few more components are needed:"; ensure only the typo is changed and
punctuation/spaces remain unchanged.
| ## DMX / Art-Net | ||
|
|
||
| ## For latest DMX support | ||
| A few examples are shown below. Note that MoonLight also supports Art-Net in which can be uased as an alternative to off-the-shelve controllers. In this scenario there is one MoonLight device sending Art-Net and one (or more) MoonLight devices receiving Art-Net |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix spelling errors: "uased" → "used" and "off-the-shelve" → "off-the-shelf"
Line 55 contains two spelling errors that should be corrected before release.
✏️ Proposed fix
-A few examples are shown below. Note that MoonLight also supports Art-Net in which can be uased as an alternative to off-the-shelve controllers. In this scenario there is one MoonLight device sending Art-Net and one (or more) MoonLight devices receiving Art-Net
+A few examples are shown below. Note that MoonLight also supports Art-Net in which can be used as an alternative to off-the-shelf controllers. In this scenario there is one MoonLight device sending Art-Net and one (or more) MoonLight devices receiving Art-Net📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| A few examples are shown below. Note that MoonLight also supports Art-Net in which can be uased as an alternative to off-the-shelve controllers. In this scenario there is one MoonLight device sending Art-Net and one (or more) MoonLight devices receiving Art-Net | |
| A few examples are shown below. Note that MoonLight also supports Art-Net in which can be used as an alternative to off-the-shelf controllers. In this scenario there is one MoonLight device sending Art-Net and one (or more) MoonLight devices receiving Art-Net |
🧰 Tools
🪛 LanguageTool
[grammar] ~55-~55: Ensure spelling is correct
Context: ...t also supports Art-Net in which can be uased as an alternative to off-the-shelve con...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
[grammar] ~55-~55: Ensure spelling is correct
Context: ...n be uased as an alternative to off-the-shelve controllers. In this scenario there is ...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
In @docs/gettingstarted/hardware.md at line 55, Fix the two typos in the
sentence "A few examples are shown below. Note that MoonLight also supports
Art-Net in which can be uased as an alternative to off-the-shelve controllers."
by changing "uased" to "used" and "off-the-shelve" to "off-the-shelf" so the
sentence reads correctly and clearly.
| MoonLight v0.7.0, 22 December 2025 | ||
| Select your ESP32-device from the list below and press the corresponding Connect button and follow these steps: | ||
|
|
||
| <img width="250" src="https://github.com/user-attachments/assets/f092743f-a362-40da-b932-d31b203d966d" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add alt text to images for accessibility.
Multiple instructional images throughout the installation flow lack alternative text, which impacts screen reader users and violates accessibility best practices. While the written instructions provide context, descriptive alt text ensures the visual information is accessible to all users.
♿ Suggested alt text additions
Consider adding descriptive alt text to each image based on what it shows:
-<img width="250" src="https://github.com/user-attachments/assets/f092743f-a362-40da-b932-d31b203d966d" />
+<img width="250" alt="Device selection dialog showing available serial ports" src="https://github.com/user-attachments/assets/f092743f-a362-40da-b932-d31b203d966d" />
-<img width="200" alt="Screenshot 2025-06-07 at 20 54 31" src="..." />
+<img width="200" alt="Serial port connection confirmation dialog" src="..." />
-<img width="250" src="https://github.com/user-attachments/assets/35045317-520d-427c-894e-418693877831" />
+<img width="250" alt="Install MoonLight button in flash dialog" src="https://github.com/user-attachments/assets/35045317-520d-427c-894e-418693877831" />Apply similar descriptive alt text to all instructional screenshots (lines 27, 32, 36, 37, 38, 61, 65, 73, 95, 101, 107, 130, 140).
Also applies to: 32-32, 36-38, 61-61, 65-65, 73-73, 95-95, 101-101, 107-107, 130-130, 140-140
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
27-27: Images should have alternate text (alt text)
(MD045, no-alt-text)
🤖 Prompt for AI Agents
In @docs/gettingstarted/installer.md at line 27, The installer docs include
<img> tags without alt attributes (e.g., the image with src containing
f092743f-a362-40da-b932-d31b203d966d and the other screenshots referenced in the
comment), so update each <img> element to include concise, descriptive alt text
that explains what the screenshot shows and its purpose in the installation
flow; apply this change to all images noted (lines 27, 32, 36, 37, 38, 61, 65,
73, 95, 101, 107, 130, 140) by editing the corresponding <img> tags to add an
appropriate alt="…" string.
|
@coderabbitai, instead of creating a map of the clients visibility, could we also store the visibility in an existing variable ? Eg client ? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2730 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 20880 🏁 Script executed: Length of output: 1092 🏁 Script executed: Length of output: 4903 🏁 Script executed: Length of output: 709 @ewowi, great question! Let me explain why storing visibility in the client object is unfortunately not feasible with the current PsychicHttp library structure: The Problem with
|
|
@coderabbitai, I am doing a fresh install and when adding the first layout driver I get the watchdog. What could be wrong? E (56021) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time: Backtrace: 0x4202ee09:0x3fcb8330 0x4202f029:0x3fcb83b0 0x42096899:0x3fcb8420 0x42096906:0x3fcb84c0 0x420879f3:0x3fcb84e0 0x42087a0e:0x3fcb8500 0x42088089:0x3fcb8520 0x420880e5:0x3fcb8550 0x4202cdcd:0x3fcb85a0 0x4202e55e:0x3fcb85d0 0x4217a3f1:0x3fcb86b0 0x4201a14a:0x3fcb86d0 0x42011fe9:0x3fcb8790 0x420f8b0a:0x3fcb8810 0x420f9380:0x3fcb88b0 0x420f7f08:0x3fcb88d0 0x4217f50a:0x3fcb88f0 0x420f80af:0x3fcb8910 0x4037fd89:0x3fcb89a0 ELF file SHA256: 8f86668b3 Rebooting... |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1738 @ewowi, This is the critical bug we discussed earlier! The watchdog is firing because of the The ProblemLooking at the backtrace: What's happening:
The FixRemove the // 🌙 error check
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
// ❌ REMOVE THIS: vTaskDelay(1); // Task watchdog got triggered.
// Don't delay in HTTP callback - just continue to next client
}
++it; // increase if no subscriptions.erase(it) took place!Why this works:
Why This Happens During Fresh InstallDuring initial setup:
Complete Solution
The This is exactly the issue I warned about earlier: "You should not use yield or delay inside callbacks." Remove that one line and the watchdog should be resolved! 🐰🔧 🧠 Learnings used |
|
@coderabbitai, I removed it but still it happens, could it be also the other addYield / vtaskdelays I added which helps when heavy load in the tasks but won't help if there are no nodes in the tasks? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1467 🏁 Script executed: Length of output: 524 🏁 Script executed: Length of output: 885 🏁 Script executed: Length of output: 1683 🏁 Script executed: Length of output: 1268 🏁 Script executed: Length of output: 2141 🏁 Script executed: Length of output: 4508 @ewowi, I found the issue! The // Line 224-230 from EventSocket.cpp
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)", event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
vTaskDelay(1); // Task watchdog got triggered. ⬅️ STILL HERE!
// [29177816][W][EventSocket.cpp:226] emitEvent(): [🐼] Failed to send event monitor from lightscontrol to client 53: ESP_FAIL (len: 49152)
// it = subscriptions.erase(it);// do not erase as we hope for better times
}
++it; // increase if no subscriptions.erase(it) took place!Why This Causes the Watchdog
The FixRemove line 226 completely: // 🌙 error check
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
// ❌ REMOVE THIS LINE: vTaskDelay(1);
}
++it; // increase if no subscriptions.erase(it) took place!Then recompile and reflash. Your Question About addYieldYou're right to think about this, but the addYield calls in node loops are fine:
So even with no nodes, both tasks feed their watchdogs correctly. The problem is purely the Please verify:
🐰🔍 🧠 Learnings used |
|
@coderabbitai, I removed it for sure, but didn't commit it yet, so look if it could be something else , maybe the esp wdt reset calls I added recently ? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1152 🏁 Script executed: Length of output: 1269 🏁 Script executed: Length of output: 8366 🏁 Script executed: Length of output: 2320 @ewowi, I found the root cause! The problem is in the task structure, not the EventSocket code. The Problem: Watchdog Reset Happens AFTER Mutex LockLook at driverTask (lines 171-192): while (true) {
bool mutexGiven = false;
xSemaphoreTake(swapMutex, portMAX_DELAY); // ⬅️ LINE 171: BLOCKS HERE!
// ... check isPositions, run loopDrivers() ...
if (!mutexGiven) xSemaphoreGive(swapMutex); // LINE 190
esp_task_wdt_reset(); // ⬅️ LINE 191: NEVER REACHED IF BLOCKED ABOVE!
vTaskDelay(1);
}If driverTask blocks waiting for What Causes the Mutex Block During Fresh Install?During fresh install when adding first driver:
The Fix: Move Watchdog Reset BEFORE Mutexvoid driverTask(void* pvParameters) {
esp_task_wdt_add(NULL);
while (true) {
bool mutexGiven = false;
// ✅ RESET WATCHDOG FIRST, before potentially blocking operations
esp_task_wdt_reset();
// Now take mutex
xSemaphoreTake(swapMutex, portMAX_DELAY);
if (layerP.lights.header.isPositions == 3) {
EXT_LOGD(ML_TAG, "positions done (3 -> 0)");
layerP.lights.header.isPositions = 0;
}
if (layerP.lights.header.isPositions == 0) {
if (newFrameReady) {
newFrameReady = false;
if (layerP.lights.useDoubleBuffer) {
xSemaphoreGive(swapMutex);
mutexGiven = true;
}
esp32sveltekit.lps++;
layerP.loopDrivers();
}
}
if (!mutexGiven) xSemaphoreGive(swapMutex);
vTaskDelay(1);
}
esp_task_wdt_delete(NULL);
}Do the same for effectTask (move Verify EventSocket Iterator FixDouble-check your local EventSocket.cpp around line 220-235. It should look like this: for (auto it = subscriptions.begin(); it != subscriptions.end(); ) {
// ... get client ...
esp_err_t result = client->sendMessage(...);
if (result != ESP_OK) {
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
// ❌ NO vTaskDelay here
// ❌ NO erase
}
++it; // ✅ ALWAYS increment, not just in else clause
}The Why This MattersEven without Summary of changes needed:
This should resolve the watchdog during fresh install! 🐰🔧 🧠 Learnings used |
|
@coderabbitai, maybe necessary but not sufficient as I got now: [ 53125][D][Module.h:153] loop(): [💫] ESP32 SvelteKit (3968) requestUIUpdate |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 3222 @ewowi, Found it! The root cause is that The Problem: Mutex Held During Slow I/OxSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); // Line 165-169
// ... iterate ALL clients and send (lines 206-239) ...
for (auto it = subscriptions.begin(); it != subscriptions.end(); ) {
// client->sendMessage() can take 10-100ms per client!
// If 5 clients, mutex held for 500ms
vTaskDelay(1); // ⬅️ LINE 232: STILL HERE, blocks while holding mutex!
}
xSemaphoreGive(clientSubscriptionsMutex); // Line 242If any client send is slow (hidden tab, network issue), ALL other threads trying to emit events block for seconds. The Fix: Minimize Critical SectionOnly hold mutex while copying subscription list, not during sends: void EventSocket::emitEvent(const String& event, const char *output, size_t len,
const char *originId, bool onlyToSameOrigin)
{
if (output == NULL || len == 0 || !isEventValid(event))
{
ESP_LOGE(SVK_TAG, "Invalid params in emitEvent for event %s", event.c_str());
return;
}
int originSubscriptionId = 0;
if (originId[0] != '\0') {
originSubscriptionId = atoi(originId);
}
// ✅ Step 1: Take mutex and COPY subscription list
std::vector<int> subscriptionsCopy;
if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(100)) == pdFALSE) {
ESP_LOGW(SVK_TAG, "clientSubscriptionsMutex wait too long for event %s", event.c_str());
if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(500)) == pdFALSE) {
ESP_LOGE(SVK_TAG, "clientSubscriptionsMutex timeout - skipping event %s", event.c_str());
return; // Give up to prevent deadlock
}
}
auto &subscriptions = client_subscriptions[event];
if (subscriptions.empty())
{
xSemaphoreGive(clientSubscriptionsMutex);
return;
}
// Copy subscription IDs to local vector
subscriptionsCopy.assign(subscriptions.begin(), subscriptions.end());
// ✅ Release mutex BEFORE sending (critical section minimized!)
xSemaphoreGive(clientSubscriptionsMutex);
// ✅ Step 2: Send to clients WITHOUT holding mutex
std::vector<int> deadClients; // Track clients to remove
if (onlyToSameOrigin && originSubscriptionId > 0)
{
auto *client = _socket.getClient(originSubscriptionId);
if (client)
{
if (event != "monitor")
ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s",
event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output);
#if FT_ENABLED(EVENT_USE_JSON)
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
#else
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
#endif
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %d: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
}
}
}
else
{
// Send to all subscribed clients (no mutex held!)
for (int subscription : subscriptionsCopy)
{
if (subscription == originSubscriptionId)
continue;
auto *client = _socket.getClient(subscription);
if (!client)
{
deadClients.push_back(subscription);
continue;
}
if (event != "monitor")
ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s",
event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output);
#if FT_ENABLED(EVENT_USE_JSON)
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
#else
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
#endif
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)",
event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
// Don't mark as dead - give it another chance
// Could add failure counter here if needed
}
}
}
// ✅ Step 3: Clean up dead clients (brief mutex re-acquisition)
if (!deadClients.empty())
{
if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(100)) == pdTRUE)
{
auto &subscriptions = client_subscriptions[event];
for (int deadClient : deadClients)
{
subscriptions.remove(deadClient);
ESP_LOGD(SVK_TAG, "Removed dead client %d from event %s subscriptions", deadClient, event.c_str());
}
xSemaphoreGive(clientSubscriptionsMutex);
}
}
}Key Changes
Benefits
Alternative: Per-Event MutexesIf contention persists, consider per-event mutexes: std::map<String, SemaphoreHandle_t> event_mutexes;But the above fix should be sufficient. Please apply this complete rewrite and verify 🐰🔓 🧠 Learnings used |
Frontend ======== - layout: add client_info event and update with visibility Backend ======= - EventSocket: add support for client info.ActiveClients, emitEvent iterator - main: esp_task_wdt_reset before semaphores, add sharedData.activeClients (Nodes.h) - MoonLight effects : Lines: RG and Blue on Z-axis, ScrollingText: add Active clients
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
lib/framework/EventSocket.cpp (1)
210-240: Critical: iterator handling is undefined behavior (double-increment +--itafter erase).
- Line 216:
++it; continue;inside afor (...; ++it)can skip entries and can increment pastend()(UB).- Line 223-224:
it = erase(it); --it;is UB when erasing the first element (you decrementbegin()).- Also: send I/O is still done while holding
clientSubscriptionsMutex, which can still block the http/ws task and recreate watchdog issues under backpressure (the PR objective called out minimizing the critical section).Proposed diff (safe iteration; keeps current locking model)
- for (auto it = subscriptions.begin(); it != subscriptions.end(); ++it) + for (auto it = subscriptions.begin(); it != subscriptions.end(); ) { int subscription = *it; if (subscription == originSubscriptionId) { - ++it; - continue; + ++it; + continue; } auto *client = _socket.getClient(subscription); if (!client) { - it = subscriptions.erase(it); - --it; // do not advance because of erase + it = subscriptions.erase(it); continue; } if (event != "monitor") ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s", event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output); #if FT_ENABLED(EVENT_USE_JSON) esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len); #else esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len); #endif // 🌙 error check if (result != ESP_OK) { ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %d)", event.c_str(), originId, client->socket(), esp_err_to_name(result), len); // it = subscriptions.erase(it);// do not erase as we hope for better times // --it; // do not advance because of erase } + ++it; }Recommended follow-up (matches PR objective): don’t hold mutex during send I/O
- auto &subscriptions = client_subscriptions[event]; + auto &subscriptions = client_subscriptions[event]; if (subscriptions.empty()) { xSemaphoreGive(clientSubscriptionsMutex); return; } + // Copy under mutex, then release to avoid blocking httpd/ws task during sends. + std::vector<int> subsCopy(subscriptions.begin(), subscriptions.end()); + xSemaphoreGive(clientSubscriptionsMutex); + + // ...send using subsCopy... + + // Reacquire only if you need to erase dead clients from `subscriptions`. + if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(100)) == pdFALSE) { ... } + // prune ... + xSemaphoreGive(clientSubscriptionsMutex); + return; - xSemaphoreGive(clientSubscriptionsMutex); + // (existing final give removed because we now early-return after the copy/send/prune flow)lib/framework/EventSocket.h (1)
48-73: Synchronize_clientVisibilityaccess and clean up on disconnect.
_clientVisibilityis written inhandleClientInfo()(line 299) from the WebSocket frame handler and read ingetActiveClients()(lines 305–306) frommain.cpp(different task) without synchronization—this is a race condition. Additionally,onWSClose()does not erase the client from_clientVisibility, causing stale entries to accumulate indefinitely. Use the existingclientSubscriptionsMutexpattern to protect this map, and add_clientVisibility.erase(client->socket())inonWSClose().src/main.cpp (1)
133-155: Potential race:isPositionscan change whileswapMutexis released (double-buffer path), but commit doesn’t re-check before publishing.In
effectTask, you checkisPositions==0under the mutex (Line 133), then (when double buffering) release the mutex and do work, then later reacquire only to swap/publish. If another task can fliplayerP.lights.header.isPositionsaway from0during that unlocked window,effectTaskcan still swap buffers + setnewFrameReady=trueeven though positions-mode has started.If that state can change concurrently, consider re-checking
isPositionsafter reacquiringswapMutexand before swapping/publishing.Proposed fix (re-check before publishing)
if (layerP.lights.useDoubleBuffer) { // Atomic swap channels xSemaphoreTake(swapMutex, portMAX_DELAY); + if (layerP.lights.header.isPositions != 0) { + // Abort publish if positions mode started while we were unlocked + xSemaphoreGive(swapMutex); + continue; + } uint8_t* temp = layerP.lights.channelsD; layerP.lights.channelsD = layerP.lights.channelsE; layerP.lights.channelsE = temp; } newFrameReady = true;
🤖 Fix all issues with AI agents
In @lib/framework/EventSocket.cpp:
- Around line 169-170: Log calls in EventSocket.cpp use the wrong printf
specifier for len (a size_t) causing varargs type mismatches; update the
ESP_LOGW/ESP_LOGI calls that format len (the ones shown and also at the other
occurrences around lines 202-204 and 236-239) to use %zu for len (or cast len to
unsigned and use %u), e.g., replace "%d" with "%zu" for the len argument while
keeping the other format specifiers unchanged and ensure the associated argument
remains the size_t variable named len.
- Around line 295-309: EventSocket::getActiveClients currently counts entries in
_clientVisibility without synchronization and entries are never removed on
disconnect, so it can overcount; add a mutex (e.g., a member std::mutex or
FreeRTOS mutex) to protect accesses to _clientVisibility and wrap both
EventSocket::handleClientInfo and EventSocket::getActiveClients with the same
lock; additionally ensure you remove the map entry when a client disconnects
(wherever disconnect is handled, e.g., in your client teardown method) by
erasing _clientVisibility[originId] under the same mutex so only truly active
clients are counted.
🧹 Nitpick comments (6)
interface/vite.config.ts (1)
19-29: Avoid hard-coded proxy targets + fix comment drift.The targets are now
192.168.1.105, but the comments still reference192.168.1.83. Also consider moving the IP to an env var (e.g.,VITE_DEVICE_HOST) so devs don’t have to edit tracked config.Proposed diff
server: { proxy: { - // Proxying REST: http://localhost:5173/rest/bar -> http://192.168.1.83/rest/bar + // Proxying REST: http://localhost:5173/rest/bar -> http://<device>/rest/bar '/rest': { - target: 'http://192.168.1.105', + target: process.env.VITE_DEVICE_REST_TARGET ?? 'http://192.168.1.105', changeOrigin: true }, - // Proxying websockets ws://localhost:5173/ws -> ws://192.168.1.83/ws + // Proxying websockets ws://localhost:5173/ws -> ws://<device>/ws '/ws': { - target: 'ws://192.168.1.105', + target: process.env.VITE_DEVICE_WS_TARGET ?? 'ws://192.168.1.105', changeOrigin: true, ws: true } } },interface/src/routes/+layout.svelte (2)
56-69: Send initialclient_infoon actual socketopen(not during listener registration).
socket.sendEvent('client_info', ...)inaddEventListeners()may fire before the WS is connected (depends onsocketimplementation). Consider moving the initial send intohandleOpen(and keep thevisibilitychangehandler for updates).Proposed diff
const addEventListeners = () => { socket.on('open', handleOpen); socket.on('close', handleClose); socket.on('error', handleError); ... - document.addEventListener('visibilitychange', handleVisibilityChange); // 🌙 Listen to visibility changes - socket.sendEvent('client_info', { visible: isPageVisible }); // 🌙 Notify server of initial info when connection opens + document.addEventListener('visibilitychange', handleVisibilityChange); }; const handleOpen = () => { // ... + socket.sendEvent('client_info', { visible: isPageVisible }); };Also applies to: 71-82
158-168: Optional: dropconsole.lognoise in visibility handler.This will log on every tab hide/show; consider gating behind a debug flag.
src/main.cpp (3)
162-196: Driver send without mutex: ensureloopDrivers()reads a stable buffer pointer in double-buffer mode.In
driverTask, when double buffering you intentionally releaseswapMutexbefore callinglayerP.loopDrivers()(Lines 181-188). That’s great for reducing lock hold time, but it only stays safe ifloopDrivers()uses a stable snapshot of the buffer pointer (and doesn’t repeatedly dereferencelayerP.lights.channelsDwhileeffectTaskmight swap it).If
loopDrivers()doesn’t already snapshot the pointer internally, consider capturingchannelsDunder the mutex and passing/using that snapshot (or making the pointer swap/read atomic).
289-296: Restart log text is fine; consider logging which allocation failed (optional).“restarting” (Line 294) matches
esp_restart()(Line 295). Optional: add context (which pointer was null / free heap) to help diagnose memory fragmentation on constrained devices.
368-376:sharedData.activeClientswrite is likely cross-task: consider making shared fields atomic or guarded.
sharedData.*is updated in the SvelteKit loop and (presumably) read from other tasks. In standard C++, that’s a data race (UB), even if it tends to “work” on ESP32 for 32-bit aligned loads/stores. If you’re not addressing this for 0.7.1, at least consider making these fieldsstd::atomic<uint32_t>(or guarding with a small critical section) when you circle back.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
interface/src/routes/+layout.svelteinterface/vite.config.tslib/framework/EventSocket.cpplib/framework/EventSocket.hlib/framework/WWWData.hsrc/MoonBase/Nodes.hsrc/MoonLight/Nodes/Effects/E_MoonLight.hsrc/main.cpp
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/interface/vite.config.ts : Configure Vite build tool, proxy settings, and LittleFS compatibility in `interface/vite.config.ts`
Applied to files:
interface/vite.config.ts
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/interface/src/routes/**/*.svelte : Create Svelte routes in `interface/src/routes/` for different pages of the web application
Applied to files:
interface/src/routes/+layout.svelte
📚 Learning: 2026-01-08T11:39:52.862Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Applied to files:
interface/src/routes/+layout.sveltesrc/main.cpplib/framework/EventSocket.h
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
src/main.cpp
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
src/main.cppsrc/MoonLight/Nodes/Effects/E_MoonLight.h
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/src/**/*.{h,cpp} : Modify C++ backend code in the `src/` and `lib/` directories for ESP32 development
Applied to files:
src/main.cpp
📚 Learning: 2026-01-08T10:20:09.363Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-04T09:31:07.533Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:07.533Z
Learning: In lib/framework/ESP32SvelteKit.cpp, the SvelteKit loop task stack size of 6KB (6 * 1024 bytes) for non-PSRAM devices has been validated through testing, showing approximately 1500 bytes of free stack remaining under load, providing adequate ~24% headroom.
Applied to files:
src/main.cpp
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/lib/PsychicHttp/**/*.{h,cpp} : Use the custom PsychicHttp library for HTTP server capabilities, request/response handling, WebSockets, and file serving
Applied to files:
lib/framework/EventSocket.h
📚 Learning: 2025-12-18T15:15:57.828Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-18T15:15:57.828Z
Learning: In DripEffect (E_WLED.h), dropsSize must be set to layer->size.y (not layer->size.x) in onSizeChanged(), because the drops array is allocated as [layer->size.y][maxNumDrops]. Using layer->size.x causes out-of-bounds access when iterating over y-coordinates.
Applied to files:
src/MoonLight/Nodes/Effects/E_MoonLight.h
🧬 Code graph analysis (3)
lib/framework/EventSocket.h (1)
lib/framework/EventSocket.cpp (4)
getActiveClients(303-309)getActiveClients(303-303)handleClientInfo(295-301)handleClientInfo(295-295)
lib/framework/EventSocket.cpp (1)
lib/PsychicHttp/src/PsychicWebSocket.cpp (2)
client(17-19)client(17-17)
src/MoonLight/Nodes/Effects/E_MoonLight.h (1)
src/MoonBase/Utilities.h (1)
Coord3D(151-163)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (7)
src/MoonLight/Nodes/Effects/E_MoonLight.h (3)
115-140: LGTM! Clean 3D line rendering with minor observation.The rewritten LinesEffect correctly renders three perpendicular moving lines (red vertical, green horizontal, blue depth) with proper dimension checks. The logic is sound and the axis-aligned rendering aligns with the project-wide 3D coordinate refactoring mentioned in the AI summary.
Minor observation: Line 116 uses
fadeToBlackBy(255), which completely clears the layer each frame. This produces sharp lines with no trailing effect—likely intentional, but unusual compared to typical trailing values (20-50). If trailing effects are desired, consider reducing this value.
224-224: LGTM! Active clients tracking integrated correctly.The addition of the "Active 🛜" option and corresponding case 9 for displaying
sharedData.activeClientsaligns perfectly with the PR objectives regarding client activity tracking. The auto-cycling logic is correct:
nrOfChoices = 9represents the 9 auto options (cases 2-10)- When no custom text: cycles through cases 2-10
- When custom text provided: cycles through cases 1-10 (including custom text)
Based on PR objectives: This change supports the new per-system client active/visibility tracking mentioned in the objectives.
Also applies to: 235-244, 289-291
527-571: LGTM! Wave effect successfully reoriented.The coordinate mapping changes correctly reorient the wave effect to draw along the x-axis with y-position varying per column. The logic properly handles:
- Wave position calculation scaled to
layer->size.y(lines 542-558)- Wrap-around detection for saw/square wave discontinuities (line 564)
- Vertical line fill to visually connect discontinuities (line 565)
This aligns with the project-wide axis reorientation mentioned in the AI summary and maintains expected wave behavior in the new coordinate system.
lib/framework/EventSocket.cpp (1)
13-29: Good: wire-up for incomingclient_info.Registering and handling
client_infoserver-side aligns well with the “hidden tab backpressure” issue. Based on learnings, this should materially reduce monitor spam to throttled clients.src/MoonBase/Nodes.h (1)
336-341: VerifysharedData.activeClientsis consistently populated and exposed.Adding the field is fine, but please ensure all producers/consumers (MoonBase publish, UI, any JSON serialization) set/read it correctly and don’t assume the old struct layout.
src/main.cpp (2)
121-160: Good: WDT reset moved before the potentially-blocking semaphore take, but long “no-delay” loops can still trip the watchdog.Resetting at the top (Line 130) is the right direction (avoids dying while blocked on
swapMutex), but iflayerP.loop()can run for seconds, you still won’t hitvTaskDelay(1)(Line 156) often enough to let the Idle task run. Please double-check that the heavy loops insidelayerP.loop()/layerP.loop20ms()have periodicvTaskDelay(1)-style yielding (not onlytaskYIELD()), especially under the “49K channels @ 100fps” stress case. Based on learnings,vTaskDelay(1)is the key primitive for letting Idle run.
334-350: Core affinity swap looks correct for ESP32: effects on core 0, drivers on core 1.Pinning
AppDriversto core 1 (Line 349) andAppEffectsto core 0 (Line 340) matches the “timing-critical drivers avoid WiFi preemption” guidance noted in learnings.
Frontend ======= - layout: visibility Backend ======= - EventSocket: Active tweaks. EmitEvent subscription iterator, Active clients: use semaphore - StatefulService, show begin transaction originId - main: isPositions in effect loop - Lights control: monitor only if active clients
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @lib/framework/EventSocket.cpp:
- Around line 295-308: In EventSocket::handleClientInfo, remove the redundant
unprotected write to _clientVisibility[originId] that occurs before taking
clientSubscriptionsMutex; keep the single assignment inside the protected
critical section (after successfully acquiring the mutex) so updates to
_clientVisibility are always protected by the mutex while leaving the existing
semaphore wait/log/give logic unchanged.
🧹 Nitpick comments (1)
src/main.cpp (1)
296-299: Early return instead of reboot for allocation failure.Changing from a hard reboot to an early return is safer during development. The error log indicates this is a dev scenario. Consider adding a fallback behavior or documenting that the system will be in a degraded state.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
docs/moonlight/effects.mdinterface/src/routes/+layout.sveltelib/framework/EventSocket.cpplib/framework/StatefulService.hlib/framework/WWWData.hsrc/MoonLight/Modules/ModuleLightsControl.hsrc/MoonLight/Nodes/Effects/E_MoonLight.hsrc/main.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
- interface/src/routes/+layout.svelte
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
src/MoonLight/Modules/ModuleLightsControl.hsrc/MoonLight/Nodes/Effects/E_MoonLight.hsrc/main.cpp
📚 Learning: 2026-01-08T11:39:52.862Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Applied to files:
src/MoonLight/Modules/ModuleLightsControl.hsrc/main.cpplib/framework/EventSocket.cpp
📚 Learning: 2025-12-18T15:15:57.828Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-18T15:15:57.828Z
Learning: In DripEffect (E_WLED.h), dropsSize must be set to layer->size.y (not layer->size.x) in onSizeChanged(), because the drops array is allocated as [layer->size.y][maxNumDrops]. Using layer->size.x causes out-of-bounds access when iterating over y-coordinates.
Applied to files:
src/MoonLight/Nodes/Effects/E_MoonLight.h
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-08T10:20:09.363Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
Applied to files:
src/main.cpp
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/src/**/*.{h,cpp} : Modify C++ backend code in the `src/` and `lib/` directories for ESP32 development
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-06T15:24:29.770Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:212-221
Timestamp: 2026-01-06T15:24:29.770Z
Learning: In VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking for unmapped lights (the else branch around lines 212-221) is intentionally disabled for performance when driving 100K+ LEDs. The design places responsibility on effect developers to stay within maxChannels. This is an explicit performance vs safety tradeoff for extreme LED counts.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-06T15:24:17.118Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:164-171
Timestamp: 2026-01-06T15:24:17.118Z
Learning: In VirtualLayer::setLight and VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking in the unmapped path (when indexV >= mappingTableSize or oneToOneMapping is true) is intentionally disabled for performance when driving very large LED counts (100K+). Effect developers are responsible for ensuring their code stays within bounds. This is an explicit design decision prioritizing performance over defensive checks in the hot path.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-04T09:31:07.533Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:07.533Z
Learning: In lib/framework/ESP32SvelteKit.cpp, the SvelteKit loop task stack size of 6KB (6 * 1024 bytes) for non-PSRAM devices has been validated through testing, showing approximately 1500 bytes of free stack remaining under load, providing adequate ~24% headroom.
Applied to files:
src/main.cpp
🧬 Code graph analysis (2)
src/MoonLight/Nodes/Effects/E_MoonLight.h (1)
src/MoonBase/Utilities.h (1)
Coord3D(151-163)
lib/framework/EventSocket.cpp (1)
lib/PsychicHttp/src/PsychicWebSocket.cpp (2)
client(17-19)client(17-17)
🪛 markdownlint-cli2 (0.18.1)
docs/moonlight/effects.md
53-53: Table pipe style
Expected: leading_only; Actual: leading_and_trailing; Unexpected trailing pipe
(MD055, table-pipe-style)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (17)
lib/framework/StatefulService.h (1)
165-177: Read operations use hardcoded "read" as originId.The approach of using
"read"as a synthetic originId for read operations is reasonable for debugging/logging purposes.src/MoonLight/Nodes/Effects/E_MoonLight.h (4)
115-140: LGTM - LinesEffect refactored to axis-aligned rendering.The effect now properly renders vertical (red), horizontal (green), and depth (blue) planes, with conditional checks for each axis dimension. This is cleaner than the previous implementation.
235-244: Auto-choice cycling logic is correct.The
nrOfChoices = 9with cases 2-10 for empty textIn and cases 1-10 for non-empty textIn correctly covers all switch cases including the new Active (case 9) and Free memory (case 10) options.
289-293: New display options for Active Clients and Free Heap added.Case 9 displays active clients count and case 10 displays free heap memory in KB. Both integrate well with the existing
sharedDatastructure.
532-570: WaveEffect axis orientation inverted.The effect now runs horizontally along the X axis with the wave amplitude in the Y direction, which is a deliberate change. The position calculations and coordinate mappings are consistent with this new orientation.
docs/moonlight/effects.md (1)
53-53: Documentation updated to reflect new Active Clients display option.The Scrolling Text preset list now correctly includes "Active (dAC)" matching the code changes in E_MoonLight.h. The emoji removals from Status, Clients, and Connected are consistent UI cleanup.
src/MoonLight/Modules/ModuleLightsControl.h (2)
506-518: Monitor emission correctly gated on active (visible) clients.Switching from
getConnectedClients()togetActiveClients()prevents sending large monitor frames (48KB) to hidden browser tabs that would fill ESP32's httpd_ws_send_frame_async() buffers and cause ESP_FAIL. This aligns with the PR discussion and the Page Visibility API integration on the frontend. Based on learnings.
524-528: Consistent active client gating for channelsD emission path.Same pattern applied to the regular channel data emission path, ensuring monitor data is only sent to active clients.
src/main.cpp (5)
128-160: Watchdog reset moved to loop start - correct placement.Moving
esp_task_wdt_reset()to the beginning of the loop ensures the watchdog is reset before any potentially blocking operations (likexSemaphoreTake). This aligns with PR discussions about preventing task watchdog resets during heavy loads.
146-156: Position mode guard added for buffer swap.The additional
isPositions == 0check at line 148 prevents the double-buffer swap when position mode changes during processing. This fixes a race condition where the swap could occur while transitioning between modes.
171-196: Driver task watchdog placement improved.Watchdog reset at the start of the driver task loop follows the same pattern as effectTask, ensuring reset occurs before potential blocking on
swapMutex.
337-353: Task core affinity correctly swapped.Effects task moved to Core 0 and Driver task to Core 1. This aligns with the learnings: effects are pure computation (WiFi preemption acceptable on Core 0), while drivers use timing-critical DMA/I2S operations that require Core 1 to avoid WiFi interference.
369-377: Active clients tracking integrated into shared data.The new
sharedData.activeClientsfield enables effects like ScrollingText to display active client count. The commentedlogYield()appears to be debug instrumentation that can be enabled when needed.lib/framework/EventSocket.cpp (4)
13-28: Client visibility tracking properly initialized.The EVENT_CLIENT_INFO event is registered during
begin()and routes tohandleClientInfo()for processing visibility updates from the frontend's Page Visibility API integration.
62-62: Client visibility state cleaned up on disconnect.Removing the client from
_clientVisibilityon WebSocket close prevents stale entries from accumulating.
211-224: Iterator handling after erase is correct.The pattern of
it = subscriptions.erase(it); --it;followed by the loop's++itresults in correct iteration to the next element. The comment clarifies the intent.
310-322: getActiveClients() properly mutex-protected.The method correctly acquires the mutex before iterating over
_clientVisibilityto count active clients, ensuring thread-safe access.
| if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(100))==pdFALSE) { | ||
| ESP_LOGI("🐼", "_accessMutex wait 100ms"); | ||
| ESP_LOGI("🐼", "_accessMutex wait 100ms", originId.c_str()); | ||
| if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(400))==pdFALSE) { | ||
| ESP_LOGW("🐼", "_accessMutex waited 500ms and continues 🤷♂️"); | ||
| ESP_LOGW("🐼", "_accessMutex waited 500ms and continues %s", originId.c_str()); | ||
| // xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Format string bug in ESP_LOGI - originId is not printed.
Line 202 passes originId.c_str() as an argument but the format string "_accessMutex wait 100ms" has no %s placeholder. The log won't show which origin is waiting.
Additionally, after 500ms total wait the code continues without acquiring the mutex (the recursive take on line 205 is commented out), which could lead to data races if the mutex is still held.
🐛 Proposed fix
inline void beginTransaction(const String &originId) // 🌙 Add originId
{
// 🌙 adding semaphore wait too long logging
if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(100))==pdFALSE) {
- ESP_LOGI("🐼", "_accessMutex wait 100ms", originId.c_str());
+ ESP_LOGI("🐼", "_accessMutex wait 100ms %s", originId.c_str());
if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(400))==pdFALSE) {
- ESP_LOGW("🐼", "_accessMutex waited 500ms and continues %s", originId.c_str());
- // xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
+ ESP_LOGW("🐼", "_accessMutex waited 500ms, blocking until available %s", originId.c_str());
+ xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
}
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(100))==pdFALSE) { | |
| ESP_LOGI("🐼", "_accessMutex wait 100ms"); | |
| ESP_LOGI("🐼", "_accessMutex wait 100ms", originId.c_str()); | |
| if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(400))==pdFALSE) { | |
| ESP_LOGW("🐼", "_accessMutex waited 500ms and continues 🤷♂️"); | |
| ESP_LOGW("🐼", "_accessMutex waited 500ms and continues %s", originId.c_str()); | |
| // xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); | |
| } | |
| } | |
| if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(100))==pdFALSE) { | |
| ESP_LOGI("🐼", "_accessMutex wait 100ms %s", originId.c_str()); | |
| if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(400))==pdFALSE) { | |
| ESP_LOGW("🐼", "_accessMutex waited 500ms, blocking until available %s", originId.c_str()); | |
| xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); | |
| } | |
| } |
|
@coderabbitai, okay we are almost done, something else. Code runs fine on ESP32-D0, S3, P4 but when flashing esp32-c3-supermini the board just doesn't boot. What is going on here. See: assert failed: pcTaskGetName tasks.c:2780 (pxTCB) Stack memory: ELF file SHA256: 40509b00a E (738) esp_core_dump_flash: Core dump flash config is corrupted! CRC=0x7bd5c66f instead of 0x0 assert failed: pcTaskGetName tasks.c:2780 (pxTCB) Stack memory: ELF file SHA256: 40509b00a E (738) esp_core_dump_flash: Core dump flash config is corrupted! CRC=0x7bd5c66f instead of 0x0 assert failed: pcTaskGetName tasks.c:2780 (pxTCB) Stack memory: ELF file SHA256: 40509b00a E (738) esp_core_dump_flash: Core dump flash config is corrupted! CRC=0x7bd5c66f instead of 0x0 |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1027 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2845 🏁 Script executed: Length of output: 1162 🏁 Script executed: Length of output: 0 @ewowi, Found it! The crash is caused by line 352 in main.cpp: xTaskCreateUniversal(driverTask,
"AppDrivers",
psramFound() ? 4 * 1024 : 3 * 1024,
NULL,
3,
&driverTaskHandle,
1 // ❌ Core 1 doesn't exist on ESP32-C3!
);The ProblemESP32-C3 is a single-core chip (only Core 0), while ESP32-S3/P4/D0 are dual-core. When you try to create a task on Core 1 on a single-core chip, FreeRTOS fails the assertion The FixUse conditional compilation to handle single-core vs multi-core chips: void setup() {
// ... existing setup code ...
// 🌙 Create tasks with core affinity handling
xTaskCreateUniversal(effectTask,
"AppEffects",
psramFound() ? 4 * 1024 : 3 * 1024,
NULL,
3,
&effectTaskHandle,
#ifdef CONFIG_FREERTOS_UNICORE
0 // Single-core: use Core 0
#else
0 // Multi-core: protocol core (high speed effect processing)
#endif
);
xTaskCreateUniversal(driverTask,
"AppDrivers",
psramFound() ? 4 * 1024 : 3 * 1024,
NULL,
3,
&driverTaskHandle,
#ifdef CONFIG_FREERTOS_UNICORE
0 // Single-core: use Core 0 (only option)
#else
1 // Multi-core: application core (avoid WiFi interference)
#endif
);
#endif
}Alternative: Use tskNO_AFFINITYIf you want to let FreeRTOS decide (simpler but less optimal): xTaskCreateUniversal(driverTask,
"AppDrivers",
psramFound() ? 4 * 1024 : 3 * 1024,
NULL,
3,
&driverTaskHandle,
#ifdef CONFIG_FREERTOS_UNICORE
tskNO_AFFINITY // Let scheduler choose
#else
1 // Pin to Core 1 on multi-core
#endif
);Why This Wasn't Caught EarlierThe recent PR changes (moving tasks between cores) work perfectly on dual-core chips but broke single-core compatibility. The ESP32-C3 configuration doesn't include any special handling for single-core operation. VerificationAfter applying the fix:
Apply this fix and the boot loop should be resolved! 🐰🔧 🧠 Learnings used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
lib/framework/StatefulService.h (1)
198-213: Fix mutex acquisition:beginTransaction()can proceed without owning the mutex, butendTransaction()always releases it.If both timed
xSemaphoreTakeRecursive()calls fail (Line 201-207), the code continues without the lock and later callsxSemaphoreGiveRecursive(_accessMutex)anyway (Line 212). That’s a correctness/concurrency bug (can assert or corrupt mutex state; also unprotected access).Proposed fix (keep logging, but guarantee acquisition)
inline void beginTransaction(const String &originId) // 🌙 Add originId { // 🌙 adding semaphore wait too long logging - if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(100))==pdFALSE) { + if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(100)) == pdFALSE) { ESP_LOGI("🐼", "_accessMutex %s wait 100ms", originId.c_str()); - if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(400))==pdFALSE) { + if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(400)) == pdFALSE) { ESP_LOGW("🐼", "_accessMutex %s waited 500ms and continues", originId.c_str()); - // xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); + // Must not proceed without owning the mutex. + xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); } } }-->
lib/framework/EventSocket.cpp (1)
176-243: Minimize mutex hold time during send operations.The mutex is currently held for the entire duration of the send loop, including all client->sendMessage() calls. These I/O operations can block (especially with slow clients or full send buffers), causing thread contention and potentially contributing to watchdog timeouts.
As discussed in the PR objectives, the recommended pattern is to copy the subscription list under mutex protection, release the mutex, then perform sends without holding the lock.
♻️ Proposed copy-subscriptions pattern
int originSubscriptionId = originId[0] ? atoi(originId) : -1; - // 🌙 adding semaphore wait too long logging + + // Copy subscriptions under mutex, then release before sending + std::vector<int> subsToSend; if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(100))==pdFALSE) { ESP_LOGW(SVK_TAG, "clientSubscriptionsMutex wait too long"); xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); } auto &subscriptions = client_subscriptions[event]; if (subscriptions.empty()) { xSemaphoreGive(clientSubscriptionsMutex); return; } + + // Copy subscription IDs + if (onlyToSameOrigin && originSubscriptionId > 0) { + subsToSend.push_back(originSubscriptionId); + } else { + for (int sub : subscriptions) { + if (sub != originSubscriptionId) { + subsToSend.push_back(sub); + } + } + } + xSemaphoreGive(clientSubscriptionsMutex); + + // Now send without holding mutex + std::vector<int> deadClients; + for (int subscription : subsToSend) { + auto *client = _socket.getClient(subscription); + if (!client) { + deadClients.push_back(subscription); + continue; + } + if (event != "monitor") + ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s", event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output); +#if FT_ENABLED(EVENT_USE_JSON) + esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len); +#else + esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len); +#endif + if (result != ESP_OK) { + ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %zu)", event.c_str(), originId, subscription, esp_err_to_name(result), len); + } + } + + // Clean up dead clients with brief mutex reacquisition + if (!deadClients.empty()) { + if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(100))==pdFALSE) { + ESP_LOGW(SVK_TAG, "clientSubscriptionsMutex wait too long"); + xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); + } + for (int deadClient : deadClients) { + subscriptions.remove(deadClient); + } + xSemaphoreGive(clientSubscriptionsMutex); + } - // if onlyToSameOrigin == true, send the message back to the origin - if (onlyToSameOrigin && originSubscriptionId > 0) - { - // ... existing code ... - } - else - { - // ... existing code ... - } - - xSemaphoreGive(clientSubscriptionsMutex);This approach:
- Minimizes mutex hold time to just copying subscription IDs
- Performs all I/O without blocking other threads
- Cleans up dead clients with a brief mutex reacquisition
Based on PR objectives discussion of EventSocket::emitEvent iterator fixes and mutex contention.
🤖 Fix all issues with AI agents
In @lib/framework/EventSocket.cpp:
- Around line 211-224: The loop over subscriptions uses erase(it) then --it and
relies on the header ++it which is non-idiomatic and fragile; change the loop to
the standard erase-while-iterating pattern by removing the ++it from the
for-header (e.g., use for (auto it = subscriptions.begin(); it !=
subscriptions.end(); ) or a while loop) and explicitly advance the iterator in
each branch: after erasing set it = subscriptions.erase(it) (which returns the
next iterator) and continue, otherwise ++it; keep checks involving
originSubscriptionId and _socket.getClient(subscription) intact so behavior is
unchanged.
🧹 Nitpick comments (2)
lib/framework/StatefulService.h (1)
165-177: Consider letting callers pass a realoriginIdfor reads (and avoid per-callStringtemporaries).Hardcoding
beginTransaction("read")(Line 167, 174) loses caller attribution and constructs a temporaryStringeach time. If origin tracking matters, add an overload likeread(..., const String& originId = "read")(or acceptconst char*inbeginTransaction).-->
lib/framework/EventSocket.cpp (1)
204-204: Consider cleaning up commented-out subscription removal code.The commented-out
subscriptions.remove()and erase logic indicates deferred decisions about how to handle send failures. The inline comment "we hope for better times" suggests clients that experience send failures are retained with the expectation they'll recover.Consider either:
- Removing the commented code entirely if the decision is firm, or
- Implementing per-client failure counters to remove clients after N consecutive failures
Also applies to: 237-239
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
lib/framework/ESP32SvelteKit.cpplib/framework/EventSocket.cpplib/framework/StatefulService.hplatformio.ini
🚧 Files skipped from review as they are similar to previous changes (1)
- platformio.ini
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
📚 Learning: 2026-01-04T09:31:01.321Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:01.321Z
Learning: In lib/framework/ESP32SvelteKit.cpp, retain a 6 KB (6 * 1024 bytes) stack size for the loop task on non-PSRAM ESP32 devices. The tested configuration shows ~1500 bytes free under load, providing about 24% headroom. Verify this stack sizing when reviewing related ESP32 loop-task implementations for non-PSRAM scenarios to ensure reliable task execution under expected load.
Applied to files:
lib/framework/ESP32SvelteKit.cpp
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
lib/framework/ESP32SvelteKit.cpp
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
lib/framework/ESP32SvelteKit.cpp
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
lib/framework/ESP32SvelteKit.cpp
📚 Learning: 2026-01-08T11:39:52.862Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Applied to files:
lib/framework/EventSocket.cpp
🧬 Code graph analysis (2)
lib/framework/EventSocket.cpp (1)
lib/PsychicHttp/src/PsychicWebSocket.cpp (2)
client(17-19)client(17-17)
lib/framework/StatefulService.h (4)
src/MoonBase/Module.cpp (4)
update(271-302)update(271-271)read(63-67)read(63-63)lib/framework/MqttSettingsService.h (2)
update(109-120)read(96-107)lib/framework/NTPSettingsService.h (2)
update(66-73)read(58-64)lib/framework/WiFiSettingsService.h (1)
read(90-224)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (7)
lib/framework/StatefulService.h (1)
123-164: Origin propagation tobeginTransaction()call sites looks consistent.All
update*overloads now passoriginIdinto the transaction boundary (Line 125, 138, 146, 159), which is what you want for the new lock-wait logging and origin-aware debugging.-->
lib/framework/ESP32SvelteKit.cpp (1)
233-237: Good fix for single-core ESP32-C3 support.The conditional core affinity correctly addresses the boot/assert failures on single-core devices. On unicore builds, Core 0 is the only valid option; on dual-core, the task is pinned to Core 1 (APP_CPU) as configured via
ESP32SVELTEKIT_RUNNING_CORE=1in platformio.ini, which avoids WiFi/BT preemption on Core 0.Optional refinement: Consider using
tskNO_AFFINITYfor the unicore case for semantic clarity (though functionally equivalent since only one core exists):Alternative with tskNO_AFFINITY
#ifdef CONFIG_FREERTOS_UNICORE - 0 // Single-core: use Core 0 (only option) + tskNO_AFFINITY // Single-core: scheduler decides (only one core) #else ESP32SVELTEKIT_RUNNING_CORE // Pin to application core #endiflib/framework/EventSocket.cpp (5)
13-13: LGTM - Client info event setup is correct.The EVENT_CLIENT_INFO macro and handler registration properly wire incoming client_info messages to the handleClientInfo method. This enables the Page Visibility API integration discussed in the PR objectives.
Based on learnings, this addresses the hidden-tab throttling issue by allowing clients to signal their visibility state.
Also applies to: 23-28
295-307: LGTM - Client visibility tracking is correctly implemented.The handleClientInfo method properly:
- Extracts visibility state with safe defaults
- Protects _clientVisibility map access with mutex
- Logs state changes at appropriate level
This integrates with the Page Visibility API on the client side to track which browser tabs are active.
309-321: LGTM - Active client counting is correct.The getActiveClients method correctly:
- Protects shared state access with mutex
- Counts only clients marked as visible
- Returns the count for use by modules to gate UI/monitor emissions
Based on learnings, this enables modules to skip monitor emission when no active clients are present, preventing the buffer overflow and backlog issues with hidden browser tabs.
62-62: LGTM - Visibility cleanup on disconnect is correct.The _clientVisibility cleanup properly removes the client's visibility state when they disconnect, maintaining consistency with the subscription cleanup.
170-170: LGTM - Enhanced logging improves debuggability.The additional context (originId, len, esp_err_to_name) in log messages will help diagnose send failures and protocol issues, especially useful for troubleshooting the large monitor frame scenarios discussed in the PR objectives.
Also applies to: 203-203, 236-236
| // 🌙 use iterator so remove / erase also removes from the iterator | ||
| for (auto it = subscriptions.begin(); it != subscriptions.end(); ++it) | ||
| { | ||
| int subscription = *it; | ||
| if (subscription == originSubscriptionId) | ||
| { | ||
| ++it; | ||
| continue; | ||
| } | ||
| auto *client = _socket.getClient(subscription); | ||
| if (!client) | ||
| { | ||
| it = subscriptions.erase(it); | ||
| --it; // do not advance because of erase | ||
| continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Refactor iterator pattern to follow standard erase idiom.
The current pattern uses --it after erase(it) to compensate for the automatic advancement, then relies on the loop's ++it to re-advance. While this technically works, it's non-idiomatic and fragile.
The standard pattern is clearer and less error-prone:
♻️ Recommended iterator pattern
- // 🌙 use iterator so remove / erase also removes from the iterator
- for (auto it = subscriptions.begin(); it != subscriptions.end(); ++it)
+ for (auto it = subscriptions.begin(); it != subscriptions.end(); )
{
int subscription = *it;
if (subscription == originSubscriptionId)
{
+ ++it;
continue;
}
auto *client = _socket.getClient(subscription);
if (!client)
{
it = subscriptions.erase(it);
- --it; // do not advance because of erase
continue;
}
if (event != "monitor")
ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s", event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output);
#if FT_ENABLED(EVENT_USE_JSON)
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
#else
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
#endif
// 🌙 error check
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %zu)", event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
// it = subscriptions.erase(it);// do not erase as we hope for better times
// --it; // do not advance because of erase
}
+ ++it;
}This removes the increment from the loop header and handles advancement explicitly in each branch, which is the idiomatic pattern when erasing during iteration.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @lib/framework/EventSocket.cpp around lines 211 - 224, The loop over
subscriptions uses erase(it) then --it and relies on the header ++it which is
non-idiomatic and fragile; change the loop to the standard erase-while-iterating
pattern by removing the ++it from the for-header (e.g., use for (auto it =
subscriptions.begin(); it != subscriptions.end(); ) or a while loop) and
explicitly advance the iterator in each branch: after erasing set it =
subscriptions.erase(it) (which returns the next iterator) and continue,
otherwise ++it; keep checks involving originSubscriptionId and
_socket.getClient(subscription) intact so behavior is unchanged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/MoonLight/Nodes/Effects/E_MoonLight.h (1)
214-288: Missing switch cases for new presets — user selection of "Connected 🛜", "Active 🛜", or "Free memory" will show incorrect/default output.The preset dropdown has 11 values (indices 0-10), but the switch only handles cases 1-8. Additionally, case 8 currently shows Free Heap, which should correspond to preset 10 ("Free memory"), not preset 8 ("Connected 🛜").
Missing implementations:
- Case 8: Should display "Connected 🛜" logic (currently shows Free Heap incorrectly)
- Case 9: Should display "Active 🛜" logic (e.g.,
sharedData.activeClients)- Case 10: Should display "Free memory" (move current case 8 code here)
🐛 Proposed fix
case 7: text.format("%d%d-%d", sharedData.clientListSize, sharedData.connectedClients, sharedData.activeClients); break; case 8: + text.format("%dC", sharedData.connectedClients); // or appropriate "Connected" display + break; + case 9: + text.format("%dA", sharedData.activeClients); // "Active" clients display + break; + case 10: text.format("%dK", ESP.getFreeHeap() / 1024); break; }Also update
nrOfChoicesif you want auto-mode to cycle through more options:-#define nrOfChoices 7 +#define nrOfChoices 9lib/framework/StatefulService.h (1)
198-208: Critical: Proceeding without mutex after timeout causes data races.After failing to acquire
_accessMutexwithin 500ms, the code logs a warning and continues execution without the lock. ThexSemaphoreTakeRecursiveon line 205 is commented out, meaningbeginTransactionreturns without holding the mutex. Subsequent state access/modification instateReaderorstateUpdaterwill be unprotected, potentially causing data corruption.Either the operation should fail/retry, or the mutex must be acquired (even if waiting longer).
🔒 Suggested fix: Either block indefinitely or return an error
Option 1: Block indefinitely after warning (safer)
inline void beginTransaction(const String &originId) { // 🌙 adding semaphore wait too long logging if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(100))==pdFALSE) { ESP_LOGI("🐼", "_accessMutex %s wait 100ms", originId.c_str()); if (xSemaphoreTakeRecursive(_accessMutex, pdMS_TO_TICKS(400))==pdFALSE) { ESP_LOGW("🐼", "_accessMutex %s waited 500ms and continues", originId.c_str()); - // xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); + xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); // Must acquire before proceeding } } }Option 2: Return bool and handle failure at call sites (more invasive refactor)
🤖 Fix all issues with AI agents
In @lib/framework/EventSocket.cpp:
- Around line 212-217: In the loop inside EventSocket.cpp where you check if
(!client), do not decrement the iterator after erase; instead assign the
returned iterator from subscriptions.erase(it) back to it and continue (i.e., it
= subscriptions.erase(it); continue;) so you avoid undefined behavior when
erasing the first element of the list and correctly advance to the next element.
🧹 Nitpick comments (3)
src/MoonLight/Modules/ModuleLiveScripts.h (2)
46-88: Unusedindexvariable.The
indexvariable is initialized at line 48 and incremented in both the effects loop (line 65) and drivers loop (line 85), but it is never used for any purpose. This appears to be dead code.🧹 Suggested fix: Remove unused variable
- uint8_t index = 0; _moduleEffects->read( [&](ModuleState& effectsState) { for (JsonObject nodeState : effectsState.data["nodes"].as<JsonArray>()) { if (updatedItem == nodeState["name"]) { EXT_LOGD(ML_TAG, "updateHandler equals current item -> livescript compile %s", updatedItem.c_str()); LiveScriptNode* liveScriptNode = (LiveScriptNode*)_moduleEffects->findLiveScriptNode(nodeState["name"]); if (liveScriptNode) { liveScriptNode->compileAndRun(); // wait until setup has been executed? _moduleEffects->requestUIUpdate = true; // update the Effects UI } EXT_LOGD(ML_TAG, "update due to new node %s done", nodeState["name"].as<const char*>()); } - index++; } }, originId); _moduleDrivers->read( [&](ModuleState& driversState) { for (JsonObject nodeState : driversState.data["nodes"].as<JsonArray>()) { if (updatedItem == nodeState["name"]) { EXT_LOGD(ML_TAG, "updateHandler equals current item -> livescript compile %s", updatedItem.c_str()); LiveScriptNode* liveScriptNode = (LiveScriptNode*)_moduleDrivers->findLiveScriptNode(nodeState["name"]); if (liveScriptNode) { liveScriptNode->compileAndRun(); // wait until setup has been executed? _moduleDrivers->requestUIUpdate = true; // update the Effects UI } EXT_LOGD(ML_TAG, "update due to new node %s done", nodeState["name"].as<const char*>()); } - index++; } }, originId);
80-80: Misleading comment.The comment says "update the Effects UI" but this code is in the drivers block. Should be "update the Drivers UI".
src/main.cpp (1)
296-299: Consider removing "dev:" prefix from error log.The "dev:" prefix in the error message appears to be a development marker. For production code, consider a cleaner error message.
- EXT_LOGE(ML_TAG, "dev: Failed to allocate shared routers"); + EXT_LOGE(ML_TAG, "Failed to allocate shared routers");
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
lib/framework/EventEndpoint.hlib/framework/EventSocket.cpplib/framework/FSPersistence.hlib/framework/HttpEndpoint.hlib/framework/MqttEndpoint.hlib/framework/StatefulService.hlib/framework/WebSocketServer.hsrc/MoonBase/NodeManager.hsrc/MoonBase/SharedEventEndpoint.hsrc/MoonBase/SharedHttpEndpoint.hsrc/MoonBase/SharedWebSocketServer.hsrc/MoonLight/Modules/ModuleDrivers.hsrc/MoonLight/Modules/ModuleLightsControl.hsrc/MoonLight/Modules/ModuleLiveScripts.hsrc/MoonLight/Nodes/Drivers/D_Infrared.hsrc/MoonLight/Nodes/Effects/E_MoonLight.hsrc/main.cpp
✅ Files skipped from review due to trivial changes (1)
- src/MoonLight/Nodes/Drivers/D_Infrared.h
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
src/MoonLight/Modules/ModuleLightsControl.hsrc/main.cppsrc/MoonLight/Nodes/Effects/E_MoonLight.h
📚 Learning: 2026-01-06T15:24:29.770Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:212-221
Timestamp: 2026-01-06T15:24:29.770Z
Learning: In VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking for unmapped lights (the else branch around lines 212-221) is intentionally disabled for performance when driving 100K+ LEDs. The design places responsibility on effect developers to stay within maxChannels. This is an explicit performance vs safety tradeoff for extreme LED counts.
Applied to files:
src/MoonLight/Modules/ModuleLightsControl.hsrc/main.cpp
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-08T10:20:09.363Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
Applied to files:
src/main.cpp
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/src/**/*.{h,cpp} : Modify C++ backend code in the `src/` and `lib/` directories for ESP32 development
Applied to files:
src/main.cpp
📚 Learning: 2026-01-08T11:39:52.862Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Applied to files:
src/main.cpplib/framework/EventSocket.cpp
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/lib/framework/**/*.{h,cpp} : Leverage the framework services in `lib/framework/` for WiFi management, MQTT, NTP, security, firmware updates, and system utilities
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-06T15:24:17.118Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:164-171
Timestamp: 2026-01-06T15:24:17.118Z
Learning: In VirtualLayer::setLight and VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking in the unmapped path (when indexV >= mappingTableSize or oneToOneMapping is true) is intentionally disabled for performance when driving very large LED counts (100K+). Effect developers are responsible for ensuring their code stays within bounds. This is an explicit design decision prioritizing performance over defensive checks in the hot path.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-04T09:31:07.533Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:07.533Z
Learning: In lib/framework/ESP32SvelteKit.cpp, the SvelteKit loop task stack size of 6KB (6 * 1024 bytes) for non-PSRAM devices has been validated through testing, showing approximately 1500 bytes of free stack remaining under load, providing adequate ~24% headroom.
Applied to files:
src/main.cpp
📚 Learning: 2025-12-18T15:15:57.828Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-18T15:15:57.828Z
Learning: In DripEffect (E_WLED.h), dropsSize must be set to layer->size.y (not layer->size.x) in onSizeChanged(), because the drops array is allocated as [layer->size.y][maxNumDrops]. Using layer->size.x causes out-of-bounds access when iterating over y-coordinates.
Applied to files:
src/MoonLight/Nodes/Effects/E_MoonLight.h
🧬 Code graph analysis (9)
src/MoonLight/Modules/ModuleLiveScripts.h (2)
src/MoonBase/Modules/FileManager.h (1)
FilesState(36-44)src/MoonBase/Module.h (1)
ModuleState(43-133)
src/MoonLight/Modules/ModuleLightsControl.h (1)
lib/framework/StatefulService.h (2)
read(165-170)read(172-177)
src/MoonBase/NodeManager.h (5)
src/MoonBase/Modules/FileManager.h (1)
FilesState(36-44)src/MoonBase/Char.h (2)
c_str(111-111)equal(163-168)lib/framework/FSPersistence.h (1)
readFromFS(44-67)src/MoonBase/Module.h (1)
readFromFS(195-197)src/MoonBase/SharedFSPersistence.h (1)
readFromFS(77-99)
lib/framework/EventSocket.cpp (1)
lib/PsychicHttp/src/PsychicWebSocket.cpp (2)
client(17-19)client(17-17)
lib/framework/StatefulService.h (6)
src/MoonBase/Module.cpp (4)
update(271-302)update(271-271)read(63-67)read(63-63)lib/framework/EthernetSettingsService.h (2)
update(74-101)read(62-72)lib/framework/MqttSettingsService.h (2)
update(109-120)read(96-107)lib/framework/APSettingsService.h (2)
update(115-144)read(102-113)lib/framework/NTPSettingsService.h (2)
update(66-73)read(58-64)lib/framework/WiFiSettingsService.h (1)
read(90-224)
src/MoonBase/SharedEventEndpoint.h (4)
src/MoonBase/Module.h (1)
ModuleState(43-133)lib/framework/StatefulService.h (2)
read(165-170)read(172-177)src/MoonBase/Module.cpp (2)
read(63-67)read(63-63)lib/framework/APSettingsService.h (1)
read(102-113)
src/MoonBase/SharedWebSocketServer.h (3)
src/MoonBase/Module.h (1)
ModuleState(43-133)lib/framework/StatefulService.h (2)
read(165-170)read(172-177)src/MoonBase/Module.cpp (2)
read(63-67)read(63-63)
src/MoonBase/SharedHttpEndpoint.h (2)
src/MoonBase/Module.h (1)
ModuleState(43-133)lib/framework/StatefulService.h (2)
read(165-170)read(172-177)
src/MoonLight/Modules/ModuleDrivers.h (2)
src/MoonLight/Nodes/Layouts/L_MoonLight.h (1)
SingleColumnLayout(398-437)src/MoonLight/Nodes/Layouts/L_SE16.h (2)
SE16Layout(15-81)LightCrafter16Layout(84-129)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (23)
src/MoonLight/Nodes/Effects/E_MoonLight.h (2)
105-141: LGTM!The 3D enhancement is well-structured. Conditional plane rendering based on axis dimension > 1 correctly handles mixed 1D/2D/3D fixtures without unnecessary drawing.
521-566: LGTM!The Y-dominant reorientation is internally consistent. Wave calculations, threshold checks, and rendering all properly reference
layer->size.y, producing correct vertical wave motion across horizontal positions.src/MoonLight/Modules/ModuleDrivers.h (3)
34-71: LGTM! Origin context threading applied correctly.The refactoring wraps pin-reading logic in a lambda passed to
ModuleIO::read(), threading_moduleNamethrough the API. The internal logic (pin parsing, array compaction, logging, map requests) remains unchanged.
94-94: LGTM! Layout replacement aligns with PR objectives.
SingleColumnLayoutcorrectly replaces references to the renamed layout class. Cross-reference withsrc/MoonLight/Nodes/Layouts/L_MoonLight.hconfirms proper definition.Also applies to: 130-130
106-112: LGTM! Board preset logic consistently threads module context.The board-specific layout registration now queries
ModuleIOstate through theread()callback pattern, passing_moduleNamefor origin tracking. The lambda captures are safe assuming synchronous execution.Also applies to: 142-148
src/MoonBase/SharedEventEndpoint.h (1)
54-54: LGTM! Origin context propagated to state read.The addition of
originIdas the third parameter tomodule->read()is consistent with the project-wide API enhancement to track event origins through state operations.lib/framework/WebSocketServer.h (1)
143-143: LGTM! WebSocket origin tracking integrated.The
originIdparameter addition enables the WebSocket server to propagate client origins through state read operations, supporting per-client event handling.src/MoonBase/SharedHttpEndpoint.h (3)
37-37: LGTM! CORS preflight handler properly gated.The OPTIONS handler for CORS preflight requests is correctly implemented and conditionally compiled. Returns 200 status as expected for preflight responses.
59-59: LGTM! Module context threaded through HTTP GET.Using
module->_moduleNameas the origin identifier is appropriate for HTTP endpoints, which lack persistent client connections. Enables module-aware state serialization.
85-85: LGTM! Module context threaded through HTTP POST response.The POST handler now threads
module->_moduleNamethrough the state read when building the response payload, consistent with the GET handler pattern.src/MoonBase/NodeManager.h (1)
44-76: LGTM! File manager integration follows origin-aware pattern.The update handler now queries
FileManagerstate through theread()callback, threadingoriginIdfor origin tracking. The internal logic (file path matching,readFromFS()invocation) remains functionally unchanged.lib/framework/MqttEndpoint.h (1)
160-160: LGTM: Consistent origin tracking in MQTT endpoint.The addition of
MQTT_ORIGIN_IDto the read call aligns with the existing usage in the update path (line 215) and correctly identifies MQTT as the origin for state reads during publish operations.lib/framework/HttpEndpoint.h (1)
69-69: LGTM: Endpoint-specific origin tracking in HTTP handlers.Using
_servicePathas the originId provides granular tracking of which HTTP endpoint triggered the read operation. This is more specific than the genericHTTP_ENDPOINT_ORIGIN_IDconstant used for update handler notifications (line 96), which appears intentional for distinguishing between endpoint-specific reads and generic HTTP update propagation.Also applies to: 102-102
src/MoonBase/SharedWebSocketServer.h (1)
107-107: LGTM: Origin propagation through module read path.The addition of
originIdto theModule::readcall correctly propagates the origin context (typicallyWEB_SOCKET_ORIGINfrom lines 58, 70) through to the module's state serialization. This aligns with the broader origin-tracking refactoring across the framework.src/main.cpp (3)
130-131: Good placement of watchdog reset before blocking mutex.Moving
esp_task_wdt_reset()to the start of the loop (beforexSemaphoreTake) is correct. This ensures the watchdog is reset before potentially blocking on mutex acquisition, preventing watchdog triggers when waiting for the lock. Based on learnings about Idle Watchdog handling.
146-156: Good TOCTOU prevention on double-buffer swap.Re-checking
isPositions == 0after re-acquiring the mutex (line 148) before performing the pointer swap is correct defensive coding. The condition could have changed while the mutex was released during the unlocked render phase.
346-357: Correct single-core (ESP32-C3) handling.The conditional core affinity using
CONFIG_FREERTOS_UNICOREproperly addresses the ESP32-C3 boot failures mentioned in PR comments. Single-core devices use Core 0 (the only option), while multi-core devices pin to Core 1 for timing-critical driver operations away from WiFi interference. Based on learnings about driver task core requirements.lib/framework/EventSocket.cpp (2)
23-28: Good addition of client visibility tracking.Registering
EVENT_CLIENT_INFOand routing tohandleClientInfoenables the client visibility feature discussed in PR comments. This addresses the browser tab throttling issue where hidden tabs cause buffer overflow. Based on learnings about the Page Visibility API solution.
287-307: Client visibility tracking implementation looks correct.The
handleClientInfoandgetActiveClientsmethods properly use mutex protection when accessing_clientVisibility. This enables modules to check for active (visible) clients before emitting expensive data like monitor frames, addressing the browser throttling issue from learnings.src/MoonLight/Modules/ModuleLightsControl.h (2)
513-517: Good: Monitor emission gated by active clients.Using
getActiveClients()instead ofgetConnectedClients()ensures monitor data is only sent to visible browser tabs, preventing the buffer overflow issue discussed in PR comments and learnings. This addresses the problem where hidden tabs causeESP_FAILfrom filled send buffers.
218-254: Pin reading logic with originId context looks correct.The
moduleIO.readnow receives_moduleNamefor origin tracking. The pin validation usingGPIO_IS_VALID_OUTPUT_GPIOandGPIO_IS_VALID_GPIOis appropriate, and UINT8_MAX as a sentinel for unconfigured pins is a valid pattern.lib/framework/EventEndpoint.h (1)
65-65: LGTM! Origin identifier correctly propagated.The addition of
originIdto theread()call maintains consistency—originIdis now threaded through both the read path (line 65) and the emit path (line 67), enabling proper origin tracking for state synchronization.lib/framework/FSPersistence.h (1)
74-74: LGTM! Filesystem origin correctly identified.Adding
_filePathas the origin identifier is semantically appropriate—it identifies the filesystem persistence layer as the source of the read operation. This usage is consistent with the existing pattern at lines 55 and 180, where_filePathis already passed asoriginIdtoupdateWithoutPropagation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
lib/framework/EventSocket.cpp (2)
204-216: Critical: Iterator double-increment bug after erase.Line 214 performs
it = subscriptions.erase(it)which returns the next valid iterator, but thencontinuecauses the loop's++itto execute, skipping an element.Impact: After removing a dead client, the next client in the subscription list is skipped and won't receive the event.
🐛 Proposed fix: Adjust iterator after erase
Option 1: Decrement after erase to compensate for loop increment:
if (!client) { it = subscriptions.erase(it); + --it; // compensate for ++it in loop header continue; }Option 2 (preferred): Use a while loop with manual iterator control:
-for (auto it = subscriptions.begin(); it != subscriptions.end(); ++it) +auto it = subscriptions.begin(); +while (it != subscriptions.end()) { int subscription = *it; if (subscription == originSubscriptionId) { + ++it; continue; } auto *client = _socket.getClient(subscription); if (!client) { it = subscriptions.erase(it); continue; } // ... send logic ... + ++it; }
171-233: Major: Mutex held during network I/O causes contention.The
clientSubscriptionsMutexis held from line 171 through line 233, encompassing all WebSocket send operations (lines 188, 220-222). Network I/O can block for milliseconds to seconds, especially when clients have full buffers.Impact: Other threads waiting on
clientSubscriptionsMutex(e.g., subscription updates, client connects/disconnects, other emit calls) are blocked during all network operations, causing contention and potentially triggering watchdog timeouts.♻️ Recommended refactor: Copy subscriptions, release mutex, then send
Based on PR objectives and learnings:
xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); auto &subscriptions = client_subscriptions[event]; if (subscriptions.empty()) { xSemaphoreGive(clientSubscriptionsMutex); return; } + +// Copy subscription IDs while holding mutex +std::vector<int> subscriptionsCopy; +subscriptionsCopy.reserve(subscriptions.size()); +for (int sub : subscriptions) { + subscriptionsCopy.push_back(sub); +} +xSemaphoreGive(clientSubscriptionsMutex); + +// Perform sends without holding mutex +std::vector<int> deadClients; -// ... existing send logic inside mutex ... +for (int subscription : subscriptionsCopy) { + if (subscription == originSubscriptionId) continue; + + auto *client = _socket.getClient(subscription); + if (!client) { + deadClients.push_back(subscription); + continue; + } + + // ... perform send ... + + if (result != ESP_OK) { + // Optionally track failures + } +} + +// Clean up dead clients with brief mutex reacquire +if (!deadClients.empty()) { + xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); + for (int dead : deadClients) { + client_subscriptions[event].remove(dead); + } + xSemaphoreGive(clientSubscriptionsMutex); +} - -xSemaphoreGive(clientSubscriptionsMutex);This approach minimizes mutex hold time to brief copy and cleanup operations, eliminating contention during network I/O.
Based on learnings, this mutex contention contributed to watchdog triggers when combined with large monitor payloads to hidden browser tabs.
🤖 Fix all issues with AI agents
In @src/MoonLight/Modules/ModuleLiveScripts.h:
- Around line 43-91: The outer _fileManager->read callback currently holds
fileManager's mutex while calling _moduleEffects->read and _moduleDrivers->read,
causing nested mutex acquisition and blocking; refactor by collecting the list
of updatedItems inside the _fileManager->read callback, then exit that
transaction and separately call a new helper that performs the
_moduleEffects->read and _moduleDrivers->read operations for each updatedItem so
their mutexes are acquired independently (allowing parallelism). Extract the
duplicated logic into a helper function (e.g.,
processUpdatedLiveScriptItem(updatedItem, ModuleStateHolder*) or a lambda) that
locates the LiveScriptNode via findLiveScriptNode, calls compileAndRun on the
node if found, and sets requestUIUpdate on the appropriate module
(_moduleEffects or _moduleDrivers); ensure logging (EXT_LOGD) remains around
compileAndRun and that nodeState["name"] comparisons use the same string access.
In @src/MoonLight/Nodes/Effects/E_MoonLight.h:
- Line 281: The format string used in the text.format call is ambiguous
("%d%d-%d"): change the format to include a separator between
sharedData.clientListSize and sharedData.connectedClients (e.g. "%d %d-%d" or
"%d,%d-%d") so values like sharedData.clientListSize,
sharedData.connectedClients, sharedData.activeClients are clearly separated when
calling text.format(sharedData.clientListSize, sharedData.connectedClients,
sharedData.activeClients).
🧹 Nitpick comments (3)
lib/framework/EventSocket.cpp (2)
195-196: Consider implementing dead-client removal strategy.The commented-out
subscriptions.remove()anderase(it)calls (lines 196, 228) suggest uncertainty about error handling for send failures.Current behavior: Failing clients remain in subscription lists indefinitely, causing repeated send attempts and ESP_FAIL logs on every emit.
Recommendation: Implement one of these strategies:
- Remove immediately on send failure (simplest): Uncomment the erase/remove calls to clean up failing clients immediately.
- Failure count threshold (as noted in PR objectives): Track per-client failure counts and remove after N consecutive failures.
- Dead client detection only: Remove only when
getClient()returns null (already done at line 214), but keep clients that return ESP_FAIL (assuming transient failures).The first strategy is simplest and aligns with the dead-client cleanup already implemented at line 214. If transient failures are expected to recover, consider strategy 2 or 3.
Also applies to: 227-228
285-306: LGTM: Client visibility tracking is correctly implemented.The
handleClientInfoandgetActiveClientsmethods properly synchronize access to_clientVisibilityusing the mutex. The logic correctly tracks per-client visibility state and counts active clients.Optional enhancement: Consider logging at
ESP_LOGVinstead ofESP_LOGD(line 293) if visibility updates occur frequently, to reduce log noise in production.src/MoonLight/Nodes/Effects/E_MoonLight.h (1)
118-139: Consider independent timing for each plane.All three planes currently use the same
beat16(bpm)timing, causing them to move in perfect synchronization. While this may be intentional, independent timing for each axis could create more visual variety.✨ Optional enhancement for independent plane timing
// vertical: red if (layer->size.x > 1) { - pos.x = ::map(beat16(bpm), 0, UINT16_MAX, 0, layer->size.x); + pos.x = ::map(beat16(bpm, 0), 0, UINT16_MAX, 0, layer->size.x); for (pos.y = 0; pos.y < layer->size.y; pos.y++) for (pos.z = 0; pos.z < layer->size.z; pos.z++) layer->setRGB(pos, CRGB::Red); } // horizontal: green if (layer->size.y > 1) { - pos.y = ::map(beat16(bpm), 0, UINT16_MAX, 0, layer->size.y); + pos.y = ::map(beat16(bpm, 21845), 0, UINT16_MAX, 0, layer->size.y); for (pos.x = 0; pos.x < layer->size.x; pos.x++) for (pos.z = 0; pos.z < layer->size.z; pos.z++) layer->setRGB(pos, CRGB::Green); } // depth: blue if (layer->size.z > 1) { - pos.z = ::map(beat16(bpm), 0, UINT16_MAX, 0, layer->size.z); + pos.z = ::map(beat16(bpm, 43690), 0, UINT16_MAX, 0, layer->size.z); for (pos.x = 0; pos.x < layer->size.x; pos.x++) for (pos.y = 0; pos.y < layer->size.y; pos.y++) layer->setRGB(pos, CRGB::Blue); }The phase offsets (0, 21845, 43690) distribute the three planes evenly across the beat cycle (0°, 120°, 240°).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
lib/framework/EventSocket.cppsrc/MoonBase/NodeManager.hsrc/MoonLight/Modules/ModuleLiveScripts.hsrc/MoonLight/Nodes/Effects/E_MoonLight.h
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
📚 Learning: 2026-01-08T11:39:52.862Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Applied to files:
lib/framework/EventSocket.cpp
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
src/MoonLight/Modules/ModuleLiveScripts.hsrc/MoonLight/Nodes/Effects/E_MoonLight.h
📚 Learning: 2026-01-06T15:24:29.770Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:212-221
Timestamp: 2026-01-06T15:24:29.770Z
Learning: In VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking for unmapped lights (the else branch around lines 212-221) is intentionally disabled for performance when driving 100K+ LEDs. The design places responsibility on effect developers to stay within maxChannels. This is an explicit performance vs safety tradeoff for extreme LED counts.
Applied to files:
src/MoonLight/Nodes/Effects/E_MoonLight.h
📚 Learning: 2025-12-18T15:15:57.828Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-18T15:15:57.828Z
Learning: In DripEffect (E_WLED.h), dropsSize must be set to layer->size.y (not layer->size.x) in onSizeChanged(), because the drops array is allocated as [layer->size.y][maxNumDrops]. Using layer->size.x causes out-of-bounds access when iterating over y-coordinates.
Applied to files:
src/MoonLight/Nodes/Effects/E_MoonLight.h
🧬 Code graph analysis (4)
lib/framework/EventSocket.cpp (1)
lib/PsychicHttp/src/PsychicWebSocket.cpp (2)
client(17-19)client(17-17)
src/MoonLight/Modules/ModuleLiveScripts.h (4)
src/MoonBase/Modules/FileManager.h (1)
FilesState(36-44)lib/framework/StatefulService.h (2)
read(165-170)read(172-177)src/MoonBase/Module.cpp (2)
read(63-67)read(63-63)src/MoonBase/Modules/FileManager.cpp (2)
read(45-56)read(45-45)
src/MoonLight/Nodes/Effects/E_MoonLight.h (1)
src/MoonLight/Nodes/Effects/E_WLED.h (32)
dim(17-17)dim(106-106)dim(180-180)dim(237-237)dim(311-311)dim(430-430)dim(461-461)dim(485-485)dim(542-542)dim(807-807)dim(974-974)dim(1082-1082)dim(1158-1158)dim(1203-1203)dim(1252-1252)dim(1305-1305)loop(44-95)loop(130-173)loop(191-229)loop(259-305)loop(347-424)loop(443-455)loop(472-479)loop(499-515)loop(632-753)loop(870-954)loop(1010-1064)loop(1097-1152)loop(1174-1197)loop(1222-1245)loop(1265-1297)loop(1367-1394)
src/MoonBase/NodeManager.h (5)
src/MoonBase/Modules/FileManager.h (1)
FilesState(36-44)src/MoonBase/Char.h (2)
c_str(111-111)equal(163-168)lib/framework/FSPersistence.h (1)
readFromFS(44-67)src/MoonBase/Module.h (1)
readFromFS(195-197)src/MoonBase/SharedFSPersistence.h (1)
readFromFS(77-99)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
src/MoonBase/NodeManager.h (1)
44-76: LGTM: Origin tracking parameter added correctly.The
originIdparameter is now properly threaded through theFileManager::readcall, consistent with the broader refactoring to track origin context across read/emit APIs throughout the codebase.lib/framework/EventSocket.cpp (2)
13-28: LGTM: Client info event registration follows established pattern.The
EVENT_CLIENT_INFOevent is correctly registered and wired tohandleClientInfo, enabling client visibility tracking.
53-60: LGTM: Cleanup logic correctly removes client visibility on disconnect.The addition of
_clientVisibility.erase()ensures that disconnected clients are properly removed from visibility tracking. TheportMAX_DELAYis acceptable in this cleanup path.src/MoonLight/Nodes/Effects/E_MoonLight.h (2)
108-108: Public API change: Effect dimensionality updated to 3D.The return value of
dim()changed from_2Dto_3D, which is a breaking change for any code that checks the dimensionality of this effect. This aligns with the new 3D rendering approach.
524-560: LGTM! Wave axis reorientation is consistent.The reorientation from X-driven to Y-driven references is correctly and consistently applied throughout:
- Position calculations switched from
size.xtosize.y- Initial
prevPosupdated tosize.y / 2- Threshold checks adjusted accordingly
The wave now progresses along the Y axis for each X column, inverting the visual orientation as intended.
| _fileManager->read( | ||
| [&](FilesState& filesState) { | ||
| // loop over all changed files (normally only one) | ||
| for (auto updatedItem : filesState.updatedItems) { | ||
| // if file is the current live script, recompile it (to do: multiple live effects) | ||
| // uint8_t index = 0; | ||
| _moduleEffects->read( | ||
| [&](ModuleState& effectsState) { | ||
| for (JsonObject nodeState : effectsState.data["nodes"].as<JsonArray>()) { | ||
| if (updatedItem == nodeState["name"]) { | ||
| EXT_LOGD(ML_TAG, "updateHandler equals current item -> livescript compile %s", updatedItem.c_str()); | ||
| LiveScriptNode* liveScriptNode = (LiveScriptNode*)_moduleEffects->findLiveScriptNode(nodeState["name"]); | ||
| if (liveScriptNode) { | ||
| liveScriptNode->compileAndRun(); | ||
|
|
||
| // wait until setup has been executed? | ||
|
|
||
| _moduleEffects->requestUIUpdate = true; // update the UI | ||
| } | ||
|
|
||
| EXT_LOGD(ML_TAG, "update due to new node %s done", nodeState["name"].as<const char*>()); | ||
| } | ||
| // index++; | ||
| } | ||
| }, | ||
| originId); | ||
| _moduleDrivers->read( | ||
| [&](ModuleState& driversState) { | ||
| for (JsonObject nodeState : driversState.data["nodes"].as<JsonArray>()) { | ||
| if (updatedItem == nodeState["name"]) { | ||
| EXT_LOGD(ML_TAG, "updateHandler equals current item -> livescript compile %s", updatedItem.c_str()); | ||
| LiveScriptNode* liveScriptNode = (LiveScriptNode*)_moduleDrivers->findLiveScriptNode(nodeState["name"]); | ||
| if (liveScriptNode) { | ||
| liveScriptNode->compileAndRun(); | ||
|
|
||
| // wait until setup has been executed? | ||
|
|
||
| _moduleDrivers->requestUIUpdate = true; // update the UI | ||
| } | ||
|
|
||
| EXT_LOGD(ML_TAG, "update due to new node %s done", nodeState["name"].as<const char*>()); | ||
| } | ||
| // index++; | ||
| } | ||
| }, | ||
| originId); | ||
| } | ||
| }); | ||
| _moduleDrivers->read([&](ModuleState& driversState) { | ||
| for (JsonObject nodeState : driversState.data["nodes"].as<JsonArray>()) { | ||
| if (updatedItem == nodeState["name"]) { | ||
| EXT_LOGD(ML_TAG, "updateHandler equals current item -> livescript compile %s", updatedItem.c_str()); | ||
| LiveScriptNode* liveScriptNode = (LiveScriptNode*)_moduleDrivers->findLiveScriptNode(nodeState["name"]); | ||
| if (liveScriptNode) { | ||
| liveScriptNode->compileAndRun(); | ||
|
|
||
| // wait until setup has been executed? | ||
|
|
||
| _moduleDrivers->requestUIUpdate = true; // update the Effects UI | ||
| } | ||
|
|
||
| EXT_LOGD(ML_TAG, "update due to new node %s done", nodeState["name"].as<const char*>()); | ||
| } | ||
| index++; | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| }, | ||
| originId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find and examine the StatefulService implementation
echo "=== Searching for StatefulService class and read method ==="
find . -type f -name "*.h" -o -name "*.cpp" | xargs grep -l "class StatefulService" | head -5
# Get the file location
STATEFUL_SERVICE=$(find . -type f \( -name "*.h" -o -name "*.cpp" \) -exec grep -l "class StatefulService" {} \;)
echo "Found StatefulService in: $STATEFUL_SERVICE"Repository: MoonModules/MoonLight
Length of output: 218
🏁 Script executed:
#!/bin/bash
# Examine StatefulService read method and transaction handling
echo "=== StatefulService read method and mutex usage ==="
rg -A 20 "void read\(" --type=cpp --type=hpp | head -80
echo ""
echo "=== Checking for mutex in StatefulService ==="
rg "mutex|Mutex" --type=cpp --type=hpp -B 2 -A 2 | grep -A 5 -B 5 "StatefulService" | head -50Repository: MoonModules/MoonLight
Length of output: 236
🏁 Script executed:
#!/bin/bash
# Verify the actual code in ModuleLiveScripts.h matches the snippet
echo "=== Examining ModuleLiveScripts.h around lines 43-91 ==="
if [ -f "src/MoonLight/Modules/ModuleLiveScripts.h" ]; then
sed -n '43,91p' src/MoonLight/Modules/ModuleLiveScripts.h
else
find . -name "ModuleLiveScripts.h" -exec sed -n '43,91p' {} +
fiRepository: MoonModules/MoonLight
Length of output: 2540
🏁 Script executed:
#!/bin/bash
# Check for nested read patterns and how they interact with mutexes
echo "=== Looking at FileManager, ModuleEffects, ModuleDrivers read patterns ==="
rg -B 5 -A 15 "class (FileManager|ModuleEffects|ModuleDrivers)" --type=cpp --type=hpp | head -100
echo ""
echo "=== Check if each module has separate mutex ==="
rg "std::mutex|recursive_mutex" --type=cpp --type=hpp -B 3 -A 1 | head -60Repository: MoonModules/MoonLight
Length of output: 261
🏁 Script executed:
#!/bin/bash
# Directly read StatefulService.h to understand read() method
echo "=== StatefulService.h structure ==="
wc -l ./lib/framework/StatefulService.hRepository: MoonModules/MoonLight
Length of output: 138
🏁 Script executed:
#!/bin/bash
# Read the read() method implementation from StatefulService
echo "=== read() method in StatefulService ==="
sed -n '1,300p' ./lib/framework/StatefulService.h | cat -nRepository: MoonModules/MoonLight
Length of output: 8619
🏁 Script executed:
#!/bin/bash
# Search specifically for read method and beginTransaction
echo "=== Looking for read and transaction methods ==="
grep -n "void read\|beginTransaction\|endTransaction\|std::lock" ./lib/framework/StatefulService.h | head -20Repository: MoonModules/MoonLight
Length of output: 980
🏁 Script executed:
#!/bin/bash
# Check file manager and module classes for mutex patterns
echo "=== FileManager mutex/lock patterns ==="
find . -name "FileManager.h" -o -name "*FileManager*" | xargs grep -l "class FileManager" | head -1 | xargs grep -C 5 "mutex\|lock"
echo ""
echo "=== ModuleEffects for mutex patterns ==="
find . -name "*ModuleEffects*" -o -name "*ModuleEffects.h" | xargs grep -l "class ModuleEffects" 2>/dev/null | head -1 | xargs grep -C 5 "mutex\|lock" 2>/dev/nullRepository: MoonModules/MoonLight
Length of output: 148
Refactor to avoid holding outer mutex while acquiring inner mutexes; also extract duplicated logic.
The nested read() calls in the outer _fileManager->read() callback acquire mutexes while the outer fileManager's _accessMutex is still held. Since StatefulService::read() holds its _accessMutex for the entire callback duration (lines 167–169 in StatefulService.h), this pattern locks: fileManager → effects → drivers sequentially, preventing parallelism during the compileAndRun() operations.
Instead, exit the fileManager transaction, then perform the effects and drivers reads independently to allow them to proceed in parallel.
Additionally, lines 49–68 (effects handling) and 69–88 (drivers handling) are nearly identical—extract into a helper to reduce duplication and maintenance risk.
♻️ Proposed refactor to reduce mutex nesting and code duplication
_fileManager->read(
[&](FilesState& filesState) {
// loop over all changed files (normally only one)
for (auto updatedItem : filesState.updatedItems) {
// Store items to process after fileManager read completes
itemsToProcess.push_back(updatedItem);
}
},
originId);
// Process items outside fileManager transaction to avoid nested mutex holds
for (auto updatedItem : itemsToProcess) {
auto processModule = [&](auto* module) {
module->read(
[&](ModuleState& moduleState) {
for (JsonObject nodeState : moduleState.data["nodes"].as<JsonArray>()) {
if (updatedItem == nodeState["name"]) {
EXT_LOGD(ML_TAG, "updateHandler equals current item -> livescript compile %s", updatedItem.c_str());
LiveScriptNode* liveScriptNode = (LiveScriptNode*)module->findLiveScriptNode(nodeState["name"]);
if (liveScriptNode) {
liveScriptNode->compileAndRun();
module->requestUIUpdate = true; // update the UI
}
EXT_LOGD(ML_TAG, "update due to new node %s done", nodeState["name"].as<const char*>());
}
}
},
originId);
};
processModule(_moduleEffects);
processModule(_moduleDrivers);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/MoonLight/Modules/ModuleLiveScripts.h around lines 43 - 91, The outer
_fileManager->read callback currently holds fileManager's mutex while calling
_moduleEffects->read and _moduleDrivers->read, causing nested mutex acquisition
and blocking; refactor by collecting the list of updatedItems inside the
_fileManager->read callback, then exit that transaction and separately call a
new helper that performs the _moduleEffects->read and _moduleDrivers->read
operations for each updatedItem so their mutexes are acquired independently
(allowing parallelism). Extract the duplicated logic into a helper function
(e.g., processUpdatedLiveScriptItem(updatedItem, ModuleStateHolder*) or a
lambda) that locates the LiveScriptNode via findLiveScriptNode, calls
compileAndRun on the node if found, and sets requestUIUpdate on the appropriate
module (_moduleEffects or _moduleDrivers); ensure logging (EXT_LOGD) remains
around compileAndRun and that nodeState["name"] comparisons use the same string
access.
| break; | ||
| case 7: | ||
| text.format("%dC", sharedData.clientListSize); | ||
| text.format("%d%d-%d", sharedData.clientListSize, sharedData.connectedClients, sharedData.activeClients); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix ambiguous number concatenation in format string.
The format string "%d%d-%d" will concatenate the first two numbers without separation, making the output ambiguous. For example, with clientListSize=10, connectedClients=5, and activeClients=3, this displays as "105-3", which is confusing.
📝 Proposed fix with clear separators
case 7:
- text.format("%d%d-%d", sharedData.clientListSize, sharedData.connectedClients, sharedData.activeClients);
+ text.format("%d/%d-%d", sharedData.clientListSize, sharedData.connectedClients, sharedData.activeClients);
break;Alternative separators: "%d %d %d" or "%d,%d,%d" depending on the desired display style.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| text.format("%d%d-%d", sharedData.clientListSize, sharedData.connectedClients, sharedData.activeClients); | |
| text.format("%d/%d-%d", sharedData.clientListSize, sharedData.connectedClients, sharedData.activeClients); |
🤖 Prompt for AI Agents
In @src/MoonLight/Nodes/Effects/E_MoonLight.h at line 281, The format string
used in the text.format call is ambiguous ("%d%d-%d"): change the format to
include a separator between sharedData.clientListSize and
sharedData.connectedClients (e.g. "%d %d-%d" or "%d,%d-%d") so values like
sharedData.clientListSize, sharedData.connectedClients, sharedData.activeClients
are clearly separated when calling text.format(sharedData.clientListSize,
sharedData.connectedClients, sharedData.activeClients).
|
@coderabbitai, after implementing the change for esp32-c3-supermini, there is still a crash and crash report looks very similar. Next to your proposed change of the drivers task I also did this for the sveltekit task (look for CONFIG_FREERTOS_UNICORE). Just to be sure, is this really the problem? Isn't xTaskCreateUniversal smart enough to not allocate to core 1 if there is only one core? assert failed: pcTaskGetName tasks.c:2780 (pxTCB) Stack memory: ELF file SHA256: 71ee73863 E (738) esp_core_dump_flash: Core dump flash config is corrupted! CRC=0x7bd5c66f instead of 0x0 assert failed: pcTaskGetName tasks.c:2780 (pxTCB) Stack memory: ELF file SHA256: 71ee73863 E (738) esp_core_dump_flash: Core dump flash config is corrupted! CRC=0x7bd5c66f instead of 0x0 assert failed: pcTaskGetName tasks.c:2780 (pxTCB) Stack memory: ELF file SHA256: 71ee73863 E (738) esp_core_dump_flash: Core dump flash config is corrupted! CRC=0x7bd5c66f instead of 0x0 |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1253 🏁 Script executed: Length of output: 684 🏁 Script executed: Length of output: 2387 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 900 🏁 Script executed: Length of output: 27 🏁 Script executed: Length of output: 1747 @ewowi, I found the root cause! The Problems1. CONFIG_FREERTOS_UNICORE Not DefinedLooking at 2. DownloadFirmwareService Has Hardcoded Core 1// DownloadFirmwareService.cpp line ~15
xTaskCreatePinnedToCore(
&updateTask,
"Update",
OTA_TASK_STACK_SIZE,
urlPtr,
(configMAX_PRIORITIES - 1),
NULL,
1 // ❌ HARDCODED CORE 1 - no ifdef protection!
)This OTA task creation always pins to Core 1, causing crashes on ESP32-C3! 3. About xTaskCreateUniversalNo, // In ESP-IDF task.h:
#define xTaskCreateUniversal(...) xTaskCreatePinnedToCore(__VA_ARGS__)If you pass core 1 to a single-core chip, it fails with the exact assertion you're seeing. The Complete Fix1. Add CONFIG_FREERTOS_UNICORE to ESP32-C3 Buildfirmware/esp32-c3.ini: [esp32-c3-base]
build_flags =
${env.build_flags}
${moonlight.build_flags}
${HP_ALL_DRIVERS.build_flags}
-D CONFIG_IDF_TARGET_ESP32C3
-D CONFIG_FREERTOS_UNICORE # ✅ ADD THIS LINE
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=12. Fix DownloadFirmwareService.cpplib/framework/DownloadFirmwareService.cpp: if (xTaskCreatePinnedToCore(
&updateTask,
"Update",
OTA_TASK_STACK_SIZE,
urlPtr,
(configMAX_PRIORITIES - 1),
NULL,
#ifdef CONFIG_FREERTOS_UNICORE
0 // Single-core: use Core 0
#else
1 // Multi-core: application core
#endif
) != pdPASS)3. Verify Your main.cpp ChangesYour driverTask and ESP32SvelteKit task already have the ifdef, but they only work if CONFIG_FREERTOS_UNICORE is defined. Once you add it to the build flags, they'll work correctly. Verify effectTask - it should probably also use ifdef (though Core 0 works on both single and multi-core): xTaskCreateUniversal(effectTask,
"AppEffects",
psramFound() ? 4 * 1024 : 3 * 1024,
NULL,
3,
&effectTaskHandle,
#ifdef CONFIG_FREERTOS_UNICORE
0 // Single-core: use Core 0
#else
0 // Multi-core: protocol core (computation)
#endif
);About PinnedToCore vs UniversalThey're equivalent - use whichever you prefer for consistency:
Both require the core parameter to be valid for the chip. Neither handles single-core automatically. Summary
The crash will be resolved once 🐰🔧 🧠 Learnings used |
|
@coderabbitai, regarding
Are you sure? Because when I select the esp32-c3 envs in vsvode/platformio I see the if part becomes highlighted without needing to specify it in esp32-c3.ini |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 3518 🏁 Script executed: Length of output: 900 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 @ewowi, you're absolutely right! The IDE highlighting confirms that The Real Problem: DownloadFirmwareServiceI found the actual bug! DownloadFirmwareService.cpp has a hardcoded Core 1 with NO ifdef protection: // lib/framework/DownloadFirmwareService.cpp line ~84
if (xTaskCreatePinnedToCore(
&updateTask,
"Update",
OTA_TASK_STACK_SIZE,
urlPtr,
(configMAX_PRIORITIES - 1),
NULL,
1 // ❌ HARDCODED CORE 1 - crashes on ESP32-C3!
) != pdPASS)This task gets created when:
The Fixlib/framework/DownloadFirmwareService.cpp: if (xTaskCreatePinnedToCore(
&updateTask,
"Update",
OTA_TASK_STACK_SIZE,
urlPtr,
(configMAX_PRIORITIES - 1),
NULL,
#ifdef CONFIG_FREERTOS_UNICORE
0 // ✅ Single-core: use Core 0
#else
1 // Multi-core: application core
#endif
) != pdPASS)Summary
Only DownloadFirmwareService needs to be fixed. Apply the ifdef to that one file and the ESP32-C3 crash should be resolved. Your IDE's IntelliSense was correct - the define exists. The problem is that DownloadFirmwareService doesn't use it! 🐰🔍 🧠 Learnings used |
|
@coderabbitai, regarding
I doubt this is the case rabbit. OTA update or download firmware was not triggered and there is an update check but that doesn't call downloadUpdate() . Please do your analysis again and better this time |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 5680 🏁 Script executed: Length of output: 1345 @ewowi, you're absolutely right - I apologize for jumping to the wrong conclusion about DownloadFirmwareService. Let me provide a proper analysis. AnalysisLooking at the task creation calls at boot:
All three appear correctly protected. Yet the crash persists with the exact same signature: Possible Causes1. Stale Build CacheThe changes might not be compiled into the binary. Try: pio run -t clean -e esp32-c3
pio run -e esp32-c32. Module Constructors Creating TasksSome module might be creating a task in its constructor (before # Search for task creation in module constructors
rg "xTaskCreate" src/MoonLight --type=cpp -B10 | grep -E "(class |::.*\(|xTaskCreate)"3. Static InitializationCheck if there's any static object initialization that creates tasks: # Look for static objects that might create tasks
rg "static.*Task|xTaskCreate" src/MoonLight src/MoonBase --type=cpp -C5Diagnostic Steps
Can you add these diagnostics and tell me:
This will help us pinpoint exactly which task creation is causing the issue. 🐰🔍 🧠 Learnings used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
lib/framework/EventSocket.cpp (2)
203-231: Critical: infinite loop when skippingoriginSubscriptionId(missing iterator advance)
Ifsubscription == originSubscriptionId, the loopcontinues without incrementingit, so it can spin forever under the mutex (exact failure mode discussed in PR objectives).Proposed fix
for (auto it = subscriptions.begin(); it != subscriptions.end();) { int subscription = *it; if (subscription == originSubscriptionId) { - continue; + ++it; + continue; } auto *client = _socket.getClient(subscription); if (!client) { it = subscriptions.erase(it); continue; } ... if (result != ESP_OK) { ESP_LOGW(...); // it = subscriptions.erase(it);// do not erase as we hope for better times } ++it; }
203-235: Don’t holdclientSubscriptionsMutexduring websocket sends (still blocks other tasks / callbacks)
Even with the iterator fix, this function still performsclient->sendMessage(...)while holdingclientSubscriptionsMutex, reintroducing the mutex-contention + watchdog/starvation scenario. Recommend: copy subscriber socket IDs under lock → release lock → send → re-lock briefly to erase dead sockets / apply failure policy. (Based on learnings, this is especially important for large monitor frames and hidden tabs.)
🤖 Fix all issues with AI agents
In @lib/framework/EventSocket.cpp:
- Around line 284-307: getActiveClients() currently only counts entries in
_clientVisibility so new sockets that haven’t sent client_info are treated as
not active; initialize visibility true for new connections in onWSOpen (set
_clientVisibility[originId]=true while holding clientSubscriptionsMutex) and/or
change getActiveClients() to treat missing map entries as visible (count them as
active when key not found), and update emission logic to skip monitor sends
per-client when _clientVisibility[socket]==false rather than using a global
gate; use the existing clientSubscriptionsMutex when reading/writing
_clientVisibility and reference the functions EventSocket::onWSOpen,
EventSocket::handleClientInfo and EventSocket::getActiveClients to locate
changes.
- Around line 53-60: The close path is blocking because emitEvent() holds
clientSubscriptionsMutex while doing network I/O; update emitEvent() so it locks
clientSubscriptionsMutex only to copy the relevant subscription list (from
client_subscriptions) into a local container, then immediately
xSemaphoreGive(clientSubscriptionsMutex) before performing sends; after sends,
briefly xSemaphoreTake the mutex again only to remove any dead/closed sockets
from client_subscriptions and update _clientVisibility. Reference
clientSubscriptionsMutex, client_subscriptions, emitEvent(), and the removal
logic currently in onWSClose() so the send loop never runs while the mutex is
held.
- Around line 166-172: The code assumes originId is non-null when logging and
when computing originSubscriptionId; add a null check and safe fallback string
to avoid dereferencing nullptr: before using originId in ESP_LOGW and in the
ternary expression that sets originSubscriptionId, ensure originId is checked
(e.g. use originId ? originId : "<null>" in the log) and compute
originSubscriptionId with a guarded expression like (originId && originId[0]) ?
atoi(originId) : -1; keep the rest of the block (xSemaphoreTake,
client_subscriptions access) the same.
🧹 Nitpick comments (1)
lib/framework/EventSocket.cpp (1)
13-29: ExposeEVENT_CLIENT_INFOconsistently (header) and avoid “register in begin()” surprises
DefiningEVENT_CLIENT_INFOin the.cppmakes it easy to diverge from any header declaration / other TUs. If this is intended as a public event name, prefer a single definition inEventSocket.h(or aconstexpr), and keep the.cppfree of “public” macros.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
lib/framework/DownloadFirmwareService.cpplib/framework/EventSocket.cppsrc/main.cpp
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
📚 Learning: 2026-01-08T11:39:52.862Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Applied to files:
lib/framework/EventSocket.cppsrc/main.cpp
📚 Learning: 2026-01-06T13:45:03.067Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Applied to files:
src/main.cpp
📚 Learning: 2025-12-28T10:28:24.157Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:34:40.097Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-08T10:20:09.363Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
Applied to files:
src/main.cpp
📚 Learning: 2025-12-25T13:31:32.367Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:31:32.367Z
Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-06T15:24:29.770Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:212-221
Timestamp: 2026-01-06T15:24:29.770Z
Learning: In VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking for unmapped lights (the else branch around lines 212-221) is intentionally disabled for performance when driving 100K+ LEDs. The design places responsibility on effect developers to stay within maxChannels. This is an explicit performance vs safety tradeoff for extreme LED counts.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-06T15:24:17.118Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: src/MoonLight/Layers/VirtualLayer.cpp:164-171
Timestamp: 2026-01-06T15:24:17.118Z
Learning: In VirtualLayer::setLight and VirtualLayer::getLight (src/MoonLight/Layers/VirtualLayer.cpp), bounds checking in the unmapped path (when indexV >= mappingTableSize or oneToOneMapping is true) is intentionally disabled for performance when driving very large LED counts (100K+). Effect developers are responsible for ensuring their code stays within bounds. This is an explicit design decision prioritizing performance over defensive checks in the hot path.
Applied to files:
src/main.cpp
📚 Learning: 2026-01-04T09:31:07.533Z
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 93
File: lib/framework/ESP32SvelteKit.cpp:229-229
Timestamp: 2026-01-04T09:31:07.533Z
Learning: In lib/framework/ESP32SvelteKit.cpp, the SvelteKit loop task stack size of 6KB (6 * 1024 bytes) for non-PSRAM devices has been validated through testing, showing approximately 1500 bytes of free stack remaining under load, providing adequate ~24% headroom.
Applied to files:
src/main.cpp
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/lib/framework/**/*.{h,cpp} : Leverage the framework services in `lib/framework/` for WiFi management, MQTT, NTP, security, firmware updates, and system utilities
Applied to files:
lib/framework/DownloadFirmwareService.cpp
🧬 Code graph analysis (1)
lib/framework/EventSocket.cpp (1)
lib/PsychicHttp/src/PsychicWebSocket.cpp (2)
client(17-19)client(17-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (9)
lib/framework/DownloadFirmwareService.cpp (1)
230-234: DownloadFirmwareService.cpp change is correct, but inconsistency found elsewhere.The conditional core selection in DownloadFirmwareService.cpp properly handles single-core ESP32-C3 (core 0) and multi-core variants (core 1), aligning with the PR objectives.
However, verification reveals effectTask in src/main.cpp (line 337) does not follow the same pattern—it's hardcoded to core 0 without a
CONFIG_FREERTOS_UNICOREconditional. This violates the PR objective to "ensure all task-creation sites are protected by the same ifdefs."The driverTask (src/main.cpp) and loopTask (ESP32SvelteKit.cpp) correctly use the conditional pattern. Apply the same
#ifdef CONFIG_FREERTOS_UNICORE/#else/#endifblock to effectTask for consistency.⛔ Skipped due to learnings
Learnt from: ewowi Repo: MoonModules/MoonLight PR: 0 File: :0-0 Timestamp: 2025-12-25T13:31:32.367Z Learning: For ESP32 LED drivers using DMA, I2S, LCD, or PARLIO: always pin driver tasks to Core 1 (APP_CPU), not Core 0. WiFi/BT runs at priority ~23 on Core 0 and would constantly preempt lower-priority driver tasks, breaking LED timing requirements. ESP-IDF explicitly recommends display/DMA processing on Core 1 to avoid WiFi interference.Learnt from: ewowi Repo: MoonModules/MoonLight PR: 0 File: :0-0 Timestamp: 2026-01-06T13:45:03.067Z Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).Learnt from: ewowi Repo: MoonModules/MoonLight PR: 0 File: :0-0 Timestamp: 2025-12-25T13:34:40.097Z Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.src/main.cpp (8)
128-131: LGTM: Watchdog reset before mutex acquisition.The placement of
esp_task_wdt_reset()at loop start, before taking the mutex, ensures the task resets its watchdog timer before potentially blocking. This aligns with the documented approach to prevent watchdog triggers during mutex contention.Based on learnings, the combination of
esp_task_wdt_reset()withvTaskDelay(1)at loop end correctly allows the IDLE task (priority 0) to run and reset the system watchdog.
146-156: LGTM: TOCTOU protection in double-buffer swap.The re-check of
isPositions == 0on line 148 after re-acquiring the mutex correctly prevents a race condition where the state could have changed during the unlocked period (lines 135-145). The conditional swap andnewFrameReadyflag handling for both double-buffer and non-double-buffer paths is sound.
171-175: LGTM: Watchdog reset before mutex acquisition.Consistent with the effectTask pattern, placing
esp_task_wdt_reset()before the mutex acquisition ensures the driver task resets its watchdog before potentially blocking, preventing spurious watchdog triggers during mutex contention.
195-195: LGTM: Correct delay at loop end.The
vTaskDelay(1)correctly allows the IDLE task to run and reset the watchdog, consistent with the effectTask pattern.
296-299: Verify intended behavior on critical allocation failure.The early return on line 298 leaves the system in an incomplete state when shared router allocation fails. This is a critical failure that renders the system non-functional, yet the Arduino
loop()will still be invoked.Is the early return intentional to allow serial debugging, or should this trigger a restart to prevent undefined behavior? Without the shared routers, subsequent module operations will likely crash when dereferencing null pointers.
Consider either:
- Calling
ESP.restart()to reboot into a clean state (preferred for production)- Adding a safe-mode flag and blocking in
loop()with diagnostic output (preferred for development/debugging)Proposed defensive options
Option 1: Restart on critical failure (production-safe)
if (!sharedHttpEndpoint || !sharedWebSocketServer || !sharedEventEndpoint) { EXT_LOGE(ML_TAG, "dev: Failed to allocate shared routers"); - return; + delay(1000); // Allow log to flush + ESP.restart(); }Option 2: Enter safe mode with diagnostic loop
+ static bool setupFailed = false; + if (!sharedHttpEndpoint || !sharedWebSocketServer || !sharedEventEndpoint) { EXT_LOGE(ML_TAG, "dev: Failed to allocate shared routers"); + setupFailed = true; return; }Then in
loop():void loop() { + if (setupFailed) { + Serial.println("Setup failed - system halted. Restart required."); + delay(5000); + return; + } #if USE_M5UNIFIEDDDisplay
337-357: LGTM: Correct task core affinity assignments.The task core assignments correctly implement the requirements:
effectTask on core 0 (line 343): Appropriate for computation-only effects processing; WiFi preemption is acceptable since there are no timing-critical hardware operations.
driverTask with conditional pinning (lines 352-356): Correctly handles single-core (ESP32-C3) vs multi-core variants:
- Single-core: must use core 0 (only option)
- Multi-core: uses core 1 to avoid WiFi/BT preemption for timing-critical DMA/I2S/LCD/PARLIO operations
Based on learnings, this fixes the boot/assert failures on ESP32-C3 caused by hardcoded core 1 pinning, while maintaining optimal core separation for multi-core targets.
373-381: LGTM: Yield instrumentation marker and active client tracking.The
logYield()comment marker on line 373 provides instrumentation for yield debugging/monitoring, consistent with the global yield utilities mentioned in the PR objectives.The
activeClientstracking on line 381 enables adaptive monitor emission based on client visibility (Page Visibility API), preventing buffer overflow and UI freeze when browser tabs are hidden, as documented in learnings.
159-159: Yields are correctly placed inside node-iteration loops.The verification confirms that both critical node-iteration loops contain internal yields:
VirtualLayer::loop()hasaddYield(10)inside the node iteration loop (line 65)PhysicalLayer::loopDrivers()hasaddYield(10)inside the node iteration loop (line 120)Combined with
vTaskDelay(1)at the task loop end (line 159), this ensures the IDLE task receives adequate opportunities to run and reset the watchdog timer, even at extreme loads.
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | ||
| for (auto &event_subscriptions : client_subscriptions) | ||
| { | ||
| event_subscriptions.second.remove(client->socket()); | ||
| } | ||
| _clientVisibility.erase((int)client->socket()); // 🌙 | ||
| xSemaphoreGive(clientSubscriptionsMutex); | ||
| ESP_LOGI(SVK_TAG, "ws[%s][%u] disconnect", client->remoteIP().toString().c_str(), client->socket()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onWSClose() can now block forever behind emitEvent() network I/O
xSemaphoreTake(..., portMAX_DELAY) here is risky because emitEvent() holds the same mutex while performing sends; if a send blocks/stalls, the close callback can hang indefinitely and delay cleanup. Minimize lock hold time in emitEvent() (copy subscriptions under lock, release, send, then briefly re-lock for removals).
🤖 Prompt for AI Agents
In @lib/framework/EventSocket.cpp around lines 53 - 60, The close path is
blocking because emitEvent() holds clientSubscriptionsMutex while doing network
I/O; update emitEvent() so it locks clientSubscriptionsMutex only to copy the
relevant subscription list (from client_subscriptions) into a local container,
then immediately xSemaphoreGive(clientSubscriptionsMutex) before performing
sends; after sends, briefly xSemaphoreTake the mutex again only to remove any
dead/closed sockets from client_subscriptions and update _clientVisibility.
Reference clientSubscriptionsMutex, client_subscriptions, emitEvent(), and the
removal logic currently in onWSClose() so the send loop never runs while the
mutex is held.
| ESP_LOGW(SVK_TAG, "Method tried to emit unregistered event: %s from %s (len %zu)", event.c_str(), originId, len); | ||
| return; | ||
| } | ||
|
|
||
| int originSubscriptionId = originId[0] ? atoi(originId) : -1; | ||
| // 🌙 adding semaphore wait too long logging | ||
| if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(100))==pdFALSE) { | ||
| ESP_LOGW(SVK_TAG, "clientSubscriptionsMutex wait too long"); | ||
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | ||
| } | ||
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | ||
| auto &subscriptions = client_subscriptions[event]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null-pointer hazard: originId[0] and logging assume originId != nullptr
originId[0] ? atoi(originId) : -1 will crash if a caller passes nullptr; the warning log also prints %s for originId. Add a null guard and use a safe fallback string.
Proposed fix
- ESP_LOGW(SVK_TAG, "Method tried to emit unregistered event: %s from %s (len %zu)", event.c_str(), originId, len);
+ const char* safeOriginId = originId ? originId : "";
+ ESP_LOGW(SVK_TAG, "Method tried to emit unregistered event: %s from %s (len %zu)", event.c_str(), safeOriginId, len);
return;
}
- int originSubscriptionId = originId[0] ? atoi(originId) : -1;
+ const char* safeOriginId = originId ? originId : "";
+ int originSubscriptionId = safeOriginId[0] ? atoi(safeOriginId) : -1;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ESP_LOGW(SVK_TAG, "Method tried to emit unregistered event: %s from %s (len %zu)", event.c_str(), originId, len); | |
| return; | |
| } | |
| int originSubscriptionId = originId[0] ? atoi(originId) : -1; | |
| // 🌙 adding semaphore wait too long logging | |
| if (xSemaphoreTake(clientSubscriptionsMutex, pdMS_TO_TICKS(100))==pdFALSE) { | |
| ESP_LOGW(SVK_TAG, "clientSubscriptionsMutex wait too long"); | |
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | |
| } | |
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | |
| auto &subscriptions = client_subscriptions[event]; | |
| const char* safeOriginId = originId ? originId : ""; | |
| ESP_LOGW(SVK_TAG, "Method tried to emit unregistered event: %s from %s (len %zu)", event.c_str(), safeOriginId, len); | |
| return; | |
| } | |
| const char* safeOriginId = originId ? originId : ""; | |
| int originSubscriptionId = safeOriginId[0] ? atoi(safeOriginId) : -1; | |
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | |
| auto &subscriptions = client_subscriptions[event]; |
🤖 Prompt for AI Agents
In @lib/framework/EventSocket.cpp around lines 166 - 172, The code assumes
originId is non-null when logging and when computing originSubscriptionId; add a
null check and safe fallback string to avoid dereferencing nullptr: before using
originId in ESP_LOGW and in the ternary expression that sets
originSubscriptionId, ensure originId is checked (e.g. use originId ? originId :
"<null>" in the log) and compute originSubscriptionId with a guarded expression
like (originId && originId[0]) ? atoi(originId) : -1; keep the rest of the block
(xSemaphoreTake, client_subscriptions access) the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
developers should make sure the originId is never 0 of emitEvent is called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ewowi, understood. If the API contract guarantees that originId is always non-null when emitEvent is called, then the defensive check isn't necessary. The code can rely on callers to uphold that precondition.
If this precondition isn't already documented in the header file or function comment, you might consider adding a brief note (e.g., @param originId Must not be NULL) to help future maintainers understand the contract, but that's entirely optional.
🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
| // 🌙 Client info / visibility / active clients | ||
|
|
||
| void EventSocket::handleClientInfo(JsonObject &data, int originId) | ||
| { | ||
| bool visible = data["visible"] | false; | ||
|
|
||
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | ||
| _clientVisibility[originId] = visible; | ||
| xSemaphoreGive(clientSubscriptionsMutex); | ||
|
|
||
| ESP_LOGD(SVK_TAG, "Client %d visible: %s", originId, visible ? "Yes" : "No"); | ||
| } | ||
|
|
||
| unsigned int EventSocket::getActiveClients() { | ||
| unsigned int count = 0; | ||
| xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); | ||
|
|
||
| for (const auto& pair : _clientVisibility) { | ||
| if (pair.second) count++; | ||
| } | ||
|
|
||
| xSemaphoreGive(clientSubscriptionsMutex); | ||
| return count; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getActiveClients() likely returns 0 until a client sends client_info
As written, a newly connected client isn’t counted “active” until it reports visibility, so upstream code may skip monitor emission even while clients are connected. Consider defaulting visibility to true in onWSOpen() and only switching to false after receiving client_info.visible=false, and/or treating “missing entry” as visible. Also consider skipping monitor sends per-client when _clientVisibility[socket]==false (not just global gating). (Based on learnings about Page Visibility throttling and buffer buildup.)
Also applies to: 58-59
🤖 Prompt for AI Agents
In @lib/framework/EventSocket.cpp around lines 284 - 307, getActiveClients()
currently only counts entries in _clientVisibility so new sockets that haven’t
sent client_info are treated as not active; initialize visibility true for new
connections in onWSOpen (set _clientVisibility[originId]=true while holding
clientSubscriptionsMutex) and/or change getActiveClients() to treat missing map
entries as visible (count them as active when key not found), and update
emission logic to skip monitor sends per-client when
_clientVisibility[socket]==false rather than using a global gate; use the
existing clientSubscriptionsMutex when reading/writing _clientVisibility and
reference the functions EventSocket::onWSOpen, EventSocket::handleClientInfo and
EventSocket::getActiveClients to locate changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please provide code suggestions for both onWSOpen and skipping monitor sends per-client
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ewowi, here are the code suggestions for both changes:
1. Initialize visibility to true in onWSOpen
File: lib/framework/EventSocket.cpp
void EventSocket::onWSOpen(PsychicWebSocketClient *client)
{
ESP_LOGI(SVK_TAG, "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket());
// Initialize new clients as visible by default
xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY);
_clientVisibility[client->socket()] = true;
xSemaphoreGive(clientSubscriptionsMutex);
}This ensures that newly connected clients are treated as active until they report otherwise via client_info.
2. Skip monitor sends to hidden clients
File: lib/framework/EventSocket.cpp
In the emitEvent function's broadcast loop (around line 208-240), add a visibility check:
// else send the message to all other clients
// 🌙 use iterator so remove / erase also removes from the iterator
for (auto it = subscriptions.begin(); it != subscriptions.end();)
{
int subscription = *it;
if (subscription == originSubscriptionId)
{
++it;
continue;
}
// Skip monitor sends to hidden/inactive clients
if (event == "monitor")
{
auto visIt = _clientVisibility.find(subscription);
if (visIt != _clientVisibility.end() && !visIt->second)
{
++it;
continue;
}
}
auto *client = _socket.getClient(subscription);
if (!client)
{
it = subscriptions.erase(it);
continue;
}
if (event != "monitor")
ESP_LOGV(SVK_TAG, "Emitting event: %s to %s[%u], Message[%d]: %s", event.c_str(), client->remoteIP().toString().c_str(), client->socket(), len, output);
#if FT_ENABLED(EVENT_USE_JSON)
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_TEXT, output, len);
#else
esp_err_t result = client->sendMessage(HTTPD_WS_TYPE_BINARY, output, len);
#endif
// 🌙 error check
if (result != ESP_OK)
{
ESP_LOGW(SVK_TAG, "Failed to send event %s from %s to client %u: %s (len: %zu)", event.c_str(), originId, client->socket(), esp_err_to_name(result), len);
// it = subscriptions.erase(it);// do not erase as we hope for better times
}
++it;
}This change checks the visibility state before sending monitor events. Clients with visible: false will be skipped, preventing buffer buildup and UI freezes when browser tabs are hidden.
Note: The visibility check uses find() to safely handle cases where the entry doesn't exist (treating missing entries as visible). Only clients explicitly marked as visible: false will be skipped.
🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @docs/develop/installation.md:
- Line 281: Several markdown links use incorrect syntax with quotes around the
URL (e.g., [ESP-Web-Tools-Tutorial]("https://...")) which prevents them
rendering; update each malformed link (notably the [ESP-Web-Tools-Tutorial]
instance and the links around lines 291–293) by removing the surrounding quotes
so they use the correct form [text](url), ensuring the URLs are plain
parentheses without quotes.
In @docs/gettingstarted/installer.md:
- Around line 45-58: The table includes esp-web-install-button entries
referencing four missing manifests (manifest_esp32-c3.json,
manifest_esp32-d0-16mb.json, manifest_esp32-s3-n16r8v.json,
manifest_esp32-s3-n8r8v.json) which return 404 and break flashing; fix by either
uploading the correct manifest files to
https://moonmodules.org/MoonLight/firmware/installer/ with those exact filenames
or update/remove the corresponding <esp-web-install-button> manifest attributes
for the rows for esp32-c3, esp32-d0-16mb, esp32-s3-n16r8v, and esp32-s3-n8r8v
(or remove the entire row if the device is unsupported), then verify each URL
returns 200 and the flash buttons function.
- Around line 21-43: The installer docs reference manifest URLs for multiple
device variants (esp32-s3-n8r8v, esp32-c3, etc.) used by the esp-web-tools
install buttons in the "Flash the firmware" section, but those manifests are
unreachable causing broken install buttons; verify each manifest URL referenced
by the device table and replace or restore the URLs so they return valid JSON
manifests, remove any unsupported variants from the device table (leaving
esp32-d0 or other confirmed-working entries), or point their install buttons to
a known-good fallback manifest, and then test the esp-web-tools <script
type="module"
src="https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module">
integration by clicking Connect → Install MoonLight to confirm flashing works
and update the docs accordingly.
🧹 Nitpick comments (1)
docs/gettingstarted/installer.md (1)
3-9: Consider emphasizing Safari incompatibility upfront.The browser support limitation (Chrome/Edge only, Safari not supported) is mentioned in line 3 but is easy to miss. Users opening the page in Safari will proceed with setup only to encounter Web Serial failures later. A prominent callout (e.g., a note block or warning box) higher in the intro might reduce support friction.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
docs/media/moonlight/effects/lines.gifis excluded by!**/*.gif
📒 Files selected for processing (5)
docs/develop/installation.mddocs/gettingstarted/installer.mddocs/moonlight/effects.mdlib/framework/EventSocket.cpplib/framework/EventSocket.h
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/framework/EventSocket.cpp
- lib/framework/EventSocket.h
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Be aware of the integrated build process where the PlatformIO build automatically builds and embeds the SvelteKit frontend into the ESP32 firmware
Applied to files:
docs/develop/installation.md
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/**/platformio.ini : Use PlatformIO to build and upload the ESP32 backend C++ code
Applied to files:
docs/develop/installation.md
🪛 LanguageTool
docs/gettingstarted/installer.md
[style] ~119-~119: Using many exclamation marks might seem excessive (in this case: 25 exclamation marks for a text that’s 9882 characters long)
Context: ...llation/#usb-to-serial-chip-drivers)
!!! info "Next steps"
* You can now ch...
(EN_EXCESSIVE_EXCLAMATION)
docs/develop/installation.md
[style] ~278-~278: Using many exclamation marks might seem excessive (in this case: 40 exclamation marks for a text that’s 7703 characters long)
Context: ...e layout changes effective is needed. !!! info "Tools used" The installer is ...
(EN_EXCESSIVE_EXCLAMATION)
docs/moonlight/effects.md
[uncategorized] ~52-~52: Did you mean “Rubik's Cube”?
Context: ...8-d60a-4eed-9e04-ea7ddbfc37ab" /> | | | Rubiks Cube 🧊 | 
🪛 markdownlint-cli2 (0.18.1)
docs/moonlight/effects.md
49-49: Images should have alternate text (alt text)
(MD045, no-alt-text)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
docs/moonlight/effects.md (2)
45-45: Verify the local image path exists.Line 45 references a local image path
../../media/moonlight/effects/lines.gifinstead of a GitHub URL. Ensure this file exists in the repository at the relative path, or confirm it will be added during the release process (as mentioned in the PR summary: "binary files will be regenerated at release time").The remark addition ("To test a layout, Red = x, Green = y, Blue = z") is clear and helpful.
53-53: Verify the "Active (dAC)" preset is implemented.The Scrolling Text preset list now includes "Active (dAC)", which aligns with the PR's addition of active client count tracking. Confirm that this preset format is implemented in the Scrolling Text effect code to ensure documentation and implementation remain in sync.
docs/develop/installation.md (2)
100-100: Wording improvement: clarity on ESP32 restart behavior.The updated phrasing—"after flashing the ESP32 device will restart"—is precise and helps users understand the expected behavior post-flash.
245-245: Well-structured MoonLight Installer section with practical guidance.The new "MoonLight Installer" subsection (lines 245–294) is comprehensive and logically organized, covering captive portal flow, UI reload/cache troubleshooting, hostname setup, driver selection, and USB-to-serial driver installation. The device-specific caveat for ESP32-P4-Nano (line 269) and tool transparency (lines 279–284) are valuable additions.
Note: The markdown link syntax errors on lines 281 and 291–293 (quoted URLs) must be fixed separately to ensure links render correctly.
Also applies to: 250-250, 264-264, 271-271, 279-279, 286-286
docs/gettingstarted/installer.md (1)
125-150: Documentation reference is correct; ESPConnect URL requires manual verification.The VSCode documentation reference (
../../develop/installation) correctly resolves todocs/develop/installation.mdwhich exists in the repository. However, the ESPConnect URL on line 141 (https://thelastoutpostworkshop.github.io/microcontroller_devkit/espconnect) cannot be verified in this environment and should be manually tested to ensure current validity.
| ## Flash the firmware | ||
|
|
||
| <script type="module" src="https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module"></script> | ||
|
|
||
| MoonLight v0.7.0, 22 December 2025 | ||
| Select your ESP32-device from the list below and press the corresponding Connect button and follow these steps: | ||
|
|
||
| <img width="250" src="https://github.com/user-attachments/assets/f092743f-a362-40da-b932-d31b203d966d" /> | ||
|
|
||
| Your device should show up in this list, Connect | ||
|
|
||
| <img width="200" alt="Screenshot 2025-06-07 at 20 54 31" src="https://github.com/user-attachments/assets/a0a8d92c-dae2-4cfe-ac78-bc7cacb24724" /> | ||
| <img width="250" src="https://github.com/user-attachments/assets/35045317-520d-427c-894e-418693877831" /> | ||
|
|
||
| Press Install MoonLight. Erase when installing MoonLight first time, do not erase if already installed (to preserve settings). Press Next and Install | ||
|
|
||
| !!! info "Bootloader mode" | ||
| If install doesn't work: some boards have to be placed in bootloader mode by press and holding the `Boot` button on the ESP32, press the `Reset` button on the ESP32 and then release the `Boot` button. | ||
|
|
||
| <img width="200" src="https://github.com/user-attachments/assets/25c05a6c-d2ef-41cb-b83c-fc40a60c6ccc" /> | ||
| <img width="200" src="https://github.com/user-attachments/assets/b2391752-51c4-400e-b95c-4fa865e93595" /> | ||
| <img width="200" src="https://github.com/user-attachments/assets/5e2ceefd-4c31-4b72-a228-f29373b677ac" /> | ||
|
|
||
| Restart the device by going to Logs & Console and press Restart. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify manifest URLs are accessible and contain expected structure
# Test representative manifests
echo "Testing manifest URL accessibility and structure..."
# Function to check manifest
check_manifest() {
local device=$1
local url="https://moonmodules.org/MoonLight/firmware/installer/manifest_${device}.json"
echo ""
echo "Checking: $url"
# Try to fetch and validate JSON structure
response=$(curl -s -f "$url" 2>&1)
if [ $? -eq 0 ]; then
# Try to parse JSON
if echo "$response" | jq empty 2>/dev/null; then
builds_check=$(echo "$response" | jq 'has("builds") and (.builds | type == "array")' 2>/dev/null)
if [ "$builds_check" = "true" ]; then
echo "✓ Manifest accessible and valid structure"
else
echo "✗ Manifest accessible but missing/invalid builds array"
echo "Content preview: $(echo "$response" | jq '.' | head -20)"
fi
else
echo "✗ Manifest accessible but invalid JSON"
fi
else
echo "✗ Manifest not accessible (HTTP error or timeout)"
fi
}
# Check representative devices from the documentation
for device in esp32-d0 esp32-s3-n8r8v esp32-c3; do
check_manifest "$device"
done
# Also check if there are other manifest files referenced
echo ""
echo "Checking base manifest directory..."
curl -s -I "https://moonmodules.org/MoonLight/firmware/installer/" | head -1Repository: MoonModules/MoonLight
Length of output: 574
🏁 Script executed:
#!/bin/bash
# Find manifest URL references in installer.md
grep -n "manifest" docs/gettingstarted/installer.md || echo "No 'manifest' found"
# Also check for any JSON references or device-specific URLs
grep -n "moonmodules.org" docs/gettingstarted/installer.md || echo "No moonmodules.org URLs found"
# Check file structure around the provided lines
echo ""
echo "=== File structure and size ==="
wc -l docs/gettingstarted/installer.mdRepository: MoonModules/MoonLight
Length of output: 10692
Manifest URLs for multiple device variants are not accessible—blocking flashing for those devices.
The flashing section (lines 21-43) with ESP Web Tools integration and bootloader-mode troubleshooting is well-designed. However, verification reveals that manifest URLs referenced in the device table are inaccessible for multiple variants: esp32-s3-n8r8v, esp32-c3, and potentially others fail to load (HTTP errors/timeouts). Only esp32-d0 tested successfully. Users selecting these devices will encounter broken install buttons. Investigate why these manifests are unavailable and restore access or remove unsupported variants from the table.
🤖 Prompt for AI Agents
In @docs/gettingstarted/installer.md around lines 21 - 43, The installer docs
reference manifest URLs for multiple device variants (esp32-s3-n8r8v, esp32-c3,
etc.) used by the esp-web-tools install buttons in the "Flash the firmware"
section, but those manifests are unreachable causing broken install buttons;
verify each manifest URL referenced by the device table and replace or restore
the URLs so they return valid JSON manifests, remove any unsupported variants
from the device table (leaving esp32-d0 or other confirmed-working entries), or
point their install buttons to a known-good fallback manifest, and then test the
esp-web-tools <script type="module"
src="https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module">
integration by clicking Connect → Install MoonLight to confirm flashing works
and update the docs accordingly.
| | Name | Image* | Flash | Shop & Board presets | | ||
| |------|--------|-------|----------------------| | ||
| | esp32-d0 | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-d0.json"></esp-web-install-button> | [Dig Uno](https://quinled.info/pre-assembled-quinled-dig-uno/):<br>{: style="width:100px"}<br>[Dig Quad](https://quinled.info/pre-assembled-quinled-dig-quad/):<br>{: style="width:100px"}<br>[Dig2Go](https://quinled.info/quinled-dig2go/):<br>{: style="width:100px"} | | ||
| | esp32-d0-16mb | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-d0-16mb.json"></esp-web-install-button> | [Dig Octa](https://quinled.info/quinled-dig-octa/):<br>{: style="width:100px"}<br>[Serg ESP32](https://www.tindie.com/products/serg74/esp32-wroom-usb-c-d1-mini32-form-factor-board/){:target="_blank"} and [Shield](https://www.tindie.com/products/serg74/wled-shield-board-for-addressable-leds/)<br>/i/93057/products/2021-08-14T14%3A44%3A14.418Z-shield_v3-1.jpg?1628927139){: style="width:100px"} | | ||
| | esp32-s3-devkitc-1-n8r8v | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-s3-devkitc-1-n8r8v.json"></esp-web-install-button> | SE-16p<br>{: style="width:100px"} | | ||
| | esp32-s3-devkitc-1-n16r8v | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-s3-devkitc-1-n16r8v.json"></esp-web-install-button> | [Ali*](https://s.click.aliexpress.com/e/_DBAtJ2H){:target="_blank"} | | ||
| | esp32-d0 | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-d0.json"></esp-web-install-button> | [Dig Uno](https://quinled.info/pre-assembled-quinled-dig-uno):<br>{: style="width:100px"}<br>[Dig Quad](https://quinled.info/pre-assembled-quinled-dig-quad):<br>{: style="width:100px"}<br>[Dig2Go](https://quinled.info/quinled-dig2go):<br>{: style="width:100px"} | | ||
| | esp32-d0-16mb | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-d0-16mb.json"></esp-web-install-button> | [Dig Octa](https://quinled.info/quinled-dig-octa):<br>{: style="width:100px"}<br>[Serg ESP32](https://www.tindie.com/products/serg74/esp32-wroom-usb-c-d1-mini32-form-factor-board){:target="_blank"} and [Shield](https://www.tindie.com/products/serg74/wled-shield-board-for-addressable-leds)<br>/i/93057/products/2021-08-14T14%3A44%3A14.418Z-shield_v3-1.jpg?1628927139){: style="width:100px"} | | ||
| | esp32-s3-n8r8v | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-s3-n8r8v.json"></esp-web-install-button> | SE-16p<br>{: style="width:100px"} | | ||
| | esp32-s3-n16r8v | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-s3-n16r8v.json"></esp-web-install-button> | [Ali*](https://s.click.aliexpress.com/e/_DBAtJ2H){:target="_blank"} | | ||
| | esp32-s3-atoms3r | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-s3-atoms3r.json"></esp-web-install-button> | [M5Stack store](https://shop.m5stack.com/products/atoms3r-dev-kit){:target="_blank"} | | ||
| | esp32-s3-zero-n4r2 | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-s3-zero-n4r2.json"></esp-web-install-button> | [Ali*](https://s.click.aliexpress.com/e/_EukjHX8){:target="_blank"} | | ||
| | esp32-p4-nano | {: style="width:100px"} | [esp32-c6-firmware-update](https://esp32-c6-firmware-update.github.io/) * <br><esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-p4-nano.json"></esp-web-install-button> | [Waveshare](https://www.waveshare.com/esp32-p4-nano.htm){:target="_blank"} <br>[ESP32-P4 shield](https://shop.myhome-control.de/en/ABC-WLED-ESP32-P4-shield/HW10027):<br>{: style="width:100px"} | | ||
| | esp32-p4-nano | {: style="width:100px"} | install [esp32-c6-firmware-update](https://esp32-c6-firmware-update.github.io) first <br><esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-p4-nano.json"></esp-web-install-button> | [Waveshare](https://www.waveshare.com/esp32-p4-nano.htm){:target="_blank"} <br>[ESP32-P4 shield](https://shop.myhome-control.de/en/ABC-WLED-ESP32-P4-shield/HW10027):<br>{: style="width:100px"} | | ||
| | esp32-d0-wrover | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-d0-wrover.json"></esp-web-install-button> | [Ali*](https://a.aliexpress.com/_EzhPi6g){:target="_blank"} | | ||
| | esp32-d0-wrover-moonbase | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-d0-wrover-moonbase.json"></esp-web-install-button> | [Ali*](https://a.aliexpress.com/_EzhPi6g){:target="_blank"} | | ||
| | esp32-d0-moonbase | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-d0-moonbase.json"></esp-web-install-button> | | | ||
| | esp32-p4-olimex 🚧 | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-p4-olimex.json"></esp-web-install-button> | [Olimex](https://www.olimex.com/Products/IoT/ESP32-P4/ESP32-P4-DevKit/open-source-hardware){:target="_blank"} | | ||
| | esp32-c3-devkitm-1 | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-c3-devkitm-1.json"></esp-web-install-button> | [Ali*](https://s.click.aliexpress.com/e/_EIl7NKw){:target="_blank"} | | ||
| | esp32-p4-olimex | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-p4-olimex.json"></esp-web-install-button> | [Olimex](https://www.olimex.com/Products/IoT/ESP32-P4/ESP32-P4-DevKit/open-source-hardware){:target="_blank"} | | ||
| | esp32-c3 | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-c3.json"></esp-web-install-button> | [Ali*](https://s.click.aliexpress.com/e/_EIl7NKw){:target="_blank"} | | ||
| | esp32-c3-supermini | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest_esp32-c3-supermini.json"></esp-web-install-button> | [Ali*](https://s.click.aliexpress.com/e/_EIl7NKw){:target="_blank"} | | ||
| | others | {: style="width:100px"} | <esp-web-install-button manifest="https://moonmodules.org/MoonLight/firmware/installer/manifest.json"></esp-web-install-button> | | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Extract device names from the markdown file and check manifest accessibility
echo "Extracting device names from installer.md..."
grep -oP 'manifest_\K[a-z0-9-]+(?=\.json)' docs/gettingstarted/installer.md | sort -u > /tmp/devices.txt
echo "Device variants found:"
cat /tmp/devices.txt
echo ""
echo "Checking manifest file accessibility..."
while IFS= read -r device; do
url="https://moonmodules.org/MoonLight/firmware/installer/manifest_${device}.json"
# Use curl with timeout and follow redirects
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null)
if [ "$http_code" = "200" ]; then
echo "✓ manifest_${device}.json (HTTP $http_code)"
else
echo "✗ manifest_${device}.json (HTTP $http_code)"
fi
done < /tmp/devices.txtRepository: MoonModules/MoonLight
Length of output: 798
Four manifest files are missing or inaccessible, breaking flash buttons for multiple device variants.
The following manifests referenced in the device table return HTTP 404 and will prevent users from flashing firmware for these devices:
manifest_esp32-c3.jsonmanifest_esp32-d0-16mb.jsonmanifest_esp32-s3-n16r8v.jsonmanifest_esp32-s3-n8r8v.json
Ensure these files exist at https://moonmodules.org/MoonLight/firmware/installer/ or remove the corresponding rows from the table.
🤖 Prompt for AI Agents
In @docs/gettingstarted/installer.md around lines 45 - 58, The table includes
esp-web-install-button entries referencing four missing manifests
(manifest_esp32-c3.json, manifest_esp32-d0-16mb.json,
manifest_esp32-s3-n16r8v.json, manifest_esp32-s3-n8r8v.json) which return 404
and break flashing; fix by either uploading the correct manifest files to
https://moonmodules.org/MoonLight/firmware/installer/ with those exact filenames
or update/remove the corresponding <esp-web-install-button> manifest attributes
for the rows for esp32-c3, esp32-d0-16mb, esp32-s3-n16r8v, and esp32-s3-n8r8v
(or remove the entire row if the device is unsupported), then verify each URL
returns 200 and the flash buttons function.
| ## Setup MoonLight | ||
|
|
||
| A new MoonLight device will show itself as a WiFi Access Point (AP) in the WiFi settings of your PC, tablet or smartphone. The name starts with ML- and 4 characters. | ||
|
|
||
| <img width="200" src="https://github.com/user-attachments/assets/9a146e3c-1a53-4906-ad2a-d70215efcf4b" /> | ||
|
|
||
| Select ML-xxxx, MoonLight will start in a browser: | ||
|
|
||
| <img width="350" src="../../media/moonlight/ML-start.png"/> | ||
|
|
||
| Follow the steps: | ||
|
|
||
| ### WiFi connection | ||
|
|
||
| Add a [WiFi](../../network/sta) connection | ||
|
|
||
| <img width="350" src="../../media/network/MB-WiFi2.png"/> | ||
|
|
||
| Enter a hostname, add your WiFi by adding the (+) button, enter SSID and password and press Apply Settings. | ||
|
|
||
| Restart the device by pressing the boot button or simple unplug and plug the usb cable. | ||
|
|
||
| Go back to your home WiFi open a browser and enter hostname.local to open MoonLight via the new WiFi connection. | ||
|
|
||
| ### IO Board Presets | ||
|
|
||
| Select the specific board you are running in [Module IO](../../moonbase/inputoutput), e.g. premade or custom boards as shown in [hardware](../hardware). | ||
|
|
||
| <img width="320" height="441" alt="io11" src="../../media/moonbase/inputoutput.png" /> | ||
|
|
||
| Make sure the defined LED pin(s) are the same as the physical connections on the ESP32 board. | ||
|
|
||
| ### Drivers | ||
|
|
||
| The drivers module contains [Layouts](../../moonlight/layouts) and [Drivers](../../moonlight/drivers). | ||
|
|
||
| Add a layout first, this defines how your LEDs are arranged. Press (+) | ||
|
|
||
| <img width="350" src="https://github.com/user-attachments/assets/8d8b9c2d-bed3-439e-a145-2bb204639c6c" /> | ||
|
|
||
| For LED strips, choose Single Column, for Panels choose Panel(s) or other layouts. Layouts are recognized by 🚥. Enter the size of your LEDs | ||
|
|
||
| Add the LED driver by pressing the blue + button and select the Physical LED driver or FastLED driver. Drivers are recognized by ☸️ | ||
|
|
||
| <img width="350" src="https://github.com/user-attachments/assets/e7fab24f-3803-4aa5-8638-459cca8a9caf" /> | ||
|
|
||
| ### Effect | ||
|
|
||
| The Effects module contains [Effects](../../moonlight/effects) and [Modifiers](../../moonlight/modifiers). Start by adding one effect, press (+) | ||
|
|
||
| <img width="350" src="https://github.com/user-attachments/assets/2c8a8b75-c429-4038-a5af-adc82b11c9de" /> | ||
|
|
||
| The effect should be visible on the Monitor in MoonLight and on your LEDs | ||
|
|
||
| ### Save and next steps | ||
|
|
||
| Press save (💾). Saves your setup to the file system, so if a device is restarted, your settings are still there. | ||
|
|
||
| If anything not working as expected, log an issue on GitHUB or contact us on Discord, Reddit or YouTube. See also [Installer troubleshooting](../../develop/installation/#moonlight-installer) and [USB-to-serial chip drivers](../../develop/installation/#usb-to-serial-chip-drivers) | ||
|
|
||
| !!! info "Next steps" | ||
| * You can now change effect or add effects, modifiers, layouts and drivers | ||
| * Go to [MoonLight Overview](../../moonlight/overview) to learn more | ||
| * press the (?) in MoonLight to go directly to the relevant page |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Explore repository structure to understand media and docs layout
git ls-files | grep -E "(media|docs)" | head -30Repository: MoonModules/MoonLight
Length of output: 867
🏁 Script executed:
# Get the full file to understand its structure and line numbers
wc -l docs/gettingstarted/installer.mdRepository: MoonModules/MoonLight
Length of output: 101
🏁 Script executed:
# List files in the media directory
find . -type d -name media | head -10Repository: MoonModules/MoonLight
Length of output: 77
🏁 Script executed:
# Check which image files exist in media directories
find . -path "*/media/*.png" -o -path "*/media/*.jpg" | head -20Repository: MoonModules/MoonLight
Length of output: 668
🏁 Script executed:
# Get the exact content of lines 60-123 from installer.md
sed -n '60,123p' docs/gettingstarted/installer.mdRepository: MoonModules/MoonLight
Length of output: 3030
🏁 Script executed:
# Check if develop/installation.md exists and has the required sections
cat docs/develop/installation.md | head -50Repository: MoonModules/MoonLight
Length of output: 1641
🏁 Script executed:
# Check for the exact capitalization of the ML-start image reference
grep -n "ML-start\|ML-Start" docs/gettingstarted/installer.mdRepository: MoonModules/MoonLight
Length of output: 127
🏁 Script executed:
# Verify documentation cross-references by checking if referenced .md files exist
grep -oP '\]\((?!https)(?!mailto)[^\)]+\.md[^\)]*\)' docs/gettingstarted/installer.md | sort -uRepository: MoonModules/MoonLight
Length of output: 47
🏁 Script executed:
# Check exact file name capitalization - potential issue detected
ls -la docs/media/moonlight/ | grep -i "ml-start"Repository: MoonModules/MoonLight
Length of output: 128
🏁 Script executed:
# Check what documentation files are referenced in installer.md
grep -oP '\]\((?!https)[^\)]+\)' docs/gettingstarted/installer.md | sort -uRepository: MoonModules/MoonLight
Length of output: 1042
🏁 Script executed:
# Verify referenced documentation paths exist (check .md files)
for ref in "network/sta" "moonbase/inputoutput" "moonlight/layouts" "moonlight/drivers" "moonlight/effects" "moonlight/modifiers" "moonlight/overview"; do
file="docs/${ref}.md"
if [ -f "$file" ]; then
echo "✓ $file exists"
else
echo "✗ $file NOT FOUND"
fi
doneRepository: MoonModules/MoonLight
Length of output: 309
🏁 Script executed:
# Check if develop/installation.md has the required anchor sections
grep -n "moonlight-installer\|usb-to-serial" docs/develop/installation.mdRepository: MoonModules/MoonLight
Length of output: 47
🏁 Script executed:
# Get full develop/installation.md to check for section headers
cat docs/develop/installation.md | grep -E "^#+|moonlight|usb"Repository: MoonModules/MoonLight
Length of output: 1196
Fix image file case mismatch on line 68.
The reference uses ../../media/moonlight/ML-start.png (lowercase 's'), but the actual file is ML-Start.png (capital 'S'). This will break the image link on case-sensitive file systems. All documentation cross-references and other media paths are correct.
🧰 Tools
🪛 LanguageTool
[style] ~119-~119: Using many exclamation marks might seem excessive (in this case: 25 exclamation marks for a text that’s 9882 characters long)
Context: ...llation/#usb-to-serial-chip-drivers)
!!! info "Next steps"
* You can now ch...
(EN_EXCESSIVE_EXCLAMATION)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @docs/develop/installation.md:
- Around line 245-296: The two <img> tags currently missing alt attributes
should be updated to include meaningful alt text (e.g., alt="CH9102
USB-to-serial chip" and alt="CP210x and CH34x USB-to-serial chips") so they meet
accessibility MD045; locate the <img src="...ch9102.jpg"> and <img
src="...cp210x-ch34x.jpg"> lines and add alt="..." accordingly, and while here,
reduce excessive exclamation styling in the "tip"/"warning"/"info" admonition
titles by removing or trimming exclamation marks to maintain a more professional
tone (adjust the admonition headings like "!!! tip", "!!! warning", "!!! info"
content strings).
- Around line 243-244: The Markdown line referencing [WiFiGeneric.cpp:1408]
lacks a link target; update the docs by either adding a reference link
definition for the label (e.g., add a "WiFiGeneric.cpp:1408" reference entry in
the References section or at the end of the file) or change the existing
occurrence to an inline link pointing to the appropriate source/line; ensure the
label matches exactly and adjust nearby text mentioning APSettingsService or
setTxPower if you change the label text.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
firmware/installer/MoonLight_esp32-c3-0-7-0_webflash.binis excluded by!**/*.binfirmware/installer/MoonLight_esp32-s3-n16r8v_0-7-0_webflash.binis excluded by!**/*.binfirmware/installer/MoonLight_esp32-s3-n8r8v_0-7-0_webflash.binis excluded by!**/*.binfirmware/installer/MoonLight_esp32-s3_0-7-0_webflash.binis excluded by!**/*.bin
📒 Files selected for processing (1)
docs/develop/installation.md
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-06T13:45:03.067Z
Learning: For MoonLight on ESP32 at extreme loads (e.g., 49K channels @ 100 fps): Task watchdog can fire when AppDrivers/AppEffects process many nodes without yielding. Even with vTaskDelay(1) at loop end, the node iteration for-loops (PhysicalLayer::loopDrivers line 108-111, PhysicalLayer::loop line 73-75) can take >5 seconds without yielding, starving IDLE task (priority 0). Solution: add taskYIELD() or vTaskDelay(1) inside the node/layer iteration loops to give IDLE task chances to run and reset watchdog. Lowering task priority won't help since IDLE is already at 0 (lowest).
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T11:39:52.862Z
Learning: In MoonLight WebSocket monitor: When browser tabs are hidden, browsers throttle JavaScript (including WebSocket onmessage handlers) but keep TCP connections open. Monitor data (48KB frames every 20-300ms) continues arriving at TCP level but isn't processed. This fills ESP32's httpd_ws_send_frame_async() buffers, causing ESP_FAIL. When tab becomes visible, hundreds of queued frames overwhelm the JavaScript main thread, freezing the UI. Solution: use Page Visibility API (client_visibility event) to stop emitting monitor data to hidden clients, preventing buffer overflow and backlog buildup.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2025-12-25T13:34:40.097Z
Learning: For the MoonLight LED system: Effects task can run on Core 0 (PRO_CPU) since it only performs computation (calculating colors, writing to channels array) without timing-critical hardware interaction. WiFi preemption is acceptable for pure computation tasks. Only the driver task (using DMA, I2S, LCD, PARLIO) requires Core 1 (APP_CPU) for uninterrupted timing-critical hardware operations.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-08T10:20:09.363Z
Learning: In MoonLight's Idle Watchdog approach: `vTaskDelay(1)` is preferred over `taskYIELD()` because taskYIELD() only yields to equal or higher priority tasks. Since Effect Task and Driver Task run at priority 3 while the Idle task runs at priority 0, only vTaskDelay(1) actually blocks the calling task and allows the Idle task to run and reset the watchdog timer.
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 84
File: src/MoonLight/Modules/ModuleLightsControl.h:367-379
Timestamp: 2025-12-28T10:28:24.157Z
Learning: In MoonLight PR #84: wrapping effectTask's layerP.loop() with monitorMutex while reading channelsE for monitor emission is thread-safe and prevents partial frames, but introduces serialization between effects rendering and monitor emission. Reading from channelsD (driver/front buffer) instead—with monitorMutex held only during the brief pointer swap—is superior because it maintains full parallelism (effects ║ monitor ║ driver), shows actual LED output, and minimizes mutex hold time.
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Be aware of the integrated build process where the PlatformIO build automatically builds and embeds the SvelteKit frontend into the ESP32 firmware
Applied to files:
docs/develop/installation.md
📚 Learning: 2025-11-29T16:17:30.619Z
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: misc/parking/GEMINI.md:0-0
Timestamp: 2025-11-29T16:17:30.619Z
Learning: Applies to misc/parking/**/platformio.ini : Use PlatformIO to build and upload the ESP32 backend C++ code
Applied to files:
docs/develop/installation.md
🪛 LanguageTool
docs/develop/installation.md
[style] ~278-~278: Using many exclamation marks might seem excessive (in this case: 40 exclamation marks for a text that’s 7695 characters long)
Context: ...e layout changes effective is needed. !!! info "Tools used" The installer is ...
(EN_EXCESSIVE_EXCLAMATION)
🪛 markdownlint-cli2 (0.18.1)
docs/develop/installation.md
243-243: Reference links and images should use a label that is defined
Missing link or image reference definition: "wifigeneric.cpp"
(MD052, reference-links-images)
295-295: Images should have alternate text (alt text)
(MD045, no-alt-text)
296-296: Images should have alternate text (alt text)
(MD045, no-alt-text)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
docs/develop/installation.md (1)
100-100: Minor wording improvement: "reboot" → "restart"This change clarifies the behavior. ✅
| * Sometimes the Serial log may show: [5817][W][WiFiGeneric.cpp:1408] setTxPower(): Neither AP or STA has been started. This is from setTxPower in APSettingsService. Delay has been added to prevent this. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a link reference definition for "WiFiGeneric.cpp"
Line 243 references [WiFiGeneric.cpp:1408] but does not define a corresponding link target. Per Markdown best practices (and the static analysis tool markdownlint), reference links should have a label defined.
📝 Proposed fix: Define the link reference
Add the following at the end of the file or in a References section:
+ [wifigeneric.cpp]: https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiGeneric.cpp#L1408Or update line 243 to use an inline link:
- * Sometimes the Serial log may show: [5817][W][WiFiGeneric.cpp:1408] setTxPower(): Neither AP or STA has been started. This is from setTxPower in APSettingsService. Delay has been added to prevent this.
+ * Sometimes the Serial log may show: `[5817][W][WiFiGeneric.cpp:1408]` setTxPower(): Neither AP or STA has been started. This is from setTxPower in APSettingsService. Delay has been added to prevent this.🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
243-243: Reference links and images should use a label that is defined
Missing link or image reference definition: "wifigeneric.cpp"
(MD052, reference-links-images)
🤖 Prompt for AI Agents
In @docs/develop/installation.md around lines 243 - 244, The Markdown line
referencing [WiFiGeneric.cpp:1408] lacks a link target; update the docs by
either adding a reference link definition for the label (e.g., add a
"WiFiGeneric.cpp:1408" reference entry in the References section or at the end
of the file) or change the existing occurrence to an inline link pointing to the
appropriate source/line; ensure the label matches exactly and adjust nearby text
mentioning APSettingsService or setTxPower if you change the label text.
| ### MoonLight Installer | ||
|
|
||
| !!! tip "4.3.2.1" | ||
| The device will show in 'Captive portal' mode. Alternatively you can close the captive portal and show it in a browser using [http://4.3.2.1](http://4.3.2.1) | ||
|
|
||
| !!! warning "UI not showing when installing new version of MoonLight" | ||
|
|
||
| If you ran previous versions of MoonLight, the UI might not show up if it has been changed since then. In this case it is needed to reload the UI from the ESP32-device (use [http://4.3.2.1](http://4.3.2.1)) | ||
|
|
||
| * Chrome: Command Shift R (Mac) or Ctrl Shift R or Control Reload (Windows) | ||
| * Safari: Reload from Origin : Option Shift R | ||
| * After this not all UI elements might be immediately visible. If you see 'MoonLight loading ...' in the browser, just wait a bit (☕️) | ||
|
|
||
| * Sometimes it migh be needed to clear the browser cache: | ||
| * Chrome: Delete Browsing data / delete data (caching data) | ||
| * Safari: Empty cache: Menu Develop (enable if not visible in the Safari Menu) / empty caches (Command Option E) | ||
|
|
||
| * More info: [how to hard refresh your browser and clear cache](https://fabricdigital.co.nz/blog/how-to-hard-refresh-your-browser-and-clear-cache) | ||
|
|
||
| !!! info "Hostname" | ||
| The hostname will be used to access your device. E.g. if the hostname is ml-home you can access it using [http://ml-home.local](http://ml-home.local) | ||
|
|
||
| * Restart the device to make the hostname known to your network (go to system status, scroll down and press restart) | ||
| * Enter the hostname in your browser e.g. [http://ml-home.local](http://ml-home.local) | ||
| * ESP32-P4-Nano: November 16, 2025: hostname is not working correctly, use IP address instead | ||
|
|
||
| !!! tip "Choose driver" | ||
| FastLED driver is best to start with if you have a normal (ws2812) LED strip or panel. | ||
|
|
||
| Choose the Parallel LED Driver if you have more then 4 LED strips or panels or non standard LEDS (e.g. RGBW lights, curtains...). Other drivers (Virtual, Hub,) not supported yet. | ||
|
|
||
| In some cases restart the device to make layout changes effective is needed. | ||
|
|
||
|
|
||
| !!! info "Tools used" | ||
| The installer is powered by [ESP Web Tools](https://esphome.github.io/esp-web-tools) | ||
| See also [ESP-Web-Tools-Tutorial](https://github.com/witnessmenow/ESP-Web-Tools-Tutorial/blob/main/README.md) | ||
|
|
||
| [ESPConnect on GitHub](https://github.com/thelastoutpostworkshop/ESPConnect) | ||
| ESPConnect is based on [Jason2866](https://github.com/Jason2866)'s [WebSerial ESPTool](https://github.com/Jason2866/WebSerial_ESPTool/tree/development). | ||
|
|
||
| ### USB-to-serial chip drivers | ||
|
|
||
| In case your computer does not recognise your connected ESP32, you might need to install the right USB-to-serial chip drivers. | ||
| Below are the drivers for common chips used in ESP devices. See the images below to see what chip your ESP32 has. | ||
|
|
||
| * CP210x drivers: [Windows & Mac](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers) | ||
| * CH342, CH343, CH9102 drivers: [Windows](https://www.wch.cn/downloads/CH343SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH34XSER_MAC_ZIP.html) (download via blue button with download icon)</li> | ||
| * CH340, CH341 drivers: [Windows](https://www.wch.cn/downloads/CH341SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH341SER_MAC_ZIP.html) (download via blue button with download icon)</li> | ||
|
|
||
| <img src="https://moonmodules.org/MoonLight/firmware/installer/images/ch9102.jpg" height="200"/> | ||
| <img src="https://moonmodules.org/MoonLight/firmware/installer/images/cp210x-ch34x.jpg" height="200"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add alt text to images and review formatting
The new "MoonLight Installer" section is comprehensive and well-structured, providing clear guidance on captive portal, UI reload, hostname configuration, driver selection, and USB-to-serial drivers—all aligned with the PR's installer improvements.
However, two minor accessibility and formatting issues need attention:
-
Missing alt text on images (lines 295–296): Both images lack
altattributes, violating MD045 accessibility standards. -
Excessive exclamation marks: The documentation uses many exclamation marks (especially in the tips/info blocks). Consider reducing for a more professional tone.
♿ Proposed fix: Add alt text to images
- <img src="https://moonmodules.org/MoonLight/firmware/installer/images/ch9102.jpg" height="200"/>
- <img src="https://moonmodules.org/MoonLight/firmware/installer/images/cp210x-ch34x.jpg" height="200"/>
+ <img src="https://moonmodules.org/MoonLight/firmware/installer/images/ch9102.jpg" height="200" alt="CH9102 USB-to-serial chip"/>
+ <img src="https://moonmodules.org/MoonLight/firmware/installer/images/cp210x-ch34x.jpg" height="200" alt="CP210x and CH34x USB-to-serial chips"/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### MoonLight Installer | |
| !!! tip "4.3.2.1" | |
| The device will show in 'Captive portal' mode. Alternatively you can close the captive portal and show it in a browser using [http://4.3.2.1](http://4.3.2.1) | |
| !!! warning "UI not showing when installing new version of MoonLight" | |
| If you ran previous versions of MoonLight, the UI might not show up if it has been changed since then. In this case it is needed to reload the UI from the ESP32-device (use [http://4.3.2.1](http://4.3.2.1)) | |
| * Chrome: Command Shift R (Mac) or Ctrl Shift R or Control Reload (Windows) | |
| * Safari: Reload from Origin : Option Shift R | |
| * After this not all UI elements might be immediately visible. If you see 'MoonLight loading ...' in the browser, just wait a bit (☕️) | |
| * Sometimes it migh be needed to clear the browser cache: | |
| * Chrome: Delete Browsing data / delete data (caching data) | |
| * Safari: Empty cache: Menu Develop (enable if not visible in the Safari Menu) / empty caches (Command Option E) | |
| * More info: [how to hard refresh your browser and clear cache](https://fabricdigital.co.nz/blog/how-to-hard-refresh-your-browser-and-clear-cache) | |
| !!! info "Hostname" | |
| The hostname will be used to access your device. E.g. if the hostname is ml-home you can access it using [http://ml-home.local](http://ml-home.local) | |
| * Restart the device to make the hostname known to your network (go to system status, scroll down and press restart) | |
| * Enter the hostname in your browser e.g. [http://ml-home.local](http://ml-home.local) | |
| * ESP32-P4-Nano: November 16, 2025: hostname is not working correctly, use IP address instead | |
| !!! tip "Choose driver" | |
| FastLED driver is best to start with if you have a normal (ws2812) LED strip or panel. | |
| Choose the Parallel LED Driver if you have more then 4 LED strips or panels or non standard LEDS (e.g. RGBW lights, curtains...). Other drivers (Virtual, Hub,) not supported yet. | |
| In some cases restart the device to make layout changes effective is needed. | |
| !!! info "Tools used" | |
| The installer is powered by [ESP Web Tools](https://esphome.github.io/esp-web-tools) | |
| See also [ESP-Web-Tools-Tutorial](https://github.com/witnessmenow/ESP-Web-Tools-Tutorial/blob/main/README.md) | |
| [ESPConnect on GitHub](https://github.com/thelastoutpostworkshop/ESPConnect) | |
| ESPConnect is based on [Jason2866](https://github.com/Jason2866)'s [WebSerial ESPTool](https://github.com/Jason2866/WebSerial_ESPTool/tree/development). | |
| ### USB-to-serial chip drivers | |
| In case your computer does not recognise your connected ESP32, you might need to install the right USB-to-serial chip drivers. | |
| Below are the drivers for common chips used in ESP devices. See the images below to see what chip your ESP32 has. | |
| * CP210x drivers: [Windows & Mac](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers) | |
| * CH342, CH343, CH9102 drivers: [Windows](https://www.wch.cn/downloads/CH343SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH34XSER_MAC_ZIP.html) (download via blue button with download icon)</li> | |
| * CH340, CH341 drivers: [Windows](https://www.wch.cn/downloads/CH341SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH341SER_MAC_ZIP.html) (download via blue button with download icon)</li> | |
| <img src="https://moonmodules.org/MoonLight/firmware/installer/images/ch9102.jpg" height="200"/> | |
| <img src="https://moonmodules.org/MoonLight/firmware/installer/images/cp210x-ch34x.jpg" height="200"/> | |
| ### MoonLight Installer | |
| !!! tip "4.3.2.1" | |
| The device will show in 'Captive portal' mode. Alternatively you can close the captive portal and show it in a browser using [http://4.3.2.1](http://4.3.2.1) | |
| !!! warning "UI not showing when installing new version of MoonLight" | |
| If you ran previous versions of MoonLight, the UI might not show up if it has been changed since then. In this case it is needed to reload the UI from the ESP32-device (use [http://4.3.2.1](http://4.3.2.1)) | |
| * Chrome: Command Shift R (Mac) or Ctrl Shift R or Control Reload (Windows) | |
| * Safari: Reload from Origin : Option Shift R | |
| * After this not all UI elements might be immediately visible. If you see 'MoonLight loading ...' in the browser, just wait a bit (☕️) | |
| * Sometimes it migh be needed to clear the browser cache: | |
| * Chrome: Delete Browsing data / delete data (caching data) | |
| * Safari: Empty cache: Menu Develop (enable if not visible in the Safari Menu) / empty caches (Command Option E) | |
| * More info: [how to hard refresh your browser and clear cache](https://fabricdigital.co.nz/blog/how-to-hard-refresh-your-browser-and-clear-cache) | |
| !!! info "Hostname" | |
| The hostname will be used to access your device. E.g. if the hostname is ml-home you can access it using [http://ml-home.local](http://ml-home.local) | |
| * Restart the device to make the hostname known to your network (go to system status, scroll down and press restart) | |
| * Enter the hostname in your browser e.g. [http://ml-home.local](http://ml-home.local) | |
| * ESP32-P4-Nano: November 16, 2025: hostname is not working correctly, use IP address instead | |
| !!! tip "Choose driver" | |
| FastLED driver is best to start with if you have a normal (ws2812) LED strip or panel. | |
| Choose the Parallel LED Driver if you have more then 4 LED strips or panels or non standard LEDS (e.g. RGBW lights, curtains...). Other drivers (Virtual, Hub,) not supported yet. | |
| In some cases restart the device to make layout changes effective is needed. | |
| !!! info "Tools used" | |
| The installer is powered by [ESP Web Tools](https://esphome.github.io/esp-web-tools) | |
| See also [ESP-Web-Tools-Tutorial](https://github.com/witnessmenow/ESP-Web-Tools-Tutorial/blob/main/README.md) | |
| [ESPConnect on GitHub](https://github.com/thelastoutpostworkshop/ESPConnect) | |
| ESPConnect is based on [Jason2866](https://github.com/Jason2866)'s [WebSerial ESPTool](https://github.com/Jason2866/WebSerial_ESPTool/tree/development). | |
| ### USB-to-serial chip drivers | |
| In case your computer does not recognise your connected ESP32, you might need to install the right USB-to-serial chip drivers. | |
| Below are the drivers for common chips used in ESP devices. See the images below to see what chip your ESP32 has. | |
| * CP210x drivers: [Windows & Mac](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers) | |
| * CH342, CH343, CH9102 drivers: [Windows](https://www.wch.cn/downloads/CH343SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH34XSER_MAC_ZIP.html) (download via blue button with download icon)</li> | |
| * CH340, CH341 drivers: [Windows](https://www.wch.cn/downloads/CH341SER_ZIP.html), [Mac](https://www.wch.cn/downloads/CH341SER_MAC_ZIP.html) (download via blue button with download icon)</li> | |
| <img src="https://moonmodules.org/MoonLight/firmware/installer/images/ch9102.jpg" height="200" alt="CH9102 USB-to-serial chip"/> | |
| <img src="https://moonmodules.org/MoonLight/firmware/installer/images/cp210x-ch34x.jpg" height="200" alt="CP210x and CH34x USB-to-serial chips"/> |
🧰 Tools
🪛 LanguageTool
[style] ~278-~278: Using many exclamation marks might seem excessive (in this case: 40 exclamation marks for a text that’s 7695 characters long)
Context: ...e layout changes effective is needed. !!! info "Tools used" The installer is ...
(EN_EXCESSIVE_EXCLAMATION)
🪛 markdownlint-cli2 (0.18.1)
295-295: Images should have alternate text (alt text)
(MD045, no-alt-text)
296-296: Images should have alternate text (alt text)
(MD045, no-alt-text)
🤖 Prompt for AI Agents
In @docs/develop/installation.md around lines 245 - 296, The two <img> tags
currently missing alt attributes should be updated to include meaningful alt
text (e.g., alt="CH9102 USB-to-serial chip" and alt="CP210x and CH34x
USB-to-serial chips") so they meet accessibility MD045; locate the <img
src="...ch9102.jpg"> and <img src="...cp210x-ch34x.jpg"> lines and add alt="..."
accordingly, and while here, reduce excessive exclamation styling in the
"tip"/"warning"/"info" admonition titles by removing or trimming exclamation
marks to maintain a more professional tone (adjust the admonition headings like
"!!! tip", "!!! warning", "!!! info" content strings).
Summary by CodeRabbit
Documentation
New Features
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.