Skip to content

Livescripts tutorial, WiFi handling, Network in/out, rest API#161

Merged
ewowi merged 4 commits intomainfrom
livescript-tuning
Apr 4, 2026
Merged

Livescripts tutorial, WiFi handling, Network in/out, rest API#161
ewowi merged 4 commits intomainfrom
livescript-tuning

Conversation

@ewowi
Copy link
Copy Markdown
Collaborator

@ewowi ewowi commented Apr 3, 2026

Summary by CodeRabbit

  • Documentation

    • Expanded tutorials and guides with pre-start notes, renamed step examples, GIF previews, Live Scripts limitations/workarounds, Svelte contributor guidance, and multiple overview/driver docs updates.
  • Bug Fixes

    • Improved Wi‑Fi connection/retry handling, prevented re-entrant connection attempts, rejected misaligned network packet writes, and added clearer network/driver status reporting.
  • New Features / Improvements

    • Added audio‑reactive PaintBrush effect, new scripting drawing/math bindings and a 3D-noise scale control, refined virtual-layer DMX/network write handling, and added API quick-links in several UI settings pages.

docs
====
- update live scripts tutorial

firmware
=======
- latest ESPLiveScript (fix setRGB(random16(NUM_LEDS), CRGB(0, 0, 255)))

backend
=======
- WiFiSettings Service: add _connecting add  WiFi.onEvent, manageSTA/configureNetwork/onStationModeDisconnected: set and check _connecting
- Live scripts: file update: startCompile instead of compileAndRun
- Network in: writePixels: Resolve bounds against the target layer's own light count, handleDDP: Reject mid-pixel offsets
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Walkthrough

Updates across docs, LiveScripts, drivers, WiFi service, UI, and build pins: adds new LiveScript examples/externals and async compile, hardens NetworkIn/Out/DMX virtual-layer bounds and status, prevents WiFi reconnect re-entrancy with a _connecting flag and event handler, and adds API quick-links in the UI.

Changes

