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.
| 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) | 30 min |
pio run -e nodemcuv2 |
Build firmware (ESP8266) | 30 min |
Always run npm ci && npm run build before pio run. The web UI build generates
required C headers for firmware compilation.
Tests use Node.js built-in test runner (node:test). The single test file is
tools/cdata-test.js. Run it with:
npm test # runs all tests via `node --test`
node --test tools/cdata-test.js # run just that file directlyThere are no C++ unit tests. Firmware is validated by successful compilation across
target environments. Always build after code changes: pio run -e esp32dev.
esp32dev, nodemcuv2, esp8266_2m, esp32c3dev, esp32s3dev_8MB_opi, lolin_s2_mini
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 depswled00/ # 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)
- 2-space indentation (no tabs in C++ files)
- K&R brace style preferred (opening brace on same line)
- Single-statement
ifbodies 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
| 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 |
- 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
- Prefer
const &for read-only function parameters - Mark getter/query methods
const; usestaticfor methods not accessing instance state - Prefer
constexprover#definefor compile-time constants when possible - Use
static_assertover#if ... #error - Use
uint_fast16_t/uint_fast8_tin hot-path code
- 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)
- Use
F("string")for string constants (saves RAM on ESP8266) - Use
PSTR()withDEBUG_PRINTF_P()for format strings - Avoid
Stringin 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
- Feature toggling:
WLED_DISABLE_*andWLED_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
//for inline (always space after),/* */for block comments- AI-generated blocks: mark with
// AI: below section was generated by an AI/// AI: end
- Use
sin8_t(),cos8_t()— NOTsin8(),cos8()(removed, won't compile) - Use
sin_approx()/cos_approx()instead ofsinf()/cosf() - Replace
inoise8/inoise16withperlin8/perlin16
- 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)
delay(1)in custom FreeRTOS tasks (NOTyield()) — feeds IDLE watchdog- Do not use
delay()in effects (FX.cpp) or hot pixel path
- 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 buildto regenerate headers - Never edit
wled00/html_*.horwled00/js_*.hdirectly
Usermods live in usermods/<name>/ with a .cpp, optional .h, library.json, and readme.md.
class MyUsermod : public Usermod {
private:
bool enabled = false;
static const char _name[];
public:
void setup() override { /* ... */ }
void loop() override { /* ... */ }
void addToConfig(JsonObject& root) override { /* ... */ }
bool readFromConfig(JsonObject& root) override { /* ... */ }
uint16_t getId() override { return USERMOD_ID_MYMOD; }
};
const char MyUsermod::_name[] PROGMEM = "MyUsermod";
static MyUsermod myUsermod;
REGISTER_USERMOD(myUsermod);- Add usermod IDs to
wled00/const.h - Activate via
custom_usermodsin platformio build config - Base new usermods on
usermods/EXAMPLE/(never edit the example directly) - Store repeated strings as
static const char[] PROGMEM
CI runs on every push/PR via GitHub Actions (.github/workflows/wled-ci.yml):
npm test(web UI build validation)- Firmware compilation for all default environments (~22 targets)
- Post-link validation of usermod linkage (
validate_modules.py)
No automated linting is configured. Match existing code style in files you edit.
- Repository language is English
- Never edit or commit auto-generated
wled00/html_*.h/wled00/js_*.h - No force-push on open PRs
- Changes to
platformio.inirequire maintainer approval - Remove dead/unused code — justify or delete it
- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor)