# CheatSheet: wled/WLED ## Source: Standard Context (/main/AGENTS.md) # AGENTS.md — WLED AI Coding Agent & AI Code Review Reference WLED is C++ firmware for ESP32/ESP8266 microcontrollers controlling addressable LEDs, with a web UI (HTML/JS/CSS). Built with PlatformIO (Arduino framework) and Node.js tooling. See also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.md`, `docs/cpp.instructions.md`, `docs/web.instructions.md`, `docs/cicd.instructions.md`, `docs/hardening.instructions.md`, `docs/securecode.instructions.md`. Always 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. ## Build Commands | Command | Purpose | Timeout | |---|---|---| | `npm ci` | Install Node.js deps (required first) | 30s | | `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s | | `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min | | `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous | | `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 5 min | | `pio run -e nodemcuv2` | Build firmware (ESP8266) | 5 min | **Always run `npm ci && npm run build` before `pio run`.** The web UI build generates required C headers for firmware compilation. ### Running a Single Test Tests use Node.js built-in test runner (`node:test`). The single test file is `tools/cdata-test.js`. Run it with: ```bash npm test # runs all tests via `node --test` node --test tools/cdata-test.js # run just that file directly ``` There are no C++ unit tests. Firmware is validated by successful compilation across target environments. Always build after code changes: `pio run -e esp32dev`. ### Common Firmware Environments `esp32dev`, `nodemcuv2`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`, `lolin_s2_mini` ### Recovery / Troubleshooting ```bash npm run build -- -f # force web UI rebuild rm -f wled00/html_*.h wled00/js_*.h && npm run build # clean + rebuild UI pio run --target clean # clean PlatformIO build artifacts rm -rf node_modules && npm ci # reinstall Node.js deps ``` ## Project Structure ```text wled00/ # Main firmware source (C++) data/ # Web UI source (HTML/JS/CSS) — tabs for indentation html_*.h, js_*.h # Auto-generated (NEVER edit or commit) src/ # Sub-modules: fonts, bundled dependencies (ArduinoJSON) usermods/ # Community usermods (each has library.json + .cpp/.h) platformio.ini # Build configuration and environments pio-scripts/ # PlatformIO build scripts (Python) tools/ # Node.js build tools (cdata.js) and tests docs/ # Coding convention docs .github/workflows/ # CI/CD (GitHub Actions) ``` ### Branch / Release Structure ```text main # Main development trunk (daily/nightly) 17.0.0-dev. Target branch for PRs. ├── V5 # special branch: code rework for esp-idf 5.5.x and new MCU types: esp32-c5, esp32-c6, esp32-p4 (unstable) 16_x # maintenance for release 16.0.x 0_15_x # maintenance (bugfixes only) for previous release 0.15.x (tag) v0.14.4 # old version 0.14.4 (no maintenance) (tag) v0.13.3 # old version 0.13.3 (no maintenance) (tag) v0. ... . ... # historical versions 0.12.x and before ``` ## C++ Code Style (wled00/, usermods/) ### Formatting - **2-space indentation** (no tabs in C++ files) - K&R brace style preferred (opening brace on same line) - Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);` - Space after keywords (`if (...)`, `for (...)`), no space before function parens (`doStuff(a)`) - No enforced line-length limit ### Comments - `//` for inline (always space after), `/* */` for block comments - Important: AI-generated source code blocks **must be mark with `// AI: below section was generated by an AI` / `// AI: end`** ### Naming Conventions | Kind | Convention | Examples | |---|---|---| | Functions, variables | camelCase | `setRandomColor()`, `effectCurrent` | | Classes, structs | PascalCase | `BusConfig`, `UsermodTemperature` | | Macros, constants | UPPER_CASE | `WLED_MAX_USERMODS`, `FX_MODE_STATIC` | | Private members | _camelCase | `_type`, `_bri`, `_len` | | Enum values | PascalCase | `PinOwner::BusDigital` | ### Includes - Include `"wled.h"` as the primary project header - Project headers first, then platform/Arduino, then third-party - Platform-conditional includes wrapped in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266` ### Types and Const - Prefer `const &` for read-only function parameters - Mark getter/query methods `const`; use `static` for methods not accessing instance state - Prefer `constexpr` over `#define` for compile-time constants when possible - Use `static_assert` over `#if ... #error` - Use `uint_fast16_t` / `uint_fast8_t` in hot-path code ### Error Handling - **No C++ exceptions** — some builds disable them - Use return codes (`false`, `-1`) and global flags (`errorFlag = ERR_LOW_MEM`) - Use early returns as guard clauses: `if (!enabled || (strip.isUpdating() && (millis() - last_time < MAX_USERMOD_DELAY))) return;` - Debug output: `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` (compiled out unless `-D WLED_DEBUG`) ### Strings and Memory - Use `F("string")` for string constants (saves RAM on ESP8266) - Use `PSTR()` with `DEBUG_PRINTF_P()` for format strings - Avoid `String` in hot paths; acceptable in config/setup code - Use `d_malloc()` (DRAM-preferred) / `p_malloc()` (PSRAM-preferred) for allocation - No VLAs — use fixed arrays or heap allocation - Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation #### ESP32 PSRAM guidelines - **Check availability**: Test chip availability with `psramFound() && ESP.getPsramSize() > 0` before assuming PSRAM is present. Never rely on `BOARD_HAS_PSRAM`only. - **DMA compatibility**: on ESP32 (classic), PSRAM buffers are **not DMA-capable**. 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. - **Fragmentation**: PSRAM allocations fragment less than DRAM because the region is larger. But avoid mixing small and large allocations in PSRAM — small allocations waste the MMU page granularity. - **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. Background Info: - 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. - Consider that ESP32 often crashes when the largest available DRAM chunk gets below 10 KB. ### Preprocessor / Feature Flags - Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!) - `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc. - `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc. - Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3` ### Math Functions - Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile) - Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()` - Replace `inoise8` / `inoise16` with `perlin8` / `perlin16` ### Hot-Path Code (Pixel Pipeline) - Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))` - Cache class members to locals before loops - Pre-compute invariants outside loops; use reciprocals to avoid division - Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)` ### ESP32 Tasks - `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog - Do not use `delay()` in effects (FX.cpp) or hot pixel path #### ESP32 Task Synchronization - Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour. - **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues. - 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. - **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. - Consider using `std::atomic` or RAII scoped guards as alternatives to mutexes, semaphores or queues. ## Web UI Code Style (wled00/data/) - **Tab indentation** for HTML, JS, and CSS - camelCase for JS functions/variables - Reuse helpers from `common.js` — do not duplicate utilities - After editing, run `npm run build` to regenerate headers - **Never edit** `wled00/html_*.h` or `wled00/js_*.h` directly ## Usermods ### Source Code Location * **In-Tree Usermods** live in `usermods//` with a `.cpp`, optional `.h`, `library.json`, and `readme.md`. An example is in `usermods/EXAMPLE` * **Out-Of-Tree Usermods** live in a separate public repository. They use the same pattern as in-tree usermods. * [Official out-of-tree usermods list](https://kno.wled.ge/advanced/community-usermods/#index) * [Writing an out-of-tree usermod](https://kno.wled.ge/advanced/custom-features/#writing-a-usermod) ### Usermod Pattern ```cpp class MyUsermod : public Usermod { private: bool enabled = false; static const char _name[]; public: void setup() override { /* ... */ } // runs once at start-up void loop() override { /* ... */ } // runs once per main loop iteration void addToConfig(JsonObject& root) override { /* ... */ } // create/add persistent settings (usermod settings) bool readFromConfig(JsonObject& root) override { /* ... */ } // read from persistent settings (usermod settings UI) uint16_t getId() override { return USERMOD_ID_MYMOD; } void addToJsonInfo(JsonObject& root) override { /* ... */ } // Add custom items to the "info" page and to /json/info void appendConfigData() override { /* ... */ } // Customize the settings page: dropdowns, checkboxes, extra text, etc. Buffer size is limited! }; const char MyUsermod::_name[] PROGMEM = "MyUsermod"; static MyUsermod myUsermod; REGISTER_USERMOD(myUsermod); ``` refer to detailed examples in `usermods/EXAMPLE/`, `usermods/user_fx/` and [in the user documentation for custom features](https://kno.wled.ge/advanced/custom-features/). - Activate via `custom_usermods = ` in platformio build config. The `usermod_v2_` prefix or `_v2` suffix can be omitted. - Base new usermods on `usermods/EXAMPLE/` (never edit the example directly) - Store repeated strings as `static const char[] PROGMEM` - Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see below) ### Usermod IDs A unique ID (registered in `wled00/const.h` and overriding `getId()`) is **only required** when a usermod needs one or more of the following: 1. **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. 2. **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. 3. **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. If 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`. ### Usermod `loop()` - Called once per main loop iteration. Usermods should simply `return` when `!enabled`. - Frequency of calls varies with system load: * up to 2000 times/sec with few LEDs and little background activity, * between 20 and 300 times/second during high workload from effects and other usermods, * (worst case) down to 1-3 times/sec during FS activity or when serving lots of network API requests. ### See Also * https://kno.wled.ge/advanced/custom-features/#usermods * https://kno.wled.ge/advanced/community-usermods/#index ## CI/CD CI runs on every push/PR via GitHub Actions (`.github/workflows/wled-ci.yml`): 1. `npm test` (web UI build validation) 2. Firmware compilation for all default environments (~22 targets) 3. Post-link validation of usermod linkage (`validate_modules.py`) No automated linting is configured. Match existing code style in files you edit. ## General Rules - Important: Repository language is **English**. This applies to source code (including comments), commit messages and any kind of documentation for developer or users. - 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. - Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`. - When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description. - No force-push on open PRs! - Important: **Changes to `platformio.ini` require maintainer approval**! - 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`. - Remove dead/unused code — justify or delete it. - Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor). - Provide references when making analyses or recommendations. Support factual claims with verifiable citations, references or concrete evidence; **never fabricate citations**. - **Highlight user-visible breaking changes and ripple effects** during reviews. Ask for confirmation that these were introduced intentionally. ### Security Hardening When writing or reviewing code in `wled00/`, `usermods/`, `wled00/data/`, or `.github/workflows/`, consult `docs/hardening.instructions.md` (concise checklist) and `docs/securecode.instructions.md` (detailed rules with examples). These files define WLED's threat model, trust boundary model, and WLED-specific constraints (no TLS baseline, no UDP authentication for protocol-defined multicast/broadcast, firewall-isolated deployment assumed). ### Attribution for AI-generated code Using AI-generated code can hide the source of the inspiration / knowledge / sources it used. - Document attribution of inspiration / knowledge / sources used in the code, e.g. link to GitHub repositories or other websites describing the principles / algorithms used. - When a larger block of code is generated by an AI tool, embed it into `// AI: below section was generated by an AI` ... `// AI: end` comments (see Comments section). - Every non-trivial AI-generated function should have a brief comment describing what it does. Explain parameters when their names alone are not self-explanatory. - AI-generated code must be well documented with meaningful comments that explain intent, assumptions, and non-obvious logic. Do not rephrase source code; explain concepts and reasoning. ### Supporting Reviews and Discussions - **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). 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. --- METRICS --- - Estimated Token Budget: ~4070 tokens - Recency Window: Active (< 180 days)