mirror of
https://github.com/wled/WLED.git
synced 2026-05-11 08:22:47 +00:00
Compare commits
41 Commits
v16.0.0-rc
...
json_error
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5adaaa44e | ||
|
|
78c1051cbb | ||
|
|
121250c0a0 | ||
|
|
2302863386 | ||
|
|
7560811480 | ||
|
|
252007d37f | ||
|
|
6e76a69417 | ||
|
|
24e2e3c195 | ||
|
|
f1c58ee75f | ||
|
|
c8fc1c89b6 | ||
|
|
12f6fbc005 | ||
|
|
c819814904 | ||
|
|
0ef5ee74d4 | ||
|
|
51862e3572 | ||
|
|
aaf51927a6 | ||
|
|
72a43c6431 | ||
|
|
7c6828d443 | ||
|
|
f8f46736ec | ||
|
|
e4f8534cf0 | ||
|
|
79ecffe0f3 | ||
|
|
1fb636d8ea | ||
|
|
e04c855e6c | ||
|
|
6a627a86b7 | ||
|
|
746df24011 | ||
|
|
da64f71ce2 | ||
|
|
8972b6bfe2 | ||
|
|
8241468fe2 | ||
|
|
e9b740a915 | ||
|
|
548bb6ffd5 | ||
|
|
a5f28d0fcb | ||
|
|
fb8f8f0b08 | ||
|
|
53fdf9a89c | ||
|
|
a1316034c1 | ||
|
|
75df4affa8 | ||
|
|
820c841376 | ||
|
|
34d50710b3 | ||
|
|
d0d62d9493 | ||
|
|
89b2bc2992 | ||
|
|
e019d36221 | ||
|
|
3bcb0c902e | ||
|
|
75b679814b |
82
.coderabbit.yaml
Normal file
82
.coderabbit.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
#
|
||||
# CodeRabbit configuration — references existing guideline files to avoid
|
||||
# duplicating conventions. See:
|
||||
# .github/copilot-instructions.md — project overview & general rules
|
||||
# docs/cpp.instructions.md — C++ coding conventions
|
||||
# docs/web.instructions.md — Web UI coding conventions
|
||||
# docs/cicd.instructions.md — GitHub Actions / CI-CD conventions
|
||||
#
|
||||
# NOTE: This file must be committed (tracked by git) for CodeRabbit to read
|
||||
# it from the repository. If it is listed in .gitignore, CodeRabbit will
|
||||
# not see it and these settings will have no effect.
|
||||
|
||||
language: en-US
|
||||
|
||||
reviews:
|
||||
path_instructions:
|
||||
- path: "**/*.{cpp,h,hpp,ino}"
|
||||
instructions: >
|
||||
Follow the C++ coding conventions documented in docs/cpp.instructions.md
|
||||
and the general project guidelines in .github/copilot-instructions.md.
|
||||
|
||||
Key rules: 2-space indentation (no tabs), camelCase functions/variables,
|
||||
PascalCase classes, UPPER_CASE macros. No C++ exceptions — use return codes and debug macros.
|
||||
|
||||
Hot-path optimization guidelines (attributes, uint_fast types, caching,
|
||||
unsigned range checks) apply from pixel set/get operations and strip.show() downward —
|
||||
NOT to effect functions in FX.cpp, which have diverse contributor styles.
|
||||
|
||||
- path: "wled00/data/**"
|
||||
instructions: >
|
||||
Follow the web UI conventions documented in docs/web.instructions.md.
|
||||
|
||||
Key rules: indent HTML and JavaScript with tabs, CSS with tabs.
|
||||
Files here are built into wled00/html_*.h and wled00/js_*.h by tools/cdata.js — never
|
||||
edit those generated headers directly.
|
||||
|
||||
- path: "wled00/html_*.h"
|
||||
instructions: >
|
||||
These files are auto-generated from wled00/data/ by tools/cdata.js.
|
||||
They must never be manually edited or committed. Flag any PR that
|
||||
includes changes to these files.
|
||||
|
||||
- path: "wled00/js_*.h"
|
||||
instructions: >
|
||||
These files are auto-generated from wled00/data/ by tools/cdata.js.
|
||||
They must never be manually edited or committed. Flag any PR that
|
||||
includes changes to these files.
|
||||
|
||||
- path: "usermods/**"
|
||||
instructions: >
|
||||
Usermods are community add-ons.
|
||||
Each usermod lives in its own directory under usermods/ and is implemented
|
||||
as a .cpp file with a dedicated library.json file to manage dependencies.
|
||||
Follow the same C++ conventions as the core firmware (docs/cpp.instructions.md).
|
||||
|
||||
- path: ".github/workflows/*.{yml,yaml}"
|
||||
instructions: >
|
||||
Follow the CI/CD conventions documented in docs/cicd.instructions.md.
|
||||
|
||||
Key rules: 2-space indentation, descriptive name: on every workflow/job/step.
|
||||
Third-party actions must be pinned to a specific version tag — branch pins
|
||||
such as @main or @master are not allowed. Declare explicit permissions: blocks
|
||||
scoped to least privilege. Never interpolate github.event.* values directly
|
||||
into run: steps — pass them through an env: variable to prevent script
|
||||
injection. Do not use pull_request_target unless fully justified.
|
||||
|
||||
- path: "**/*.instructions.md"
|
||||
instructions: |
|
||||
This file contains both AI-facing rules and human-only reference sections.
|
||||
Human-only sections are enclosed in `<!-- HUMAN_ONLY_START -->` /
|
||||
`<!-- HUMAN_ONLY_END -->` HTML comment markers and should not be used as
|
||||
actionable review criteria.
|
||||
|
||||
When this file is modified in a PR, perform the following alignment check:
|
||||
1. For each `<!-- HUMAN_ONLY_START --> ... <!-- HUMAN_ONLY_END -->` block,
|
||||
verify that its examples and guidance are consistent with (and do not
|
||||
contradict) the AI-facing rules stated in the same file.
|
||||
2. Flag any HUMAN_ONLY section whose content has drifted from the surrounding
|
||||
AI-facing rules due to edits introduced in this PR.
|
||||
3. If new AI-facing rules were added without updating a related HUMAN_ONLY
|
||||
reference section, note this as a suggestion (not a required fix).
|
||||
118
.github/agent-build.instructions.md
vendored
Normal file
118
.github/agent-build.instructions.md
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
applyTo: "**"
|
||||
---
|
||||
# Agent-Mode Build & Test Instructions
|
||||
|
||||
Detailed build workflow, timeouts, and troubleshooting for making code changes in agent mode. Always reference these instructions first when running builds or validating changes.
|
||||
|
||||
## Build Timing and Timeouts
|
||||
|
||||
Use these timeout values when running builds:
|
||||
|
||||
| Command | Typical Time | Minimum Timeout | Notes |
|
||||
|---|---|---|---|
|
||||
| `npm run build` | ~3 s | 30 s | Web UI → `wled00/html_*.h` `wled00/js_*.h` headers |
|
||||
| `npm test` | ~40 s | 2 min | Validates build system |
|
||||
| `npm run dev` | continuous | — | Watch mode, auto-rebuilds on changes |
|
||||
| `pio run -e <env>` | 15–20 min | 30 min | First build downloads toolchains; subsequent builds are faster |
|
||||
|
||||
**NEVER cancel long-running builds.** PlatformIO downloads and compilation require patience.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Code Style Summary
|
||||
- **C++** files in `wled00/` and `usermods/`: 2-space indentation (no tabs), camelCase functions/variables, PascalCase classes, UPPER_CASE macros. No C++ exceptions — use return codes and debug macros.
|
||||
- **Web UI** files in `wled00/data`: indent HTML and JavaScript with tabs, CSS with tabs.
|
||||
- **CI/CD workflows** in `.github/workflows`: 2-space indentation, descriptive `name:` on every workflow/job/step. Third-party actions must be pinned to a specific version tag — branch pins such as `@main` or `@master` are not allowed. SHA pinning recommended.
|
||||
|
||||
### Web UI Changes
|
||||
|
||||
1. Edit files in `wled00/data/`
|
||||
2. Run `npm run build` to regenerate `wled00/html_*.h` `wled00/js_*.h` headers
|
||||
3. Test with local HTTP server (see Manual Testing below)
|
||||
4. Run `npm test` to validate
|
||||
|
||||
### Firmware Changes
|
||||
|
||||
1. Edit files in `wled00/` (but **never** `html_*.h` and `js_*.h` files)
|
||||
2. Ensure web UI is built first: `npm run build`
|
||||
3. Build firmware: `pio run -e esp32dev` (set timeout ≥ 30 min)
|
||||
4. Flash to device: `pio run -e [target] --target upload`
|
||||
|
||||
### Combined Web + Firmware Changes
|
||||
|
||||
1. Always build web UI first
|
||||
2. Test web interface manually
|
||||
3. Then build and test firmware
|
||||
|
||||
|
||||
## Before Finishing Work - Testing
|
||||
|
||||
**You MUST complete ALL of these before marking work as done:**
|
||||
|
||||
1. **Run tests**: `npm test` — must pass
|
||||
2. **Build firmware**: `pio run -e esp32dev` — must succeed after source code changes, **never skip this step**.
|
||||
- Set timeout to 30+ minutes, **never cancel**
|
||||
- Choose `esp32dev` as a common, representative environment
|
||||
- If the build fails, fix the issue before proceeding
|
||||
3. **For web UI changes**: manually test the interface (see below)
|
||||
|
||||
If any step fails, fix the issue. **Do NOT mark work complete with failing builds or tests.**
|
||||
|
||||
## Manual Web UI Testing
|
||||
|
||||
Start a local server:
|
||||
|
||||
```sh
|
||||
cd wled00/data && python3 -m http.server 8080
|
||||
# Open http://localhost:8080/index.htm
|
||||
```
|
||||
|
||||
Test these scenarios after every web UI change:
|
||||
|
||||
- **Load**: `index.htm` loads without JavaScript errors (check browser console)
|
||||
- **Navigation**: switching between main page and settings pages works
|
||||
- **Color controls**: color picker and brightness controls function correctly
|
||||
- **Effects**: effect selection and parameter changes work
|
||||
- **Settings**: form submission and validation work
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Build Issues
|
||||
|
||||
| Problem | Solution |
|
||||
|---|---|
|
||||
| Missing `html_*.h` | Run `npm ci; npm run build` |
|
||||
| Web UI looks broken | Check browser console for JS errors |
|
||||
| PlatformIO network errors | Retry — downloads can be flaky |
|
||||
| Node.js version mismatch | Ensure Node.js 20+ (check `.nvmrc`) |
|
||||
|
||||
### Recovery Steps
|
||||
|
||||
- **Force web UI rebuild**: `npm run build -- -f`
|
||||
- **Clear generated files**: `rm -f wled00/html_*.h wled00/js_*.h` then `npm run build`
|
||||
- **Clean PlatformIO build artifacts**: `pio run --target clean`
|
||||
- **Reinstall Node deps**: `rm -rf node_modules && npm ci`
|
||||
|
||||
## CI/CD Validation
|
||||
|
||||
The GitHub Actions CI workflow will:
|
||||
1. Install Node.js and Python dependencies
|
||||
2. Run `npm test`
|
||||
3. Build web UI (automatic via PlatformIO)
|
||||
4. Compile firmware for **all** `default_envs` targets
|
||||
|
||||
**To ensure CI success, always validate locally:**
|
||||
- Run `npm test` and ensure it passes
|
||||
- Run `pio run -e esp32dev` (or another common firmware environment, see next section) and ensure it completes successfully
|
||||
- If either fails locally, it WILL fail in CI
|
||||
|
||||
Match this workflow in local development to catch failures before pushing.
|
||||
|
||||
## Important Reminders
|
||||
|
||||
- Always **commit source code**
|
||||
- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`
|
||||
- Web UI rebuild is part of the PlatformIO firmware compilation pipeline
|
||||
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`
|
||||
- List all PlatformIO targets: `pio run --list-targets`
|
||||
245
.github/copilot-instructions.md
vendored
245
.github/copilot-instructions.md
vendored
@@ -4,175 +4,132 @@ WLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver
|
||||
|
||||
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
||||
|
||||
## Working Effectively
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
### Initial Setup
|
||||
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
|
||||
- Install dependencies: `npm ci` (takes ~5 seconds)
|
||||
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
|
||||
## Setup
|
||||
|
||||
### Build and Test Workflow
|
||||
- **ALWAYS build web UI first**: `npm run build` -- takes 3 seconds. NEVER CANCEL.
|
||||
- **Run tests**: `npm test` -- takes 40 seconds. NEVER CANCEL. Set timeout to 2+ minutes.
|
||||
- **Development mode**: `npm run dev` -- monitors file changes and auto-rebuilds web UI
|
||||
- **Hardware firmware build**: `pio run -e [environment]` -- takes 15+ minutes. NEVER CANCEL. Set timeout to 30+ minutes.
|
||||
- Node.js 20+ (see `.nvmrc`)
|
||||
- Install dependencies: `npm ci`
|
||||
- PlatformIO (required only for firmware compilation): `pip install -r requirements.txt`
|
||||
|
||||
### Build Process Details
|
||||
The build has two main phases:
|
||||
1. **Web UI Generation** (`npm run build`):
|
||||
- Processes files in `wled00/data/` (HTML, CSS, JS)
|
||||
- Minifies and compresses web content
|
||||
- Generates `wled00/html_*.h` files with embedded web content
|
||||
- **CRITICAL**: Must be done before any hardware build
|
||||
## Build and Test
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
2. **Hardware Compilation** (`pio run`):
|
||||
- Compiles C++ firmware for various ESP32/ESP8266 targets
|
||||
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
|
||||
- List all targets: `pio run --list-targets`
|
||||
| Command | Purpose | Typical Time |
|
||||
|---|---|---|
|
||||
| `npm run build` | Build web UI → generates `wled00/html_*.h` and `wled00/js_*.h` headers | ~3 s |
|
||||
| `npm test` | Run test suite | ~40 s |
|
||||
| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |
|
||||
| `pio run -e <env>` | Build firmware for a hardware target | 15–20 min |
|
||||
|
||||
## Before Finishing Work
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
**CRITICAL: You MUST complete ALL of these steps before marking your work as complete:**
|
||||
- **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).
|
||||
- The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.
|
||||
- **Build firmware to validate code changes**: `pio run -e esp32dev` — must succeed, never skip this step.
|
||||
- Common firmware environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`
|
||||
|
||||
1. **Run the test suite**: `npm test` -- Set timeout to 2+ minutes. NEVER CANCEL.
|
||||
- All tests MUST pass
|
||||
- If tests fail, fix the issue before proceeding
|
||||
For detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).
|
||||
|
||||
2. **Build at least one hardware environment**: `pio run -e esp32dev` -- Set timeout to 30+ minutes. NEVER CANCEL.
|
||||
- Choose `esp32dev` as it's a common, representative environment
|
||||
- See "Hardware Compilation" section above for the full list of common environments
|
||||
- The build MUST complete successfully without errors
|
||||
- If the build fails, fix the issue before proceeding
|
||||
- **DO NOT skip this step** - it validates that firmware compiles with your changes
|
||||
### Usermod Guidelines
|
||||
|
||||
3. **For web UI changes only**: Manually test the interface
|
||||
- See "Manual Testing Scenarios" section below
|
||||
- Verify the UI loads and functions correctly
|
||||
- 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.
|
||||
- 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!
|
||||
- New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/main/wled00/const.h#L160).
|
||||
- To activate a usermod, a custom build configuration should be used. Add the usermod name to `custom_usermods`.
|
||||
|
||||
**If any of these validation steps fail, you MUST fix the issues before finishing. Do NOT mark work as complete with failing builds or tests.**
|
||||
## Project Structure Overview
|
||||
|
||||
## Validation and Testing
|
||||
### Project Branch / Release Structure
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
### Web UI Testing
|
||||
- **ALWAYS validate web UI changes manually**:
|
||||
- Start local server: `cd wled00/data && python3 -m http.server 8080`
|
||||
- Open `http://localhost:8080/index.htm` in browser
|
||||
- Test basic functionality: color picker, effects, settings pages
|
||||
- **Check for JavaScript errors** in browser console
|
||||
```text
|
||||
main # Main development trunk (daily/nightly) 17.0.0-dev
|
||||
├── V5 # special branch: code rework for esp-idf 5.5.x (unstable)
|
||||
├── V5-C6 # special branch: integration of new MCU types: esp32-c5, esp32-c6, esp32-p4 (unstable)
|
||||
16_x # current beta, preparations for next release 16.0.0
|
||||
0_15_x # maintenance (bugfixes only) for current release 0.15.4
|
||||
(tag) v0.14.4 # previous 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
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### Code Validation
|
||||
- **No automated linting configured** - follow existing code style in files you edit
|
||||
- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
|
||||
- **Language**: The repository language is English (british, american, canadian, or australian). If you find other languages, suggest a translation into English.
|
||||
- **C++ formatting available**: `clang-format` is installed but not in CI
|
||||
- **Always run tests before finishing**: `npm test`
|
||||
- **MANDATORY: Always run a hardware build before finishing** (see "Before Finishing Work" section below)
|
||||
|
||||
### Manual Testing Scenarios
|
||||
After making changes to web UI, always test:
|
||||
- **Load main interface**: Verify index.htm loads without errors
|
||||
- **Navigation**: Test switching between main page and settings pages
|
||||
- **Color controls**: Verify color picker and brightness controls work
|
||||
- **Effects**: Test effect selection and parameter changes
|
||||
- **Settings**: Test form submission and validation
|
||||
|
||||
## Common Tasks
|
||||
- ``main``: development trunk (daily/nightly)
|
||||
- ``V5`` and ``V5-C6``: code rework for esp-idf 5.5.x (unstable) - branched from ``main``.
|
||||
- ``0_15_x``: bugfixing / maintenance for release 0.15.x
|
||||
|
||||
### Repository Structure
|
||||
```
|
||||
wled00/ # Main firmware source (C++)
|
||||
├── data/ # Web interface files
|
||||
│ ├── index.htm # Main UI
|
||||
|
||||
tl;dr:
|
||||
* Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.
|
||||
* Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h` — **never edit or commit**.
|
||||
* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.
|
||||
* Usermods: `usermods/` (C++, with individual library.json).
|
||||
* Contributor docs: `docs/` (coding guidelines, etc).
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
Detailed overview:
|
||||
|
||||
```text
|
||||
wled00/ # Main firmware source (C++) "WLED core"
|
||||
├── data/ # Web interface files
|
||||
│ ├── index.htm # Main UI
|
||||
│ ├── settings*.htm # Settings pages
|
||||
│ └── *.js/*.css # Frontend resources
|
||||
├── *.cpp/*.h # Firmware source files
|
||||
└── html_*.h # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)
|
||||
tools/ # Build tools (Node.js)
|
||||
│ └── *.js/*.css # Frontend resources
|
||||
├── *.cpp/*.h # Firmware source files
|
||||
├── html_*.h # Auto-generated embedded web files (DO NOT EDIT, DO NOT COMMIT)
|
||||
├── src/ # Modules used by the WLED core (C++)
|
||||
│ ├── fonts/ # Font libraries for scrolling text effect
|
||||
└ └── dependencies/ # Utility functions - some of them have their own licensing terms
|
||||
lib/ # Project specific custom libraries. PlatformIO will compile them to separate static libraries and link them
|
||||
platformio.ini # Hardware build configuration
|
||||
|
||||
platformio_override.sample.ini # examples for custom build configurations - entries must be copied into platformio_override.ini to use them.
|
||||
# platformio_override.ini is _not_ stored in the WLED repository!
|
||||
usermods/ # User-contributed addons to the WLED core, maintained by individual contributors (C++, with individual library.json)
|
||||
package.json # Node.js dependencies and scripts, release identification
|
||||
pio-scripts/ # Build tools (PlatformIO)
|
||||
tools/ # Build tools (Node.js), partition files, and generic utilities
|
||||
├── cdata.js # Web UI build script
|
||||
└── cdata-test.js # Test suite
|
||||
platformio.ini # Hardware build configuration
|
||||
package.json # Node.js dependencies and scripts
|
||||
docs/ # Contributor docs, coding guidelines
|
||||
.github/workflows/ # CI/CD pipelines
|
||||
```
|
||||
|
||||
### Key Files and Their Purpose
|
||||
- `wled00/data/index.htm` - Main web interface
|
||||
- `wled00/data/settings*.htm` - Configuration pages
|
||||
- `tools/cdata.js` - Converts web files to C++ headers
|
||||
- `wled00/wled.h` - Main firmware configuration
|
||||
- `platformio.ini` - Hardware build targets and settings
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
## General Guidelines
|
||||
|
||||
### Development Workflow (applies to agent mode only)
|
||||
1. **For web UI changes**:
|
||||
- Edit files in `wled00/data/`
|
||||
- Run `npm run build` to regenerate headers
|
||||
- Test with local HTTP server
|
||||
- Run `npm test` to validate build system
|
||||
- **Repository language is English.** Suggest translations for non-English content.
|
||||
- **Use VS Code with PlatformIO extension** for best development experience.
|
||||
- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`.
|
||||
- If updating Web UI files in `wled00/data/`, **make use of common functions in `wled00/data/common.js` whenever possible**.
|
||||
- **When unsure, say so.** Gather more information rather than guessing.
|
||||
- **Acknowledge good patterns** when you see them. Summarize good practices as part of your review - positive feedback always helps.
|
||||
- **Provide references** when making analyses or recommendations. Base them on the correct branch or PR.
|
||||
- **Highlight user-visible breaking changes and ripple effects**. Ask for confirmation that these were introduced intentionally.
|
||||
- **Unused / dead code must be justified or removed**. This helps to keep the codebase clean, maintainable and readable.
|
||||
- **C++ formatting available**: `clang-format` is installed but not in CI
|
||||
- No automated linting is configured — match existing code style in files you edit.
|
||||
|
||||
2. **For firmware changes**:
|
||||
- Edit files in `wled00/` (but NOT `html_*.h` files)
|
||||
- Ensure web UI is built first (`npm run build`)
|
||||
- Build firmware: `pio run -e [target]`
|
||||
- Flash to device: `pio run -e [target] --target upload`
|
||||
Refer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.
|
||||
|
||||
3. **For both web and firmware**:
|
||||
- Always build web UI first
|
||||
- Test web interface manually
|
||||
- Build and test firmware if making firmware changes
|
||||
### 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, mark it with an `// AI: below section was generated by an AI` comment (see C++ guidelines).
|
||||
- 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.
|
||||
|
||||
## Build Timing and Timeouts
|
||||
### Pull Request Expectations
|
||||
|
||||
**IMPORTANT: Use these timeout values when running builds:**
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
- **Web UI build** (`npm run build`): 3 seconds typical - Set timeout to 30 seconds minimum
|
||||
- **Test suite** (`npm test`): 40 seconds typical - Set timeout to 120 seconds (2 minutes) minimum
|
||||
- **Hardware builds** (`pio run -e [target]`): 15-20 minutes typical for first build - Set timeout to 1800 seconds (30 minutes) minimum
|
||||
- Subsequent builds are faster due to caching
|
||||
- First builds download toolchains and dependencies which takes significant time
|
||||
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation require patience
|
||||
### 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.
|
||||
|
||||
**When validating your changes before finishing, you MUST wait for the hardware build to complete successfully. Set the timeout appropriately and be patient.**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Build fails with missing html_*.h**: Run `npm run build` first
|
||||
- **Web UI looks broken**: Check browser console for JavaScript errors
|
||||
- **PlatformIO network errors**: Try again, downloads can be flaky
|
||||
- **Node.js version issues**: Ensure Node.js 20+ is installed (check `.nvmrc`)
|
||||
|
||||
### When Things Go Wrong
|
||||
- **Clear generated files**: `rm -f wled00/html_*.h` then rebuild
|
||||
- **Force web UI rebuild**: `npm run build -- --force` or `npm run build -- -f`
|
||||
- **Clean PlatformIO cache**: `pio run --target clean`
|
||||
- **Reinstall dependencies**: `rm -rf node_modules && npm install`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Always commit source files**
|
||||
- **Web UI re-built is part of the platformio firmware compilation**
|
||||
- **do not commit generated html_*.h files**
|
||||
- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated. If needed, modify Web UI files in `wled00/data/`.
|
||||
- **Test web interface manually after any web UI changes**
|
||||
- When reviewing a PR: the PR author does not need to update/commit generated html_*.h files - these files will be auto-generated when building the firmware binary.
|
||||
- **If you are not sure about something, just answer that you are not sure.** Gather more information instead of continuing with a wild guess.
|
||||
- If asked for an analysis, assessment or web research, **always provide references** to justify your conclusions.
|
||||
- If updating Web UI files in `wled00/data/`, make use of common functions availeable in `wled00/data/common.js` where possible.
|
||||
- **Use VS Code with PlatformIO extension for best development experience**
|
||||
- **Hardware builds require appropriate ESP32/ESP8266 development board**
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
**The GitHub Actions CI workflow will:**
|
||||
1. Installs Node.js and Python dependencies
|
||||
2. Runs `npm test` to validate build system (MUST pass)
|
||||
3. Builds web UI with `npm run build` (automatically run by PlatformIO)
|
||||
4. Compiles firmware for ALL hardware targets listed in `default_envs` (MUST succeed for all)
|
||||
5. Uploads build artifacts
|
||||
|
||||
**To ensure CI success, you MUST locally:**
|
||||
- Run `npm test` and ensure it passes
|
||||
- Run `pio run -e esp32dev` (or another common environment from "Hardware Compilation" section) and ensure it completes successfully
|
||||
- If either fails locally, it WILL fail in CI
|
||||
|
||||
**Match this workflow in your local development to ensure CI success. Do not mark work complete until you have validated builds locally.**
|
||||
|
||||
@@ -9,6 +9,14 @@ We'll work with you to refine your contribution, but we'll also push back if som
|
||||
|
||||
Here are a few suggestions to make it easier for you to contribute:
|
||||
|
||||
### Important Developer Infos
|
||||
* [Project Structure, Files and Directories](.github/copilot-instructions.md#project-structure-overview) (in our AI instructions)
|
||||
* [Instructions for creating usermods](.github/copilot-instructions.md#usermod-guidelines) (in our AI instructions)
|
||||
* KB: [Compiling WLED](https://kno.wled.ge/advanced/compiling-wled/) - slightly outdated but still helpful :-)
|
||||
* Arduino IDE is not supported any more. Use VSCode with the PlatformIO extension.
|
||||
* [Compiling in VSCode/Platformio](https://github.com/wled/WLED-Docs/issues/161) - modern way without command line or platformio.ini changes.
|
||||
* If you add a new feature, consider making a PR to [``wled-docs``](https://github.com/wled/WLED-Docs) for updating our official documentation.
|
||||
|
||||
### PR from a branch in your own fork
|
||||
Start your pull request (PR) in a branch of your own fork. Don't make a PR directly from your main branch.
|
||||
This lets you update your PR if needed, while you can work on other tasks in 'main' or in other branches.
|
||||
@@ -20,7 +28,6 @@ This lets you update your PR if needed, while you can work on other tasks in 'ma
|
||||
>
|
||||
> <img width="295" height="134" alt="image: fork and edit" src="https://github.com/user-attachments/assets/f0dc7567-edcb-4409-a530-cd621ae9661f" />
|
||||
|
||||
|
||||
### Target branch for pull requests
|
||||
|
||||
Please make all PRs against the `main` branch.
|
||||
@@ -167,7 +174,12 @@ AI tools are powerful but "often wrong" - your judgment is essential! 😊
|
||||
|
||||
Don't stress too much about style! When in doubt, just match the style in the files you're editing. 😊
|
||||
|
||||
Here are our main guidelines:
|
||||
Our review bot (coderabbit) has learned lots of detailed guides and hints - it will suggest them automatically when you submit a PR for review.
|
||||
If you are curious, these are the detailed guides:
|
||||
* [C++ Coding](docs/cpp.instructions.md)
|
||||
* [WebUi: HTML, JS, CSS](docs/web.instructions.md)
|
||||
|
||||
Below are the main rules used in the WLED repository:
|
||||
|
||||
#### Indentation
|
||||
|
||||
|
||||
162
docs/cicd.instructions.md
Normal file
162
docs/cicd.instructions.md
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
applyTo: ".github/workflows/*.yml,.github/workflows/*.yaml"
|
||||
---
|
||||
# CI/CD Conventions — GitHub Actions Workflows
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## YAML Style
|
||||
|
||||
- Indent with **2 spaces** (no tabs)
|
||||
- Every workflow, job, and step must have a `name:` field that clearly describes its purpose
|
||||
- Group related steps logically; separate unrelated groups with a blank line
|
||||
- Comments (`#`) are encouraged for non-obvious decisions (e.g., why `fail-fast: false` is set, what a cron expression means)
|
||||
|
||||
## Workflow Structure
|
||||
|
||||
### Triggers
|
||||
|
||||
- Declare `on:` triggers explicitly; avoid bare `on: push` without branch filters on long-running or expensive jobs
|
||||
- Prefer `workflow_call` for shared build logic (see `build.yml`) to avoid duplicating steps across workflows
|
||||
- Document scheduled triggers (`cron:`) with a human-readable comment:
|
||||
|
||||
```yaml
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC daily
|
||||
```
|
||||
|
||||
### Jobs
|
||||
|
||||
- Express all inter-job dependencies with `needs:` — never rely on implicit ordering
|
||||
- Use job `outputs:` + step `id:` to pass structured data between jobs (see `get_default_envs` in `build.yml`)
|
||||
- Set `fail-fast: false` on matrix builds so that a single failing environment does not cancel others
|
||||
|
||||
### Runners
|
||||
|
||||
- Pin to a specific Ubuntu version (`ubuntu-22.04`, `ubuntu-24.04`) rather than `ubuntu-latest` for reproducible builds
|
||||
- Only use `ubuntu-latest` in jobs where exact environment reproducibility is not required (e.g., trivial download/publish steps)
|
||||
|
||||
### Tool and Language Versions
|
||||
|
||||
- Pin tool versions explicitly:
|
||||
```yaml
|
||||
python-version: '3.12'
|
||||
```
|
||||
- Do not rely on the runner's pre-installed tool versions — always install via a versioned setup action
|
||||
|
||||
### Caching
|
||||
|
||||
- Always cache package managers and build tool directories when the job installs dependencies:
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
```
|
||||
- Include the environment name or a relevant identifier in cache keys when building multiple targets
|
||||
|
||||
### Artifacts
|
||||
|
||||
- Name artifacts with enough context to be unambiguous (e.g., `firmware-${{ matrix.environment }}`)
|
||||
- Avoid uploading artifacts that will never be consumed downstream
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
Important: Several current workflows still violate parts of the baseline below - migration is in progress.
|
||||
|
||||
### Permissions — Least Privilege
|
||||
|
||||
Declare explicit `permissions:` blocks. The default token permissions are broad; scope them to the minimum required:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read # for checkout
|
||||
```
|
||||
|
||||
For jobs that publish releases or write to the repository:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: write # create/update releases
|
||||
```
|
||||
|
||||
A common safe baseline for build-only jobs:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read
|
||||
```
|
||||
|
||||
### Supply Chain — Action Pinning
|
||||
|
||||
**Third-party actions** (anything outside the `actions/` and `github/` namespaces) should be pinned to a specific release tag. Branch pins (`@main`, `@master`) are **not allowed** — they can be updated by the action author at any time without notice:
|
||||
|
||||
```yaml
|
||||
# ✅ Acceptable — specific version tag. SHA pinning recommended for more security, as @v2 is still a mutable tag.
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
# ❌ Not acceptable — mutable branch reference
|
||||
uses: andelf/nightly-release@main
|
||||
```
|
||||
|
||||
SHA pinning (e.g., `uses: someorg/some-action@abc1234`) is the most secure option for third-party actions; it is recommended when auditing supply-chain risk is a priority. At minimum, always use a specific version tag.
|
||||
|
||||
**First-party actions** (`actions/checkout`, `actions/cache`, `actions/upload-artifact`, etc.) pinned to a major version tag (e.g., `@v4`) are acceptable because GitHub maintains and audits these.
|
||||
|
||||
When adding a new third-party action:
|
||||
1. Check that the action's repository is actively maintained
|
||||
2. Review the action's source before adding it
|
||||
3. Prefer well-known, widely-used actions over obscure ones
|
||||
|
||||
### Credentials and Secrets
|
||||
|
||||
- Use `${{ secrets.GITHUB_TOKEN }}` for operations within the same repository — it is automatically scoped and rotated
|
||||
- Never commit secrets, tokens, or passwords into workflow files or any tracked file
|
||||
- Never print secrets in `run:` steps, even with `echo` — GitHub masks known secrets but derived values are not automatically masked
|
||||
- Scope secrets to the narrowest step that needs them using `env:` at the step level, not at the workflow level:
|
||||
|
||||
```yaml
|
||||
# ✅ Scoped to the step that needs it
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ❌ Unnecessarily broad
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
- Personal Access Tokens (PATs, stored as repository secrets) should have the minimum required scopes and should be rotated periodically
|
||||
|
||||
### Script Injection
|
||||
|
||||
`${{ }}` expressions are evaluated before the shell script runs. If an expression comes from untrusted input (PR titles, issue bodies, branch names from forks), it can inject arbitrary shell commands.
|
||||
|
||||
**Never** interpolate `github.event.*` values directly into a `run:` step:
|
||||
|
||||
```yaml
|
||||
# ❌ Injection risk — PR title is attacker-controlled
|
||||
- run: echo "${{ github.event.pull_request.title }}"
|
||||
|
||||
# ✅ Safe — value passed through an environment variable
|
||||
- env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
run: echo "$PR_TITLE"
|
||||
```
|
||||
|
||||
This rule applies to any value that originates outside the repository (issue bodies, labels, comments, commit messages from forks).
|
||||
|
||||
### Pull Request Workflows
|
||||
|
||||
- Workflows triggered by `pull_request` from a fork run with **read-only** token permissions and no access to repository secrets — this is intentional and correct
|
||||
- Do not use `pull_request_target` unless you fully understand the security implications; it runs in the context of the base branch and *does* have secret access, making it a common attack surface
|
||||
524
docs/cpp.instructions.md
Normal file
524
docs/cpp.instructions.md
Normal file
@@ -0,0 +1,524 @@
|
||||
---
|
||||
applyTo: "**/*.cpp,**/*.h,**/*.hpp,**/*.ino"
|
||||
---
|
||||
# C++ Coding Conventions
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hiding this reference, to avoid cyclic "include" loops -->
|
||||
See also: [CONTRIBUTING.md](../CONTRIBUTING.md) for general style guidelines that apply to all contributors.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## Formatting
|
||||
|
||||
- Indent with **2 spaces** (no tabs in C++ files)
|
||||
- Opening braces on the same line is preferred (K&R style). Brace on a separate line (Allman style) is acceptable
|
||||
- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`
|
||||
- Space between keyword and parenthesis: `if (...)`, `for (...)`. No space between function name and parenthesis: `doStuff(a)`
|
||||
- No enforced line-length limit; wrap when a line exceeds your editor width
|
||||
|
||||
## Naming
|
||||
|
||||
- **camelCase** for functions and variables: `setValuesFromMainSeg()`, `effectCurrent`
|
||||
- **PascalCase** for classes and structs: `PinManagerClass`, `BusConfig`
|
||||
- **UPPER_CASE** for macros and constants: `WLED_MAX_USERMODS`, `DEFAULT_CLIENT_SSID`
|
||||
|
||||
## General
|
||||
|
||||
- Follow the existing style in the file you are editing
|
||||
- If possible, use `static` for local (C-style) variables and functions (keeps the global namespace clean)
|
||||
- Avoid unexplained "magic numbers". Prefer named constants (`constexpr`) or C-style `#define` constants for repeated numbers that have the same meaning
|
||||
- Include `"wled.h"` as the primary project header where needed
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## Header Guards
|
||||
|
||||
Most headers use `#ifndef` / `#define` guards. Some newer headers add `#pragma once` before the guard:
|
||||
|
||||
```cpp
|
||||
#ifndef WLED_EXAMPLE_H
|
||||
#define WLED_EXAMPLE_H
|
||||
// ...
|
||||
#endif // WLED_EXAMPLE_H
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## Comments
|
||||
|
||||
- `//` for inline comments, `/* ... */` for block comments. Always put a space after `//`
|
||||
- **AI attribution:** When a larger block of code is generated by an AI tool, mark it with an `// AI:` comment so reviewers know to scrutinize it:
|
||||
|
||||
```cpp
|
||||
// AI: below section was generated by an AI
|
||||
void calculateCRC(const uint8_t* data, size_t len) {
|
||||
...
|
||||
}
|
||||
// AI: end of AI-generated section
|
||||
```
|
||||
|
||||
Single-line AI-assisted edits do not need the marker — use it when the AI produced a contiguous block that a human did not write line-by-line.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hidden from AI for now, as it created too many "please add a description" review findings in my first tests -->
|
||||
- **Function & feature comments:** Every non-trivial function should have a brief comment above it describing what it does. Include a note about each parameter when the names alone are not self-explanatory:
|
||||
|
||||
```cpp
|
||||
/* *****
|
||||
* Apply gamma correction to a single color channel.
|
||||
* @param value raw 8-bit channel value (0–255)
|
||||
* @param gamma gamma exponent (typically 2.8)
|
||||
* @return corrected 8-bit value
|
||||
***** */
|
||||
uint8_t gammaCorrect(uint8_t value, float gamma);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Short accessor-style functions (getters/setters, one-liners) may skip this if their purpose is obvious from the name.
|
||||
|
||||
## Preprocessor & Feature Flags
|
||||
|
||||
- Prefer compile-time feature flags (`#ifdef` / `#ifndef`) over runtime checks where possible
|
||||
- Platform differentiation: `ARDUINO_ARCH_ESP32` vs `ESP8266`
|
||||
- PSRAM availability: `BOARD_HAS_PSRAM`
|
||||
|
||||
## Error Handling
|
||||
|
||||
- `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` for developer diagnostics (compiled out unless `-D WLED_DEBUG`)
|
||||
- Don't rely on C++ exceptions — use return codes (`-1` / `false` for errors) and global flags (e.g. `errorFlag = ERR_LOW_MEM`). Some builds don't support C++ exceptions.
|
||||
|
||||
## Strings
|
||||
|
||||
- Use `const char*` for temporary/parsed strings
|
||||
- Avoid `String` (Arduino heap-allocated string) in hot paths; acceptable in config/setup code
|
||||
- Use `F("string")` for string constants (major RAM win on ESP8266; mostly overload/type compatibility on ESP32)
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
On **ESP8266** this explicitly stores the string in flash (PROGMEM), saving precious RAM — every byte counts on that platform.
|
||||
On **ESP32**, `PROGMEM` is a no-op and string literals already reside in flash/rodata, so `F()` yields little RAM benefit but remains harmless (it satisfies `__FlashStringHelper*` overloads that some APIs expect).
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
```cpp
|
||||
DEBUG_PRINTLN(F("WS client connected.")); // string stays in flash, not RAM
|
||||
DEBUG_PRINTF_P(PSTR("initE: Ignoring attempt for invalid ethernetType (%d)\n"), ethernetType); // format string stays in flash
|
||||
```
|
||||
|
||||
## Memory
|
||||
|
||||
- **PSRAM-aware allocation**: use `d_malloc()` (prefer DRAM), `p_malloc()` (prefer PSRAM) from `fcn_declare.h`
|
||||
- **Avoid Variable Length Arrays (VLAs)**: FreeRTOS task stacks are typically 2–8 KB. A runtime-sized VLA can silently exhaust the stack. Use fixed-size arrays or heap allocation (`d_malloc` / `p_malloc`). Any VLA must be explicitly justified in source or PR.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
GCC/Clang support VLAs as an extension (they are not part of the C++ standard), so they look like a legitimate feature — but they are allocated on the stack at runtime. On ESP32/ESP8266, a VLA whose size depends on a runtime parameter (segment dimensions, pixel counts, etc.) can silently exhaust the stack and cause the program to behave in unexpected ways or crash.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- **Larger buffers** (LED data, JSON documents) should use PSRAM when available and technically feasible
|
||||
- **Hot-path**: some data should stay in DRAM or IRAM for performance reasons
|
||||
- Memory efficiency matters, but is less critical on boards with PSRAM
|
||||
|
||||
Heap fragmentation is a concern:
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
- Fragmentation can lead to crashes, even when the overall amount of available heap is still good. The C++ runtime doesn't do any "garbage collection".
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- Avoid frequent `d_malloc` and `d_free` inside a function, especially for small sizes.
|
||||
- Avoid frequent creation / destruction of objects.
|
||||
- Allocate buffers early, and try to re-use them.
|
||||
- Instead of incrementally appending to a `String`, reserve the expected max buffer upfront by using the `reserve()` method.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
```cpp
|
||||
String result;
|
||||
result.reserve(65); // pre-allocate to avoid realloc fragmentation
|
||||
```
|
||||
|
||||
```cpp
|
||||
// prefer DRAM; falls back gracefully and enforces MIN_HEAP_SIZE guard
|
||||
_ledsDirty = (byte*) d_malloc(getBitArrayBytes(_len));
|
||||
```
|
||||
|
||||
```cpp
|
||||
_mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation - does not increase size()
|
||||
_modeData.reserve(_modeCount); // allocate memory to prevent initial fragmentation - does not increase size()
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## `const` and `constexpr`
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
`const` is a promise to the compiler that a value (or object) will not change - a function declared with a `const char* message` parameter is not allowed to modify the content of `message`.
|
||||
This pattern enables optimizations and makes intent clear to reviewers.
|
||||
|
||||
`constexpr` allows to define constants that are *guaranteed* to be evaluated by the compiler (zero run-time costs).
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- For function parameters that are read-only, prefer `const &` or `const`.
|
||||
|
||||
### `const` locals
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
* Adding `const` to a local variable that is only assigned once is optional, but *not* strictly necessary.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
* In hot-path code, `const` on cached locals may help the compiler keep values in registers.
|
||||
```cpp
|
||||
const uint_fast16_t cols = vWidth();
|
||||
const uint_fast16_t rows = vHeight();
|
||||
```
|
||||
|
||||
### `const` references to avoid copies
|
||||
- Pass objects by `const &` (or `&`) instead of copying them implicitly.
|
||||
- Use `const &` (or `&`) inside loops - This avoids constructing temporary objects on every access.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
const auto &m = _mappings[i]; // reference, not a copy (bus_manager.cpp)
|
||||
Segment& sourcesegment = strip.getSegment(sourceid); // alias — avoids creating a temporary Segment instance
|
||||
```
|
||||
|
||||
For function parameters that are read-only, prefer `const &`:
|
||||
```cpp
|
||||
BusManager::add(const BusConfig &bc, bool placeholder) {
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- Class **Data Members:** Avoid reference data members (`T&` or `const T&`) in a class.
|
||||
A reference member can outlive the object it refers to, causing **dangling reference** bugs that are hard to diagnose. Prefer value storage or use a pointer and document the expected lifetime.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hidden from AI for now - codebase is not compliant to this rule (slowly migrating) -->
|
||||
### `constexpr` over `#define`
|
||||
|
||||
- Prefer `constexpr` for compile-time constants. Unlike `#define`, `constexpr` respects scope and type safety, keeping the global namespace clean.
|
||||
|
||||
```cpp
|
||||
// Prefer:
|
||||
constexpr uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
constexpr int WLED_MAX_BUSSES = WLED_MAX_DIGITAL_CHANNELS + WLED_MAX_ANALOG_CHANNELS;
|
||||
|
||||
// Avoid (when possible):
|
||||
#define TWO_CHANNEL_MASK 0x00FF00FF
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### `static_assert` over `#error`
|
||||
|
||||
- Use `static_assert` instead of the C-style `#if … #error … #endif` pattern when validating compile-time constants. It provides a clear message and works with `constexpr` values.
|
||||
- `#define` and `#if ... #else ... #endif` is still needed for conditional-compilation guards and build-flag-overridable values.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
// Prefer:
|
||||
constexpr int WLED_MAX_BUSSES = WLED_MAX_DIGITAL_CHANNELS + WLED_MAX_ANALOG_CHANNELS;
|
||||
static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
|
||||
// Avoid:
|
||||
#if (WLED_MAX_BUSSES > 32)
|
||||
#error "WLED_MAX_BUSSES exceeds hard limit"
|
||||
#endif
|
||||
```
|
||||
|
||||
```cpp
|
||||
// using static_assert() to validate enumerated types (zero cost at runtime)
|
||||
static_assert(0u == static_cast<uint8_t>(PinOwner::None),
|
||||
"PinOwner::None must be zero, so default array initialization works as expected");
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### `static` and `const` class methods
|
||||
|
||||
#### `const` member functions
|
||||
|
||||
Marking a member function `const` tells the compiler that it does not modify the object's state:
|
||||
|
||||
```cpp
|
||||
uint16_t length() const { return _len; }
|
||||
bool isActive() const { return _active; }
|
||||
```
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
Benefits for GCC/Xtensa/RISC-V:
|
||||
- The compiler knows the method cannot write to `this`, so it is free to **keep member values in registers** across the call and avoid reload barriers.
|
||||
- `const` methods can be called on `const` objects and `const` references — essential when passing large objects as `const &` to avoid copying.
|
||||
- `const` allows the compiler to **eliminate redundant loads**: if a caller already has a member value cached, the compiler can prove the `const` call cannot invalidate it.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Declare getter, query, or inspection methods `const`. If you need to mark a member `mutable` to work around this (e.g. for a cache or counter), document the reason.
|
||||
|
||||
#### `static` member functions
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
A `static` member function has no implicit `this` pointer. This has two distinct advantages:
|
||||
|
||||
1. **Smaller code, faster calls**: no `this` is passed in a register. On Xtensa and RISC-V, this removes one register argument from every call site and prevents the compiler from emitting `this`-preservation code around inlined blocks.
|
||||
2. **Better inlining**: GCC can inline a `static` method with more certainty because it cannot be overridden by a derived class (no virtual dispatch ambiguity) and has no aliasing concern through `this`.
|
||||
|
||||
Use `static` for any method that does not need access to instance members:
|
||||
|
||||
```cpp
|
||||
// Factory / utility — no instance needed:
|
||||
static BusConfig fromJson(JsonObject obj);
|
||||
|
||||
// Pure computation helpers:
|
||||
static uint8_t gamma8(uint8_t val);
|
||||
static uint32_t colorBalance(uint32_t color, uint8_t r, uint8_t g, uint8_t b);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
`static` communicates intent clearly: a reviewer immediately knows the method is stateless and safe to call without a fully constructed object.
|
||||
|
||||
> **Rule of thumb**: if a method does not read or write any member variable, make it `static`. If it only reads member variables, make it `const`. Note: `static` methods cannot also be `const`-qualified because there is no implicit `this` pointer to be const — just use `static`. Both qualifiers reduce coupling and improve generated code on all ESP32 targets.
|
||||
|
||||
---
|
||||
|
||||
## Hot-Path Optimization
|
||||
|
||||
The hot path is the per-frame pixel pipeline: **Segment → Strip → BusManager → Bus(Digital,HUB75,Network) or PolyBus → LED driver, plus ``WS2812FX::show()`` and below**.
|
||||
Speed is the priority here. The patterns below are taken from existing hot-path code (`FX_fcn.cpp`, `FX_2Dfcn.cpp`, `bus_manager.cpp`, `colors.cpp`) and should be followed when modifying these files.
|
||||
|
||||
Note: `FX.cpp` (effect functions) is written by many contributors and has diverse styles — that is acceptable.
|
||||
|
||||
### Function Attributes
|
||||
|
||||
Stack the appropriate attributes on hot-path functions. Defined in `const.h`:
|
||||
|
||||
| Attribute | Meaning | When to use |
|
||||
|---|---|---|
|
||||
| `__attribute__((hot))` | Branch-prediction hint | hot-path functions with complex logic |
|
||||
| `IRAM_ATTR` | Place in fast IRAM (ESP32) | Critical per-pixel functions (e.g. `BusDigital::setPixelColor`) |
|
||||
| `IRAM_ATTR_YN` | IRAM on ESP32, no-op on ESP8266 | Hot functions that ESP8266 can't fit in IRAM |
|
||||
| `WLED_O2_ATTR` | Force `-O2` optimization | Most hot-path functions |
|
||||
| `WLED_O3_ATTR` | Force `-O3,fast-math` | Innermost color math (e.g. `color_blend`) |
|
||||
| `[[gnu::hot]] inline` | Modern C++ attribute + inline | Header-defined accessors (e.g. `progress()`, `currentBri()`) |
|
||||
|
||||
Note: `WLED_O3_ATTR` sometimes causes performance loss compared to `WLED_O2_ATTR`. Choose optimization levels based on test results.
|
||||
|
||||
Example signature:
|
||||
|
||||
```cpp
|
||||
void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(unsigned i, uint32_t c)
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Cache Members to Locals Before Loops
|
||||
|
||||
Copy class members and virtual-call results to local variables before entering a loop:
|
||||
|
||||
```cpp
|
||||
uint_fast8_t count = numBusses; // avoid repeated member access
|
||||
for (uint_fast8_t i = 0; i < count; i++) {
|
||||
Bus* const b = busses[i]; // const pointer hints to compiler
|
||||
uint_fast16_t bstart = b->getStart();
|
||||
uint_fast16_t blen = b->getLength();
|
||||
...
|
||||
}
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### Unsigned Range Check
|
||||
|
||||
Replace two-comparison range tests with a single unsigned subtraction:
|
||||
|
||||
```cpp
|
||||
// Instead of: if (pix >= bstart && pix < bstart + blen)
|
||||
if ((uint_fast16_t)(pix - bstart) < blen) // also catches negative pix via unsigned underflow
|
||||
```
|
||||
|
||||
### Early Returns
|
||||
|
||||
Guard every hot-path function with the cheapest necessary checks first:
|
||||
|
||||
```cpp
|
||||
if (!isActive()) return; // inactive segment
|
||||
if (unsigned(i) >= vLength()) return; // bounds check (catches negative i too)
|
||||
```
|
||||
|
||||
### Avoid Nested Calls — Fast Path / Complex Path
|
||||
|
||||
Avoid calling non-inline functions or making complex decisions inside per-pixel hot-path code. When a function has both a common simple case and a rare complex case, split it into two variants and choose once per frame rather than per pixel.
|
||||
|
||||
General rules:
|
||||
- Keep fast-path functions free of non-inline calls, multi-way branches, and complex switch-case decisions.
|
||||
- Hoist per-frame decisions (e.g. simple vs. complex segment) out of the per-pixel loop.
|
||||
- Code duplication between fast/slow variants is acceptable to keep the fast path lean.
|
||||
|
||||
### Function Pointers to Eliminate Repeated Decisions
|
||||
|
||||
When the same decision (e.g. "which drawing routine?") would be evaluated for every pixel, assign the chosen variant to a function pointer once and let the inner loop call through the pointer. This removes the branch entirely — the calling code (e.g. the GIF decoder loop) only ever invokes one function per frame, with no per-pixel decision.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
`image_loader.cpp` demonstrates the pattern: `calculateScaling()` picks the best drawing callback once per frame based on segment dimensions and GIF size, then passes it to the decoder via `setDrawPixelCallback()`:
|
||||
|
||||
```cpp
|
||||
// calculateScaling() — called once per frame
|
||||
if ((perPixelX < 2) && (perPixelY < 2))
|
||||
decoder.setDrawPixelCallback(drawPixelCallbackDownScale2D); // downscale-only variant
|
||||
else
|
||||
decoder.setDrawPixelCallback(drawPixelCallback2D); // full-scaling variant
|
||||
```
|
||||
|
||||
Each callback is a small, single-purpose function with no internal branching — the decoder's per-pixel loop never re-evaluates which strategy to use.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Template Specialization (Advanced)
|
||||
|
||||
Templates can eliminate runtime decisions by generating separate code paths at compile time. For example, a pixel setter could be templated on color order or channel count so the compiler removes dead branches and produces tight, specialized machine code:
|
||||
|
||||
```cpp
|
||||
template<bool hasWhite>
|
||||
void setChannel(uint8_t* out, uint32_t col) {
|
||||
out[0] = R(col); out[1] = G(col); out[2] = B(col);
|
||||
if constexpr (hasWhite) out[3] = W(col); // compiled out when hasWhite==false
|
||||
}
|
||||
```
|
||||
|
||||
Use sparingly — each instantiation duplicates code in flash. On ESP8266 and small-flash ESP32 boards this can exhaust IRAM/flash. Prefer templates only when the hot path is measurably faster and the number of instantiations is small (2–4).
|
||||
|
||||
### RAII Lock-Free Synchronization (Advanced)
|
||||
|
||||
Where contention is rare and the critical section is short, consider replacing mutex-based locking with lock-free techniques using `std::atomic` and RAII scoped guards. A scoped guard sets a flag on construction and clears it on destruction, guaranteeing cleanup even on early return:
|
||||
|
||||
```cpp
|
||||
struct ScopedBusyFlag {
|
||||
std::atomic<bool>& flag;
|
||||
bool acquired;
|
||||
ScopedBusyFlag(std::atomic<bool>& f) : flag(f), acquired(false) {
|
||||
bool expected = false;
|
||||
acquired = flag.compare_exchange_strong(expected, true);
|
||||
}
|
||||
~ScopedBusyFlag() { if (acquired) flag.store(false); }
|
||||
explicit operator bool() const { return acquired; }
|
||||
};
|
||||
|
||||
// Usage
|
||||
static std::atomic<bool> busySending{false};
|
||||
ScopedBusyFlag guard(busySending);
|
||||
if (!guard) return; // another task is already sending
|
||||
// ... do work — flag auto-clears when guard goes out of scope
|
||||
```
|
||||
|
||||
This avoids FreeRTOS semaphore overhead and the risk of forgetting to return a semaphore. There are no current examples of this pattern in the codebase — consult with maintainers before introducing it in new code, to ensure it aligns with the project's synchronization conventions.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Pre-Compute Outside Loops
|
||||
|
||||
Move invariant calculations before the loop. Pre-compute reciprocals to replace division with multiplication.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
const uint_fast16_t cols = virtualWidth();
|
||||
const uint_fast16_t rows = virtualHeight();
|
||||
uint_fast8_t fadeRate = (255U - rate) >> 1;
|
||||
float mappedRate_r = 1.0f / (float(fadeRate) + 1.1f); // reciprocal — avoid division inside loop
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Parallel Channel Processing
|
||||
|
||||
Process R+B and W+G channels simultaneously using the two-channel mask pattern:
|
||||
|
||||
```cpp
|
||||
constexpr uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK;
|
||||
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK;
|
||||
return rb | wg;
|
||||
```
|
||||
|
||||
### Bit Shifts Over Division (mainly for RISC-V boards)
|
||||
|
||||
ESP32 and ESP32-S3 (Xtensa core) have a fast "integer divide" instruction, so manual shifts rarely help.
|
||||
On RISC-V targets (ESP32-C3/C6/P4) and ESP8266, prefer explicit bit-shifts for power-of-two arithmetic — the compiler does **not** always convert divisions to shifts.
|
||||
Always use unsigned operands for right shifts; signed right-shift is implementation-defined.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
On RISC-V-based boards (ESP32-C3, ESP32-C6, ESP32-C5) explicit shifts can be beneficial.
|
||||
```cpp
|
||||
position >> 3 // instead of position / 8
|
||||
(255U - rate) >> 1 // instead of (255 - rate) / 2
|
||||
i & 0x0007 // instead of i % 8
|
||||
```
|
||||
|
||||
**Important**: The bit-shifted expression should be unsigned. On some MCUs, "signed right-shift" is implemented by an "arithmetic shift right" that duplicates the sign bit: ``0b1010 >> 1 = 0b1101``.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Static Caching for Expensive Computations
|
||||
|
||||
Cache results in static locals when the input rarely changes between calls:
|
||||
|
||||
```cpp
|
||||
static uint16_t lastKelvin = 0;
|
||||
static byte correctionRGB[4] = {255,255,255,0};
|
||||
if (lastKelvin != kelvin) {
|
||||
colorKtoRGB(kelvin, correctionRGB); // expensive — only recalculate when input changes
|
||||
lastKelvin = kelvin;
|
||||
}
|
||||
```
|
||||
|
||||
### Inlining Strategy
|
||||
|
||||
- Move frequently-called small functions to headers for inlining (e.g. `Segment::setPixelColorRaw` is in `FX.h`)
|
||||
- Use `static inline` for file-local helpers
|
||||
|
||||
### Math & Trigonometric Functions
|
||||
|
||||
- WLED uses a custom `fastled_slim` library. The old FastLED trig aliases (`sin8`, `cos8`, `sin16`, `cos16`) **no longer exist and cause a compile error** — use `sin8_t()`, `cos8_t()`, `sin16_t()`, `cos16_t()` instead. For float approximations use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`. Replace FastLED noise aliases (`inoise8`, `inoise16`) with `perlin8`, `perlin16`.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
| ❌ Do not use (compile error) | ✅ Use instead | Source |
|
||||
|---|---|---|
|
||||
| `sin8()`, `cos8()` | `sin8_t()`, `cos8_t()` | `fastled_slim.h` → `wled_math.cpp` |
|
||||
| `sin16()`, `cos16()` | `sin16_t()`, `cos16_t()` | `fastled_slim.h` → `wled_math.cpp` |
|
||||
| `sinf()`, `cosf()` | `sin_approx()`, `cos_approx()` | `wled_math.cpp` |
|
||||
| `atan2f()`, `atan2()` | `atan2_t()` | `wled_math.cpp` |
|
||||
| `sqrt()` on integers | `sqrt32_bw()` | `fcn_declare.h` → `wled_math.cpp` |
|
||||
| `sqrtf()` on floats | `sqrtf()` (acceptable) | — no WLED replacement |
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
---
|
||||
|
||||
## `delay()` vs `yield()` in ESP32 Tasks
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
* On ESP32, `delay(ms)` calls `vTaskDelay(ms / portTICK_PERIOD_MS)`, which **suspends only the calling task**. The FreeRTOS scheduler immediately runs all other ready tasks.
|
||||
* The Arduino `loop()` function runs inside `loopTask`. Calling `delay()` there does *not* block the network stack, audio FFT, LED DMA, nor any other FreeRTOS task.
|
||||
* This differs from ESP8266, where `delay()` stalls the entire system unless `yield()` was called inside.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
- On ESP32, `delay()` is generally allowed, as it helps to efficiently manage CPU usage of all tasks.
|
||||
- On ESP8266, only use `delay()` and `yield()` in the main `loop()` context. If not sure, protect with `if (can_yield()) ...`.
|
||||
- Do *not* use `delay()` in effects (FX.cpp) or in the hot pixel path.
|
||||
- `delay()` on the bus-level is allowed, it might be needed to achieve exact timing in LED drivers.
|
||||
|
||||
### IDLE Watchdog and Custom Tasks on ESP32
|
||||
|
||||
- In arduino-esp32, `yield()` calls `vTaskDelay(0)`, which only switches to tasks at equal or higher priority — the IDLE task (priority 0) is never reached.
|
||||
- **Do not use `yield()` to pace ESP32 tasks or assume it feeds any watchdog**.
|
||||
- **Custom `xTaskCreate()` tasks must call `delay(1)` in their loop, not `yield()`.** Without a real blocking call, the IDLE task is starved. The IDLE watchdog panic is the first visible symptom — but the damage starts earlier: deleted task memory leaks, software timers stop firing, light sleep is disabled, and Wi-Fi/BT idle hooks don't run. Structure custom tasks like this:
|
||||
```cpp
|
||||
// WRONG — IDLE task is never scheduled; yield() does not feed the idle task watchdog.
|
||||
void myTask(void*) {
|
||||
for (;;) {
|
||||
doWork();
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
// CORRECT — delay(1) suspends the task for ≥1 ms, IDLE task runs, IDLE watchdog is fed
|
||||
void myTask(void*) {
|
||||
for (;;) {
|
||||
doWork();
|
||||
delay(1); // DO NOT REMOVE — lets IDLE(0) run and feeds its watchdog
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Prefer blocking FreeRTOS primitives (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) over `delay(1)` polling where precise timing or event-driven behaviour is needed.
|
||||
- **Watchdog note.** WLED disables the Task Watchdog by default (`WLED_WATCHDOG_TIMEOUT 0` in `wled.h`). When enabled, `esp_task_wdt_reset()` is called at the end of each `loop()` iteration. Long blocking operations inside `loop()` — such as OTA downloads or slow file I/O — must call `esp_task_wdt_reset()` periodically, or be restructured so the main loop is not blocked for longer than the configured timeout.
|
||||
|
||||
## Caveats and Pitfalls
|
||||
|
||||
- **LittleFS filenames**: File paths passed to `file.open()` must not exceed 255 bytes (`LFS_NAME_MAX`). Validate constructed paths (e.g., `/ledmap_` + segment name + `.json`) stay within this limit (assume standard configurations, like WLED_MAX_SEGNAME_LEN = 64).
|
||||
|
||||
- **Float-to-unsigned conversion is undefined behavior when the value is out of range.** Converting a negative `float` directly to an unsigned integer type (`uint8_t`, `uint16_t`, …) is UB per the C++ standard — the Xtensa (ESP32) toolchain may silently wrap, but RISC-V (ESP32-C3/C5/C6/P4) can produce different results due to clamping. Cast through a signed integer first:
|
||||
```cpp
|
||||
// Undefined behavior — avoid:
|
||||
uint8_t angle = 40.74f * atan2f(dy, dx); // negative float → uint8_t is UB
|
||||
|
||||
// Correct — cast through int first:
|
||||
// atan2f returns [-π..+π], scaled ≈ [-128..+128] as int; uint8_t wraps negative ints via 2's complement (e.g. -1 → 255)
|
||||
uint8_t angle = int(40.74f * atan2f(dy, dx)); // float→int (defined), int→uint8_t (defined)
|
||||
```
|
||||
30
docs/web.instructions.md
Normal file
30
docs/web.instructions.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
applyTo: "wled00/data/**"
|
||||
---
|
||||
# Web UI Coding Conventions
|
||||
|
||||
## Formatting
|
||||
|
||||
- Indent **HTML and JavaScript** with **tabs**
|
||||
- Indent **CSS** with **tabs**
|
||||
|
||||
## JavaScript Style
|
||||
|
||||
- **camelCase** for functions and variables: `gId()`, `selectedFx`, `currentPreset`
|
||||
- Abbreviated helpers are common: `d` for `document`, `gId()` for `getElementById()`
|
||||
|
||||
## Key Files
|
||||
|
||||
- `index.htm` — main interface
|
||||
- `index.js` — functions that manage / update the main interface
|
||||
- `settings*.htm` — configuration pages
|
||||
- `*.css` — stylesheets (inlined during build)
|
||||
- `common.js` — helper functions
|
||||
|
||||
**Reuse shared helpers from `common.js` whenever possible** instead of duplicating utilities in page-local scripts.
|
||||
|
||||
## Build Integration
|
||||
|
||||
Files in this directory are processed by `tools/cdata.js` into generated headers
|
||||
(`wled00/html_*.h`, `wled00/js_*.h`).
|
||||
Run `npm run build` after any change. **Never edit generated headers directly.**
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "16.0.0-alpha",
|
||||
"version": "17.0.0-dev",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wled",
|
||||
"version": "16.0.0-alpha",
|
||||
"version": "17.0.0-dev",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
@@ -132,9 +132,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
|
||||
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
@@ -611,9 +611,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "16.0.0-alpha",
|
||||
"version": "17.0.0-dev",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
||||
@@ -38,7 +38,7 @@ pyelftools==0.32
|
||||
# via platformio
|
||||
pyserial==3.5
|
||||
# via platformio
|
||||
requests==2.32.4
|
||||
requests==2.33.0
|
||||
# via platformio
|
||||
semantic-version==2.10.0
|
||||
# via platformio
|
||||
|
||||
@@ -3498,11 +3498,14 @@ void candle(bool multi)
|
||||
{
|
||||
if (multi && SEGLEN > 1) {
|
||||
//allocate segment data
|
||||
unsigned dataSize = sizeof(uint32_t) + max(1, (int)SEGLEN -1) *3; //max. 1365 pixels (ESP8266)
|
||||
unsigned dataSize = sizeof(uint32_t) + max(1, (int)SEGLEN -1) *3;
|
||||
if (!SEGENV.allocateData(dataSize)) candle(false); //allocation failed
|
||||
} else {
|
||||
unsigned dataSize = sizeof(uint32_t); // for last call timestamp
|
||||
if (!SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC; //allocation failed
|
||||
}
|
||||
uint32_t* lastcall = reinterpret_cast<uint32_t*>(SEGENV.data);
|
||||
uint8_t* candleData = reinterpret_cast<uint8_t*>(SEGENV.data + sizeof(uint32_t));
|
||||
uint8_t* candleData = reinterpret_cast<uint8_t*>(SEGENV.data + sizeof(uint32_t)); // only used for multi-candle
|
||||
|
||||
//limit update rate
|
||||
if (strip.now - *lastcall < FRAMETIME_FIXED) return;
|
||||
@@ -4425,7 +4428,8 @@ void mode_flow(void)
|
||||
if (zones & 0x01) zones++; //zones must be even
|
||||
if (zones < 2) zones = 2;
|
||||
int zoneLen = SEGLEN / zones;
|
||||
zones += 2; //add two extra zones to cover beginning and end of segment (compensate integer truncation)
|
||||
int requiredZones = (SEGLEN + zoneLen - 1) / zoneLen;
|
||||
zones = requiredZones + 2; //add extra zones to cover beginning and end of segment (compensate integer truncation)
|
||||
int offset = ((int)SEGLEN - (zones * zoneLen)) / 2; // center the zones on the segment (can not use bit shift on negative number)
|
||||
|
||||
for (int z = 0; z < zones; z++)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
#include <ESP32-VirtualMatrixPanel-I2S-DMA.h>
|
||||
#include <FastLED.h>
|
||||
#include "src/dependencies/fastled_slim/fastled_slim.h"
|
||||
|
||||
#endif
|
||||
/*
|
||||
|
||||
@@ -84,7 +84,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
JsonArray nw_ins = nw["ins"];
|
||||
if (!nw_ins.isNull()) {
|
||||
// as password are stored separately in wsec.json when reading configuration vector resize happens there, but for dynamic config we need to resize if necessary
|
||||
if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing
|
||||
size_t newSize = min((size_t)WLED_MAX_WIFI_COUNT, nw_ins.size()); // cap at WLED_MAX_WIFI_COUNT (prevent oversizing when too many Wi-Fi entries)
|
||||
if (nw_ins.size() > 1 && newSize > multiWiFi.size()) multiWiFi.resize(newSize); // resize constructs objects while resizing
|
||||
for (JsonObject wifi : nw_ins) {
|
||||
JsonArray ip = wifi["ip"];
|
||||
JsonArray gw = wifi["gw"];
|
||||
@@ -790,14 +791,14 @@ void resetConfig() {
|
||||
}
|
||||
|
||||
bool deserializeConfigFromFS() {
|
||||
[[maybe_unused]] bool success = deserializeConfigSec();
|
||||
(void) deserializeConfigSec(); // success/failure is ignored intentionally. We need to read cfg.json even when wsec.json has errors.
|
||||
|
||||
if (!requestJSONBufferLock(JSON_LOCK_CFG_DES)) return false;
|
||||
|
||||
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
|
||||
|
||||
success = readObjectFromFile(s_cfg_json, nullptr, pDoc);
|
||||
|
||||
bool success = readObjectFromFile(s_cfg_json, nullptr, pDoc);
|
||||
if (!success || (pDoc->overflowed())) pDoc->clear(); // corrupted/too-large → same as missing: seed defaults
|
||||
// NOTE: This routine deserializes *and* applies the configuration
|
||||
// Therefore, must also initialize ethernet from this function
|
||||
JsonObject root = pDoc->as<JsonObject>();
|
||||
@@ -1263,7 +1264,7 @@ bool deserializeConfigSec() {
|
||||
if (!requestJSONBufferLock(JSON_LOCK_CFG_SEC_DES)) return false;
|
||||
|
||||
bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc);
|
||||
if (!success) {
|
||||
if (!success || pDoc->overflowed() || pDoc->size() < 1) { // reject if overflowed (noMemory) or empty
|
||||
releaseJSONBufferLock();
|
||||
return false;
|
||||
}
|
||||
@@ -1273,7 +1274,8 @@ bool deserializeConfigSec() {
|
||||
size_t n = 0;
|
||||
JsonArray nw_ins = root["nw"]["ins"];
|
||||
if (!nw_ins.isNull()) {
|
||||
if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing
|
||||
size_t newSize = min((size_t)WLED_MAX_WIFI_COUNT, nw_ins.size()); // cap at WLED_MAX_WIFI_COUNT (prevent oversizing when too many Wi-Fi entries)
|
||||
if (nw_ins.size() > 1 && newSize > multiWiFi.size()) multiWiFi.resize(newSize); // resize constructs objects while resizing
|
||||
for (JsonObject wifi : nw_ins) {
|
||||
char pw[65] = "";
|
||||
getStringFromJson(pw, wifi["psk"], 65);
|
||||
|
||||
@@ -310,10 +310,10 @@ function onLoad()
|
||||
sl.addEventListener('touchstart', toggleBubble);
|
||||
sl.addEventListener('touchend', toggleBubble);
|
||||
});
|
||||
// limiter for all number inputs: limit inputs instantly
|
||||
// limiter for all number inputs except segment inputs: limit inputs instantly note: segment inputs are special if matrix is enabled, they allow for trailing strips, need a lot of special cases to handle that
|
||||
d.addEventListener("input", function(e) {
|
||||
const t = e.target;
|
||||
if (t.tagName === "INPUT" && t.type === "number") {
|
||||
if (t.tagName === "INPUT" && t.type === "number" && !(t.id && t.id.startsWith("seg"))) {
|
||||
let val = parseFloat(t.value);
|
||||
const max = parseFloat(t.max);
|
||||
const min = parseFloat(t.min);
|
||||
@@ -1182,7 +1182,7 @@ function updateLen(s)
|
||||
let mySD = gId("mkSYD");
|
||||
if (isM) {
|
||||
// do we have 1D segment *after* the matrix?
|
||||
if (start >= mw*mh) {
|
||||
if (start >= mw*mh && s > 0) {
|
||||
if (sY) { sY.value = 0; sY.max = 0; sY.min = 0; }
|
||||
if (eY) { eY.value = 1; eY.max = 1; eY.min = 0; }
|
||||
sX.min = mw*mh; sX.max = ledCount-1;
|
||||
|
||||
@@ -1195,7 +1195,7 @@ async function imgPlay(url,name){
|
||||
on:true,
|
||||
seg: cur!==null && cur!==tgt
|
||||
? [{id:cur,fx:0,n:""},{id:tgt,fx:53,frz:false,sx:128,n:name}]
|
||||
: {id:tgt,fx:53,frz:false,sx:128,n:name}
|
||||
: {id:tgt,fx:53,frz:false,sx:128,ix:0,n:name}
|
||||
};
|
||||
const r=await fetch(getURL('/json/state'),{method:'POST',body:JSON.stringify(j)});
|
||||
const out=await r.json();
|
||||
|
||||
@@ -188,6 +188,7 @@ static bool appendObjectToFile(const char* key, const JsonDocument* content, uin
|
||||
if (f.size() < 3) {
|
||||
char init[10];
|
||||
strcpy_P(init, PSTR("{\"0\":{}}"));
|
||||
f.seek(0, SeekSet); // rewind to ensure we overwrite from the start, instead of appending
|
||||
f.print(init);
|
||||
}
|
||||
|
||||
@@ -354,12 +355,17 @@ bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest, c
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter) deserializeJson(*dest, f, DeserializationOption::Filter(*filter));
|
||||
else deserializeJson(*dest, f);
|
||||
DeserializationError jsonErr = DeserializationError::Ok;
|
||||
if (filter) jsonErr = deserializeJson(*dest, f, DeserializationOption::Filter(*filter));
|
||||
else jsonErr = deserializeJson(*dest, f);
|
||||
|
||||
f.close();
|
||||
DEBUGFS_PRINTF("Read, took %lu ms\n", millis() - s);
|
||||
return true;
|
||||
|
||||
if (jsonErr) { // EmptyInput, IncompleteInput, InvalidInput, NoMemory, TooDeep
|
||||
DEBUG_PRINTF_P(PSTR("readObjectFromFile(%s): JSON %s !\n"), fileName, jsonErr.c_str());
|
||||
return jsonErr == DeserializationError::NoMemory || jsonErr == DeserializationError::EmptyInput; // NoMemory => data is partial but usable; empty => handled by caller
|
||||
} else return true;
|
||||
}
|
||||
|
||||
void updateFSInfo() {
|
||||
|
||||
@@ -28,6 +28,10 @@ int fileReadCallback(void) {
|
||||
}
|
||||
|
||||
int fileReadBlockCallback(void * buffer, int numberOfBytes) {
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
unsigned t0 = millis();
|
||||
while (strip.isUpdating() && (millis() - t0 < 15)) yield(); // be nice, but not too nice. Waits up to 15ms to avoid glitches
|
||||
#endif
|
||||
return file.read((uint8_t*)buffer, numberOfBytes);
|
||||
}
|
||||
|
||||
|
||||
@@ -377,6 +377,7 @@ bool isWiFiConfigured() {
|
||||
static u8_t blockRouterAdvertisements(void* arg, struct raw_pcb* pcb, struct pbuf* p, const ip_addr_t* addr) {
|
||||
// ICMPv6 type is the first byte of the payload, so we skip the header
|
||||
if (p->len > 0 && (pbuf_get_at(p, sizeof(struct ip6_hdr)) == ICMP6_TYPE_RA)) {
|
||||
pbuf_free(p);
|
||||
return 1; // claim the packet — lwIP will not pass it further
|
||||
}
|
||||
return 0; // not consumed, pass it on
|
||||
|
||||
Reference in New Issue
Block a user