Cohort / File(s) Summary
Docs & Tutorials
docs/moonlight/effects-tutorial.md, docs/moonlight/livescripts.md, docs/moonlight/drivers.md, docs/moonlight/overview.md, docs/moonbase/overview.md, docs/gettingstarted/installer.md, docs/index.md, CLAUDE.md, misc/instructions/*
Added “Important notes”, renamed examples, added GIFs, expanded LiveScripts limitations and Svelte guidance, and updated multiple links and overview content.
LiveScript examples & assets
livescripts/Effects/E_*.sc, livescripts/Effects/E_PaintBrush.sc
Renamed example scripts, adjusted beatsin8(...) calls to 5-arg form, introduced intermediate CRGB color variables, and added new E_PaintBrush.sc with controls and loop logic plus GIFs.
LiveScript runtime & API
src/MoonBase/LiveScriptNode.cpp, src/MoonLight/Modules/ModuleLiveScripts.h
Registered new externals (sqrt, drawLine3D, blur2d) and switched live-script start flow from synchronous compileAndRun() to asynchronous startCompile().
WiFi connection management
lib/framework/WiFiSettingsService.h, lib/framework/WiFiSettingsService.cpp
Added private _connecting flag, set it before WiFi.begin(), added ARDUINO_EVENT_WIFI_STA_CONNECTED handler to clear the flag and reset timer, prevented re-entrant connect attempts, and adjusted disconnect/retry behavior.
Network drivers & pixel I/O
src/MoonLight/Nodes/Drivers/D_NetworkIn.h, src/MoonLight/Nodes/Drivers/D_NetworkOut.h, src/MoonLight/Nodes/Drivers/D_DMXIn.h
NetworkIn: added status/receive tracking, per-layer clamping, virtual-channel write path, and DDP alignment checks. NetworkOut: added status/lastStatusCode, status lifecycle and protocol checks. DMXIn: virtual-layer writes now target virtualChannels with added validation.
LayerManager backward-compat
src/MoonLight/Layers/LayerManager.h
Added checks to ignore legacy bare preset keys when per-layer keys are absent and early-return on invalid selected layer.
Modules & minor tweaks
src/MoonLight/Modules/ModuleEffects.h, src/MoonLight/Modules/ModuleLiveScripts.h, platformio.ini, livescripts/Effects/E_hello.sc
Disabled a debug log, switched to async compile start, bumped APP_DATE and FastLED/ESPLiveScript pins, and minor script constructor tweak (rgb()CRGB()).
Interface: REST quick-links
interface/src/routes/.../*.svelte
Added API quick-link icons/links (Api icon → /rest/...) across multiple settings pages (MQTT, NTP, Ethernet, FileManager, Module, CoreDump, SystemStatus, AP, Wi‑Fi).

Sequence Diagram(s)

sequenceDiagram
    participant Mgmt as Connection Manager\n(manageSTA)
    participant WiFi as WiFi Stack
    participant Event as Event Handler

    rect rgba(100,150,200,0.5)
    Note over Mgmt,Event: Start connection attempt
    Mgmt->>Mgmt: set _connecting = true
    Mgmt->>WiFi: WiFi.begin(ssid,pwd)
    end

    rect rgba(150,200,100,0.5)
    Note over Mgmt: Prevent re-entry while connecting
    Mgmt->>Mgmt: if _connecting == true → early return
    end

    rect rgba(200,150,100,0.5)
    Note over WiFi,Event: On successful connect
    WiFi->>Event: ARDUINO_EVENT_WIFI_STA_CONNECTED
    Event->>Mgmt: clear _connecting = false
    Event->>Mgmt: set _lastConnectionAttempt = millis()
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nudged a flag, made connects behave,

Scripts wake softly, no rush to save.
Paintbrush hops, lines bloom in light,
Drivers guard pixels through day and night.
Carrots for code — a tiny debug rave!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and overly broad, using generic terms like 'tuning' and listing multiple unrelated components without clearly identifying the main change. Consider a more specific title that highlights the primary change, such as 'Improve WiFi reconnection handling and add LiveScript external bindings' or 'Add WiFi connection guards and expand LiveScript runtime capabilities'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch livescript-tuning

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (3)
src/MoonLight/Nodes/Drivers/D_NetworkIn.h (1)

215-218: Consider aligning the pre-check with the new layer-aware bounds logic.

Line 216 checks startPixelU against the physical layer's nrOfLights, but writePixels() now correctly handles virtual layers that may have a different light count. If a virtual layer is selected (layer > 0) and has more lights than the physical layer, this pre-check would incorrectly reject valid DDP packets before they reach writePixels().

This is pre-existing behavior, but the new writePixels() logic makes the inconsistency more apparent. Art-Net and E1.31 handlers don't have this pre-check and rely entirely on writePixels() for bounds validation.

♻️ Suggested fix to align with other handlers
     const uint32_t startPixelU = offset / channelsPerLight;
-    if (startPixelU >= static_cast<uint32_t>(layerP.lights.header.nrOfLights)) return;
 
     writePixels(static_cast<int>(startPixelU), safeDataLen / static_cast<int>(channelsPerLight), pixelData);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonLight/Nodes/Drivers/D_NetworkIn.h` around lines 215 - 218, The
pre-check comparing startPixelU to layerP.lights.header.nrOfLights incorrectly
rejects packets for virtual layers; remove or change this check so bounds
validation is delegated to writePixels (which already handles virtual vs
physical layer counts). Specifically, update the logic around the startPixelU
calculation and the early return so it either uses the layer-aware light count
(the virtual layer's nrOfLights) or omits the check entirely and lets
writePixels(static_cast<int>(startPixelU), ...) perform the validation; adjust
references to startPixelU, layerP.lights.header.nrOfLights, and writePixels
accordingly.
docs/moonlight/effects-tutorial.md (2)

366-366: Remove debugging comment.

The comment // this works !! appears to be a testing artifact and doesn't add instructional value. Consider removing it to keep the documentation clean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/moonlight/effects-tutorial.md` at line 366, Remove the debugging/testing
comment appended to the example call so the documentation stays clean: edit the
line containing the call to setRGBXYZ(x, y, z, ColorFromPalette(n, bright)) and
delete the trailing comment "// this works !!" (leave the function call and
parameters intact).

186-187: Inconsistent ColorFromPalette usage pattern.

Some examples use intermediate variables before calling setRGBXY (lines 186-187, 261-262), while others pass ColorFromPalette(...) inline (line 160). If the inline constructor limitation mentioned in the Important notes has been resolved by the library update, these intermediate variables are unnecessary. If workarounds are still needed in some contexts, document when to use which pattern.

Also applies to: 261-262

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/moonlight/effects-tutorial.md` around lines 186 - 187, The examples show
mixed usage of ColorFromPalette: some assign CRGB color = ColorFromPalette(...)
then call setRGBXY(x,y,color) (eg. the blocks around ColorFromPalette/setRGBXY
at lines referenced) while others call setRGBXY(x,y, ColorFromPalette(...))
inline; decide and unify: if the library limitation preventing inline
temporaries has been fixed, remove the intermediate CRGB variable (keep
setRGBXY(x,y, ColorFromPalette(...)) everywhere) and update the two occurrences
that currently use a temporary; if the limitation still applies, keep the
temporary pattern and add a brief note near the examples (mentioning
ColorFromPalette and setRGBXY) explaining when the workaround (temporary CRGB
variable) is required vs when inline calls are safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/moonlight/effects-tutorial.md`:
- Line 269: The watchdog note is vague; update the docs around the E_Noise3D
example to explicitly explain that heavy computation inside Arduino's loop()
(e.g., triple-nested loops calling inoise8 per-pixel) can trigger watchdog
resets, and provide concrete fixes: either insert a yield/delay(1) inside the
per-frame loop, reduce workload by lowering scale or processing fewer pixels per
frame, or move expensive noise computation off the tight loop; reference the
loop() function, the inoise8 call, and the E_Noise3D example so readers can find
and apply the workaround.

In `@docs/moonlight/livescripts.md`:
- Line 44: The doc note about inline constructor arguments (example: setRGB(i,
CRGB(0, 0, 255))) is ambiguous given the updated ESPLiveScript in platformio.ini
and working examples like E_hello.sc and the tutorial final example; either
update the sentence to state that the inline-CRGB issue was fixed in the current
ESPLiveScript version (reference ESPLiveScript/platformio.ini) or change all
examples that use inline CRGB(...) to use the intermediate variable form (CRGB
color = CRGB(...); setRGB(..., color)) so the docs and examples are consistent;
locate occurrences of setRGB, CRGB, E_hello.sc and the tutorial final example
and apply the chosen consistency fix.

In `@src/MoonLight/Modules/ModuleLiveScripts.h`:
- Around line 54-55: The current code sets _moduleEffects->requestUIUpdate
immediately after calling liveScriptNode->startCompile(), but startCompile() is
asynchronous (it may queue needsCompile/compileInProgress), so move the UI
refresh to the async compile completion path instead: remove the immediate
assignment after startCompile() and instead re-arm
_moduleEffects->requestUIUpdate at the point where the deferred compile/execute
finishes (the callback/worker or the code path that runs the deferred execute
for liveScriptNode), so the UI is updated only after the new script has actually
been compiled/executed.

---

Nitpick comments:
In `@docs/moonlight/effects-tutorial.md`:
- Line 366: Remove the debugging/testing comment appended to the example call so
the documentation stays clean: edit the line containing the call to setRGBXYZ(x,
y, z, ColorFromPalette(n, bright)) and delete the trailing comment "// this
works !!" (leave the function call and parameters intact).
- Around line 186-187: The examples show mixed usage of ColorFromPalette: some
assign CRGB color = ColorFromPalette(...) then call setRGBXY(x,y,color) (eg. the
blocks around ColorFromPalette/setRGBXY at lines referenced) while others call
setRGBXY(x,y, ColorFromPalette(...)) inline; decide and unify: if the library
limitation preventing inline temporaries has been fixed, remove the intermediate
CRGB variable (keep setRGBXY(x,y, ColorFromPalette(...)) everywhere) and update
the two occurrences that currently use a temporary; if the limitation still
applies, keep the temporary pattern and add a brief note near the examples
(mentioning ColorFromPalette and setRGBXY) explaining when the workaround
(temporary CRGB variable) is required vs when inline calls are safe.

In `@src/MoonLight/Nodes/Drivers/D_NetworkIn.h`:
- Around line 215-218: The pre-check comparing startPixelU to
layerP.lights.header.nrOfLights incorrectly rejects packets for virtual layers;
remove or change this check so bounds validation is delegated to writePixels
(which already handles virtual vs physical layer counts). Specifically, update
the logic around the startPixelU calculation and the early return so it either
uses the layer-aware light count (the virtual layer's nrOfLights) or omits the
check entirely and lets writePixels(static_cast<int>(startPixelU), ...) perform
the validation; adjust references to startPixelU,
layerP.lights.header.nrOfLights, and writePixels accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 12867819-f5c5-4a46-907b-86802e78e67f

📥 Commits

Reviewing files that changed from the base of the PR and between 7946f77 and 865590f.

⛔ Files ignored due to path filters (11)
  • docs/media/moonlight/effects/E_Ball2D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Cosmic3D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Geq2D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Hello1D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Noise2D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Noise3D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Orbit2D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Oscillate2D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Random1D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Sweep2D.gif is excluded by !**/*.gif
  • docs/media/moonlight/effects/E_Vu1D.gif is excluded by !**/*.gif
📒 Files selected for processing (9)
  • docs/moonlight/effects-tutorial.md
  • docs/moonlight/livescripts.md
  • lib/framework/WiFiSettingsService.cpp
  • lib/framework/WiFiSettingsService.h
  • livescripts/Effects/E_hello.sc
  • platformio.ini
  • src/MoonLight/Modules/ModuleEffects.h
  • src/MoonLight/Modules/ModuleLiveScripts.h
  • src/MoonLight/Nodes/Drivers/D_NetworkIn.h

Doc updates

Live scripts
==========
- Add E_PaintBrush.sc

Backend
=======
- Live scripts: add drawLine3D and blur2D and sqrt, file Update: no requestUIUpdate
- LayerManager: check for old style presets
- DMX in / Network in: store layers in virtualChannels
- Network in/out: add status
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a 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 (1)
src/MoonLight/Nodes/Drivers/D_NetworkIn.h (1)

239-252: ⚠️ Potential issue | 🟠 Major

DDP still caps virtual-layer offsets at the physical light count.

This handler rejects startPixelU against layerP.lights.header.nrOfLights before writePixels() gets to use the target layer's own size. For virtual layouts larger than the physical fixture, valid DDP offsets in the upper part of the virtual grid now return early. Use the same per-layer max-light calculation here before the early return.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonLight/Nodes/Drivers/D_NetworkIn.h` around lines 239 - 252, The
handler currently rejects startPixelU against the physical count
(layerP.lights.header.nrOfLights) which incorrectly drops valid DDP offsets for
virtual layers; change the early-return check to use the per-layer maximum
lights calculation used by the target layer (the same calculation used before
calling writePixels) instead of layerP.lights.header.nrOfLights. Compute the
layer-specific max lights (using the existing per-layer calc/function used
elsewhere in this file/logic) and replace the if (startPixelU >=
static_cast<uint32_t>(layerP.lights.header.nrOfLights)) return; with a check
against that per-layer max before calling writePixels, leaving the rest of the
offset/dataLen/safeDataLen logic intact.
🧹 Nitpick comments (1)
livescripts/Effects/E_PaintBrush.sc (1)

39-48: Avoid sqrt in the hot loop when only checking > 1.

Line 42 and Line 47 can use squared-distance checks to reduce per-frame compute cost.

♻️ Suggested optimization
-      float dx = x2 - x1;
-      float dy = y2 - y1;
-      float dz = z2 - z1;
-      float len = sqrt(dx * dx + dy * dy + dz * dz);
-      if (len > 1) drawLine3D(x1, y1, z1, x2, y2, z2, color);
+      int dx = (int)x2 - (int)x1;
+      int dy = (int)y2 - (int)y1;
+      int dz = (int)z2 - (int)z1;
+      int dist2 = dx * dx + dy * dy + dz * dz;
+      if (dist2 > 1) drawLine3D(x1, y1, z1, x2, y2, z2, color);
     } else {
-      float dx = x2 - x1;
-      float dy = y2 - y1;
-      float len = sqrt(dx * dx + dy * dy);
-      if (len > 1) drawLine(x1, y1, x2, y2, color);
+      int dx = (int)x2 - (int)x1;
+      int dy = (int)y2 - (int)y1;
+      int dist2 = dx * dx + dy * dy;
+      if (dist2 > 1) drawLine(x1, y1, x2, y2, color);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@livescripts/Effects/E_PaintBrush.sc` around lines 39 - 48, The code currently
computes len = sqrt(dx*dx + dy*dy (+ dz*dz)) and checks len > 1 before calling
drawLine3D/drawLine; replace the sqrt with a squared-distance check to avoid the
hot-loop sqrt: compute lenSq = dx*dx + dy*dy (+ dz*dz) and use if (lenSq > 1.0)
to decide calling drawLine3D(x1,y1,z1,x2,y2,z2,color) or
drawLine(x1,y1,x2,y2,color). Update both branches where dx, dy, dz and len are
used (the blocks that call drawLine3D and drawLine) and remove the unused sqrt
and len variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/MoonLight/Nodes/Drivers/D_DMXIn.h`:
- Around line 242-249: This branch must not write directly into
VirtualLayer::virtualChannels from driverTask because
compositeTo()/compositeLayers() reads that buffer outside swapMutex and the
layer may be unallocated; instead stop the memcpy into vLayer->virtualChannels
here and route the incoming DMX into the mapped channelsD path or hand it off to
the effect task for safe mutation. Concretely: remove the memcpy to
vLayer->virtualChannels in this block (the code touching VirtualLayer* vLayer,
virtualChannels, and nrOfLights) and replace with either (a) writing the input
into the corresponding channelsD mapping for that layer/header, or (b)
enqueueing the input data to the effect task which will take swapMutex and
update virtualChannels safely; ensure you still check for vLayer existence but
never mutate virtualChannels from driverTask and preserve use of swapMutex only
in the effect/composite update path.

In `@src/MoonLight/Nodes/Drivers/D_NetworkIn.h`:
- Around line 183-190: The loop currently writes incoming DMX into
vLayer->virtualChannels under the driverTask protected only by swapMutex, which
races with compositeLayers()/compositeTo() and can miss-unset when
virtualChannels is unallocated; instead stop routing network input into
virtualChannels from this loop—always write into the physical buffer
layerP.lights.channelsD (using the same ledIndex and channelsPerLight math) or,
if you need to update a virtual layer, enqueue the update to the effect/driver
task that owns virtualChannels so the write happens under the same context that
allocates and reads it; update the branch that references vLayer,
virtualChannels, swapMutex, compositeTo(), and channelsD accordingly.

In `@src/MoonLight/Nodes/Drivers/D_NetworkOut.h`:
- Around line 501-510: The status is set to "Sending DDP" before calling
loopDDP(), which can immediately return for unsupported layouts and thus show a
misleading state; modify the branch handling protocol == 1 in D_NetworkOut (use
symbols: protocol, lastStatusCode, status, updateControl, loopDDP, header) to
only change lastStatusCode and call updateControl("status", status) after
verifying the frame/layout is actually supported by loopDDP (i.e., perform the
same channel-count/layout check used inside loopDDP before setting the status),
or alternatively set an explicit unsupported-layout status when the check fails
so the UI reflects dropped/unsupported frames rather than "Sending DDP".

---

Outside diff comments:
In `@src/MoonLight/Nodes/Drivers/D_NetworkIn.h`:
- Around line 239-252: The handler currently rejects startPixelU against the
physical count (layerP.lights.header.nrOfLights) which incorrectly drops valid
DDP offsets for virtual layers; change the early-return check to use the
per-layer maximum lights calculation used by the target layer (the same
calculation used before calling writePixels) instead of
layerP.lights.header.nrOfLights. Compute the layer-specific max lights (using
the existing per-layer calc/function used elsewhere in this file/logic) and
replace the if (startPixelU >=
static_cast<uint32_t>(layerP.lights.header.nrOfLights)) return; with a check
against that per-layer max before calling writePixels, leaving the rest of the
offset/dataLen/safeDataLen logic intact.

---

Nitpick comments:
In `@livescripts/Effects/E_PaintBrush.sc`:
- Around line 39-48: The code currently computes len = sqrt(dx*dx + dy*dy (+
dz*dz)) and checks len > 1 before calling drawLine3D/drawLine; replace the sqrt
with a squared-distance check to avoid the hot-loop sqrt: compute lenSq = dx*dx
+ dy*dy (+ dz*dz) and use if (lenSq > 1.0) to decide calling
drawLine3D(x1,y1,z1,x2,y2,z2,color) or drawLine(x1,y1,x2,y2,color). Update both
branches where dx, dy, dz and len are used (the blocks that call drawLine3D and
drawLine) and remove the unused sqrt and len variable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f059cae9-106f-4256-b14e-1dbf13cb78f2

📥 Commits

Reviewing files that changed from the base of the PR and between 865590f and 9abc4a2.

📒 Files selected for processing (13)
  • CLAUDE.md
  • docs/moonlight/drivers.md
  • docs/moonlight/effects-tutorial.md
  • livescripts/Effects/E_PaintBrush.sc
  • misc/instructions/MoonLight.md
  • misc/instructions/svelte.instructions.md
  • platformio.ini
  • src/MoonBase/LiveScriptNode.cpp
  • src/MoonLight/Layers/LayerManager.h
  • src/MoonLight/Modules/ModuleLiveScripts.h
  • src/MoonLight/Nodes/Drivers/D_DMXIn.h
  • src/MoonLight/Nodes/Drivers/D_NetworkIn.h
  • src/MoonLight/Nodes/Drivers/D_NetworkOut.h
✅ Files skipped from review due to trivial changes (4)
  • misc/instructions/MoonLight.md
  • CLAUDE.md
  • misc/instructions/svelte.instructions.md
  • docs/moonlight/drivers.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/MoonLight/Modules/ModuleLiveScripts.h
  • docs/moonlight/effects-tutorial.md

Docs
====
- Rework moonlight and moonbase overview.md: add Reporting issues and refer to it across docs

frontend
=======
- add link to (rest) api on all module pages

backend
=======
- latest FastLED
- Network in: ddp: Compute per-layer max
- Network out: ddp light preset check
- DMX in: processChannels: validates vLayer->virtualChannels inside the mutex
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (3)
interface/src/routes/ethernet/Ethernet.svelte (1)

562-565: Consider using protocol-relative or current protocol URL.

The hardcoded http:// protocol may cause mixed-content issues if the UI is served over HTTPS. The link would either fail or trigger a browser security warning.

Proposed fix to use the current page protocol
-		<a href="http://{page.url.host}/rest/ethernetSettings" target="_blank" rel="noopener noreferrer" title="API: http://{page.url.host}/rest/ethernetSettings"><Api class="mr-2 h-6 w-6 shrink-0 self-end" /></a>
+		<a href="{page.url.protocol}//{page.url.host}/rest/ethernetSettings" target="_blank" rel="noopener noreferrer" title="API: {page.url.protocol}//{page.url.host}/rest/ethernetSettings"><Api class="mr-2 h-6 w-6 shrink-0 self-end" /></a>

Alternatively, use a simple relative path /rest/ethernetSettings without the host, which inherits the current origin automatically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/ethernet/Ethernet.svelte` around lines 562 - 565, The
anchor uses a hardcoded "http://" which can cause mixed-content issues; update
the <a> element that currently builds href/title with
"http://{page.url.host}/rest/ethernetSettings" (and its title attribute) to use
a protocol-agnostic URL—preferably a relative path "/rest/ethernetSettings" so
it inherits the current origin, and ensure the title string is updated
accordingly; adjust the anchor around the Api component to use the new
href/title values.
interface/src/routes/wifi/ap/Accesspoint.svelte (1)

426-429: Same protocol hardcoding issue as other files.

Using hardcoded http:// may cause mixed-content issues when served over HTTPS. Consider using page.url.protocol or a relative path /rest/apSettings for consistency and to avoid mixed-content browser warnings.

Proposed fix
-		<a href="http://{page.url.host}/rest/apSettings" target="_blank" rel="noopener noreferrer" title="API: http://{page.url.host}/rest/apSettings"><Api class="mr-2 h-6 w-6 shrink-0 self-end" /></a>
+		<a href="{page.url.protocol}//{page.url.host}/rest/apSettings" target="_blank" rel="noopener noreferrer" title="API: {page.url.protocol}//{page.url.host}/rest/apSettings"><Api class="mr-2 h-6 w-6 shrink-0 self-end" /></a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/wifi/ap/Accesspoint.svelte` around lines 426 - 429, The
anchor uses a hardcoded "http://" in the href (the <a
href="http://{page.url.host}/rest/apSettings" ...> containing the Api
component), which causes mixed-content when served over HTTPS; update the href
to either use the page.url.protocol value (e.g., construct with
"{page.url.protocol}//{page.url.host}/rest/apSettings") or, better, use a
protocol-relative or relative path like "/rest/apSettings" so the link follows
the current page protocol and avoids mixed-content issues.
interface/src/routes/moonbase/module/Module.svelte (1)

214-223: Handle null module parameter and protocol hardcoding.

Two issues with this link:

  1. Null module param: page.url.searchParams.get('module') returns null if the parameter is missing, resulting in a link to /rest/null. Consider adding a fallback or conditionally rendering the link.

  2. Hardcoded http://: Same as other files—may cause mixed-content issues over HTTPS.

Proposed fix
 	<!-- 🌙 link to api -->
+	{`@const` moduleName = page.url.searchParams.get('module')}
+	{`#if` moduleName}
 	<div class="flex justify-end px-4 pb-3">
 		<a
-			href="http://{page.url.host}/rest/{page.url.searchParams.get('module')}"
+			href="{page.url.protocol}//{page.url.host}/rest/{moduleName}"
 			target="_blank"
 			rel="noopener noreferrer"
-			title="API: http://{page.url.host}/rest/{page.url.searchParams.get('module')}"
+			title="API: {page.url.protocol}//{page.url.host}/rest/{moduleName}"
 			><Api class="mr-2 h-6 w-6 shrink-0 self-end" /></a
 		>
 	</div>
+	{/if}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/moonbase/module/Module.svelte` around lines 214 - 223,
The anchor currently builds a URL using hardcoded "http://" and
page.url.searchParams.get('module') which can be null; update Module.svelte to
(1) only render the <a> (the anchor that wraps <Api>) when
page.url.searchParams.get('module') is non-null/non-empty or supply a safe
fallback (e.g., omit "null" by using the param value || ''), and (2) derive the
scheme from the runtime URL (use page.url.protocol or the runtime
location.protocol to prefix "//" or `${protocol}//`) instead of hardcoding
"http://", so the anchor always uses the correct protocol and avoids
mixed-content issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/moonlight/overview.md`:
- Around line 169-181: The documentation currently mixes REST endpoint casing
("/rest/ModuleEffects" vs lowercase examples "/rest/effects", "/rest/drivers",
"/rest/lightscontrol"); pick one canonical format (prefer lowercase plural
endpoints like "/rest/effects") and update the earlier URL example that mentions
"/rest/ModuleEffects" to match the chosen canonical form, and ensure the three
example endpoints (effects, drivers, lightscontrol) and any explanatory text all
use the same casing and path style (adjust references in the paragraph that
constructs the URL and the tip about browser access).
- Line 108: Typo: change the frontend stack entry that currently reads "TailWind
4" to the correct casing "Tailwind 4" in the overview text (look for the string
"TailWind 4" in the document where the frontend stack is listed alongside
"Svelte 5, DaisyUI 5"); update that token to "Tailwind 4" to fix the naming
consistency.

---

Nitpick comments:
In `@interface/src/routes/ethernet/Ethernet.svelte`:
- Around line 562-565: The anchor uses a hardcoded "http://" which can cause
mixed-content issues; update the <a> element that currently builds href/title
with "http://{page.url.host}/rest/ethernetSettings" (and its title attribute) to
use a protocol-agnostic URL—preferably a relative path "/rest/ethernetSettings"
so it inherits the current origin, and ensure the title string is updated
accordingly; adjust the anchor around the Api component to use the new
href/title values.

In `@interface/src/routes/moonbase/module/Module.svelte`:
- Around line 214-223: The anchor currently builds a URL using hardcoded
"http://" and page.url.searchParams.get('module') which can be null; update
Module.svelte to (1) only render the <a> (the anchor that wraps <Api>) when
page.url.searchParams.get('module') is non-null/non-empty or supply a safe
fallback (e.g., omit "null" by using the param value || ''), and (2) derive the
scheme from the runtime URL (use page.url.protocol or the runtime
location.protocol to prefix "//" or `${protocol}//`) instead of hardcoding
"http://", so the anchor always uses the correct protocol and avoids
mixed-content issues.

In `@interface/src/routes/wifi/ap/Accesspoint.svelte`:
- Around line 426-429: The anchor uses a hardcoded "http://" in the href (the <a
href="http://{page.url.host}/rest/apSettings" ...> containing the Api
component), which causes mixed-content when served over HTTPS; update the href
to either use the page.url.protocol value (e.g., construct with
"{page.url.protocol}//{page.url.host}/rest/apSettings") or, better, use a
protocol-relative or relative path like "/rest/apSettings" so the link follows
the current page protocol and avoids mixed-content issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bc2492f7-c36c-4e4a-8d37-ab4467b6f674

📥 Commits

Reviewing files that changed from the base of the PR and between 9abc4a2 and 5b887e8.

📒 Files selected for processing (19)
  • docs/gettingstarted/installer.md
  • docs/index.md
  • docs/moonbase/overview.md
  • docs/moonlight/overview.md
  • interface/src/routes/connections/mqtt/MQTT.svelte
  • interface/src/routes/connections/ntp/NTP.svelte
  • interface/src/routes/ethernet/Ethernet.svelte
  • interface/src/routes/moonbase/filemanager/FileManager.svelte
  • interface/src/routes/moonbase/module/Module.svelte
  • interface/src/routes/system/coredump/+page.svelte
  • interface/src/routes/system/status/SystemStatus.svelte
  • interface/src/routes/wifi/ap/Accesspoint.svelte
  • interface/src/routes/wifi/sta/Wifi.svelte
  • lib/framework/WWWData.h
  • livescripts/Effects/E_PaintBrush.sc
  • platformio.ini
  • src/MoonLight/Nodes/Drivers/D_DMXIn.h
  • src/MoonLight/Nodes/Drivers/D_NetworkIn.h
  • src/MoonLight/Nodes/Drivers/D_NetworkOut.h
✅ Files skipped from review due to trivial changes (9)
  • interface/src/routes/moonbase/filemanager/FileManager.svelte
  • interface/src/routes/system/coredump/+page.svelte
  • interface/src/routes/wifi/sta/Wifi.svelte
  • docs/gettingstarted/installer.md
  • interface/src/routes/connections/ntp/NTP.svelte
  • interface/src/routes/connections/mqtt/MQTT.svelte
  • interface/src/routes/system/status/SystemStatus.svelte
  • docs/index.md
  • docs/moonbase/overview.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/MoonLight/Nodes/Drivers/D_NetworkIn.h

@ewowi
Copy link
Copy Markdown
Collaborator Author

ewowi commented Apr 4, 2026

@coderabbitai, I am about to merge this PR. Please produce three outputs:

  1. PR review — in-depth review of all commits: a concise summary of what changed and why, a merge recommendation, and a prioritised list of follow-up actions. For the most urgent items (blockers or high-risk changes), include a ready-to-paste prompt that a Claude Code agent can execute immediately before merge.

  2. End-user docs prompt — a ready-to-paste prompt for a Claude Code agent to update /docs. Rules: only describe usage implications (what changed for the user); no internals, no code, no architecture; check existing pages before adding — update in place rather than duplicating; keep additions compact and user-friendly.

  3. Developer docs prompt — a ready-to-paste prompt for a Claude Code agent to update /docs/develop. Rules: target contributors, not end users; be concise — if the detail is already in the code or commit messages, do not repeat it; focus on decisions, patterns, and guidance that are not obvious from reading the source.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Tip

For best results, initiate chat on the files or code changes.

@ewowi Here are the three requested outputs.


1 · PR Review

Summary of changes

Area What changed Risk
WiFiSettingsService Added _connecting flag to prevent re-entrant WiFi.begin() calls; moved reconnect-trigger logic from onStationModeDisconnected() into an event handler for ARDUINO_EVENT_WIFI_STA_CONNECTED; manageSTA() now early-returns when already connecting 🔴 High
D_NetworkIn Virtual-layer pixel write path changed from forEachLightIndex() → direct virtualChannels[] write; DDP alignment guard added; status text control + lastPacketMs tracking 🟠 Medium-High
D_DMXIn Same forEachLightIndex()virtualChannels[] switch 🟠 Medium
ModuleLiveScripts compileAndRun()startCompile() in file-change handler; direct requestUIUpdate removed (delegated to LayerManager) 🟡 Medium
LayerManager Backward-compat guard: skips bare brightness/start/end updates when _0-keyed versions are absent in state 🟡 Medium
LiveScriptNode Added sqrt, drawLine3D, blur2d external bindings 🟢 Low
D_NetworkOut Added status control + per-protocol status transitions, DDP unsupported-layout guard 🟢 Low
E_PaintBrush.sc New audio-reactive 2D/3D paintbrush live-script effect 🟢 Low
ESPLiveScript pin Updated to 48715099 (20260402) enabling partial CRGB inline-arg fix 🟢 Low
FastLED pin Updated to eca4af57 (20260404) 🟢 Low
Docs / UI Effects tutorial updated, livescripts Important-notes section, API icon links on 10+ UI pages 🟢 Low

Merge recommendation

⚠️ Conditional merge — the WiFi and virtual-layer changes are solid improvements but carry non-trivial regression risk in edge cases. Review the two prompts below before merging if you want pre-merge hardening; otherwise merge and treat items 1–3 as P0 follow-ups.

Prioritised follow-up actions

P0 – Address before or immediately after merge

  1. WiFi _connecting liveness: If ARDUINO_EVENT_WIFI_STA_CONNECTED is never delivered (IDF event loop not yet running, or missed event), _connecting stays true permanently and WiFi recovery is blocked. A safety escape (e.g. millis() - _lastConnectionAttempt > 30000 forces _connecting = false) is needed in manageSTA().
  2. Virtual-layer write regression (NetworkIn/DMXIn): The old forEachLightIndex() respected the mapping table; the new direct virtualChannels[ledIndex] path assumes a flat 0-based index. Verify with a non-trivial virtual layer (e.g. zigzag or segment map) that pixels still land on correct LEDs.
  3. LayerManager null guard false-positive: A freshly created node writes UI state asynchronously. If _0 keys haven't been written yet when the first UI update arrives, brightness/start/end are silently dropped. Add a log warning so this is visible in the serial monitor.

P1 – Short follow-up PR

  1. E_PaintBrush.sc 3D path: sqrt is now available as an external binding; consider replacing the squared-distance comparison (> 1) with a true distance threshold for perceptually consistent line filtering.
  2. D_NetworkOut DDP unsupported-layout guard returns without sending but does not decrement any retry counter — verify no spin-loop risk on misconfigured nodes.
  3. ESPLiveScript inline-constructor fix is partial (per learning ba895631); update livescripts.md Known Limitations to note which patterns are now fixed vs still broken.

P2 – Nice-to-have

  1. UI API links use http:// hardcoded — will break on HTTPS deployments. Consider page.url.protocol instead.
  2. E_hello.sc rgb()CRGB() migration: verify no other example scripts still use the old rgb() helper.

🔴 Ready-to-paste Claude Code prompt — pre-merge hardening

You are reviewing MoonModules/MoonLight on the `livescript-tuning` branch.
Fix the following three issues before merge. Make minimal, targeted changes only.

## Fix 1 — WiFiSettingsService: _connecting liveness escape

File: lib/framework/WiFiSettingsService.cpp
In `manageSTA()`, after the early-return `if (_connecting) return;` block,
add a timeout escape: if `_connecting` is true AND
`millis() - _lastConnectionAttempt > 30000`, log a warning and reset
`_connecting = false` so the next call can retry. This prevents permanent
lock-out when ARDUINO_EVENT_WIFI_STA_CONNECTED is never delivered.

## Fix 2 — LayerManager: log warning for skipped legacy-preset updates

File: src/MoonLight/Layers/LayerManager.h
In `handleUpdate()`, in each branch that detects the _0 key is null and
returns early (the "old preset" path), add an EXT_LOGW/EXT_LOGD message
e.g. "LayerManager: skipping bare '%s' update — _0 key not yet present"
so developers can distinguish a legitimate old-preset from a timing bug.

## Fix 3 — D_NetworkIn: verify virtual-layer write path

File: src/MoonLight/Nodes/Drivers/D_NetworkIn.h
In `writePixels()`, for the virtual-layer (layer > 0) path, add an
assertion/log that confirms `vLayer->virtualChannels` is non-null and
`ledIndex < vLayer->nrOfLights` before writing. If the mapping table
(vLayer->mappingTableSize > 0) is non-empty, log a one-time warning that
the direct virtualChannels write ignores the mapping table, so this
regression is surfaced during testing with non-flat virtual layers.

After making changes, do NOT commit. Show a unified diff for review.

2 · End-user docs prompt

You are a technical writer updating the MoonLight end-user documentation
under /docs. Follow these rules strictly:
- Describe only what changed FOR THE USER (behaviour, UI, scripts).
- No internals, no code snippets, no architecture.
- Check every existing page before adding content — update in place,
  never duplicate.
- Keep additions compact: prefer a bullet or a sentence over a paragraph.

## Tasks

### docs/moonlight/livescripts.md
The "Important notes" section was added in this PR. Verify it is already
present. If it is, make no change. If not, add it with these user-facing
bullets only:
- The scripting language is under active development; expect rough edges.
- Example scripts live in the livescripts/Effects folder (accessible via
  File Manager).
- Debugging is serial-monitor only; no in-browser console.
- Heavy calculations in loop() can trigger a watchdog reboot; yield if
  needed.
- Passing inline-constructed values (e.g. CRGB(r,g,b)) as function
  arguments may fail in some patterns; use an intermediate variable as
  a workaround.
- Some expressions require an explicit cast to avoid unexpected values.

### docs/moonlight/effects-tutorial.md
The tutorial script filenames were renamed (e.g. E_hello.sc → E_Hello1D.sc,
E_sweep.sc → E_Sweep2D.sc, etc.). Verify all filename references in the
page match the new names. The VU meter now divides volume by 400 before
scaling to NUM_LEDS; update any user-visible description of the VU meter
sensitivity. No other functional changes need documenting here.

### docs/moonlight/drivers.md — Network In
Add a short note under the "Network In" driver section:
- The driver now displays a live "status" field in the UI showing whether
  it is receiving data, not connected, or has a configuration error.
- DDP packets that are not aligned to the configured channels-per-light
  are silently discarded.

### docs/moonlight/drivers.md — Network Out
Under the "Network Out" section, add:
- A live "status" field now shows connection state, missing IPs, or
  unsupported DDP layout directly in the UI.
- DDP output requires channels-per-light to be 3 or 4; other values
  will show an error and stop sending.

### docs/moonlight/overview.md (or appropriate page)
WiFi reconnection is now more robust: the firmware will no longer attempt
multiple simultaneous connection attempts when the access point is slow
to respond. No user action is required.

3 · Developer docs prompt

You are updating the MoonLight contributor/developer documentation under
/docs/develop. Rules:
- Target contributors, not end users.
- Be concise — do not repeat what is already obvious in the source code
  or commit messages.
- Focus on decisions, non-obvious patterns, and cross-cutting guidance.

## Tasks

### /docs/develop (find or create a WiFi / connectivity page)
Document the _connecting flag pattern introduced in WiFiSettingsService:
- `_connecting` is set true immediately before `WiFi.begin()`.
- It is cleared in the `ARDUINO_EVENT_WIFI_STA_CONNECTED` event handler
  (success path) and in `onStationModeDisconnected()` (failure path).
- `manageSTA()` early-returns when `_connecting` is true to prevent
  re-entrant calls.
- Decision rationale: the ESP-IDF WiFi stack is not re-entrant; calling
  `WiFi.begin()` while a previous attempt is still in the IDF state
  machine causes silent corruption.
- Known gap: if STA_CONNECTED is never delivered, `_connecting` stays
  true permanently (planned fix: 30-second timeout escape in manageSTA).

### /docs/develop (find or create a virtual-layer / driver page)
Document the new convention for virtual-layer pixel writes in drivers:
- Drivers MUST write pixel data into `VirtualLayer::virtualChannels[]`
  (one contiguous block per pixel index, channelsPerLight wide).
- Do NOT use `forEachLightIndex()` to write directly into
  `layerP.lights.channelsD`; that bypasses the compositor.
- `compositeTo()` is called separately by the layer pipeline and
  handles mapping-table traversal.
- Warning: direct virtualChannels writes assume a flat (0-based) pixel
  index and ignore any non-trivial mapping table. If a driver is used
  with a segment or zigzag virtual layer, verify pixel placement.

### /docs/develop (find or create a LiveScripts / bindings page)
Document the three new external bindings added to LiveScriptNode::setup():
- `float sqrt(float)` → sqrtf (C standard; safe in loop()).
- `void drawLine3D(x1,y1,z1,x2,y2,z2,CRGB)` → layer->drawLine3D.
- `void blur2d(uint8_t)` → layer->blur2d.
Note that ESPLiveScript is pinned to commit 48715099 which partially
fixes inline CRGB constructor arguments; some patterns still require an
intermediate variable (see livescripts Known Limitations).

### /docs/develop (find or create a LayerManager / preset page)
Document the backward-compat guard for legacy preset JSON keys:
- If `brightness_0` / `start_0` / `end_0` are absent in state JSON,
  the bare `brightness` / `start` / `end` update is ignored and defaults
  are kept (255, {0,0,0}, {100,100,100}).
- This prevents old presets from overwriting per-layer values with global
  values that lack layer context.
- Contributors adding new per-layer controls must follow the same `_N`
  suffix convention and add equivalent null guards.

### /docs/develop (find or create a LiveScripts compile page)
Document the ModuleLiveScripts file-change handler change:
- The handler now calls `startCompile()` (async, non-blocking) instead
  of `compileAndRun()` (synchronous).
- `requestUIUpdate` is no longer set directly by the handler; LayerManager
  sets it after the compiled effect's first execution completes.
- Rationale: prevents blocking the SvelteKit loop task during recompile
  of large scripts.

🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-19T12:50:37.875Z
Learning: In MoonModules/MoonLight PR `#153` (livescripts branch): The final merged set of 6 commits correctly implements: (1) counting semaphore (max 4) for LiveScript frame-sync with guarded scriptsToSync increment; (2) async startCompile() with compileInProgress/needsCompile deferred queue; (3) hasLoopTask gate to prevent loop() signalling when no task is running; (4) lightPreset_RGBCCT 5-channel support via offsetWhite2 sentinel (UINT8_MAX) through LightsHeader→parlio chain; (5) audio (bands[16], volume) and IMU (gravityX/Y/Z) external bindings for LiveScripts. All critical review items were resolved before merge.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-19T12:12:24.260Z
Learning: In MoonModules/MoonLight (src/MoonBase/LiveScriptNode.cpp + src/main.cpp): The LiveScript frame-sync uses a counting semaphore (max 4) gating script tasks, and a `scriptsToSync` counter consumed by `effectTask` via `ulTaskNotifyTake`. The critical invariant is: `scriptsToSync` MUST only be incremented when `xSemaphoreGive` succeeds. If `xSemaphoreGive` fails (semaphore full, 5th+ script), the script task is never unblocked and will never call `xTaskNotifyGive(effectTaskHandle)`, so incrementing `scriptsToSync` unconditionally causes a deadlock in `effectTask`. The correct behavior (already implemented) is to skip the frame for scripts beyond the 4-slot cap and log a warning. Do NOT suggest "always increment scriptsToSync" or increasing the cap as a fix — the guarded increment is the intentional deadlock-prevention mechanism.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 136
File: src/MoonBase/LiveScriptNode.cpp:22-39
Timestamp: 2026-03-04T09:15:48.019Z
Learning: In MoonModules/MoonLight LiveScriptNode (src/MoonBase/LiveScriptNode.cpp): The global `gNode` (Node*) and `WaitAnimationSync` (binary semaphore) are intentionally global because ESPLiveScript's `addExternalFunction()` only accepts plain C function pointers with no void* userData/context parameter, and `setFunctionToSync()` accepts a single void(*)() with no per-instance binding. There is no way to attach per-instance state to callbacks within the current ESPLiveScript API. This is a known limitation acknowledged with a `// todo: this is not working well with multiple scripts running!!!` comment at line 211. Fixing it would require modifying the ESPLiveScript library itself to support userData/context on callbacks.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 161
File: docs/moonlight/livescripts.md:44-44
Timestamp: 2026-04-03T12:18:07.382Z
Learning: In MoonModules/MoonLight (docs/moonlight/livescripts.md + ESPLiveScript library): The inline constructor argument limitation (e.g., `setRGB(i, CRGB(0, 0, 255))`) is only partially resolved as of PR `#161` (livescript-tuning). Some patterns like `setRGB(random16(NUM_LEDS), CRGB(0, 0, 255))` now work with the updated ESPLiveScript commit (48715099), but the fix is not complete for all inline constructor argument cases. The intermediate variable workaround (`CRGB color = CRGB(...); setRGB(i, color);`) is still the recommended fallback. Do not suggest the inline constructor form is fully fixed.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 136
File: src/MoonBase/LiveScriptNode.cpp:174-178
Timestamp: 2026-03-04T09:22:15.040Z
Learning: In MoonModules/MoonLight LiveScriptNode (src/MoonBase/LiveScriptNode.cpp): `compileAndRun()` is always called from the SvelteKit loop task (Core 1) — either via `LiveScriptNode::setup()` during node creation or from ModuleLiveScripts file-change handlers. The commented-out `runInAppTask` deferral wrapper (lines 177-178, 222) is an abandoned TODO from an earlier architecture where `compileAndRun()` might have been called from an HTTP handler. There is no deferred task queue (`runInAppTask`) in the current codebase. Deferring via a lambda would also introduce object-lifetime hazards because the `LiveScriptNode` could be destroyed before the lambda runs. Do not suggest adding a deferred task queue or lambda capture for `compileAndRun()`.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 153
File: src/MoonBase/LiveScriptNode.cpp:50-51
Timestamp: 2026-03-19T10:14:00.024Z
Learning: In MoonModules/MoonLight LiveScriptNode (src/MoonBase/LiveScriptNode.cpp): `effectTaskHandle` is used without a null check in `sync()` via `xTaskNotifyGive(effectTaskHandle)`. This is intentional — MoonLight follows a "fail-fast at boot" / "minimal code for max speed" philosophy: if `effectTaskHandle` is not initialized, the application is already in an unrecoverable state and defensive null checks in hot paths are explicitly unwanted. Do not suggest adding null/nullptr guards for `effectTaskHandle` in `sync()` or similar critical-path code.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-08T10:02:02.378Z
Learning: In MoonModules/MoonLight (interface/src/routes/moonbase/module/Module.svelte), the `updateRecursive` function merges incoming WebSocket/HTTP state into the Svelte `$state` store by array INDEX (not by control `name` or `p`). With `pruneMissing=false` (the default for WS partial updates), keys absent in new data are never deleted from old objects. This causes stale properties (like `ro: true`) from one control to persist on a different control when the controls array changes length/order between node setup cycles. The fix is to replace array items entirely when their `p` (pointer) or `name` changes, rather than merging.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-26T14:59:22.091Z
Learning: In MoonModules/MoonLight PR `#114`: ModuleDevices deadlock caused by: (1) StatefulService.h line 204-205 bug - after 500ms mutex timeout, continues WITHOUT acquiring mutex, causing race conditions and JSON corruption. (2) receiveUDP() while-loop processes own UDP broadcasts via loopback, creating circular call chain: sendUDP→broadcast→receiveUDP→updateDevices→update→handlers→sendUDP. (3) updateDevices() holds _accessMutex during expensive sort/JSON operations with 15 devices, can exceed 500ms with nested calls. Solution: Fix StatefulService to wait forever or reboot on timeout; skip own broadcasts by checking message.name == ownHostname; optimize updateDevices to sort outside mutex.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-15T12:38:33.598Z
Learning: In MoonLight GameOfLifeEffect (src/MoonLight/Nodes/Effects/E_MoonModules.h): The effect's nested loops (especially lines 257-323) iterate through all cells (e.g., 128×128 = 16,384) with nested neighbor checks (18 per cell = 294,912 operations) without yielding. This starves effectTask (priority 3) when higher-priority tasks like HTTP server (priority 5) run, causing the effect to disappear. UI actions (save, refresh) temporarily show one frame because HTTP completes and briefly releases CPU back to effectTask. Solution: Add taskYIELD() inside outer X loop to prevent task starvation. This is the same root cause as issue `#102` (Starsky glitches with many driver nodes).

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T14:12:53.662Z
Learning: Applies to src/lib/components/moonbase/**/*.test.ts : Add unit tests for pure, side-effect-free TypeScript functions in MoonLight-specific frontend files (under `src/routes/moonbase/`, `src/lib/components/moonbase/`, stores, and types) with tests co-located next to source

@ewowi
Copy link
Copy Markdown
Collaborator Author

ewowi commented Apr 4, 2026

@coderabbitai, please do your review again, also looking at comments I made (which I now all set "resolved') and latest commits including 5b887e8, because I think your report does not reflect latest status

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@ewowi Let me fetch the latest commit details before triggering the re-review.


🏁 Script executed:

#!/bin/bash
# Get the list of recent commits on the PR
gh pr view 161 --repo MoonModules/MoonLight --json commits --jq '.commits[-10:] | .[] | {oid: .oid, message: .messageHeadline, authoredDate: .committedDate}'

echo "---"
# Get the diff for the specific commit mentioned
gh api repos/MoonModules/MoonLight/commits/5b887e829f55b3a7ace788b76ca2e88439716e25 --jq '.commit.message'
echo "---"
# Get files changed in that commit
gh api repos/MoonModules/MoonLight/commits/5b887e829f55b3a7ace788b76ca2e88439716e25 --jq '.files[] | {filename: .filename, status: .status, additions: .additions, deletions: .deletions, patch: .patch}'

Length of output: 46013


@ewowi Here is the fully updated review reflecting all three commits, including the latest 5b887e8.


Updated PR #161 Review — "Livescripts tuning"

Complete change inventory (all 3 commits)

Area Change Status
WiFiSettingsService _connecting flag prevents re-entrant WiFi.begin(); manageSTA() early-returns; onStationModeDisconnected() deferred via _lastConnectionAttempt ✅ Merged logic sound
D_NetworkIn handleDDP() Per-layer ddpMaxLights computed correctly for virtual layers (commit 5b887e8) ✅ Fixed
D_NetworkIn writePixels() Direct virtualChannels[] write; mapping-table warning log not yet added ⚠️ P1 open
D_DMXIn processChannels() Bounds checked before taking mutex; null-check moved inside mutex (TOCTOU fix in 5b887e8) ✅ Fixed
D_NetworkOut Per-protocol status strings + lastStatusCode=3 for unsupported DDP layout ✅ Fixed
ModuleLiveScripts compileAndRun()startCompile() (async)
LayerManager Backward-compat guard for bare brightness/start/end; no warning log ⚠️ P1 open
LiveScriptNode Added sqrt, drawLine3D, blur2d external bindings
E_PaintBrush.sc New audio-reactive 2D/3D paintbrush effect; sqrt→integer dist2 (5b887e8)
E_hello.sc rgb()CRGB()
ESPLiveScript pin Updated to 48715099 (20260402)
FastLED pin Updated to eca4af57 (20260404)
UI: API icon links All 10+ module pages now show REST endpoint link ✅ (note below)
Docs: moonlight/overview.md Full rework — Why MoonLight table, emoji key table, Reporting Issues section
Docs: moonbase/overview.md Full rework — File Manager, Devices, Tasks, IO, Status Bar table
Docs: livescripts.md Important notes section added
Docs: effects-tutorial.md Script renames, beatsin8 args, VU meter sensitivity fix
Docs: drivers.md Network Out examples clarified

Merge recommendation

Ready to merge. The two remaining open items are low-risk and appropriate for a follow-up PR. The high-risk WiFi and virtual-layer changes are solid; the latest commit closed the remaining gaps in DMXIn and DDP bounds checking.


Remaining open items (post-merge follow-up)

P1 — Short follow-up PR

  1. WiFi _connecting liveness escapemanageSTA() still has no safety timeout for the case where ARDUINO_EVENT_WIFI_STA_CONNECTED is never delivered. If the IDF event loop misses the event, _connecting stays true permanently and WiFi recovery is blocked indefinitely. A millis() - _lastConnectionAttempt > 30000 reset in manageSTA() would close this.

  2. Virtual-layer write with non-flat mapping tableD_NetworkIn::writePixels() writes directly into virtualChannels[ledIndex] and ignores the mapping table when mappingTableSize > 0. This is harmless for flat/sequential virtual layers but silently misplaces pixels on zigzag or segment maps. A one-time EXT_LOGW when entering this path with a non-empty mapping table would make regressions visible during testing.

  3. LayerManager: silent discard of legacy-preset updates — When brightness_0/start_0/end_0 keys are absent, the update is silently dropped. Timing bugs (keys not yet written) look identical to a genuine old-preset. A EXT_LOGD line distinguishing the two would help.

P2 — Nice-to-have

  1. http:// hardcoded in all API icon links — All 10 pages use http://{page.url.host}/rest/.... This breaks on HTTPS deployments. Changing to {page.url.protocol}//{page.url.host}/rest/... would be future-proof.

Ready-to-paste Claude Code prompt — post-merge follow-up

You are working on MoonModules/MoonLight (main branch, post-PR-161 merge).
Make minimal, targeted changes only. Do NOT commit — show a unified diff.

## Fix 1 — WiFiSettingsService: _connecting liveness escape
File: lib/framework/WiFiSettingsService.cpp
In manageSTA(), inside the `if (_connecting) return;` early-return block,
add a timeout escape BEFORE the return:
  if (millis() - _lastConnectionAttempt > 30000) {
    EXT_LOGW(ML_TAG, "WiFi: _connecting stuck >30s, resetting flag");
    _connecting = false;
  } else return;
This prevents permanent lockout when ARDUINO_EVENT_WIFI_STA_CONNECTED
is never delivered.

## Fix 2 — D_NetworkIn: log warning for non-flat virtual-layer writes
File: src/MoonLight/Nodes/Drivers/D_NetworkIn.h
In writePixels(), in the virtual-layer (layer > 0) path, after resolving
vLayer, add a ONE-TIME log (use a static bool logged = false guard) when
vLayer->mappingTableSize > 0:
  static bool mappingTableWarned = false;
  if (!mappingTableWarned && vLayer->mappingTableSize > 0) {
    EXT_LOGW(ML_TAG, "NetworkIn: direct virtualChannels write ignores non-flat mapping table");
    mappingTableWarned = true;
  }

## Fix 3 — LayerManager: log skipped legacy-preset updates
File: src/MoonLight/Layers/LayerManager.h
In handleUpdate(), in each branch that detects the _0 key is null and
returns early, add:
  EXT_LOGD(ML_TAG, "LayerManager: skipping bare '%s' update — _0 key absent (old preset or timing)", updatedItem.name.c_str());
so contributors can distinguish a genuine old-preset from a race condition.

## Fix 4 — UI: use page.url.protocol instead of hardcoded http://
In every Svelte file that contains `http://{page.url.host}/rest/`:
Replace all occurrences of `http://{page.url.host}` with
`{page.url.protocol}//{page.url.host}`.
Affected files (search with rg):
  interface/src/routes/connections/mqtt/MQTT.svelte
  interface/src/routes/connections/ntp/NTP.svelte
  interface/src/routes/ethernet/Ethernet.svelte
  interface/src/routes/moonbase/filemanager/FileManager.svelte
  interface/src/routes/moonbase/module/Module.svelte
  interface/src/routes/system/coredump/+page.svelte
  interface/src/routes/system/status/SystemStatus.svelte
  interface/src/routes/wifi/ap/Accesspoint.svelte
  interface/src/routes/wifi/sta/Wifi.svelte

Would you like me to open a GitHub issue for the follow-up items so they are tracked?


🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-19T12:50:37.875Z
Learning: In MoonModules/MoonLight PR `#153` (livescripts branch): The final merged set of 6 commits correctly implements: (1) counting semaphore (max 4) for LiveScript frame-sync with guarded scriptsToSync increment; (2) async startCompile() with compileInProgress/needsCompile deferred queue; (3) hasLoopTask gate to prevent loop() signalling when no task is running; (4) lightPreset_RGBCCT 5-channel support via offsetWhite2 sentinel (UINT8_MAX) through LightsHeader→parlio chain; (5) audio (bands[16], volume) and IMU (gravityX/Y/Z) external bindings for LiveScripts. All critical review items were resolved before merge.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-26T14:59:22.091Z
Learning: In MoonModules/MoonLight PR `#114`: ModuleDevices deadlock caused by: (1) StatefulService.h line 204-205 bug - after 500ms mutex timeout, continues WITHOUT acquiring mutex, causing race conditions and JSON corruption. (2) receiveUDP() while-loop processes own UDP broadcasts via loopback, creating circular call chain: sendUDP→broadcast→receiveUDP→updateDevices→update→handlers→sendUDP. (3) updateDevices() holds _accessMutex during expensive sort/JSON operations with 15 devices, can exceed 500ms with nested calls. Solution: Fix StatefulService to wait forever or reboot on timeout; skip own broadcasts by checking message.name == ownHostname; optimize updateDevices to sort outside mutex.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-19T12:12:24.260Z
Learning: In MoonModules/MoonLight (src/MoonBase/LiveScriptNode.cpp + src/main.cpp): The LiveScript frame-sync uses a counting semaphore (max 4) gating script tasks, and a `scriptsToSync` counter consumed by `effectTask` via `ulTaskNotifyTake`. The critical invariant is: `scriptsToSync` MUST only be incremented when `xSemaphoreGive` succeeds. If `xSemaphoreGive` fails (semaphore full, 5th+ script), the script task is never unblocked and will never call `xTaskNotifyGive(effectTaskHandle)`, so incrementing `scriptsToSync` unconditionally causes a deadlock in `effectTask`. The correct behavior (already implemented) is to skip the frame for scripts beyond the 4-slot cap and log a warning. Do NOT suggest "always increment scriptsToSync" or increasing the cap as a fix — the guarded increment is the intentional deadlock-prevention mechanism.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 161
File: docs/moonlight/livescripts.md:44-44
Timestamp: 2026-04-03T12:18:07.382Z
Learning: In MoonModules/MoonLight (docs/moonlight/livescripts.md + ESPLiveScript library): The inline constructor argument limitation (e.g., `setRGB(i, CRGB(0, 0, 255))`) is only partially resolved as of PR `#161` (livescript-tuning). Some patterns like `setRGB(random16(NUM_LEDS), CRGB(0, 0, 255))` now work with the updated ESPLiveScript commit (48715099), but the fix is not complete for all inline constructor argument cases. The intermediate variable workaround (`CRGB color = CRGB(...); setRGB(i, color);`) is still the recommended fallback. Do not suggest the inline constructor form is fully fixed.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-08T10:02:02.378Z
Learning: In MoonModules/MoonLight (interface/src/routes/moonbase/module/Module.svelte), the `updateRecursive` function merges incoming WebSocket/HTTP state into the Svelte `$state` store by array INDEX (not by control `name` or `p`). With `pruneMissing=false` (the default for WS partial updates), keys absent in new data are never deleted from old objects. This causes stale properties (like `ro: true`) from one control to persist on a different control when the controls array changes length/order between node setup cycles. The fix is to replace array items entirely when their `p` (pointer) or `name` changes, rather than merging.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-15T12:38:33.598Z
Learning: In MoonLight GameOfLifeEffect (src/MoonLight/Nodes/Effects/E_MoonModules.h): The effect's nested loops (especially lines 257-323) iterate through all cells (e.g., 128×128 = 16,384) with nested neighbor checks (18 per cell = 294,912 operations) without yielding. This starves effectTask (priority 3) when higher-priority tasks like HTTP server (priority 5) run, causing the effect to disappear. UI actions (save, refresh) temporarily show one frame because HTTP completes and briefly releases CPU back to effectTask. Solution: Add taskYIELD() inside outer X loop to prevent task starvation. This is the same root cause as issue `#102` (Starsky glitches with many driver nodes).

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-04-01T14:08:59.261Z
Learning: In MoonModules/MoonLight LayerManager (src/MoonLight/Layers/LayerManager.h): restoreNonSelectedLayers() must suspend effectTaskHandle for its entire duration — from the first ensureLayer() call through all nm.addNode() pushes — because ensureLayer() immediately makes layerP.layers[i] non-null, so effectTask (Core 0) can call layer->loop() and iterate layer->nodes while addNode() push_back is in progress (vector realloc = use-after-free). The fix (added in PR `#159` commit b21a42d) wraps the full for-loop with `#ifdef` ARDUINO vTaskSuspend(effectTaskHandle) / vTaskResume(effectTaskHandle). Same suspend pattern is used in prepareForPresetLoad() and onNodeRemoved().

Learnt from: MoonModules
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-28T16:20:39.049Z
Learning: In MoonModules/MoonLight, the project enforces a "never copy code into test files" policy (documented in CLAUDE.md). If a function lives in a header with ESP32/Arduino dependencies and needs native testing, it must be extracted into a pure-C++ header under `src/MoonBase/utilities/` (no Arduino/ESP32 includes), then `#include`d from both the original file and the test. The BoardNames.h extraction in PR `#158` (commit a2a6e41) is the canonical example of this pattern.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-02-27T13:03:49.950Z
Learning: In MoonModules/MoonLight ModuleIO.onUpdate: During file load, compareRecursive processes JSON keys in order they appear. If boardPreset appears before modded in the file, checking _state.data["modded"] in the boardPreset handler reads the old code default (false), not the value being restored from file (true), causing custom pins to be overwritten. Fix: Skip boardPreset handling during backend/file updates (originId.toInt()==false). Instead, handle boardPreset in loop20ms() after full file load completes, where final modded state is available. Check: if modded==false, apply setBoardPresetDefaults(); if modded==true, skip and preserve custom pins from file.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 153
File: src/MoonBase/LiveScriptNode.cpp:50-51
Timestamp: 2026-03-19T10:14:00.024Z
Learning: In MoonModules/MoonLight LiveScriptNode (src/MoonBase/LiveScriptNode.cpp): `effectTaskHandle` is used without a null check in `sync()` via `xTaskNotifyGive(effectTaskHandle)`. This is intentional — MoonLight follows a "fail-fast at boot" / "minimal code for max speed" philosophy: if `effectTaskHandle` is not initialized, the application is already in an unrecoverable state and defensive null checks in hot paths are explicitly unwanted. Do not suggest adding null/nullptr guards for `effectTaskHandle` in `sync()` or similar critical-path code.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 136
File: src/MoonBase/LiveScriptNode.cpp:22-39
Timestamp: 2026-03-04T09:15:48.019Z
Learning: In MoonModules/MoonLight LiveScriptNode (src/MoonBase/LiveScriptNode.cpp): The global `gNode` (Node*) and `WaitAnimationSync` (binary semaphore) are intentionally global because ESPLiveScript's `addExternalFunction()` only accepts plain C function pointers with no void* userData/context parameter, and `setFunctionToSync()` accepts a single void(*)() with no per-instance binding. There is no way to attach per-instance state to callbacks within the current ESPLiveScript API. This is a known limitation acknowledged with a `// todo: this is not working well with multiple scripts running!!!` comment at line 211. Fixing it would require modifying the ESPLiveScript library itself to support userData/context on callbacks.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 136
File: src/MoonBase/LiveScriptNode.cpp:174-178
Timestamp: 2026-03-04T09:22:15.040Z
Learning: In MoonModules/MoonLight LiveScriptNode (src/MoonBase/LiveScriptNode.cpp): `compileAndRun()` is always called from the SvelteKit loop task (Core 1) — either via `LiveScriptNode::setup()` during node creation or from ModuleLiveScripts file-change handlers. The commented-out `runInAppTask` deferral wrapper (lines 177-178, 222) is an abandoned TODO from an earlier architecture where `compileAndRun()` might have been called from an HTTP handler. There is no deferred task queue (`runInAppTask`) in the current codebase. Deferring via a lambda would also introduce object-lifetime hazards because the `LiveScriptNode` could be destroyed before the lambda runs. Do not suggest adding a deferred task queue or lambda capture for `compileAndRun()`.

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T14:12:53.662Z
Learning: Applies to src/lib/components/moonbase/**/*.test.ts : Add unit tests for pure, side-effect-free TypeScript functions in MoonLight-specific frontend files (under `src/routes/moonbase/`, `src/lib/components/moonbase/`, stores, and types) with tests co-located next to source

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (1)
platformio.ini (1)

229-229: Consider removing the commented dependency with known link errors.

The commented FastLED commit at line 229 is noted as having "link errors." Keeping broken dependencies in the lib_deps section (even commented) can cause confusion for future maintainers who might attempt to uncomment it. Consider either:

  • Removing the line entirely, or
  • Moving it to a historical comment block outside lib_deps
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platformio.ini` at line 229, Remove or relocate the commented broken
dependency entry ";
https://github.com/FastLED/FastLED#eca4af576737353e703866fa54d4c897cebb9208 ;
20260404 third party Yves link errors" from the lib_deps area in platformio.ini:
either delete the commented line entirely to avoid confusion, or move it into a
clearly marked historical/comments section outside lib_deps (e.g., a "HISTORY"
or "NOTES" block) with a short note about the link error so maintainers don't
accidentally re-enable it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@platformio.ini`:
- Around line 284-285: Update the comment on the commit URL line to state that
the CRGB constructor fix at commit 48715099a is only partial: note that patterns
like setRGB(random16(NUM_LEDS), CRGB(0, 0, 255)) now work but other inline CRGB
constructor uses still fail; recommend the workaround of creating a temporary
CRGB variable (e.g., CRGB color = CRGB(...); setRGB(i, color);) where needed,
and keep mention that stack size / PSRAM task support remains present (see
__LS_STACK_CAPS usage in xTaskCreateWithCaps).

---

Nitpick comments:
In `@platformio.ini`:
- Line 229: Remove or relocate the commented broken dependency entry ";
https://github.com/FastLED/FastLED#eca4af576737353e703866fa54d4c897cebb9208 ;
20260404 third party Yves link errors" from the lib_deps area in platformio.ini:
either delete the commented line entirely to avoid confusion, or move it into a
clearly marked historical/comments section outside lib_deps (e.g., a "HISTORY"
or "NOTES" block) with a short note about the link error so maintainers don't
accidentally re-enable it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c4396a72-873f-41a0-a4be-871120aceb7a

📥 Commits

Reviewing files that changed from the base of the PR and between 5b887e8 and da791b6.

📒 Files selected for processing (15)
  • docs/moonlight/livescripts.md
  • docs/moonlight/overview.md
  • interface/src/routes/connections/mqtt/MQTT.svelte
  • interface/src/routes/connections/ntp/NTP.svelte
  • interface/src/routes/ethernet/Ethernet.svelte
  • interface/src/routes/moonbase/filemanager/FileManager.svelte
  • interface/src/routes/moonbase/module/Module.svelte
  • interface/src/routes/system/coredump/+page.svelte
  • interface/src/routes/system/status/SystemStatus.svelte
  • interface/src/routes/wifi/ap/Accesspoint.svelte
  • interface/src/routes/wifi/sta/Wifi.svelte
  • lib/framework/WWWData.h
  • lib/framework/WiFiSettingsService.cpp
  • platformio.ini
  • src/MoonLight/Nodes/Drivers/D_NetworkIn.h
✅ Files skipped from review due to trivial changes (9)
  • interface/src/routes/connections/ntp/NTP.svelte
  • interface/src/routes/system/status/SystemStatus.svelte
  • interface/src/routes/connections/mqtt/MQTT.svelte
  • interface/src/routes/moonbase/filemanager/FileManager.svelte
  • interface/src/routes/ethernet/Ethernet.svelte
  • docs/moonlight/livescripts.md
  • interface/src/routes/wifi/sta/Wifi.svelte
  • interface/src/routes/moonbase/module/Module.svelte
  • interface/src/routes/system/coredump/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
  • interface/src/routes/wifi/ap/Accesspoint.svelte
  • src/MoonLight/Nodes/Drivers/D_NetworkIn.h
  • lib/framework/WiFiSettingsService.cpp

Comment on lines +284 to +285
; https://github.com/ewowi/ESPLiveScript.git#bd364c3bf08af2fb3eb4c142c86268403b6ce7e6 ; 20260328 stack size argument and tasks in psram
https://github.com/ewowi/ESPLiveScript.git#48715099a9c82cc914efe576b70c7a7364f258ce ; 20260402 fix setRGB(random16(NUM_LEDS), CRGB(0, 0, 255));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify ESPLiveScript API usage in LiveScriptNode and check for inline CRGB constructor patterns in scripts

echo "=== Checking LiveScriptNode.cpp for ESPLiveScript API usage ==="
rg -n -A3 -B3 'addExternalFunction|setFunctionToSync|startCompile|compileAndRun' src/MoonBase/LiveScriptNode.cpp

echo -e "\n=== Checking for inline CRGB constructor patterns in LiveScript examples ==="
fd -e sc -x rg -l 'setRGB.*CRGB\(' {} \;

echo -e "\n=== Checking for CRGB/CHSV usage in LiveScriptNode external bindings ==="
rg -n 'static.*CRGB|static.*CHSV' src/MoonBase/LiveScriptNode.cpp

Repository: MoonModules/MoonLight

Length of output: 5244


Update line 285 comment to clarify the CRGB constructor fix is partial and recommend the workaround.

The ESPLiveScript commit upgrade from bd364c3bf to 48715099a retains stack size and PSRAM task features (verified by __LS_STACK_CAPS usage in xTaskCreateWithCaps). However, the setRGB CRGB constructor fix at commit 48715099a is only partial—patterns like setRGB(random16(NUM_LEDS), CRGB(0, 0, 255)) now work, but not all inline constructor cases are resolved. Since inline CRGB patterns are actively used across multiple livescripts (E_lines.sc, E_random.sc, E_hello.sc, E_Kitt.sc, E_FireworkDust.sc), update the comment on line 285 to clarify the limitation and recommend the intermediate variable workaround (CRGB color = CRGB(...); setRGB(i, color);) where needed.

Suggested comment update
  https://github.com/ewowi/ESPLiveScript.git#48715099a9c82cc914efe576b70c7a7364f258ce  ; 20260402 partial fix: setRGB(random16(NUM_LEDS), CRGB(0, 0, 255)) now works; use CRGB temp var for other patterns
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platformio.ini` around lines 284 - 285, Update the comment on the commit URL
line to state that the CRGB constructor fix at commit 48715099a is only partial:
note that patterns like setRGB(random16(NUM_LEDS), CRGB(0, 0, 255)) now work but
other inline CRGB constructor uses still fail; recommend the workaround of
creating a temporary CRGB variable (e.g., CRGB color = CRGB(...); setRGB(i,
color);) where needed, and keep mention that stack size / PSRAM task support
remains present (see __LS_STACK_CAPS usage in xTaskCreateWithCaps).

@ewowi
Copy link
Copy Markdown
Collaborator Author

ewowi commented Apr 4, 2026

@coderabbitai, regarding

Remaining open items (post-merge follow-up)

please review da791b6 as they are addressed here and update your merge readiness table

This are the responses to your open items:

│ Item │ Verdict │ Action │
├─────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ WiFi _connecting liveness │ Valid │ Added 30 s timeout in manageSTA() — after which _connecting is reset with a │
│ escape │ │ log warning and the next call retries normally │
├─────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ Non-flat mapping table │ Review framing wrong — compositeTo() applies the mapping table so virtual-order writes are │ Added a comment in writePixels() explaining the intended behaviour │
│ "misplacement" │ correct; a sender expecting physical order should use layer 0 │ │
├─────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
│ LayerManager silent discard of │ Already fixed — EXT_LOGD lines present at all three guard points │ No change │
│ legacy presets │ │ │
└─────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────┘

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get commit message and files changed
gh api repos/MoonModules/MoonLight/commits/da791b622525cd6b336a87e50d3575a0a57a4964 --jq '.commit.message'
echo "=== FILES CHANGED ==="
gh api repos/MoonModules/MoonLight/commits/da791b622525cd6b336a87e50d3575a0a57a4964 --jq '.files[] | {filename: .filename, additions: .additions, deletions: .deletions, patch: .patch}'

Length of output: 12853


@ewowi — I've reviewed commit da791b6 in full. Here is the updated merge readiness table:


Updated merge readiness table — PR #161

Item Previous status Verdict Resolution in da791b6
WiFi _connecting liveness escape ⚠️ P1 open Closed 30 s timeout + ESP_LOGW added in manageSTA() before resetting _connecting = false
Virtual-layer mapping-table "misplacement" ⚠️ P1 open Closed — review framing was wrong compositeTo() applies the mapping table when compositing to channelsD; data written in virtual-pixel order to virtualChannels[] is the correct contract. Explanatory comment added in writePixels()
LayerManager silent discard of legacy presets ⚠️ P1 open Already fixed EXT_LOGD lines were already present from a prior commit; no change needed
UI API links hardcoded http:// ⚠️ P2 open Closed All 10 pages switched to relative /rest/... paths — cleaner than page.url.protocol and protocol/port agnostic

Additional noteworthy change in da791b6

FastLED pin revertedeca4af57 (20260404) caused link errors with third-party code (FASTLED_BUILD comment updated to 20260331); the stable 682179f8 pin is now active again. Worth keeping an eye on when the newer commit is re-evaluated.


Overall merge readiness

Ready to merge — all P1 and P2 items resolved. No blocking issues remain.


I'll update my learning on the virtual-layer write path to reflect the correct architecture:


✏️ Learnings added
Learnt from: ewowi
URL: https://github.com/MoonModules/MoonLight/pull/161

Timestamp: 2026-04-04T09:23:16.541Z
Learning: In MoonModules/MoonLight D_NetworkIn and D_DMXIn (src/MoonLight/Nodes/Drivers/D_NetworkIn.h, D_DMXIn.h): Writing pixel data to VirtualLayer::virtualChannels[] in virtual-pixel (0-based) order is architecturally correct. compositeTo() applies the full mapping table (zigzag/segment/etc.) when compositing virtualChannels into channelsD. The write is NOT a pixel-misplacement risk — the mapping is handled downstream. A sender expecting physical-LED order should target layer 0 instead. Do NOT flag direct virtualChannels writes in these drivers as a mapping regression.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-19T12:50:37.875Z
Learning: In MoonModules/MoonLight PR `#153` (livescripts branch): The final merged set of 6 commits correctly implements: (1) counting semaphore (max 4) for LiveScript frame-sync with guarded scriptsToSync increment; (2) async startCompile() with compileInProgress/needsCompile deferred queue; (3) hasLoopTask gate to prevent loop() signalling when no task is running; (4) lightPreset_RGBCCT 5-channel support via offsetWhite2 sentinel (UINT8_MAX) through LightsHeader→parlio chain; (5) audio (bands[16], volume) and IMU (gravityX/Y/Z) external bindings for LiveScripts. All critical review items were resolved before merge.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 161
File: docs/moonlight/livescripts.md:44-44
Timestamp: 2026-04-03T12:18:07.382Z
Learning: In MoonModules/MoonLight (docs/moonlight/livescripts.md + ESPLiveScript library): The inline constructor argument limitation (e.g., `setRGB(i, CRGB(0, 0, 255))`) is only partially resolved as of PR `#161` (livescript-tuning). Some patterns like `setRGB(random16(NUM_LEDS), CRGB(0, 0, 255))` now work with the updated ESPLiveScript commit (48715099), but the fix is not complete for all inline constructor argument cases. The intermediate variable workaround (`CRGB color = CRGB(...); setRGB(i, color);`) is still the recommended fallback. Do not suggest the inline constructor form is fully fixed.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 161
File: src/MoonLight/Nodes/Drivers/D_NetworkIn.h:183-190
Timestamp: 2026-04-04T07:35:48.139Z
Learning: In MoonModules/MoonLight NetworkInDriver and DMXInDriver (src/MoonLight/Nodes/Drivers/D_NetworkIn.h, D_DMXIn.h): For VIRTUAL layers (layer > 0), incoming pixel data MUST be written to vLayer->virtualChannels (not to layerP.lights.channelsD). Writing to channelsD directly would be silently zeroed every frame because compositeLayers() recomposes channelsD from virtualChannels on every pass. The write is safe under swapMutex because compositeLayers() also holds swapMutex when it reads virtualChannels — the two are mutually exclusive. virtualChannels reallocation is gated by isPositions, not by swapMutex. For PHYSICAL layer (layer == 0), writing directly to channelsD is correct (bypasses compositing). Do NOT flag virtual-layer writes to virtualChannels in these drivers as a data race — the swapMutex serialization makes it safe and channelsD is the wrong target for virtual-layer input.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-26T14:59:22.091Z
Learning: In MoonModules/MoonLight PR `#114`: ModuleDevices deadlock caused by: (1) StatefulService.h line 204-205 bug - after 500ms mutex timeout, continues WITHOUT acquiring mutex, causing race conditions and JSON corruption. (2) receiveUDP() while-loop processes own UDP broadcasts via loopback, creating circular call chain: sendUDP→broadcast→receiveUDP→updateDevices→update→handlers→sendUDP. (3) updateDevices() holds _accessMutex during expensive sort/JSON operations with 15 devices, can exceed 500ms with nested calls. Solution: Fix StatefulService to wait forever or reboot on timeout; skip own broadcasts by checking message.name == ownHostname; optimize updateDevices to sort outside mutex.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-19T12:12:24.260Z
Learning: In MoonModules/MoonLight (src/MoonBase/LiveScriptNode.cpp + src/main.cpp): The LiveScript frame-sync uses a counting semaphore (max 4) gating script tasks, and a `scriptsToSync` counter consumed by `effectTask` via `ulTaskNotifyTake`. The critical invariant is: `scriptsToSync` MUST only be incremented when `xSemaphoreGive` succeeds. If `xSemaphoreGive` fails (semaphore full, 5th+ script), the script task is never unblocked and will never call `xTaskNotifyGive(effectTaskHandle)`, so incrementing `scriptsToSync` unconditionally causes a deadlock in `effectTask`. The correct behavior (already implemented) is to skip the frame for scripts beyond the 4-slot cap and log a warning. Do NOT suggest "always increment scriptsToSync" or increasing the cap as a fix — the guarded increment is the intentional deadlock-prevention mechanism.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-18T12:49:07.297Z
Learning: In MoonLight channel reordering architecture: Keep rgbwBufferMapping in driver task (not effect task) because: (1) Driver has spare CPU cycles waiting for DMA/I2S/Art-Net timing; effect task is CPU-bound and starvation-prone. (2) Contiguous RGB writes in effects are cache-friendly; scattered writes at final offsets are cache-hostile for non-RGB orders (GRB, BGR, etc.). (3) forEachLightIndex already handles sparse mapping iteration; moving reordering to effects adds complexity without benefit since drivers must send full frames anyway (can't skip iteration). (4) rgbwBufferMapping does brightness/gamma LUT + white extraction + reordering—moving just reordering creates code duplication. (5) Effects should express color intent (CRGB), not hardware details (offsetRed/Green/Blue). Driver-side transformation maintains separation of concerns and task load balance for scaling to 98K+ pixels.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-04-01T14:08:59.261Z
Learning: In MoonModules/MoonLight LayerManager (src/MoonLight/Layers/LayerManager.h): restoreNonSelectedLayers() must suspend effectTaskHandle for its entire duration — from the first ensureLayer() call through all nm.addNode() pushes — because ensureLayer() immediately makes layerP.layers[i] non-null, so effectTask (Core 0) can call layer->loop() and iterate layer->nodes while addNode() push_back is in progress (vector realloc = use-after-free). The fix (added in PR `#159` commit b21a42d) wraps the full for-loop with `#ifdef` ARDUINO vTaskSuspend(effectTaskHandle) / vTaskResume(effectTaskHandle). Same suspend pattern is used in prepareForPresetLoad() and onNodeRemoved().

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-08T10:36:33.812Z
Learning: In MoonModules/MoonLight D_FastLEDDriver: FastLED RMT5 (default on IDF >= 5) lacks the dual-buffer WiFi-resistance design of FastLED RMT4, causing WebSocket/WiFi throughput degradation at high LED frame rates. FastLED I2S is also incompatible with WiFi on ESP32-S3. MoonLight's D_ParallelLEDDriver (parlio.cpp) avoids this entirely by using its own DMA-streaming PARLIO implementation with zero per-frame ISR callbacks. Fix: add `-D FASTLED_RMT5=0` to platformio.ini for IDF5-based boards to force RMT4's dual-buffer design. This flag may need to be re-enabled even when a specific FastLED commit appeared stable, as RMT5 behavior varies across FastLED master commits.

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.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-08T10:36:33.812Z
Learning: In MoonModules/MoonLight D_FastLEDDriver vs D_ParallelLEDDriver: The PARLIO driver (parlio.cpp) uses `queue_nonblocking = 1` in `parlio_transmit_config_t`, making LED transmission fully fire-and-forget via hardware DMA — zero ISR callbacks during TX, CPU is free for WiFi. FastLED RMT5 (default on IDF5+) uses ISR-driven segment streaming with ISR priority capped at 1-3, which causes constant WiFi interrupt contention and WebSocket throughput degradation at high frame rates. I2S has the same ISR-driven problem. Fix: add `-D FASTLED_RMT5=0` to platformio.ini to force FastLED RMT4, which has an explicit dual-buffer WiFi-resistant design. WARNING: RMT4 and RMT5 cannot coexist in the same binary (startup abort). The affinity="PARLIO" option in FastLED Driver uses FastLED's own PARLIO wrapper, not MoonLight's custom parlio.cpp.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-02-27T12:33:23.675Z
Learning: In MoonModules/MoonLight ModuleIO.onUpdate: The boardPreset handler must be placed BEFORE the if (!originId.toInt()) return; check to handle both UI updates (originId="1") and boot/file-load updates (originId="module"). When modded=false and boardPreset changes on boot, setBoardPresetDefaults() must be called to apply current code-defined defaults (not just pins from file), ensuring board preset definitions can evolve across code updates. Only UI-specific handlers (modded, switch1/2, maxPower, usage, index, i2cFreq) should come after the originId check.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-02-27T13:03:49.950Z
Learning: In MoonModules/MoonLight ModuleIO.onUpdate: During file load, compareRecursive processes JSON keys in order they appear. If boardPreset appears before modded in the file, checking _state.data["modded"] in the boardPreset handler reads the old code default (false), not the value being restored from file (true), causing custom pins to be overwritten. Fix: Skip boardPreset handling during backend/file updates (originId.toInt()==false). Instead, handle boardPreset in loop20ms() after full file load completes, where final modded state is available. Check: if modded==false, apply setBoardPresetDefaults(); if modded==true, skip and preserve custom pins from file.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-03-08T10:02:02.378Z
Learning: In MoonModules/MoonLight (interface/src/routes/moonbase/module/Module.svelte), the `updateRecursive` function merges incoming WebSocket/HTTP state into the Svelte `$state` store by array INDEX (not by control `name` or `p`). With `pruneMissing=false` (the default for WS partial updates), keys absent in new data are never deleted from old objects. This causes stale properties (like `ro: true`) from one control to persist on a different control when the controls array changes length/order between node setup cycles. The fix is to replace array items entirely when their `p` (pointer) or `name` changes, rather than merging.

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-03-05T07:10:15.113Z
Learning: In MoonModules/MoonLight (lib/PsychicHttp/src/PsychicWebSocket.cpp): `PsychicWebSocketClient::sendMessage()` must guard with `httpd_ws_get_fd_info()` before calling `httpd_ws_send_frame_async()`. Without the guard, when a client disconnects and reconnects (reusing the same socket fd) while a browser tab is hidden, repeated failed sends degrade the lwIP netconn state until `lwip_netconn_do_write` hard-asserts `(state!)` instead of returning ESP_FAIL, causing a reboot. Fix: check `httpd_ws_get_fd_info(server, socket) == HTTPD_WS_CLIENT_WEBSOCKET` first and return ESP_FAIL early if not. Also, in EventSocket.cpp, erase stale clients from subscriptions on ESP_FAIL instead of keeping them.

Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 136
File: src/MoonBase/LiveScriptNode.cpp:22-39
Timestamp: 2026-03-04T09:15:48.019Z
Learning: In MoonModules/MoonLight LiveScriptNode (src/MoonBase/LiveScriptNode.cpp): The global `gNode` (Node*) and `WaitAnimationSync` (binary semaphore) are intentionally global because ESPLiveScript's `addExternalFunction()` only accepts plain C function pointers with no void* userData/context parameter, and `setFunctionToSync()` accepts a single void(*)() with no per-instance binding. There is no way to attach per-instance state to callbacks within the current ESPLiveScript API. This is a known limitation acknowledged with a `// todo: this is not working well with multiple scripts running!!!` comment at line 211. Fixing it would require modifying the ESPLiveScript library itself to support userData/context on callbacks.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@ewowi ewowi changed the title Livescripts tuning Livescripts tutorial, WiFi handling, Network in/out, rest API Apr 4, 2026
@ewowi ewowi merged commit 1327897 into main Apr 4, 2026
39 checks passed
@ewowi ewowi deleted the livescript-tuning branch April 4, 2026 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant