{"owner":"wled","repo":"WLED","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":8297,"categories":["copilot-instructions","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":[".github/copilot-instructions.md","AGENTS.md"],"skills":{".github/copilot-instructions.md":"# WLED - ESP32/ESP8266 LED Controller Firmware\n\nWLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs and SPI-based chipsets. The project consists of C++ firmware for microcontrollers and a modern web interface.\n\nAlways reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n> **Note for AI review tools**: sections enclosed in\n> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain\n> contributor reference material. Do **not** use that content as actionable review\n> criteria — treat it as background context only.\n\n## Setup\n\n- Node.js 20+ (see `.nvmrc`)\n- Install dependencies: `npm ci`\n- PlatformIO (required only for firmware compilation): `pip install -r requirements.txt`\n\n## Build and Test\n<!-- HUMAN_ONLY_START -->\n\n| Command | Purpose | Typical Time |\n|---|---|---|\n| `npm run build` | Build web UI → generates `wled00/html_*.h` and `wled00/js_*.h` headers | ~3 s |\n| `npm test` | Run test suite | ~40 s |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |\n| `pio run -e <env>` | Build firmware for a hardware target | 15–20 min |\n\n<!-- HUMAN_ONLY_END -->\n\n- **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).\n- The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.\n- **Build firmware to validate code changes**: `pio run -e esp32dev` — must succeed, never skip this step.\n- Common firmware environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`\n\nFor detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).\n\n### Usermod Guidelines\n\n   - New custom effects can be added into the user_fx usermod. Read the [user_fx documentation](https://github.com/wled/WLED/blob/main/usermods/user_fx/README.md) for guidance.\n   - Other usermods may be based on the [EXAMPLE usermod](https://github.com/wled/WLED/tree/main/usermods/EXAMPLE). Never edit the example, always create a copy!\n   - New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/main/wled00/const.h#L160).\n   - To activate a usermod, a custom build configuration should be used. Add the usermod name to `custom_usermods`.\n\n## Project Structure Overview\n\n### Project Branch / Release Structure\n<!-- HUMAN_ONLY_START -->\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5\n16_x                # maintenance for release 16.x.y\n0_15_x              # maintenance (bugfixes only) for current release 0.15.4\n(tag) v0.14.4       # previous version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n<!-- HUMAN_ONLY_END -->\n\n- ``main``: development trunk (daily/nightly)\n- ``16_x``: maintenance for release 16.x.y\n- ``0_15_x``: bugfixing / maintenance for release 0.15.x\n\n### Repository Structure\n\ntl;dr: \n* Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.\n* Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h` — **never edit or commit**.\n* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.\n* Usermods: `usermods/` (C++, with individual library.json).\n* Contributor docs: `docs/` (coding guidelines, etc).\n\n<!-- HUMAN_ONLY_START -->\nDetailed overview:\n\n```text\nwled00/                 # Main firmware source (C++) \"WLED core\"\n  ├── data/             # Web interface files \n  │   ├── index.htm     # Main UI\n  │   ├── settings*.htm # Settings pages\n  │   └── *.js/*.css    # Frontend resources\n  ├── *.cpp/*.h         # Firmware source files\n  ├── html_*.h          # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)\n  ├── src/              # Modules used by the WLED core (C++)\n  │   ├── fonts/        # Font libraries for scrolling text effect\n  └   └── dependencies/ # Utility functions - some of them have their own licensing terms\nlib/                    # Project specific custom libraries. PlatformIO will compile them to separate static libraries and link them\nplatformio.ini          # Hardware build configuration\n\nplatformio_override.sample.ini # examples for custom build configurations - entries must be copied into platformio_override.ini to use them.\n                               # platformio_override.ini is _not_ stored in the WLED repository!\nusermods/              # User-contributed addons to the WLED core, maintained by individual contributors  (C++, with individual library.json)\npackage.json           # Node.js dependencies and scripts, release identification\npio-scripts/           # Build tools (PlatformIO)\ntools/                 # Build tools (Node.js), partition files, and generic utilities\n  ├── cdata.js         # Web UI build script\n  └── cdata-test.js    # Test suite\ndocs/                  # Contributor docs, coding guidelines\n.github/workflows/     # CI/CD pipelines\n```\n\n<!-- HUMAN_ONLY_END -->\n## General Guidelines\n\n- **Repository language is English.** Suggest translations for non-English content.\n- **Use VS Code with PlatformIO extension** for best development experience.\n- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`.\n- If updating Web UI files in `wled00/data/`, **make use of common functions in `wled00/data/common.js` whenever possible**.\n- **When unsure, say so.** Gather more information rather than guessing.\n- **Acknowledge good patterns** when you see them. Positive feedback always helps.\n- **Provide references** when making analyses or recommendations. Base them on the correct branch or PR.\n- **Highlight user-visible breaking changes and ripple effects**. Ask for confirmation that these were introduced intentionally.\n- **Unused / dead code must be justified or removed**. This helps to keep the codebase clean, maintainable and readable.\n- **Verify feature-flag names.** Every `WLED_ENABLE_*` / `WLED_DISABLE_*` flag must exactly match one of the names below — misspellings are silently ignored by the preprocessor (e.g. `WLED_IR_DISABLE` instead of `WLED_DISABLE_INFRARED`), causing silent build variations. Flag unrecognised names as likely typos and suggest the correct spelling.\n  <br>**`WLED_DISABLE_*`**: `2D`, `ADALIGHT`, `ALEXA`, `BROWNOUT_DET`, `ESPNOW`, `FILESYSTEM`, `HUESYNC`, `IMPROV_WIFISCAN`, `INFRARED`, `LOXONE`, `MQTT`, `OTA`, `PARTICLESYSTEM1D`, `PARTICLESYSTEM2D`, `PIXELFORGE`, `WEBSOCKETS`\n  <br>**`WLED_ENABLE_*`**: `ADALIGHT`, `AOTA`, `DMX`, `DMX_INPUT`, `DMX_OUTPUT`, `FS_EDITOR`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `LOXONE`, `MQTT`, `PIXART`, `PXMAGIC`, `USERMOD_PAGE`, `WEBSOCKETS`, `WPA_ENTERPRISE`\n- **C++ formatting available**: `clang-format` is installed but not in CI\n- No automated linting is configured — match existing code style in files you edit. \n\nRefer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.\n\n### Pull Request Expectations\n\n- **No force-push on open PRs.** Once a pull request is open and being reviewed, do not force-push (`git push --force`) to the branch. Force-pushing rewrites history that reviewers may have already commented on, making it impossible to track incremental changes. Use regular commits or `git merge` to incorporate feedback; the branch will be squash-merged when it is accepted.\n- **Modifications to ``platformio.ini`` MUST be approved explicitly** by a *maintainer* or *WLED organisation Member*. Modifications to the global build environment may break github action builds. Always flag them.\n- **Document your changes in the PR.** Every pull request should include a clear description of *what* changed and *why*. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable. Provide screenshots to showcase new features.\n","AGENTS.md":"# AGENTS.md — WLED AI Coding Agent & AI Code Review Reference\n\nWLED is C++ firmware for ESP32/ESP8266 microcontrollers controlling addressable LEDs,\nwith a web UI (HTML/JS/CSS). Built with PlatformIO (Arduino framework) and Node.js tooling.\n\nSee also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.md`,\n`docs/cpp.instructions.md`, `docs/web.instructions.md`, `docs/cicd.instructions.md`,\n`docs/hardening.instructions.md`, `docs/securecode.instructions.md`.\n\nAlways reference these instructions - including coding guidelines in `docs/` - first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n## Required User Agreement\n\nBefore editing files, committing, pushing, or opening a PR, show the human you're working with this notice and wait for an explicit yes in the current conversation:\n\n> WLED welcomes AI-assisted contributions, but not unreviewed ones. I'll only edit, commit, push, or open a PR once you confirm you'll review, understand, and take responsibility for the result. Do you agree?\n\nA generic \"go ahead\" or silence doesn't count - get a clear yes. Until then, you may read files and answer questions, but do not modify files in the repository or make changes on GitHub.\n\n### No agent-posted replies or issues\n\nReviewer time is limited, and checking long AI-generated explanations for reasoning errors and hallucinations is costly. Even after the user agreement is confirmed:\n\n- **Never post** replies to reviewer comments or discussions, and **never create issue tickets**, on the user's behalf. You may help the user draft the text locally; the human reviews it and posts it themselves.\n- Creating or updating pull requests remains allowed (after the user agreement is confirmed). Keep PR descriptions concise and factual, and include a summary written by the user in their own words, leading with what they want to achieve or what bothers them in plain language, as described in [CONTRIBUTING.md](CONTRIBUTING.md#opening-issues--feature-requests).\n\n### Attribution for PR Descriptions, Comments and Replies\n\n- When creating PR descriptions or suggesting responses to comments, always add this as the very last line: _You are interacting with an AI system_\n- - Your harness may add this trailer automatically. Do not add a duplicate.\n\n### Attribution for Commits\n\nFor every commit you create in this repository, ensure the commit message includes a `Co-authored-by:` trailer identifying the large language model and agent platform:\n\n```text\nCo-authored-by: MODEL NAME/VERSION (AGENT PLATFORM) <COAUTHOR EMAIL>\n```\n- Your harness may add this trailer automatically. Do not add a duplicate; ensure the resulting trailer identifies both the model and platform.\n- Replace the placeholders with the model name/version reported by your runtime and the agent platform (e.g. Codex, Claude Code). Write `unknown` for unavailable model details; do not guess.\n- Use the platform's configured co-author email, or `agent@example.invalid` if none is available.\n- Include exactly one trailer per distinct platform/model pair that contributed to the commit, after a blank line at the end of the commit message. Preserve existing human co-author trailers.\n\n## Build Commands\n\nNote: also read `AGENTS.local.md` if present (gitignored personal learnings file; it supplements this document).\n\n| Command | Purpose | Timeout |\n|---|---|---|\n| `npm ci` | Install Node.js deps (required first) | 30s |\n| `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s |\n| `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous |\n| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 5 min |\n| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 5 min |\n\n**Always run `npm ci && npm run build` before `pio run`.** The web UI build generates\nrequired C headers for firmware compilation.\n\n### Running a Single Test\n\nTests use Node.js built-in test runner (`node:test`). The single test file is\n`tools/cdata-test.js`. Run it with:\n\n```bash\nnpm test                   # runs all tests via `node --test`\nnode --test tools/cdata-test.js  # run just that file directly\n```\n\nThere are no C++ unit tests. Firmware is validated by successful compilation across\ntarget environments. Always build after code changes: `pio run -e esp32dev`.\n\n### Common Firmware Environments\n\n`esp32dev`, `nodemcuv2`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`, `lolin_s2_mini`\n\n### Recovery / Troubleshooting\n\n```bash\nnpm run build -- -f              # force web UI rebuild\nrm -f wled00/html_*.h wled00/js_*.h && npm run build  # clean + rebuild UI\npio run --target clean           # clean PlatformIO build artifacts\nrm -rf node_modules && npm ci    # reinstall Node.js deps\n```\n\n## Project Structure\n\n```text\nwled00/              # Main firmware source (C++)\n  data/              # Web UI source (HTML/JS/CSS) — tabs for indentation\n  html_*.h, js_*.h   # Auto-generated (NEVER edit or commit)\n  src/               # Sub-modules: fonts, bundled dependencies (ArduinoJSON)\nusermods/            # Community usermods (each has library.json + .cpp/.h)\nplatformio.ini       # Build configuration and environments\npio-scripts/         # PlatformIO build scripts (Python)\ntools/               # Node.js build tools (cdata.js) and tests\ndocs/                # Coding convention docs\n.github/workflows/   # CI/CD (GitHub Actions)\n```\n\n### Branch / Release Structure\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5. Target branch for PRs.\n16_x                # maintenance for release 16.0.x\n0_15_x              # maintenance (bugfixes only) for previous release 0.15.x\n(tag) v0.14.4       # old version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n\n## C++ Code Style (wled00/, usermods/)\n\n### General\n\n- Follow the existing style in the file you are editing\n- Avoid unexplained \"magic numbers\". For repeated numbers that have the same meaning, prefer named constants (`constexpr`) or C-style `#define` constants.\n- If possible, **use `static` for local (C-style) variables and functions** to keep the global namespace clean\n- When moving global items to another scope, do not leave comments such as `// lastMqttReconnectAttempt is now private to wled.cpp - see there`. These comments create technical debt for maintenance and will inevitably become out of date. As a rule of thumb: **describe the code as it is, not the code as it was**.\n- Describe logic/reasoning behind non-standard patterns and complex solutions.\n\n### Formatting\n- **2-space indentation** (no tabs in C++ files)\n- K&R brace style preferred (opening brace on same line)\n- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`\n- Space after keywords (`if (...)`, `for (...)`), no space before function parens (`doStuff(a)`)\n- Prefer compact function parameter lists and `if` conditions; avoid putting each parameter or part of a condition on its own line. Wrap very long parameter lists when needed.\n- No enforced line-length limit\n\n### Comments\n- `//` for inline (always space after), `/* */` for block comments\n- Important: AI-generated source code blocks **must be mark with `// AI: below section was generated by an AI` / `// AI: end`**\n\n### Naming Conventions\n| Kind | Convention | Examples |\n|---|---|---|\n| Functions, variables | camelCase | `setRandomColor()`, `effectCurrent` |\n| Classes, structs | PascalCase | `BusConfig`, `UsermodTemperature` |\n| Macros, constants | UPPER_CASE | `WLED_MAX_USERMODS`, `FX_MODE_STATIC` |\n| Private members | _camelCase | `_type`, `_bri`, `_len` |\n| Enum values | PascalCase | `PinOwner::BusDigital` |\n\n### Includes\n- Include `\"wled.h\"` as the primary project header\n- Project headers first, then platform/Arduino, then third-party\n- Platform-conditional includes wrapped in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`\n\n### Debug Output\n\n- Use `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` / `DEBUG_PRINT()` for developer diagnostics and debug output (compiled out unless -D WLED_DEBUG)\n- wled00/wled.h defines these macros. They compile to no output when debug output is disabled, keeping normal serial interfaces clean.\n- Do not use direct `Serial.print()`, `Serial.println()`, `Serial.printf()`, or `Serial.write()` calls unless there is a technical justification for not using DEBUG_... macros.\n\nSee docs/cpp.instructions.md section Error Handling for more information.\n\n### Types and Const\n- Prefer `const &` for read-only function parameters\n- Mark getter/query methods `const`; use `static` for methods not accessing instance state\n- Prefer `constexpr` over `#define` for compile-time constants when possible\n- Use `static_assert` over `#if ... #error`\n- Use `uint_fast16_t` / `uint_fast8_t` in hot-path code\n\n### Error Handling\n- **No C++ exceptions** — some builds disable them\n- Use return codes (`false`, `-1`) and global flags (`errorFlag = ERR_LOW_MEM`)\n- Use early returns as guard clauses: `if (!enabled || (strip.isUpdating() && (millis() - last_time < MAX_USERMOD_DELAY))) return;`\n- Debug output: `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` (see previous section)\n\n### Strings and Memory\n- Use `F(\"string\")` for string constants (saves RAM on ESP8266)\n- Use `PSTR()` with `DEBUG_PRINTF_P()` for format strings\n- Avoid `String` in hot paths; acceptable in config/setup code\n- Use `d_malloc()` (DRAM-preferred) / `p_malloc()` (PSRAM-preferred) for allocation\n- No VLAs — use fixed arrays or heap allocation\n- Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation\n\n### ESP32 PSRAM guidelines\n\n- **Check availability**: Use `psramFound() && ESP.getPsramSize() > 0`; never rely on `BOARD_HAS_PSRAM` alone.\n- **DMA**: Not possible on ESP32 classic. S3 can do it in hardware (see next bullet), but descriptors must stay in DRAM and bandwidth is poor while the CPU also uses PSRAM. Keeping DMA buffers in DRAM is usually better for performance.\n   - On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.\n- **Cache**: PSRAM shares the CPU cache with flash (~32 KB data cache by default). Hot working sets below that run near DRAM speed. Large or random-access buffers run at raw PSRAM speed, and big sweeps can evict cached code and slow down everything.\n- **Flash writes**: PSRAM is inaccessible while the flash cache is off. ISRs that run during flash writes must use only DRAM or IRAM data, and they must be declared with `IRAM_ATTR` so instructions don't  need to be fetched from flash while writing.\n- **Performance**: Prefer DRAM (or IRAM) for hot-path data that is *frequently* used. Prefer PSRAM for capacity-oriented buffers where slightly slower access times can be tolerated. Small hot working sets (<~32 KB) in PSRAM can run near DRAM speed, especially when accessed sequentially.\n\n#### Background Info:\n\n- PSRAM access is up to 15× slower than DRAM on ESP32 (dual-SPI bus), 3–10× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM. These values are rough estimates only.\n- PSRAM uses the CPU data cache (typically ~32 KB) that is also shared with flash. Small hot working sets (<~32 KB) run near DRAM speed; larger or random-access buffers fall back to raw PSRAM speed, and big PSRAM sweeps can evict cached flash code and slow down everything else. Keep hot data in DRAM and use PSRAM for large buffers that are accessed sequentially (Espressif's ESP-IDF external RAM guide, \"Restrictions\").\n- Caching read-only files in PSRAM is usually not faster, because flash and PSRAM share the same SPI bus and often run at same speeds. Minor gains are still possible on ESP32-S3 when CONFIG_SOC_PSRAM_DMA_CAPABLE and CONFIG_SPIRAM_MODE_OCT are enabled. Better performance potential is expected on ESP32-P4 with CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT. In special cases, file caching can still help by reducing interrupt contention (for example RMT refill pressure) and preventing visible LED flashing.\n- Consider that ESP32 often crashes when the largest available DRAM chunk gets below 10 KB.\n\n### Preprocessor / Feature Flags\n- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)\n- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.\n- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.\n- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`\n\n### Math Functions\n- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)\n- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`\n- Replace `inoise8` / `inoise16` with `perlin8` / `perlin16`\n\n### Hot-Path Code (Pixel Pipeline)\n- Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))`\n- Cache class members to locals before loops\n- Pre-compute invariants outside loops; use reciprocals to avoid division\n- Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)`\n\n### ESP32 Tasks\n- `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog\n- Do not use `delay()` in effects (FX.cpp) or hot pixel path\n\n#### ESP32 Task Synchronization\n\n- Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour.\n- **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues.\n- Don't use `portMAX_DELAY` when waiting to acquire a mutex - this can lock the task indefinitely. Find a reasonable max waiting time, and handle mutex timeouts gracefully.\n- **Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow, for example when function calls are sequenced within the same loop iteration.\n- Consider using `std::atomic` or RAII scoped guards as alternatives to mutexes, semaphores or queues.\n\n## Web UI Code Style (wled00/data/)\n\n- **Tab indentation** for HTML, JS, and CSS\n- camelCase for JS functions/variables\n- Reuse helpers from `common.js` — do not duplicate utilities\n- After editing, run `npm run build` to regenerate headers\n- **Never edit** `wled00/html_*.h` or `wled00/js_*.h` directly\n\n## Usermods\n \n### Source Code Location\n\n* **In-Tree Usermods** live in `usermods/<name>/` with a `.cpp`, optional `.h`, `library.json`, and `readme.md`.  An example is in `usermods/EXAMPLE`\n* **Out-Of-Tree Usermods** live in a separate public repository. They use the same pattern as in-tree usermods.\n\n* [Official out-of-tree usermods list](https://kno.wled.ge/advanced/community-usermods/#index)\n* [Writing an out-of-tree usermod](https://kno.wled.ge/advanced/custom-features/#writing-a-usermod)\n\n### Usermod Pattern\n\n```cpp\nclass MyUsermod : public Usermod {\n  private:\n    bool enabled = false;\n    static const char _name[];\n  public:\n    void setup() override { /* ... */ }                          // runs once at start-up\n    void loop() override { /* ... */ }                           // runs once per main loop iteration\n    void addToConfig(JsonObject& root) override { /* ... */ }    // create/add persistent settings (usermod settings)\n    bool readFromConfig(JsonObject& root) override { /* ... */ } // read from persistent settings (usermod settings UI)\n    uint16_t getId() override { return USERMOD_ID_MYMOD; }\n    void addToJsonInfo(JsonObject& root) override { /* ... */ }  // Add custom items to the \"info\" page and to /json/info\n    void appendConfigData() override { /* ... */ }               // Customize the settings page: dropdowns, checkboxes, extra text, etc. Buffer size is limited!\n};\nconst char MyUsermod::_name[] PROGMEM = \"MyUsermod\";\nstatic MyUsermod myUsermod;\nREGISTER_USERMOD(myUsermod);\n```\n\nrefer to detailed examples in `usermods/EXAMPLE/`, `usermods/user_fx/` and [in the user documentation for custom features](https://kno.wled.ge/advanced/custom-features/).\n\n- Activate via `custom_usermods = ` in platformio build config. The `usermod_v2_` prefix or `_v2` suffix can be omitted.\n- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)\n- Store repeated strings as `static const char[] PROGMEM`\n- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see below)\n\n### Pin ownership via `pinManager`\n- Before performing any operation on I/O pins, the usermod must allocate its pins from the `pinManager`.\n- I/O pins are allocated via `PinManager::allocatePin(byte gpio, bool output, PinOwner tag)` (or `PinManager::allocateMultiplePins()`), and returned via `PinManager::deallocatePin()` when re-configuring pin numbers.\n- If the usermod does not register a unique usermod ID, use `PinOwner::UM_Unspecified` as PinOwner tag for generic pin use.\n- `PinManager::allocatePin()` will return an error code in case that a pin is already assigned to another WLED function.\n- You can use `-1` = `255` for unconfigured / unassigned pin functions.\n- Check for valid pin numbers with `PinManager::isPinOk(byte gpio, bool output)`; this function knows which GPI(O) pins are possible on your specific MCU.\n\n#### I2C (Wire) and SPI busses\n\n- PIN numbers for I2C and SPI busses are configured globally in the 'Global I2C & SPI' part of the usermod settings page.\n- Usually, a usermod will not start the I2C or SPI units explicitly, since they are already initialised when WLED starts.\n- Don't call `Wire.begin()` in your `Usermod::setup()`, as this may break other usermods that need the I2C bus.\n- Don't allocate `Wire` I2C pins to the usermod - it would cause a pin conflict, as global I2C pins are already allocated to `PinOwner::HW_I2C`.\n- Use this pattern to check for an incomplete I2C setup: `if (i2c_scl<0 || i2c_sda<0) {enabled = false; return;}`.\n\n### Usermod IDs\n\nA unique ID (registered in `wled00/const.h` and overriding `getId()`) is **only required** when a usermod needs one or more of the following:\n\n1. **Inter-usermod communication** — another usermod or an FX effect calls `UsermodManager::lookup(mod_id)` or `UsermodManager::getUMData(..., mod_id)` to find or request data from this specific usermod.\n2. **Pin ownership via `PinManager`** — the usermod allocates GPIO pins through `pinManager`. Pin ownership is tracked by `PinOwner` enum values that map directly to `USERMOD_ID_*` constants (see `wled00/pin_manager.h`). This prevents pin-conflict bugs.\n3. **Identification in JSON info** — `UsermodManager::addToJsonInfo` emits each mod's ID into the `\"um\"` array; a unique ID makes the mod identifiable in that output.\n\nIf none of the above apply, the usermod may omit `getId()` (or return the default `USERMOD_ID_UNSPECIFIED`) and does **not** need an entry in `const.h`.\n\n### Usermod `loop()`\n\n- Called once per main loop iteration. Usermods should simply `return` when `!enabled`.\n- Avoid blocking calls or `delay()` in the usermod `loop()` - delays block the WLED main loop and lead to effect stuttering.\n- Frequency of Usermod `loop()` calls varies with system load:\n    * up to 2000 times/sec with few LEDs and little background activity,\n    * between 20 and 300 times/second during high workload from effects and other usermods,\n    * (worst case) down to 1-3 times/sec during FS activity or when serving lots of network API requests.\n\n### See Also\n* https://kno.wled.ge/advanced/custom-features/#usermods\n* https://kno.wled.ge/advanced/community-usermods/#index\n* generic instructions for Debug Output, as per previous section in this file.\n\n## CI/CD\n\nCI runs on every push/PR via GitHub Actions (`.github/workflows/wled-ci.yml`):\n\n1. `npm test` (web UI build validation)\n2. Firmware compilation for all default environments (~22 targets)\n3. Post-link validation of usermod linkage (`validate_modules.py`)\n\nNo automated linting is configured. Match existing code style in files you edit.\n\n## General Rules\n\n- Important: Repository language is **English**. This applies to source code (including comments), commit messages and any kind of documentation for developer or users.\n- Provide references when making analyses or recommendations. Support factual claims with verifiable citations, references or concrete evidence; **never fabricate citations**.\n- The `docs/` folder is for developer/contributor information (coding conventions, architecture, etc.). User documentation is maintained in the [wled/WLED-Docs](https://github.com/wled/WLED-Docs) repository.\n- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`.\n- Remove dead/unused code — justify or delete it.\n- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor).\n- Important: **Changes to `platformio.ini` require maintainer approval**!\n- PRs should respect `.gitignore` and not upload files like  `platformio_override.ini`. PR authors may add buildenv examples for custom boards into `platformio_override.ini.sample`.\n- When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description.\n\n### Pull Request Expectations\n- **No force-push** on open PRs!\n- Every pull request needs a **clear description of what changed and why**. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable.\n- Best practice: Consider adding screenshots to showcase new features.\n- **Do not prefix the PR title** with `fix:`, `feat:`, `doc:` or other keywords meant to define the type of PR. Use combinations of labels (`bug`, `enhancement`, `effect`, `usermod`, `slop`, etc.) instead.\n- **Do not fix trailing whitespace** or perform pretty-formatting in unrelated code. Such fixes can lead to merge conflicts when other forks want to cherry-pick changes. A separate PR should be used for whitespace optimisation and pretty-formatting.\n- Important: **Fully or partially AI coded PRs MUST be declared clearly** in the description - in addition to comments markers in the source code (see Comments section).\n\n### Security Hardening\n\nWhen writing or reviewing code in `wled00/`, `usermods/`, `wled00/data/`, or `.github/workflows/`,\nconsult `docs/hardening.instructions.md` (concise checklist) and `docs/securecode.instructions.md` (detailed rules with examples).\nThese files define WLED's threat model, trust boundary model, and WLED-specific constraints (no TLS baseline, no UDP authentication for protocol-defined\nmulticast/broadcast, firewall-isolated deployment assumed).\n\n### Attribution for AI-generated code\n\nUsing AI-generated code can obscure the original source or logic of a solution. It may even create hidden licensing risks if sources cannot be verified independently.\n\n- If your agent activity has referenced specific external sources (e.g., science papers, Wikipedia, or documentation) to derive the solution, include these references to allow for independent verification.\n- While standard coding patterns require no further explanation, please describe the logic or architectural reasoning behind any unconventional or complex solutions.\n- Use the required source code comment markers for non-trivial AI generated code blocks (see Comments section).\n- Every non-trivial AI-generated function should have a *brief* comment summarizing what it does. Explain parameters when their names alone are not self-explanatory. Do not rephrase source code; explain non-standard concepts and reasoning.\n\n### Guardrails for Coding Agents\n\nThese apply to any coding agent (Copilot or otherwise) acting on a contributor's behalf in this repository:\n\n- **Confirm before you commit.** Make sure the human understands and can explain the change - especially root-cause claims or refactors - before you open a PR.\n- **Stay scoped, but don't dodge architecture.** Touch only what the task requires, and flag broader fixes to the human instead of silently expanding the change. But don't use \"keep it minimal\" as an excuse to avoid a needed core-level discussion - if the clean fix requires a new core capability or interface, say so and propose it, rather than bolting on a weak hook, monkey-patch, or board-specific workaround just to keep the diff small.\n- **Warning: avoid overconfident root-cause claims.** When drafting issue tickets or PR descriptions, don't state a root cause with confidence unless you've actually traced the code path or reproduced the failure. A plausible-sounding explanation is not the same as a verified one - overconfident claims that later \"dissolve into nothing\" under review waste maintainer time and damage trust in the whole PR. When in doubt, say so explicitly.\n- **State what's tested.** Distinguish \"verified\" from \"should work based on reading the code\" in the PR description.\n- **Disclose AI involvement.** Declare AI assistance in the PR description.\n- **Let issues start non-technical.** When helping draft an issue or feature request, elicit the human's objective or observation in plain language first - don't lead with a proposed code path or fix. Maintainers are better positioned than you to map that objective onto a technical solution.\n\n### Supporting Reviews and Discussions\n\n- **For \"is it worth doing?\" debates** about proposed reliability, safety, or data-integrity mechanisms (CRC checks, backups, power-loss protection): suggest a software **FMEA** (Failure Mode and Effects Analysis).\n  Clarify the main feared events, enumerate failure modes, assess each mitigation's effectiveness per failure mode, note common-cause failures, and rate credibility for the typical WLED use case.\n"},"files":{".github/copilot-instructions.md":"# WLED - ESP32/ESP8266 LED Controller Firmware\n\nWLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs and SPI-based chipsets. The project consists of C++ firmware for microcontrollers and a modern web interface.\n\nAlways reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n> **Note for AI review tools**: sections enclosed in\n> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain\n> contributor reference material. Do **not** use that content as actionable review\n> criteria — treat it as background context only.\n\n## Setup\n\n- Node.js 20+ (see `.nvmrc`)\n- Install dependencies: `npm ci`\n- PlatformIO (required only for firmware compilation): `pip install -r requirements.txt`\n\n## Build and Test\n<!-- HUMAN_ONLY_START -->\n\n| Command | Purpose | Typical Time |\n|---|---|---|\n| `npm run build` | Build web UI → generates `wled00/html_*.h` and `wled00/js_*.h` headers | ~3 s |\n| `npm test` | Run test suite | ~40 s |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |\n| `pio run -e <env>` | Build firmware for a hardware target | 15–20 min |\n\n<!-- HUMAN_ONLY_END -->\n\n- **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).\n- The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.\n- **Build firmware to validate code changes**: `pio run -e esp32dev` — must succeed, never skip this step.\n- Common firmware environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`\n\nFor detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).\n\n### Usermod Guidelines\n\n   - New custom effects can be added into the user_fx usermod. Read the [user_fx documentation](https://github.com/wled/WLED/blob/main/usermods/user_fx/README.md) for guidance.\n   - Other usermods may be based on the [EXAMPLE usermod](https://github.com/wled/WLED/tree/main/usermods/EXAMPLE). Never edit the example, always create a copy!\n   - New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/main/wled00/const.h#L160).\n   - To activate a usermod, a custom build configuration should be used. Add the usermod name to `custom_usermods`.\n\n## Project Structure Overview\n\n### Project Branch / Release Structure\n<!-- HUMAN_ONLY_START -->\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5\n16_x                # maintenance for release 16.x.y\n0_15_x              # maintenance (bugfixes only) for current release 0.15.4\n(tag) v0.14.4       # previous version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n<!-- HUMAN_ONLY_END -->\n\n- ``main``: development trunk (daily/nightly)\n- ``16_x``: maintenance for release 16.x.y\n- ``0_15_x``: bugfixing / maintenance for release 0.15.x\n\n### Repository Structure\n\ntl;dr: \n* Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.\n* Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h` — **never edit or commit**.\n* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.\n* Usermods: `usermods/` (C++, with individual library.json).\n* Contributor docs: `docs/` (coding guidelines, etc).\n\n<!-- HUMAN_ONLY_START -->\nDetailed overview:\n\n```text\nwled00/                 # Main firmware source (C++) \"WLED core\"\n  ├── data/             # Web interface files \n  │   ├── index.htm     # Main UI\n  │   ├── settings*.htm # Settings pages\n  │   └── *.js/*.css    # Frontend resources\n  ├── *.cpp/*.h         # Firmware source files\n  ├── html_*.h          # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)\n  ├── src/              # Modules used by the WLED core (C++)\n  │   ├── fonts/        # Font libraries for scrolling text effect\n  └   └── dependencies/ # Utility functions - some of them have their own licensing terms\nlib/                    # Project specific custom libraries. PlatformIO will compile them to separate static libraries and link them\nplatformio.ini          # Hardware build configuration\n\nplatformio_override.sample.ini # examples for custom build configurations - entries must be copied into platformio_override.ini to use them.\n                               # platformio_override.ini is _not_ stored in the WLED repository!\nusermods/              # User-contributed addons to the WLED core, maintained by individual contributors  (C++, with individual library.json)\npackage.json           # Node.js dependencies and scripts, release identification\npio-scripts/           # Build tools (PlatformIO)\ntools/                 # Build tools (Node.js), partition files, and generic utilities\n  ├── cdata.js         # Web UI build script\n  └── cdata-test.js    # Test suite\ndocs/                  # Contributor docs, coding guidelines\n.github/workflows/     # CI/CD pipelines\n```\n\n<!-- HUMAN_ONLY_END -->\n## General Guidelines\n\n- **Repository language is English.** Suggest translations for non-English content.\n- **Use VS Code with PlatformIO extension** for best development experience.\n- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`.\n- If updating Web UI files in `wled00/data/`, **make use of common functions in `wled00/data/common.js` whenever possible**.\n- **When unsure, say so.** Gather more information rather than guessing.\n- **Acknowledge good patterns** when you see them. Positive feedback always helps.\n- **Provide references** when making analyses or recommendations. Base them on the correct branch or PR.\n- **Highlight user-visible breaking changes and ripple effects**. Ask for confirmation that these were introduced intentionally.\n- **Unused / dead code must be justified or removed**. This helps to keep the codebase clean, maintainable and readable.\n- **Verify feature-flag names.** Every `WLED_ENABLE_*` / `WLED_DISABLE_*` flag must exactly match one of the names below — misspellings are silently ignored by the preprocessor (e.g. `WLED_IR_DISABLE` instead of `WLED_DISABLE_INFRARED`), causing silent build variations. Flag unrecognised names as likely typos and suggest the correct spelling.\n  <br>**`WLED_DISABLE_*`**: `2D`, `ADALIGHT`, `ALEXA`, `BROWNOUT_DET`, `ESPNOW`, `FILESYSTEM`, `HUESYNC`, `IMPROV_WIFISCAN`, `INFRARED`, `LOXONE`, `MQTT`, `OTA`, `PARTICLESYSTEM1D`, `PARTICLESYSTEM2D`, `PIXELFORGE`, `WEBSOCKETS`\n  <br>**`WLED_ENABLE_*`**: `ADALIGHT`, `AOTA`, `DMX`, `DMX_INPUT`, `DMX_OUTPUT`, `FS_EDITOR`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `LOXONE`, `MQTT`, `PIXART`, `PXMAGIC`, `USERMOD_PAGE`, `WEBSOCKETS`, `WPA_ENTERPRISE`\n- **C++ formatting available**: `clang-format` is installed but not in CI\n- No automated linting is configured — match existing code style in files you edit. \n\nRefer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.\n\n### Pull Request Expectations\n\n- **No force-push on open PRs.** Once a pull request is open and being reviewed, do not force-push (`git push --force`) to the branch. Force-pushing rewrites history that reviewers may have already commented on, making it impossible to track incremental changes. Use regular commits or `git merge` to incorporate feedback; the branch will be squash-merged when it is accepted.\n- **Modifications to ``platformio.ini`` MUST be approved explicitly** by a *maintainer* or *WLED organisation Member*. Modifications to the global build environment may break github action builds. Always flag them.\n- **Document your changes in the PR.** Every pull request should include a clear description of *what* changed and *why*. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable. Provide screenshots to showcase new features.\n","AGENTS.md":"# AGENTS.md — WLED AI Coding Agent & AI Code Review Reference\n\nWLED is C++ firmware for ESP32/ESP8266 microcontrollers controlling addressable LEDs,\nwith a web UI (HTML/JS/CSS). Built with PlatformIO (Arduino framework) and Node.js tooling.\n\nSee also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.md`,\n`docs/cpp.instructions.md`, `docs/web.instructions.md`, `docs/cicd.instructions.md`,\n`docs/hardening.instructions.md`, `docs/securecode.instructions.md`.\n\nAlways reference these instructions - including coding guidelines in `docs/` - first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n## Required User Agreement\n\nBefore editing files, committing, pushing, or opening a PR, show the human you're working with this notice and wait for an explicit yes in the current conversation:\n\n> WLED welcomes AI-assisted contributions, but not unreviewed ones. I'll only edit, commit, push, or open a PR once you confirm you'll review, understand, and take responsibility for the result. Do you agree?\n\nA generic \"go ahead\" or silence doesn't count - get a clear yes. Until then, you may read files and answer questions, but do not modify files in the repository or make changes on GitHub.\n\n### No agent-posted replies or issues\n\nReviewer time is limited, and checking long AI-generated explanations for reasoning errors and hallucinations is costly. Even after the user agreement is confirmed:\n\n- **Never post** replies to reviewer comments or discussions, and **never create issue tickets**, on the user's behalf. You may help the user draft the text locally; the human reviews it and posts it themselves.\n- Creating or updating pull requests remains allowed (after the user agreement is confirmed). Keep PR descriptions concise and factual, and include a summary written by the user in their own words, leading with what they want to achieve or what bothers them in plain language, as described in [CONTRIBUTING.md](CONTRIBUTING.md#opening-issues--feature-requests).\n\n### Attribution for PR Descriptions, Comments and Replies\n\n- When creating PR descriptions or suggesting responses to comments, always add this as the very last line: _You are interacting with an AI system_\n- - Your harness may add this trailer automatically. Do not add a duplicate.\n\n### Attribution for Commits\n\nFor every commit you create in this repository, ensure the commit message includes a `Co-authored-by:` trailer identifying the large language model and agent platform:\n\n```text\nCo-authored-by: MODEL NAME/VERSION (AGENT PLATFORM) <COAUTHOR EMAIL>\n```\n- Your harness may add this trailer automatically. Do not add a duplicate; ensure the resulting trailer identifies both the model and platform.\n- Replace the placeholders with the model name/version reported by your runtime and the agent platform (e.g. Codex, Claude Code). Write `unknown` for unavailable model details; do not guess.\n- Use the platform's configured co-author email, or `agent@example.invalid` if none is available.\n- Include exactly one trailer per distinct platform/model pair that contributed to the commit, after a blank line at the end of the commit message. Preserve existing human co-author trailers.\n\n## Build Commands\n\nNote: also read `AGENTS.local.md` if present (gitignored personal learnings file; it supplements this document).\n\n| Command | Purpose | Timeout |\n|---|---|---|\n| `npm ci` | Install Node.js deps (required first) | 30s |\n| `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s |\n| `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous |\n| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 5 min |\n| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 5 min |\n\n**Always run `npm ci && npm run build` before `pio run`.** The web UI build generates\nrequired C headers for firmware compilation.\n\n### Running a Single Test\n\nTests use Node.js built-in test runner (`node:test`). The single test file is\n`tools/cdata-test.js`. Run it with:\n\n```bash\nnpm test                   # runs all tests via `node --test`\nnode --test tools/cdata-test.js  # run just that file directly\n```\n\nThere are no C++ unit tests. Firmware is validated by successful compilation across\ntarget environments. Always build after code changes: `pio run -e esp32dev`.\n\n### Common Firmware Environments\n\n`esp32dev`, `nodemcuv2`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`, `lolin_s2_mini`\n\n### Recovery / Troubleshooting\n\n```bash\nnpm run build -- -f              # force web UI rebuild\nrm -f wled00/html_*.h wled00/js_*.h && npm run build  # clean + rebuild UI\npio run --target clean           # clean PlatformIO build artifacts\nrm -rf node_modules && npm ci    # reinstall Node.js deps\n```\n\n## Project Structure\n\n```text\nwled00/              # Main firmware source (C++)\n  data/              # Web UI source (HTML/JS/CSS) — tabs for indentation\n  html_*.h, js_*.h   # Auto-generated (NEVER edit or commit)\n  src/               # Sub-modules: fonts, bundled dependencies (ArduinoJSON)\nusermods/            # Community usermods (each has library.json + .cpp/.h)\nplatformio.ini       # Build configuration and environments\npio-scripts/         # PlatformIO build scripts (Python)\ntools/               # Node.js build tools (cdata.js) and tests\ndocs/                # Coding convention docs\n.github/workflows/   # CI/CD (GitHub Actions)\n```\n\n### Branch / Release Structure\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5. Target branch for PRs.\n16_x                # maintenance for release 16.0.x\n0_15_x              # maintenance (bugfixes only) for previous release 0.15.x\n(tag) v0.14.4       # old version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n\n## C++ Code Style (wled00/, usermods/)\n\n### General\n\n- Follow the existing style in the file you are editing\n- Avoid unexplained \"magic numbers\". For repeated numbers that have the same meaning, prefer named constants (`constexpr`) or C-style `#define` constants.\n- If possible, **use `static` for local (C-style) variables and functions** to keep the global namespace clean\n- When moving global items to another scope, do not leave comments such as `// lastMqttReconnectAttempt is now private to wled.cpp - see there`. These comments create technical debt for maintenance and will inevitably become out of date. As a rule of thumb: **describe the code as it is, not the code as it was**.\n- Describe logic/reasoning behind non-standard patterns and complex solutions.\n\n### Formatting\n- **2-space indentation** (no tabs in C++ files)\n- K&R brace style preferred (opening brace on same line)\n- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`\n- Space after keywords (`if (...)`, `for (...)`), no space before function parens (`doStuff(a)`)\n- Prefer compact function parameter lists and `if` conditions; avoid putting each parameter or part of a condition on its own line. Wrap very long parameter lists when needed.\n- No enforced line-length limit\n\n### Comments\n- `//` for inline (always space after), `/* */` for block comments\n- Important: AI-generated source code blocks **must be mark with `// AI: below section was generated by an AI` / `// AI: end`**\n\n### Naming Conventions\n| Kind | Convention | Examples |\n|---|---|---|\n| Functions, variables | camelCase | `setRandomColor()`, `effectCurrent` |\n| Classes, structs | PascalCase | `BusConfig`, `UsermodTemperature` |\n| Macros, constants | UPPER_CASE | `WLED_MAX_USERMODS`, `FX_MODE_STATIC` |\n| Private members | _camelCase | `_type`, `_bri`, `_len` |\n| Enum values | PascalCase | `PinOwner::BusDigital` |\n\n### Includes\n- Include `\"wled.h\"` as the primary project header\n- Project headers first, then platform/Arduino, then third-party\n- Platform-conditional includes wrapped in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`\n\n### Debug Output\n\n- Use `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` / `DEBUG_PRINT()` for developer diagnostics and debug output (compiled out unless -D WLED_DEBUG)\n- wled00/wled.h defines these macros. They compile to no output when debug output is disabled, keeping normal serial interfaces clean.\n- Do not use direct `Serial.print()`, `Serial.println()`, `Serial.printf()`, or `Serial.write()` calls unless there is a technical justification for not using DEBUG_... macros.\n\nSee docs/cpp.instructions.md section Error Handling for more information.\n\n### Types and Const\n- Prefer `const &` for read-only function parameters\n- Mark getter/query methods `const`; use `static` for methods not accessing instance state\n- Prefer `constexpr` over `#define` for compile-time constants when possible\n- Use `static_assert` over `#if ... #error`\n- Use `uint_fast16_t` / `uint_fast8_t` in hot-path code\n\n### Error Handling\n- **No C++ exceptions** — some builds disable them\n- Use return codes (`false`, `-1`) and global flags (`errorFlag = ERR_LOW_MEM`)\n- Use early returns as guard clauses: `if (!enabled || (strip.isUpdating() && (millis() - last_time < MAX_USERMOD_DELAY))) return;`\n- Debug output: `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` (see previous section)\n\n### Strings and Memory\n- Use `F(\"string\")` for string constants (saves RAM on ESP8266)\n- Use `PSTR()` with `DEBUG_PRINTF_P()` for format strings\n- Avoid `String` in hot paths; acceptable in config/setup code\n- Use `d_malloc()` (DRAM-preferred) / `p_malloc()` (PSRAM-preferred) for allocation\n- No VLAs — use fixed arrays or heap allocation\n- Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation\n\n### ESP32 PSRAM guidelines\n\n- **Check availability**: Use `psramFound() && ESP.getPsramSize() > 0`; never rely on `BOARD_HAS_PSRAM` alone.\n- **DMA**: Not possible on ESP32 classic. S3 can do it in hardware (see next bullet), but descriptors must stay in DRAM and bandwidth is poor while the CPU also uses PSRAM. Keeping DMA buffers in DRAM is usually better for performance.\n   - On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.\n- **Cache**: PSRAM shares the CPU cache with flash (~32 KB data cache by default). Hot working sets below that run near DRAM speed. Large or random-access buffers run at raw PSRAM speed, and big sweeps can evict cached code and slow down everything.\n- **Flash writes**: PSRAM is inaccessible while the flash cache is off. ISRs that run during flash writes must use only DRAM or IRAM data, and they must be declared with `IRAM_ATTR` so instructions don't  need to be fetched from flash while writing.\n- **Performance**: Prefer DRAM (or IRAM) for hot-path data that is *frequently* used. Prefer PSRAM for capacity-oriented buffers where slightly slower access times can be tolerated. Small hot working sets (<~32 KB) in PSRAM can run near DRAM speed, especially when accessed sequentially.\n\n#### Background Info:\n\n- PSRAM access is up to 15× slower than DRAM on ESP32 (dual-SPI bus), 3–10× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM. These values are rough estimates only.\n- PSRAM uses the CPU data cache (typically ~32 KB) that is also shared with flash. Small hot working sets (<~32 KB) run near DRAM speed; larger or random-access buffers fall back to raw PSRAM speed, and big PSRAM sweeps can evict cached flash code and slow down everything else. Keep hot data in DRAM and use PSRAM for large buffers that are accessed sequentially (Espressif's ESP-IDF external RAM guide, \"Restrictions\").\n- Caching read-only files in PSRAM is usually not faster, because flash and PSRAM share the same SPI bus and often run at same speeds. Minor gains are still possible on ESP32-S3 when CONFIG_SOC_PSRAM_DMA_CAPABLE and CONFIG_SPIRAM_MODE_OCT are enabled. Better performance potential is expected on ESP32-P4 with CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT. In special cases, file caching can still help by reducing interrupt contention (for example RMT refill pressure) and preventing visible LED flashing.\n- Consider that ESP32 often crashes when the largest available DRAM chunk gets below 10 KB.\n\n### Preprocessor / Feature Flags\n- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)\n- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.\n- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.\n- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`\n\n### Math Functions\n- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)\n- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`\n- Replace `inoise8` / `inoise16` with `perlin8` / `perlin16`\n\n### Hot-Path Code (Pixel Pipeline)\n- Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))`\n- Cache class members to locals before loops\n- Pre-compute invariants outside loops; use reciprocals to avoid division\n- Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)`\n\n### ESP32 Tasks\n- `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog\n- Do not use `delay()` in effects (FX.cpp) or hot pixel path\n\n#### ESP32 Task Synchronization\n\n- Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour.\n- **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues.\n- Don't use `portMAX_DELAY` when waiting to acquire a mutex - this can lock the task indefinitely. Find a reasonable max waiting time, and handle mutex timeouts gracefully.\n- **Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow, for example when function calls are sequenced within the same loop iteration.\n- Consider using `std::atomic` or RAII scoped guards as alternatives to mutexes, semaphores or queues.\n\n## Web UI Code Style (wled00/data/)\n\n- **Tab indentation** for HTML, JS, and CSS\n- camelCase for JS functions/variables\n- Reuse helpers from `common.js` — do not duplicate utilities\n- After editing, run `npm run build` to regenerate headers\n- **Never edit** `wled00/html_*.h` or `wled00/js_*.h` directly\n\n## Usermods\n \n### Source Code Location\n\n* **In-Tree Usermods** live in `usermods/<name>/` with a `.cpp`, optional `.h`, `library.json`, and `readme.md`.  An example is in `usermods/EXAMPLE`\n* **Out-Of-Tree Usermods** live in a separate public repository. They use the same pattern as in-tree usermods.\n\n* [Official out-of-tree usermods list](https://kno.wled.ge/advanced/community-usermods/#index)\n* [Writing an out-of-tree usermod](https://kno.wled.ge/advanced/custom-features/#writing-a-usermod)\n\n### Usermod Pattern\n\n```cpp\nclass MyUsermod : public Usermod {\n  private:\n    bool enabled = false;\n    static const char _name[];\n  public:\n    void setup() override { /* ... */ }                          // runs once at start-up\n    void loop() override { /* ... */ }                           // runs once per main loop iteration\n    void addToConfig(JsonObject& root) override { /* ... */ }    // create/add persistent settings (usermod settings)\n    bool readFromConfig(JsonObject& root) override { /* ... */ } // read from persistent settings (usermod settings UI)\n    uint16_t getId() override { return USERMOD_ID_MYMOD; }\n    void addToJsonInfo(JsonObject& root) override { /* ... */ }  // Add custom items to the \"info\" page and to /json/info\n    void appendConfigData() override { /* ... */ }               // Customize the settings page: dropdowns, checkboxes, extra text, etc. Buffer size is limited!\n};\nconst char MyUsermod::_name[] PROGMEM = \"MyUsermod\";\nstatic MyUsermod myUsermod;\nREGISTER_USERMOD(myUsermod);\n```\n\nrefer to detailed examples in `usermods/EXAMPLE/`, `usermods/user_fx/` and [in the user documentation for custom features](https://kno.wled.ge/advanced/custom-features/).\n\n- Activate via `custom_usermods = ` in platformio build config. The `usermod_v2_` prefix or `_v2` suffix can be omitted.\n- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)\n- Store repeated strings as `static const char[] PROGMEM`\n- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see below)\n\n### Pin ownership via `pinManager`\n- Before performing any operation on I/O pins, the usermod must allocate its pins from the `pinManager`.\n- I/O pins are allocated via `PinManager::allocatePin(byte gpio, bool output, PinOwner tag)` (or `PinManager::allocateMultiplePins()`), and returned via `PinManager::deallocatePin()` when re-configuring pin numbers.\n- If the usermod does not register a unique usermod ID, use `PinOwner::UM_Unspecified` as PinOwner tag for generic pin use.\n- `PinManager::allocatePin()` will return an error code in case that a pin is already assigned to another WLED function.\n- You can use `-1` = `255` for unconfigured / unassigned pin functions.\n- Check for valid pin numbers with `PinManager::isPinOk(byte gpio, bool output)`; this function knows which GPI(O) pins are possible on your specific MCU.\n\n#### I2C (Wire) and SPI busses\n\n- PIN numbers for I2C and SPI busses are configured globally in the 'Global I2C & SPI' part of the usermod settings page.\n- Usually, a usermod will not start the I2C or SPI units explicitly, since they are already initialised when WLED starts.\n- Don't call `Wire.begin()` in your `Usermod::setup()`, as this may break other usermods that need the I2C bus.\n- Don't allocate `Wire` I2C pins to the usermod - it would cause a pin conflict, as global I2C pins are already allocated to `PinOwner::HW_I2C`.\n- Use this pattern to check for an incomplete I2C setup: `if (i2c_scl<0 || i2c_sda<0) {enabled = false; return;}`.\n\n### Usermod IDs\n\nA unique ID (registered in `wled00/const.h` and overriding `getId()`) is **only required** when a usermod needs one or more of the following:\n\n1. **Inter-usermod communication** — another usermod or an FX effect calls `UsermodManager::lookup(mod_id)` or `UsermodManager::getUMData(..., mod_id)` to find or request data from this specific usermod.\n2. **Pin ownership via `PinManager`** — the usermod allocates GPIO pins through `pinManager`. Pin ownership is tracked by `PinOwner` enum values that map directly to `USERMOD_ID_*` constants (see `wled00/pin_manager.h`). This prevents pin-conflict bugs.\n3. **Identification in JSON info** — `UsermodManager::addToJsonInfo` emits each mod's ID into the `\"um\"` array; a unique ID makes the mod identifiable in that output.\n\nIf none of the above apply, the usermod may omit `getId()` (or return the default `USERMOD_ID_UNSPECIFIED`) and does **not** need an entry in `const.h`.\n\n### Usermod `loop()`\n\n- Called once per main loop iteration. Usermods should simply `return` when `!enabled`.\n- Avoid blocking calls or `delay()` in the usermod `loop()` - delays block the WLED main loop and lead to effect stuttering.\n- Frequency of Usermod `loop()` calls varies with system load:\n    * up to 2000 times/sec with few LEDs and little background activity,\n    * between 20 and 300 times/second during high workload from effects and other usermods,\n    * (worst case) down to 1-3 times/sec during FS activity or when serving lots of network API requests.\n\n### See Also\n* https://kno.wled.ge/advanced/custom-features/#usermods\n* https://kno.wled.ge/advanced/community-usermods/#index\n* generic instructions for Debug Output, as per previous section in this file.\n\n## CI/CD\n\nCI runs on every push/PR via GitHub Actions (`.github/workflows/wled-ci.yml`):\n\n1. `npm test` (web UI build validation)\n2. Firmware compilation for all default environments (~22 targets)\n3. Post-link validation of usermod linkage (`validate_modules.py`)\n\nNo automated linting is configured. Match existing code style in files you edit.\n\n## General Rules\n\n- Important: Repository language is **English**. This applies to source code (including comments), commit messages and any kind of documentation for developer or users.\n- Provide references when making analyses or recommendations. Support factual claims with verifiable citations, references or concrete evidence; **never fabricate citations**.\n- The `docs/` folder is for developer/contributor information (coding conventions, architecture, etc.). User documentation is maintained in the [wled/WLED-Docs](https://github.com/wled/WLED-Docs) repository.\n- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`.\n- Remove dead/unused code — justify or delete it.\n- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor).\n- Important: **Changes to `platformio.ini` require maintainer approval**!\n- PRs should respect `.gitignore` and not upload files like  `platformio_override.ini`. PR authors may add buildenv examples for custom boards into `platformio_override.ini.sample`.\n- When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description.\n\n### Pull Request Expectations\n- **No force-push** on open PRs!\n- Every pull request needs a **clear description of what changed and why**. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable.\n- Best practice: Consider adding screenshots to showcase new features.\n- **Do not prefix the PR title** with `fix:`, `feat:`, `doc:` or other keywords meant to define the type of PR. Use combinations of labels (`bug`, `enhancement`, `effect`, `usermod`, `slop`, etc.) instead.\n- **Do not fix trailing whitespace** or perform pretty-formatting in unrelated code. Such fixes can lead to merge conflicts when other forks want to cherry-pick changes. A separate PR should be used for whitespace optimisation and pretty-formatting.\n- Important: **Fully or partially AI coded PRs MUST be declared clearly** in the description - in addition to comments markers in the source code (see Comments section).\n\n### Security Hardening\n\nWhen writing or reviewing code in `wled00/`, `usermods/`, `wled00/data/`, or `.github/workflows/`,\nconsult `docs/hardening.instructions.md` (concise checklist) and `docs/securecode.instructions.md` (detailed rules with examples).\nThese files define WLED's threat model, trust boundary model, and WLED-specific constraints (no TLS baseline, no UDP authentication for protocol-defined\nmulticast/broadcast, firewall-isolated deployment assumed).\n\n### Attribution for AI-generated code\n\nUsing AI-generated code can obscure the original source or logic of a solution. It may even create hidden licensing risks if sources cannot be verified independently.\n\n- If your agent activity has referenced specific external sources (e.g., science papers, Wikipedia, or documentation) to derive the solution, include these references to allow for independent verification.\n- While standard coding patterns require no further explanation, please describe the logic or architectural reasoning behind any unconventional or complex solutions.\n- Use the required source code comment markers for non-trivial AI generated code blocks (see Comments section).\n- Every non-trivial AI-generated function should have a *brief* comment summarizing what it does. Explain parameters when their names alone are not self-explanatory. Do not rephrase source code; explain non-standard concepts and reasoning.\n\n### Guardrails for Coding Agents\n\nThese apply to any coding agent (Copilot or otherwise) acting on a contributor's behalf in this repository:\n\n- **Confirm before you commit.** Make sure the human understands and can explain the change - especially root-cause claims or refactors - before you open a PR.\n- **Stay scoped, but don't dodge architecture.** Touch only what the task requires, and flag broader fixes to the human instead of silently expanding the change. But don't use \"keep it minimal\" as an excuse to avoid a needed core-level discussion - if the clean fix requires a new core capability or interface, say so and propose it, rather than bolting on a weak hook, monkey-patch, or board-specific workaround just to keep the diff small.\n- **Warning: avoid overconfident root-cause claims.** When drafting issue tickets or PR descriptions, don't state a root cause with confidence unless you've actually traced the code path or reproduced the failure. A plausible-sounding explanation is not the same as a verified one - overconfident claims that later \"dissolve into nothing\" under review waste maintainer time and damage trust in the whole PR. When in doubt, say so explicitly.\n- **State what's tested.** Distinguish \"verified\" from \"should work based on reading the code\" in the PR description.\n- **Disclose AI involvement.** Declare AI assistance in the PR description.\n- **Let issues start non-technical.** When helping draft an issue or feature request, elicit the human's objective or observation in plain language first - don't lead with a proposed code path or fix. Maintainers are better positioned than you to map that objective onto a technical solution.\n\n### Supporting Reviews and Discussions\n\n- **For \"is it worth doing?\" debates** about proposed reliability, safety, or data-integrity mechanisms (CRC checks, backups, power-loss protection): suggest a software **FMEA** (Failure Mode and Effects Analysis).\n  Clarify the main feared events, enumerate failure modes, assess each mitigation's effectiveness per failure mode, note common-cause failures, and rate credibility for the typical WLED use case.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/wled/WLED/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md — WLED AI Coding Agent & AI Code Review Reference\n\nWLED is C++ firmware for ESP32/ESP8266 microcontrollers controlling addressable LEDs,\nwith a web UI (HTML/JS/CSS). Built with PlatformIO (Arduino framework) and Node.js tooling.\n\nSee also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.md`,\n`docs/cpp.instructions.md`, `docs/web.instructions.md`, `docs/cicd.instructions.md`,\n`docs/hardening.instructions.md`, `docs/securecode.instructions.md`.\n\nAlways reference these instructions - including coding guidelines in `docs/` - first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n## Required User Agreement\n\nBefore editing files, committing, pushing, or opening a PR, show the human you're working with this notice and wait for an explicit yes in the current conversation:\n\n> WLED welcomes AI-assisted contributions, but not unreviewed ones. I'll only edit, commit, push, or open a PR once you confirm you'll review, understand, and take responsibility for the result. Do you agree?\n\nA generic \"go ahead\" or silence doesn't count - get a clear yes. Until then, you may read files and answer questions, but do not modify files in the repository or make changes on GitHub.\n\n### No agent-posted replies or issues\n\nReviewer time is limited, and checking long AI-generated explanations for reasoning errors and hallucinations is costly. Even after the user agreement is confirmed:\n\n- **Never post** replies to reviewer comments or discussions, and **never create issue tickets**, on the user's behalf. You may help the user draft the text locally; the human reviews it and posts it themselves.\n- Creating or updating pull requests remains allowed (after the user agreement is confirmed). Keep PR descriptions concise and factual, and include a summary written by the user in their own words, leading with what they want to achieve or what bothers them in plain language, as described in [CONTRIBUTING.md](CONTRIBUTING.md#opening-issues--feature-requests).\n\n### Attribution for PR Descriptions, Comments and Replies\n\n- When creating PR descriptions or suggesting responses to comments, always add this as the very last line: _You are interacting with an AI system_\n- - Your harness may add this trailer automatically. Do not add a duplicate.\n\n### Attribution for Commits\n\nFor every commit you create in this repository, ensure the commit message includes a `Co-authored-by:` trailer identifying the large language model and agent platform:\n\n```text\nCo-authored-by: MODEL NAME/VERSION (AGENT PLATFORM) <COAUTHOR EMAIL>\n```\n- Your harness may add this trailer automatically. Do not add a duplicate; ensure the resulting trailer identifies both the model and platform.\n- Replace the placeholders with the model name/version reported by your runtime and the agent platform (e.g. Codex, Claude Code). Write `unknown` for unavailable model details; do not guess.\n- Use the platform's configured co-author email, or `agent@example.invalid` if none is available.\n- Include exactly one trailer per distinct platform/model pair that contributed to the commit, after a blank line at the end of the commit message. Preserve existing human co-author trailers.\n\n## Build Commands\n\nNote: also read `AGENTS.local.md` if present (gitignored personal learnings file; it supplements this document).\n\n| Command | Purpose | Timeout |\n|---|---|---|\n| `npm ci` | Install Node.js deps (required first) | 30s |\n| `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s |\n| `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous |\n| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 5 min |\n| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 5 min |\n\n**Always run `npm ci && npm run build` before `pio run`.** The web UI build generates\nrequired C headers for firmware compilation.\n\n### Running a Single Test\n\nTests use Node.js built-in test runner (`node:test`). The single test file is\n`tools/cdata-test.js`. Run it with:\n\n```bash\nnpm test                   # runs all tests via `node --test`\nnode --test tools/cdata-test.js  # run just that file directly\n```\n\nThere are no C++ unit tests. Firmware is validated by successful compilation across\ntarget environments. Always build after code changes: `pio run -e esp32dev`.\n\n### Common Firmware Environments\n\n`esp32dev`, `nodemcuv2`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`, `lolin_s2_mini`\n\n### Recovery / Troubleshooting\n\n```bash\nnpm run build -- -f              # force web UI rebuild\nrm -f wled00/html_*.h wled00/js_*.h && npm run build  # clean + rebuild UI\npio run --target clean           # clean PlatformIO build artifacts\nrm -rf node_modules && npm ci    # reinstall Node.js deps\n```\n\n## Project Structure\n\n```text\nwled00/              # Main firmware source (C++)\n  data/              # Web UI source (HTML/JS/CSS) — tabs for indentation\n  html_*.h, js_*.h   # Auto-generated (NEVER edit or commit)\n  src/               # Sub-modules: fonts, bundled dependencies (ArduinoJSON)\nusermods/            # Community usermods (each has library.json + .cpp/.h)\nplatformio.ini       # Build configuration and environments\npio-scripts/         # PlatformIO build scripts (Python)\ntools/               # Node.js build tools (cdata.js) and tests\ndocs/                # Coding convention docs\n.github/workflows/   # CI/CD (GitHub Actions)\n```\n\n### Branch / Release Structure\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5. Target branch for PRs.\n16_x                # maintenance for release 16.0.x\n0_15_x              # maintenance (bugfixes only) for previous release 0.15.x\n(tag) v0.14.4       # old version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n\n## C++ Code Style (wled00/, usermods/)\n\n### General\n\n- Follow the existing style in the file you are editing\n- Avoid unexplained \"magic numbers\". For repeated numbers that have the same meaning, prefer named constants (`constexpr`) or C-style `#define` constants.\n- If possible, **use `static` for local (C-style) variables and functions** to keep the global namespace clean\n- When moving global items to another scope, do not leave comments such as `// lastMqttReconnectAttempt is now private to wled.cpp - see there`. These comments create technical debt for maintenance and will inevitably become out of date. As a rule of thumb: **describe the code as it is, not the code as it was**.\n- Describe logic/reasoning behind non-standard patterns and complex solutions.\n\n### Formatting\n- **2-space indentation** (no tabs in C++ files)\n- K&R brace style preferred (opening brace on same line)\n- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`\n- Space after keywords (`if (...)`, `for (...)`), no space before function parens (`doStuff(a)`)\n- Prefer compact function parameter lists and `if` conditions; avoid putting each parameter or part of a condition on its own line. Wrap very long parameter lists when needed.\n- No enforced line-length limit\n\n### Comments\n- `//` for inline (always space after), `/* */` for block comments\n- Important: AI-generated source code blocks **must be mark with `// AI: below section was generated by an AI` / `// AI: end`**\n\n### Naming Conventions\n| Kind | Convention | Examples |\n|---|---|---|\n| Functions, variables | camelCase | `setRandomColor()`, `effectCurrent` |\n| Classes, structs | PascalCase | `BusConfig`, `UsermodTemperature` |\n| Macros, constants | UPPER_CASE | `WLED_MAX_USERMODS`, `FX_MODE_STATIC` |\n| Private members | _camelCase | `_type`, `_bri`, `_len` |\n| Enum values | PascalCase | `PinOwner::BusDigital` |\n\n### Includes\n- Include `\"wled.h\"` as the primary project header\n- Project headers first, then platform/Arduino, then third-party\n- Platform-conditional includes wrapped in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`\n\n### Debug Output\n\n- Use `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` / `DEBUG_PRINT()` for developer diagnostics and debug output (compiled out unless -D WLED_DEBUG)\n- wled00/wled.h defines these macros. They compile to no output when debug output is disabled, keeping normal serial interfaces clean.\n- Do not use direct `Serial.print()`, `Serial.println()`, `Serial.printf()`, or `Serial.write()` calls unless there is a technical justification for not using DEBUG_... macros.\n\nSee docs/cpp.instructions.md section Error Handling for more information.\n\n### Types and Const\n- Prefer `const &` for read-only function parameters\n- Mark getter/query methods `const`; use `static` for methods not accessing instance state\n- Prefer `constexpr` over `#define` for compile-time constants when possible\n- Use `static_assert` over `#if ... #error`\n- Use `uint_fast16_t` / `uint_fast8_t` in hot-path code\n\n### Error Handling\n- **No C++ exceptions** — some builds disable them\n- Use return codes (`false`, `-1`) and global flags (`errorFlag = ERR_LOW_MEM`)\n- Use early returns as guard clauses: `if (!enabled || (strip.isUpdating() && (millis() - last_time < MAX_USERMOD_DELAY))) return;`\n- Debug output: `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` (see previous section)\n\n### Strings and Memory\n- Use `F(\"string\")` for string constants (saves RAM on ESP8266)\n- Use `PSTR()` with `DEBUG_PRINTF_P()` for format strings\n- Avoid `String` in hot paths; acceptable in config/setup code\n- Use `d_malloc()` (DRAM-preferred) / `p_malloc()` (PSRAM-preferred) for allocation\n- No VLAs — use fixed arrays or heap allocation\n- Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation\n\n### ESP32 PSRAM guidelines\n\n- **Check availability**: Use `psramFound() && ESP.getPsramSize() > 0`; never rely on `BOARD_HAS_PSRAM` alone.\n- **DMA**: Not possible on ESP32 classic. S3 can do it in hardware (see next bullet), but descriptors must stay in DRAM and bandwidth is poor while the CPU also uses PSRAM. Keeping DMA buffers in DRAM is usually better for performance.\n   - On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.\n- **Cache**: PSRAM shares the CPU cache with flash (~32 KB data cache by default). Hot working sets below that run near DRAM speed. Large or random-access buffers run at raw PSRAM speed, and big sweeps can evict cached code and slow down everything.\n- **Flash writes**: PSRAM is inaccessible while the flash cache is off. ISRs that run during flash writes must use only DRAM or IRAM data, and they must be declared with `IRAM_ATTR` so instructions don't  need to be fetched from flash while writing.\n- **Performance**: Prefer DRAM (or IRAM) for hot-path data that is *frequently* used. Prefer PSRAM for capacity-oriented buffers where slightly slower access times can be tolerated. Small hot working sets (<~32 KB) in PSRAM can run near DRAM speed, especially when accessed sequentially.\n\n#### Background Info:\n\n- PSRAM access is up to 15× slower than DRAM on ESP32 (dual-SPI bus), 3–10× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM. These values are rough estimates only.\n- PSRAM uses the CPU data cache (typically ~32 KB) that is also shared with flash. Small hot working sets (<~32 KB) run near DRAM speed; larger or random-access buffers fall back to raw PSRAM speed, and big PSRAM sweeps can evict cached flash code and slow down everything else. Keep hot data in DRAM and use PSRAM for large buffers that are accessed sequentially (Espressif's ESP-IDF external RAM guide, \"Restrictions\").\n- Caching read-only files in PSRAM is usually not faster, because flash and PSRAM share the same SPI bus and often run at same speeds. Minor gains are still possible on ESP32-S3 when CONFIG_SOC_PSRAM_DMA_CAPABLE and CONFIG_SPIRAM_MODE_OCT are enabled. Better performance potential is expected on ESP32-P4 with CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT. In special cases, file caching can still help by reducing interrupt contention (for example RMT refill pressure) and preventing visible LED flashing.\n- Consider that ESP32 often crashes when the largest available DRAM chunk gets below 10 KB.\n\n### Preprocessor / Feature Flags\n- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)\n- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.\n- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.\n- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`\n\n### Math Functions\n- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)\n- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`\n- Replace `inoise8` / `inoise16` with `perlin8` / `perlin16`\n\n### Hot-Path Code (Pixel Pipeline)\n- Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))`\n- Cache class members to locals before loops\n- Pre-compute invariants outside loops; use reciprocals to avoid division\n- Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)`\n\n### ESP32 Tasks\n- `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog\n- Do not use `delay()` in effects (FX.cpp) or hot pixel path\n\n#### ESP32 Task Synchronization\n\n- Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour.\n- **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues.\n- Don't use `portMAX_DELAY` when waiting to acquire a mutex - this can lock the task indefinitely. Find a reasonable max waiting time, and handle mutex timeouts gracefully.\n- **Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow, for example when function calls are sequenced within the same loop iteration.\n- Consider using `std::atomic` or RAII scoped guards as alternatives to mutexes, semaphores or queues.\n\n## Web UI Code Style (wled00/data/)\n\n- **Tab indentation** for HTML, JS, and CSS\n- camelCase for JS functions/variables\n- Reuse helpers from `common.js` — do not duplicate utilities\n- After editing, run `npm run build` to regenerate headers\n- **Never edit** `wled00/html_*.h` or `wled00/js_*.h` directly\n\n## Usermods\n \n### Source Code Location\n\n* **In-Tree Usermods** live in `usermods/<name>/` with a `.cpp`, optional `.h`, `library.json`, and `readme.md`.  An example is in `usermods/EXAMPLE`\n* **Out-Of-Tree Usermods** live in a separate public repository. They use the same pattern as in-tree usermods.\n\n* [Official out-of-tree usermods list](https://kno.wled.ge/advanced/community-usermods/#index)\n* [Writing an out-of-tree usermod](https://kno.wled.ge/advanced/custom-features/#writing-a-usermod)\n\n### Usermod Pattern\n\n```cpp\nclass MyUsermod : public Usermod {\n  private:\n    bool enabled = false;\n    static const char _name[];\n  public:\n    void setup() override { /* ... */ }                          // runs once at start-up\n    void loop() override { /* ... */ }                           // runs once per main loop iteration\n    void addToConfig(JsonObject& root) override { /* ... */ }    // create/add persistent settings (usermod settings)\n    bool readFromConfig(JsonObject& root) override { /* ... */ } // read from persistent settings (usermod settings UI)\n    uint16_t getId() override { return USERMOD_ID_MYMOD; }\n    void addToJsonInfo(JsonObject& root) override { /* ... */ }  // Add custom items to the \"info\" page and to /json/info\n    void appendConfigData() override { /* ... */ }               // Customize the settings page: dropdowns, checkboxes, extra text, etc. Buffer size is limited!\n};\nconst char MyUsermod::_name[] PROGMEM = \"MyUsermod\";\nstatic MyUsermod myUsermod;\nREGISTER_USERMOD(myUsermod);\n```\n\nrefer to detailed examples in `usermods/EXAMPLE/`, `usermods/user_fx/` and [in the user documentation for custom features](https://kno.wled.ge/advanced/custom-features/).\n\n- Activate via `custom_usermods = ` in platformio build config. The `usermod_v2_` prefix or `_v2` suffix can be omitted.\n- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)\n- Store repeated strings as `static const char[] PROGMEM`\n- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see below)\n\n### Pin ownership via `pinManager`\n- Before performing any operation on I/O pins, the usermod must allocate its pins from the `pinManager`.\n- I/O pins are allocated via `PinManager::allocatePin(byte gpio, bool output, PinOwner tag)` (or `PinManager::allocateMultiplePins()`), and returned via `PinManager::deallocatePin()` when re-configuring pin numbers.\n- If the usermod does not register a unique usermod ID, use `PinOwner::UM_Unspecified` as PinOwner tag for generic pin use.\n- `PinManager::allocatePin()` will return an error code in case that a pin is already assigned to another WLED function.\n- You can use `-1` = `255` for unconfigured / unassigned pin functions.\n- Check for valid pin numbers with `PinManager::isPinOk(byte gpio, bool output)`; this function knows which GPI(O) pins are possible on your specific MCU.\n\n#### I2C (Wire) and SPI busses\n\n- PIN numbers for I2C and SPI busses are configured globally in the 'Global I2C & SPI' part of the usermod settings page.\n- Usually, a usermod will not start the I2C or SPI units explicitly, since they are already initialised when WLED starts.\n- Don't call `Wire.begin()` in your `Usermod::setup()`, as this may break other usermods that need the I2C bus.\n- Don't allocate `Wire` I2C pins to the usermod - it would cause a pin conflict, as global I2C pins are already allocated to `PinOwner::HW_I2C`.\n- Use this pattern to check for an incomplete I2C setup: `if (i2c_scl<0 || i2c_sda<0) {enabled = false; return;}`.\n\n### Usermod IDs\n\nA unique ID (registered in `wled00/const.h` and overriding `getId()`) is **only required** when a usermod needs one or more of the following:\n\n1. **Inter-usermod communication** — another usermod or an FX effect calls `UsermodManager::lookup(mod_id)` or `UsermodManager::getUMData(..., mod_id)` to find or request data from this specific usermod.\n2. **Pin ownership via `PinManager`** — the usermod allocates GPIO pins through `pinManager`. Pin ownership is tracked by `PinOwner` enum values that map directly to `USERMOD_ID_*` constants (see `wled00/pin_manager.h`). This prevents pin-conflict bugs.\n3. **Identification in JSON info** — `UsermodManager::addToJsonInfo` emits each mod's ID into the `\"um\"` array; a unique ID makes the mod identifiable in that output.\n\nIf none of the above apply, the usermod may omit `getId()` (or return the default `USERMOD_ID_UNSPECIFIED`) and does **not** need an entry in `const.h`.\n\n### Usermod `loop()`\n\n- Called once per main loop iteration. Usermods should simply `return` when `!enabled`.\n- Avoid blocking calls or `delay()` in the usermod `loop()` - delays block the WLED main loop and lead to effect stuttering.\n- Frequency of Usermod `loop()` calls varies with system load:\n    * up to 2000 times/sec with few LEDs and little background activity,\n    * between 20 and 300 times/second during high workload from effects and other usermods,\n    * (worst case) down to 1-3 times/sec during FS activity or when serving lots of network API requests.\n\n### See Also\n* https://kno.wled.ge/advanced/custom-features/#usermods\n* https://kno.wled.ge/advanced/community-usermods/#index\n* generic instructions for Debug Output, as per previous section in this file.\n\n## CI/CD\n\nCI runs on every push/PR via GitHub Actions (`.github/workflows/wled-ci.yml`):\n\n1. `npm test` (web UI build validation)\n2. Firmware compilation for all default environments (~22 targets)\n3. Post-link validation of usermod linkage (`validate_modules.py`)\n\nNo automated linting is configured. Match existing code style in files you edit.\n\n## General Rules\n\n- Important: Repository language is **English**. This applies to source code (including comments), commit messages and any kind of documentation for developer or users.\n- Provide references when making analyses or recommendations. Support factual claims with verifiable citations, references or concrete evidence; **never fabricate citations**.\n- The `docs/` folder is for developer/contributor information (coding conventions, architecture, etc.). User documentation is maintained in the [wled/WLED-Docs](https://github.com/wled/WLED-Docs) repository.\n- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`.\n- Remove dead/unused code — justify or delete it.\n- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor).\n- Important: **Changes to `platformio.ini` require maintainer approval**!\n- PRs should respect `.gitignore` and not upload files like  `platformio_override.ini`. PR authors may add buildenv examples for custom boards into `platformio_override.ini.sample`.\n- When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description.\n\n### Pull Request Expectations\n- **No force-push** on open PRs!\n- Every pull request needs a **clear description of what changed and why**. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable.\n- Best practice: Consider adding screenshots to showcase new features.\n- **Do not prefix the PR title** with `fix:`, `feat:`, `doc:` or other keywords meant to define the type of PR. Use combinations of labels (`bug`, `enhancement`, `effect`, `usermod`, `slop`, etc.) instead.\n- **Do not fix trailing whitespace** or perform pretty-formatting in unrelated code. Such fixes can lead to merge conflicts when other forks want to cherry-pick changes. A separate PR should be used for whitespace optimisation and pretty-formatting.\n- Important: **Fully or partially AI coded PRs MUST be declared clearly** in the description - in addition to comments markers in the source code (see Comments section).\n\n### Security Hardening\n\nWhen writing or reviewing code in `wled00/`, `usermods/`, `wled00/data/`, or `.github/workflows/`,\nconsult `docs/hardening.instructions.md` (concise checklist) and `docs/securecode.instructions.md` (detailed rules with examples).\nThese files define WLED's threat model, trust boundary model, and WLED-specific constraints (no TLS baseline, no UDP authentication for protocol-defined\nmulticast/broadcast, firewall-isolated deployment assumed).\n\n### Attribution for AI-generated code\n\nUsing AI-generated code can obscure the original source or logic of a solution. It may even create hidden licensing risks if sources cannot be verified independently.\n\n- If your agent activity has referenced specific external sources (e.g., science papers, Wikipedia, or documentation) to derive the solution, include these references to allow for independent verification.\n- While standard coding patterns require no further explanation, please describe the logic or architectural reasoning behind any unconventional or complex solutions.\n- Use the required source code comment markers for non-trivial AI generated code blocks (see Comments section).\n- Every non-trivial AI-generated function should have a *brief* comment summarizing what it does. Explain parameters when their names alone are not self-explanatory. Do not rephrase source code; explain non-standard concepts and reasoning.\n\n### Guardrails for Coding Agents\n\nThese apply to any coding agent (Copilot or otherwise) acting on a contributor's behalf in this repository:\n\n- **Confirm before you commit.** Make sure the human understands and can explain the change - especially root-cause claims or refactors - before you open a PR.\n- **Stay scoped, but don't dodge architecture.** Touch only what the task requires, and flag broader fixes to the human instead of silently expanding the change. But don't use \"keep it minimal\" as an excuse to avoid a needed core-level discussion - if the clean fix requires a new core capability or interface, say so and propose it, rather than bolting on a weak hook, monkey-patch, or board-specific workaround just to keep the diff small.\n- **Warning: avoid overconfident root-cause claims.** When drafting issue tickets or PR descriptions, don't state a root cause with confidence unless you've actually traced the code path or reproduced the failure. A plausible-sounding explanation is not the same as a verified one - overconfident claims that later \"dissolve into nothing\" under review waste maintainer time and damage trust in the whole PR. When in doubt, say so explicitly.\n- **State what's tested.** Distinguish \"verified\" from \"should work based on reading the code\" in the PR description.\n- **Disclose AI involvement.** Declare AI assistance in the PR description.\n- **Let issues start non-technical.** When helping draft an issue or feature request, elicit the human's objective or observation in plain language first - don't lead with a proposed code path or fix. Maintainers are better positioned than you to map that objective onto a technical solution.\n\n### Supporting Reviews and Discussions\n\n- **For \"is it worth doing?\" debates** about proposed reliability, safety, or data-integrity mechanisms (CRC checks, backups, power-loss protection): suggest a software **FMEA** (Failure Mode and Effects Analysis).\n  Clarify the main feared events, enumerate failure modes, assess each mitigation's effectiveness per failure mode, note common-cause failures, and rate credibility for the typical WLED use case.\n","isInternal":false,"tokens":6224,"sizeBytes":26033},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/wled/WLED/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# WLED - ESP32/ESP8266 LED Controller Firmware\n\nWLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs and SPI-based chipsets. The project consists of C++ firmware for microcontrollers and a modern web interface.\n\nAlways reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n> **Note for AI review tools**: sections enclosed in\n> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain\n> contributor reference material. Do **not** use that content as actionable review\n> criteria — treat it as background context only.\n\n## Setup\n\n- Node.js 20+ (see `.nvmrc`)\n- Install dependencies: `npm ci`\n- PlatformIO (required only for firmware compilation): `pip install -r requirements.txt`\n\n## Build and Test\n<!-- HUMAN_ONLY_START -->\n\n| Command | Purpose | Typical Time |\n|---|---|---|\n| `npm run build` | Build web UI → generates `wled00/html_*.h` and `wled00/js_*.h` headers | ~3 s |\n| `npm test` | Run test suite | ~40 s |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |\n| `pio run -e <env>` | Build firmware for a hardware target | 15–20 min |\n\n<!-- HUMAN_ONLY_END -->\n\n- **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).\n- The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.\n- **Build firmware to validate code changes**: `pio run -e esp32dev` — must succeed, never skip this step.\n- Common firmware environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`\n\nFor detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).\n\n### Usermod Guidelines\n\n   - New custom effects can be added into the user_fx usermod. Read the [user_fx documentation](https://github.com/wled/WLED/blob/main/usermods/user_fx/README.md) for guidance.\n   - Other usermods may be based on the [EXAMPLE usermod](https://github.com/wled/WLED/tree/main/usermods/EXAMPLE). Never edit the example, always create a copy!\n   - New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/main/wled00/const.h#L160).\n   - To activate a usermod, a custom build configuration should be used. Add the usermod name to `custom_usermods`.\n\n## Project Structure Overview\n\n### Project Branch / Release Structure\n<!-- HUMAN_ONLY_START -->\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5\n16_x                # maintenance for release 16.x.y\n0_15_x              # maintenance (bugfixes only) for current release 0.15.4\n(tag) v0.14.4       # previous version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n<!-- HUMAN_ONLY_END -->\n\n- ``main``: development trunk (daily/nightly)\n- ``16_x``: maintenance for release 16.x.y\n- ``0_15_x``: bugfixing / maintenance for release 0.15.x\n\n### Repository Structure\n\ntl;dr: \n* Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.\n* Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h` — **never edit or commit**.\n* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.\n* Usermods: `usermods/` (C++, with individual library.json).\n* Contributor docs: `docs/` (coding guidelines, etc).\n\n<!-- HUMAN_ONLY_START -->\nDetailed overview:\n\n```text\nwled00/                 # Main firmware source (C++) \"WLED core\"\n  ├── data/             # Web interface files \n  │   ├── index.htm     # Main UI\n  │   ├── settings*.htm # Settings pages\n  │   └── *.js/*.css    # Frontend resources\n  ├── *.cpp/*.h         # Firmware source files\n  ├── html_*.h          # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)\n  ├── src/              # Modules used by the WLED core (C++)\n  │   ├── fonts/        # Font libraries for scrolling text effect\n  └   └── dependencies/ # Utility functions - some of them have their own licensing terms\nlib/                    # Project specific custom libraries. PlatformIO will compile them to separate static libraries and link them\nplatformio.ini          # Hardware build configuration\n\nplatformio_override.sample.ini # examples for custom build configurations - entries must be copied into platformio_override.ini to use them.\n                               # platformio_override.ini is _not_ stored in the WLED repository!\nusermods/              # User-contributed addons to the WLED core, maintained by individual contributors  (C++, with individual library.json)\npackage.json           # Node.js dependencies and scripts, release identification\npio-scripts/           # Build tools (PlatformIO)\ntools/                 # Build tools (Node.js), partition files, and generic utilities\n  ├── cdata.js         # Web UI build script\n  └── cdata-test.js    # Test suite\ndocs/                  # Contributor docs, coding guidelines\n.github/workflows/     # CI/CD pipelines\n```\n\n<!-- HUMAN_ONLY_END -->\n## General Guidelines\n\n- **Repository language is English.** Suggest translations for non-English content.\n- **Use VS Code with PlatformIO extension** for best development experience.\n- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`.\n- If updating Web UI files in `wled00/data/`, **make use of common functions in `wled00/data/common.js` whenever possible**.\n- **When unsure, say so.** Gather more information rather than guessing.\n- **Acknowledge good patterns** when you see them. Positive feedback always helps.\n- **Provide references** when making analyses or recommendations. Base them on the correct branch or PR.\n- **Highlight user-visible breaking changes and ripple effects**. Ask for confirmation that these were introduced intentionally.\n- **Unused / dead code must be justified or removed**. This helps to keep the codebase clean, maintainable and readable.\n- **Verify feature-flag names.** Every `WLED_ENABLE_*` / `WLED_DISABLE_*` flag must exactly match one of the names below — misspellings are silently ignored by the preprocessor (e.g. `WLED_IR_DISABLE` instead of `WLED_DISABLE_INFRARED`), causing silent build variations. Flag unrecognised names as likely typos and suggest the correct spelling.\n  <br>**`WLED_DISABLE_*`**: `2D`, `ADALIGHT`, `ALEXA`, `BROWNOUT_DET`, `ESPNOW`, `FILESYSTEM`, `HUESYNC`, `IMPROV_WIFISCAN`, `INFRARED`, `LOXONE`, `MQTT`, `OTA`, `PARTICLESYSTEM1D`, `PARTICLESYSTEM2D`, `PIXELFORGE`, `WEBSOCKETS`\n  <br>**`WLED_ENABLE_*`**: `ADALIGHT`, `AOTA`, `DMX`, `DMX_INPUT`, `DMX_OUTPUT`, `FS_EDITOR`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `LOXONE`, `MQTT`, `PIXART`, `PXMAGIC`, `USERMOD_PAGE`, `WEBSOCKETS`, `WPA_ENTERPRISE`\n- **C++ formatting available**: `clang-format` is installed but not in CI\n- No automated linting is configured — match existing code style in files you edit. \n\nRefer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.\n\n### Pull Request Expectations\n\n- **No force-push on open PRs.** Once a pull request is open and being reviewed, do not force-push (`git push --force`) to the branch. Force-pushing rewrites history that reviewers may have already commented on, making it impossible to track incremental changes. Use regular commits or `git merge` to incorporate feedback; the branch will be squash-merged when it is accepted.\n- **Modifications to ``platformio.ini`` MUST be approved explicitly** by a *maintainer* or *WLED organisation Member*. Modifications to the global build environment may break github action builds. Always flag them.\n- **Document your changes in the PR.** Every pull request should include a clear description of *what* changed and *why*. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable. Provide screenshots to showcase new features.\n","isInternal":false,"tokens":2073,"sizeBytes":8306}],"systemPromptSnippet":"<yakaai_skills repo=\"wled/WLED\">\n<!-- File: AGENTS.md (Tokens: ~6224 | Category: root-instruction) -->\n# AGENTS.md — WLED AI Coding Agent & AI Code Review Reference\n\nWLED is C++ firmware for ESP32/ESP8266 microcontrollers controlling addressable LEDs,\nwith a web UI (HTML/JS/CSS). Built with PlatformIO (Arduino framework) and Node.js tooling.\n\nSee also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.md`,\n`docs/cpp.instructions.md`, `docs/web.instructions.md`, `docs/cicd.instructions.md`,\n`docs/hardening.instructions.md`, `docs/securecode.instructions.md`.\n\nAlways reference these instructions - including coding guidelines in `docs/` - first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n## Required User Agreement\n\nBefore editing files, committing, pushing, or opening a PR, show the human you're working with this notice and wait for an explicit yes in the current conversation:\n\n> WLED welcomes AI-assisted contributions, but not unreviewed ones. I'll only edit, commit, push, or open a PR once you confirm you'll review, understand, and take responsibility for the result. Do you agree?\n\nA generic \"go ahead\" or silence doesn't count - get a clear yes. Until then, you may read files and answer questions, but do not modify files in the repository or make changes on GitHub.\n\n### No agent-posted replies or issues\n\nReviewer time is limited, and checking long AI-generated explanations for reasoning errors and hallucinations is costly. Even after the user agreement is confirmed:\n\n- **Never post** replies to reviewer comments or discussions, and **never create issue tickets**, on the user's behalf. You may help the user draft the text locally; the human reviews it and posts it themselves.\n- Creating or updating pull requests remains allowed (after the user agreement is confirmed). Keep PR descriptions concise and factual, and include a summary written by the user in their own words, leading with what they want to achieve or what bothers them in plain language, as described in [CONTRIBUTING.md](CONTRIBUTING.md#opening-issues--feature-requests).\n\n### Attribution for PR Descriptions, Comments and Replies\n\n- When creating PR descriptions or suggesting responses to comments, always add this as the very last line: _You are interacting with an AI system_\n- - Your harness may add this trailer automatically. Do not add a duplicate.\n\n### Attribution for Commits\n\nFor every commit you create in this repository, ensure the commit message includes a `Co-authored-by:` trailer identifying the large language model and agent platform:\n\n```text\nCo-authored-by: MODEL NAME/VERSION (AGENT PLATFORM) <COAUTHOR EMAIL>\n```\n- Your harness may add this trailer automatically. Do not add a duplicate; ensure the resulting trailer identifies both the model and platform.\n- Replace the placeholders with the model name/version reported by your runtime and the agent platform (e.g. Codex, Claude Code). Write `unknown` for unavailable model details; do not guess.\n- Use the platform's configured co-author email, or `agent@example.invalid` if none is available.\n- Include exactly one trailer per distinct platform/model pair that contributed to the commit, after a blank line at the end of the commit message. Preserve existing human co-author trailers.\n\n## Build Commands\n\nNote: also read `AGENTS.local.md` if present (gitignored personal learnings file; it supplements this document).\n\n| Command | Purpose | Timeout |\n|---|---|---|\n| `npm ci` | Install Node.js deps (required first) | 30s |\n| `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s |\n| `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous |\n| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 5 min |\n| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 5 min |\n\n**Always run `npm ci && npm run build` before `pio run`.** The web UI build generates\nrequired C headers for firmware compilation.\n\n### Running a Single Test\n\nTests use Node.js built-in test runner (`node:test`). The single test file is\n`tools/cdata-test.js`. Run it with:\n\n```bash\nnpm test                   # runs all tests via `node --test`\nnode --test tools/cdata-test.js  # run just that file directly\n```\n\nThere are no C++ unit tests. Firmware is validated by successful compilation across\ntarget environments. Always build after code changes: `pio run -e esp32dev`.\n\n### Common Firmware Environments\n\n`esp32dev`, `nodemcuv2`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`, `lolin_s2_mini`\n\n### Recovery / Troubleshooting\n\n```bash\nnpm run build -- -f              # force web UI rebuild\nrm -f wled00/html_*.h wled00/js_*.h && npm run build  # clean + rebuild UI\npio run --target clean           # clean PlatformIO build artifacts\nrm -rf node_modules && npm ci    # reinstall Node.js deps\n```\n\n## Project Structure\n\n```text\nwled00/              # Main firmware source (C++)\n  data/              # Web UI source (HTML/JS/CSS) — tabs for indentation\n  html_*.h, js_*.h   # Auto-generated (NEVER edit or commit)\n  src/               # Sub-modules: fonts, bundled dependencies (ArduinoJSON)\nusermods/            # Community usermods (each has library.json + .cpp/.h)\nplatformio.ini       # Build configuration and environments\npio-scripts/         # PlatformIO build scripts (Python)\ntools/               # Node.js build tools (cdata.js) and tests\ndocs/                # Coding convention docs\n.github/workflows/   # CI/CD (GitHub Actions)\n```\n\n### Branch / Release Structure\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5. Target branch for PRs.\n16_x                # maintenance for release 16.0.x\n0_15_x              # maintenance (bugfixes only) for previous release 0.15.x\n(tag) v0.14.4       # old version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n\n## C++ Code Style (wled00/, usermods/)\n\n### General\n\n- Follow the existing style in the file you are editing\n- Avoid unexplained \"magic numbers\". For repeated numbers that have the same meaning, prefer named constants (`constexpr`) or C-style `#define` constants.\n- If possible, **use `static` for local (C-style) variables and functions** to keep the global namespace clean\n- When moving global items to another scope, do not leave comments such as `// lastMqttReconnectAttempt is now private to wled.cpp - see there`. These comments create technical debt for maintenance and will inevitably become out of date. As a rule of thumb: **describe the code as it is, not the code as it was**.\n- Describe logic/reasoning behind non-standard patterns and complex solutions.\n\n### Formatting\n- **2-space indentation** (no tabs in C++ files)\n- K&R brace style preferred (opening brace on same line)\n- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`\n- Space after keywords (`if (...)`, `for (...)`), no space before function parens (`doStuff(a)`)\n- Prefer compact function parameter lists and `if` conditions; avoid putting each parameter or part of a condition on its own line. Wrap very long parameter lists when needed.\n- No enforced line-length limit\n\n### Comments\n- `//` for inline (always space after), `/* */` for block comments\n- Important: AI-generated source code blocks **must be mark with `// AI: below section was generated by an AI` / `// AI: end`**\n\n### Naming Conventions\n| Kind | Convention | Examples |\n|---|---|---|\n| Functions, variables | camelCase | `setRandomColor()`, `effectCurrent` |\n| Classes, structs | PascalCase | `BusConfig`, `UsermodTemperature` |\n| Macros, constants | UPPER_CASE | `WLED_MAX_USERMODS`, `FX_MODE_STATIC` |\n| Private members | _camelCase | `_type`, `_bri`, `_len` |\n| Enum values | PascalCase | `PinOwner::BusDigital` |\n\n### Includes\n- Include `\"wled.h\"` as the primary project header\n- Project headers first, then platform/Arduino, then third-party\n- Platform-conditional includes wrapped in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`\n\n### Debug Output\n\n- Use `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` / `DEBUG_PRINT()` for developer diagnostics and debug output (compiled out unless -D WLED_DEBUG)\n- wled00/wled.h defines these macros. They compile to no output when debug output is disabled, keeping normal serial interfaces clean.\n- Do not use direct `Serial.print()`, `Serial.println()`, `Serial.printf()`, or `Serial.write()` calls unless there is a technical justification for not using DEBUG_... macros.\n\nSee docs/cpp.instructions.md section Error Handling for more information.\n\n### Types and Const\n- Prefer `const &` for read-only function parameters\n- Mark getter/query methods `const`; use `static` for methods not accessing instance state\n- Prefer `constexpr` over `#define` for compile-time constants when possible\n- Use `static_assert` over `#if ... #error`\n- Use `uint_fast16_t` / `uint_fast8_t` in hot-path code\n\n### Error Handling\n- **No C++ exceptions** — some builds disable them\n- Use return codes (`false`, `-1`) and global flags (`errorFlag = ERR_LOW_MEM`)\n- Use early returns as guard clauses: `if (!enabled || (strip.isUpdating() && (millis() - last_time < MAX_USERMOD_DELAY))) return;`\n- Debug output: `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` (see previous section)\n\n### Strings and Memory\n- Use `F(\"string\")` for string constants (saves RAM on ESP8266)\n- Use `PSTR()` with `DEBUG_PRINTF_P()` for format strings\n- Avoid `String` in hot paths; acceptable in config/setup code\n- Use `d_malloc()` (DRAM-preferred) / `p_malloc()` (PSRAM-preferred) for allocation\n- No VLAs — use fixed arrays or heap allocation\n- Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation\n\n### ESP32 PSRAM guidelines\n\n- **Check availability**: Use `psramFound() && ESP.getPsramSize() > 0`; never rely on `BOARD_HAS_PSRAM` alone.\n- **DMA**: Not possible on ESP32 classic. S3 can do it in hardware (see next bullet), but descriptors must stay in DRAM and bandwidth is poor while the CPU also uses PSRAM. Keeping DMA buffers in DRAM is usually better for performance.\n   - On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.\n- **Cache**: PSRAM shares the CPU cache with flash (~32 KB data cache by default). Hot working sets below that run near DRAM speed. Large or random-access buffers run at raw PSRAM speed, and big sweeps can evict cached code and slow down everything.\n- **Flash writes**: PSRAM is inaccessible while the flash cache is off. ISRs that run during flash writes must use only DRAM or IRAM data, and they must be declared with `IRAM_ATTR` so instructions don't  need to be fetched from flash while writing.\n- **Performance**: Prefer DRAM (or IRAM) for hot-path data that is *frequently* used. Prefer PSRAM for capacity-oriented buffers where slightly slower access times can be tolerated. Small hot working sets (<~32 KB) in PSRAM can run near DRAM speed, especially when accessed sequentially.\n\n#### Background Info:\n\n- PSRAM access is up to 15× slower than DRAM on ESP32 (dual-SPI bus), 3–10× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM. These values are rough estimates only.\n- PSRAM uses the CPU data cache (typically ~32 KB) that is also shared with flash. Small hot working sets (<~32 KB) run near DRAM speed; larger or random-access buffers fall back to raw PSRAM speed, and big PSRAM sweeps can evict cached flash code and slow down everything else. Keep hot data in DRAM and use PSRAM for large buffers that are accessed sequentially (Espressif's ESP-IDF external RAM guide, \"Restrictions\").\n- Caching read-only files in PSRAM is usually not faster, because flash and PSRAM share the same SPI bus and often run at same speeds. Minor gains are still possible on ESP32-S3 when CONFIG_SOC_PSRAM_DMA_CAPABLE and CONFIG_SPIRAM_MODE_OCT are enabled. Better performance potential is expected on ESP32-P4 with CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT. In special cases, file caching can still help by reducing interrupt contention (for example RMT refill pressure) and preventing visible LED flashing.\n- Consider that ESP32 often crashes when the largest available DRAM chunk gets below 10 KB.\n\n### Preprocessor / Feature Flags\n- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)\n- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.\n- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.\n- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`\n\n### Math Functions\n- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)\n- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`\n- Replace `inoise8` / `inoise16` with `perlin8` / `perlin16`\n\n### Hot-Path Code (Pixel Pipeline)\n- Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))`\n- Cache class members to locals before loops\n- Pre-compute invariants outside loops; use reciprocals to avoid division\n- Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)`\n\n### ESP32 Tasks\n- `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog\n- Do not use `delay()` in effects (FX.cpp) or hot pixel path\n\n#### ESP32 Task Synchronization\n\n- Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour.\n- **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues.\n- Don't use `portMAX_DELAY` when waiting to acquire a mutex - this can lock the task indefinitely. Find a reasonable max waiting time, and handle mutex timeouts gracefully.\n- **Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow, for example when function calls are sequenced within the same loop iteration.\n- Consider using `std::atomic` or RAII scoped guards as alternatives to mutexes, semaphores or queues.\n\n## Web UI Code Style (wled00/data/)\n\n- **Tab indentation** for HTML, JS, and CSS\n- camelCase for JS functions/variables\n- Reuse helpers from `common.js` — do not duplicate utilities\n- After editing, run `npm run build` to regenerate headers\n- **Never edit** `wled00/html_*.h` or `wled00/js_*.h` directly\n\n## Usermods\n \n### Source Code Location\n\n* **In-Tree Usermods** live in `usermods/<name>/` with a `.cpp`, optional `.h`, `library.json`, and `readme.md`.  An example is in `usermods/EXAMPLE`\n* **Out-Of-Tree Usermods** live in a separate public repository. They use the same pattern as in-tree usermods.\n\n* [Official out-of-tree usermods list](https://kno.wled.ge/advanced/community-usermods/#index)\n* [Writing an out-of-tree usermod](https://kno.wled.ge/advanced/custom-features/#writing-a-usermod)\n\n### Usermod Pattern\n\n```cpp\nclass MyUsermod : public Usermod {\n  private:\n    bool enabled = false;\n    static const char _name[];\n  public:\n    void setup() override { /* ... */ }                          // runs once at start-up\n    void loop() override { /* ... */ }                           // runs once per main loop iteration\n    void addToConfig(JsonObject& root) override { /* ... */ }    // create/add persistent settings (usermod settings)\n    bool readFromConfig(JsonObject& root) override { /* ... */ } // read from persistent settings (usermod settings UI)\n    uint16_t getId() override { return USERMOD_ID_MYMOD; }\n    void addToJsonInfo(JsonObject& root) override { /* ... */ }  // Add custom items to the \"info\" page and to /json/info\n    void appendConfigData() override { /* ... */ }               // Customize the settings page: dropdowns, checkboxes, extra text, etc. Buffer size is limited!\n};\nconst char MyUsermod::_name[] PROGMEM = \"MyUsermod\";\nstatic MyUsermod myUsermod;\nREGISTER_USERMOD(myUsermod);\n```\n\nrefer to detailed examples in `usermods/EXAMPLE/`, `usermods/user_fx/` and [in the user documentation for custom features](https://kno.wled.ge/advanced/custom-features/).\n\n- Activate via `custom_usermods = ` in platformio build config. The `usermod_v2_` prefix or `_v2` suffix can be omitted.\n- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)\n- Store repeated strings as `static const char[] PROGMEM`\n- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see below)\n\n### Pin ownership via `pinManager`\n- Before performing any operation on I/O pins, the usermod must allocate its pins from the `pinManager`.\n- I/O pins are allocated via `PinManager::allocatePin(byte gpio, bool output, PinOwner tag)` (or `PinManager::allocateMultiplePins()`), and returned via `PinManager::deallocatePin()` when re-configuring pin numbers.\n- If the usermod does not register a unique usermod ID, use `PinOwner::UM_Unspecified` as PinOwner tag for generic pin use.\n- `PinManager::allocatePin()` will return an error code in case that a pin is already assigned to another WLED function.\n- You can use `-1` = `255` for unconfigured / unassigned pin functions.\n- Check for valid pin numbers with `PinManager::isPinOk(byte gpio, bool output)`; this function knows which GPI(O) pins are possible on your specific MCU.\n\n#### I2C (Wire) and SPI busses\n\n- PIN numbers for I2C and SPI busses are configured globally in the 'Global I2C & SPI' part of the usermod settings page.\n- Usually, a usermod will not start the I2C or SPI units explicitly, since they are already initialised when WLED starts.\n- Don't call `Wire.begin()` in your `Usermod::setup()`, as this may break other usermods that need the I2C bus.\n- Don't allocate `Wire` I2C pins to the usermod - it would cause a pin conflict, as global I2C pins are already allocated to `PinOwner::HW_I2C`.\n- Use this pattern to check for an incomplete I2C setup: `if (i2c_scl<0 || i2c_sda<0) {enabled = false; return;}`.\n\n### Usermod IDs\n\nA unique ID (registered in `wled00/const.h` and overriding `getId()`) is **only required** when a usermod needs one or more of the following:\n\n1. **Inter-usermod communication** — another usermod or an FX effect calls `UsermodManager::lookup(mod_id)` or `UsermodManager::getUMData(..., mod_id)` to find or request data from this specific usermod.\n2. **Pin ownership via `PinManager`** — the usermod allocates GPIO pins through `pinManager`. Pin ownership is tracked by `PinOwner` enum values that map directly to `USERMOD_ID_*` constants (see `wled00/pin_manager.h`). This prevents pin-conflict bugs.\n3. **Identification in JSON info** — `UsermodManager::addToJsonInfo` emits each mod's ID into the `\"um\"` array; a unique ID makes the mod identifiable in that output.\n\nIf none of the above apply, the usermod may omit `getId()` (or return the default `USERMOD_ID_UNSPECIFIED`) and does **not** need an entry in `const.h`.\n\n### Usermod `loop()`\n\n- Called once per main loop iteration. Usermods should simply `return` when `!enabled`.\n- Avoid blocking calls or `delay()` in the usermod `loop()` - delays block the WLED main loop and lead to effect stuttering.\n- Frequency of Usermod `loop()` calls varies with system load:\n    * up to 2000 times/sec with few LEDs and little background activity,\n    * between 20 and 300 times/second during high workload from effects and other usermods,\n    * (worst case) down to 1-3 times/sec during FS activity or when serving lots of network API requests.\n\n### See Also\n* https://kno.wled.ge/advanced/custom-features/#usermods\n* https://kno.wled.ge/advanced/community-usermods/#index\n* generic instructions for Debug Output, as per previous section in this file.\n\n## CI/CD\n\nCI runs on every push/PR via GitHub Actions (`.github/workflows/wled-ci.yml`):\n\n1. `npm test` (web UI build validation)\n2. Firmware compilation for all default environments (~22 targets)\n3. Post-link validation of usermod linkage (`validate_modules.py`)\n\nNo automated linting is configured. Match existing code style in files you edit.\n\n## General Rules\n\n- Important: Repository language is **English**. This applies to source code (including comments), commit messages and any kind of documentation for developer or users.\n- Provide references when making analyses or recommendations. Support factual claims with verifiable citations, references or concrete evidence; **never fabricate citations**.\n- The `docs/` folder is for developer/contributor information (coding conventions, architecture, etc.). User documentation is maintained in the [wled/WLED-Docs](https://github.com/wled/WLED-Docs) repository.\n- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`.\n- Remove dead/unused code — justify or delete it.\n- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor).\n- Important: **Changes to `platformio.ini` require maintainer approval**!\n- PRs should respect `.gitignore` and not upload files like  `platformio_override.ini`. PR authors may add buildenv examples for custom boards into `platformio_override.ini.sample`.\n- When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description.\n\n### Pull Request Expectations\n- **No force-push** on open PRs!\n- Every pull request needs a **clear description of what changed and why**. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable.\n- Best practice: Consider adding screenshots to showcase new features.\n- **Do not prefix the PR title** with `fix:`, `feat:`, `doc:` or other keywords meant to define the type of PR. Use combinations of labels (`bug`, `enhancement`, `effect`, `usermod`, `slop`, etc.) instead.\n- **Do not fix trailing whitespace** or perform pretty-formatting in unrelated code. Such fixes can lead to merge conflicts when other forks want to cherry-pick changes. A separate PR should be used for whitespace optimisation and pretty-formatting.\n- Important: **Fully or partially AI coded PRs MUST be declared clearly** in the description - in addition to comments markers in the source code (see Comments section).\n\n### Security Hardening\n\nWhen writing or reviewing code in `wled00/`, `usermods/`, `wled00/data/`, or `.github/workflows/`,\nconsult `docs/hardening.instructions.md` (concise checklist) and `docs/securecode.instructions.md` (detailed rules with examples).\nThese files define WLED's threat model, trust boundary model, and WLED-specific constraints (no TLS baseline, no UDP authentication for protocol-defined\nmulticast/broadcast, firewall-isolated deployment assumed).\n\n### Attribution for AI-generated code\n\nUsing AI-generated code can obscure the original source or logic of a solution. It may even create hidden licensing risks if sources cannot be verified independently.\n\n- If your agent activity has referenced specific external sources (e.g., science papers, Wikipedia, or documentation) to derive the solution, include these references to allow for independent verification.\n- While standard coding patterns require no further explanation, please describe the logic or architectural reasoning behind any unconventional or complex solutions.\n- Use the required source code comment markers for non-trivial AI generated code blocks (see Comments section).\n- Every non-trivial AI-generated function should have a *brief* comment summarizing what it does. Explain parameters when their names alone are not self-explanatory. Do not rephrase source code; explain non-standard concepts and reasoning.\n\n### Guardrails for Coding Agents\n\nThese apply to any coding agent (Copilot or otherwise) acting on a contributor's behalf in this repository:\n\n- **Confirm before you commit.** Make sure the human understands and can explain the change - especially root-cause claims or refactors - before you open a PR.\n- **Stay scoped, but don't dodge architecture.** Touch only what the task requires, and flag broader fixes to the human instead of silently expanding the change. But don't use \"keep it minimal\" as an excuse to avoid a needed core-level discussion - if the clean fix requires a new core capability or interface, say so and propose it, rather than bolting on a weak hook, monkey-patch, or board-specific workaround just to keep the diff small.\n- **Warning: avoid overconfident root-cause claims.** When drafting issue tickets or PR descriptions, don't state a root cause with confidence unless you've actually traced the code path or reproduced the failure. A plausible-sounding explanation is not the same as a verified one - overconfident claims that later \"dissolve into nothing\" under review waste maintainer time and damage trust in the whole PR. When in doubt, say so explicitly.\n- **State what's tested.** Distinguish \"verified\" from \"should work based on reading the code\" in the PR description.\n- **Disclose AI involvement.** Declare AI assistance in the PR description.\n- **Let issues start non-technical.** When helping draft an issue or feature request, elicit the human's objective or observation in plain language first - don't lead with a proposed code path or fix. Maintainers are better positioned than you to map that objective onto a technical solution.\n\n### Supporting Reviews and Discussions\n\n- **For \"is it worth doing?\" debates** about proposed reliability, safety, or data-integrity mechanisms (CRC checks, backups, power-loss protection): suggest a software **FMEA** (Failure Mode and Effects Analysis).\n  Clarify the main feared events, enumerate failure modes, assess each mitigation's effectiveness per failure mode, note common-cause failures, and rate credibility for the typical WLED use case.\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~2073 | Category: copilot-instructions) -->\n# WLED - ESP32/ESP8266 LED Controller Firmware\n\nWLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs and SPI-based chipsets. The project consists of C++ firmware for microcontrollers and a modern web interface.\n\nAlways reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n> **Note for AI review tools**: sections enclosed in\n> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain\n> contributor reference material. Do **not** use that content as actionable review\n> criteria — treat it as background context only.\n\n## Setup\n\n- Node.js 20+ (see `.nvmrc`)\n- Install dependencies: `npm ci`\n- PlatformIO (required only for firmware compilation): `pip install -r requirements.txt`\n\n## Build and Test\n<!-- HUMAN_ONLY_START -->\n\n| Command | Purpose | Typical Time |\n|---|---|---|\n| `npm run build` | Build web UI → generates `wled00/html_*.h` and `wled00/js_*.h` headers | ~3 s |\n| `npm test` | Run test suite | ~40 s |\n| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |\n| `pio run -e <env>` | Build firmware for a hardware target | 15–20 min |\n\n<!-- HUMAN_ONLY_END -->\n\n- **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).\n- The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.\n- **Build firmware to validate code changes**: `pio run -e esp32dev` — must succeed, never skip this step.\n- Common firmware environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`\n\nFor detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).\n\n### Usermod Guidelines\n\n   - New custom effects can be added into the user_fx usermod. Read the [user_fx documentation](https://github.com/wled/WLED/blob/main/usermods/user_fx/README.md) for guidance.\n   - Other usermods may be based on the [EXAMPLE usermod](https://github.com/wled/WLED/tree/main/usermods/EXAMPLE). Never edit the example, always create a copy!\n   - New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/main/wled00/const.h#L160).\n   - To activate a usermod, a custom build configuration should be used. Add the usermod name to `custom_usermods`.\n\n## Project Structure Overview\n\n### Project Branch / Release Structure\n<!-- HUMAN_ONLY_START -->\n\n```text\nmain                # Main development trunk (daily/nightly) 17.0.0-devV5\n16_x                # maintenance for release 16.x.y\n0_15_x              # maintenance (bugfixes only) for current release 0.15.4\n(tag) v0.14.4       # previous version 0.14.4 (no maintenance)\n(tag) v0.13.3       # old version 0.13.3 (no maintenance)\n(tag) v0. ... . ... # historical versions 0.12.x and before\n```\n<!-- HUMAN_ONLY_END -->\n\n- ``main``: development trunk (daily/nightly)\n- ``16_x``: maintenance for release 16.x.y\n- ``0_15_x``: bugfixing / maintenance for release 0.15.x\n\n### Repository Structure\n\ntl;dr: \n* Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.\n* Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h` — **never edit or commit**.\n* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.\n* Usermods: `usermods/` (C++, with individual library.json).\n* Contributor docs: `docs/` (coding guidelines, etc).\n\n<!-- HUMAN_ONLY_START -->\nDetailed overview:\n\n```text\nwled00/                 # Main firmware source (C++) \"WLED core\"\n  ├── data/             # Web interface files \n  │   ├── index.htm     # Main UI\n  │   ├── settings*.htm # Settings pages\n  │   └── *.js/*.css    # Frontend resources\n  ├── *.cpp/*.h         # Firmware source files\n  ├── html_*.h          # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)\n  ├── src/              # Modules used by the WLED core (C++)\n  │   ├── fonts/        # Font libraries for scrolling text effect\n  └   └── dependencies/ # Utility functions - some of them have their own licensing terms\nlib/                    # Project specific custom libraries. PlatformIO will compile them to separate static libraries and link them\nplatformio.ini          # Hardware build configuration\n\nplatformio_override.sample.ini # examples for custom build configurations - entries must be copied into platformio_override.ini to use them.\n                               # platformio_override.ini is _not_ stored in the WLED repository!\nusermods/              # User-contributed addons to the WLED core, maintained by individual contributors  (C++, with individual library.json)\npackage.json           # Node.js dependencies and scripts, release identification\npio-scripts/           # Build tools (PlatformIO)\ntools/                 # Build tools (Node.js), partition files, and generic utilities\n  ├── cdata.js         # Web UI build script\n  └── cdata-test.js    # Test suite\ndocs/                  # Contributor docs, coding guidelines\n.github/workflows/     # CI/CD pipelines\n```\n\n<!-- HUMAN_ONLY_END -->\n## General Guidelines\n\n- **Repository language is English.** Suggest translations for non-English content.\n- **Use VS Code with PlatformIO extension** for best development experience.\n- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`.\n- If updating Web UI files in `wled00/data/`, **make use of common functions in `wled00/data/common.js` whenever possible**.\n- **When unsure, say so.** Gather more information rather than guessing.\n- **Acknowledge good patterns** when you see them. Positive feedback always helps.\n- **Provide references** when making analyses or recommendations. Base them on the correct branch or PR.\n- **Highlight user-visible breaking changes and ripple effects**. Ask for confirmation that these were introduced intentionally.\n- **Unused / dead code must be justified or removed**. This helps to keep the codebase clean, maintainable and readable.\n- **Verify feature-flag names.** Every `WLED_ENABLE_*` / `WLED_DISABLE_*` flag must exactly match one of the names below — misspellings are silently ignored by the preprocessor (e.g. `WLED_IR_DISABLE` instead of `WLED_DISABLE_INFRARED`), causing silent build variations. Flag unrecognised names as likely typos and suggest the correct spelling.\n  <br>**`WLED_DISABLE_*`**: `2D`, `ADALIGHT`, `ALEXA`, `BROWNOUT_DET`, `ESPNOW`, `FILESYSTEM`, `HUESYNC`, `IMPROV_WIFISCAN`, `INFRARED`, `LOXONE`, `MQTT`, `OTA`, `PARTICLESYSTEM1D`, `PARTICLESYSTEM2D`, `PIXELFORGE`, `WEBSOCKETS`\n  <br>**`WLED_ENABLE_*`**: `ADALIGHT`, `AOTA`, `DMX`, `DMX_INPUT`, `DMX_OUTPUT`, `FS_EDITOR`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `LOXONE`, `MQTT`, `PIXART`, `PXMAGIC`, `USERMOD_PAGE`, `WEBSOCKETS`, `WPA_ENTERPRISE`\n- **C++ formatting available**: `clang-format` is installed but not in CI\n- No automated linting is configured — match existing code style in files you edit. \n\nRefer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.\n\n### Pull Request Expectations\n\n- **No force-push on open PRs.** Once a pull request is open and being reviewed, do not force-push (`git push --force`) to the branch. Force-pushing rewrites history that reviewers may have already commented on, making it impossible to track incremental changes. Use regular commits or `git merge` to incorporate feedback; the branch will be squash-merged when it is accepted.\n- **Modifications to ``platformio.ini`` MUST be approved explicitly** by a *maintainer* or *WLED organisation Member*. Modifications to the global build environment may break github action builds. Always flag them.\n- **Document your changes in the PR.** Every pull request should include a clear description of *what* changed and *why*. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable. Provide screenshots to showcase new features.\n</yakaai_skills>"}