Compare commits

...

10 Commits

Author SHA1 Message Date
95137a6d65 Clean up RFP ESP32-S3 target and tooling
Some checks failed
WLED CI / wled_build (push) Has been cancelled
Deploy Nightly / wled_build (push) Has been cancelled
Deploy Nightly / Deploy nightly (push) Has been cancelled
2026-04-17 01:00:37 +02:00
Frank Möhle
3a01c00635 hiding reference to contributing.md from AI
contributing.md makes reference out to this file again
2026-04-08 21:36:34 +02:00
Frank Möhle
e4c9fd5c62 Fix formatting in CI/CD workflows section 2026-04-07 19:38:40 +02:00
Frank Möhle
08529a744e Add code style summary to agent build instructions
Added basic style guidelines for C++, Web UI, and CI/CD workflows.
2026-04-07 19:36:33 +02:00
Copilot
64529bbd84 chore: move coding guidelines from .github to docs/ (#358)
* Reorganized repository documentation and updated internal configuration to reference the new docs location.
Documentation
* Updated contributor and instruction guides to point to the relocated documentation files so links and references remain correct.
---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
2026-04-07 19:02:20 +02:00
Frank
e4c8e349da minor clarifications 2026-04-07 17:56:41 +02:00
Frank Möhle
a718caf4f7 Update FreeRTOS task management instructions
Clarify usage of delay() and yield() in FreeRTOS tasks, emphasizing the differences between ESP32 and ESP8266. Update instructions on task management and watchdog behavior.
2026-04-07 17:18:30 +02:00
Copilot
6c9922d072 docs(esp-idf): add millis/micros internals, precision-wait pattern, PDM 16-bit note, ESP_ERROR_CHECK_WITHOUT_ABORT (#357)
* Clarified PDM microphone behavior: data unit width is effectively 16-bit in PDM mode
* Updated microsecond timing with Arduino-ESP32 note about direct timer usage
* Added "Precision waiting" subsection: coarse delay then busy-spin for microsecond accuracy
* Expanded error-handling docs with non-aborting check example
* Adjusted logging example presentation for human readers

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
2026-04-06 19:32:02 +02:00
Frank
231373acac small update to AI instructions
upstream sync
2026-04-06 18:07:48 +02:00
Frank
eb2352774d align AI instructions with upstream
some updates coming from ongoing work
2026-04-06 17:14:40 +02:00
15 changed files with 441 additions and 107 deletions

View File

@@ -3,10 +3,10 @@
# CodeRabbit configuration — references existing guideline files to avoid # CodeRabbit configuration — references existing guideline files to avoid
# duplicating conventions. See: # duplicating conventions. See:
# .github/copilot-instructions.md — project overview & general rules # .github/copilot-instructions.md — project overview & general rules
# .github/cpp.instructions.md — C++ coding conventions # docs/cpp.instructions.md — C++ coding conventions
# .github/web.instructions.md — Web UI coding conventions # docs/web.instructions.md — Web UI coding conventions
# .github/cicd.instructions.md — GitHub Actions / CI-CD conventions # docs/cicd.instructions.md — GitHub Actions / CI-CD conventions
# .github/esp-idf.instructions.md — ESP-IDF / chip-specific coding guidelines # docs/esp-idf.instructions.md — ESP-IDF / chip-specific coding guidelines
# (apply when code directly uses ESP-IDF APIs: # (apply when code directly uses ESP-IDF APIs:
# esp_idf_*, I2S, RMT, ADC, GPIO, heap_caps, etc.) # esp_idf_*, I2S, RMT, ADC, GPIO, heap_caps, etc.)
# #
@@ -20,11 +20,11 @@ reviews:
path_instructions: path_instructions:
- path: "**/*.{cpp,h,hpp,ino}" - path: "**/*.{cpp,h,hpp,ino}"
instructions: > instructions: >
Follow the C++ coding conventions documented in .github/cpp.instructions.md Follow the C++ coding conventions documented in docs/cpp.instructions.md
and the general project guidelines in .github/copilot-instructions.md. and the general project guidelines in .github/copilot-instructions.md.
If the code under review directly uses ESP-IDF APIs (e.g. heap_caps_malloc, If the code under review directly uses ESP-IDF APIs (e.g. heap_caps_malloc,
I2S, RMT, ADC, GPIO, esp_timer, or any esp_idf_* / soc_* symbols), also I2S, RMT, ADC, GPIO, esp_timer, or any esp_idf_* / soc_* symbols), also
apply the guidelines in .github/esp-idf.instructions.md. apply the guidelines in docs/esp-idf.instructions.md.
Key rules: 2-space indentation (no tabs), camelCase functions/variables, Key rules: 2-space indentation (no tabs), camelCase functions/variables,
PascalCase classes, UPPER_CASE macros. Mark WLED-MM-specific changes with PascalCase classes, UPPER_CASE macros. Mark WLED-MM-specific changes with
@@ -36,7 +36,7 @@ reviews:
- path: "wled00/data/**" - path: "wled00/data/**"
instructions: > instructions: >
Follow the web UI conventions documented in .github/web.instructions.md. Follow the web UI conventions documented in docs/web.instructions.md.
Key rules: indent HTML and JavaScript with tabs, CSS with tabs or spaces. Key rules: indent HTML and JavaScript with tabs, CSS with tabs or spaces.
Files here are built into wled00/html_*.h by tools/cdata.js — never Files here are built into wled00/html_*.h by tools/cdata.js — never
@@ -54,11 +54,11 @@ reviews:
Each usermod lives in its own directory under usermods/ and is implemented Each usermod lives in its own directory under usermods/ and is implemented
as a .h file that is pulled in by wled00/usermods_list.cpp (guarded by as a .h file that is pulled in by wled00/usermods_list.cpp (guarded by
#ifdef). Usermods do not use library.json. Follow the same C++ conventions #ifdef). Usermods do not use library.json. Follow the same C++ conventions
as the core firmware (.github/cpp.instructions.md). as the core firmware (docs/cpp.instructions.md).
- path: ".github/workflows/*.{yml,yaml}" - path: ".github/workflows/*.{yml,yaml}"
instructions: > instructions: >
Follow the CI/CD conventions documented in .github/cicd.instructions.md. Follow the CI/CD conventions documented in docs/cicd.instructions.md.
Key rules: 2-space indentation, descriptive name: on every workflow/job/step. 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 Third-party actions must be pinned to a specific version tag — branch pins
@@ -67,7 +67,7 @@ reviews:
into run: steps — pass them through an env: variable to prevent script into run: steps — pass them through an env: variable to prevent script
injection. Do not use pull_request_target unless fully justified. injection. Do not use pull_request_target unless fully justified.
- path: ".github/*.instructions.md" - path: "**/*.instructions.md"
instructions: | instructions: |
This file contains both AI-facing rules and human-only reference sections. This file contains both AI-facing rules and human-only reference sections.
Human-only sections are enclosed in `<!-- HUMAN_ONLY_START -->` / Human-only sections are enclosed in `<!-- HUMAN_ONLY_START -->` /
@@ -81,4 +81,4 @@ reviews:
2. Flag any HUMAN_ONLY section whose content has drifted from the surrounding 2. Flag any HUMAN_ONLY section whose content has drifted from the surrounding
AI-facing rules due to edits introduced in this PR. AI-facing rules due to edits introduced in this PR.
3. If new AI-facing rules were added without updating a related HUMAN_ONLY 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). reference section, note this as a suggestion (not a required fix).

View File

@@ -11,7 +11,7 @@ Use these timeout values when running builds:
| Command | Typical Time | Minimum Timeout | Notes | | Command | Typical Time | Minimum Timeout | Notes |
|---|---|---|---| |---|---|---|---|
| `npm run build` | ~3 s | 30 s | Web UI → `wled00/html_*.h` headers | | `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 test` | ~40 s | 2 min | Validates build system |
| `npm run dev` | continuous | — | Watch mode, auto-rebuilds on changes | | `npm run dev` | continuous | — | Watch mode, auto-rebuilds on changes |
| `pio run -e <env>` | 1520 min | 30 min | First build downloads toolchains; subsequent builds are faster | | `pio run -e <env>` | 1520 min | 30 min | First build downloads toolchains; subsequent builds are faster |
@@ -20,16 +20,21 @@ Use these timeout values when running builds:
## Development Workflow ## 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 ### Web UI Changes
1. Edit files in `wled00/data/` 1. Edit files in `wled00/data/`
2. Run `npm run build` to regenerate `wled00/html_*.h` headers 2. Run `npm run build` to regenerate `wled00/html_*.h` `wled00/js_*.h` headers
3. Test with local HTTP server (see Manual Testing below) 3. Test with local HTTP server (see Manual Testing below)
4. Run `npm test` to validate 4. Run `npm test` to validate
### Firmware Changes ### Firmware Changes
1. Edit files in `wled00/` (but **never** `html_*.h` files) 1. Edit files in `wled00/` (but **never** `html_*.h` and `js_*.h` files)
2. Ensure web UI is built first: `npm run build` 2. Ensure web UI is built first: `npm run build`
3. Build firmware: `pio run -e esp32_4MB_V4_M` (set timeout ≥ 30 min) 3. Build firmware: `pio run -e esp32_4MB_V4_M` (set timeout ≥ 30 min)
4. Flash to device: `pio run -e [target] --target upload` 4. Flash to device: `pio run -e [target] --target upload`
@@ -85,8 +90,8 @@ Test these scenarios after every web UI change:
### Recovery Steps ### Recovery Steps
- **Force web UI rebuild**: `npm run build -- -f` - **Force web UI rebuild**: `npm run build -- -f`
- **Clear generated files**: `rm -f wled00/html_*.h` then `npm run build` - **Clear generated files**: `rm -f wled00/html_*.h wled00/js_*.h` then `npm run build`
- **Clean PlatformIO cache**: `pio run --target clean` - **Clean PlatformIO build artifacts**: `pio run --target clean`
- **Reinstall Node deps**: `rm -rf node_modules && npm ci` - **Reinstall Node deps**: `rm -rf node_modules && npm ci`
## CI/CD Validation ## CI/CD Validation
@@ -106,7 +111,8 @@ Match this workflow in local development to catch failures before pushing.
## Important Reminders ## Important Reminders
- **Never edit or commit** `wled00/html_*.h` — auto-generated from `wled00/data/` - 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 - Web UI rebuild is part of the PlatformIO firmware compilation pipeline
- Common firmware environments: `esp32_4MB_V4_M`, `esp32_16MB_V4_S_HUB75`, `esp32S3_8MB_PSRAM_M_qspi`, `esp32_16MB_V4_M_eth`, `esp8266_4MB_S` (deprecated), `esp32dev_compat` - Common firmware environments: `esp32_4MB_V4_M`, `esp32_16MB_V4_S_HUB75`, `esp32S3_8MB_PSRAM_M_qspi`, `esp32_16MB_V4_M_eth`, `esp8266_4MB_S` (deprecated), `esp32dev_compat`
- List all PlatformIO targets: `pio run --list-targets` - List all PlatformIO targets: `pio run --list-targets`

View File

@@ -33,29 +33,28 @@ Always reference these instructions first and fallback to search or bash command
| Command | Purpose | Typical Time | | Command | Purpose | Typical Time |
|---|---|---| |---|---|---|
| `npm run build` | Build web UI → generates `wled00/html_*.h` headers | ~3 s | | `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 test` | Run test suite | ~40 s |
| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — | | `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |
| `pio run -e <env>` | Build firmware for a hardware target | 1520 min | | `pio run -e <env>` | Build firmware for a hardware target | 1520 min |
<!-- HUMAN_ONLY_END --> <!-- HUMAN_ONLY_END -->
**Always run `npm ci; npm run build` before `pio run`.** The web UI build generates `wled00/html_*.h` header files required by firmware compilation. - **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).
**Build firmware to validate code changes**: `pio run -e esp32_4MB_V4_M` — must succeed, never skip this step. - The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.
Common firmware environments: `esp32_4MB_V4_M`, `esp32_16MB_V4_S_HUB75`, `esp32S3_8MB_PSRAM_M_qspi`, `esp32_16MB_V4_M_eth`, `esp32dev_compat`, `esp8266_4MB_S` (deprecated) - **Build firmware to validate code changes**: `pio run -e esp32_4MB_V4_M` — must succeed, never skip this step.
- Common firmware environments: `esp32_4MB_V4_M`, `esp32_16MB_V4_S_HUB75`, `esp32S3_8MB_PSRAM_M_qspi`, `esp32_16MB_V4_M_eth`, `esp32dev_compat`, `esp8266_4MB_S` (deprecated)
For detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md). For detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).
## Repository Structure ## Repository Structure
tl;dr: tl;dr:
* Firmware source: `wled00/` (C++). * Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.
* Build targets: `platformio.ini`. * Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h`**never edit or commit**.
* Web UI source: `wled00/data/`. * ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.
* Auto-generated headers: `wled00/html_*.h`**never edit or commit**.
* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json`
* Usermods: `usermods/` (`.h` files, included via `usermods_list.cpp`). * Usermods: `usermods/` (`.h` files, included via `usermods_list.cpp`).
* CI/CD: `.github/workflows/`. * Contributor docs: `docs/` (coding guidelines, design docs).
Main development trunk: `mdev` branch. Make PRs against this branch. Main development trunk: `mdev` branch. Make PRs against this branch.
@@ -79,29 +78,33 @@ tools/ # Build tools (Node.js), partition files, and generi
tools/cdata.js # Web UI → header build script tools/cdata.js # Web UI → header build script
tools/cdata-test.js # Test suite tools/cdata-test.js # Test suite
package.json # Node.js scripts and release ID package.json # Node.js scripts and release ID
docs/ # Contributor docs: coding guidelines and design documentation
.github/workflows/ # CI/CD pipelines .github/workflows/ # CI/CD pipelines
``` ```
<!-- HUMAN_ONLY_END -->
<!-- HUMAN_ONLY_END -->
## General Guidelines ## General Guidelines
- **Never edit or commit** `wled00/html_*.h` — auto-generated from `wled00/data/`.
- **Repository language is English.** Suggest translations for non-English content. - **Repository language is English.** Suggest translations for non-English content.
- **Use VS Code with PlatformIO extension** for best development experience. - **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. - **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. - **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. - **Provide references** when making analyses or recommendations. Base them on the correct branch or PR.
- **Look for user-visible breaking changes and ripple effects**. Ask for confirmation that these were introduced intentionally. - **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. - **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 - **C++ formatting available**: `clang-format` is installed but not in CI
- No automated linting is configured — match existing code style in files you edit. See `cpp.instructions.md` and `web.instructions.md` for language-specific conventions, and `cicd.instructions.md` for GitHub Actions workflows. - No automated linting is configured — match existing code style in files you edit.
See `docs/cpp.instructions.md`, `docs/esp-idf.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.
### Attribution for AI-generated code ### Attribution for AI-generated code
Using AI-generated code can hide the source of the inspiration / knowledge / sources it used. 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. - 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). - 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. - 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; comment-to-code ratio > 15% is expected. Do not rephrase source code, but explain the concepts/logic behind the code. - 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.
### Pull Request Expectations ### Pull Request Expectations

3
.gitignore vendored
View File

@@ -5,8 +5,11 @@
.gitignore .gitignore
.idea .idea
.pio .pio
.piohome
.pioenvs .pioenvs
.piolibdeps .piolibdeps
.tools
.venv
.vscode .vscode
.vscode/extensions.json .vscode/extensions.json

View File

@@ -76,9 +76,9 @@ When in doubt, it is easiest to replicate the code style you find in the files y
Our review bot (coderabbit) has learned lots of detailed guides and hints - it will suggest them automatically when you submit a PR for review. 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: If you are curious, these are the detailed guides:
* [C++ Coding](https://github.com/MoonModules/WLED-MM/blob/mdev/.github/cpp.instructions.md) * [C++ Coding](https://github.com/MoonModules/WLED-MM/blob/mdev/docs/cpp.instructions.md)
* [WebUi: HTML, JS, CSS](https://github.com/MoonModules/WLED-MM/blob/mdev/.github/web.instructions.md) * [WebUi: HTML, JS, CSS](https://github.com/MoonModules/WLED-MM/blob/mdev/docs/web.instructions.md)
* [Using ESP-IDF directly](https://github.com/MoonModules/WLED-MM/blob/mdev/.github/esp-idf.instructions.md) * [Using ESP-IDF directly](https://github.com/MoonModules/WLED-MM/blob/mdev/docs/esp-idf.instructions.md)
Below are the main rules used the WLED-MM repository. Below are the main rules used the WLED-MM repository.

View File

@@ -71,6 +71,8 @@ schedule:
## Security ## Security
Important: Several current workflows still violate parts of the baseline below - migration is in progress.
### Permissions — Least Privilege ### Permissions — Least Privilege
Declare explicit `permissions:` blocks. The default token permissions are broad; scope them to the minimum required: Declare explicit `permissions:` blocks. The default token permissions are broad; scope them to the minimum required:

View File

@@ -8,7 +8,10 @@ applyTo: "**/*.cpp,**/*.h,**/*.hpp,**/*.ino"
> contributor reference material. Do **not** use that content as actionable review > contributor reference material. Do **not** use that content as actionable review
> criteria — treat it as background context only. > 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. See also: [CONTRIBUTING.md](../CONTRIBUTING.md) for general style guidelines that apply to all contributors.
<!-- HUMAN_ONLY_END -->
## Formatting ## Formatting
@@ -18,13 +21,13 @@ See also: [CONTRIBUTING.md](../CONTRIBUTING.md) for general style guidelines tha
- Space between keyword and parenthesis: `if (...)`, `for (...)`. No space between function name and parenthesis: `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 - No enforced line-length limit; wrap when a line exceeds your editor width
<!-- HUMAN_ONLY_START -->
## Naming ## Naming
- **camelCase** for functions and variables: `setValuesFromMainSeg()`, `effectCurrent` - **camelCase** for functions and variables: `setValuesFromMainSeg()`, `effectCurrent`
- **PascalCase** for classes and structs: `PinManagerClass`, `BusConfig` - **PascalCase** for classes and structs: `PinManagerClass`, `BusConfig`
- **UPPER_CASE** for macros and constants: `WLED_MAX_USERMODS`, `DEFAULT_CLIENT_SSID` - **UPPER_CASE** for macros and constants: `WLED_MAX_USERMODS`, `DEFAULT_CLIENT_SSID`
<!-- HUMAN_ONLY_START -->
## Header Guards ## Header Guards
Most headers use `#ifndef` / `#define` guards. Some newer headers add `#pragma once` before the guard: Most headers use `#ifndef` / `#define` guards. Some newer headers add `#pragma once` before the guard:
@@ -64,6 +67,7 @@ void calculateCRC(const uint8_t* data, size_t len) {
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. 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 --> <!-- 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: - **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 ```cpp
@@ -77,7 +81,6 @@ uint8_t gammaCorrect(uint8_t value, float gamma);
``` ```
<!-- HUMAN_ONLY_END --> <!-- HUMAN_ONLY_END -->
Short accessor-style functions (getters/setters, one-liners) may skip this if their purpose is obvious from the name. Short accessor-style functions (getters/setters, one-liners) may skip this if their purpose is obvious from the name.
## Preprocessor & Feature Flags ## Preprocessor & Feature Flags
@@ -106,12 +109,38 @@ uint8_t gammaCorrect(uint8_t value, float gamma);
- **PSRAM-aware allocation**: use `d_malloc()` (prefer DRAM), `p_malloc()` (prefer PSRAM) from `util.h` - **PSRAM-aware allocation**: use `d_malloc()` (prefer DRAM), `p_malloc()` (prefer PSRAM) from `util.h`
- **Avoid Variable Length Arrays (VLAs)**: FreeRTOS task stacks are typically 28 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. - **Avoid Variable Length Arrays (VLAs)**: FreeRTOS task stacks are typically 28 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 --> <!-- 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. 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 --> <!-- HUMAN_ONLY_END -->
- **Larger buffers** (LED data, JSON documents) should use PSRAM when available and technically feasible - **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 - **Hot-path**: some data should stay in DRAM or IRAM for performance reasons
- Memory efficiency matters, but is less critical on boards with PSRAM - 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` ## `const` and `constexpr`
Add `const` to cached locals in hot-path code (helps the compiler keep values in registers). Pass and store objects by `const&` to avoid copies in loops. Add `const` to cached locals in hot-path code (helps the compiler keep values in registers). Pass and store objects by `const&` to avoid copies in loops.
@@ -121,12 +150,13 @@ This pattern enables optimizations and makes intent clear to reviewers.
### `const` locals ### `const` locals
Adding `const` to a local variable that is only assigned once is not necessary — but it **is** required when the variable is passed to a function that takes a `const` parameter (pointer or reference). In hot-path code, `const` on cached locals helps the compiler keep values in registers: * Adding `const` to a local variable that is only assigned once is optional, but *not* strictly necessary.
* 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();
```
```cpp
const uint_fast16_t cols = virtualWidth();
const uint_fast16_t rows = virtualHeight();
```
<!-- HUMAN_ONLY_END --> <!-- HUMAN_ONLY_END -->
### `const` references to avoid copies ### `const` references to avoid copies
@@ -175,6 +205,12 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#error "WLED_MAX_BUSSES exceeds hard limit" #error "WLED_MAX_BUSSES exceeds hard limit"
#endif #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 --> <!-- HUMAN_ONLY_END -->
Prefer `constexpr` over `#define` for typed constants (scope-safe, debuggable). Use `static_assert` instead of `#if … #error` for compile-time validation. Prefer `constexpr` over `#define` for typed constants (scope-safe, debuggable). Use `static_assert` instead of `#if … #error` for compile-time validation.
@@ -486,22 +522,28 @@ Always pair every `esp32SemTake` with a matching `esp32SemGive`. Choose a timeou
**Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow. For example, `volatile bool` flags like `suspendStripService`, `doInitBusses`, `loadLedmap`, and `OTAisRunning` (declared in `wled.h`) are checked sequentially in the main loop (`wled.cpp`), so they serialize access without requiring a semaphore. Use mutexes when true concurrent access from multiple FreeRTOS tasks is possible and race-conditions can lead to unexpected behaviour. Rely on control-flow ordering when operations are sequenced within the same loop iteration. **Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow. For example, `volatile bool` flags like `suspendStripService`, `doInitBusses`, `loadLedmap`, and `OTAisRunning` (declared in `wled.h`) are checked sequentially in the main loop (`wled.cpp`), so they serialize access without requiring a semaphore. Use mutexes when true concurrent access from multiple FreeRTOS tasks is possible and race-conditions can lead to unexpected behaviour. Rely on control-flow ordering when operations are sequenced within the same loop iteration.
### `delay()` vs `yield()` in FreeRTOS Tasks ### `delay()` vs `yield()` in FreeRTOS 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()` stalled the entire system unless `yield()` was called inside.
<!-- HUMAN_ONLY_END -->
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. This differs from ESP8266, where `delay()` stalled the entire system unless `yield()` was called inside. - 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 ``busses`` level is allowed, it might be needed to achieve exact timing in LED drivers.
- **`yield()` is a no-op in WLED-MM on ESP32.** `WLEDMM_FASTPATH` redefines `yield()` to an empty macro.
```cpp
#define yield() {} // WLEDMM: yield() is completely unnecessary on ESP32
```
**`delay()` in `loopTask` is allowed.** The Arduino `loop()` function runs inside `loopTask`. Calling `delay()` there does not block the network stack, audio FFT, LED DMA, or any other FreeRTOS task. ### IDLE Watchdog and Custom Tasks on ESP32
**`yield()` is a no-op in WLED-MM on ESP32.** `WLEDMM_FASTPATH` redefines `yield()` to an empty macro: - **Do NOT use `yield()` to pace ESP32 tasks or assume it feeds any watchdog**.
```cpp - Even in stock arduino-esp32, `yield()` calls `vTaskDelay(0)`, which only switches to tasks at equal or higher priority — the IDLE task (priority 0) is never reached.
#define yield() {} // WLEDMM: yield() is completely unnecessary on ESP32
```
Even in stock 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. See `esp-idf.instructions.md` for a full explanation of what IDLE does. Structure custom tasks like this:
- **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. See `esp-idf.instructions.md` for a full explanation of what IDLE does. Structure custom tasks like this:
```cpp ```cpp
// WRONG — IDLE task is never scheduled; yield() does not feed the idle task watchdog. // WRONG — IDLE task is never scheduled; yield() does not feed the idle task watchdog.
void myTask(void*) { void myTask(void*) {
@@ -520,9 +562,8 @@ void myTask(void*) {
} }
``` ```
Prefer blocking FreeRTOS primitives (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) over `delay(1)` polling where precise timing or event-driven behaviour is needed. - Prefer blocking FreeRTOS primitives (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) over `delay(1)` polling where precise timing or event-driven behaviour is needed.
- **Watchdog note.** WLED-MM 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.
**Watchdog note.** WLED-MM 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.
## General ## General
@@ -530,7 +571,8 @@ Prefer blocking FreeRTOS primitives (`xQueueReceive`, `ulTaskNotifyTake`, `vTask
- If possible, use `static` for local (C-style) variables and functions (keeps the global namespace clean) - 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 - 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 - Include `"wled.h"` as the primary project header where needed
- **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/C6) can produce different results due to clamping. Cast through a signed integer first:
- **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 ```cpp
// Undefined behavior — avoid: // Undefined behavior — avoid:
uint8_t angle = 40.74f * atan2f(dy, dx); // negative float → uint8_t is UB uint8_t angle = 40.74f * atan2f(dy, dx); // negative float → uint8_t is UB

View File

@@ -499,6 +499,9 @@ The ESP32 has an audio PLL for precise sample rates. Rules:
- Not supported on ESP32-C3 (`SOC_I2S_SUPPORTS_PDM_RX` not defined). - Not supported on ESP32-C3 (`SOC_I2S_SUPPORTS_PDM_RX` not defined).
- ESP32-S3 PDM has known issues: sample rate at 50% of expected, very low amplitude. - ESP32-S3 PDM has known issues: sample rate at 50% of expected, very low amplitude.
- **16-bit data width**: Espressif's IDF documentation states that in PDM mode the data unit width is always 16 bits, regardless of the configured `bits_per_sample`.
- See [espressif/esp-idf#8660](https://github.com/espressif/esp-idf/issues/8660) for the upstream issue.
- **Flag `bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT` in PDM mode** — this causes the S3 low-amplitude symptom.
- No clock pin (`I2S_CKPIN = -1`) triggers PDM mode in WLED-MM. - No clock pin (`I2S_CKPIN = -1`) triggers PDM mode in WLED-MM.
--- ---
@@ -579,13 +582,21 @@ if (!pinManager.allocatePin(myPin, true, PinOwner::UM_MyUsermod)) {
### Microsecond timing ### Microsecond timing
For high-resolution timing, prefer `esp_timer_get_time()` (microsecond resolution, 64-bit) over `millis()` or `micros()`: For high-resolution timing, prefer `esp_timer_get_time()` (microsecond resolution, 64-bit) over `millis()` or `micros()`.
<!-- HUMAN_ONLY_START -->
```cpp ```cpp
#include <esp_timer.h> #include <esp_timer.h>
int64_t now_us = esp_timer_get_time(); // monotonic, not affected by NTP int64_t now_us = esp_timer_get_time(); // monotonic, not affected by NTP
``` ```
> **Note**: In arduino-esp32, both `millis()` and `micros()` are thin wrappers around `esp_timer_get_time()` — they share the same monotonic clock source. Prefer the direct call when you need the full 64-bit value or ISR-safe access without truncation:
> ```cpp
> // arduino-esp32 internals (cores/esp32/esp32-hal-misc.c):
> // unsigned long micros() { return (unsigned long)(esp_timer_get_time()); }
> // unsigned long millis() { return (unsigned long)(esp_timer_get_time() / 1000ULL); }
> ```
<!-- HUMAN_ONLY_END -->
<!-- HUMAN_ONLY_START --> <!-- HUMAN_ONLY_START -->
### Periodic timers ### Periodic timers
@@ -606,6 +617,27 @@ esp_timer_start_periodic(timer, 1000); // 1 ms period
Always prefer `ESP_TIMER_TASK` dispatch over `ESP_TIMER_ISR` unless you need ISR-level latency — ISR callbacks have severe restrictions (no logging, no heap allocation, no FreeRTOS API calls). Always prefer `ESP_TIMER_TASK` dispatch over `ESP_TIMER_ISR` unless you need ISR-level latency — ISR callbacks have severe restrictions (no logging, no heap allocation, no FreeRTOS API calls).
### Precision waiting: coarse delay then spin-poll
When waiting for a precise future deadline (e.g., FPS limiting, protocol timing), avoid spinning the entire duration — that wastes CPU and starves other tasks. Instead, yield to FreeRTOS while time allows, then spin only for the final window.
<!-- HUMAN_ONLY_START -->
```cpp
// Wait until 'target_us' (a micros() / esp_timer_get_time() timestamp)
long time_to_wait = (long)(target_us - micros());
// Coarse phase: yield to FreeRTOS while we have more than ~2 ms remaining.
// vTaskDelay(1) suspends the task for one RTOS tick, letting other task run freely.
while (time_to_wait > 2000) {
vTaskDelay(1);
time_to_wait = (long)(target_us - micros());
}
// Fine phase: busy-poll the last ≤2 ms for microsecond accuracy.
// micros() wraps esp_timer_get_time() so this is low-overhead.
while ((long)(target_us - micros()) > 0) { /* spin */ }
```
<!-- HUMAN_ONLY_END -->
> The threshold (2000 µs as an example) should be at least one RTOS tick (default 1 ms on ESP32) plus some margin. A value of 15003000 µs works well in practice.
--- ---
## ADC Best Practices ## ADC Best Practices
@@ -672,14 +704,15 @@ RMT drives NeoPixel LED output (via NeoPixelBus) and IR receiver input. Both use
- New chips (C6, P4) have different RMT channel counts — use `SOC_RMT_TX_CANDIDATES_PER_GROUP` to check availability. - New chips (C6, P4) have different RMT channel counts — use `SOC_RMT_TX_CANDIDATES_PER_GROUP` to check availability.
- The new RMT API requires an "encoder" object (`rmt_encoder_t`) to translate data formats — this is more flexible but requires more setup code. - The new RMT API requires an "encoder" object (`rmt_encoder_t`) to translate data formats — this is more flexible but requires more setup code.
<!-- HUMAN_ONLY_END -->
--- ---
## Espressif Best Practices (from official examples) ## Espressif Best Practices (from official examples)
### Error handling ### Error handling
Always check `esp_err_t` return values. Use `ESP_ERROR_CHECK()` in initialization code, but handle errors gracefully in runtime code: Always check `esp_err_t` return values. Use `ESP_ERROR_CHECK()` in initialization code, but handle errors gracefully in runtime code.
<!-- HUMAN_ONLY_START -->
```cpp ```cpp
// Initialization — crash early on failure // Initialization — crash early on failure
ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &config, 0, nullptr)); ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &config, 0, nullptr));
@@ -693,6 +726,17 @@ if (err != ESP_OK) {
``` ```
<!-- HUMAN_ONLY_END --> <!-- HUMAN_ONLY_END -->
For situations between these two extremes — where you want the `ESP_ERROR_CHECK` formatted log message (file, line, error name) but must not abort — use `ESP_ERROR_CHECK_WITHOUT_ABORT()`.
<!-- HUMAN_ONLY_START -->
```cpp
// Logs in the same format as ESP_ERROR_CHECK, but returns the error code instead of aborting.
// Useful for non-fatal driver calls where you want visibility without crashing.
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_set_clk(AR_I2S_PORT, rate, bits, ch));
if (err != ESP_OK) return; // handle as needed
```
<!-- HUMAN_ONLY_END -->
### Logging ### Logging
WLED-MM uses its own logging macros — **not** `ESP_LOGx()`. For application-level code, always use the WLED-MM macros defined in `wled.h`: WLED-MM uses its own logging macros — **not** `ESP_LOGx()`. For application-level code, always use the WLED-MM macros defined in `wled.h`:
@@ -706,13 +750,14 @@ All of these wrap `Serial` output through the `DEBUGOUT` / `DEBUGOUTLN` / `DEBUG
**Exception — low-level driver code**: When writing code that interacts directly with ESP-IDF APIs (e.g., I2S initialization, RMT setup), use `ESP_LOGx()` macros instead. They support tag-based filtering and compile-time log level control: **Exception — low-level driver code**: When writing code that interacts directly with ESP-IDF APIs (e.g., I2S initialization, RMT setup), use `ESP_LOGx()` macros instead. They support tag-based filtering and compile-time log level control:
<!-- HUMAN_ONLY_START -->
```cpp ```cpp
static const char* TAG = "my_module"; static const char* TAG = "my_module";
ESP_LOGI(TAG, "Initialized with %d buffers", count); ESP_LOGI(TAG, "Initialized with %d buffers", count);
ESP_LOGW(TAG, "PSRAM not available, falling back to DRAM"); ESP_LOGW(TAG, "PSRAM not available, falling back to DRAM");
ESP_LOGE(TAG, "Failed to allocate %u bytes", size); ESP_LOGE(TAG, "Failed to allocate %u bytes", size);
``` ```
<!-- HUMAN_ONLY_END -->
### Task creation and pinning ### Task creation and pinning
<!-- HUMAN_ONLY_START --> <!-- HUMAN_ONLY_START -->

View File

@@ -0,0 +1,64 @@
# RFP ESP32-S3 WROOM-1 N16R8 (3 x 106)
This repository includes a tracked PlatformIO target for the RFP ESP32-S3 WROOM-1 N16R8 nodes with three LED outputs and 106 pixels per output.
Build target:
- `rfp_esp32s3_wroom1_n16r8_3x106`
Default output pins:
- Output 1: `GPIO4`
- Output 2: `GPIO5`
- Output 3: `GPIO6`
Pins intentionally avoided:
- `GPIO0`, `GPIO3`, `GPIO45`, `GPIO46` for boot / strapping
- `GPIO19`, `GPIO20` for USB
- `GPIO33` to `GPIO37` because they are reserved by octal PSRAM / flash on `N16R8`
- `GPIO48` because it is used as the onboard status pixel
Build:
```powershell
python -m platformio run -e rfp_esp32s3_wroom1_n16r8_3x106
```
Build and upload:
```powershell
.\tools\flash_rfp_s3.ps1 -ComPort COM7
```
Build only with the helper script:
```powershell
.\tools\flash_rfp_s3.ps1 -BuildOnly
```
Local Wi-Fi defaults:
- Keep SSID and password in the ignored file `wled00/my_config.h`.
- If the file does not exist yet, create it with your local values:
```cpp
#pragma once
#define CLIENT_SSID "your-ssid"
#define CLIENT_PASS "your-password"
```
Important:
- The `3 x 106` bus layout is used as the default when the device has no saved `cfg.json`.
- If a board already has a saved WLED config, do a factory reset or erase settings once so the default bus layout is recreated.
Onboard status pixel on `GPIO48`:
- `green blinking`: DDP realtime active
- `green solid`: network connected
- `blue blinking`: AP / setup mode active
- `red fast blinking`: Wi-Fi configured but currently disconnected
- `amber fast blinking`: network connected, MQTT configured, but MQTT not connected
- `off`: idle / no status to show

View File

@@ -24,5 +24,6 @@ applyTo: "wled00/data/**"
## Build Integration ## Build Integration
Files in this directory are processed by `tools/cdata.js` into `wled00/html_*.h` headers. Files in this directory are processed by `tools/cdata.js` into generated headers
Run `npm run build` after any change. **Never edit the generated `html_*.h` files directly.** (`wled00/html_*.h`, `wled00/js_*.h`).
Run `npm run build` after any change. **Never edit generated headers directly.**

View File

@@ -107,6 +107,7 @@ default_envs =
;; === esp32-S3 === with 16MB flash ;; === esp32-S3 === with 16MB flash
esp32S3_16MB_PSRAM_M_HUB75 ;; for S3 with 16MB flash, HUB75 supported (MOONHUB HUB75 adapter board) esp32S3_16MB_PSRAM_M_HUB75 ;; for S3 with 16MB flash, HUB75 supported (MOONHUB HUB75 adapter board)
esp32S3_WROOM-2_M ;; for S3 WROOM-2; HUB75 supported esp32S3_WROOM-2_M ;; for S3 WROOM-2; HUB75 supported
; rfp_esp32s3_wroom1_n16r8_3x106 ;; RFP ESP32-S3 WROOM-1 N16R8, 3x106 pixels on GPIO 4/5/6
;; ;;
;; === esp32-S2 boards === ;; === esp32-S2 boards ===
esp32s2_PSRAM_S ;; OTA-compatible with upstream esp32s2_PSRAM_S ;; OTA-compatible with upstream
@@ -2438,6 +2439,32 @@ build_flags = ${env:esp32S3_8MB_PSRAM_M_opi.build_flags}
[env:esp32S3_8MB_PSRAM_M] ;; legacy alias [env:esp32S3_8MB_PSRAM_M] ;; legacy alias
extends = env:esp32S3_8MB_PSRAM_M_opi extends = env:esp32S3_8MB_PSRAM_M_opi
[env:rfp_esp32s3_wroom1_n16r8_3x106]
;; RFP ESP32-S3 WROOM-1 N16R8, 16MB flash / 8MB OPI PSRAM, 3 outputs x 106 pixels
extends = env:esp32S3_8MB_PSRAM_M_opi
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.partitions = tools/WLED_ESP32_16MB.csv
build_unflags = ${env:esp32S3_8MB_PSRAM_M_opi.build_unflags}
-D WLED_RELEASE_NAME=esp32S3_8MB_PSRAM_M_opi
-D LEDPIN=21
-D BTNPIN=0
-D RLYPIN=1
-D IRPIN=-1
-D AUDIOPIN=-1
build_flags = ${env:esp32S3_8MB_PSRAM_M_opi.build_flags}
-D WLED_RELEASE_NAME=RFP_ESP32S3_N16R8_3x106
-D LEDPIN=4
-D DATA_PINS=4,5,6
-D PIXEL_COUNTS=106,106,106
-D DEFAULT_LED_COUNT=106
-D STATUSPIXELPIN=48
-D STATUSPIXELCOLORORDER=COL_ORDER_GRB
-D BTNPIN=-1
-D RLYPIN=-1
-D IRPIN=-1
-D AUDIOPIN=-1
[env:esp32S3_8MB_S] [env:esp32S3_8MB_S]
;; MM for ESP32-S3 boards - FASTPATH + optimize for speed; ; HUB75 support included (may still have pin conflicts) ;; MM for ESP32-S3 boards - FASTPATH + optimize for speed; ; HUB75 support included (may still have pin conflicts)
extends = esp32_4MB_V4_M_base extends = esp32_4MB_V4_M_base

44
tools/flash_rfp_s3.ps1 Normal file
View File

@@ -0,0 +1,44 @@
param(
[string]$ComPort,
[switch]$BuildOnly
)
$ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = Split-Path -Parent $scriptDir
$envName = "rfp_esp32s3_wroom1_n16r8_3x106"
$venvPython = Join-Path $repoRoot ".venv\Scripts\python.exe"
if (-not $BuildOnly -and [string]::IsNullOrWhiteSpace($ComPort)) {
throw "ComPort is required unless -BuildOnly is used."
}
if (Test-Path $venvPython) {
$pythonCommand = $venvPython
$pythonArgs = @()
} elseif (Get-Command py -ErrorAction SilentlyContinue) {
$pythonCommand = "py"
$pythonArgs = @("-3")
} elseif (Get-Command python -ErrorAction SilentlyContinue) {
$pythonCommand = "python"
$pythonArgs = @()
} else {
throw "No Python runtime found. Install Python or create .venv first."
}
$pioHome = Join-Path $repoRoot ".piohome"
$env:PLATFORMIO_CORE_DIR = $pioHome
$env:PLATFORMIO_PACKAGES_DIR = Join-Path $pioHome "packages"
$env:PLATFORMIO_PLATFORMS_DIR = Join-Path $pioHome "platforms"
$env:PLATFORMIO_CACHE_DIR = Join-Path $pioHome ".cache"
$env:PLATFORMIO_BUILD_CACHE_DIR = Join-Path $pioHome "buildcache"
$args = @("-m", "platformio", "run", "-e", $envName)
if (-not $BuildOnly) {
$args += @("-t", "upload", "--upload-port", $ComPort)
}
& $pythonCommand @pythonArgs @args
exit $LASTEXITCODE

View File

@@ -168,6 +168,9 @@ String PinManagerClass::getPinSpecialText(int gpio) { // special purpose PIN in
#endif #endif
#if defined(STATUSPIXELPIN)
if (gpio == STATUSPIXELPIN) return(F("WLED Status Pixel"));
#endif
#if defined(STATUSLED) #if defined(STATUSLED)
if (gpio == STATUSLED) return(F("WLED Status LED")); if (gpio == STATUSLED) return(F("WLED Status LED"));
#endif #endif

View File

@@ -71,6 +71,73 @@
#endif #endif
// WLEDMM end // WLEDMM end
#if defined(STATUSLED) || defined(STATUSPIXELPIN)
static inline void writeStatusIndicator(uint32_t color) {
#if defined(STATUSPIXELPIN)
if (statusPixelBus != nullptr) {
if (statusPixelBus->canShow()) {
statusPixelBus->setPixelColor(0, color);
statusPixelBus->show();
}
return;
}
#endif
#if defined(STATUSLED)
#if STATUSLED >= 0
#ifdef STATUSLEDINVERTED
digitalWrite(STATUSLED, color ? LOW : HIGH);
#else
digitalWrite(STATUSLED, color ? HIGH : LOW);
#endif
#else
busses.setStatusPixel(color);
#endif
#endif
}
#endif
#if defined(STATUSPIXELPIN)
#ifndef STATUSPIXELCOLORORDER
#define STATUSPIXELCOLORORDER COL_ORDER_GRB
#endif
static void initStatusPixelBus() {
if (statusPixelBus != nullptr) return;
if (pinManager.isPinAllocated(STATUSPIXELPIN)) {
USER_PRINTF("Skipping status pixel on GPIO %u because the pin is already in use.\n", STATUSPIXELPIN);
return;
}
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2)
constexpr uint8_t maxStatusPixelBusses = 4;
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
constexpr uint8_t maxStatusPixelBusses = 2;
#else
constexpr uint8_t maxStatusPixelBusses = 8;
#endif
uint8_t busIndex = busses.getNumBusses();
if (busIndex >= maxStatusPixelBusses) {
USER_PRINTLN(F("Skipping status pixel because no free hardware LED channel is left."));
return;
}
uint8_t pins[] = {STATUSPIXELPIN};
BusConfig statusCfg(TYPE_WS2812_RGB, pins, 0, 1, STATUSPIXELCOLORORDER, false, 0, RGBW_MODE_MANUAL_ONLY);
statusPixelBus = new BusDigital(statusCfg, busIndex, busses.getColorOrderMap());
if (statusPixelBus == nullptr || !statusPixelBus->isOk()) {
delete statusPixelBus;
statusPixelBus = nullptr;
USER_PRINTLN(F("Failed to initialize onboard status pixel."));
return;
}
statusPixelBus->setBrightness(255, true);
writeStatusIndicator(0);
}
#endif
#if INCLUDE_xTaskGetHandle && defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(WLED_DEBUG_HEAP)) #if INCLUDE_xTaskGetHandle && defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(WLED_DEBUG_HEAP))
// WLEDMM stack debug tool - find async_tcp task, and queries it's free stack // WLEDMM stack debug tool - find async_tcp task, and queries it's free stack
@@ -883,6 +950,9 @@ void WLED::setup()
DEBUG_PRINTLN(F("Initializing strip")); DEBUG_PRINTLN(F("Initializing strip"));
beginStrip(); beginStrip();
#if defined(STATUSPIXELPIN)
initStatusPixelBus();
#endif
DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize()); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize());
USER_PRINTLN(F("\nUsermods setup ...")); USER_PRINTLN(F("\nUsermods setup ..."));
@@ -1568,58 +1638,79 @@ void WLED::handleConnection()
} }
// If status LED pin is allocated for other uses, does nothing // If status LED pin is allocated for other uses, does nothing
// else blink at 1Hz when WLED_CONNECTED is false (no WiFi, ?? no Ethernet ??) // green blink = DDP realtime active
// else blink at 2Hz when MQTT is enabled but not connected // blue blink = AP active
// else turn the status LED off // green solid = network connected
// amber fast blink = MQTT configured but disconnected
// red fast blink = WiFi configured but currently disconnected
// off = idle / no status to show
void WLED::handleStatusLED() void WLED::handleStatusLED()
{ {
#if defined(STATUSLED) #if defined(STATUSLED) || defined(STATUSPIXELPIN)
[[maybe_unused]] uint32_t c = 0; uint32_t c = 0;
uint8_t nextType = 0;
uint16_t blinkIntervalMs = 0;
#if STATUSLED>=0 #if defined(STATUSLED) && STATUSLED>=0
if (pinManager.isPinAllocated(STATUSLED)) { if (pinManager.isPinAllocated(STATUSLED)) {
return; //lower priority if something else uses the same pin return; //lower priority if something else uses the same pin
} }
#endif #endif
if (WLED_CONNECTED) { if (realtimeMode == REALTIME_MODE_DDP) {
c = RGBW32(0,255,0,0); c = RGBW32(0,255,0,0);
ledStatusType = 2; nextType = 2;
} else if (WLED_MQTT_CONNECTED) { blinkIntervalMs = 250;
c = RGBW32(0,128,0,0);
ledStatusType = 4;
} else if (apActive) { } else if (apActive) {
c = RGBW32(0,0,255,0); c = RGBW32(0,0,255,0);
ledStatusType = 1; nextType = 2;
} blinkIntervalMs = 500;
if (ledStatusType) { } else if (WLED_CONNECTED) {
if (millis() - ledStatusLastMillis >= (1000/ledStatusType)) { #ifndef WLED_DISABLE_MQTT
ledStatusLastMillis = millis(); if (mqttEnabled && mqttServer[0] != 0 && !WLED_MQTT_CONNECTED) {
#if 1 c = RGBW32(255,96,0,0);
// WLEDMM un-comment this to stop the blinking nextType = 3;
if ((ledStatusType != 2) && (ledStatusType != 4)) blinkIntervalMs = 250;
ledStatusState = !ledStatusState; } else
else
ledStatusState = HIGH;
#else
ledStatusState = !ledStatusState;
#endif
#if STATUSLED>=0
digitalWrite(STATUSLED, ledStatusState);
#else
busses.setStatusPixel(ledStatusState ? c : 0);
#endif
}
} else {
#if STATUSLED>=0
#ifdef STATUSLEDINVERTED
digitalWrite(STATUSLED, HIGH);
#else
digitalWrite(STATUSLED, LOW);
#endif
#else
busses.setStatusPixel(0);
#endif #endif
{
c = RGBW32(0,255,0,0);
nextType = 1;
}
} else if (WLED_WIFI_CONFIGURED) {
c = RGBW32(255,0,0,0);
nextType = 3;
blinkIntervalMs = 250;
}
if (nextType != ledStatusType) {
ledStatusType = nextType;
ledStatusLastMillis = millis();
ledStatusState = (nextType == 1);
writeStatusIndicator(ledStatusState ? c : 0);
return;
}
if (nextType == 0) {
if (ledStatusState) {
ledStatusState = false;
writeStatusIndicator(0);
}
return;
}
if (nextType == 1) {
if (!ledStatusState) {
ledStatusState = true;
writeStatusIndicator(c);
}
return;
}
if (millis() - ledStatusLastMillis >= blinkIntervalMs) {
ledStatusLastMillis = millis();
ledStatusState = !ledStatusState;
writeStatusIndicator(ledStatusState ? c : 0);
} }
#endif #endif
} }

View File

@@ -751,12 +751,15 @@ WLED_GLOBAL bool doSerializeConfig _INIT(false); // flag to initiate savi
WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers
WLED_GLOBAL bool doPublishMqtt _INIT(false); WLED_GLOBAL bool doPublishMqtt _INIT(false);
// status led // status led / status pixel
#if defined(STATUSLED) #if defined(STATUSLED) || defined(STATUSPIXELPIN)
WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0); WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0);
WLED_GLOBAL uint8_t ledStatusType _INIT(0); // current status type - corresponds to number of blinks per second WLED_GLOBAL uint8_t ledStatusType _INIT(0); // 0=off, 1=solid, 2=slow blink, 3=fast blink
WLED_GLOBAL bool ledStatusState _INIT(false); // the current LED state WLED_GLOBAL bool ledStatusState _INIT(false); // the current LED state
#endif #endif
#if defined(STATUSPIXELPIN)
WLED_GLOBAL BusDigital* statusPixelBus _INIT(nullptr);
#endif
// server library objects // server library objects
WLED_GLOBAL AsyncWebServer server _INIT_N(((80))); WLED_GLOBAL AsyncWebServer server _INIT_N(((80)));