Skip to content

Conversation

@softhack007
Copy link
Collaborator

@softhack007 softhack007 commented Dec 18, 2025

Backport of the new PixelForge by @DedeHai

Additional Improvements

  • improved stability when several tasks try to draw/show leds in parallel
  • json de-serialializer: interlock added to prevent segment updates while strip.service() runs
  • json de-serialializer: reduced delay when updating presets (stip.service lock now released before writing to presets.json)
  • UDP realtime streaming: small speedup, improved stability and prevent "broken frames".

toDo:

  • [-] Make "scrolling text" tab work - adjustment to reflect available tags; also add #FPS, #AMP, #ERR
    -> postponed. Back-porting the relevant upstream "scrolling text" part will come later in a separate PR.
  • test if "other tools" work
    -> PixelPaint works, but refuses to accept more than 128 pixels in one dimension
    -> PixelMagic has known incompatibilities with WLED-MM (large image presets cause error message)
    -> we could add an additional download link for VideoLab (beta)
  • test with VideoLab output (DDP over ws)
  • more testing

Known Issues and Caveats

WebUI HTML build system:

  • If you see strange "file read errors" - make sure your working folder path does not have any # characters. For example, i was working in C:\Users\frank\Arduino\#ESP32\MoonModules\WLED-MM, had to rename #ESP32 to ESP32 to get npm run build working
  • If you see compile errors, delete all html_*.h files, to force re-generation
  • if "npm install" or "npm run build" still abort with errors, try to delete the content of your npm-cache folder (e.g. C:\Users\frank\AppData\Local\npm-cache)

other

  • options in the "scrolling text" tab only work in upstream, but the syntax in WLED-MM is still based on 0.14.x. Need to adjust this later, or maybe we'll backport the upstream scrolling text code in a new PR.

Summary by CodeRabbit

  • New Features

    • Adds PixelForge, Pixel Magic and PixArt web tools with image/GIF editing, gallery, upload and preset export; adds GIF encode/decode support.
  • Tests

    • Adds Node.js tests for UI build tooling and incremental rebuild behavior.
  • Style

    • Externalized stylesheet, improved button hover/transition effects and accessibility titles.
  • Refactor

    • Modernizes frontend build/minification and integrates UI build into the device build process.
  • Chores

    • Updates package metadata, adds test script, requires Node >=20 and upgrades build tools.
  • Stability

    • Adds runtime synchronization, file-write locking, build validation and other reliability improvements.

✏️ Tip: You can customize this high-level summary in your review settings.

softhack007 and others added 3 commits December 18, 2025 23:40
align with tools/cdata.js from upstream WLED

--> prerequisite for pixelforge tool !
replaces the pixel magic tool with a much more feature-rich tool for handling gif images. Also adds a scrolling text interface and the possibility to add more tools with a single button click like the classic pixel magic tool and the pixel-painter tool.
* different call for listing images
* different call for deleting images
* minor cleanups
@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

📝 Walkthrough

Walkthrough

Adds PixelForge/PixArt UIs and GIF codec, introduces a Node-based frontend build step and tests, PlatformIO middleware and a post-link usermod validator, enables PixelForge/PixArt build flags across many envs, updates npm tooling, and adds ESP32 mutex/spinlock protections across drawing, realtime, and file operations.

Changes

Cohort / File(s) Summary
Package & Tooling
package.json
Update description, add test script, bump/replace deps (clean-css, html-minifier-terser, web-resource-inliner, nodemon), remove inliner/zlib, add engines.node: ">=20.0.0".
Frontend build runner
pio-scripts/build_ui.py
New pre-build script: verifies Node, runs npm ci and npm run build, aborts build on failures with colored messages.
Build metadata middleware
pio-scripts/set_metadata.py
New middleware: reads git repo and package.json version, injects WLED_REPO and WLED_VERSION into CPPDEFINES for wled_metadata.cpp builds.
Post-link usermod validator
pio-scripts/validate_modules.py
New post-link validator: parses linker .map, verifies usermod object contributions, fails build if objects missing.
PlatformIO config
platformio.ini
Replace pre-build hook with pio-scripts/build_ui.py; enable -D WLED_ENABLE_PIXELFORGE and -D WLED_ENABLE_PIXART across many envs; minor pin/flag tweaks.
cdata build tool & tests
tools/cdata.js, tools/cdata-test.js
Modernize imports to node: scheme, switch to web-resource-inliner, async minify/gzip pipeline, export timestamp helpers, add comprehensive incremental-build tests.
PixelForge UI & GIF codec
wled00/data/pixelforge/pixelforge.htm, wled00/data/pixelforge/omggif.js
Add PixelForge SPA and self-contained GifWriter/GifReader (LZW encode/decode, palettes, frames, loops, transparency).
Pixel Magic tool
wled00/data/pxmagic/pxmagic.htm
Add Pixel Magic single-file tool for image→WLED preset conversion, animation/export, simulation.
Shared client utilities
wled00/data/common.js
New client utilities: DOM helpers, tooltips/toasts, resource loader, URL helpers, upload, WebSocket connector, DDP chunk sender.
Server static routes
wled00/wled_server.cpp, wled00/data/common.js
Serve /pixelforge.htm and /common.js (gzip + cache headers); wired into initServer and serveSettingsJS under compile guards.
Static UI updates
wled00/data/index.htm, wled00/data/settings.htm, wled00/data/style.css
Add PixelForge link/button and hidden nav buttons; move styling to external CSS; add button transitions/hover and accessibility titles.
ESP32 concurrency & drawing sync
wled00/wled.h, wled00/wled.cpp, wled00/FX.h, wled00/FX_fcn.cpp, wled00/udp.cpp, wled00/json.cpp, wled00/util.cpp, wled00/e131.cpp, wled00/file.cpp, wled00/presets.cpp, wled00/fcn_declare.h, wled00/ws.cpp
Add busDrawMux, segmentMux, jsonBufferLockMutex, presetFileMux and macros (esp32SemTake/esp32SemGive, WLED_create_spinlock); introduce mutex/spinlock protections across drawing, segment management, realtime packet handling, JSON/presets writes and file writes; update requestJSONBufferLock signature and call sites; change WS2812FX::waitUntilIdle signature.
Map/UI wiring & settings toggles
wled00/xml.cpp, wled00/data/pixart/pixart.js
Show/hide settings buttons for PixArt/PxMagic/PixelForge; small JS scope fix (const cv).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant PIO as PlatformIO
    participant BuildPy as pio-scripts/build_ui.py
    participant Node as Node/npm
    participant Compiler as C++ Compiler
    participant Middleware as set_metadata.py
    participant Linker as Linker (.map)
    participant Validator as validate_modules.py
    participant Server as WLED Server
    participant Browser as Client

    PIO->>BuildPy: run pre-build hook
    BuildPy->>Node: npm ci
    Node-->>BuildPy: deps installed
    BuildPy->>Node: npm run build (generate html headers)
    Node-->>BuildPy: headers produced
    BuildPy-->>PIO: place headers into build dir

    PIO->>Compiler: compile sources
    Compiler->>Middleware: build wled_metadata.cpp
    Middleware->>Compiler: inject CPPDEFINES (WLED_REPO/WLED_VERSION)
    Compiler->>Linker: link -> ELF + .map
    Linker-->>PIO: .map produced

    PIO->>Validator: post-link hook
    Validator->>Linker: parse .map for usermod objects
    alt all usermods present
        Validator-->>PIO: success
    else missing objects
        Validator-->>PIO: abort build
    end

    Browser->>Server: GET /pixelforge.htm
    Server-->>Browser: gzip pixelforge + common.js (cached)
    Browser->>Server: upload / websocket (DDP)
    Server-->>Browser: responses/events
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • Upgrade reporting #286 — Overlapping edits to tools/cdata.js build logic and header timestamping; likely directly related.

Poem

I'm a hopping rabbit, nibbling code by night,
I wrapped GIFs and built UIs under soft moonlight.
I threaded locks for safety and made builds run clean,
Headers, tests, and PixelForge — a crunchy, leafy scene. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes: PixelForge backport, UI stability improvements, and UDP real-time speedup.
✨ Finishing touches
  • 📝 Generate docstrings

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

@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: 9

🧹 Nitpick comments (11)
tools/cdata.js (2)

18-24: Inconsistent node: prefix usage in imports.

Line 19 uses require("path") while lines 18 and 21 use the node: prefix (node:fs, node:zlib). For consistency and to explicitly signal built-in modules, consider using node:path.

🔎 Proposed fix
 const fs = require("node:fs");
-const path = require("path");
+const path = require("node:path");
 const inline = require("web-resource-inliner");
 const zlib = require("node:zlib");

216-228: Potential issue: directory mtime is checked before recursing.

On line 220, isFileNewerThan(filePath, time) is called for all entries including directories. Directory mtimes change when files are added/removed in them, which may cause false positives. Additionally, if the intent is only to check file contents, checking directory mtimes may be misleading.

🔎 Proposed fix: Check file type before mtime comparison
 function isAnyFileInFolderNewerThan(folderPath, time) {
   const files = fs.readdirSync(folderPath, { withFileTypes: true });
   for (const file of files) {
     const filePath = path.join(folderPath, file.name);
-    if (isFileNewerThan(filePath, time)) {
-      return true;
-    }
     if (file.isDirectory() && isAnyFileInFolderNewerThan(filePath, time)) {
       return true;
+    } else if (file.isFile() && isFileNewerThan(filePath, time)) {
+      return true;
     }
   }
   return false;
 }
tools/cdata-test.js (2)

120-123: Flaky assertion: 500ms threshold may be too tight.

The 500ms window for checking if a file was "newly created" could cause intermittent test failures on slower CI systems or under heavy load. Consider increasing the threshold or using a different approach (e.g., comparing mtimes before and after the operation).

🔎 Proposed fix
-  async function checkIfFileWasNewlyCreated(file) {
-    const modifiedTime = fs.statSync(file).mtimeMs;
-    assert(Date.now() - modifiedTime < 500, file + ' was not modified');
+  async function checkIfFileWasNewlyCreated(file, beforeTime) {
+    const modifiedTime = fs.statSync(file).mtimeMs;
+    assert(modifiedTime > beforeTime, file + ' was not modified');
   }

Then capture beforeTime = Date.now() before running the script.


5-6: Inconsistent module import style with cdata.js.

The test file uses require('fs') while cdata.js uses require('node:fs'). For consistency across the codebase, consider aligning the import style.

🔎 Proposed fix
-const fs = require('fs');
-const path = require('path');
+const fs = require('node:fs');
+const path = require('node:path');
pio-scripts/set_metadata.py (1)

84-85: Consider adding error handling for package.json parsing.

If package.json is malformed or missing the version field, this will raise an unhandled exception during the build. Consider wrapping this in a try/except with a sensible fallback.

🔎 Proposed fix
-with open("package.json", "r") as package:
-    WLED_VERSION = json.load(package)["version"]
+try:
+    with open("package.json", "r") as package:
+        WLED_VERSION = json.load(package).get("version", "unknown")
+except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+    print(f"Warning: Could not read version from package.json: {e}")
+    WLED_VERSION = "unknown"
pio-scripts/validate_modules.py (1)

47-50: Minor style nit: double space and unnecessary return.

Line 50 has a double space before env.subst, and the explicit return None on line 76 is unnecessary (Python implicitly returns None). The source and target parameters are required by the SCons post-action callback signature, so the static analysis warnings about them being unused are false positives.

🔎 Proposed fix
 def validate_map_file(source, target, env):
     """ Validate that all modules appear in the output build """
     build_dir = Path(env.subst("$BUILD_DIR"))
-    map_file_path = build_dir /  env.subst("${PROGNAME}.map")
+    map_file_path = build_dir / env.subst("${PROGNAME}.map")

And at line 76:

-    return None
+    # SCons post-action doesn't use return value
wled00/data/pixelforge/pixelforge.htm (3)

540-541: TODO: Image loading can fail with 503 when switching tabs.

The comment indicates a known issue where switching tabs can cause 503 errors and unloaded images. This is flagged in the PR objectives as "More testing" being a remaining TODO.

Would you like help implementing a retry mechanism with exponential backoff to handle transient 503 errors more gracefully?


10-10: TODO: Sequential loading and code optimization.

This TODO notes pending work for sequential loading of scripts and code size optimization (e.g., getURL() from common.js). Consider tracking this in a follow-up issue.

Do you want me to open an issue to track these optimizations?


1168-1170: Unhandled promise rejection in fetch chain.

The txtUp function catches errors but only logs them. Consider providing user feedback via the existing msg() toast function for consistency with other error handlers in this file.

🔎 Proposed fix
 	fetch(`${wu}/json/state`,{method:'POST',body:JSON.stringify(j)})
 		.then(r => { if(r.ok) segLoad(); })
-		.catch(console.error);
+		.catch(e => { console.error(e); msg('Failed to update', 'err'); });
wled00/data/pxmagic/pxmagic.htm (2)

1418-1424: External API dependency introduces reliability and privacy concerns.

The animation frame extraction relies on an external third-party service (pixelmagictool.vercel.app). This creates:

  1. A single point of failure if the service is unavailable
  2. Potential privacy concerns as user images are sent to an external server
  3. Dependency on external service availability

Consider documenting this dependency or providing a fallback/local implementation.


850-858: Consider a more informative fallback for missing hostname.

The fallback to "0.0.0.0" when no hostname is available may confuse users. Consider using "localhost" or displaying a clearer error state.

🔎 Proposed fix
     const host = params.get("hn")
       ? params.get("hn")
       : window.location.host
       ? window.location.host
-      : "0.0.0.0";
+      : "localhost";
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5deaf92 and 45b97e8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • package.json (2 hunks)
  • pio-scripts/build_ui.py (1 hunks)
  • pio-scripts/set_metadata.py (1 hunks)
  • pio-scripts/validate_modules.py (1 hunks)
  • platformio.ini (1 hunks)
  • tools/cdata-test.js (1 hunks)
  • tools/cdata.js (6 hunks)
  • wled00/data/index.htm (1 hunks)
  • wled00/data/pixelforge/omggif.js (1 hunks)
  • wled00/data/pixelforge/pixelforge.htm (1 hunks)
  • wled00/data/pxmagic/pxmagic.htm (1 hunks)
  • wled00/data/settings.htm (1 hunks)
  • wled00/data/style.css (2 hunks)
  • wled00/wled_server.cpp (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**/*.{htm,html,css,js}: Use tabs for indentation in web UI files (.htm/.html/.css/.js) under wled00/data
After editing web UI files, run npm run build to regenerate embedded headers
Manually validate web UI changes via local HTTP server and browser

Files:

  • wled00/data/pxmagic/pxmagic.htm
  • wled00/data/pixelforge/pixelforge.htm
  • wled00/data/pixelforge/omggif.js
  • wled00/data/index.htm
  • wled00/data/style.css
  • wled00/data/settings.htm
wled00/data/**/*.{htm,html,js,css}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Check the browser console for JavaScript errors after web UI changes

Files:

  • wled00/data/pxmagic/pxmagic.htm
  • wled00/data/pixelforge/pixelforge.htm
  • wled00/data/pixelforge/omggif.js
  • wled00/data/index.htm
  • wled00/data/style.css
  • wled00/data/settings.htm
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/wled_server.cpp
🧠 Learnings (14)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : After editing web UI files, run npm run build to regenerate embedded headers
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : After editing web UI files, run npm run build to regenerate embedded headers

Applied to files:

  • wled00/data/pxmagic/pxmagic.htm
  • package.json
  • wled00/data/pixelforge/pixelforge.htm
  • tools/cdata.js
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Manually validate web UI changes via local HTTP server and browser

Applied to files:

  • wled00/data/pxmagic/pxmagic.htm
  • wled00/data/pixelforge/pixelforge.htm
  • tools/cdata.js
  • wled00/data/index.htm
  • wled00/data/settings.htm
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,js,css} : Check the browser console for JavaScript errors after web UI changes

Applied to files:

  • wled00/data/pxmagic/pxmagic.htm
  • wled00/data/pixelforge/pixelforge.htm
  • tools/cdata.js
  • wled00/data/index.htm
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/html_*.h : Do not edit generated embedded web header files: wled00/html_*.h

Applied to files:

  • wled00/data/pixelforge/pixelforge.htm
  • tools/cdata.js
  • wled00/wled_server.cpp
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Always commit both source web files and the generated wled00/html_*.h files

Applied to files:

  • tools/cdata.js
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web UI files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • tools/cdata.js
  • wled00/data/settings.htm
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Always build the web UI first before any hardware/firmware build

Applied to files:

  • tools/cdata.js
  • platformio.ini
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Always run npm test before finishing work

Applied to files:

  • tools/cdata-test.js
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-11-02T22:17:26.380Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-06-30T17:53:37.232Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 248
File: usermods/audioreactive/audio_reactive.h:199-200
Timestamp: 2025-06-30T17:53:37.232Z
Learning: In WLED AudioReactive usermod, when using ArduinoFFT (enabled via UM_AUDIOREACTIVE_USE_ARDUINO_FFT), it's acceptable and necessary to redefine the global sqrt macro to sqrtf within the conditional compilation block for performance optimization. This is a specific requirement for ArduinoFFT optimization that can provide 10-50% performance improvement on ESP32.

Applied to files:

  • wled00/wled_server.cpp
🧬 Code graph analysis (2)
tools/cdata-test.js (1)
tools/cdata.js (3)
  • fs (18-18)
  • path (19-19)
  • files (217-217)
wled00/wled_server.cpp (1)
wled00/file.cpp (2)
  • handleFileRead (513-558)
  • handleFileRead (513-513)
🪛 Biome (2.1.2)
tools/cdata.js

[error] 249-249: Illegal return statement outside of a function

(parse)


[error] 256-256: Illegal return statement outside of a function

(parse)

wled00/data/pixelforge/omggif.js

[error] 438-438: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 464-464: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 465-465: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 466-466: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 467-467: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 468-468: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 469-469: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 470-470: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 471-471: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 472-472: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 473-473: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 474-474: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 475-475: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 483-483: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 512-512: This code is unreachable

(lint/correctness/noUnreachable)


[error] 32-32: Shouldn't redeclare 'gopts'. Consider to delete it or rename it.

'gopts' is defined here:

(lint/suspicious/noRedeclare)


[error] 32-32: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 32-32: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 448-448: Shouldn't redeclare 'block_size'. Consider to delete it or rename it.

'block_size' is defined here:

(lint/suspicious/noRedeclare)


[error] 477-477: Shouldn't redeclare 'has_local_palette'. Consider to delete it or rename it.

'has_local_palette' is defined here:

(lint/suspicious/noRedeclare)


[error] 487-487: Shouldn't redeclare 'block_size'. Consider to delete it or rename it.

'block_size' is defined here:

(lint/suspicious/noRedeclare)

🪛 Ruff (0.14.8)
pio-scripts/build_ui.py

1-1: Undefined name Import

(F821)


8-8: Undefined name env

(F821)


13-13: Undefined name env

(F821)


16-16: Undefined name env

(F821)

pio-scripts/set_metadata.py

1-1: Undefined name Import

(F821)


23-23: Starting a process with a partial executable path

(S607)


28-28: subprocess call: check for execution of untrusted input

(S603)


28-28: Starting a process with a partial executable path

(S607)


40-40: subprocess call: check for execution of untrusted input

(S603)


40-40: Starting a process with a partial executable path

(S607)


67-67: Consider moving this statement to an else block

(TRY300)


75-75: Do not catch blind exception: Exception

(BLE001)


113-113: Undefined name env

(F821)

pio-scripts/validate_modules.py

47-47: Unused function argument: source

(ARG001)


47-47: Unused function argument: target

(ARG001)


78-78: Undefined name Import

(F821)


79-79: Undefined name env

(F821)


79-79: Undefined name env

(F821)


80-80: Undefined name env

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (seeed_esp32c3_4MB_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32dev_compat)
🔇 Additional comments (15)
platformio.ini (1)

223-223: LGTM!

The pre-build script replacement from build-html.py to build_ui.py aligns with the PR's objective to upgrade the HTML build system. The new naming convention is consistent with Python standards.

pio-scripts/set_metadata.py (1)

1-4: LGTM - PlatformIO-specific constructs are valid.

The static analysis warnings about Import and env being undefined are false positives. These are PlatformIO/SCons-specific constructs that are injected at runtime. The subprocess calls use hardcoded git commands with no user input, so the security warnings are also not applicable here.

package.json (1)

26-33: Dependency updates are appropriate for the build system upgrade.

The migration from inliner to web-resource-inliner and removal of zlib (in favor of built-in node:zlib) are sensible changes. The Node.js >=20.0.0 engine requirement aligns with the built-in test runner usage. All specified dependency versions are secure: web-resource-inliner ^7.0.0 has no known vulnerabilities, html-minifier-terser ^7.2.0 has no direct vulnerabilities, clean-css ^5.3.3 is safe to use, and nodemon ^3.1.9 shows no reported security vulnerabilities. Consider updating nodemon to 3.1.10 or later for the latest improvements.

pio-scripts/validate_modules.py (1)

78-80: Build integration uses standard SCons/PlatformIO idioms.

The Import("env") is the standard SCons idiom to import the build environment, and env.Append/env.AddPostAction correctly wire up the validation. The Ruff warnings about undefined names are false positives in this PlatformIO context.

wled00/data/pixelforge/pixelforge.htm (2)

436-445: Verify the external tool download URL.

The initialization constructs wu from a query parameter or falls back to window.location.host, which is the expected pattern for cross-origin tooling. However, line 589 hardcodes an external URL (https://dedehai.github.io/) for downloading additional tools. Please verify this is the intended and trusted source.


324-325: Scrolling Text tab marked as not yet supported in WLED-MM.

The comments indicate the Scrolling Text functionality (lines 324-392) is not yet supported in WLED-MM, which aligns with the PR objective "Make scrolling text tab work" being listed as a remaining TODO.

wled00/data/pixelforge/omggif.js (2)

1-28: Third-party library (omggif) vendored with MIT license.

This is a well-established GIF encoder/decoder library. The static analysis warnings (variable redeclarations, switch declarations, unreachable code) are characteristic of pre-ES6 JavaScript patterns and are not worth modifying in vendored code.


807-809: **GifReader export is commented out.**The original omggif library exports both GifWriter and GifReader, but here only GifWriter is exported. Since pixelforge.htm uses GifReader directly (line 639), this works because both classes are in the global scope when loaded via <script> tag. The commented-out line appears intentional for this browser-based usage.

wled00/data/style.css (1)

36-48: Button styling improvements look good.

The added transition and hover effects improve the user experience with visual feedback. As per coding guidelines, remember to run npm run build to regenerate embedded headers and manually validate the changes via local HTTP server.

wled00/wled_server.cpp (2)

12-14: LGTM!

The conditional include for PixelForge follows the same pattern as the existing PIXART feature flag, maintaining consistency in the codebase.


454-465: LGTM!

The PixelForge route handler correctly follows the established pattern used by other static pages (pixart.htm, cpal.htm, simple.htm):

  1. First attempts to serve from filesystem via handleFileRead
  2. Checks ETag cache headers for 304 responses
  3. Falls back to embedded gzipped content with proper cache headers

The use of static const char in PROGMEM for the path string is appropriate for ESP memory optimization.

wled00/data/settings.htm (2)

71-71: LGTM!

The @import directive correctly references the external stylesheet, improving maintainability by centralizing common styles. This aligns with the broader refactoring to move shared styles to style.css.


79-87: LGTM!

The button styling simplification to display: block; padding: 0; with other visual properties moved to the external stylesheet is a clean separation of concerns.

wled00/data/index.htm (1)

207-209: LGTM!

The PixelForge navigation button follows the established pattern for custom palette navigation, correctly handling both local file protocol and HTTP access via the loc variable. The added title attributes on all three buttons improve accessibility.

wled00/data/pxmagic/pxmagic.htm (1)

10-565: CSS and HTML structure looks well-organized.

The embedded CSS uses CSS variables for theming, includes responsive breakpoints, and follows a consistent naming convention. The HTML form structure has proper validation attributes.

Comment on lines +6 to +9
if node_ex is None:
print('\x1b[0;31;43m' + 'Node.js is not installed or missing from PATH html css js will not be processed check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
exitCode = env.Execute("null")
exit(exitCode)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Problematic "null" command execution.

Executing "null" as a shell command is incorrect—it will fail on most systems (the command doesn't exist). The intent appears to be aborting the build with a non-zero exit code. Consider using Exit(1) from SCons directly or exit(1).

🔎 Proposed fix
+from SCons.Script import Exit
+
 node_ex = shutil.which("node")
 # Check if Node.js is installed and present in PATH if it failed, abort the build
 if node_ex is None:
     print('\x1b[0;31;43m' + 'Node.js is not installed or missing from PATH html css js will not be processed check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
-    exitCode = env.Execute("null")
-    exit(exitCode)
+    Exit(1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if node_ex is None:
print('\x1b[0;31;43m' + 'Node.js is not installed or missing from PATH html css js will not be processed check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
exitCode = env.Execute("null")
exit(exitCode)
from SCons.Script import Exit
node_ex = shutil.which("node")
# Check if Node.js is installed and present in PATH if it failed, abort the build
if node_ex is None:
print('\x1b[0;31;43m' + 'Node.js is not installed or missing from PATH html css js will not be processed check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
Exit(1)
🧰 Tools
🪛 Ruff (0.14.8)

8-8: Undefined name env

(F821)

🤖 Prompt for AI Agents
In pio-scripts/build_ui.py around lines 6 to 9, remove the incorrect
env.Execute("null") call and stop using a non-existent "null" shell command;
instead abort with a proper non-zero exit: call SCons' Exit(1) (from
SCons.Script) if the script is running under SCons, or use sys.exit(1) / exit(1)
to terminate the process with status 1, and keep the existing error print before
that.

Comment on lines +11 to +13
# Install the necessary node packages for the pre-build asset bundling script
print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m')
env.Execute("npm ci")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing error check on npm ci.

The return value of npm ci is not checked. If dependency installation fails, the build will continue and likely fail later with a confusing error.

🔎 Proposed fix
     # Install the necessary node packages for the pre-build asset bundling script
     print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m')
-    env.Execute("npm ci")
+    exitCode = env.Execute("npm ci")
+    if exitCode:
+        print('\x1b[0;31;43m' + 'npm ci failed check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
+        exit(exitCode)
🧰 Tools
🪛 Ruff (0.14.8)

13-13: Undefined name env

(F821)

🤖 Prompt for AI Agents
In pio-scripts/build_ui.py around lines 11-13, the script calls env.Execute("npm
ci") but doesn’t check its return code; capture the return value (rc =
env.Execute("npm ci")) and if rc is non-zero, print/log an error with context
and exit with a non-zero status (e.g., sys.exit(1) or raise RuntimeError) so the
build stops immediately when npm ci fails.

Comment on lines +247 to +257
// Don't run this script if we're in a test environment
if (process.env.NODE_ENV === 'test') {
return;
}

console.info(wledBanner);

if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.argv[2] !== '-f') {
console.info("Web UI is already built");
return;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Illegal return statements at module level.

The static analysis correctly identifies that return statements at lines 249 and 256 are outside any function, which is technically invalid in strict CommonJS modules and may cause issues in certain Node.js configurations or when bundling.

🔎 Proposed fix: Use process.exit() or conditional execution
 // Don't run this script if we're in a test environment
 if (process.env.NODE_ENV === 'test') {
-  return;
+  // Skip execution in test environment
+} else {
+  console.info(wledBanner);
+
+  if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.argv[2] !== '-f') {
+    console.info("Web UI is already built");
+  } else {
+    writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index', false);
+    // ... rest of build invocations
+  }
 }
-
-console.info(wledBanner);
-
-if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.argv[2] !== '-f') {
-  console.info("Web UI is already built");
-  return;
-}

Alternatively, wrap the main logic in an async IIFE:

+async function main() {
   // Don't run this script if we're in a test environment
   if (process.env.NODE_ENV === 'test') {
     return;
   }
   
   console.info(wledBanner);
   
   if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.argv[2] !== '-f') {
     console.info("Web UI is already built");
     return;
   }
   
   // ... build invocations
+}
+
+main().catch(console.error);

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 249-249: Illegal return statement outside of a function

(parse)


[error] 256-256: Illegal return statement outside of a function

(parse)

🤖 Prompt for AI Agents
In tools/cdata.js around lines 247 to 257, the code contains module-level return
statements which are illegal in CommonJS modules; replace those returns with
proper early-exit behavior: either wrap the script's main logic in a
function/async IIFE and use return inside that function, or keep top-level flow
and call process.exit(0) for normal early exits (and process.exit(1) on fatal
errors). Update both occurrences (the NODE_ENV test and the "already built"
check) accordingly and ensure any cleanup or logging happens before calling
process.exit.

@softhack007
Copy link
Collaborator Author

softhack007 commented Dec 18, 2025

@DedeHai if you find some time, it would be cool if you could check that I've found all the "pitfalls" that need adjustments for 0.14.x and 0.15.x APIs.

Edit: how about adding an extra download link for your new VideoLab tool ? WLED-MM supports DDP over webSockets, so i guess it should work.

Copy link

@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

♻️ Duplicate comments (2)
tools/cdata.js (2)

142-165: Callback-based inline.html not properly integrated with async/await.

The writeHtmlGzipped function is declared async but uses a callback-based API (inline.html) without wrapping it in a Promise. This means the function returns immediately before the callback completes, which can cause race conditions when multiple files are processed concurrently.


247-257: Illegal return statements at module level.

The static analysis correctly identifies that return statements at lines 249 and 256 are outside any function. While Node.js CommonJS modules technically allow this (the module is wrapped in a function internally), it's non-standard and may cause issues when bundling or in strict ESM contexts.

🧹 Nitpick comments (1)
wled00/wled_server.cpp (1)

454-471: Route handlers follow established patterns.

The PixelForge route handlers correctly mirror existing patterns for serving static HTML and JavaScript content with proper caching and gzip encoding.

Optional: Remove commented-out code

Line 457 contains a commented-out alternative implementation referencing a non-existent handleStaticContent function. Since the current implementation is working and follows the established pattern, consider removing this dead code for cleaner maintainability.

  server.on(_pixelforge_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
-    //handleStaticContent(request, FPSTR(_pixelforge_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixelforge, PAGE_pixelforge_length);
    if (handleFileRead(request, FPSTR(_pixelforge_htm))) return;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45b97e8 and eeb4973.

📒 Files selected for processing (3)
  • tools/cdata.js (6 hunks)
  • wled00/data/common.js (1 hunks)
  • wled00/wled_server.cpp (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/wled_server.cpp
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**/*.{htm,html,css,js}: Use tabs for indentation in web UI files (.htm/.html/.css/.js) under wled00/data
After editing web UI files, run npm run build to regenerate embedded headers
Manually validate web UI changes via local HTTP server and browser

Files:

  • wled00/data/common.js
wled00/data/**/*.{htm,html,js,css}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Check the browser console for JavaScript errors after web UI changes

Files:

  • wled00/data/common.js
🧠 Learnings (14)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/html_*.h : Do not edit generated embedded web header files: wled00/html_*.h

Applied to files:

  • wled00/wled_server.cpp
  • tools/cdata.js
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-11-02T22:17:26.380Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-06-30T17:53:37.232Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 248
File: usermods/audioreactive/audio_reactive.h:199-200
Timestamp: 2025-06-30T17:53:37.232Z
Learning: In WLED AudioReactive usermod, when using ArduinoFFT (enabled via UM_AUDIOREACTIVE_USE_ARDUINO_FFT), it's acceptable and necessary to redefine the global sqrt macro to sqrtf within the conditional compilation block for performance optimization. This is a specific requirement for ArduinoFFT optimization that can provide 10-50% performance improvement on ESP32.

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,js,css} : Check the browser console for JavaScript errors after web UI changes

Applied to files:

  • wled00/data/common.js
  • tools/cdata.js
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : After editing web UI files, run npm run build to regenerate embedded headers

Applied to files:

  • tools/cdata.js
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • tools/cdata.js
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Manually validate web UI changes via local HTTP server and browser

Applied to files:

  • tools/cdata.js
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Always commit both source web files and the generated wled00/html_*.h files

Applied to files:

  • tools/cdata.js
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web UI files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • tools/cdata.js
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Always build the web UI first before any hardware/firmware build

Applied to files:

  • tools/cdata.js
🧬 Code graph analysis (1)
wled00/wled_server.cpp (1)
wled00/file.cpp (2)
  • handleFileRead (513-558)
  • handleFileRead (513-513)
🪛 Biome (2.1.2)
tools/cdata.js

[error] 249-249: Illegal return statement outside of a function

(parse)


[error] 256-256: Illegal return statement outside of a function

(parse)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp8266_2MB_S)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32dev_compat)
🔇 Additional comments (18)
wled00/wled_server.cpp (1)

12-14: LGTM - Consistent conditional include pattern.

The conditional include follows the same pattern as PixArt and other optional features, properly guarding the PixelForge headers.

tools/cdata.js (9)

18-27: LGTM!

The imports are properly structured using Node.js built-in module prefixes (node:fs, node:zlib). The module exports for isFileNewerThan and isAnyFileInFolderNewerThan are correctly hoisted since they reference function declarations defined later in the file.


42-61: LGTM!

The generateBuildTime function and singleHeader constant provide a clean cache-busting mechanism. The #ifdef/#undef/#define pattern ensures the build timestamp is always fresh, and PROGMEM is correctly used for embedded targets.


98-112: LGTM!

The adoptVersionAndRepo function correctly handles URL replacement for the WLED-MM fork while preserving upstream credits. The replaceAll usage is compatible with the Node 20+ requirement.


167-207: LGTM!

The specToChunk and writeChunks functions are properly implemented with async/await. The sequential processing in writeChunks is appropriate, and error handling for unknown methods is correctly implemented.


209-228: LGTM with minor note.

The helper functions are well-structured for build freshness checks. Note that isFileNewerThan will throw if a file is deleted between readdirSync and statSync calls, but this race condition is unlikely during normal build operations.


230-245: LGTM!

The isAlreadyBuilt function correctly implements build freshness detection by:

  1. Finding the oldest output file timestamp
  2. Checking if any source file, package.json, or the script itself is newer
  3. Gracefully handling missing output files by triggering a rebuild

Including __filename in the check ensures that script changes trigger a rebuild, which is a good practice.


259-278: PixelForge build configuration added correctly.

The new pixelforge.htm build entry at line 263 correctly disables CSS inlining (inlineCss = false), which aligns with the PR objectives. The migration of edit.htm from writeHtmlGzipped to writeChunks is consistent with the build refactoring.


307-312: LGTM!

The new common.js chunk configuration is correctly added with gzip compression and JS minification, consistent with the build system upgrade.


480-490: LGTM!

The change to gzip with js-minify for iro.js and rangetouch.js is a good optimization that will reduce the embedded size of these JavaScript libraries.

wled00/data/common.js (8)

10-15: Type guard implementations look correct.

The implementations properly handle their respective type checks. Note that isE(o) expects an object argument and will throw on null/undefined, but this is acceptable given the name implies "isEmpty" for objects.


17-53: Tooltip implementation is functional with minor edge-case caveat.

The tooltip logic correctly positions and displays tooltips. The cleanup at lines 45-48 removes all .tooltip elements rather than tracking the specific tooltip for this element. In typical usage this is fine, but if multiple tooltips could trigger simultaneously, there's potential interference.


106-129: Location and URL helpers handle multiple hosting scenarios correctly.

The getLoc() function properly detects file mode, reverse proxy configurations, and normal hosting. The use of prompt() at line 112 is acceptable for file mode initial setup, though it blocks the UI. The path manipulation logic correctly handles trailing slashes and nested paths.


130-139: Toast notification implementation is correct.

The function properly manages toast display with timeout cleanup and graceful handling of missing DOM elements. The global timeout variable ensures only one toast shows at a time.


140-150: File upload implementation is correct.

The function properly constructs FormData, sends via XMLHttpRequest, handles success/error cases, and clears the file input afterward. The return value of false suggests this is designed to be used as an event handler.


151-168: WebSocket connection management is well-implemented.

The function elegantly reuses existing WebSocket connections when available and creates new ones when needed. The try-catch at line 154 properly handles cross-origin access restrictions. Setting binaryType to "arraybuffer" at line 164 is essential for the binary DDP protocol used later.


170-210: DDP protocol implementation is correct and well-structured.

The function properly implements the DDP (Distributed Display Protocol) framing with:

  • Correct packet structure (WLED indicator + 10-byte DDP header + RGB data)
  • Proper frame size limits (472 RGB pixels = 1416 bytes data + 10 DDP + 1 WLED + 1 WebSocket = 1428 total)
  • Chunking logic with accurate offset calculations
  • Push flag set only on the final packet to trigger rendering
  • Robust error handling with try-catch

The commented ESP8266 support at line 178 could be helpful if re-enabled in the future.


87-105: GetV() is called without being defined in common.js, but this may be dead code.

Line 97 calls GetV() which is not defined in this file. While GetV() is defined as a placeholder in update.htm (injected with values), each settings page defines its own local loadJS() function that shadows this implementation and does not call GetV(). The enhanced loadJS() version in common.js (with preGetV/postGetV parameters) appears to be unused because pages define their own local versions that override it. Verify whether the common.js loadJS() implementation is actually called in any context—if not, this is dead code; if it is called, ensure GetV() is available globally in those contexts.

function gId(c) { return d.getElementById(c); } // getElementById
function cE(e) { return d.createElement(e); } // createElement
function gEBCN(c) { return d.getElementsByClassName(c); } // getElementsByClassName
function gN(s) { return d.getElementsByName(s)[0]; } // getElementsByName
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null safety to gN() function.

The function accesses index [0] without checking if any elements exist. If getElementsByName(s) returns an empty NodeList, this will return undefined and cause TypeErrors when callers expect an element.

🔎 Proposed fix
-function gN(s)      { return d.getElementsByName(s)[0]; } // getElementsByName
+function gN(s)      { const els = d.getElementsByName(s); return els.length > 0 ? els[0] : null; } // getElementsByName

Alternatively, if callers always expect an element to exist, document this assumption with a comment or add a runtime assertion.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function gN(s) { return d.getElementsByName(s)[0]; } // getElementsByName
function gN(s) { const els = d.getElementsByName(s); return els.length > 0 ? els[0] : null; } // getElementsByName
🤖 Prompt for AI Agents
In wled00/data/common.js around line 9, gN(s) currently returns
d.getElementsByName(s)[0] without checking for empty results; update it to
handle the case where getElementsByName returns no elements by checking the
NodeList length and returning null (or throwing/asserting if callers require a
guaranteed element). Replace the direct indexing with a safe conditional that
returns the first element when present and a null/undefined (or explicit error)
when not, and if you choose null ensure callers are updated or documented to
handle the null case.

@DedeHai
Copy link

DedeHai commented Dec 19, 2025

I will take a look. let me know if you have found any other issues (the rabbit does not seem too happy, did not read it yet though)
The reason I did not add the video lab tool yet is that is in a rough beta state, the UI is all ugly still and there are some bugs I need to work out. Since I am quite new to JS it takes me a lot of trial and error and help from AI. Once I deem it ready it will be added.

@softhack007
Copy link
Collaborator Author

softhack007 commented Dec 19, 2025

WLED-MM supports DDP over webSockets, so i guess it should work.

@DedeHai seems that DDP over webSockets - at least in WLED-MM - still needs some robustness improvements.
The problem is that webSockets run in the async_tcp tasks, and this brings back all the concurrent segment / effect and sPC access problems from last year, just on a "new level"; async_tcp has a very hight priority (prio=10) and it can completely block out the main looptask. I'll need some time to think about a solution.,..

this got lost while backporting upstream code
@DedeHai
Copy link

DedeHai commented Dec 19, 2025

WLED-MM supports DDP over webSockets, so i guess it should work.

@DedeHai seems that DDP over webSockets - at least in WLED-MM - still needs some robustness improvements. The problem is that webSockets run in the async_tcp tasks, and this brings back all the concurrent segment / effect and sPC access problems from last year, just on a "new level"; async_tcp has a very hight priority (prio=10) and it can completely block out the main looptask. I'll need some time to think about a solution.,..

What are the specific test scenarios? For 0.16 this works pretty well, tested on C3 without an FPS limit and it works flawlessly.

* protect pixel buffer changes with critical section
* protect segment list modification with critical section
* make _isServicing and _triggered atomic (move out of packed bitarray)
* allow custom timeout for waitUntilIdle - DDP needs a longer timeout
* protect strip.show() and effect drawing functions with mutex, to prevent crashes
Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/e131.cpp (1)

15-38: Tighten DDP semaphore handling and clean up disabled sequence logic

Two points here:

  1. Unused lastPushSeq with disabled filter

    lastPushSeq (Line 16) is now only referenced inside the #if 0 block (Lines 19–30). As written, it's unused in the compiled code and will typically warn.

    Easiest fix is to wrap its declaration in the same #if 0 or remove both together until the "fixme" logic is revisited.

  2. busDrawMux failure path reintroduces race + mis-give semantics

    In handleDDPPacket you now do:

    #if defined(ARDUINO_ARCH_ESP32)
    if (xSemaphoreTake(busDrawMux, 200) != pdTRUE) { delay(1); }
    #endif
    for (uint16_t i = start; i < stop; i++) {
      setRealtimePixel(...);
      pixels++;
    }
    packets++;
    #if defined(ARDUINO_ARCH_ESP32)
    xSemaphoreGive(busDrawMux);
    #endif

    If xSemaphoreTake times out, you still write pixels without synchronization and call xSemaphoreGive without having taken the semaphore—the latter pushes a binary semaphore into an inconsistent state. Skip the frame instead when the lock cannot be acquired in time, and only Give when Take succeeded:

    #if defined(ARDUINO_ARCH_ESP32)
  • if (xSemaphoreTake(busDrawMux, 200) != pdTRUE) { delay(1);}
  • BaseType_t gotLock = xSemaphoreTake(busDrawMux, 200);
  • if (gotLock != pdTRUE) {
  • delay(1);
  • return;
  • }
    #endif
    for (uint16_t i = start; i < stop; i++) {
    setRealtimePixel(...);
    pixels++;
    }
    packets++;
    #if defined(ARDUINO_ARCH_ESP32)
    xSemaphoreGive(busDrawMux);
    #endif
    
    
🧹 Nitpick comments (4)
wled00/FX_fcn.cpp (3)

1916-1932: Clarify and document waitUntilIdle(timeout) semantics

The new WS2812FX::waitUntilIdle(unsigned timeout) implementation:

  • Forces timeout to be at least MAX_IDLE_WAIT_MS (currently 120ms).
  • Spins until _isServicing clears or timeout expires.

This is fine for the current call sites (waitUntilIdle(350) and the helper wrapper), but it means:

  • Callers cannot request a “quick check” with a very small timeout; they always pay at least ~120ms when a service() is in progress.
  • strip_wait_until_idle() (no-arg) will also wait at least MAX_IDLE_WAIT_MS when a frame is in flight.

Consider adding a short comment in the header where waitUntilIdle is declared to document that behavior, and confirm all call sites are comfortable with this minimum wait rather than a custom per‑call value.


59-68: strip_wait_until_idle wrapper relies on default argument and uses dynamic String

Two minor points about strip_wait_until_idle(String whoCalledMe):

  • It calls strip.waitUntilIdle(); without passing a timeout; this assumes the declaration in FX.h provides a default argument. If you ever remove the default in the header, this will silently diverge. It’s slightly safer to pass an explicit timeout (e.g. MAX_IDLE_WAIT_MS) to keep call sites self‑documenting.
  • The String parameter causes a heap allocation every time this helper is used. Given it’s used for debug output only, switching to const char* (or const __FlashStringHelper*) would avoid dynamic allocation and fragmentation on constrained MCUs.

Example:

-void strip_wait_until_idle(String whoCalledMe) {
+void strip_wait_until_idle(const char* whoCalledMe) {
   ...
-  DEBUG_PRINTLN(whoCalledMe + String(": strip is still drawing effects."));
-  strip.waitUntilIdle();
+  DEBUG_PRINT(whoCalledMe);
+  DEBUG_PRINTLN(F(": strip is still drawing effects."));
+  strip.waitUntilIdle(MAX_IDLE_WAIT_MS);

1998-2013: Consider defensive coding for mutex acquire/release semantics

The mutex guarding in service() and show() correctly prevents concurrent drawing on ESP32. The current approach is functionally safe: when xSemaphoreTake fails, the control flow (continue or return) prevents xSemaphoreGive from being called, which is correct.

However, making the success of the Take explicit would improve clarity and future-proof against refactors:

#if defined(ARDUINO_ARCH_ESP32)
-  if (xSemaphoreTake(busDrawMux, 200) != pdTRUE) { delay(1); continue;}
+  BaseType_t gotLock = xSemaphoreTake(busDrawMux, 200);
+  if (gotLock != pdTRUE) { delay(1); continue; }
#endif
   ...
#if defined(ARDUINO_ARCH_ESP32)
-  xSemaphoreGive(busDrawMux);
+  if (gotLock == pdTRUE) xSemaphoreGive(busDrawMux);
#endif

Apply the same pattern in show(). Additionally, consider adding a debug log when the 200ms timeout occurs—this would surface stalls during stress testing.

wled00/FX.h (1)

1041-1041: Remove commented-out code.

The commented-out setPixelSegment declaration should be removed entirely rather than left as a comment, as it clutters the API surface and creates confusion about its availability.

🔎 Suggested cleanup
      __attribute__((pure)) getActiveSegsLightCapabilities(bool selectedOnly = false);
-      //setPixelSegment(uint8_t n);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10c20b7 and c25f082.

📒 Files selected for processing (6)
  • wled00/FX.h (3 hunks)
  • wled00/FX_fcn.cpp (10 hunks)
  • wled00/e131.cpp (3 hunks)
  • wled00/udp.cpp (1 hunks)
  • wled00/wled.cpp (1 hunks)
  • wled00/wled.h (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/wled.h
  • wled00/wled.cpp
  • wled00/udp.cpp
  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
  • wled00/FX.h
🧠 Learnings (16)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/html_*.h : Do not edit generated embedded web header files: wled00/html_*.h

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/wled.h
  • wled00/wled.cpp
  • wled00/e131.cpp
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/wled.h
  • wled00/wled.cpp
  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
  • wled00/FX.h
📚 Learning: 2025-10-22T21:50:25.839Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Applied to files:

  • wled00/wled.h
  • wled00/wled.cpp
  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
  • wled00/FX.h
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/wled.h
  • wled00/wled.cpp
  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
  • wled00/FX.h
📚 Learning: 2025-06-30T17:53:37.232Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 248
File: usermods/audioreactive/audio_reactive.h:199-200
Timestamp: 2025-06-30T17:53:37.232Z
Learning: In WLED AudioReactive usermod, when using ArduinoFFT (enabled via UM_AUDIOREACTIVE_USE_ARDUINO_FFT), it's acceptable and necessary to redefine the global sqrt macro to sqrtf within the conditional compilation block for performance optimization. This is a specific requirement for ArduinoFFT optimization that can provide 10-50% performance improvement on ESP32.

Applied to files:

  • wled00/wled.h
  • wled00/wled.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-02T22:17:26.380Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Applied to files:

  • wled00/wled.h
  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
  • wled00/FX.h
📚 Learning: 2025-11-24T00:15:38.069Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-24T00:15:38.069Z
Learning: In MoonModules/WLED-MM, when reading ESP32/ESP32-S3 eFuse blocks using esp_efuse_read_field_blob(): EFUSE_BLK0 has only 7 × 32-bit registers (224 bits total), not 8 registers (256 bits). Reading more than 7 words from EFUSE_BLK0 triggers assertion failure in esp_efuse_utility_read_reg(). Use maximum 7 words when reading EFUSE_BLK0 to avoid crashes. This applies to all ESP32 variants (ESP32, ESP32-S2, ESP32-S3, ESP32-C3).

Applied to files:

  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T22:24:46.163Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Applied to files:

  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-07-12T04:20:14.546Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Applied to files:

  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/**/*.{cpp,h} : Use spaces (2 per level) for C++ firmware source and headers in wled00

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-05-04T10:32:30.457Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-14T12:42:22.059Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 240
File: wled00/image_loader.cpp:0-0
Timestamp: 2025-11-14T12:42:22.059Z
Learning: In MoonModules/WLED-MM and embedded systems code, the pattern `unsigned timePassed = millis() - last_time;` is rollover-safe and proven to work correctly even when millis() wraps around at ~49.7 days uptime. This works due to modular arithmetic in unsigned integers. The subtraction yields the correct elapsed time regardless of rollover. This pattern should not be flagged as problematic or refactored to use signed arithmetic.

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T21:50:22.379Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/FX.cpp:11799-11813
Timestamp: 2025-10-22T21:50:22.379Z
Learning: In PR #270 (16-bit effect IDs PoC), the current addEffect behavior of appending and ignoring explicit ids >= _mode.size() is intentional for the proof-of-concept; changes to honor explicit ids are deferred to a follow-up.

Applied to files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (3)
wled00/e131.cpp (2)
usermods/artifx/arti.h (1)
  • millis (150-153)
wled00/udp.cpp (2)
  • setRealtimePixel (651-668)
  • setRealtimePixel (651-651)
wled00/FX_fcn.cpp (3)
wled00/wled.h (1)
  • size (195-203)
wled00/FX.h (1)
  • isServicing (1028-1028)
usermods/artifx/arti.h (1)
  • millis (150-153)
wled00/FX.h (1)
wled00/FX_fcn.cpp (4)
  • waitUntilIdle (1916-1932)
  • waitUntilIdle (1916-1916)
  • getActiveSegsLightCapabilities (2257-2263)
  • getActiveSegsLightCapabilities (2257-2257)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp01_1MB_S)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
🔇 Additional comments (6)
wled00/wled.h (1)

762-771: Global ESP32 draw semaphore definition looks consistent

The new busDrawMux global is correctly guarded with #ifdef ARDUINO_ARCH_ESP32 and wired through WLED_GLOBAL, so it should integrate cleanly with the ESP32‑only synchronization you’re adding elsewhere.

wled00/udp.cpp (1)

163-166: Finite idle wait before entering realtime mode is reasonable

Switching to strip.waitUntilIdle(350) gives a bounded (but still generous) wait to let service() finish before going into realtime. This plays fine with the new timeout‑based waitUntilIdle implementation.

wled00/FX_fcn.cpp (2)

1874-1888: ESP32 critical sections around LED buffers look correct and focused

The new s_wled_strip_mux critical sections:

  • In Segment::allocLeds(), wrap free(ledsrgb) and calloc of the new buffer.
  • In WS2812FX::finalizeInit(), wrap free(Segment::_globalLeds) and purgeSegments(true) while _globalLeds is being torn down.
  • In WS2812FX::fixInvalidSegments(), wrap segment bounds fixes and purgeSegments().

are:

  • Short (no delay(), FS, or network calls inside).
  • Limited to pointer/size updates that might be touched from multiple cores/tasks.

Given that suspendStripService and busDrawMux are also in play, this added hardware‑level lock around global LED buffers is a reasonable extra safeguard and doesn’t introduce obvious deadlock paths.

Also applies to: 2494-2524, 120-129


2723-2735: Reallocf is already in use elsewhere in the codebase; defensive approach is appropriate

This code pattern using reallocf with a calloc fallback is consistent with existing usage in FX_2Dfcn.cpp and is appropriate for current WLED-MM targets. Reallocf is part of newlib's standard memory management library (stdlib.h), and modern ESP8266 Arduino cores use newlib 4.0.0. Additionally, ESP8266 support is being phased out in WLED-MM, with focus on ESP32 platforms that all include newlib with reallocf support.

The defensive implementation—attempting reallocf then falling back to calloc on failure—is already established in the codebase and provides appropriate safety. No additional compatibility changes are required.

wled00/FX.h (2)

987-987: LGTM: Backward-compatible API enhancement.

The addition of the default timeout parameter to waitUntilIdle maintains backward compatibility while enabling timeout-controlled idle waits, aligning with the ESP32 synchronization improvements in FX_fcn.cpp.


1252-1257: Remove placeholder bitfields or document their necessity.

The search found no sizeof(WS2812FX) calls, EEPROM serialization, or direct layout-dependent code for the WS2812FX class itself. While the bitfield members _isOffRefreshRequired and _hasWhiteChannel are actively used, the placeholder bits ("bullshit", "bullshit2") appear unnecessary unless they preserve layout for an undocumented reason. If layout preservation is intentional (e.g., for ABI compatibility with compiled libraries), add a comment explaining why. Otherwise, remove the placeholders and commented bitfields to improve code clarity.

@softhack007
Copy link
Collaborator Author

softhack007 commented Dec 19, 2025

What are the specific test scenarios? For 0.16 this works pretty well, tested on C3 without an FPS limit and it works flawlessly.

Hi @DedeHai,

I've tested it with WLED-MM on esp32-S3, with a 192x64 hub75 panel. My last commit was necessary to prevent random crashes, but this could be MM specific.

The "Akemi" effect runs with ~40fps on my 192x64 pixel HUB75, so its unlikely that the pixel drawing performance in WLED is the limiting factor.

Overall I see low performance of the DDP-over-ws solution when going above 64x32 pixels, using one of your example videos:

  • 64 x 32 => 30fps
  • 64 x 64 => 22fps
  • 128x64 => 7 fps,, possibly with some packet loss
  • 192x64 => 3 fps, and very "jaggy" updates

I think one problem is the limited esp32 buffer capacity for incoming websocket data, this might hurt us above 64x64 pixels when >8 DDP packets are needed to transmit a single frame. But I've also seen very small DDP packets (~16 pixels) that included a DDP sync flag, especially with higher resolutions. 🤔 not sure if this comes from your tool, or it is just a consequence of network buffer congestion.

Another bug: VideoLab has a drop-down for the target segment, but I could not limit video output to a segment with this. The segment dimensions are used, but the normal DDP mode just fills all pixels sequentially.

Edit: video playback also gets very unstable (with larger sizes) when I have the webUI open, showing LEDs preview. This might be another "smoking gun" of network buffer congestion.

@DedeHai
Copy link

DedeHai commented Dec 19, 2025

I am not sure using my barely beta stage tool to benchmark WS speed is a valid test ;) I never tested it above 64x64, for a network benchmark you should (have AI) write a simple tool that just pushes a lot of pixels without much processing to take one variable out of the equation.

@softhack007
Copy link
Collaborator Author

softhack007 commented Dec 19, 2025

I am not sure using my barely beta stage tool to benchmark WS speed is a valid test ;) I never tested it above 64x64, for a network benchmark you should (have AI) write a simple tool that just pushes a lot of pixels without much processing to take one variable out of the equation.

Yes you're right, its not a good test because too many "known unknowns".

Actually after some more tinkering, i get the feeling that the botleneck comes from the web browser client. I am using Win11, and my first test was with MS Edge, over my home wifi (fast, but wifi sends non-realtime packets in batches)

I've added some extra code to ws.cpp to speed up the part of collecting the DDP data in handleWs(). I still see an unusual slowdown starting at ~64x64 pixels, but it seems to depend on the browser:

MS Edge to HUB75 64x64

21 packets processed in 12ms (1024ms realtime). 1 frames received
100 packets processed in 71ms (1017ms realtime). 11 frames received
13 packets processed in 11ms (1039ms realtime). 1 frames received
almost one minute of "backlog" after streaming ended

Chrome to HUB75 64x64

146 packets processed in 106ms (1003ms realtime). 16 frames received
225 packets processed in 168ms (1017ms realtime). 25 frames received
no "backlog" after streaming ended

Firefox to HUB75 64x64

30 packets processed in 22ms (1021ms realtime). 3 frames received
35 packets processed in 25ms (1004ms realtime). 4 frames received

small "backlog" after streaming ended, video preview in the browser was not smooth

@DedeHai could it be that the browser (JS) has some rate limiting for websocket? It looks like my esp32-S3 is not at its limits, but DDP packets are arriving very slowly which leads to strong visual stutter. Just an educated guess, because I have no real clue about javascript 😇

@softhack007
Copy link
Collaborator Author

softhack007 commented Dec 19, 2025

I am not sure using my barely beta stage tool to benchmark WS speed is a valid test

free beta testing :-) i really like your idea to stream videos to WLED via DDP, so I'm doing my best to make sure WLED-MM can support your new tool once it stabilizes 😀

@DedeHai
Copy link

DedeHai commented Dec 19, 2025

Just an educated guess, because I have no real clue about javascript

you and me both :) I know for a fact that the video streaming tool is very demanding on CPU as it is not much optimized for processing speed. I did some optimization but since i am pretty much clueless on what browser functions are fast and what better to avoid I am relying 100% on AI to be accurate about that and that may just be the core issue of the stuttering ;)

edit:
if you want to get to the bottom of it, take my sending code, feed it to claude.ai and ask for a perfomance test htm file.

unchecking "Receive UDP realtime" in Sync settings will immediately end realtime display, also over webSockets.
Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/e131.cpp (1)

19-30: Track the disabled packet filtering logic.

The late-packet rejection logic has been disabled with #if 0. While the FIXME comment acknowledges the issue, this should be tracked formally (e.g., in an issue) to ensure it's addressed before release, especially given the PR discussion about packet loss at higher resolutions.

Would you like me to open an issue to track re-enabling this logic with proper multi-packet-per-frame support?

🧹 Nitpick comments (1)
wled00/e131.cpp (1)

94-95: Consider documenting the kill switch behavior.

The early exit when receiveDirect is false provides a mechanism to disable incoming realtime data, but the conditions under which receiveDirect becomes false are not documented here. Adding a brief comment explaining when this flag is cleared would improve code clarity.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c25f082 and 0d24bb3.

📒 Files selected for processing (1)
  • wled00/e131.cpp (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/e131.cpp
🧠 Learnings (9)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-10-22T22:24:46.163Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-11-02T22:17:26.380Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-10-22T21:50:25.839Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-07-12T04:20:14.546Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/e131.cpp
🧬 Code graph analysis (1)
wled00/e131.cpp (1)
wled00/udp.cpp (4)
  • setRealtimePixel (651-668)
  • setRealtimePixel (651-651)
  • exitRealtime (207-222)
  • exitRealtime (207-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (esp01_1MB_S)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_4MB_V4_S)

Copy link

@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: 0

♻️ Duplicate comments (1)
wled00/e131.cpp (1)

34-37: Remaining race condition on debug counter reads.

While the counter increments (lines 65, 67) are now protected by busDrawMux on ESP32, the read-and-reset operations at lines 79-81 remain unprotected. Concurrent calls to handleDDPPacket from async_tcp tasks can still race when reading and resetting these counters, leading to inaccurate debug output.

Protect the read-and-reset sequence at lines 79-81 with the same busDrawMux semaphore, or convert these counters to std::atomic<unsigned> and use .load() / .store() for all accesses.

Based on PR objectives describing concurrency concerns with async_tcp tasks, and building upon the past review comment about thread-safety.

🧹 Nitpick comments (1)
wled00/e131.cpp (1)

58-71: Semaphore usage now correct; consider timeout constant and debug logging.

The critical issue from the previous review has been resolved—xSemaphoreGive is now only called when xSemaphoreTake succeeds. However, consider two refinements:

  1. Use pdMS_TO_TICKS(200) instead of the raw value 200 to clarify that the timeout is 200 milliseconds.
  2. When lock acquisition fails, the packet is silently dropped. Consider adding a WLED_DEBUG message to aid troubleshooting under high contention.
🔎 Optional refinements
   #if defined(ARDUINO_ARCH_ESP32)
   // WLEDMM acquire drawing permission (wait max 200ms) before setting pixels
-  if (xSemaphoreTake(busDrawMux, 200) == pdTRUE) { 
+  if (xSemaphoreTake(busDrawMux, pdMS_TO_TICKS(200)) == pdTRUE) { 
   #endif
     for (uint16_t i = start; i < stop; i++) {
       setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0);
       c += ddpChannelsPerLed;
       pixels++;
     }
     packets ++;
   #if defined(ARDUINO_ARCH_ESP32)
     xSemaphoreGive(busDrawMux);                                  // WLEDMM release drawing permissions
+  } else {
+    #ifdef WLED_DEBUG
+    USER_PRINTLN(F("DDP: Failed to acquire busDrawMux, packet dropped"));
+    #endif
   }
   #endif

Based on the past review that identified the semaphore misuse.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d24bb3 and e5a3942.

📒 Files selected for processing (1)
  • wled00/e131.cpp (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/e131.cpp
🧠 Learnings (9)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-11-02T22:17:26.380Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-10-22T22:24:46.163Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-10-22T21:50:25.839Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/html_*.h : Do not edit generated embedded web header files: wled00/html_*.h

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/e131.cpp
🧬 Code graph analysis (1)
wled00/e131.cpp (1)
wled00/udp.cpp (4)
  • setRealtimePixel (651-668)
  • setRealtimePixel (651-651)
  • exitRealtime (207-222)
  • exitRealtime (207-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp01_1MB_S)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32dev_compat)
🔇 Additional comments (2)
wled00/e131.cpp (2)

19-30: Out-of-sequence filtering intentionally disabled.

The disabled code block correctly reflects the need to handle many packets per frame in the DDP-over-WebSockets scenario described in the PR objectives, where >8 packets per frame are common for larger displays.


96-97: The receiveDirect implementation is correct and properly initialized.

The variable is declared as a global bool with default value true, loaded from persistent storage (EEPROM) on startup, and controlled through UI toggles and API endpoints. The kill-switch logic at lines 96-97 follows the established pattern used elsewhere in the codebase (e.g., udp.cpp:285, 288, 528) and maintains the intended semantics for enabling/disabling UDP realtime reception.

revised mutex and critical section handling for segment drawing
- simplified code with macros (no more #if ESP32 ... #endif)
- remove some critical sections (prevents interrupt stalling)
- added mutex to functions that change the list of segments
- added mutexes to all (potential) background drawing code
- use recursive mutexes to prevent accidental self-locking of tasks
Copy link

@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: 0

♻️ Duplicate comments (1)
wled00/e131.cpp (1)

34-38: DDP debug counters are still not fully synchronized across tasks.

packets/pixels are incremented while holding busDrawMux, but the debug print and reset happen outside that critical section, so concurrent handleDDPPacket calls can still race on these statics (especially with async_tcp tasks on multiple cores). If you care about accurate metrics, you’d need to either read/reset them under the same mutex, or move them behind a separate debug lock; otherwise they should be treated as approximate only.

Also applies to: 70-78

🧹 Nitpick comments (4)
wled00/json.cpp (1)

345-414: Image-array drawing critical section looks correct; consider handling lock timeouts explicitly.

The new busDrawMux guard plus seg.calc_virtualLength() bounds checks make the JSON "i" image path much safer against concurrent drawing and OOB writes. The only thing I’d suggest is optionally logging or counting failures when esp32SemTake(busDrawMux, 250) times out, instead of silently skipping the update, to make diagnosing contention-induced frame drops easier later.

wled00/e131.cpp (1)

57-67: busDrawMux protection for DDP/DMX looks sound; note that lock timeouts silently drop frames.

Wrapping DDP and DMX pixel writes in esp32SemTake(busDrawMux, 200)…esp32SemGive(busDrawMux) is consistent with the new drawing mutex used in service()/show() and should eliminate concurrent access to the LED buses from realtime and effect paths. If the Take times out, the code correctly skips the write rather than calling Give on an unacquired semaphore.

Two behavioral notes to be aware of:

  • When contention is high, those 200‑tick timeouts will silently drop DDP/DMX frames; if that becomes an issue, you might want to add a debug counter/log for lock failures.
  • The new if (!receiveDirect) { exitRealtime(); return; } kill‑switch at the top of handleE131Packet now disables all E1.31/ArtNet/DDP updates when receiveDirect is false, which seems consistent with “disable UDP realtime”, but it’s worth double‑checking that this matches the existing UI/config expectations.

Also applies to: 92-124, 185-218, 361-374

wled00/FX_fcn.cpp (2)

1875-1884: Global LED buffer and service()/show() now share a coherent locking scheme; be aware of silent skips on mutex timeouts.

  • The _globalLeds swap in finalizeInit() is now protected by a short ESP32 critical section (portENTER_CRITICAL on s_wled_strip_mux) while the pointer is nulled, then free(oldGLeds) happens outside. With suspendStripService set earlier in finalizeInit, this avoids concurrent use of the freed buffer from the main effect loop.
  • waitUntilIdle(unsigned timeout) on ESP32+WLEDMM_PROTECT_SERVICE now busy‑waits up to at least MAX_IDLE_WAIT_MS (120ms) while isServicing(), which bounds the wait and prevents indefinite stalls while still giving the effect loop time to finish a frame.
  • In service(), effect execution is guarded by esp32SemTake(busDrawMux, 200)/esp32SemGive(busDrawMux), and show() wraps busses.show() with the same mutex and timeout. This aligns realtime (DDP/DMX), JSON, and effect drawing on a single bus‑level lock, preventing overlapping bus writes.

The one behavioral caveat is that if esp32SemTake times out in service() or show(), you silently skip that frame (with a small delay(1) in service()). That’s probably the right trade‑off for robustness, but if debugging dropped frames becomes important later, you may want to add a lightweight counter or debug log for lock failures.

Also applies to: 1907-1927, 1930-2008, 2139-2152


2718-2737: Check that reallocf is available on all supported toolchains in deserializeMap().

The new deserializeMap() uses reallocf(customMappingTable, sizeof(uint16_t) * size) to resize the mapping table, with a fallback to calloc if that fails—which is a nice pattern (and you correctly reset customMappingTableSize and errorFlag depending on outcomes). However, reallocf is not part of standard C and isn’t guaranteed to exist in all libc/newlib variants.

Please double‑check that both your ESP32 and ESP8266 toolchains ship reallocf; if not, you may need to replace it with plain realloc plus an explicit error‑handling path.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5a3942 and 7df63db.

📒 Files selected for processing (5)
  • wled00/FX_fcn.cpp (16 hunks)
  • wled00/e131.cpp (7 hunks)
  • wled00/json.cpp (2 hunks)
  • wled00/wled.cpp (2 hunks)
  • wled00/wled.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/wled.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/e131.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
  • wled00/json.cpp
🧠 Learnings (15)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/e131.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-02T22:17:26.380Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Applied to files:

  • wled00/e131.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T21:50:25.839Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Applied to files:

  • wled00/e131.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T22:24:46.163Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Applied to files:

  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/e131.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/e131.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-07-12T04:20:14.546Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Applied to files:

  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-14T12:42:22.059Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 240
File: wled00/image_loader.cpp:0-0
Timestamp: 2025-11-14T12:42:22.059Z
Learning: In MoonModules/WLED-MM and embedded systems code, the pattern `unsigned timePassed = millis() - last_time;` is rollover-safe and proven to work correctly even when millis() wraps around at ~49.7 days uptime. This works due to modular arithmetic in unsigned integers. The subtraction yields the correct elapsed time regardless of rollover. This pattern should not be flagged as problematic or refactored to use signed arithmetic.

Applied to files:

  • wled00/e131.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/e131.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/html_*.h : Do not edit generated embedded web header files: wled00/html_*.h

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-06-30T17:53:37.232Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 248
File: usermods/audioreactive/audio_reactive.h:199-200
Timestamp: 2025-06-30T17:53:37.232Z
Learning: In WLED AudioReactive usermod, when using ArduinoFFT (enabled via UM_AUDIOREACTIVE_USE_ARDUINO_FFT), it's acceptable and necessary to redefine the global sqrt macro to sqrtf within the conditional compilation block for performance optimization. This is a specific requirement for ArduinoFFT optimization that can provide 10-50% performance improvement on ESP32.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-11-24T00:15:38.069Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-24T00:15:38.069Z
Learning: In MoonModules/WLED-MM, when reading ESP32/ESP32-S3 eFuse blocks using esp_efuse_read_field_blob(): EFUSE_BLK0 has only 7 × 32-bit registers (224 bits total), not 8 registers (256 bits). Reading more than 7 words from EFUSE_BLK0 triggers assertion failure in esp_efuse_utility_read_reg(). Use maximum 7 words when reading EFUSE_BLK0 to avoid crashes. This applies to all ESP32 variants (ESP32, ESP32-S2, ESP32-S3, ESP32-C3).

Applied to files:

  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-05-04T10:32:30.457Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T21:50:22.379Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/FX.cpp:11799-11813
Timestamp: 2025-10-22T21:50:22.379Z
Learning: In PR #270 (16-bit effect IDs PoC), the current addEffect behavior of appending and ignoring explicit ids >= _mode.size() is intentional for the proof-of-concept; changes to honor explicit ids are deferred to a follow-up.

Applied to files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (1)
wled00/e131.cpp (1)
wled00/udp.cpp (4)
  • setRealtimePixel (651-668)
  • setRealtimePixel (651-651)
  • exitRealtime (207-222)
  • exitRealtime (207-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp8266_4MB_S)
  • GitHub Check: Builds (esp8266_2MB_S)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (athom_music_esp32_4MB_M)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
🔇 Additional comments (4)
wled00/e131.cpp (1)

18-30: DDP late‑packet filter is now compiled out.

The #if 0 around the late‑packet rejection logic means e131SkipOutOfSequence no longer has any effect for DDP traffic. That matches the comment about having far more than 5–10 packets per frame, but it’s worth keeping in mind that, as‑is, DDP will accept all in‑sequence packets regardless of frame boundary heuristics. If you intend to reintroduce filtering later, consider adding a TODO or comment pointing to the desired new heuristic.

wled00/wled.h (1)

769-785: busDrawMux global and esp32SemTake/esp32SemGive macros are well‑structured; ensure early initialization on ESP32.

The SemaphoreHandle_t busDrawMux plus esp32SemTake/esp32SemGive wrappers give a clean, cross‑platform way to guard drawing, and the dummy macros keep ESP8266 builds simple. Just make sure busDrawMux is created (e.g. via xSemaphoreCreateRecursiveMutex() or similar) very early in setup, before any code paths that call esp32SemTake(busDrawMux, …) can run, so that all the new guards in service(), show(), JSON/DDP/DMX, etc. are working as intended.

wled00/FX_fcn.cpp (2)

107-133: allocLeds refactor looks safe; OOM behavior is acceptable but note loss of old buffer.

The new allocLeds() logic (freeing oldLedsRgb then calloc’ing a larger buffer and updating ledsrgbSize) is simpler and avoids doing heap allocations inside a critical section, which is good. On allocation failure you now end up with ledsrgb == nullptr, ledsrgbSize == 0, and ERR_LOW_BUF set—which is a reasonable fail‑safe even though any previously allocated (too‑small) buffer is discarded rather than kept as a fallback. That trade‑off is fine given the alternative would be writing past the old buffer.


2346-2363: Using busDrawMux around segment structural changes is a good safeguard.

purgeSegments(), resetSegments(false), makeAutoSegments(true), and fixInvalidSegments() now all take busDrawMux (with either a finite timeout or portMAX_DELAY) while mutating the _segments vector and segment bounds. That’s consistent with the rest of the drawing synchronization and should prevent segment reconfiguration from racing with effect rendering or realtime writers.

fixInvalidSegments() correctly releases the lock before calling purgeSegments(), so there’s no lock inversion, and re‑acquisition relies on the same mutex. Given the use of a recursive semaphore, any nested calls from the same task would still be safe.

Also applies to: 2387-2415, 2418-2472, 2496-2523

@softhack007 softhack007 changed the title Pixelforge backport Pixelforge backport, and UI stability improvements Dec 20, 2025
@DedeHai
Copy link

DedeHai commented Dec 20, 2025

regarding the rabbits issues with the pixelmagic tool: I tested the one from pixelforge i.e. the gz file from my repo (which I either pulled from upstream or original source, can't remember) and there were no console errors except when I tried to use it on a 1D strip which I think it does not support. You'd need to test if the version from the "other tools" link works with MM.

edit: I was thinking of removing the pixelmagic tool entirely from the WLED build as it is much more convenient for users to just hit that download button but kept it for backwards build compatibility.
On another note: I did not think of any concept for versioning of the external htm files. They are currently in my repo and hitting that download link will always pull the same file which I guess may become an issue at some point as it needs to stay backwards compatible if say new feature are added in 0.17 which a 0.16 would not support. Adding a version number to the htm file is one option should such a need arrive.

* avoid implicit creation of global vars
* removed stray </small>
* fix assignment with undefined variables
@coderabbitai
Copy link

coderabbitai bot commented Dec 25, 2025

Tip

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

@softhack007 Short answer: not reliably on-device; partially yes on a host.

  • ThreadSanitizer (TSan): No. It detects data races and some synchronization bugs, not resource/FD leaks or “forgot to close” patterns.

  • Address/LeakSanitizer (ASan/LSan): No. They cover memory errors/leaks, not file descriptor or VFS handle leaks.

  • Valgrind:

    • Memcheck by default: No.
    • Memcheck with --track-fds=yes: Yes, it reports open file descriptors at process exit, so a “double open without close” shows up as an FD leak. Caveats: runs on Linux/macOS, not on ESP32; you need a host build and a test that exits.
    • Helgrind/DRD: concurrency issues only, not FD leaks.
  • Dr. Memory (Windows): Can report handle/FD leaks in host builds, but not applicable on ESP32.

Practical ways to catch/prevent this in WLED-MM:

  • Keep the pre-close guard we discussed: in writeObjectToFile() call closeFile() when doCloseFile is true before any new open.
  • Serialize all presets.json writes with presetFileMux (already planned) to avoid concurrent writers.
  • Add a debug-only assertion/instrumentation: log/ASSERT if we enter writeObjectToFile() with doCloseFile true (helps find new offenders fast).
  • Host-side test: build a minimal file.cpp harness for Linux, run under Valgrind with --track-fds=yes, and exercise back-to-back writes (one with deferred close) to verify no leaked FDs and no “double open”.
  • Optional static analysis: clang static analyzer/cppcheck can flag fopen/fclose mismatches on POSIX FILE*, but won’t recognize Arduino fs::File unless you wrap it behind a tiny RAII shim for host builds.

Bottom line: TSan/ASan won’t catch this; Valgrind can in a host test with --track-fds. On ESP32, the safest path is the code fixes you’re applying plus lightweight debug assertions/instrumentation.


🧠 Learnings used
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.915Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.126Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.500Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.737Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Repository owner deleted a comment from coderabbitai bot Dec 25, 2025
@DedeHai
Copy link

DedeHai commented Dec 27, 2025

@softhack007 the VideoLab tool has gotten a big speed bump by scaling down the video input before processing and I added WS queue overflow protection as per your request. Please check it out (available in my repo)

Copy link

@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: 0

🧹 Nitpick comments (1)
wled00/wled.h (1)

768-791: Consider guarding portENTER_CRITICAL/portEXIT_CRITICAL redefinitions for ESP8266 fallback.

The recursive mutex design is correct—heap operations can safely be performed while holding esp32SemTake/esp32SemGive, unlike spinlock critical sections. However, the unconditional redefinition of portENTER_CRITICAL and portEXIT_CRITICAL as no-ops on ESP8266 (lines 789–790) lacks guard conditions. While no current conflicts exist in the codebase, adding #ifndef guards is defensive:

+#ifndef portENTER_CRITICAL
 #define portENTER_CRITICAL(sname)
+#endif
+#ifndef portEXIT_CRITICAL
 #define portEXIT_CRITICAL(sname)
+#endif

This prevents accidental redefinition conflicts if future ESP8266 SDK updates introduce these macros.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb55b4f and 35c8474.

📒 Files selected for processing (4)
  • wled00/FX_fcn.cpp
  • wled00/udp.cpp
  • wled00/util.cpp
  • wled00/wled.h
✅ Files skipped from review due to trivial changes (1)
  • wled00/udp.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
🧠 Learnings (21)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.915Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.126Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.500Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.
📚 Learning: 2025-12-23T21:29:14.915Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.915Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Applied to files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-12-22T21:14:08.797Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Applied to files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-12-23T21:36:41.126Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.126Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Applied to files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-12-25T00:46:23.500Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.500Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Applied to files:

  • wled00/util.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/util.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-12-22T20:31:17.991Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Applied to files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-07-12T04:20:14.546Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Applied to files:

  • wled00/util.cpp
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-24T00:15:38.069Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-24T00:15:38.069Z
Learning: In MoonModules/WLED-MM, when reading ESP32/ESP32-S3 eFuse blocks using esp_efuse_read_field_blob(): EFUSE_BLK0 has only 7 × 32-bit registers (224 bits total), not 8 registers (256 bits). Reading more than 7 words from EFUSE_BLK0 triggers assertion failure in esp_efuse_utility_read_reg(). Use maximum 7 words when reading EFUSE_BLK0 to avoid crashes. This applies to all ESP32 variants (ESP32, ESP32-S2, ESP32-S3, ESP32-C3).

Applied to files:

  • wled00/util.cpp
  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/html_*.h : Do not edit generated embedded web header files: wled00/html_*.h

Applied to files:

  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-12-21T22:26:03.261Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Applied to files:

  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/**/*.{cpp,h} : Use spaces (2 per level) for C++ firmware source and headers in wled00

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-10-22T21:50:25.839Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Applied to files:

  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-06-30T17:53:37.232Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 248
File: usermods/audioreactive/audio_reactive.h:199-200
Timestamp: 2025-06-30T17:53:37.232Z
Learning: In WLED AudioReactive usermod, when using ArduinoFFT (enabled via UM_AUDIOREACTIVE_USE_ARDUINO_FFT), it's acceptable and necessary to redefine the global sqrt macro to sqrtf within the conditional compilation block for performance optimization. This is a specific requirement for ArduinoFFT optimization that can provide 10-50% performance improvement on ESP32.

Applied to files:

  • wled00/wled.h
  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-02T22:17:26.380Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-10-22T22:24:46.163Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-11-14T12:42:22.059Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 240
File: wled00/image_loader.cpp:0-0
Timestamp: 2025-11-14T12:42:22.059Z
Learning: In MoonModules/WLED-MM and embedded systems code, the pattern `unsigned timePassed = millis() - last_time;` is rollover-safe and proven to work correctly even when millis() wraps around at ~49.7 days uptime. This works due to modular arithmetic in unsigned integers. The subtraction yields the correct elapsed time regardless of rollover. This pattern should not be flagged as problematic or refactored to use signed arithmetic.

Applied to files:

  • wled00/FX_fcn.cpp
📚 Learning: 2025-05-04T10:32:30.457Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Applied to files:

  • wled00/FX_fcn.cpp
🧬 Code graph analysis (2)
wled00/util.cpp (1)
usermods/artifx/arti.h (1)
  • millis (150-153)
wled00/FX_fcn.cpp (1)
wled00/FX.h (2)
  • is2D (619-619)
  • calc_virtualWidth (747-758)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp01_1MB_S)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (athom_music_esp32_4MB_M)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32dev_compat)
🔇 Additional comments (13)
wled00/util.cpp (2)

225-257: Recursive mutex pattern looks correct for ESP32.

The JSON buffer locking implementation properly uses a recursive mutex with a 1800ms timeout that exceeds the suspendStripService timeout (1500ms), preventing deadlocks. The ESP8266 fallback with can_yield() busy-wait is appropriate for the single-core platform.

One minor observation: Lines 240-242 handle the edge case where the mutex was acquired but jsonBufferLock is still set (indicating another context holds it). Releasing the mutex before returning is correct.


261-271: Release order is correct.

Setting jsonBufferLock = 0 before calling esp32SemGive(jsonBufferLockMutex) is the correct order. Any waiting task cannot proceed until it acquires the mutex, so the flag will be visible as cleared when the waiting task resumes.

wled00/FX_fcn.cpp (10)

119-133: Atomic pointer swap pattern is correct.

The allocLeds() reallocation follows the proper pattern per learnings:

  1. Capture old pointer outside critical section
  2. Set ledsrgb = nullptr inside spinlock (atomic clear)
  3. Free old buffer outside critical section (heap ops not in critical sections ✓)
  4. Allocate new buffer outside critical section (heap ops not in critical sections ✓)
  5. Set ledsrgb = newLedsRgb inside spinlock (atomic assignment)

This addresses the prior review comment about race conditions. The brief nullptr window is acceptable since concurrent readers will see either the old valid pointer, nullptr, or the new valid pointer.


574-596: Graceful failure handling in setUp().

The segmentMux acquisition with 2100ms timeout and the fallback behavior (setting boundsUnchanged = true and logging) is appropriate. This addresses the prior review concern about infinite waits. The mutex is correctly released before calling markForReset() and refreshLightCapabilities() which may trigger further operations.


1467-1469: Thread-safe dimension calculation in fill().

Using calc_virtualWidth() / calc_virtualHeight() instead of the cached versions ensures correct dimensions when fill() is called from json.cpp outside the normal segment frame context. This prevents stale cached values from causing incorrect fill operations.


1892-1901: Correct atomic swap pattern for _globalLeds.

The _globalLeds cleanup follows the same safe pattern as allocLeds(): capture pointer, atomically clear under spinlock, then free outside the critical section. Calling purgeSegments(true) after ensures segments don't hold stale references.


1929-1944: Flexible timeout for waitUntilIdle().

The new unsigned timeout parameter allows callers to specify custom wait times while ensuring a minimum of MAX_IDLE_WAIT_MS (120ms). This is more flexible than the previous fixed wait and aligns with the configurable timeout pattern used elsewhere.


2004-2019: Per-segment mutex acquisition in service() loop.

The busDrawMux is acquired and released for each segment's effect execution (lines 2006, 2019). While this adds lock/unlock overhead per segment, it allows other high-priority tasks (DDP, WebSockets) to interleave during long effect chains, which aligns with the PR's goal of improving stability with concurrent access.

If mutex acquisition fails (line 2006), the delay(1); continue; pattern silently skips the segment for this frame. This matches the author's stated preference for silent frame drops over log flooding. Consider adding a rate-limited debug counter if visibility into contention is ever needed.


2155-2159: Bus show protected by mutex.

The busDrawMux protection around busses.show() prevents race conditions with concurrent effect drawing. The silent early return on mutex timeout aligns with the author's stated design preference to avoid log flooding under high contention.


2354-2370: Segment vector modifications properly protected.

Both purgeSegments() and resetSegments() acquire segmentMux before modifying the _segments vector, preventing iterator invalidation if service() is iterating concurrently. The 300ms timeout in purgeSegments() is appropriate for a quick operation; the 2100ms in resetSegments() allows for longer initialization scenarios.


2507-2536: Correct mutex release before nested call.

Line 2530 correctly releases segmentMux before calling purgeSegments(), which also acquires the same mutex. Since these are recursive mutexes, nested acquisition would technically work, but releasing first is cleaner and avoids holding the lock longer than necessary.


2429-2482: makeAutoSegments() protected during segment creation.

The segmentMux protection (lines 2431-2482) ensures the _segments vector isn't modified by other tasks during the auto-segmentation logic. The mutex is released before fixInvalidSegments() is called (line 2504 is outside the protected block), allowing that function to acquire its own lock independently.

wled00/wled.h (1)

771-773: No action needed. The semaphore handles (busDrawMux, segmentMux, jsonBufferLockMutex) are properly created with xSemaphoreCreateRecursiveMutex() in wled.cpp lines 480-482 during setup, before any code attempts to use them. Error handling is in place (line 483), and all usage sites check the return value of esp32SemTake() before proceeding.

…nter

* this prevents "open after open", that could lead to file corruption when "f" was still holding some changes in its buffer (written out with f.close())
prevents concurrent presets.json writes from doSaveState() and savePreset()
Copy link

@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: 0

🧹 Nitpick comments (1)
wled00/presets.cpp (1)

328-334: Minor: Fix copy-paste error in comment.

The comment at line 332 incorrectly references "doSaveState()" but this code is in the savePreset() function. Otherwise, the mutex acquisition is correct and properly guards the immediate write path.

🔎 Proposed fix
-        USER_PRINTLN(F("doSaveState(): preset file busy, cannot write"));
+        USER_PRINTLN(F("savePreset(): preset file busy, cannot write"));
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35c8474 and 2a56f9b.

📒 Files selected for processing (4)
  • wled00/file.cpp
  • wled00/presets.cpp
  • wled00/wled.cpp
  • wled00/wled.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/wled.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/presets.cpp
  • wled00/file.cpp
  • wled00/wled.h
🧠 Learnings (15)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : After editing web UI files, run npm run build to regenerate embedded headers
📚 Learning: 2025-12-23T21:29:14.927Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Applied to files:

  • wled00/presets.cpp
  • wled00/file.cpp
  • wled00/wled.h
📚 Learning: 2025-12-23T21:36:41.138Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Applied to files:

  • wled00/presets.cpp
  • wled00/file.cpp
  • wled00/wled.h
📚 Learning: 2025-12-25T00:46:23.516Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Applied to files:

  • wled00/presets.cpp
  • wled00/file.cpp
📚 Learning: 2025-12-25T00:45:39.751Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Applied to files:

  • wled00/presets.cpp
  • wled00/file.cpp
📚 Learning: 2025-12-21T22:26:03.261Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-12-22T21:14:08.797Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-10-12T21:55:59.766Z
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-12T21:55:59.766Z
Learning: Applies to wled00/html_*.h : Do not edit generated embedded web header files: wled00/html_*.h

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-07-02T23:22:57.175Z
Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-10-22T21:50:25.839Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-12-22T20:31:17.991Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/wled.h
📚 Learning: 2025-06-30T17:53:37.232Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 248
File: usermods/audioreactive/audio_reactive.h:199-200
Timestamp: 2025-06-30T17:53:37.232Z
Learning: In WLED AudioReactive usermod, when using ArduinoFFT (enabled via UM_AUDIOREACTIVE_USE_ARDUINO_FFT), it's acceptable and necessary to redefine the global sqrt macro to sqrtf within the conditional compilation block for performance optimization. This is a specific requirement for ArduinoFFT optimization that can provide 10-50% performance improvement on ESP32.

Applied to files:

  • wled00/wled.h
🧬 Code graph analysis (1)
wled00/presets.cpp (2)
wled00/util.cpp (4)
  • requestJSONBufferLock (223-258)
  • requestJSONBufferLock (223-223)
  • releaseJSONBufferLock (261-271)
  • releaseJSONBufferLock (261-261)
wled00/file.cpp (2)
  • writeObjectToFileUsingId (286-291)
  • writeObjectToFileUsingId (286-286)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (seeed_esp32c3_4MB_S)
  • GitHub Check: Builds (athom_music_esp32_4MB_M)
  • GitHub Check: Builds (esp8266_4MB_S)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp8266_2MB_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
🔇 Additional comments (5)
wled00/file.cpp (1)

302-305: LGTM! Critical file-handle safety guard implemented correctly.

The pre-close check prevents file corruption when async and immediate write paths race by ensuring any previously deferred file close completes before opening a new file handle. This mirrors the existing pattern in readObjectFromFile() and addresses the corruption issue documented in learnings.

Based on learnings, this guard is critical for preventing presets.json corruption when multiple write paths attempt concurrent access to the global file handle.

wled00/presets.cpp (2)

41-48: LGTM! Async write path correctly serialized with file mutex.

The doSaveState function properly acquires presetFileMux before writing and releases it afterward. The 2500ms timeout accommodates typical 200KB file writes with safety margin. Cleanup on mutex acquisition failure correctly releases the JSON buffer lock before returning.

Based on learnings, this mutex guard prevents corruption when the async write path races with the immediate write path in savePreset().

Also applies to: 92-93


348-348: LGTM! Immediate write path properly releases file mutex.

The mutex is correctly released after the synchronous file write completes, ensuring other write operations can proceed.

wled00/wled.h (2)

10-10: LGTM! Standard version increment.

Version bumped from 2512291 to 2512292, following the yymmddb format.


769-792: LGTM! Mutex primitives correctly implemented for ESP32 with appropriate fallbacks.

The mutex declarations and convenience macros are well-designed:

  • Recursive mutexes: Correctly using xSemaphoreTakeRecursive/xSemaphoreGiveRecursive which, per learnings, are appropriate for protecting heap operations (unlike critical sections which must not block).
  • Timeout handling: The esp32SemTake macro correctly converts milliseconds to ticks using pdMS_TO_TICKS.
  • Spinlock macro: Creates static portMUX_TYPE for very short, non-blocking critical sections.
  • ESP8266 fallbacks: Dummy implementations allow code to compile on single-threaded 8266 without actual synchronization overhead.

The four mutexes serve distinct purposes: busDrawMux (concurrent draw protection), segmentMux (segment operations), jsonBufferLockMutex (JSON buffer access), and presetFileMux (file write serialization per learnings).

@softhack007
Copy link
Collaborator Author

@coderabbitai please verify if the root causes identified for presets.json corruption - #293 (comment) and #293 (comment) - are solved now.

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

🧩 Analysis chain

@softhack007 Here’s the verification status for the two root causes:

  1. Race between immediate save and async doSaveState
  • Verified: presetFileMux exists, is created in setup, and all presets.json write paths are wrapped with esp32SemTake/Give(presetFileMux).
  • Verified: doSaveState() acquires presetFileMux before file I/O and releases it after; the immediate API save path is also guarded. This serializes concurrent writes and removes the original race.
  1. Global file handle reuse (pre-close-before-open)
  • Verified: writeObjectToFile() now checks if (doCloseFile) closeFile() before opening the global File f. That prevents overwriting an open handle with unflushed buffers, addressing the second root cause.

Additional safety checks

  • Verified: If taking presetFileMux fails, code releases the JSON buffer lock before returning, avoiding lock leaks.
  • Note: Atomic replace (tmp write + rename) is not required to fix the two root causes. If not yet present everywhere, it remains a recommended hardening step for future (guards against power-loss mid-write), but it’s orthogonal to the two issues you asked about.

Conclusion

  • Both identified root causes are fixed in the current PR state. If you’d like, I can also add a quick follow-up patch to standardize atomic write (temp + rename) for presets.json for extra resilience.

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for presetFileMux declaration and initialization ==="
rg -n --no-heading "presetFileMux" wled00/wled.h wled00/wled.cpp || true

echo -e "\n=== Confirm esp32SemTake/Give wrappers exist ==="
rg -n --no-heading "esp32SemTake|esp32SemGive|WLED_create_spinlock" wled00/wled.h || true

echo -e "\n=== Inspect presets.cpp: doSaveState and savePreset paths ==="
rg -n --no-heading "^void doSaveState\\(|^void savePreset\\(|writeObjectToFileUsingId\\(|writeObjectToFile\\(" wled00/presets.cpp || true

echo -e "\n--- doSaveState() body (first ~140 lines after signature) ---"
start=$(rg -n "^void doSaveState\\(" wled00/presets.cpp | cut -d: -f1 | head -1 || echo 0)
if [ "$start" -gt 0 ]; then
  end=$((start+160))
  sed -n "${start},${end}p" wled00/presets.cpp
fi

echo -e "\n--- savePreset() body (show around immediate write path) ---"
start2=$(rg -n "^void savePreset\\(" wled00/presets.cpp | cut -d: -f1 | head -1 || echo 0)
if [ "$start2" -gt 0 ]; then
  end2=$((start2+260))
  sed -n "${start2},${end2}p" wled00/presets.cpp
fi

echo -e "\n=== Check that preset file mutex guards are present around all writes ==="
rg -n -C3 --no-heading "esp32SemTake\\(presetFileMux|esp32SemGive\\(presetFileMux|writeObjectToFileUsingId\\(|writeObjectToFile\\(" wled00/presets.cpp || true

echo -e "\n=== Inspect file.cpp: doCloseFile handling and writeObjectToFile() ==="
rg -n --no-heading "doCloseFile|closeFile\\(|^bool writeObjectToFile\\(|^bool writeObjectToFileUsingId\\(" wled00/file.cpp || true

echo -e "\n--- writeObjectToFile() prologue ---"
start3=$(rg -n "^bool writeObjectToFile\\(" wled00/file.cpp | cut -d: -f1 | head -1 || echo 0)
if [ "$start3" -gt 0 ]; then
  end3=$((start3+60))
  sed -n "${start3},${end3}p" wled00/file.cpp
fi

echo -e "\n=== Check for atomic write (tmp + rename) or tmp.json usage ==="
rg -n --no-heading "rename\\(|tmp\\.json|\\.tmp" wled00/ || true

echo -e "\n=== Ensure JSON buffer lock is released on presetFileMux acquisition failure ==="
rg -n -C3 --no-heading "esp32SemTake\\(presetFileMux.*\\) \\{[\\s\\S]*releaseJSONBufferLock\\(\\)" wled00/presets.cpp || true

Length of output: 9465


@softhack007 Thanks for the script output. Verification results:

What’s fixed now

  • Root cause 1 (concurrent writes: immediate API save vs async doSaveState) is addressed:
    • doSaveState() acquires presetFileMux before any write and releases it afterward. On failure to take the mutex, it releases the JSON buffer lock and exits. Evidence: wled00/presets.cpp Lines [41]–[47], [85]–[93].
    • Immediate API save path also acquires presetFileMux before writing and releases it afterward. Evidence: wled00/presets.cpp Lines [330]–[349].
    • The mutex is declared/created once and initialized in setup. Evidence: wled00/wled.h Line [774]; wled00/wled.cpp Lines [483], [490] (NULL checks at Line [484]).
  • Root cause 2 (global File f reopen without pre-closing) is addressed:
    • writeObjectToFile() now checks doCloseFile and calls closeFile() before any open. Evidence: wled00/file.cpp Lines [302]–[307].

One remaining gap to close

  • deletePreset() writes to presets.json without taking presetFileMux, so it can still race with an in‑flight async or immediate save:
    • Evidence: wled00/presets.cpp Lines [360]–[365].
    • Minimal fix suggestion:
// wled00/presets.cpp
void deletePreset(byte index) {
  #ifdef ARDUINO_ARCH_ESP32
  if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
    USER_PRINTLN(F("deletePreset(): preset file busy, cannot write"));
    return;
  }
  #endif

  StaticJsonDocument<24> empty;
  writeObjectToFileUsingId(getFileName(), index, &empty);

  #ifdef ARDUINO_ARCH_ESP32
  esp32SemGive(presetFileMux);
  #endif

  presetsModifiedTime = toki.second();
  updateFSInfo();
}

Conclusion

  • The two root causes you cited are fixed in current code.
  • To fully eliminate remaining races on presets.json, please add the same presetFileMux guard to deletePreset() as shown above. After that, all known write paths will be serialized.

If you want, I can scan for any other presets.json writers to apply the same guard, but based on the current grep, the above are the active paths.


🧠 Learnings used
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
wled00/presets.cpp (2)

360-365: Critical: Missing mutex protection in deletePreset().

This function writes to presets.json without acquiring presetFileMux, allowing concurrent writes that can corrupt the file. All write paths to presets.json must be serialized using the file mutex.

🔎 Proposed fix
 void deletePreset(byte index) {
+  // WLEDMM Acquire file mutex before writing presets.json
+  if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
+    USER_PRINTLN(F("deletePreset(): preset file busy, cannot delete"));
+    return;
+  }
+
   StaticJsonDocument<24> empty;
   writeObjectToFileUsingId(getFileName(), index, &empty);
+
+  esp32SemGive(presetFileMux);  // Release file mutex
+
   presetsModifiedTime = toki.second(); //unix time
   updateFSInfo();
 }

Based on learnings, all presets.json write paths must be protected by presetFileMux to prevent corruption.


121-135: Add mutex protection to initPresetsFile() for consistency.

While the risk is lower since this only runs when the file doesn't exist, concurrent calls could still race if multiple paths detect the missing file simultaneously. For consistency and safety, all presets.json writes should be protected by presetFileMux.

🔎 Proposed fix
 void initPresetsFile()
 {
   if (WLED_FS.exists(getFileName())) return;
 
+  // WLEDMM Acquire file mutex before creating presets.json
+  if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
+    USER_PRINTLN(F("initPresetsFile(): preset file busy, cannot initialize"));
+    errorFlag = ERR_FS_GENERAL;
+    return;
+  }
+
+  // Double-check after acquiring mutex (another thread may have created it)
+  if (WLED_FS.exists(getFileName())) {
+    esp32SemGive(presetFileMux);
+    return;
+  }
+
   StaticJsonDocument<64> doc;
   JsonObject sObj = doc.to<JsonObject>();
   sObj.createNestedObject("0");
   File f = WLED_FS.open(getFileName(), "w");
   if (!f) {
+    esp32SemGive(presetFileMux);
     errorFlag = ERR_FS_GENERAL;
     return;
   }
   serializeJson(doc, f);
   f.close();
+
+  esp32SemGive(presetFileMux);
 }

Based on learnings, all presets.json write paths should be protected by presetFileMux.

🧹 Nitpick comments (1)
wled00/presets.cpp (1)

328-348: Mutex correctly protects immediate API save path.

The mutex acquisition successfully prevents the race condition between this immediate write path and the async doSaveState() path that was causing presets.json corruption. The 2500ms timeout is appropriate.

However, consider these improvements:

  1. Incomplete error recovery: Line 333 returns without clearing presetToSave, leaving the save request pending. If this is intentional (to retry later), add a comment explaining the behavior.

  2. Comment clarity: Line 328's comment "often causes presets.json corruption in the past" should clarify that the mutex now prevents this issue.

  3. Missing atomic write: As with the async path, this should use write-to-temp + rename for atomic file replacement.

🔎 Suggested improvements
-  // we will save API call immediately (often causes presets.json corruption in the past)
+  // we will save API call immediately (mutex prevents corruption from concurrent writes)

   // WLEDMM Acquire file mutex before writing presets.json, to prevent presets.json corruption
   if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
       USER_PRINTLN(F("savePreset(): preset file busy, cannot write"));
+      presetToSave = 0; // clear pending save request
       return; // early exit, no change
   }

Based on learnings, presets.json writes should use atomic file replacement.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a56f9b and 76cf309.

📒 Files selected for processing (1)
  • wled00/presets.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/presets.cpp
🧠 Learnings (5)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.
📚 Learning: 2025-12-23T21:29:14.927Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-23T21:36:41.138Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:46:23.516Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:45:39.751Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Applied to files:

  • wled00/presets.cpp
🧬 Code graph analysis (1)
wled00/presets.cpp (2)
wled00/util.cpp (4)
  • requestJSONBufferLock (223-258)
  • requestJSONBufferLock (223-223)
  • releaseJSONBufferLock (261-271)
  • releaseJSONBufferLock (261-261)
wled00/file.cpp (2)
  • writeObjectToFileUsingId (286-291)
  • writeObjectToFileUsingId (286-286)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
🔇 Additional comments (1)
wled00/presets.cpp (1)

41-48: Mutex protection correctly serializes async writes.

The mutex acquisition and release logic is correct for the async write path:

  • Proper lock ordering (JSON buffer lock → file mutex)
  • Appropriate 2500ms timeout
  • Correct error handling with early return and JSON buffer lock cleanup
  • Both RAM buffer and regular write paths are protected

However, the learnings indicate that atomic writes (write-to-temp + rename) should be implemented to prevent partial file corruption if a write is interrupted.

Based on learnings, presets.json writes should use atomic file replacement.

Do you want me to generate a diff showing how to implement atomic writes using a temp file + rename pattern?

Also applies to: 92-94

@softhack007
Copy link
Collaborator Author

@coderabbitai please check if the global status variables in presets.json are kept consistent/correct in case of presetFileMux acquisition failure.

deletePreset() also needs to acquire presetFileMux before writing.
@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate preset file mutex declaration and uses =="
rg -n --no-heading "presetFileMux|esp32SemTake\\(presetFileMux|esp32SemGive\\(presetFileMux" -S

echo
echo "== Inspect doSaveState/savePreset/deletePreset in presets.cpp =="
rg -n --no-heading "^void\\s+doSaveState\\(|^void\\s+savePreset\\(|^void\\s+deletePreset\\(" wled00/presets.cpp

echo
echo "--- doSaveState() (context) ---"
start=$(rg -n "^void\\s+doSaveState\\(" wled00/presets.cpp | cut -d: -f1 | head -1 || true)
if [[ -n "${start:-}" ]]; then
  sed -n "$((start)),$((start+220))p" wled00/presets.cpp
fi

echo
echo "--- savePreset() (context) ---"
start=$(rg -n "^void\\s+savePreset\\(" wled00/presets.cpp | cut -d: -f1 | head -1 || true)
if [[ -n "${start:-}" ]]; then
  sed -n "$((start)),$((start+260))p" wled00/presets.cpp
fi

echo
echo "--- deletePreset() (context) ---"
start=$(rg -n "^void\\s+deletePreset\\(" wled00/presets.cpp | cut -d: -f1 | head -1 || true)
if [[ -n "${start:-}" ]]; then
  sed -n "$((start)),$((start+120))p" wled00/presets.cpp
fi

echo
echo "== Find global/status vars likely affected =="
rg -n --no-heading -C2 "presetsModifiedTime|presetToSave|updateFSInfo\\(|fileDoc\\b|jsonBufferLock|releaseJSONBufferLock\\(" wled00/presets.cpp

echo
echo "== Check other writers of presets.json (playlist/json paths) =="
rg -n --no-heading -C2 "writeObjectToFileUsingId\\(|writeObjectToFile\\(" wled00/*.cpp | grep -v "file.cpp" || true

echo
echo "== Verify file helper pre-close-before-open path =="
rg -n --no-heading -C3 "^bool\\s+writeObjectToFile\\(|doCloseFile|closeFile\\(" wled00/file.cpp

echo
echo "== Ensure early-return failure paths release JSON buffer and keep state consistent =="
rg -n --no-heading -C3 "esp32SemTake\\(presetFileMux.*\\)\\s*\\)\\s*\\{[\\s\\S]*return" wled00/presets.cpp | sed -n '1,200p'

Length of output: 14097


@softhack007 Here’s what I verified regarding status consistency when presetFileMux acquisition fails:

  • doSaveState() failure path is consistent

    • On failure to take presetFileMux (wled00/presets.cpp Line 44), it logs and releases the JSON buffer lock (Line 46) and returns. It does not touch presetsModifiedTime or updateFSInfo(), and it does not clear presetToSave here. Because presetToSave stays > 0, handlePresets() (Lines 157–159) will retry the async save on a later loop. This keeps status accounting correct and avoids false “modified” timestamps.
  • “Immediate API save” failure path needs one small adjustment

    • In savePreset(), presetToSave is set to index upfront (Line 316). In the immediate branch, if taking presetFileMux fails (Line 331), the function returns early (Line 333) without clearing presetToSave. That means the main loop will later attempt doSaveState(), i.e., an async “state” save, which is not what the “immediate API call” path intended. Internally this is still “consistent,” but it changes behavior and can confuse clients expecting only the API-call payload to be stored.

    • Minimal fix:

      • Clear presetToSave before returning on the mutex failure in the immediate branch, mirroring the success path’s Line 336.

      Example patch:

      // wled00/presets.cpp
      // inside the immediate API save branch
      if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
        USER_PRINTLN(F("savePreset(): preset file busy, cannot write"));
        presetToSave = 0;   // prevent unintended async state save later
        return;
      }
      
  • Success paths are correct

    • doSaveState(): presetsModifiedTime is updated only on successful persistent writes (Line 91), then updateFSInfo() (Line 95), and presetToSave is cleared (Line 99).
    • Immediate API save: after a successful write, it updates presetsModifiedTime (Line 349), calls updateFSInfo() (Line 350), and exits with presetToSave already cleared (Line 336).

Side note for completeness (not directly your question)

  • deletePreset() currently writes without taking presetFileMux (Lines 360–365). To keep write-side behavior uniform and avoid races with async/immediate saves, consider guarding it with presetFileMux as well.

Summary

  • Yes for doSaveState(): status variables remain consistent on mutex failure (no timestamp/FS info updates; operation will retry).
  • For immediate API save: one small consistency/semantics fix recommended — clear presetToSave on mutex failure to avoid a later unintended async state save.

Would you like me to prepare a small PR patch with the one-line change above and the optional deletePreset() guard?


🧠 Learnings used
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/presets.cpp (1)

331-348: Critical: Mutex leak on early return path.

Line 337 returns early after acquiring presetFileMux at line 331, but before releasing it at line 348. This creates a mutex leak that will permanently lock the presets file and deadlock all subsequent preset save/delete operations.

🔎 Proposed fix: Release mutex before early return
       if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
           USER_PRINTLN(F("savePreset(): preset file busy, cannot write"));
           return; // early exit, no change
       }

       presetToSave = 0;
-      if (index > 250 || !fileDoc) return; // cannot save API calls to temporary preset (255)
+      if (index > 250 || !fileDoc) {
+        esp32SemGive(presetFileMux);  // Release mutex before early return
+        return; // cannot save API calls to temporary preset (255)
+      }
       sObj.remove("o");
       sObj.remove("v");
       sObj.remove("time");
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76cf309 and d505dcc.

📒 Files selected for processing (1)
  • wled00/presets.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/presets.cpp
🧠 Learnings (6)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.
📚 Learning: 2025-12-23T21:29:14.927Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-23T21:36:41.138Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:46:23.516Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:45:39.751Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-21T22:26:03.261Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Applied to files:

  • wled00/presets.cpp
🧬 Code graph analysis (1)
wled00/presets.cpp (2)
wled00/util.cpp (4)
  • requestJSONBufferLock (223-258)
  • requestJSONBufferLock (223-223)
  • releaseJSONBufferLock (261-271)
  • releaseJSONBufferLock (261-261)
wled00/file.cpp (2)
  • writeObjectToFileUsingId (286-291)
  • writeObjectToFileUsingId (286-286)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (athom_music_esp32_4MB_M)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp8266_4MB_S)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
🔇 Additional comments (2)
wled00/presets.cpp (2)

41-94: LGTM! Mutex protection correctly serializes async preset writes.

The file mutex acquisition at line 44 with the 2500ms timeout (matching the learning that this accommodates typical 200KB writes ~750ms with safety margin) prevents race conditions between async and immediate write paths. Error handling correctly releases the JSON buffer lock before returning, and the mutex acquire/release pair is properly balanced.

Based on learnings, this change addresses the root cause of presets.json corruption where doSaveState could race with the immediate write path in savePreset.


360-373: LGTM! Mutex protection completes serialization of all preset write paths.

The presetFileMux acquisition guards the delete operation, closing the final unprotected write path to presets.json. The acquire/release pair is correctly balanced with no early return paths after mutex acquisition, and the 2500ms timeout is appropriate per learnings.

Based on learnings, this change ensures deletePreset() cannot corrupt presets.json by racing with doSaveState() or savePreset().

@softhack007
Copy link
Collaborator Author

@coderabbitai in case of mutex failure in savePreset(), should we also restore the old value of playlistSave? or is this not necessary due to the overall logic of presets.cpp? what about file writing that is initiated from playlist.cpp?

early return considered harmful 😆
@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find playlistSave symbol and its usages ==="
rg -n -C3 --no-heading '\bplaylistSave\b' wled00/presets.cpp wled00/*.cpp wled00/*.h || true

echo
echo "=== Show savePreset() implementation for mutex failure handling and playlistSave changes ==="
rg -n --no-heading '^void\s+savePreset\(' wled00/presets.cpp
start=$(rg -n '^void\s+savePreset\(' wled00/presets.cpp | cut -d: -f1 | head -1 || true)
if [[ -n "${start:-}" ]]; then
  sed -n "$((start)),$((start+260))p" wled00/presets.cpp
fi

echo
echo "=== Show doSaveState() for context (mutex and status vars handling) ==="
rg -n --no-heading '^void\s+doSaveState\(' wled00/presets.cpp
start=$(rg -n '^void\s+doSaveState\(' wled00/presets.cpp | cut -d: -f1 | head -1 || true)
if [[ -n "${start:-}" ]]; then
  sed -n "$((start)),$((start+180))p" wled00/presets.cpp
fi

echo
echo "=== Check deletePreset() and any other preset writers for mutex guarding ==="
rg -n -C2 --no-heading '^void\s+deletePreset\(' wled00/presets.cpp
start=$(rg -n '^void\s+deletePreset\(' wled00/presets.cpp | cut -d: -f1 | head -1 || true)
if [[ -n "${start:-}" ]]; then
  sed -n "$((start)),$((start+120))p" wled00/presets.cpp
fi

echo
echo "=== Search for file writes in playlist.cpp and whether they use mutex or file helpers ==="
rg -n -C3 --no-heading 'WLED_FS\.open|\bFile\b|writeObjectToFile\(|writeObjectToFileUsingId\(|appendObjectToFile\(' wled00/playlist.cpp || true

echo
echo "=== Verify presetFileMux declaration and typical usage pattern macros ==="
rg -n --no-heading 'presetFileMux|esp32SemTake\(|esp32SemGive\(' wled00/wled.h wled00/wled.cpp | sed -n '1,200p'

echo
echo "=== Confirm writeObjectToFile() pre-closes when doCloseFile is true ==="
rg -n --no-heading '^bool\s+writeObjectToFile\(' wled00/file.cpp
start=$(rg -n '^bool\s+writeObjectToFile\(' wled00/file.cpp | cut -d: -f1 | head -1 || true)
if [[ -n "${start:-}" ]]; then
  sed -n "$((start)),$((start+80))p" wled00/file.cpp
fi

echo
echo "=== Grep for playlistSave assignments to see if we should restore on failure ==="
rg -n -C2 --no-heading '\bplaylistSave\s*=' wled00/presets.cpp || true

Length of output: 10102


@softhack007

Short answer

  • Yes — on mutex acquisition failure in savePreset()’s immediate API path, you should restore the previous value of playlistSave. Otherwise a previously pending playlist save can be lost because savePreset() unconditionally sets playlistSave = false before we know whether we can proceed.
  • playlist.cpp does not perform file writes in the current codebase; all writes to presets.json go through presets.cpp/file.cpp, which are now guarded. No extra action is needed there.

Details and minimal patch

  • Where it can break today:

    • savePreset() snapshots oldpresetToSave but not playlistSave.
    • It immediately does playlistSave = false; then, if esp32SemTake(presetFileMux, 2500) fails in the immediate API branch, it restores presetToSave but leaves playlistSave cleared.
    • If a playlist save was previously queued (playlistSave == true), this early-return drops that pending action, and presetsSavePending()/presetsActionPending() will no longer report it.
  • Minimal fix (keep semantics unchanged, preserve pending work):

    • Snapshot and restore playlistSave around the immediate API branch.

Suggested change in wled00/presets.cpp inside savePreset():

void savePreset(byte index, const char* pname, JsonObject sObj)
{
  // ...
  auto oldpresetToSave  = presetToSave;   // already present
  bool oldPlaylistSave  = playlistSave;   // NEW: snapshot
  // Optional hygiene if you want to preserve quickLoad on failure, too:
  char oldQuickLoad[sizeof(quickLoad)];
  memcpy(oldQuickLoad, quickLoad, sizeof(quickLoad));

  presetToSave = index;
  playlistSave = false;
  if (sObj[F("ql")].is<const char*>())
    strlcpy(quickLoad, sObj[F("ql")].as<const char*>(), sizeof(quickLoad));

  if (sObj.size()==0 || sObj["o"].isNull()) {
    // async state path (unchanged)
  } else {
    if (sObj[F("playlist")].isNull()) {
      // Immediate API path
      if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
        USER_PRINTLN(F("savePreset(): preset file busy, cannot write"));
        presetToSave = oldpresetToSave;      // already present
        playlistSave = oldPlaylistSave;      // NEW: restore on failure
        memcpy(quickLoad, oldQuickLoad, sizeof(quickLoad)); // Optional restore
        return;
      }
      presetToSave = 0;
      // ... immediate write as-is ...
      esp32SemGive(presetFileMux);
      // ... timestamps & FS info ...
    } else {
      // playlist path
      includeBri   = true;
      playlistSave = true;
    }
  }
}

Notes

  • This fix ensures presetsSavePending()/presetsActionPending() remain accurate and that no previously queued playlist write is lost due to a transient presetFileMux contention.
  • Optional: The quickLoad snapshot/restore prevents savePreset() from changing quickLoad for a previously pending async state save if the immediate branch exits early. It’s not strictly required for playlistSave correctness but avoids subtle side-effects.
  • deletePreset() is already guarded by presetFileMux in your current code (good).
  • playlist.cpp: grep shows no direct FS writes (no WLED_FS.open(), no writeObjectToFile* calls). It builds playlist data; writes are performed in presets.cpp via doSaveState()/savePreset(), now serialized by presetFileMux and additionally protected by the pre-close fix in file.cpp.

If you want, I can also prepare a small follow-up patch to add the quickLoad snapshot/restore, but the playlistSave restoration above is the essential bit to avoid dropped actions.


🧠 Learnings used
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Copy link

@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: 0

🧹 Nitpick comments (1)
wled00/presets.cpp (1)

341-344: Consider restoring state on this early return path.

When returning early due to invalid index or missing fileDoc, the file mutex is correctly released. However, playlistSave was set to false on line 319 and is not restored here, unlike the mutex-failure path at lines 335-336.

For consistency and to avoid unintended state changes on aborted operations, consider restoring oldplaylistSave before returning.

🔎 Proposed fix to restore state
       presetToSave = 0;
       if (index > 250 || !fileDoc) {
+        playlistSave = oldplaylistSave;  // Restore on early exit
         esp32SemGive(presetFileMux);  // Release file mutex
         return; // cannot save API calls to temporary preset (255)
       }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d505dcc and e1c7e81.

📒 Files selected for processing (1)
  • wled00/presets.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/presets.cpp
🧠 Learnings (6)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.
📚 Learning: 2025-12-23T21:29:14.927Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-23T21:36:41.138Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:46:23.516Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:45:39.751Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-21T22:26:03.261Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Applied to files:

  • wled00/presets.cpp
🧬 Code graph analysis (1)
wled00/presets.cpp (1)
wled00/util.cpp (4)
  • requestJSONBufferLock (223-258)
  • requestJSONBufferLock (223-223)
  • releaseJSONBufferLock (261-271)
  • releaseJSONBufferLock (261-261)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp8266_4MB_S)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Gather Environments
🔇 Additional comments (7)
wled00/presets.cpp (7)

41-48: LGTM! Correct mutex acquisition with proper cleanup.

The file mutex is correctly acquired after the JSON buffer lock, with appropriate timeout and error handling. The early return on failure properly releases the JSON buffer lock, preventing resource leaks.

Based on learnings, the 2500ms timeout accommodates typical 200KB file writes (~750ms) with safety margin.


93-93: LGTM! Mutex released in correct order.

The file mutex is released before the JSON buffer lock, maintaining proper LIFO release order (inverse of acquisition).


315-316: LGTM! State snapshot enables proper recovery.

Capturing the old state before modifications allows restoration if mutex acquisition fails, maintaining internal consistency on early exit paths.


355-355: LGTM! Mutex released after successful write.

The file mutex is properly released after the immediate write completes on the API-call path.


368-372: LGTM! Mutex properly guards deletePreset() write.

The file mutex correctly serializes the delete operation, preventing corruption. As noted in the PR objectives, deletePreset() previously wrote presets.json without mutex protection.

Based on learnings, this addresses a previously unguarded write path.


377-377: LGTM! Mutex released after delete completes.

The file mutex is properly released, completing the deletePreset() serialization.


330-338: Acquire JSON buffer lock before accessing fileDoc in the immediate API save path.

The immediate API save path (lines 329–355) accesses the global fileDoc variable without holding the JSON buffer lock. While the file mutex prevents races between immediate and async writes, it does not protect the fileDoc object itself from concurrent modification. The async path in doSaveState() explicitly calls requestJSONBufferLock() before acquiring the file mutex; the immediate path should follow the same pattern to ensure thread-safe access to fileDoc.

Add requestJSONBufferLock() before line 333, and corresponding releaseJSONBufferLock() after esp32SemGive() at line 355 (or early return at line 343). This ensures consistent lock ordering: JSON buffer lock first, then file mutex.

⛔ Skipped due to learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

@softhack007
Copy link
Collaborator Author

wled00/presets.cpp (1)

341-344: Consider restoring state on this early return path.
When returning early due to invalid index or missing fileDoc, the file mutex is correctly released. However, playlistSave was set to false on line 319 and is not restored here, unlike the mutex-failure path at lines 335-336.
For consistency and to avoid unintended state changes on aborted operations, consider restoring oldplaylistSave before returning.

@coderabbitai this early return is from legacy code, however I agree its strange that playlistSave or presetToSave were never restored in this path. Please perform an in-depth code review, to understand if this is a bug or not.

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Tip

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

@softhack007 Thanks for flagging this. I did a focused control‑flow pass over savePreset() around the immediate API save branch and that legacy early‑return.

Conclusion

  • It is a bug. On the “invalid index or !fileDoc” early return, we release presetFileMux and return without restoring presetToSave and playlistSave that were modified earlier in savePreset(). That leaves:
    • presetToSave > 0, which can later trigger an unintended async doSaveState() run (not what the caller intended after an aborted immediate save).
    • playlistSave cleared, potentially dropping a previously pending playlist write.

Recommended fix

  • Mirror the mutex‑failure path: snapshot old values at the top of savePreset() and restore them on this early return before exiting. If you already snapshot quickLoad for the mutex‑failure case, also restore it here for consistency.

Minimal patch sketch (illustrative context)

void savePreset(byte index, const char* pname, JsonObject sObj)
{
  // Snapshot current status to allow restoration on any early exit
  auto oldpresetToSave = presetToSave;
  bool oldPlaylistSave = playlistSave;
  char oldQuickLoad[sizeof(quickLoad)];
  memcpy(oldQuickLoad, quickLoad, sizeof(oldQuickLoad));

  presetToSave = index;
  playlistSave = false;
  if (sObj[F("ql")].is<const char*>())
    strlcpy(quickLoad, sObj[F("ql")].as<const char*>(), sizeof(quickLoad));

  // ... enter immediate API branch ...

  if (esp32SemTake(presetFileMux, 2500) != pdTRUE) {
    // existing failure path — already restores presetToSave and playlistSave
    presetToSave = oldpresetToSave;
    playlistSave = oldPlaylistSave;
    memcpy(quickLoad, oldQuickLoad, sizeof(oldQuickLoad));
    return;
  }

  // Legacy early return (invalid index / missing fileDoc)
  if (index > 250 || !fileDoc) {
    esp32SemGive(presetFileMux);
    // NEW: restore status to avoid unintended async save or dropped playlist
    presetToSave = oldpresetToSave;
    playlistSave = oldPlaylistSave;
    memcpy(quickLoad, oldQuickLoad, sizeof(oldQuickLoad));
    return;
  }

  // Success path clears presetToSave then performs the immediate write...
  presetToSave = 0;
  // writeObjectToFileUsingId(...), timestamps, updateFSInfo()...
}

Why this is safe

  • Keeps the status machine coherent: no spurious async save, no lost pending playlist write, no change to successful paths.
  • Aligns behavior with the already corrected mutex‑failure path, making both early exits consistent.

If you’d like, I can also scan the file for any other early‑returns inside savePreset() and apply the same restoration pattern (I didn’t see additional ones that modify these flags after the snapshots).


✏️ Learnings added
Learnt from: softhack007
URL: https://github.com/MoonModules/WLED-MM/pull/293

Timestamp: 2025-12-29T20:31:11.720Z
Learning: In MoonModules/WLED-MM wled00/presets.cpp, savePreset() must restore both presetToSave and playlistSave (and quickLoad if modified) on all early-return paths in the immediate API save branch (e.g., invalid index or missing fileDoc). Failing to restore leaves presetToSave > 0 (triggering unintended async save) and can drop a previously pending playlist save.

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: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 237
File: wled00/FXparticleSystem.cpp:678-743
Timestamp: 2025-05-04T10:32:30.457Z
Learning: In the Particle System implementation for WLED, stack allocation is preferred over heap allocation for temporary rendering buffers because there's enough stack memory available and heap allocations can cause memory fragmentation.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:17:26.380Z
Learning: In wled00/FX.cpp, the `#ifndef WLED_DISABLE_2D` guard around the Akemi 2D fallback in the `mode_oops()` function (lines 142-146) is intentionally kept. This guard is necessary because when users build with `-D WLED_DISABLE_2D` to remove all 2D effects from the build, the Akemi effect is also not compiled, so the guard prevents calling a non-existent function.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/json.cpp:295-304
Timestamp: 2025-10-22T22:24:46.163Z
Learning: When deriving an upper-bound index from a count in WLED-MM PR #270, use a safe guard: last = count ? count - 1 : 0. Avoid min/max with 0 on unsigned types and never pass (count-1) directly to helpers (e.g., updateVal16, relativeChange, random8) without guarding. Effect IDs are 16-bit; palette IDs remain 8-bit but should use the same guard when count can be 0.

Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

* Keeps the status machine coherent: no spurious async save, no lost pending playlist write, no change to successful paths.
* Aligns behavior with the already corrected mutex‑failure path, making both early exits consistent.
* default timeout = 1800ms
* reduced timeout for ws (300ms)
Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/presets.cpp (1)

315-320: Restore quickLoad on early returns in the immediate API save branch.

Recovery variables are saved for presetToSave and playlistSave, but quickLoad is set at line 320 and not saved for restoration. If savePreset() returns early (mutex failure at lines 335-337 or invalid conditions at lines 342-346), the stale quickLoad value persists and will be incorrectly used by a subsequent doSaveState() call.

Based on learnings, quickLoad must be restored (cleared) on all early-return paths in the immediate API save branch to maintain state machine coherence.

🔎 Proposed fix to save and restore quickLoad
 DEBUG_PRINT(F("Saving preset (")); DEBUG_PRINT(index); DEBUG_PRINT(F(") ")); DEBUG_PRINTLN(saveName);
 auto oldpresetToSave = presetToSave; // for recovery in case that esp32SemTake(presetFileMux) fails
 auto oldplaylistSave = playlistSave;
+char oldquickLoad[sizeof(quickLoad)];
+strlcpy(oldquickLoad, quickLoad, sizeof(oldquickLoad));

 presetToSave = index;
 playlistSave = false;
 if (sObj[F("ql")].is<const char*>()) strlcpy(quickLoad, sObj[F("ql")].as<const char*>(), sizeof(quickLoad)); // client limits QL to 2 chars, buffer for 12 bytes to allow encoded unicode
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1c7e81 and 80b9069.

📒 Files selected for processing (4)
  • wled00/fcn_declare.h
  • wled00/presets.cpp
  • wled00/util.cpp
  • wled00/ws.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/presets.cpp
  • wled00/util.cpp
  • wled00/fcn_declare.h
  • wled00/ws.cpp
🧠 Learnings (14)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-29T20:31:11.720Z
Learning: In MoonModules/WLED-MM wled00/presets.cpp, savePreset() must restore both presetToSave and playlistSave (and quickLoad if modified) on all early-return paths in the immediate API save branch (e.g., invalid index or missing fileDoc). Failing to restore leaves presetToSave > 0 (triggering unintended async save) and can drop a previously pending playlist save.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.
📚 Learning: 2025-12-23T21:29:14.927Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Applied to files:

  • wled00/presets.cpp
  • wled00/util.cpp
  • wled00/ws.cpp
📚 Learning: 2025-12-23T21:36:41.138Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Applied to files:

  • wled00/presets.cpp
  • wled00/util.cpp
  • wled00/fcn_declare.h
📚 Learning: 2025-12-29T20:31:11.720Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-29T20:31:11.720Z
Learning: In MoonModules/WLED-MM wled00/presets.cpp, savePreset() must restore both presetToSave and playlistSave (and quickLoad if modified) on all early-return paths in the immediate API save branch (e.g., invalid index or missing fileDoc). Failing to restore leaves presetToSave > 0 (triggering unintended async save) and can drop a previously pending playlist save.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:46:23.516Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Applied to files:

  • wled00/presets.cpp
  • wled00/util.cpp
  • wled00/fcn_declare.h
📚 Learning: 2025-12-25T00:45:39.751Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:45:39.751Z
Learning: In wled00/file.cpp, writeObjectToFile() must call closeFile() when doCloseFile is true before opening the global File f for a new write. Without this pre-close, a second write can reassign f while the previous write’s handle is still open, risking unflushed buffers and file corruption. Read paths already pre-close; writes need the same guard.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-21T22:26:03.261Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Applied to files:

  • wled00/presets.cpp
  • wled00/util.cpp
📚 Learning: 2025-12-22T21:14:08.797Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Applied to files:

  • wled00/util.cpp
📚 Learning: 2025-12-22T20:31:17.991Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Applied to files:

  • wled00/util.cpp
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/util.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/util.cpp
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/util.cpp
  • wled00/fcn_declare.h
📚 Learning: 2025-07-12T04:20:14.546Z
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.

Applied to files:

  • wled00/util.cpp
📚 Learning: 2025-11-24T00:15:38.069Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-24T00:15:38.069Z
Learning: In MoonModules/WLED-MM, when reading ESP32/ESP32-S3 eFuse blocks using esp_efuse_read_field_blob(): EFUSE_BLK0 has only 7 × 32-bit registers (224 bits total), not 8 registers (256 bits). Reading more than 7 words from EFUSE_BLK0 triggers assertion failure in esp_efuse_utility_read_reg(). Use maximum 7 words when reading EFUSE_BLK0 to avoid crashes. This applies to all ESP32 variants (ESP32, ESP32-S2, ESP32-S3, ESP32-C3).

Applied to files:

  • wled00/util.cpp
🧬 Code graph analysis (4)
wled00/presets.cpp (2)
wled00/util.cpp (4)
  • requestJSONBufferLock (223-258)
  • requestJSONBufferLock (223-223)
  • releaseJSONBufferLock (261-271)
  • releaseJSONBufferLock (261-261)
wled00/file.cpp (2)
  • writeObjectToFileUsingId (286-291)
  • writeObjectToFileUsingId (286-286)
wled00/util.cpp (1)
usermods/artifx/arti.h (1)
  • millis (150-153)
wled00/fcn_declare.h (1)
wled00/util.cpp (2)
  • requestJSONBufferLock (223-258)
  • requestJSONBufferLock (223-223)
wled00/ws.cpp (1)
wled00/util.cpp (2)
  • requestJSONBufferLock (223-258)
  • requestJSONBufferLock (223-223)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (seeed_esp32c3_4MB_S)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32_16MB_V4_M_debug)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
🔇 Additional comments (7)
wled00/ws.cpp (1)

52-52: LGTM! Explicit timeout improves WebSocket responsiveness.

The 300ms timeout for WebSocket JSON buffer acquisition is appropriately shorter than the default 1800ms to prevent blocking the WebSocket event loop. This aligns with the PR's goal of reducing lock contention and improving UI responsiveness.

Also applies to: 141-141

wled00/fcn_declare.h (1)

442-442: LGTM! Configurable timeout with sensible default.

The 1800ms default timeout provides a safety margin above the suspendStripService timeout (1500ms) mentioned in the implementation, while allowing call sites to specify shorter timeouts when appropriate (e.g., 300ms for WebSocket paths).

wled00/util.cpp (2)

268-270: LGTM! Mutex properly released.

The ESP32 mutex release in releaseJSONBufferLock() correctly pairs with the acquisition in requestJSONBufferLock(), ensuring the mutex is held for the entire duration that the JSON buffer is locked.


223-257: Update comment values and clarify suspendStripService timeout relationship.

The comment at lines 230 and 235 states suspendStripService timeout is 1500ms, but realtimeTimeoutMs defaults to 2500ms (wled.h:448). More importantly, the current implementation releases suspendStripService early during deserializeState() processing—before stateUpdated() completes—making the timeout relationship different than the comment suggests. The 300ms WebSocket timeout should be safe given this early release pattern. Correct the hardcoded value (1500ms → 2500ms) and clarify that the lock is released progressively during state deserialization, not held for the full timeout interval.

Likely an incorrect or invalid review comment.

wled00/presets.cpp (3)

41-48: LGTM: File mutex properly serializes async preset writes.

The mutex acquisition before writing (with 2500ms timeout and proper cleanup on failure) and release after completion correctly implements the file-level synchronization to prevent presets.json corruption.

Based on learnings, this serialization of write paths prevents races between async doSaveState() and immediate savePreset() paths.

Also applies to: 93-93


369-382: LGTM: File mutex correctly guards preset deletion.

The mutex acquisition, write operation, and release are properly sequenced to serialize deletes with other preset write operations.


330-330: Good documentation of the historical corruption issue.

The comment clearly documents why the file mutex was introduced, which helps future maintainers understand the rationale for the synchronization.

* restore quickload on early exit
* replace magic numbers with constants
Copy link

@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: 0

🧹 Nitpick comments (1)
wled00/presets.cpp (1)

317-320: Consider backing up saveName for complete state restoration.

presetToSave, playlistSave, and quickLoad are correctly backed up for restoration on mutex failure. However, saveName is modified at lines 310-314 before this backup code runs, so on early return (lines 342-343 or 350-351), the original saveName value is lost.

If this function is called while a previous async save is pending, corrupting saveName could affect the pending operation. Consider also backing up and restoring saveName:

🔎 Proposed fix to also backup saveName
   auto oldpresetToSave = presetToSave; // for recovery in case that esp32SemTake(presetFileMux) fails
   auto oldplaylistSave = playlistSave;
   char oldQuickLoad[QLOAD_BUFFER+1];
   strlcpy(oldQuickLoad, quickLoad, sizeof(oldQuickLoad));
+  char oldSaveName[FNAME_BUFFER+1];
+  strlcpy(oldSaveName, saveName, sizeof(oldSaveName));

Then restore it on early exits:

   presetToSave = oldpresetToSave;
   playlistSave = oldplaylistSave;
   strlcpy(quickLoad, oldQuickLoad, sizeof(quickLoad));
+  strlcpy(saveName, oldSaveName, sizeof(saveName));
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80b9069 and 868b2ca.

📒 Files selected for processing (1)
  • wled00/presets.cpp
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for C++ firmware source and headers in wled00

Files:

  • wled00/presets.cpp
🧠 Learnings (11)
📓 Common learnings
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-29T20:31:11.720Z
Learning: In MoonModules/WLED-MM wled00/presets.cpp, savePreset() must restore both presetToSave and playlistSave (and quickLoad if modified) on all early-return paths in the immediate API save branch (e.g., invalid index or missing fileDoc). Failing to restore leaves presetToSave > 0 (triggering unintended async save) and can drop a previously pending playlist save.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.
Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR #248 merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.
📚 Learning: 2025-12-23T21:29:14.927Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-23T21:36:41.138Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-29T20:31:11.720Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-29T20:31:11.720Z
Learning: In MoonModules/WLED-MM wled00/presets.cpp, savePreset() must restore both presetToSave and playlistSave (and quickLoad if modified) on all early-return paths in the immediate API save branch (e.g., invalid index or missing fileDoc). Failing to restore leaves presetToSave > 0 (triggering unintended async save) and can drop a previously pending playlist save.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-25T00:46:23.516Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-10-22T21:59:24.331Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR #270, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue #272; do not address it within PR #270.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-22T20:31:17.991Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-11-02T22:07:15.689Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-02T22:07:15.689Z
Learning: In MoonModules/WLED-MM, WLEDMM_FASTPATH will become the default code path. Code inside "#ifndef WLEDMM_FASTPATH" blocks represents deprecated paths that are being phased out and should be ignored during reviews.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-11-14T13:55:44.442Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-11-14T13:55:44.442Z
Learning: In MoonModules/WLED-MM, always flag Variable Length Arrays (VLA) during code reviews, especially when they could overflow available stack space (typically 2K-4K on ESP32/ESP8266). VLAs based on dynamic parameters like segment dimensions, particle counts, or pixel buffers should be highlighted and require justification from the author. Prefer fixed-size arrays, heap allocation, or bounded VLAs with explicit maximum size constraints.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-10-22T21:50:25.839Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR #270, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Applied to files:

  • wled00/presets.cpp
📚 Learning: 2025-12-21T22:26:03.261Z
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Applied to files:

  • wled00/presets.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32_4MB_M_eth)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (athom_music_esp32_4MB_M)
  • GitHub Check: Builds (seeed_esp32c3_4MB_S)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32dev_compat)
  • GitHub Check: Builds (esp32_pico_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
🔇 Additional comments (8)
wled00/presets.cpp (8)

15-18: Good use of named constants for buffer sizes.

Defining QLOAD_BUFFER and FNAME_BUFFER as constants and using them consistently for array sizing and strlcpy bounds is a solid improvement for maintainability and prevents off-by-one errors.


43-50: Correct mutex acquisition order and error handling.

The sequence is sound: JSON buffer lock is acquired first (line 43), then the file mutex (line 46). On mutex failure, the JSON lock is properly released before returning. The 2500ms timeout provides adequate margin for large preset files. Based on learnings, this aligns with the recommended pattern for serializing preset writes.


94-96: Proper LIFO lock release order.

The mutex is released before the JSON buffer lock, maintaining correct reverse acquisition order. This prevents potential deadlock scenarios.


310-314: Consistent buffer size usage.

Using FNAME_BUFFER+1 consistently with the saveName array declaration ensures correct bounds for strlcpy.


334-343: Mutex acquisition and state restoration correctly implemented.

The immediate API save path now properly acquires presetFileMux before writing, addressing the race condition with doSaveState(). The state restoration on mutex failure (lines 339-341) correctly restores presetToSave, playlistSave, and quickLoad as requested in past review comments.


345-352: Correct mutex release and state restoration on validation failure.

The index > 250 check is appropriate here since API calls cannot be saved to temporary presets (251-255). The mutex is correctly released before returning, and state variables are properly restored. This addresses the concerns from past review comments.


359-365: Mutex correctly scoped around file write operation.

The file mutex is held through initPresetsFile() and writeObjectToFileUsingId(), then released before updating non-file-related state. This ensures the critical section is as short as necessary while protecting the file write.


375-388: Delete operation correctly protected by file mutex.

In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux). This change properly guards the delete path with presetFileMux, completing the mutex coverage across all three write paths (doSaveState, savePreset immediate branch, and deletePreset). Based on learnings, this is necessary to prevent corruption.

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.

4 participants