Compare commits

..

7 Commits

Author SHA1 Message Date
Frank f5adaaa44e Merge branch 'main' into json_error_handling 2026-04-08 21:29:57 +02:00
Frank 121250c0a0 Merge branch 'main' into json_error_handling 2026-04-08 21:21:17 +02:00
Frank 252007d37f shorter 2026-04-07 15:43:22 +02:00
Frank 6e76a69417 preserve legacy behaviour 2026-04-07 15:34:20 +02:00
Frank 24e2e3c195 prevent excessive resize - part 2
overlooked this one
2026-04-07 15:30:17 +02:00
Frank Möhle f1c58ee75f fix indentation 2026-04-07 15:11:10 +02:00
Frank c8fc1c89b6 improve robustness against broken / malformed JSON
* malformed / too big wsec.json and cfg.json are treated as read error
* debug_print JSON parse errors
2026-04-07 15:05:15 +02:00
105 changed files with 1268 additions and 4182 deletions
+1 -178
View File
@@ -6,8 +6,6 @@
# docs/cpp.instructions.md — C++ coding conventions
# docs/web.instructions.md — Web UI coding conventions
# docs/cicd.instructions.md — GitHub Actions / CI-CD conventions
# docs/hardening.instructions.md — basic rules for code hardening and robustness
# docs/securecode.instructions.md — more detailed checklists for common vulnerabilities
#
# 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
@@ -16,36 +14,11 @@
language: en-US
reviews:
# generic review setting, see https://docs.coderabbit.ai/reference/configuration#reference
auto_apply_labels: false
# abort_on_close: false
high_level_summary: true
review_status: true
collapse_walkthrough: false
poem: false
# sequence_diagrams: false
auto_review:
enabled: true
base_branches:
- main
- 16_x
- 0_15_x
- V5
ignore_title_keywords:
- WIP
tools:
fbinfer:
enabled: false # Arduino.h not available on Linux analysis host
cppcheck:
enabled: true # cppcheck works fine without Arduino headers
clang:
enabled: true # clang tidy likewise works
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 AGENTS.md and .github/copilot-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.
@@ -54,37 +27,6 @@ reviews:
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.
When reviewing PRs labeled "AI" or when source code appears to be AI-generated, perform these additional checks:
1. VERIFY all referenced preprocessor macros, constants and flags exist by searching the codebase - do not trust the AI's claims about what exists.
2. CHECK for reinvention: search for existing functions/patterns that already solve the same problem.
3. CHECK for singleton data (defined but never used) and for dead/disabled code, and suggest to remove them.
4. VERIFY comments match code behavior - AI frequently generates plausible but incorrect comments.
5. VERIFY numerical stability / accuracy of arithmetic expressions. AI is often wrong when it comes to math and numbers.
6. CHECK for implied but weakly justified assumptions - like usermod loop() call frequency - and ask for clarification.
7. FLAG changes that appear unrelated: deleted comments, unnecessary re-formatting or re-factoring, and modifications in files that seem unrelated to the PR description.
# ── Security hardening — firmware (trust-boundary-aware) ────────────────
- path: "wled00/**/*.{cpp,h,hpp,ino}"
instructions: >
Apply the WLED security hardening rules from docs/hardening.instructions.md,
and consult docs/securecode.instructions.md when more details are needed for actionable recommendations.
Trust Boundary Model — enforce input-validation and bounds-checking rules
ONLY at the first untrusted ingress point. Untrusted ingress points are:
- HTTP/JSON API request bodies and query parameters (/json/*, /win, etc.)
- WebSocket message payloads
- UDP datagrams (parsePacket() / recvfrom() and protocol wrappers for
E1.31, DDP, Art-Net, TPM2.net)
- TCP socket reads
- Serial/UART command input
- ESP-NOW raw messages input
A value that has been validated and range-clamped at its ingress handler is
considered TRUSTED for all subsequent WLED core processing. Do NOT flag or suggest
repeated bounds/range checks or internal uses of already-sanitized data.
When it is unclear whether a value has been sanitized upstream, prefer
requesting clarification over raising a false-positive finding.
- path: "wled00/data/**"
instructions: >
Follow the web UI conventions documented in docs/web.instructions.md.
@@ -93,21 +35,6 @@ reviews:
Files here are built into wled00/html_*.h and wled00/js_*.h by tools/cdata.js — never
edit those generated headers directly.
# ── Security hardening — WebUI (always an ingress/output surface) ────────
- path: "wled00/data/**"
instructions: >
Apply the WLED web UI security rules from docs/securecode.instructions.md
(sections WEB1-WEB7).
The Trust Boundary Model does NOT reduce scope here: the WebUI is both
an ingress point (user input, postMessage, fetched config data) and an
output/rendering surface. Always flag DOM XSS risks, unsafe
innerHTML / document.write / insertAdjacentHTML / outerHTML assignments,
postMessage handlers without origin validation, eval() / new Function(),
unsafe location.href or location.replace() assignments, and DOM insertion
from fetched or config-derived data — regardless of where the data
originates.
- path: "wled00/html_*.h"
instructions: >
These files are auto-generated from wled00/data/ by tools/cdata.js.
@@ -127,33 +54,6 @@ reviews:
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).
# ── Security hardening — usermods (trust-boundary-aware, narrow scope) ───
- path: "usermods/**/*.{cpp,h,hpp}"
instructions: >
For usermods, the untrusted ingress points are:
- readFromConfig(JsonObject& root) and calls to getJsonValue()
- readFromJsonState(JsonObject& obj) — JSON is parsed, but values are client-supplied
- onMqttMessage(char* topic, char* payload) — raw network strings, no core sanitization
- onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) — raw radio bytes
- onUdpPacket(uint8_t* payload, size_t len) — raw UDP buffer, no core filtering
Values retrieved at these ingress points are considered trusted only after the
usermod itself has validated and range-clamped them.
Flag ONLY downstream uses of ingress-derived values where an out-of-range or
unexpected value can cause misbehaviour that is not already guarded, for example:
- `switch` statements on an ingress-derived value with no `default` branch,
or with a missing `break` where fall-through is unintentional
- array or buffer indexing with an ingress-derived value where the index is
not clamped before use
- arithmetic with an ingress-derived value that can overflow or produce a
negative result used as a size or count
Do NOT flag:
- getJsonValue() call sites themselves (type coercion is handled by ArduinoJson)
- Internal logic that operates on values already confirmed safe at ingress
- Repeated range checks on values that have already been clamped
- General memory-safety patterns unrelated to ingress-derived data flow
- path: ".github/workflows/*.{yml,yaml}"
instructions: >
Follow the CI/CD conventions documented in docs/cicd.instructions.md.
@@ -180,80 +80,3 @@ reviews:
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).
# ── Secrets / sensitive information scanning ────────────────────────────
- path: "platformio*.ini*"
instructions: >
Scan for secrets, passwords, and other sensitive information accidentally
committed to PlatformIO configuration files (platformio.ini,
platformio_override.ini, platformio_override.ini.sample).
Flag any of the following:
- build_flags entries that define credentials as literal values, e.g.:
-DWIFI_SSID=\"<YOUR_SSID>\" -DWIFI_PASS=\"<YOUR_PASSWORD>\"
-DOTA_PASS=\"<OTA_PASSWORD>\" -DMQTT_PASS=\"<MQTT_PASSWORD>\"
Flag only when the value is not a recognisable placeholder (see below).
- upload_flags or upload_port values that embed a password or auth token (e.g., --auth=<PASSWORD> or any URL using credential-bearing userinfo).
- Any key = <value> pair whose key name contains "pass", "password",
"secret", "token", "key", "credential", or "auth" where the value is
a non-empty, non-placeholder literal string.
- Hardcoded IP addresses or hostnames paired with credentials in the
same environment section.
- API keys or access tokens as literal strings in any field.
Do NOT flag:
- Values that are clearly template placeholders (e.g., YOUR_SSID,
<YOUR_PASSWORD>, changeme, example_token, your_password_here).
- Values that use PlatformIO environment variable substitution (${sysenv.WIFI_PASS} or ${env:WIFI_PASS}).
- Comments that only explain what a field should contain.
- platformio_override.ini.sample entries that contain only
placeholder/example values.
- path: "usermods/**/library.json"
instructions: >
Scan for secrets and sensitive information in usermod dependency manifests.
Flag any of the following:
- Dependency URLs that embed credentials in the URL itself (e.g., any URL containing credential-bearing userinfo).
- Personal access tokens, OAuth tokens, or API keys as literal strings
anywhere in the file.
- Values matching well-known secret patterns: GitHub PATs (ghp_...,
github_pat_...), AWS access keys (AKIA...), or similarly structured
high-entropy tokens.
Do NOT flag:
- Plain HTTPS or SSH URLs without embedded credentials.
- Version specifiers, semver ranges, or commit SHA references that
contain no credential prefix.
- Repository owner/name path segments (not credential material).
- path: "usermods/**/{readme,README,Readme}.md"
instructions: >
Scan for secrets, passwords, and sensitive information in usermod
documentation files, including inside code blocks, inline code, and prose.
Flag any of the following:
- Hardcoded Wi-Fi SSID or password values that appear to be real (non-placeholder)
strings in configuration or installation examples.
- Hardcoded OTA, AP, or MQTT passwords in code snippets or step-by-step
instructions.
- API keys, bearer tokens, or access tokens shown as literal values.
- Example platformio_override.ini snippets that contain real-looking
credential values instead of placeholders.
- Hardcoded IP addresses combined with credentials in the same example.
Do NOT flag:
- Values that are clearly template placeholders (e.g., YOUR_SSID,
<password>, my_secret, changeme, ****).
- Generic prose describing what a field means without supplying a value.
- Asterisk-masked values (e.g., ******, ••••••).
finishing_touches:
# Docstrings | Options for generating Docstrings for your PRs/MRs.
docstrings:
# Docstrings | Allow CodeRabbit to generate docstrings for PRs/MRs.
# default: true - disabled in WLED: has caused confusion in the past
enabled: false
unit_tests:
# default: true - disabled in WLED: we don't have a unit test framework, this option just confuses contributors
enabled: false
-14
View File
@@ -1,14 +0,0 @@
{
"features": {
"ghcr.io/ar90n/devcontainer-features/platformio:1": {
"version": "1.0.2",
"resolved": "ghcr.io/ar90n/devcontainer-features/platformio@sha256:4a28f8c147d81ff996afebe6f43e453355b6373dd4022960351090b532ac9dd3",
"integrity": "sha256:4a28f8c147d81ff996afebe6f43e453355b6373dd4022960351090b532ac9dd3"
},
"ghcr.io/devcontainers/features/node:2": {
"version": "2.0.0",
"resolved": "ghcr.io/devcontainers/features/node@sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f",
"integrity": "sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f"
}
}
}
+9 -9
View File
@@ -1,10 +1,11 @@
{
"name": "WLED-dev",
"image": "mcr.microsoft.com/devcontainers/python:3",
"features": {
"ghcr.io/ar90n/devcontainer-features/platformio:1": {},
"ghcr.io/devcontainers/features/node:2": {
"version": "v20.18.3"
"name": "Python 3",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
"VARIANT": "3"
}
},
@@ -42,8 +43,7 @@
},
"extensions": [
"ms-python.python",
"platformio.platformio-ide",
"ms-vscode.cpptools-extension-pack"
"platformio.platformio-ide"
]
}
},
@@ -52,7 +52,7 @@
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "",
"postCreateCommand": "bash -i -c 'nvm install && npm ci'",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
+4 -7
View File
@@ -5,9 +5,7 @@ body:
- type: markdown
attributes:
value: |
Please quickly search existing issues first before submitting a bug.
Please don't submit long error analyses created by an AI agent, these are often totally wrong.
Just describe the problem in your own words.
Please quickly search existing issues first before submitting a bug.
- type: textarea
id: what-happened
attributes:
@@ -61,13 +59,12 @@ body:
options:
- ESP8266
- ESP32
- ESP32 with ethernet
- ESP32-S2
- ESP32-S3
- ESP32-S2
- ESP32-C3
- ESP32-C5 (experimental)
- Other
- ESP32-C6 (experimental)
- ESP32-P4 (experimental)
- ESP32-C5 (experimental)
validations:
required: true
- type: textarea
-2
View File
@@ -112,9 +112,7 @@ Match this workflow in local development to catch failures before pushing.
## Important Reminders
- Always **commit source code**
-  Every pull request MUST include a clear description of *what* changed and *why*.
- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`
- After modifying source code files, check that any **previous comments have been preserved** or updated to reflect the new behaviour.
- 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`
+14 -7
View File
@@ -50,7 +50,7 @@ For detailed build timeouts, development workflows, troubleshooting, and validat
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 # maintenance for release 16.x.y
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)
@@ -60,7 +60,6 @@ main # Main development trunk (daily/nightly) 17.0.0-dev
- ``main``: development trunk (daily/nightly)
- ``V5`` and ``V5-C6``: code rework for esp-idf 5.5.x (unstable) - branched from ``main``.
- ``16_x``: maintenance for release 16.x.y
- ``0_15_x``: bugfixing / maintenance for release 0.15.x
### Repository Structure
@@ -109,20 +108,28 @@ docs/ # Contributor docs, coding guidelines
- **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. 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.
- **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.
- **Verify feature-flag names.** Every `WLED_ENABLE_*` / `WLED_DISABLE_*` flag must exactly match one of the names below — misspellings are silently ignored by the preprocessor (e.g. `WLED_IR_DISABLE` instead of `WLED_DISABLE_INFRARED`), causing silent build variations. Flag unrecognised names as likely typos and suggest the correct spelling.
<br>**`WLED_DISABLE_*`**: `2D`, `ADALIGHT`, `ALEXA`, `BROWNOUT_DET`, `ESPNOW`, `FILESYSTEM`, `HUESYNC`, `IMPROV_WIFISCAN`, `INFRARED`, `LOXONE`, `MQTT`, `OTA`, `PARTICLESYSTEM1D`, `PARTICLESYSTEM2D`, `PIXELFORGE`, `WEBSOCKETS`
<br>**`WLED_ENABLE_*`**: `ADALIGHT`, `AOTA`, `DMX`, `DMX_INPUT`, `DMX_OUTPUT`, `FS_EDITOR`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `LOXONE`, `MQTT`, `PIXART`, `PXMAGIC`, `USERMOD_PAGE`, `WEBSOCKETS`, `WPA_ENTERPRISE`
- **C++ formatting available**: `clang-format` is installed but not in CI
- No automated linting is configured — match existing code style in files you edit.
Refer to `docs/cpp.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
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.
### Pull Request Expectations
- **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.
- **Modifications to ``platformio.ini`` MUST be approved explicitly** by a *maintainer* or *WLED organisation Member*. Modifications to the global build environment may break github action builds. Always flag them.
- **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.
### 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.
-45
View File
@@ -1,45 +0,0 @@
; ----------------------------------------------------------------------------
; platformio_release.ini.template
; ----------------------------------------------------------------------------
; Copied to platformio_release.ini by the release CI workflow
; (.github/workflows/release.yml -> build.yml with `release: true`)
; in order to extend the matrix of `default_envs` built and published
; for tagged releases and remove the debugging usersmods env
;
; This file overrides `[platformio].default_envs` from platformio.ini via
; `extra_configs`. It MUST list every env that should be released - including
; the regular CI default_envs - because it fully replaces the parent value.
;
; Do NOT commit a generated platformio_release.ini (it's in .gitignore).
; ----------------------------------------------------------------------------
[platformio]
default_envs = nodemcuv2
esp8266_2m
esp8266_2m_min
esp01_1m_full
nodemcuv2_160
esp8266_2m_160
esp01_1m_full_160
nodemcuv2_compat
esp8266_2m_compat
esp01_1m_full_compat
esp32dev
esp32dev_debug
esp32_eth
esp32_wrover
lolin_s2_mini
esp32c3dev
esp32c3dev_qio
esp32S3_wroom2
esp32s3dev_16MB_opi
esp32s3dev_8MB_opi
esp32s3dev_8MB_qspi
esp32s3dev_8MB_none
esp32s3_4M_qspi
; HUB75 release-only envs
esp32dev_hub75
esp32dev_hub75_forum_pinout
adafruit_matrixportal_esp32s3
esp32s3dev_16MB_opi_hub75 ;; MoonHub
esp32s3dev_4MB_qspi_hub75 ;; HD-WF2
+4 -30
View File
@@ -3,15 +3,7 @@ name: WLED Build
# Only included into other workflows
on:
workflow_call:
inputs:
release:
description: 'Build the release env matrix (uses .github/platformio_release.ini.template)'
type: boolean
default: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
get_default_envs:
@@ -19,9 +11,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Apply release config
if: inputs.release
run: cp .github/platformio_release.ini.template platformio_release.ini
- uses: actions/setup-python@v5
with:
python-version: '3.12'
@@ -46,9 +35,6 @@ jobs:
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
steps:
- uses: actions/checkout@v4
- name: Apply release config
if: inputs.release
run: cp .github/platformio_release.ini.template platformio_release.ini
- name: Set up Node.js
uses: actions/setup-node@v4
with:
@@ -65,8 +51,8 @@ jobs:
~/.platformio/.cache
~/.buildcache
build_output
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', '.github/platformio_release.ini.template', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', '.github/platformio_release.ini.template', 'pio-scripts/output_bins.py') }}-
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
- name: Set up Python
uses: actions/setup-python@v5
with:
@@ -77,21 +63,9 @@ jobs:
- name: Build firmware
run: pio run -e ${{ matrix.environment }}
- name: Get artifact name from bin filename
id: artifact_name
run: |
bin=$(ls build_output/release/*.bin 2>/dev/null | head -1)
if [ -n "$bin" ]; then
# Strip WLED_<version>_ prefix from WLED_<version>_<RELEASE_NAME>.bin
base=$(basename "$bin" .bin)
release_name=$(echo "$base" | sed 's/^[^_]*_[^_]*_//')
echo "name=firmware-$release_name" >> $GITHUB_OUTPUT
else
echo "name=firmware-${{ matrix.environment }}" >> $GITHUB_OUTPUT
fi
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact_name.outputs.name }}
name: firmware-${{ matrix.environment }}
path: |
build_output/release/*.bin
build_output/release/*_ESP02*.bin.gz
+2 -5
View File
@@ -7,9 +7,6 @@ on:
# This can be used to allow manually triggering nightlies from the web interface
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
wled_build:
uses: ./.github/workflows/build.yml
@@ -29,12 +26,12 @@ jobs:
uses: janheinrichmerker/action-github-changelog-generator@v2.4
with:
token: ${{ secrets.GITHUB_TOKEN }}
sinceTag: v16.0.0
sinceTag: v0.15.0
output: CHANGELOG_NIGHTLY.md
# Exclude issues that were closed without resolution from changelog
excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned'
- name: Update Nightly Release
uses: andelf/nightly-release@5834076edc55cc05975561c9722043f072ac5c26
uses: andelf/nightly-release@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
+4 -14
View File
@@ -5,15 +5,10 @@ on:
tags:
- '*'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
wled_build:
uses: ./.github/workflows/build.yml
with:
release: true
release:
name: Create Release
@@ -23,14 +18,6 @@ jobs:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Create draft release
uses: softprops/action-gh-release@v1
with:
body: "Release assets uploaded. Release notes pending..."
draft: True
files: |
*.bin
*.bin.gz
- name: "✏️ Generate release changelog"
id: changelog
uses: janheinrichmerker/action-github-changelog-generator@v2.4
@@ -40,9 +27,12 @@ jobs:
maxIssues: 500
# Exclude issues that were closed without resolution from changelog
excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned'
- name: Update release description
- name: Create draft release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
draft: True
files: |
*.bin
*.bin.gz
+11 -110
View File
@@ -4,51 +4,33 @@ on:
pull_request:
paths:
- usermods/**
push:
paths:
- usermods/**
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
get_usermod_envs:
# Only run for pull requests from forks (not from branches within wled/WLED)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
if: github.event.pull_request.head.repo.full_name != github.repository
name: Gather Usermods
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
fetch-depth: 0
- name: Get changed usermod environments
python-version: '3.12'
cache: 'pip'
- name: Install PlatformIO
run: pip install -r requirements.txt
- name: Get default environments
id: envs
run: |
# Usermods whose directories changed in this PR
changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} HEAD \
| grep '^usermods/' | cut -d/ -f2 | sort -u || true)
# All usermods with a library.json (excluding known-incompatible ones)
all=$(find usermods/ -name library.json \
| xargs dirname | xargs -n 1 basename \
| grep -v PWM_fan | grep -v BME68X_v2 | grep -v pixels_dice_tray \
| sort || true)
if [ -z "$changed" ] || [ -z "$all" ]; then
echo "usermods=[]" >> $GITHUB_OUTPUT
else
usermods=$(comm -12 <(echo "$all") <(echo "$changed") | jq -R | jq --slurp -c)
echo "usermods=$usermods" >> $GITHUB_OUTPUT
fi
echo "usermods=$(find usermods/ -name library.json | xargs dirname | xargs -n 1 basename | jq -R | grep -v PWM_fan | grep -v BME68X_v2| grep -v pixels_dice_tray | jq --slurp -c)" >> $GITHUB_OUTPUT
outputs:
usermods: ${{ steps.envs.outputs.usermods }}
build:
# Only run for pull requests from forks (not from branches within wled/WLED)
# Skip when no changed usermods were found (e.g. only non-library changes)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && needs.get_usermod_envs.outputs.usermods != '[]'
if: github.event.pull_request.head.repo.full_name != github.repository
name: Build Enviornments
runs-on: ubuntu-latest
needs: get_usermod_envs
@@ -89,85 +71,4 @@ jobs:
cat platformio_override.ini
- name: Build firmware
run: pio run -e ${{ matrix.environment }}
get_custom_build_envs:
name: Gather Custom Build Environments
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Find usermods with custom build environments
id: custom_envs
run: |
# On PRs: only scan usermods whose directories changed.
# On push: scan all usermods (validates the full set on merge).
if [ "${{ github.event_name }}" = "pull_request" ]; then
changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} HEAD \
| grep '^usermods/' | cut -d/ -f2 | sort -u || true)
if [ -z "$changed" ]; then
echo "matrix=[]" >> $GITHUB_OUTPUT
exit 0
fi
samples=$(for mod in $changed; do
f="usermods/$mod/platformio_override.ini.sample"
[ -f "$f" ] && echo "$f"
done | sort)
else
samples=$(find usermods/ -name "platformio_override.ini.sample" | sort)
fi
result='[]'
for sample in $samples; do
usermod=$(dirname "$sample" | xargs basename)
# Skip usermods known to be incompatible (same list as get_usermod_envs)
case "$usermod" in PWM_fan|BME68X_v2|pixels_dice_tray) continue ;; esac
envs=$(grep -E '^\[env:[^]]+\]' "$sample" | sed 's/^\[env:\(.*\)\]$/\1/')
for env in $envs; do
result=$(echo "$result" | jq --arg u "$usermod" --arg e "$env" '. + [{usermod: $u, env: $e}]')
done
done
echo "matrix=$(echo "$result" | jq -c '.')" >> $GITHUB_OUTPUT
outputs:
matrix: ${{ steps.custom_envs.outputs.matrix }}
build_custom:
name: Build Custom Env (${{ matrix.usermod }} / ${{ matrix.env }})
runs-on: ubuntu-latest
needs: get_custom_build_envs
if: needs.get_custom_build_envs.outputs.matrix != '[]'
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.get_custom_build_envs.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm ci
- name: Cache PlatformIO
uses: actions/cache@v4
with:
path: |
~/.platformio/.cache
~/.buildcache
build_output
key: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
restore-keys: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install PlatformIO
run: pip install -r requirements.txt
- name: Apply custom build environment
run: cp -v "usermods/${{ matrix.usermod }}/platformio_override.ini.sample" platformio_override.ini
- name: Build firmware
run: pio run -e ${{ matrix.env }}
run: pio run -e ${{ matrix.environment }}
-1
View File
@@ -16,7 +16,6 @@ __pycache__/
esp01-update.sh
platformio_override.ini
platformio_release.ini
replace_fs.py
wled-update.sh
-267
View File
@@ -1,267 +0,0 @@
# AGENTS.md — WLED AI Coding Agent & AI Code Review Reference
WLED is C++ firmware for ESP32/ESP8266 microcontrollers controlling addressable LEDs,
with a web UI (HTML/JS/CSS). Built with PlatformIO (Arduino framework) and Node.js tooling.
See also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.md`,
`docs/cpp.instructions.md`, `docs/web.instructions.md`, `docs/cicd.instructions.md`,
`docs/hardening.instructions.md`, `docs/securecode.instructions.md`.
Always reference these instructions - including coding guidelines in `docs/` - first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
## Build Commands
| Command | Purpose | Timeout |
|---|---|---|
| `npm ci` | Install Node.js deps (required first) | 30s |
| `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s |
| `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min |
| `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous |
| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 5 min |
| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 5 min |
**Always run `npm ci && npm run build` before `pio run`.** The web UI build generates
required C headers for firmware compilation.
### Running a Single Test
Tests use Node.js built-in test runner (`node:test`). The single test file is
`tools/cdata-test.js`. Run it with:
```bash
npm test # runs all tests via `node --test`
node --test tools/cdata-test.js # run just that file directly
```
There are no C++ unit tests. Firmware is validated by successful compilation across
target environments. Always build after code changes: `pio run -e esp32dev`.
### Common Firmware Environments
`esp32dev`, `nodemcuv2`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`, `lolin_s2_mini`
### Recovery / Troubleshooting
```bash
npm run build -- -f # force web UI rebuild
rm -f wled00/html_*.h wled00/js_*.h && npm run build # clean + rebuild UI
pio run --target clean # clean PlatformIO build artifacts
rm -rf node_modules && npm ci # reinstall Node.js deps
```
## Project Structure
```text
wled00/ # Main firmware source (C++)
data/ # Web UI source (HTML/JS/CSS) — tabs for indentation
html_*.h, js_*.h # Auto-generated (NEVER edit or commit)
src/ # Sub-modules: fonts, bundled dependencies (ArduinoJSON)
usermods/ # Community usermods (each has library.json + .cpp/.h)
platformio.ini # Build configuration and environments
pio-scripts/ # PlatformIO build scripts (Python)
tools/ # Node.js build tools (cdata.js) and tests
docs/ # Coding convention docs
.github/workflows/ # CI/CD (GitHub Actions)
```
### Branch / Release Structure
```text
main # Main development trunk (daily/nightly) 17.0.0-dev. Target branch for PRs.
├── V5 # special branch: code rework for esp-idf 5.5.x (unstable)
├── V5-C6 # special branch: integration of new MCU types: esp32-c5, esp32-c6, esp32-p4 (unstable)
16_x # maintenance for release 16.0.x
0_15_x # maintenance (bugfixes only) for previous release 0.15.x
(tag) v0.14.4 # old version 0.14.4 (no maintenance)
(tag) v0.13.3 # old version 0.13.3 (no maintenance)
(tag) v0. ... . ... # historical versions 0.12.x and before
```
## C++ Code Style (wled00/, usermods/)
### Formatting
- **2-space indentation** (no tabs in C++ files)
- K&R brace style preferred (opening brace on same line)
- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`
- Space after keywords (`if (...)`, `for (...)`), no space before function parens (`doStuff(a)`)
- No enforced line-length limit
### Comments
- `//` for inline (always space after), `/* */` for block comments
- Important: AI-generated source code blocks **must be mark with `// AI: below section was generated by an AI` / `// AI: end`**
### Naming Conventions
| Kind | Convention | Examples |
|---|---|---|
| Functions, variables | camelCase | `setRandomColor()`, `effectCurrent` |
| Classes, structs | PascalCase | `BusConfig`, `UsermodTemperature` |
| Macros, constants | UPPER_CASE | `WLED_MAX_USERMODS`, `FX_MODE_STATIC` |
| Private members | _camelCase | `_type`, `_bri`, `_len` |
| Enum values | PascalCase | `PinOwner::BusDigital` |
### Includes
- Include `"wled.h"` as the primary project header
- Project headers first, then platform/Arduino, then third-party
- Platform-conditional includes wrapped in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`
### Types and Const
- Prefer `const &` for read-only function parameters
- Mark getter/query methods `const`; use `static` for methods not accessing instance state
- Prefer `constexpr` over `#define` for compile-time constants when possible
- Use `static_assert` over `#if ... #error`
- Use `uint_fast16_t` / `uint_fast8_t` in hot-path code
### Error Handling
- **No C++ exceptions** — some builds disable them
- Use return codes (`false`, `-1`) and global flags (`errorFlag = ERR_LOW_MEM`)
- Use early returns as guard clauses: `if (!enabled || (strip.isUpdating() && (millis() - last_time < MAX_USERMOD_DELAY))) return;`
- Debug output: `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` (compiled out unless `-D WLED_DEBUG`)
### Strings and Memory
- Use `F("string")` for string constants (saves RAM on ESP8266)
- Use `PSTR()` with `DEBUG_PRINTF_P()` for format strings
- Avoid `String` in hot paths; acceptable in config/setup code
- Use `d_malloc()` (DRAM-preferred) / `p_malloc()` (PSRAM-preferred) for allocation
- No VLAs — use fixed arrays or heap allocation
- Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation
#### ESP32 PSRAM guidelines
- **Check availability**: Test chip availability with `psramFound() && ESP.getPsramSize() > 0` before assuming PSRAM is present. Never rely on `BOARD_HAS_PSRAM`only.
- **DMA compatibility**: on ESP32 (classic), PSRAM buffers are **not DMA-capable**. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.
- **Fragmentation**: PSRAM allocations fragment less than DRAM because the region is larger. But avoid mixing small and large allocations in PSRAM — small allocations waste the MMU page granularity.
- **Performance**: Prefer DRAM (or IRAM) for hot-path data that is *frequently* used. Prefer PSRAM for capacity-oriented buffers where slightly slower access times can be tolerated.
Background Info:
- PSRAM access is up to 18× slower than DRAM on ESP32 (dual-SPI bus), 310× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM.
- Consider that ESP32 often crashes when the largest DRAM chunk gets below 10 KB.
### Preprocessor / Feature Flags
- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)
- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.
- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.
- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`
### Math Functions
- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)
- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`
- Replace `inoise8` / `inoise16` with `perlin8` / `perlin16`
### Hot-Path Code (Pixel Pipeline)
- Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))`
- Cache class members to locals before loops
- Pre-compute invariants outside loops; use reciprocals to avoid division
- Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)`
### ESP32 Tasks
- `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog
- Do not use `delay()` in effects (FX.cpp) or hot pixel path
#### ESP32 Task Synchronization
- Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour.
- **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues.
- **Important**: Not every shared resource needs a mutex. Some synchronization is guaranteed by the overall control flow, for example when function calls are sequenced within the same loop iteration.
- Consider using `std::atomic` or RAII scoped guards as alternatives to mutexes, semaphores or queues.
## Web UI Code Style (wled00/data/)
- **Tab indentation** for HTML, JS, and CSS
- camelCase for JS functions/variables
- Reuse helpers from `common.js` — do not duplicate utilities
- After editing, run `npm run build` to regenerate headers
- **Never edit** `wled00/html_*.h` or `wled00/js_*.h` directly
## Usermod Pattern
Usermods live in `usermods/<name>/` with a `.cpp`, optional `.h`, `library.json`, and `readme.md`.
```cpp
class MyUsermod : public Usermod {
private:
bool enabled = false;
static const char _name[];
public:
void setup() override { /* ... */ } // runs once at start-up
void loop() override { /* ... */ } // runs once per main loop iteration
void addToConfig(JsonObject& root) override { /* ... */ } // create/add persistent settings (usermod settings)
bool readFromConfig(JsonObject& root) override { /* ... */ } // read from persistent settings (usermod settings UI)
uint16_t getId() override { return USERMOD_ID_MYMOD; }
void addToJsonInfo(JsonObject& root) override { /* ... */ } // Add custom items to the "info" page and to /json/info
void appendConfigData() override { /* ... */ } // Customize the settings page: dropdowns, checkboxes, extra text, etc. Buffer size is limited!
};
const char MyUsermod::_name[] PROGMEM = "MyUsermod";
static MyUsermod myUsermod;
REGISTER_USERMOD(myUsermod);
```
refer to detailed examples in `usermods/EXAMPLE/`, `usermods/user_fx/` and [in the user documentation for custom features](https://kno.wled.ge/advanced/custom-features/).
- Activate via `custom_usermods = ` in platformio build config. The `usermod_v2_` prefix or `_v2` suffix can be omitted.
- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)
- Store repeated strings as `static const char[] PROGMEM`
- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see below)
### Usermod IDs
A unique ID (registered in `wled00/const.h` and overriding `getId()`) is **only required** when a usermod needs one or more of the following:
1. **Inter-usermod communication** — another usermod or an FX effect calls `UsermodManager::lookup(mod_id)` or `UsermodManager::getUMData(..., mod_id)` to find or request data from this specific usermod.
2. **Pin ownership via `pinManager`** — the usermod allocates GPIO pins through `pinManager`. Pin ownership is tracked by `PinOwner` enum values that map directly to `USERMOD_ID_*` constants (see `wled00/pin_manager.h`). This prevents pin-conflict bugs.
3. **Identification in JSON info**`UsermodManager::addToJsonInfo` emits each mod's ID into the `"um"` array; a unique ID makes the mod identifiable in that output.
If none of the above apply, the usermod may omit `getId()` (or return the default `USERMOD_ID_UNSPECIFIED`) and does **not** need an entry in `const.h`.
### Usermod `loop()`
- Called once per main loop iteration. Usermods should simply `return` when `!enabled`.
- Frequency of calls varies with system load:
* up to 2000 times/sec with few LEDs and little background activity,
* between 20 and 300 times/second during high workload from effects and other usermods,
* (worst case) down to 1-3 times/sec during FS activity or when serving lots of network API requests.
## CI/CD
CI runs on every push/PR via GitHub Actions (`.github/workflows/wled-ci.yml`):
1. `npm test` (web UI build validation)
2. Firmware compilation for all default environments (~22 targets)
3. Post-link validation of usermod linkage (`validate_modules.py`)
No automated linting is configured. Match existing code style in files you edit.
## General Rules
- Important: Repository language is **English**. This applies to source code (including comments), commit messages and any kind of documentation for developer or users.
- The `docs/` folder is for developer/contributor information (coding conventions, architecture, etc.). User documentation is maintained in the [wled/WLED-Docs](https://github.com/wled/WLED-Docs) repository.
- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`.
- When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description.
- No force-push on open PRs!
- Important: **Changes to `platformio.ini` require maintainer approval**!
- PRs should respect `.gitignore` and not upload files like `platformio_override.ini`. PR authors may add buildenv examples for custom boards into `platformio_override.ini.sample`.
- Remove dead/unused code — justify or delete it.
- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor).
- Provide references when making analyses or recommendations. Support factual claims with verifiable citations, references or concrete evidence; **never fabricate citations**.
- **Highlight user-visible breaking changes and ripple effects** during reviews. Ask for confirmation that these were introduced intentionally.
### Security Hardening
When writing or reviewing code in `wled00/`, `usermods/`, `wled00/data/`, or `.github/workflows/`,
consult `docs/hardening.instructions.md` (concise checklist) and `docs/securecode.instructions.md` (detailed rules with examples).
These files define WLED's threat model, trust boundary model, and WLED-specific constraints (no TLS baseline, no UDP authentication for protocol-defined
multicast/broadcast, firewall-isolated deployment assumed).
### Attribution for AI-generated code
Using AI-generated code can hide the source of the inspiration / knowledge / sources it used.
- Document attribution of inspiration / knowledge / sources used in the code, e.g. link to GitHub repositories or other websites describing the principles / algorithms used.
- When a larger block of code is generated by an AI tool, embed it into `// AI: below section was generated by an AI` ... `// AI: end` comments (see Comments section).
- Every non-trivial AI-generated function should have a brief comment describing what it does. Explain parameters when their names alone are not self-explanatory.
- AI-generated code must be well documented with meaningful comments that explain intent, assumptions, and non-obvious logic. Do not rephrase source code; explain concepts and reasoning.
### Supporting Reviews and Discussions
- **For "is it worth doing?" debates** about proposed reliability, safety, or data-integrity mechanisms (CRC checks, backups, power-loss protection): suggest a software **FMEA** (Failure Mode and Effects Analysis).
Clarify the main feared events, enumerate failure modes, assess each mitigation's effectiveness per failure mode, note common-cause failures, and rate credibility for the typical WLED use case.
+6 -5
View File
@@ -10,8 +10,8 @@ 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](AGENTS.md#project-structure) (in our AI instructions)
* [Instructions for creating usermods](AGENTS.md#usermod-pattern) (in our AI instructions)
* [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.
@@ -30,8 +30,7 @@ This lets you update your PR if needed, while you can work on other tasks in 'ma
### Target branch for pull requests
> [!IMPORTANT]
> Please make all PRs against the `main` branch.
Please make all PRs against the `main` branch.
### Describing your PR
@@ -141,6 +140,8 @@ Sometimes you might hit merge conflicts with `main` that are harder to solve. He
### Additional Resources
Want to know more? Check out:
- 📚 [GitHub Desktop documentation](https://docs.github.com/en/desktop) - if you prefer GUI tools
- 🎓 [How to properly submit a PR](https://github.com/wled-dev/WLED/wiki/How-to-properly-submit-a-PR) - detailed tips and tricks
## After Approval
Once approved, a maintainer will merge your PR (possibly squashing commits).
@@ -166,7 +167,7 @@ AI tools are powerful but "often wrong" - your judgment is essential! 😊
- ✅ **Understand the code** - As the person contributing to WLED, make sure you understand exactly what the AI-generated source code does
- ✅ **Review carefully** - AI can lose comments, introduce bugs, or make unnecessary changes
- ✅ **Be transparent** - Add comments `// AI: below section was generated by an AI` ... `// AI: end` around larger chunks
- ✅ **Be transparent** - Add a comment like `// This section was AI-generated` for larger chunks
- ✅ **Use AI for translation** - AI is great for translating comments to English (but verify technical terms!)
### Code style
+1 -5
View File
@@ -25,7 +25,6 @@ See also: [CONTRIBUTING.md](../CONTRIBUTING.md) for general style guidelines tha
- **camelCase** for functions and variables: `setValuesFromMainSeg()`, `effectCurrent`
- **PascalCase** for classes and structs: `PinManagerClass`, `BusConfig`
- **PascalCase** for enum values: `PinOwner::BusDigital`
- **UPPER_CASE** for macros and constants: `WLED_MAX_USERMODS`, `DEFAULT_CLIENT_SSID`
## General
@@ -58,7 +57,7 @@ Most headers use `#ifndef` / `#define` guards. Some newer headers add `#pragma o
void calculateCRC(const uint8_t* data, size_t len) {
...
}
// AI: end
// 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.
@@ -96,7 +95,6 @@ uint8_t gammaCorrect(uint8_t value, float gamma);
- 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)
- Store repeated strings as `static const char[] PROGMEM`
<!-- HUMAN_ONLY_START -->
On **ESP8266** this explicitly stores the string in flash (PROGMEM), saving precious RAM — every byte counts on that platform.
@@ -515,8 +513,6 @@ void myTask(void*) {
- **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).
- In C/C++, additive operators (`+`, `-`) have HIGHER precedence than shift operators (`<<`, `>>`). Therefore `x - edge0 << 8` correctly parses as `(x - edge0) << 8`. Do NOT flag this pattern as a precedence bug. When reviewing WLED fixed-point code or any C/C++ shift expressions, verify against cppreference before claiming precedence issues with mixed `-`/`+` and `<<`/`>>` expressions.
- **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:
-859
View File
@@ -1,859 +0,0 @@
---
applyTo: "**/*.cpp,**/*.h,**/*.hpp,**/*.ino"
---
# ESP-IDF Coding Guide (within arduino-esp32)
WLED runs on the Arduino-ESP32 framework, which wraps ESP-IDF. Understanding the ESP-IDF layer is essential when writing chip-specific code, managing peripherals, or preparing for the IDF v5.x migration. This guide documents patterns already used in the codebase and best practices derived from Espressif's official examples.
> **Scope**: This file is an optional review guideline. It applies when touching chip-specific code, peripheral drivers, memory allocation, or platform conditionals.
> **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 -->
## Identifying the Build Target: `CONFIG_IDF_TARGET_*`
Use `CONFIG_IDF_TARGET_*` macros to gate chip-specific code at compile time. These are set by the build system and are mutually exclusive — exactly one is defined per build.
| Macro | Chip | Architecture | Notes |
|---|---|---|---|
| `CONFIG_IDF_TARGET_ESP32` | ESP32 (classic) | Xtensa dual-core | Primary target. Has DAC, APLL, I2S ADC mode |
| `CONFIG_IDF_TARGET_ESP32S2` | ESP32-S2 | Xtensa single-core | Limited peripherals. 13-bit ADC |
| `CONFIG_IDF_TARGET_ESP32S3` | ESP32-S3 | Xtensa dual-core | Preferred for large installs. Octal PSRAM, USB-OTG |
| `CONFIG_IDF_TARGET_ESP32C3` | ESP32-C3 | RISC-V single-core | Minimal peripherals. RISC-V clamps out-of-range float→unsigned casts |
| `CONFIG_IDF_TARGET_ESP32C5` | ESP32-C5 | RISC-V single-core | Wi-Fi 2.4Ghz + 5Ghz, Thread/Zigbee. Future target |
| `CONFIG_IDF_TARGET_ESP32C6` | ESP32-C6 | RISC-V single-core | Wi-Fi 6, Thread/Zigbee. Future target |
| `CONFIG_IDF_TARGET_ESP32P4` | ESP32-P4 | RISC-V dual-core | High performance. Future target |
<!-- HUMAN_ONLY_END -->
### Build-time validation
WLED validates at compile time that exactly one target is defined and that it is a supported chip (`wled.cpp` lines 3961). Follow this pattern when adding new chip-specific branches:
<!-- HUMAN_ONLY_START -->
```cpp
#if defined(CONFIG_IDF_TARGET_ESP32)
// classic ESP32 path
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
// S3-specific path
#elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4)
// RISC-V common path
#else
#warning "Untested chip — review peripheral availability"
#endif
```
<!-- HUMAN_ONLY_END -->
### Guidelines
- **Always test on the actual chip** before claiming support. Simulators and cross-compilation can hide peripheral differences.
- **Prefer `#elif` chains** over nested `#ifdef` for readability.
- **Do not use `CONFIG_IDF_TARGET_*` for feature detection.** Use `SOC_*` capability macros instead (see next section). For example, use `SOC_I2S_SUPPORTS_ADC` instead of `CONFIG_IDF_TARGET_ESP32` to check for I2S ADC support.
- When a feature must be disabled on certain chips, use explicit `static_assert()` or `#warning` directives so the build clearly reports what is missing.
---
## Hardware Capability Detection: `SOC_*` Macros
`SOC_*` macros (from `soc/soc_caps.h`) describe what the current chip supports. They are the correct way to check for peripheral features — they stay accurate when new chips are added, unlike `CONFIG_IDF_TARGET_*` checks.
<!-- HUMAN_ONLY_START -->
### Important `SOC_*` macros used in WLED
| Macro | Type | Used in | Purpose |
|---|---|---|---|
| `SOC_I2S_NUM` | `int` | `audio_source.h` | Number of I2S peripherals (1 or 2) |
| `SOC_I2S_SUPPORTS_ADC` | `bool` | `usermods/audioreactive/audio_source.h` | I2S ADC sampling mode (ESP32 only) |
| `SOC_I2S_SUPPORTS_APLL` | `bool` | `usermods/audioreactive/audio_source.h` | Audio PLL for precise sample rates |
| `SOC_I2S_SUPPORTS_PDM_RX` | `bool` | `usermods/audioreactive/audio_source.h` | PDM microphone input |
| `SOC_ADC_MAX_BITWIDTH` | `int` | `util.cpp` | ADC resolution (12 or 13 bits). Renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH` in IDF v5 |
| `SOC_ADC_CHANNEL_NUM(unit)` | `int` | `pin_manager.cpp` | ADC channels per unit |
| `SOC_UART_NUM` | `int` | `dmx_input.cpp` | Number of UART peripherals |
| `SOC_DRAM_LOW` / `SOC_DRAM_HIGH` | `addr` | `util.cpp` | DRAM address boundaries for validation |
<!-- HUMAN_ONLY_END -->
### Key pitfall
`SOC_ADC_MAX_BITWIDTH` (ADC resolution 12 or 13 bits) was renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH` in IDF v5.
<!-- HUMAN_ONLY_START -->
### Less commonly used but valuable
| Macro | Purpose |
|---|---|
| `SOC_RMT_TX_CANDIDATES_PER_GROUP` | Number of RMT TX channels (varies 28 by chip) |
| `SOC_LEDC_CHANNEL_NUM` | Number of LEDC (PWM) channels |
| `SOC_GPIO_PIN_COUNT` | Total GPIO pin count |
| `SOC_DAC_SUPPORTED` | Whether the chip has a DAC (ESP32/S2 only) |
| `SOC_SPIRAM_SUPPORTED` | Whether PSRAM interface exists |
| `SOC_CPU_CORES_NUM` | Core count (1 or 2) — useful for task pinning decisions |
<!-- HUMAN_ONLY_END -->
### Best practices
```cpp
// Good: feature-based detection
#if SOC_I2S_SUPPORTS_PDM_RX
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
#else
#warning "PDM microphones not supported on this chip"
#endif
// Avoid: chip-name-based detection
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
// happens to be correct today, but breaks when a new chip adds PDM support
#endif
```
<!-- HUMAN_ONLY_START -->
### PSRAM capability macros
For PSRAM presence, mode, and DMA access patterns:
| Macro | Meaning |
|---|---|
| `CONFIG_SPIRAM` / `BOARD_HAS_PSRAM` | PSRAM is present in the build configuration |
| `CONFIG_SPIRAM_MODE_QUAD` | Quad-SPI PSRAM (standard, used on ESP32 classic and some S2/S3 boards) |
| `CONFIG_SPIRAM_MODE_OCT` | Octal-SPI PSRAM — 8 data lines, DTR mode. Used on ESP32-S3 with octal PSRAM (e.g. N8R8 / N16R8 modules). Reserves GPIO 3337 for the PSRAM bus — **do not allocate these pins** when this macro is defined. `wled.cpp` uses this to gate GPIO reservation. |
| `CONFIG_SPIRAM_MODE_HEX` | Hex-SPI (16-line) PSRAM — future interface on ESP32-P4 running at up to 200 MHz. Used in `json.cpp` to report the PSRAM mode. |
| `CONFIG_SOC_PSRAM_DMA_CAPABLE` | PSRAM buffers can be used with DMA (ESP32-S3 with octal PSRAM) |
| `CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT` | SPI flash and PSRAM on separate buses (no speed contention) |
<!-- HUMAN_ONLY_END -->
#### Detecting octal/hex flash
On ESP32-S3 modules with OPI flash (e.g. N8R8 modules where the SPI flash itself runs in Octal-PI mode), the build system sets:
| Macro | Meaning |
|---|---|
| `CONFIG_ESPTOOLPY_FLASHMODE_OPI` | Octal-PI flash mode. On S3, implies GPIO 3337 are used by the flash/PSRAM interface — the same GPIO block as octal PSRAM. `wled.cpp` uses `CONFIG_ESPTOOLPY_FLASHMODE_OPI \|\| (CONFIG_SPIRAM_MODE_OCT && BOARD_HAS_PSRAM)` to decide whether to reserve these GPIOs. `json.cpp` uses this to report the flash mode string as `"🚀OPI"`. |
| `CONFIG_ESPTOOLPY_FLASHMODE_HEX` | Hex flash mode (ESP32-P4). Reported as `"🚀🚀HEX"` in `json.cpp`. |
**Pattern used in WLED** (from `wled.cpp`) to reserve the octal-bus GPIOs on S3:
```cpp
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#if CONFIG_ESPTOOLPY_FLASHMODE_OPI || (CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM))
// S3: GPIO 33-37 are used by the octal PSRAM/flash bus
managed_pin_type pins[] = { {33, true}, {34, true}, {35, true}, {36, true}, {37, true} };
pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM);
#endif
#endif
```
---
## ESP-IDF Version Conditionals
<!-- HUMAN_ONLY_START -->
### Checking the IDF version
```cpp
#include <esp_idf_version.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// IDF v5+ code path
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
// IDF v4.4+ code path
#else
// Legacy IDF v3/v4.x path
#endif
```
### Key ESP-IDF version thresholds for WLED
| Version | What changed |
|---|---|
| **4.0.0** | Filesystem API (`SPIFFS`/`LittleFS`), GPIO driver overhaul |
| **4.2.0** | ADC/GPIO API updates; `esp_adc_cal` introduced |
| **4.4.0** | I2S driver refactored (legacy API remains); `adc_deprecated.h` headers appear for newer targets |
| **4.4.44.4.8** | Known I2S channel-swap regression on ESP32 (workaround in `audio_source.h`) |
| **5.0.0** | **Major breaking changes** — RMT, I2S, ADC, SPI flash APIs replaced (see migration section) |
| **5.1.0** | Matter protocol support; new `esp_flash` API stable |
| **5.3+** | arduino-esp32 v3.x compatibility; C6/P4 support |
<!-- HUMAN_ONLY_END -->
### Guidelines
- When adding a version guard, **always include a comment** explaining *what* changed and *why* the guard is needed.
- Avoid version ranges that silently break — prefer `>=` over exact version matches.
- Known regressions should use explicit range guards:
```cpp
// IDF 4.4.44.4.8 swapped I2S left/right channels (fixed in 4.4.9)
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 4)) && \
(ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 8))
#define I2S_CHANNELS_SWAPPED
#endif
```
---
## Migrating from ESP-IDF v4.4.x to v5.x
The jump from IDF v4.4 (arduino-esp32 v2.x) to IDF v5.x (arduino-esp32 v3.x) is the largest API break in ESP-IDF history. This section documents the critical changes and recommended migration patterns based on the upstream WLED `V5-C6` branch (`https://github.com/wled/WLED/tree/V5-C6`). Note: WLED has not yet migrated to IDF v5 — these patterns prepare for the future migration.
<!-- HUMAN_ONLY_START -->
### Compiler changes
IDF v5.x ships a much newer GCC toolchain. Key versions:
| ESP-IDF | GCC | C++ default | Notes |
|---|---|---|---|
| 4.4.x (current) | **8.4.0** | C++17 (gnu++17) | Xtensa + RISC-V |
| 5.15.3 | **13.2** | C++20 (gnu++2b) | Significant warning changes |
| 5.45.5 | **14.2** | C++23 (gnu++2b) | Latest; stricter diagnostics |
Notable behavioral differences:
| Change | Impact | Action |
|---|---|---|
| Stricter `-Werror=enum-conversion` | Implicit int-to-enum casts now error | Use explicit `static_cast<>` or typed enums |
| C++20/23 features available | `consteval`, `concepts`, `std::span`, `std::expected` | Use judiciously — ESP8266 builds still require GCC 10.x with C++17 |
| `-Wdeprecated-declarations` enforced | Deprecated API calls become warnings/errors | Migrate to new APIs (see below) |
| `-Wdangling-reference` (GCC 13+) | Warns when a reference binds to a temporary that will be destroyed | Fix the lifetime issue; do not suppress the warning |
| `-fno-common` default (GCC 12+) | Duplicate tentative definitions across translation units cause linker errors | Use `extern` declarations in headers, define in exactly one `.cpp` |
| RISC-V codegen improvements | C3/C6/P4 benefit from better register allocation | No action needed — automatic |
### C++ language features: GCC 8 → GCC 14
The jump from GCC 8.4 to GCC 14.2 spans six major compiler releases. This section lists features that become available and patterns that need updating.
#### Features safe to use after migration
These work in GCC 13+/14+ but **not** in GCC 8.4. Guard with `#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)` if the code must compile on both IDF v4 and v5.
| Feature | Standard | Example | Benefit |
|---|---|---|---|
| Designated initializers (C++20) | C++20 | `gpio_config_t cfg = { .mode = GPIO_MODE_OUTPUT };` | Already used as a GNU extension in GCC 8; becomes standard and portable in C++20 |
| `[[likely]]` / `[[unlikely]]` | C++20 | `if (err != ESP_OK) [[unlikely]] { ... }` | Hints for branch prediction; useful in hot paths |
| `[[nodiscard("reason")]]` | C++20 | `[[nodiscard("leak if ignored")]] void* allocBuffer();` | Enforces checking return values — helpful for `esp_err_t` wrappers |
| `std::span<T>` | C++20 | `void process(std::span<uint8_t> buf)` | Safe, non-owning view of contiguous memory — replaces raw pointer + length pairs |
| `consteval` | C++20 | `consteval uint32_t packColor(...)` | Guarantees compile-time evaluation; useful for color constants |
| `constinit` | C++20 | `constinit static int counter = 0;` | Prevents static initialization order fiasco |
| Concepts / `requires` | C++20 | `template<typename T> requires std::integral<T>` | Clearer constraints than SFINAE; improves error messages |
| Three-way comparison (`<=>`) | C++20 | `auto operator<=>(const Version&) const = default;` | Less boilerplate for comparable types |
| `std::bit_cast` | C++20 | `float f = std::bit_cast<float>(uint32_val);` | Type-safe reinterpretation — replaces `memcpy` or `union` tricks |
| `if consteval` | C++23 | `if consteval { /* compile-time */ } else { /* runtime */ }` | Cleaner than `std::is_constant_evaluated()` |
| `std::expected<T, E>` | C++23 | `std::expected<int, esp_err_t> readSensor()` | Monadic error handling — cleaner than returning error codes |
| `std::to_underlying` | C++23 | `auto val = std::to_underlying(myEnum);` | Replaces `static_cast<int>(myEnum)` |
#### Features already available in GCC 8 (C++17)
These work on both IDF v4.4 and v5.x — prefer them now:
| Feature | Example | Notes |
|---|---|---|
| `if constexpr` | `if constexpr (sizeof(T) == 4) { ... }` | Compile-time branching; already used in WLED |
| `std::optional<T>` | `std::optional<uint8_t> pin;` | Nullable value without sentinel values like `-1` |
| `std::string_view` | `void log(std::string_view msg)` | Non-owning, non-allocating string reference |
| Structured bindings | `auto [err, value] = readSensor();` | Useful with `std::pair` / `std::tuple` returns |
| Fold expressions | `(addSegment(args), ...);` | Variadic template expansion |
| Inline variables | `inline constexpr int MAX_PINS = 50;` | Avoids ODR issues with header-defined constants |
| `[[maybe_unused]]` | `[[maybe_unused]] int debug_only = 0;` | Suppresses unused-variable warnings cleanly |
| `[[fallthrough]]` | `case 1: doA(); [[fallthrough]]; case 2:` | Documents intentional switch fallthrough |
| Nested namespaces | `namespace wled::audio { }` | Shorter than nested `namespace` blocks |
#### Patterns that break or change behavior
| Pattern | GCC 8 behavior | GCC 14 behavior | Fix |
|---|---|---|---|
| `int x; enum E e = x;` | Warning (often ignored) | Error with `-Werror=enum-conversion` | `E e = static_cast<E>(x);` |
| `int g;` in two `.cpp` files | Both compile, linker merges (tentative definition) | Error: multiple definitions (`-fno-common`) | `extern int g;` in header, `int g;` in one `.cpp` |
| `const char* ref = std::string(...).c_str();` | Silent dangling pointer | Warning (`-Wdangling-reference`) | Extend lifetime: store the `std::string` in a local variable |
| `register int x;` | Accepted (ignored) | Warning or error (`register` removed in C++17) | Remove `register` keyword |
| Narrowing in aggregate init | Warning | Error | Use explicit cast or wider type |
| Implicit `this` capture in lambdas | Accepted in `[=]` | Deprecated warning; error in C++20 mode | Use `[=, this]` or `[&]` |
<!-- HUMAN_ONLY_END -->
#### Recommendations
- **Do not raise the minimum C++ standard yet.** WLED must still build on IDF v4.4 (GCC 8.4, C++17). Use `#if __cplusplus > 201703L` to gate C++20 features.
- **Mark intentional fallthrough** with `[[fallthrough]]` — GCC 14 warns on unmarked fallthrough by default.
<!-- HUMAN_ONLY_START -->
- **Prefer `std::optional` over sentinel values** (e.g., `-1` for "no pin") in new code — it works on both compilers.
- **Use `std::string_view`** for read-only string parameters instead of `const char*` or `const String&` — zero-copy and works on GCC 8+.
- **Avoid raw `union` type punning** — prefer `memcpy` (GCC 8) or `std::bit_cast` (GCC 13+) for strict-aliasing safety.
<!-- HUMAN_ONLY_END -->
### Deprecated and removed APIs
#### RMT (Remote Control Transceiver)
The legacy `rmt_*` functions are removed in IDF v5. Do not introduce new legacy RMT calls.
<!-- HUMAN_ONLY_START -->
The new API is channel-based:
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|---|---|---|
| `rmt_config()` + `rmt_driver_install()` | `rmt_new_tx_channel()` / `rmt_new_rx_channel()` | Channels are now objects |
| `rmt_write_items()` | `rmt_transmit()` with encoder | Requires `rmt_encoder_t` |
| `rmt_set_idle_level()` | Configure in channel config | Set at creation time |
| `rmt_item32_t` | `rmt_symbol_word_t` | Different struct layout |
<!-- HUMAN_ONLY_END -->
**WLED impact**: NeoPixelBus LED output and IR receiver both use legacy RMT. The upstream `V5-C6` branch adds `-D WLED_USE_SHARED_RMT` and disables IR until the library is ported.
#### I2S (Inter-IC Sound)
Legacy `i2s_driver_install()` + `i2s_read()` API is deprecated. When touching audio source code, wrap legacy I2S init and reading in `#if ESP_IDF_VERSION_MAJOR < 5` / `#else`.
<!-- HUMAN_ONLY_START -->
The new API uses channel handles:
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|---|---|---|
| `i2s_driver_install()` | `i2s_channel_init_std_mode()` | Separate STD/PDM/TDM modes |
| `i2s_set_pin()` | Pin config in `i2s_std_gpio_config_t` | Set at init time |
| `i2s_read()` | `i2s_channel_read()` | Uses channel handle |
| `i2s_set_clk()` | `i2s_channel_reconfig_std_clk()` | Reconfigure running channel |
| `i2s_config_t` | `i2s_std_config_t` | Separate config for each mode |
**Migration pattern** (from Espressif examples):
```cpp
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/i2s_std.h"
i2s_chan_handle_t rx_handle;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_new_channel(&chan_cfg, NULL, &rx_handle);
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(22050),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = { .din = GPIO_NUM_32, .mclk = I2S_GPIO_UNUSED, ... },
};
i2s_channel_init_std_mode(rx_handle, &std_cfg);
i2s_channel_enable(rx_handle);
#else
// Legacy i2s_driver_install() path
#endif
```
<!-- HUMAN_ONLY_END -->
**WLED impact**: The audioreactive usermod (`audio_source.h`) heavily uses legacy I2S. Migration requires rewriting the `I2SSource` class for channel-based API.
<!-- HUMAN_ONLY_START -->
#### ADC (Analog-to-Digital Converter)
Legacy `adc1_get_raw()` and `esp_adc_cal_*` are deprecated:
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|---|---|---|
| `adc1_config_width()` + `adc1_get_raw()` | `adc_oneshot_new_unit()` + `adc_oneshot_read()` | Object-based API |
| `esp_adc_cal_characterize()` | `adc_cali_create_scheme_*()` | Calibration is now scheme-based |
| `adc_continuous_*` (old) | `adc_continuous_*` (restructured) | Config struct changes |
#### SPI Flash
| IDF v4 (legacy) | IDF v5 (new) |
|---|---|
| `spi_flash_read()` | `esp_flash_read()` |
| `spi_flash_write()` | `esp_flash_write()` |
| `spi_flash_erase_range()` | `esp_flash_erase_region()` |
WLED already has a compatibility shim in `ota_update.cpp` that maps old names to new ones.
#### GPIO
| IDF v4 (legacy) | IDF v5 (recommended) |
|---|---|
| `gpio_pad_select_gpio()` | `esp_rom_gpio_pad_select_gpio()` (or use `gpio_config()`) |
| `gpio_set_direction()` + `gpio_set_pull_mode()` | `gpio_config()` with `gpio_config_t` struct |
### Features disabled in IDF v5 builds
The upstream `V5-C6` branch explicitly disables features with incompatible library dependencies:
```ini
# platformio.ini [esp32_idf_V5]
-D WLED_DISABLE_INFRARED # IR library uses legacy RMT
-D WLED_DISABLE_MQTT # AsyncMqttClient incompatible with IDF v5
-D ESP32_ARDUINO_NO_RGB_BUILTIN # Prevents RMT driver conflict with built-in LED
-D WLED_USE_SHARED_RMT # Use new shared RMT driver for NeoPixel output
```
<!-- HUMAN_ONLY_END -->
### Migration checklist for new code
1. **Never use a removed API without a version guard.** Always provide both old and new paths, or disable the feature on IDF v5.
2. **Test on both IDF v4.4 and v5.x builds** if the code must be backward-compatible.
3. **Prefer the newer API** when writing new code — wrap the old API in an `#else` block.
4. **Mark migration TODOs** with `// TODO(idf5):` so they are easy to find later.
---
## Memory Management: `heap_caps_*` Best Practices
ESP32 has multiple memory regions with different capabilities. Using the right allocator is critical for performance and stability.
<!-- HUMAN_ONLY_START -->
### Memory regions
| Region | Flag | Speed | DMA | Size | Use for |
|---|---|---|---|---|---|
| DRAM | `MALLOC_CAP_INTERNAL \| MALLOC_CAP_8BIT` | Fast | Yes (ESP32) | 200320 KB | Hot-path buffers, task stacks, small allocations |
| IRAM | `MALLOC_CAP_EXEC` | Fastest | No | 32128 KB | Code (automatic via `IRAM_ATTR`) |
| PSRAM (SPIRAM) | `MALLOC_CAP_SPIRAM \| MALLOC_CAP_8BIT` | Slower | Chip-dependent | 216 MB | Large buffers, JSON documents, image data |
| RTC RAM | `MALLOC_CAP_RTCRAM` | Moderate | No | 8 KB | Data surviving deep sleep; small persistent buffers |
<!-- HUMAN_ONLY_END -->
### WLED allocation wrappers
WLED provides convenience wrappers with automatic fallback. **Always prefer these over raw `heap_caps_*` calls**:
| Function | Allocation preference | Use case |
|---|---|---|
| `d_malloc(size)` | RTC → DRAM → PSRAM | General-purpose; prefers fast memory |
| `d_calloc(n, size)` | Same as `d_malloc`, zero-initialized | Arrays, structs |
| `p_malloc(size)` | PSRAM → DRAM | Large buffers; prefers abundant memory |
| `p_calloc(n, size)` | Same as `p_malloc`, zero-initialized | Large arrays |
| `d_malloc_only(size)` | RTC → DRAM (no PSRAM fallback) | DMA buffers, time-critical data |
### PSRAM guidelines
- **Check availability**: test availability with `psramFound() && ESP.getPsramSize() > 0` before assuming PSRAM is present. Never rely on `BOARD_HAS_PSRAM`only.
- **DMA compatibility**: on ESP32 (classic), PSRAM buffers are **not DMA-capable** — use `d_malloc_only()` to allocate DMA buffers in DRAM only. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.
- **JSON documents**: use the `PSRAMDynamicJsonDocument` allocator (defined in `wled.h`) to put large JSON documents in PSRAM:
```cpp
PSRAMDynamicJsonDocument doc(16384); // allocated in PSRAM if available
```
- **Fragmentation**: PSRAM allocations fragment less than DRAM because the region is larger. But avoid mixing small and large allocations in PSRAM — small allocations waste the MMU page granularity.
- **Heap validation**: use `d_measureHeap()` and `d_measureContiguousFreeHeap()` to monitor remaining DRAM. Allocations that would drop free DRAM below `MIN_HEAP_SIZE` should go to PSRAM instead.
- **Performance**: Keep hot-path data in DRAM. Prefer PSRAM for capacity-oriented buffers and monitor contiguous DRAM headroom.
<!-- HUMAN_ONLY_START -->
PSRAM access is up to 15× slower than DRAM on ESP32, 310× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel at 80 MHz (120 MHz is possible with CONFIG_SPIRAM_SPEED_120M, which requires enabling experimental ESP-IDF features). On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM. Keep hot-path data in DRAM regardless, but consider that ESP32 often crashes when the largest DRAM chunk gets below 10 KB.
<!-- HUMAN_ONLY_END -->
<!-- HUMAN_ONLY_START -->
### Pattern: preference-based allocation
When you need a buffer that works on boards with or without PSRAM:
```cpp
// Prefer PSRAM for large buffers, fall back to DRAM
uint8_t* buf = (uint8_t*)heap_caps_malloc_prefer(bufSize, 2,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, // first choice: PSRAM
MALLOC_CAP_DEFAULT); // fallback: any available
// Or simply:
uint8_t* buf = (uint8_t*)p_malloc(bufSize);
```
<!-- HUMAN_ONLY_END -->
---
## I2S Audio: Best Practices
The audioreactive usermod uses I2S for microphone input. Key patterns:
### Port selection
```cpp
constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0;
// I2S_NUM_1 has limitations: no MCLK routing, no ADC support, no PDM support
```
Always use `I2S_NUM_0` unless you have a specific reason and have verified support on all target chips.
### DMA buffer tuning
DMA buffer size controls latency vs. reliability:
| Scenario | `dma_buf_count` | `dma_buf_len` | Latency | Notes |
|---|---|---|---|---|
| With HUB75 matrix | 18 | 128 | ~100 ms | Higher count prevents I2S starvation during matrix DMA |
| Without PSRAM | 24 | 128 | ~140 ms | More buffers compensate for slower interrupt response |
| Default | 8 | 128 | ~46 ms | Acceptable for most setups |
### Interrupt priority
Choose interrupt priority based on coexistence with other drivers:
```cpp
#ifdef WLED_ENABLE_HUB75MATRIX
.intr_alloc_flags = ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1, // level 1 (lowest) to avoid starving HUB75
#else
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_LEVEL3, // accept level 2 or 3 (allocator picks available)
#endif
```
### APLL (Audio PLL) usage
The ESP32 has an audio PLL for precise sample rates. Rules:
- Enable APLL when an MCLK pin is provided and precision matters.
- **Disable APLL** when Ethernet or HUB75 is active — they also use the APLL.
- APLL is broken on ESP32 revision 0 silicon.
- Not all chips have APLL — gate with `SOC_I2S_SUPPORTS_APLL`.
```cpp
#if !defined(SOC_I2S_SUPPORTS_APLL)
_config.use_apll = false;
#elif defined(WLED_USE_ETHERNET) || defined(WLED_ENABLE_HUB75MATRIX)
_config.use_apll = false; // APLL conflict
#endif
```
### PDM microphone caveats
- 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.
- **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.
---
## HUB75 LED Matrix: Best Practices
WLED uses the `ESP32-HUB75-MatrixPanel-I2S-DMA` library for HUB75 matrix output.
### Chip-specific panel limits
```cpp
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)
maxChainLength = 6; // S3 + PSRAM: up to 6 panels (DMA-capable PSRAM)
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
maxChainLength = 2; // S2: limited DMA channels
#else
maxChainLength = 4; // Classic ESP32: default
#endif
```
### Color depth vs. pixel count
The driver dynamically reduces color depth for larger displays to stay within DMA buffer limits:
| Pixel count | Color depth | Bits per pixel |
|---|---|---|
| ≤ `MAX_PIXELS_10BIT` | 10-bit (30-bit color) | High quality (experimental) |
| ≤ `MAX_PIXELS_8BIT` | 8-bit (24-bit color) | Full quality |
| ≤ `MAX_PIXELS_6BIT` | 6-bit (18-bit color) | Slight banding |
| ≤ `MAX_PIXELS_4BIT` | 4-bit (12-bit color) | Visible banding |
| larger | 3-bit (9-bit color) | Minimal color range |
### Resource conflicts
- **APLL**: HUB75 I2S DMA uses the APLL. Disable APLL in the audio I2S driver when HUB75 is active.
- **I2S peripheral**: HUB75 uses `I2S_NUM_1` (or `I2S_NUM_0` on single-I2S chips). Audio must use the other port.
- **Pin count**: HUB75 requires 1314 GPIO pins. On ESP32-S2 this severely limits remaining GPIO.
- **Reboot required**: on ESP32-S3, changing HUB75 driver options requires a full reboot — the I2S DMA cannot be reconfigured at runtime.
---
<!-- HUMAN_ONLY_START -->
## GPIO Best Practices
### Prefer `gpio_config()` over individual calls
```cpp
// Preferred: single struct-based configuration
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << pin),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
// Avoid: multiple separate calls (more error-prone, deprecated in IDF v5)
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
gpio_set_pull_mode(pin, GPIO_FLOATING);
```
<!-- HUMAN_ONLY_END -->
### Pin manager integration
Always allocate pins through WLED's `pinManager` before using GPIO APIs:
```cpp
if (!pinManager.allocatePin(myPin, true, PinOwner::UM_MyUsermod)) {
return; // pin in use by another module
}
// Now safe to configure
```
---
## Timer Best Practices
### Microsecond timing
For high-resolution timing, prefer `esp_timer_get_time()` (microsecond resolution, 64-bit) over `millis()` or `micros()`.
<!-- HUMAN_ONLY_START -->
```cpp
#include <esp_timer.h>
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 -->
### Periodic timers
For periodic tasks with sub-millisecond precision, use `esp_timer`:
```cpp
esp_timer_handle_t timer;
esp_timer_create_args_t args = {
.callback = myCallback,
.arg = nullptr,
.dispatch_method = ESP_TIMER_TASK, // run in timer task (not ISR)
.name = "my_timer",
};
esp_timer_create(&args, &timer);
esp_timer_start_periodic(timer, 1000); // 1 ms period
```
<!-- HUMAN_ONLY_END -->
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
<!-- HUMAN_ONLY_START -->
### Version-aware ADC code
ADC is one of the most fragmented APIs across IDF versions:
```cpp
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// IDF v5: oneshot driver
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
adc_oneshot_unit_handle_t adc_handle;
adc_oneshot_unit_init_cfg_t unit_cfg = { .unit_id = ADC_UNIT_1 };
adc_oneshot_new_unit(&unit_cfg, &adc_handle);
#else
// IDF v4: legacy driver
#include "driver/adc.h"
#include "esp_adc_cal.h"
adc1_config_width(ADC_WIDTH_BIT_12);
int raw = adc1_get_raw(ADC1_CHANNEL_0);
#endif
```
<!-- HUMAN_ONLY_END -->
### Bit width portability
Not all chips have 12-bit ADC. `SOC_ADC_MAX_BITWIDTH` reports the maximum resolution (12 or 13 bits). Note that in IDF v5, this macro was renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH`. Write version-aware guards:
```cpp
// IDF v4: SOC_ADC_MAX_BITWIDTH IDF v5: CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
#if defined(CONFIG_SOC_ADC_RTC_MAX_BITWIDTH) // IDF v5+
#define MY_ADC_MAX_BITWIDTH CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
#elif defined(SOC_ADC_MAX_BITWIDTH) // IDF v4
#define MY_ADC_MAX_BITWIDTH SOC_ADC_MAX_BITWIDTH
#else
#define MY_ADC_MAX_BITWIDTH 12 // safe fallback
#endif
#if MY_ADC_MAX_BITWIDTH == 13
adc1_config_width(ADC_WIDTH_BIT_13); // ESP32-S2
#else
adc1_config_width(ADC_WIDTH_BIT_12); // ESP32, S3, C3, etc.
#endif
```
WLED's `util.cpp` uses the IDF v4 form (`SOC_ADC_MAX_BITWIDTH`) — this will need updating when the codebase migrates to IDF v5.
---
<!-- HUMAN_ONLY_START -->
## RMT Best Practices
### Current usage in WLED
RMT drives NeoPixel LED output (via NeoPixelBus) and IR receiver input. Both use the legacy API that is removed in IDF v5.
### Migration notes
- The upstream `V5-C6` branch uses `-D WLED_USE_SHARED_RMT` to switch to the new RMT driver for NeoPixel output.
- IR is disabled on IDF v5 until the IR library is ported.
- 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.
<!-- HUMAN_ONLY_END -->
---
## Espressif Best Practices (from official examples)
### Error handling
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
// Initialization — crash early on failure
ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &config, 0, nullptr));
// Runtime — log and recover
esp_err_t err = i2s_read(I2S_NUM_0, buf, len, &bytes_read, portMAX_DELAY);
if (err != ESP_OK) {
DEBUGSR_PRINTF("I2S read failed: %s\n", esp_err_to_name(err));
return;
}
```
<!-- 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
WLED uses its own logging macros — **not** `ESP_LOGx()`. For application-level code, always use the WLED macros defined in `wled.h`:
| Macro family | Defined in | Controlled by | Use for |
|---|---|---|---|
| `DEBUG_PRINT` / `DEBUG_PRINTLN` / `DEBUG_PRINTF` | `wled.h` | `WLED_DEBUG` build flag | Development/diagnostic output; compiled out in release builds |
All of these wrap `Serial` output through the `DEBUGOUT` / `DEBUGOUTLN` / `DEBUGOUTF` macros.
**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
static const char* TAG = "my_module";
ESP_LOGI(TAG, "Initialized with %d buffers", count);
ESP_LOGW(TAG, "PSRAM not available, falling back to DRAM");
ESP_LOGE(TAG, "Failed to allocate %u bytes", size);
```
<!-- HUMAN_ONLY_END -->
### Task creation and pinning
<!-- HUMAN_ONLY_START -->
On dual-core chips (ESP32, S3, P4), pin latency-sensitive tasks to a specific core:
```cpp
xTaskCreatePinnedToCore(
audioTask, // function
"audio", // name
4096, // stack size
nullptr, // parameter
5, // priority (higher = more important)
&audioTaskHandle, // handle
0 // core ID (0 = protocol core, 1 = app core)
);
```
<!-- HUMAN_ONLY_END -->
Guidelines:
- Pin network/protocol tasks to core 0 (where Wi-Fi runs).
- Pin real-time tasks (audio, LED output) to core 1.
- On single-core chips (S2, C3, C5, C6), only core 0 exists — pinning to core 1 will fail. Use `SOC_CPU_CORES_NUM > 1` guards or `tskNO_AFFINITY`.
- Use `SOC_CPU_CORES_NUM` to conditionally pin tasks:
```cpp
#if SOC_CPU_CORES_NUM > 1
xTaskCreatePinnedToCore(audioTask, "audio", 4096, nullptr, 5, &handle, 1);
#else
xTaskCreate(audioTask, "audio", 4096, nullptr, 5, &handle);
#endif
```
**Tip: use xTaskCreateUniversal()** - from arduino-esp32 - to avoid the conditional on `SOC_CPU_CORES_NUM`. This function has the same signature as ``xTaskCreatePinnedToCore()``, but automatically falls back to ``xTaskCreate()`` on single-core MCUs.
### `delay()`, `yield()`, and the IDLE task
FreeRTOS on ESP32 is **preemptive** — all tasks are scheduled by priority regardless of `yield()` calls. This is fundamentally different from ESP8266 cooperative multitasking.
<!-- HUMAN_ONLY_START -->
| Call | What it does | Reaches IDLE (priority 0)? |
|---|---|---|
| `delay(ms)` / `vTaskDelay(ticks)` | Suspends calling task; scheduler runs all other ready tasks | ✅ Yes |
| `yield()` / `vTaskDelay(0)` | Hint to switch to tasks at **equal or higher** priority only | ❌ No |
| `taskYIELD()` | Same as `vTaskDelay(0)` | ❌ No |
| Blocking API (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) | Suspends task until event or timeout; IDLE runs freely | ✅ Yes |
<!-- HUMAN_ONLY_END -->
**`delay()` in `loopTask` is safe.** Arduino's `loop()` runs inside `loopTask`. Calling `delay()` suspends only `loopTask` — all other FreeRTOS tasks (Wi-Fi stack, audio FFT, LED DMA) continue uninterrupted on either core.
**`yield()` does not yield to IDLE.** Any task that loops with only `yield()` calls will starve the IDLE task, causing the IDLE watchdog to fire. Always use `delay(1)` (or a blocking FreeRTOS call) in tight task loops. Note: WLED redefines `yield()` as an empty macro on ESP32 WLEDMM_FASTPATH builds.
#### Why the IDLE task is not optional
<!-- HUMAN_ONLY_START -->
The FreeRTOS IDLE task (one per core on dual-core ESP32 and ESP32-S3; single instance on single-core chips) is not idle in the casual sense — it performs essential system housekeeping:
- **Frees deleted task memory**: when a task calls `vTaskDelete()`, the IDLE task reclaims its TCB and stack. Without IDLE running, deleted tasks leak memory permanently.
- **Runs the idle hook**: when `configUSE_IDLE_HOOK = 1`, the IDLE task calls `vApplicationIdleHook()` on every iteration — some ESP-IDF components register low-priority background work here.
- **Implements tickless idle / light sleep**: on battery-powered devices, IDLE is the entry point for low-power sleep. A permanently starved IDLE task disables light sleep entirely.
- **Runs registered idle hooks**: ESP-IDF components register callbacks via `esp_register_freertos_idle_hook()` (e.g., Wi-Fi background maintenance, Bluetooth housekeeping). These only fire when IDLE runs.
<!-- HUMAN_ONLY_END -->
In short: **starving IDLE corrupts memory cleanup, breaks background activities, disables low-power sleep, and prevents Wi-Fi/BT maintenance.** The IDLE watchdog panic is a symptom — the real damage happens before the watchdog fires.
### Watchdog management
Long-running operations may trigger the task watchdog. Feed it explicitly:
```cpp
#include <esp_task_wdt.h>
esp_task_wdt_reset(); // feed the watchdog in long loops
```
For tasks that intentionally block for extended periods, consider subscribing/unsubscribing from the TWDT:
```cpp
esp_task_wdt_delete(NULL); // remove current task from TWDT (IDF v4.4)
// ... long blocking operation ...
esp_task_wdt_add(NULL); // re-register
```
> **IDF v5 note**: In IDF v5, `esp_task_wdt_add()` and `esp_task_wdt_delete()` require an explicit `TaskHandle_t`. Use `xTaskGetCurrentTaskHandle()` instead of `NULL`.
<!-- HUMAN_ONLY_START -->
---
## Quick Reference: IDF v4 → v5 API Mapping
| Component | IDF v4 Header | IDF v5 Header | Key Change |
|---|---|---|---|
| I2S | `driver/i2s.h` | `driver/i2s_std.h` | Channel-based API |
| ADC (oneshot) | `driver/adc.h` | `esp_adc/adc_oneshot.h` | Unit/channel handles |
| ADC (calibration) | `esp_adc_cal.h` | `esp_adc/adc_cali.h` | Scheme-based calibration |
| RMT | `driver/rmt.h` | `driver/rmt_tx.h` / `rmt_rx.h` | Encoder-based transmit |
| SPI Flash | `spi_flash.h` | `esp_flash.h` | `esp_flash_*` functions |
| GPIO | `driver/gpio.h` | `driver/gpio.h` | `gpio_pad_select_gpio()` removed |
| Timer | `driver/timer.h` | `driver/gptimer.h` | General-purpose timer handles |
| PCNT | `driver/pcnt.h` | `driver/pulse_cnt.h` | Handle-based API |
<!-- HUMAN_ONLY_END -->
-58
View File
@@ -1,58 +0,0 @@
---
applyTo: "**/*.{cpp,h,hpp,ino,js,htm,html,css,yml,yaml}"
description: "WLED strict-mode security review: low-noise checklist."
---
# WLED Security Review — Low Noise Mode
Use these code hardening rules for automated reviews with minimal false positives.
## WLED Constraints (apply to all rules)
- Assume firewall/DMZ/VPN deployment; focus on realistic LAN-local and supply-chain risks.
- Do **not** require TLS/HTTPS as a baseline control.
- Do **not** require authentication for standards-based UDP multicast/broadcast paths where authentication is not defined in the protocol specification.
> **Trust boundary model**: Apply input-validation rules **only at the first untrusted ingress point**
> (HTTP/JSON API body or query string, WebSocket payload, UDP datagram, TCP read, serial command, ESP-NOW raw messages).
> Values that have been validated and range-clamped at ingress are **trusted** for internal WLED
> processing. Do not flag subsequent uses or internal copies of already-sanitized data.
## CRITICAL Rules
1. **No unchecked buffer copies** (`memcpy`, `memmove`, `strcpy`) in firmware paths when source buffer or size comes from an untrusted origin; prefer bounded alternatives (`strncpy`, `strlcpy`); require length validation before copying.
2. **No user-controlled format strings** in `DEBUG_PRINTF*` and similar logging APIs.
3. **Validate all untrusted external input** (HTTP/JSON/UDP/serial) before index/length/pin usage.
4. **Auth required for state-changing control endpoints where feasible** (for example HTTP/JSON); do not flag protocol-defined unauthenticated UDP multicast/broadcast channels solely for missing auth.
5. **No fail-open on parse/allocation errors** for config/state updates.
6. **No DOM XSS sinks with untrusted data** (`innerHTML`, unsafe HTML insertion). Server-side generation of JavaScript property-assignment statements (as used in WLED's printSetForm* helpers) is exempt.
7. **No dynamic code execution** (`eval`, `new Function`, string timers).
8. **No hardcoded secrets/credentials/tokens/keys** in committed files.
9. **No sensitive data in logs** (passwords, tokens, Wi-Fi secrets, auth headers).
10. **No secret exposure in workflows/log output, or in LittleFS files other than `wsec.json`**.
11. **No unsafe third-party GitHub Action pinning** (`@main`/`@master` disallowed).
12. **No untrusted expression interpolation in workflow shell commands**.
## IMPORTANT Rules
13. Avoid potentially unbounded string/memory operations (`strcmp`, `strchr`, `strlen`, `sprintf`) in firmware paths; prefer bounded alternatives (`strnlen`, `strncmp`, `snprintf`).
14. Check integer overflow risks in size/index arithmetic, but consider that unsigned wrap-around on small types might be intentional.
15. Reject repeated heap allocation churn in hot render/effect loops.
16. Avoid repeated `String` growth in hot paths; prefer bounded/pre-allocated buffers.
17. Ensure UI validation is mirrored by firmware-side validation.
18. Require strict origin checks for `postMessage` listeners.
19. Disallow untrusted redirect/navigation targets.
20. Prevent verbose error responses that leak internals.
21. Review new dependencies for typosquatting and known vulnerability risk.
22. Keep workflow `permissions` least-privilege.
23. Verify new `WLED_ENABLE_*` / `WLED_DISABLE_*` names are valid known flags.
24. New privileged behavior must not be enabled by insecure defaults; first-use default-credential change required where applicable.
25. OTA paths (Update.begin(), Update.write()) must verify firmware integrity (checksum/hash); TLS not required.
26. Flag xTaskCreate/xTaskCreatePinnedToCore tasks with insufficient stack for String/JSON use; flag MDNS.begin() / ArduinoOTA.setHostname() with unsanitized hostnames.
27. Flag API/config serialization that exposes Wi-Fi/AP/MQTT password fields to unauthenticated clients.
28. Treat fetched and config-derived strings as untrusted when inserting into the DOM; explicit sanitization required for HTML contexts.
## Reviewer Output Format
- Include severity, exact file and line, and one concrete fix direction.
- Prioritize CRITICAL findings before IMPORTANT findings.
-227
View File
@@ -1,227 +0,0 @@
---
applyTo: "**/*.{cpp,h,hpp,ino,js,htm,html,css,yml,yaml}"
description: "WLED-focused security review guide based on OWASP Top 10 for embedded firmware and web UI."
---
# WLED Security Review Standards (Embedded + Web UI)
Use this guide for AI-assisted code reviews in:
- `wled00/`
- `usermods/`
- `.github/workflows/`
## WLED Constraints and Threat Model Assumptions
- Assume typical deployment behind a firewall/DMZ/VPN; prioritize LAN-local and supply-chain risks.
- Do **not** require TLS/HTTPS as a baseline control for findings in this repo.
- Do **not** require authentication for standards-based UDP multicast/broadcast protocols where auth is not part of the spec.
- Do not propose mitigations that break protocol compliance just to add authentication.
### Trust Boundary Model
**Untrusted data** enters WLED only at the following explicit ingress points:
- HTTP/JSON API request bodies and query parameters (e.g., `/json/*`, `/win`)
- WebSocket message payloads
- UDP datagrams (`parsePacket()` / `recvfrom()` and higher-level protocol wrappers)
- TCP socket reads
- Serial/UART input used as commands
- ESP-NOW raw messages input
**Validation and range-clamping applied at the ingress point renders data trusted** for all subsequent use within the WLED core.
**Do not flag:**
- Repeated bounds or range checks on a value that has already been validated and clamped at its ingress handler.
- Internal WLED core logic that operates on values confirmed safe by the ingress layer.
If it is unclear whether a value has been sanitized upstream (e.g., passed through multiple function calls without a clear annotation), prefer asking for clarification over raising a false-positive finding.
### Locally-Stored Configuration Files (Robustness, not a primary trust boundary)
Files read from LittleFS (`presets.json`, `cfg.json`, `ledmap.json`, `ir.json`, etc.) are written only via privileged access (`/edit`) and are considered trusted in the threat model.
However, parse them defensively (validate structure, clamp array sizes, handle missing keys gracefully) to avoid bootloops from filesystem corruption or accidental malformation.
## Severity
- **CRITICAL** — exploitable vulnerability; block merge.
- **IMPORTANT** — meaningful risk; fix before or with merge when practical.
- **SUGGESTION** — defense-in-depth; track for follow-up.
## Scope (WLED-relevant)
Prioritize:
- C++ memory safety and input validation
- Auth and access checks for state-changing HTTP/JSON APIs
- XSS and DOM safety in `wled00/data/*`
- Secrets handling (`wsec.json`) and secure logging
- Dependency and GitHub Actions supply-chain hygiene
- Fail-safe behavior on constrained devices
De-prioritize unless explicitly introduced by a PR:
- SQL/NoSQL checks, JWT/OAuth flows, GraphQL-specific checks, generic backend framework checks not used by WLED.
## Firmware Security (C++, OWASP A01/A04/A05/A10)
### FW1: Unsafe buffer operations
- **Severity**: CRITICAL
- Flag `strcpy`, `sprintf`, unchecked memory access (`memcpy`, `memmove`, `memcmp`, `strcmp`, `strlen`), unchecked pointer arithmetic.
- Require explicit bounds checks and length validation.
- Prefer bounded alternatives for string operations (`strnlen`, `strncmp`, `strncpy`, `strlcpy`, `snprintf`).
- Treat a finding against FW1 as **suggestion** only when the operation is provably bounded
and both the destination capacity and copied/compared length are known safe.
### FW2: Format-string injection
- **Severity**: CRITICAL
- Do not pass untrusted input as a format string to `DEBUG_PRINTF*` or similar APIs.
### FW3: Integer overflow in length and offset math
- **Severity**: IMPORTANT
- Review `count * size`, index math, narrowing casts before allocations or copies.
### FW4: Unvalidated external input
- **Severity**: CRITICAL
- At each **untrusted ingress point** (see Trust Boundary Model above), validate and clamp values from HTTP/JSON/UDP/serial before use as lengths, indices, IDs, or pin references.
- Do not flag repeated range checks on values that have already been validated at their ingress point.
- In UDP handlers (`parsePacket()`, `read()`, and any lower-level socket wrappers), validate `packetSize` before buffer writes and clamp protocol-specific universe/channel ranges to valid limits.
### FW5: Missing auth checks on state-changing endpoints (where auth is feasible)
- **Severity**: CRITICAL
- HTTP/JSON and other control paths that support auth must enforce configured auth policy.
- Do not flag the HTTP endpoint `/reset` as state-changing. This endpoint triggers a reboot, causing a short interruption without loss of user data.
- Do not flag standards-based UDP multicast/broadcast paths solely for lacking authentication when authentication is not defined in the protocol specification.
### FW6: Fail-open behavior after parse or allocation errors
- **Severity**: IMPORTANT
- On error, reject update and preserve safe previous state.
- Explicitly check parse status (`DeserializationError error = deserializeJson(...); if (error) return/reject;`) and avoid silently applying unsafe zero/default values to safety-relevant fields (for example LED count and pin assignment).
### FW7: Heap churn in hot paths
- **Severity**: IMPORTANT
- Avoid repeated dynamic allocation in render/effect loops; prefer pre-allocation and reuse.
- Flag allocation patterns in loop and ISR-adjacent paths that can trigger fragmentation or timing instability.
### FW8: Unsafe use of `String` in performance-critical paths
- **Severity**: IMPORTANT
- In hot paths, avoid repeated `String` growth; reserve or use fixed buffers.
- Flag repeated `String` concatenation inside loop-heavy or ISR-adjacent code.
### FW9: Unsafe feature flag names
- **Severity**: IMPORTANT
- Verify all new `WLED_ENABLE_*`/`WLED_DISABLE_*` names are valid known flags; typos silently alter build behavior.
### FW10: OTA integrity verification (without TLS requirement)
- **Severity**: IMPORTANT
- OTA update flows should validate firmware integrity using the checksum/hash/signature mechanism available in the firmware/platform implementation.
- Do not require TLS/certificate pinning as a mandatory review criterion.
- In OTA paths (`Update.begin()`, `Update.write()`, and related flows), flag flashing without integrity verification.
### FW11: FreeRTOS task stack and recursion safety
- **Severity**: IMPORTANT
- In `xTaskCreate`/`xTaskCreatePinnedToCore` tasks that process `String`/JSON-heavy data, verify stack-size sufficiency and avoid unbounded recursion.
### FW12: mDNS and hostname sanitization
- **Severity**: IMPORTANT
- For `MDNS.begin()`, `MDNS.addService()`, and `ArduinoOTA.setHostname()`, ensure user-provided hostnames are RFC-compliant (letters/digits/hyphen, no leading/trailing hyphen) and clamped to 63 characters.
### FW13: Outbound URL validation (no HTTPS requirement)
- **Severity**: SUGGESTION
- When using user-provided URL strings with `HTTPClient.begin()`/equivalent, validate scheme/format and constrain host targets (allowlist or equivalent policy).
- Do not require HTTPS/TLS as a baseline review rule.
### FW14: Optional unicast UDP source filtering
- **Severity**: SUGGESTION
- For unicast UDP receive paths, prefer optional user-configurable source filtering.
- Do not require this for multicast/broadcast protocol flows.
## Web UI Security (`wled00/data/*`, OWASP A01/A02/A05)
### WEB1: DOM XSS through `innerHTML`
- **Severity**: CRITICAL
- Prefer `textContent`; if HTML is required, sanitize trusted content path explicitly.
### WEB2: Dynamic code execution
- **Severity**: CRITICAL
- Reject `eval`, `new Function`, and string-based timer execution.
### WEB3: `postMessage` without origin validation
- **Severity**: IMPORTANT
- Require strict origin allowlist checks before processing payloads.
### WEB4: Unsafe redirects/navigation
- **Severity**: IMPORTANT
- Do not navigate directly from untrusted query/input without relative-path or allowlist checks.
### WEB5: Client-only validation
- **Severity**: IMPORTANT
- UI validation is not sufficient; equivalent firmware-side validation is required.
### WEB6: Direct DOM insertion from fetched/config data
- **Severity**: IMPORTANT
- Treat fetched and config-derived strings as untrusted unless proven otherwise.
### WEB7: CSRF checks for state-changing HTTP routes (advisory)
- **Severity**: SUGGESTION
- For state-changing HTTP routes (for example `/json/state`, `/win`), prefer `Origin`/`Referer` header validation as low-cost defense-in-depth for deployments that are not directly internet-exposed.
- Treat this as advisory only, since some legitimate clients may omit these headers.
## Secrets and Logging (OWASP A04/A09/A10)
### SEC1: Hardcoded secrets and credentials
- **Severity**: CRITICAL
- Reject committed API keys, passwords, tokens, private keys, or test backdoors with potential security impact.
### SEC2: Sensitive values in logs
- **Severity**: CRITICAL
- Do not log passwords, tokens, Wi-Fi keys, auth headers, or full sensitive payloads.
### SEC3: Insecure defaults
- **Severity**: IMPORTANT
- Reject new default credentials or insecure auto-enable behavior for privileged functions.
- For setup/onboarding flows, require first-change behavior for default credentials where applicable.
### SEC4: Overly detailed error responses
- **Severity**: IMPORTANT
- Avoid exposing stack traces or internal details to API/UI consumers.
### SEC5: Credential exposure in API/config responses
- **Severity**: IMPORTANT
- Flag API/config serialization that exposes password-like fields (for example Wi-Fi/AP/MQTT passwords) to unauthenticated or untrusted clients.
### SEC6: Security-relevant event logging coverage
- **Severity**: SUGGESTION
- Prefer explicit logging for auth failures, OTA attempts, config resets, and AP activation events, without logging secret values.
## Supply Chain and CI/CD (OWASP A03/A08)
### SC1: New dependency risk
- **Severity**: IMPORTANT
- Review new npm/pip/PlatformIO dependencies for legitimacy, pinning, and known vulnerabilities.
### SC2: Workflow permission hardening regressions
- **Severity**: IMPORTANT
- Check for broad `permissions`, unpinned third-party actions, or unsafe secret exposure.
- Flag mutable third-party action refs (`@main`, `@master`, broad tags) where SHA pinning is expected by project policy.
- Flag overly broad permissions such as `write-all` without clear need.
### SC3: Script injection in workflows
- **Severity**: IMPORTANT
- Avoid direct interpolation of untrusted `${{ github.event.* }}` values in `run` commands.
## Reviewer Checklist
- [ ] No new memory-safety hazards (bounds, overflow, unsafe copies/format strings)
- [ ] External input is validated and range-clamped at ingress points (HTTP/JSON, WebSocket, UDP, TCP, serial, ESP-NOW)
- [ ] State-changing API paths enforce auth policy
- [ ] OTA paths enforce integrity verification (without requiring TLS baseline)
- [ ] Suggested rule patterns are checked where relevant (UDP bounds, hostname sanitization, workflow pinning/permissions)
- [ ] Web UI changes avoid unsafe DOM execution/injection patterns
- [ ] No secrets added; no sensitive logging introduced
- [ ] Error handling remains fail-safe and non-leaky
- [ ] Dependency/workflow changes are supply-chain safe
- [ ] Feature-flag names are valid and not typoed
## AI Review Behavior
- Prefer concrete, file/line-specific findings over generic guidance.
- Prioritize **CRITICAL** and **IMPORTANT** findings.
- Skip irrelevant framework checks not used by WLED.
- If control-flow trust is unclear, ask for clarification instead of guessing.
-6
View File
@@ -23,12 +23,6 @@ applyTo: "wled00/data/**"
**Reuse shared helpers from `common.js` whenever possible** instead of duplicating utilities in page-local scripts.
## Accessibility & Interaction
The WLED web UI targets commonly used browser/platform combinations: desktop browsers on Mac and PC (primarily pointer-driven, touch rare),
and touch-only devices (phones, tablets). If possible, keep the UI accessible to users with disabilities.
Full keyboard operability is not a strict requirement - adding keyboard shortcuts should be a case-by-case decision.
## Build Integration
Files in this directory are processed by `tools/cdata.js` into generated headers
+12 -79
View File
@@ -2,85 +2,18 @@
# This is implemented as a pio post-script to ensure that we can
# place our linker script at the correct point in the command arguments.
Import("env")
import shutil
from pathlib import Path
# Linker script fragment injected into the rodata output section of whichever
# platform we're building for. Placed just before the end-of-rodata marker so
# that the dynarray entries land in flash rodata and are correctly sorted.
DYNARRAY_INJECTION = (
"\n /* dynarray: WLED dynamic module arrays */\n"
" . = ALIGN(0x10);\n"
" KEEP(*(SORT_BY_INIT_PRIORITY(.dynarray.*)))\n"
" "
)
def inject_before_marker(path, marker):
"""Patch a linker script file in-place, inserting DYNARRAY_INJECTION before marker."""
original = path.read_text()
marker_pos = original.find(marker)
if marker_pos < 0:
raise RuntimeError(
f"DYNARRAY injection marker not found in linker script: path={path}, marker={marker!r}"
)
patched = original[:marker_pos] + DYNARRAY_INJECTION + original[marker_pos:]
path.write_text(patched)
if env.get("PIOPLATFORM") == "espressif32":
# Find sections.ld on the linker search path (LIBPATH).
sections_ld_path = None
for ld_dir in env.get("LIBPATH", []):
candidate = Path(str(ld_dir)) / "sections.ld"
if candidate.exists():
sections_ld_path = candidate
break
if sections_ld_path is not None:
# Inject inside the existing .flash.rodata output section, just before
# _rodata_end. IDF v5 enforces zero gaps between adjacent output
# sections via ASSERT statements, so INSERT AFTER .flash.rodata would
# fail. Injecting inside the section creates no new output section and
# leaves the ASSERTs satisfied.
build_dir = Path(env.subst("$BUILD_DIR"))
patched_path = build_dir / "dynarray_sections.ld"
shutil.copy(sections_ld_path, patched_path)
inject_before_marker(patched_path, "_rodata_end = ABSOLUTE(.);")
# Replace "sections.ld" in LINKFLAGS with an absolute path to our
# patched copy. The flag may appear as a bare token, combined as
# "-Tsections.ld", or split across two tokens ("-T", "sections.ld").
patched_str = str(patched_path)
new_flags = []
skip_next = False
for flag in env.get("LINKFLAGS", []):
if skip_next:
new_flags.append(patched_str if flag == "sections.ld" else flag)
skip_next = False
elif flag == "-T":
new_flags.append(flag)
skip_next = True
else:
new_flags.append(flag.replace("sections.ld", patched_str))
env.Replace(LINKFLAGS=new_flags)
platform = env.get("PIOPLATFORM")
script_file = Path(f"tools/dynarray_{platform}.ld")
if script_file.is_file():
linker_script = f"-T{script_file}"
if platform == "espressif32":
# For ESP32, the script must be added at the right point in the list
linkflags = env.get("LINKFLAGS", [])
idx = linkflags.index("memory.ld")
linkflags.insert(idx+1, linker_script)
env.Replace(LINKFLAGS=linkflags)
else:
# Assume sections.ld will be built (ESP-IDF format); add a post-action to patch it
# TODO: consider using ESP-IDF linker fragment (https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/linker-script-generation.html)
# For now, patch after building
sections_ld = Path(env.subst("$BUILD_DIR")) / "sections.ld"
def patch_sections_ld(target, source, env):
inject_before_marker(sections_ld, "_rodata_end = ABSOLUTE(.);")
env.AddPostAction(str(sections_ld), patch_sections_ld)
elif env.get("PIOPLATFORM") == "espressif8266":
# The ESP8266 framework preprocesses eagle.app.v6.common.ld.h into
# local.eagle.app.v6.common.ld in $BUILD_DIR/ld/ at build time. Register
# a post-action on that generated file so the injection happens after
# C-preprocessing but before linking.
build_ld = Path(env.subst("$BUILD_DIR")) / "ld" / "local.eagle.app.v6.common.ld"
def patch_esp8266_ld(target, source, env):
inject_before_marker(build_ld, "_irom0_text_end = ABSOLUTE(.);")
env.AddPostAction(str(build_ld), patch_esp8266_ld)
# For other platforms, put it in last
env.Append(LINKFLAGS=[linker_script])
+54 -105
View File
@@ -1,12 +1,11 @@
import os
import re
import subprocess
from pathlib import Path
from pathlib import Path # For OS-agnostic path manipulation
from click import secho
from SCons.Script import Action, Exit
Import("env")
_ATTR = re.compile(r'\bDW_AT_(name|comp_dir)\b')
def read_lines(p: Path):
""" Read in the contents of a file for analysis """
@@ -14,156 +13,105 @@ def read_lines(p: Path):
return f.readlines()
def _get_readelf_path(env) -> str:
""" Derive the readelf tool path from the build environment """
# Derive from the C compiler: xtensa-esp32-elf-gcc → xtensa-esp32-elf-readelf
cc = Path(env.subst("$CC"))
return str(cc.with_name(re.sub(r'(gcc|g\+\+)$', 'readelf', cc.name)))
def _get_nm_path(env) -> str:
""" Derive the nm tool path from the build environment """
if "NM" in env:
return env.subst("$NM")
# Derive from the C compiler: xtensa-esp32-elf-gcc → xtensa-esp32-elf-nm
cc = env.subst("$CC")
nm = re.sub(r'(gcc|g\+\+)$', 'nm', os.path.basename(cc))
return os.path.join(os.path.dirname(cc), nm)
def check_elf_modules(elf_path: Path, env, module_lib_builders) -> set[str]:
""" Check which modules have at least one compilation unit in the ELF.
""" Check which modules have at least one defined symbol placed in the ELF.
The map file is not a reliable source for this: with LTO, original object
file paths are replaced by temporary ltrans.o partitions in all output
sections, making per-module attribution impossible from the map alone.
Instead we invoke readelf --debug-dump=info --dwarf-depth=1 on the ELF,
which reads only the top-level compilation-unit DIEs from .debug_info.
Each CU corresponds to one source file; matching DW_AT_comp_dir +
DW_AT_name against the module src_dirs is sufficient to confirm a module
was compiled into the ELF. The output volume is proportional to the
number of source files, not the number of symbols.
Instead we invoke nm --defined-only -l on the ELF, which uses DWARF debug
info to attribute each placed symbol to its original source file.
Requires usermod libraries to be compiled with -g so that DWARF sections
are present in the ELF. load_usermods.py injects -g for all WLED modules
via dep.env.AppendUnique(CCFLAGS=["-g"]).
Returns the set of build_dir basenames for confirmed modules.
"""
readelf_path = _get_readelf_path(env)
nm_path = _get_nm_path(env)
try:
result = subprocess.run(
[readelf_path, "--debug-dump=info", "--dwarf-depth=1", str(elf_path)],
[nm_path, "--defined-only", "-l", str(elf_path)],
capture_output=True, text=True, errors="ignore", timeout=120,
)
output = result.stdout
if result.returncode != 0 or result.stderr.strip():
secho(f"WARNING: readelf exited {result.returncode}: {result.stderr.strip()}", fg="yellow", err=True)
nm_output = result.stdout
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
secho(f"WARNING: readelf failed ({e}); skipping per-module validation", fg="yellow", err=True)
secho(f"WARNING: nm failed ({e}); skipping per-module validation", fg="yellow", err=True)
return {Path(b.build_dir).name for b in module_lib_builders} # conservative pass
# Match placed symbols against builders as we parse nm output, exiting early
# once all builders are accounted for.
# nm --defined-only still includes debugging symbols (type 'N') such as the
# per-CU markers GCC emits in .debug_info (e.g. "usermod_example_cpp_6734d48d").
# These live at address 0x00000000 in their debug section — not in any load
# segment — so filtering them out leaves only genuinely placed symbols.
# nm -l appends a tab-separated "file:lineno" location to each symbol line.
remaining = {Path(str(b.src_dir)): Path(b.build_dir).name for b in module_lib_builders}
found = set()
project_dir = Path(env.subst("$PROJECT_DIR"))
def _flush_cu(comp_dir: str | None, name: str | None) -> None:
"""Match one completed CU against remaining builders."""
if not name or not remaining:
return
p = Path(name)
src_path = (Path(comp_dir) / p) if (comp_dir and not p.is_absolute()) else p
# In arduino+espidf dual-framework builds the IDF toolchain sets DW_AT_comp_dir
# to the virtual path "/IDF_PROJECT" rather than the real project root, so
# src_path won't match. Pre-compute a fallback using $PROJECT_DIR and check
# both candidates in a single pass.
use_fallback = not p.is_absolute() and comp_dir and Path(comp_dir) != project_dir
src_path_real = project_dir / p if use_fallback else None
for src_dir in list(remaining):
if src_path.is_relative_to(src_dir) or (src_path_real and src_path_real.is_relative_to(src_dir)):
found.add(remaining.pop(src_dir))
return
# readelf emits one DW_TAG_compile_unit DIE per source file. Attributes
# of interest:
# DW_AT_name — source file (absolute, or relative to comp_dir)
# DW_AT_comp_dir — compile working directory
# Both appear as either a direct string or an indirect string:
# DW_AT_name : foo.cpp
# DW_AT_name : (indirect string, offset: 0x…): foo.cpp
# Taking the portion after the *last* ": " on the line handles both forms.
comp_dir = name = None
for line in output.splitlines():
if 'Compilation Unit @' in line:
_flush_cu(comp_dir, name)
comp_dir = name = None
continue
for line in nm_output.splitlines():
if not remaining:
break # all builders matched
m = _ATTR.search(line)
if m:
_, _, val = line.rpartition(': ')
val = val.strip()
if m.group(1) == 'name':
name = val
else:
comp_dir = val
_flush_cu(comp_dir, name) # flush the last CU
addr, _, _ = line.partition(' ')
if not addr.lstrip('0'):
continue # zero address — skip debug-section marker
if '\t' not in line:
continue
loc = line.rsplit('\t', 1)[1]
# Strip trailing :lineno (e.g. "/path/to/foo.cpp:42" → "/path/to/foo.cpp")
src_path = Path(loc.rsplit(':', 1)[0])
# Path.is_relative_to() handles OS-specific separators correctly without
# any regex, avoiding Windows path escaping issues.
for src_dir in list(remaining):
if src_path.is_relative_to(src_dir):
found.add(remaining.pop(src_dir))
break
return found
DYNARRAY_SECTION = ".dtors" if env.get("PIOPLATFORM") == "espressif8266" else ".dynarray"
USERMODS_SECTION = f"{DYNARRAY_SECTION}.usermods.1"
def count_usermod_objects(map_file: list[str]) -> int:
""" Returns the number of usermod objects in the usermod list.
Computes the count from the address span between the .dynarray.usermods.0
and .dynarray.usermods.99999 sentinel sections. This mirrors the
DYNARRAY_LENGTH macro and is reliable under LTO, where all entries are
merged into a single ltrans partition so counting section occurrences
always yields 1 regardless of the true count.
"""
ENTRY_SIZE = 4 # sizeof(Usermod*) on 32-bit targets
addr_begin = None
addr_end = None
for i, line in enumerate(map_file):
stripped = line.strip()
if stripped == '.dynarray.usermods.0':
if i + 1 < len(map_file):
m = re.search(r'0x([0-9a-fA-F]+)', map_file[i + 1])
if m:
addr_begin = int(m.group(1), 16)
elif stripped == '.dynarray.usermods.99999':
if i + 1 < len(map_file):
m = re.search(r'0x([0-9a-fA-F]+)', map_file[i + 1])
if m:
addr_end = int(m.group(1), 16)
if addr_begin is not None and addr_end is not None:
break
if addr_begin is None or addr_end is None:
return 0
return (addr_end - addr_begin) // ENTRY_SIZE
""" Returns the number of usermod objects in the usermod list """
# Count the number of entries in the usermods table section
return len([x for x in map_file if USERMODS_SECTION in x])
def validate_map_file(source, target, env):
""" Validate that all modules appear in the output build """
build_dir = Path(env.subst("$BUILD_DIR"))
map_file_path = build_dir / env.subst("${PROGNAME}.map")
map_file_path = build_dir / env.subst("${PROGNAME}.map")
if not map_file_path.exists():
secho(f"ERROR: Map file not found: {map_file_path}", fg="red", err=True)
Exit(1)
# Identify the WLED module builders, set by load_usermods.py
module_lib_builders = env.get('WLED_MODULES')
if module_lib_builders is None:
secho("ERROR: WLED_MODULES not set — ensure load_usermods.py is run as a pre: script", fg="red", err=True)
Exit(1)
module_lib_builders = env['WLED_MODULES']
# Extract the values we care about
modules = {Path(builder.build_dir).name: builder.name for builder in module_lib_builders}
secho(f"INFO: {len(modules)} libraries included as WLED optional/user modules")
secho(f"INFO: {len(modules)} libraries linked as WLED optional/user modules")
# Now parse the map file
map_file_contents = read_lines(map_file_path)
usermod_object_count = count_usermod_objects(map_file_contents)
secho(f"INFO: {usermod_object_count} usermod object entries found")
secho(f"INFO: {usermod_object_count} usermod object entries")
elf_path = build_dir / env.subst("${PROGNAME}.elf")
confirmed_modules = check_elf_modules(elf_path, env, module_lib_builders)
if confirmed_modules:
secho(f"INFO: Code from usermod libraries found in binary: {', '.join(confirmed_modules)}")
# else - if there's no usermods found, don't generate a message. If we're legitimately missing all entries, the error report on the
# next line will trip; and if the usermod set is expected to be empty, then there's no need for yet another null message.
missing_modules = [modname for mdir, modname in modules.items() if mdir not in confirmed_modules]
if missing_modules:
@@ -172,6 +120,7 @@ def validate_map_file(source, target, env):
fg="red",
err=True)
Exit(1)
return None
env.Append(LINKFLAGS=[env.subst("-Wl,--Map=${BUILD_DIR}/${PROGNAME}.map")])
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", Action(validate_map_file, cmdstr='Checking linked optional modules (usermods) in map file'))
+122 -228
View File
@@ -30,7 +30,6 @@ default_envs = nodemcuv2
esp32s3dev_16MB_opi
esp32s3dev_8MB_opi
esp32s3dev_8MB_qspi
esp32s3dev_8MB_none
esp32s3_4M_qspi
usermods
@@ -39,9 +38,42 @@ data_dir = ./wled00/data
build_cache_dir = ~/.buildcache
extra_configs =
platformio_override.ini
platformio_release.ini
[common]
# ------------------------------------------------------------------------------
# PLATFORM:
# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266
#
# arduino core 2.6.3 = platformIO 2.3.2
# arduino core 2.7.0 = platformIO 2.5.0
# ------------------------------------------------------------------------------
arduino_core_2_6_3 = espressif8266@2.3.3
arduino_core_2_7_4 = espressif8266@2.6.2
arduino_core_3_0_0 = espressif8266@3.0.0
arduino_core_3_0_2 = espressif8266@3.2.0
arduino_core_3_1_0 = espressif8266@4.1.0
arduino_core_3_1_2 = espressif8266@4.2.1
# Development platforms
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
# Platform to use for ESP8266
platform_wled_default = ${common.arduino_core_3_1_2}
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
#platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502
platformio/tool-esptool #@ ~1.413.0
platformio/tool-esptoolpy #@ ~1.30000.0
## previous platform for 8266, in case of problems with the new one
## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_0_2, which does not support Ucs890x
;; platform_wled_default = ${common.arduino_core_3_0_2}
;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
;; platformio/toolchain-xtensa @ ~2.40802.200502
;; platformio/tool-esptool @ ~1.413.0
;; platformio/tool-esptoolpy @ ~1.30000.0
# ------------------------------------------------------------------------------
# FLAGS: DEBUG
# esp8266 : see https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
@@ -160,43 +192,7 @@ lib_deps =
extra_scripts = ${scripts_defaults.extra_scripts}
[esp8266]
# ------------------------------------------------------------------------------
# PLATFORM:
# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266
#
# arduino core 2.6.3 = platformIO 2.3.2
# arduino core 2.7.0 = platformIO 2.5.0
# ------------------------------------------------------------------------------
arduino_core_2_6_3 = espressif8266@2.3.3
arduino_core_2_7_4 = espressif8266@2.6.2
arduino_core_3_0_0 = espressif8266@3.0.0
arduino_core_3_0_2 = espressif8266@3.2.0
arduino_core_3_1_0 = espressif8266@4.1.0
arduino_core_3_1_2 = espressif8266@4.2.1
# Development platforms
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
# Platform to use for ESP8266
platform_wled_default = ${esp8266.arduino_core_3_1_2}
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
#platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502
platformio/tool-esptool #@ ~1.413.0
platformio/tool-esptoolpy #@ ~1.30000.0
## previous platform for 8266, in case of problems with the new one
## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_0_2, which does not support Ucs890x
;; platform_wled_default = ${esp8266.arduino_core_3_0_2}
;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
;; platformio/toolchain-xtensa @ ~2.40802.200502
;; platformio/tool-esptool @ ~1.413.0
;; platformio/tool-esptoolpy @ ~1.30000.0
platform = ${esp8266.platform_wled_default}
build_unflags = ${common.build_unflags}
custom_usermods =
build_flags =
-DESP8266
-DFP_IN_IROM
@@ -225,11 +221,8 @@ lib_deps =
ESPAsyncTCP @ 1.2.2
ESPAsyncUDP
ESP8266PWM
https://github.com/tignioj/ArduinoUZlib.git#20aff95cd80c141f80bdbf66895409a0046d2c2f
${env.lib_deps}
monitor_filters = esp8266_exception_decoder
;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48
build_flags_compat =
-DESP8266
@@ -258,7 +251,6 @@ lib_deps_compat =
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.9
https://github.com/blazoncek/QuickESPNow.git#optional-debug
https://github.com/tignioj/ArduinoUZlib.git#20aff95cd80c141f80bdbf66895409a0046d2c2f
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
[esp32_all_variants]
@@ -277,7 +269,6 @@ platform_packages =
build_unflags = ${common.build_unflags}
build_flags = ${esp32_idf_V4.build_flags}
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
@@ -299,18 +290,8 @@ AR_lib_deps = ;; for pre-usermod-library platformio_override compatibility
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
;; tasmota platform (default)
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ;; Tasmota Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8
platform_packages =
;; espressif platform (optional - needs 300KB extra flash)
;;; arduino-esp32 2.0.17 + esp-idf 4.4.7
;; platform = espressif32@ ~6.13.0
;; platform_packages =
;;; arduino-esp32 2.0.14 + esp-idf 4.4.6
;; platform = espressif32@ ~6.6.0
;; platform_packages = platformio/framework-arduinoespressif32 @ 3.20014.231204
build_unflags = ${common.build_unflags}
build_flags = -g
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
@@ -340,7 +321,6 @@ build_flags = -g
lib_deps =
${esp32_idf_V4.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
monitor_filters = esp32_exception_decoder
[esp32c3]
;; generic definitions for all ESP32-C3 boards
@@ -360,7 +340,6 @@ lib_deps =
${esp32_idf_V4.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[esp32s3]
;; generic definitions for all ESP32-S3 boards
@@ -380,8 +359,6 @@ build_flags = -g
lib_deps =
${esp32_idf_V4.lib_deps}
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
upload_speed = 921600
monitor_filters = esp32_exception_decoder
# ------------------------------------------------------------------------------
@@ -389,12 +366,15 @@ monitor_filters = esp32_exception_decoder
# ------------------------------------------------------------------------------
[env:nodemcuv2]
extends = esp8266
board = nodemcuv2
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
[env:nodemcuv2_compat]
extends = env:nodemcuv2
@@ -404,7 +384,6 @@ platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9
custom_usermods = audioreactive
[env:nodemcuv2_160]
extends = env:nodemcuv2
@@ -414,13 +393,15 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
custom_usermods = audioreactive
[env:esp8266_2m]
extends = esp8266
board = esp_wroom_02
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\"
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PARTICLESYSTEM1D
custom_usermods = audioreactive
lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m_compat]
extends = env:esp8266_2m
@@ -430,7 +411,6 @@ platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
[env:esp8266_2m_160]
extends = env:esp8266_2m
@@ -440,37 +420,18 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
[env:esp8266_2m_min]
;; Minimal-feature build for ESP02 (2MB flash).
;; Use this to recover from a failed OTA: flash via serial, then OTA-upload the regular esp8266_2m binary.
;; OTA is intentionally kept enabled. All other optional features are stripped to minimise binary size.
extends = esp8266
board = esp_wroom_02
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_min\"
-D WLED_DISABLE_ALEXA
-D WLED_DISABLE_HUESYNC
-D WLED_DISABLE_INFRARED
-D WLED_DISABLE_MQTT
-D WLED_DISABLE_ADALIGHT
-D WLED_DISABLE_LOXONE
-D WLED_DISABLE_WEBSOCKETS
-D WLED_DISABLE_ESPNOW
-D WLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PIXELFORGE
-D WLED_DISABLE_IMPROV_WIFISCAN
[env:esp01_1m_full]
extends = esp8266
board = esp01_1m
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PIXELFORGE
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full_compat]
extends = env:esp01_1m_full
@@ -493,11 +454,15 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
custom_usermods = audioreactive
[env:esp32dev]
extends = esp32
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
custom_usermods = audioreactive
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
@@ -509,33 +474,50 @@ build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_DEBUG\"
[env:esp32dev_8M]
extends = env:esp32dev
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.large_partitions}
board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608
; board_build.f_flash = 80000000L
board_build.flash_mode = dio
[env:esp32dev_16M]
extends = env:esp32dev
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.extreme_partitions}
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.f_flash = 80000000L
board_build.flash_mode = dio
[env:esp32_eth]
extends = esp32
board = esp32-poe
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
-D SR_DMTYPE=-1 -D AUDIOPIN=-1 -D I2S_SDPIN=-1 -D I2S_WSPIN=-1 -D I2S_CKPIN=-1 -D MCLK_PIN=-1 ;; force AR to not allocate any PINs at startup
-D DATA_PINS=4 ;; default led pin = 16 conflicts with pins used for ethernet
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only => uncomment if your board uses ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
@@ -546,14 +528,20 @@ board_build.f_flash = 80000000L
board_build.flash_mode = qio
board_build.partitions = ${esp32.extended_partitions}
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\"
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
-DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html
-D DATA_PINS=25
lib_deps = ${esp32_idf_V4.lib_deps}
[env:esp32c3dev]
extends = esp32c3
platform = ${esp32c3.platform}
platform_packages = ${esp32c3.platform_packages}
framework = arduino
board = esp32-c3-devkitm-1
board_build.partitions = ${esp32.default_partitions}
custom_usermods = audioreactive
build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\"
-D WLED_WATCHDOG_TIMEOUT=0
@@ -561,6 +549,8 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=
-DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB
;-DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip
upload_speed = 460800
build_unflags = ${common.build_unflags}
lib_deps = ${esp32c3.lib_deps}
board_build.flash_mode = dio ; safe default, required for OTA updates to 0.16 from older version which used dio (must match the bootloader!)
[env:esp32c3dev_qio]
@@ -570,34 +560,45 @@ board_build.flash_mode = qio ; qio is faster and works on almost all boards (som
[env:esp32s3dev_16MB_opi]
;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
extends = esp32s3
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\"
-D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.extreme_partitions}
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32s3dev_8MB_opi]
;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
extends = esp32s3
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\"
-D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.large_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32s3dev_8MB_qspi]
;; generic ESP32-S3 board with 8MB FLASH and PSRAM (memory_type: qio_qspi). Try this one if esp32s3dev_8MB_opi does not work on your board
@@ -610,27 +611,18 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
;; -DLOLIN_WIFI_FIX ;; uncomment if you have WiFi connectivity problems
[env:esp32s3dev_8MB_none]
;; ESP32-S3 development board, 8MB FLASH, no PSRAM
extends = esp32s3
board = esp32-s3-devkitc-1 ;; generic dev board
custom_usermods = audioreactive
build_unflags = ${esp32s3.build_unflags} -DBOARD_HAS_PSRAM ;; make sure PSRAM support is removed
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_none\"
-D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32S3_wroom2]
;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1
;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi)
extends = esp32s3
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
board = esp32s3camlcd ;; this is the only standard board with "opi_opi"
board_build.arduino.memory_type = opi_opi
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\"
-D WLED_WATCHDOG_TIMEOUT=0
-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
@@ -640,9 +632,12 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
-D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1
;;-D WLED_DEBUG
-D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.extreme_partitions}
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
monitor_filters = esp32_exception_decoder
[env:esp32S3_wroom2_32MB]
;; For ESP32-S3 WROOM-2 with 32MB Flash, and >= 8MB PSRAM (memory_type: opi_opi)
@@ -659,42 +654,36 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
board_build.partitions = tools/WLED_ESP32_32MB.csv
board_upload.flash_size = 32MB
board_upload.maximum_size = 33554432
monitor_filters = esp32_exception_decoder
[env:esp32s3_4M_qspi]
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
extends = esp32s3
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\"
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
[env:esp32s3_4M_none]
;; ESP32-S3 with 4MB FLASH, no PSRAM
extends = esp32s3
board = esp32-s3-devkitc-1
custom_usermods = audioreactive
build_unflags = ${esp32s3.build_unflags} -D BOARD_HAS_PSRAM ;; make sure PSRAM support is removed
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_none\"
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:lolin_s2_mini]
extends = esp32s2
platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages}
board = lolin_s2_mini
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = qio
board_build.f_flash = 80000000L
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\"
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MSC_ON_BOOT=0
@@ -709,113 +698,18 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=
-D HW_PIN_DATASPI=11
-D HW_PIN_MISOSPI=9
; -D STATUSLED=15
lib_deps = ${esp32s2.lib_deps}
[env:usermods]
extends = env:esp32dev
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_USERMODS\"
-DTOUCH_CS=9
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dio
custom_usermods = * ; Expands to all usermods in usermods folder
board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat
# ------------------------------------------------------------------------------
# Hub75 examples
# ------------------------------------------------------------------------------
# Note: some panels may experience ghosting with default full brightness. use -D WLED_HUB75_MAX_BRIGHTNESS=239 or lower to fix it.
[hub75]
;; Shared values for all HUB75 build envs.
;; Core HUB75 flags - common to every HUB75 build
build_flags =
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D NO_CIE1931 ;; disable driver-internal gamma correction
-D WLED_DEBUG_BUS
-D LED_TYPES=TYPE_HUB75MATRIX_HS
; -D WLED_DEBUG
;; Default I2S mic pins disabled (HUB75 uses GPIOs that would otherwise clash).
;; Envs that wire up a real mic should NOT include this and define I2S_*PIN themselves.
i2s_disable_flags = -D SR_DMTYPE=1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
;; Pinned HUB75 driver libraries
lib_deps = https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#f17fb7fe9d487e9643f919eb5aeedea8d9d1f8d7 ;; 3.0.14
;; Extra flags shared by all S3-based HUB75 builds
s3_build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this (sets lower TX power)
-D WLED_WATCHDOG_TIMEOUT=0
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
[env:esp32dev_hub75]
extends = env:esp32dev
upload_speed = 921600
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} ${hub75.build_flags} ${hub75.i2s_disable_flags}
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
-D WLED_RELEASE_NAME=\"ESP32_HUB75\"
lib_deps = ${esp32_idf_V4.lib_deps}
${hub75.lib_deps}
[env:esp32dev_hub75_forum_pinout]
extends = env:esp32dev_hub75
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} ${hub75.build_flags} ${hub75.i2s_disable_flags}
-DARDUINO_USB_CDC_ON_BOOT=0
-D WLED_RELEASE_NAME=\"ESP32_HUB75_forum_pinout\"
-D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins
[env:esp32s3dev_4MB_qspi_hub75]
; HD-WF2 - NOTE: this board has NO PSRAM, so BOARD_HAS_PSRAM must not be set
; (BOARD_HAS_PSRAM causes the DMA library to allocate only in SPIRAM, which fails without PSRAM)
extends = env:esp32s3dev_8MB_qspi
board_build.partitions = ${esp32.extended_partitions} ;; 1.65MB firmware, 700KB filesystem - only 4MB flash usable on this board
build_unflags = ${esp32s3.build_unflags} -DBOARD_HAS_PSRAM
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags} ${hub75.i2s_disable_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_HD-WF2\"
-D HD_WF2_PINOUT ;; Huidu HD-WF2 specific GPIO wiring
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
[env:adafruit_matrixportal_esp32s3]
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
extends = env:esp32s3dev_8MB_qspi
board = adafruit_matrixportal_esp32s3_wled ; modified board definition: removed flash section that causes FS erase on upload
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags} ${hub75.i2s_disable_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_Adafruit_Matrixportal\"
-D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
;; board_build.partitions = tools/partitions-8MB_spiffs-tinyuf2.csv ;; supports adafruit UF2 bootloader
[env:waveshare_esp32s3_32MB_hub75]
;; Waveshare ESP32-S3-RGB-Matrix (memory_type: opi_opi); see https://docs.waveshare.com/ESP32-S3-RGB-Matrix
extends = env:esp32S3_wroom2_32MB
monitor_filters = esp32_exception_decoder
build_unflags = ${env:esp32S3_wroom2_32MB.build_unflags}
-D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2_32MB\" ;; need to un-set the relese name before setting a new one
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags} ${hub75.i2s_disable_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_Waveshare_HUB75\"
-D WAVESHARE_S3_PINOUT
; -D WLED_USE_SD_SPI
; -D SD_PRINT_HOME_DIR
; -D WLED_DEBUG
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
[env:esp32s3dev_16MB_opi_hub75]
;; MOONHUB HUB75 adapter board (lilygo T7-S3 with 16MB flash and octal PSRAM)
extends = env:esp32s3dev_8MB_opi
board = lilygo-t7-s3
board_build.partitions = ${esp32.extreme_partitions} ;; for 16MB flash (overrides large_partitions for 8MB)
;; Note: real I2S mic pins are wired here, so we do NOT include ${hub75.i2s_disable_flags}.
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi_HUB75\"
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
-D LEDPIN=14 -D BTNPIN=0 -D RLYPIN=15 -D IRPIN=-1 -D AUDIOPIN=-1 ;; defaults that avoid pin conflicts with HUB75
-D SR_DMTYPE=1 -D I2S_SDPIN=10 -D I2S_CKPIN=11 -D I2S_WSPIN=12 -D MCLK_PIN=-1 ;; I2S mic
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
+220 -110
View File
@@ -5,7 +5,7 @@
# Please visit documentation: https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = WLED_generic8266_1M, esp32dev_dio80 # put the name(s) of your own build environment here. You can define as many as you need
default_envs = WLED_generic8266_1M, esp32dev_V4_dio80 # put the name(s) of your own build environment here. You can define as many as you need
#----------
# SAMPLE
@@ -13,8 +13,8 @@ default_envs = WLED_generic8266_1M, esp32dev_dio80 # put the name(s) of your ow
[env:WLED_generic8266_1M]
extends = env:esp01_1m_full # when you want to extend the existing environment (define only updated options)
; board = esp01_1m # uncomment when ou need different board
; platform = ${esp8266.platform_wled_default} # uncomment and change when you want particular platform
; platform_packages = ${esp8266.platform_packages}
; platform = ${common.platform_wled_default} # uncomment and change when you want particular platform
; platform_packages = ${common.platform_packages}
; board_build.ldscript = ${common.ldscript_1m128k}
; upload_speed = 921600 # fast upload speed (remove ';' if your board supports fast upload speed)
# Sample libraries used for various usermods. Uncomment when using particular usermod.
@@ -30,18 +30,10 @@ lib_deps = ${esp8266.lib_deps}
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_generic_1M\"
-D WLED_DISABLE_PARTICLESYSTEM1D -D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PIXELFORGE
-D WLED_DISABLE_OTA -D WLED_DISABLE_2D
build_flags = ${common.build_flags} ${esp8266.build_flags}
;
; *** To use the below defines/overrides, copy and paste each onto its own line just below build_flags in the section above.
; *** Note: on adding custom usermods
; custom_usermods entries are a separate key in the env section (not inside build_flags). Place them on their own line in the env.
; Multiple usermods are separated by a space, the names can be found in the library.json file of the usermod.
; Example: use the default usermods from the esp32dev env and add the Temperature and four_line_display_ALT usermods
; custom_usermods = ${env:esp32dev.custom_usermods} Temperature four_line_display_ALT
;
;
; Set a release name that may be used to distinguish required binary for flashing
; -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
;
@@ -106,17 +98,17 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
; -D WLED_DEBUG_PORT=7868
;
; Use Autosave usermod and set it to do save after 90s
; custom_usermods = ${env:esp32dev.custom_usermods} auto_save
; -D USERMOD_AUTO_SAVE
; -D AUTOSAVE_AFTER_SEC=90
;
; Use AHT10/AHT15/AHT20 usermod
; custom_usermods = ${env:esp32dev.custom_usermods} AHT10_v2
; -D USERMOD_AHT10
;
; Use INA226 usermod
; custom_usermods = ${env:esp32dev.custom_usermods} INA226_v2
; -D USERMOD_INA226
;
; Use 4 Line Display usermod with SPI display
; custom_usermods = ${env:esp32dev.custom_usermods} four_line_display_ALT
; -D USERMOD_FOUR_LINE_DISPLAY
; -DFLD_SPI_DEFAULT
; -D FLD_TYPE=SSD1306_SPI64
; -D FLD_PIN_CLOCKSPI=14
@@ -126,22 +118,22 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
; -D FLD_PIN_RESET=27
;
; Use Rotary encoder usermod (in conjunction with 4LD)
; custom_usermods = ${env:esp32dev.custom_usermods} rotary_encoder_ui_ALT
; -D USERMOD_ROTARY_ENCODER_UI
; -D ENCODER_DT_PIN=5
; -D ENCODER_CLK_PIN=18
; -D ENCODER_SW_PIN=19
;
; Use Dallas DS18B20 temperature sensor usermod and configure it to use GPIO13
; custom_usermods = ${env:esp32dev.custom_usermods} Temperature
; -D USERMOD_DALLASTEMPERATURE
; -D TEMPERATURE_PIN=13
;
; Use Multi Relay usermod and configure it to use 6 relays and appropriate GPIO
; custom_usermods = ${env:esp32dev.custom_usermods} multi_relay
; -D USERMOD_MULTI_RELAY
; -D MULTI_RELAY_MAX_RELAYS=6
; -D MULTI_RELAY_PINS=12,23,22,21,24,25
;
; Use PIR sensor usermod and configure it to use GPIO4 and timer of 60s
; custom_usermods = ${env:esp32dev.custom_usermods} PIR_sensor_switch
; -D USERMOD_PIRSWITCH
; -D PIR_SENSOR_PIN=4 # use -1 to disable usermod
; -D PIR_SENSOR_OFF_SEC=60
; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering)
@@ -154,12 +146,12 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
; -D I2S_CKPIN=19
;
; Use PWM fan usermod
; custom_usermods = ${env:esp32dev.custom_usermods} PWM_fan
; -D USERMOD_PWM_FAN
; -D TACHO_PIN=33
; -D PWM_PIN=32
;
; Use POV Display usermod
; custom_usermods = ${env:esp32dev.custom_usermods} pov_display
; -D USERMOD_POV_DISPLAY
; Use built-in or custom LED as a status indicator (assumes LED is connected to GPIO16)
; -D STATUSLED=16
;
@@ -192,7 +184,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
; configure I2C and SPI interface (for various hardware)
; -D I2CSDAPIN=33 # initialise interface
; -D I2CSCLPIN=35 # initialise interface
; # HW_PIN_* informs the WebUI about default pins - don't initialise interface
; -D HW_PIN_SCL=35
; -D HW_PIN_SDA=33
; -D HW_PIN_CLOCKSPI=7
@@ -202,8 +193,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
# ------------------------------------------------------------------------------
# Optional: build flags for speed, instead of optimising for size.
# Add ${Speed_Flags.build_flags} / ${Speed_Flags.build_unflags} to your own env
# in platformio_override.ini to opt in.
# Example of usage: see [env:esp32S3_PSRAM_HUB75]
# ------------------------------------------------------------------------------
[Speed_Flags]
@@ -224,60 +214,63 @@ build_flags =
[env:esp07]
board = esp07
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_ESP07\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:d1_mini]
board = d1_mini
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
upload_speed = 921600
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_D1MINI\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags}
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
[env:heltec_wifi_kit_8]
board = d1_mini
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_HT_D1MINI\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:h803wf]
board = d1_mini
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=1 -D WLED_DISABLE_INFRARED -D WLED_RELEASE_NAME=\"ESP8266_HT803WF\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=1 -D WLED_DISABLE_INFRARED
lib_deps = ${esp8266.lib_deps}
[env:esp32dev_qio80]
extends = env:esp32dev # we want to extend the existing esp32dev environment (and define only updated options)
board = esp32dev
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_qio80\" #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32_idf_V4.lib_deps}
build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.f_flash = 80000000L
board_build.flash_mode = qio
[env:esp32dev_dio80]
extends = env:esp32dev_qio80 # we want to extend the previous environment, to change flash speed
build_unflags = ${env:esp32dev_qio80.build_unflags} -D WLED_RELEASE_NAME=\"ESP32_qio80\" # need to remove the previous WLED_RELEASE_NAME
build_flags = ${env:esp32dev_qio80.build_flags} -D WLED_RELEASE_NAME=\"ESP32_dio80\" # ... and then we can set a new one
board_build.flash_mode = dio # change flash mode to "dio", for boards that cannot not start with "qio" mode
[env:esp32dev_V4_dio80]
;; experimental ESP32 env using ESP-IDF V4.4.x
;; Warning: this build environment is not stable!!
;; please erase your device before installing.
extends = esp32_idf_V4 # based on newer "esp-idf V4" platform environment
board = esp32dev
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions} ;; if you get errors about "out of program space", change this to ${esp32.extended_partitions} or even ${esp32.big_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = dio
[env:esp32s2_saola]
extends = esp32s2
@@ -287,51 +280,53 @@ platform_packages = ${esp32s2.platform_packages}
framework = arduino
board_build.flash_mode = qio
upload_speed = 460800
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2_saola\"
build_flags = ${common.build_flags} ${esp32s2.build_flags}
;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work
-DARDUINO_USB_CDC_ON_BOOT=1
lib_deps = ${esp32s2.lib_deps}
[env:esp32s3dev_8MB_PSRAM_qspi]
;; ESP32-TinyS3 development board, with 8MB FLASH and PSRAM (memory_type: qio_qspi)
extends = env:esp32s3dev_8MB_PSRAM_opi
;board = um_tinys3 ; -> needs workaround from https://github.com/wled-dev/WLED/pull/2905#issuecomment-1328049860
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB
[env:esp8285_4CH_MagicHome]
board = esp8285
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA -D WLED_RELEASE_NAME=\"ESP8285_4CH_MagicHome\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA
lib_deps = ${esp8266.lib_deps}
[env:esp8285_H801]
board = esp8285
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA -D WLED_RELEASE_NAME=\"ESP8285_H801\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_5CH_Shojo_PCB]
board = d1_mini
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB ;; NB: WLED_USE_SHOJO_PCB is not used anywhere in the source code. Not sure why its needed.
-D WLED_RELEASE_NAME=\"ESP8266_5CH_Shojo_PCB\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_debug]
board = d1_mini
build_type = debug
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} ${common.debug_flags} -D WLED_RELEASE_NAME=\"ESP8266_D1MINI_DEBUG\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags} ${common.debug_flags}
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_ota]
@@ -339,23 +334,20 @@ board = d1_mini
upload_protocol = espota
# exchange for your WLED IP
upload_port = "10.10.1.27"
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_D1MINI_OTA\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:anavi_miracle_controller]
board = d1_mini
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=12 -D IRPIN=-1 -D RLYPIN=2
-D WLED_RELEASE_NAME=\"ESP8266_ANAVI_MIRACLE\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
[env:esp32c3dev_2MB]
@@ -366,7 +358,6 @@ platform = ${esp32c3.platform}
platform_packages = ${esp32c3.platform_packages}
board = esp32-c3-devkitm-1
build_flags = ${common.build_flags} ${esp32c3.build_flags}
-D WLED_RELEASE_NAME=\"ESP32-C3_2MB\"
-D WLED_WATCHDOG_TIMEOUT=0
-D WLED_DISABLE_OTA
; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB
@@ -383,7 +374,6 @@ board_upload.maximum_size = 2097152
extends = esp32 ;; use default esp32 platform
board = esp32dev
upload_speed = 460800
custom_usermods = ${env:esp32dev.custom_usermods} Temperature four_line_display_ALT
build_flags = ${common.build_flags} ${esp32.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_wemos_shield\"
-D DATA_PINS=16
@@ -391,8 +381,12 @@ build_flags = ${common.build_flags} ${esp32.build_flags}
-D BTNPIN=17
-D IRPIN=18
-UWLED_USE_MY_CONFIG
-D USERMOD_DALLASTEMPERATURE
-D USERMOD_FOUR_LINE_DISPLAY
-D TEMPERATURE_PIN=23
lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5 ;; needed for USERMOD_DALLASTEMPERATURE
olikraus/U8g2 @ ^2.28.8 ;; needed for USERMOD_FOUR_LINE_DISPLAY
board_build.partitions = ${esp32.default_partitions}
[env:esp32_pico-D4]
@@ -415,84 +409,75 @@ board_build.f_flash = 80000000L
[env:m5atom]
extends = env:esp32dev # we want to extend the existing esp32dev environment (and define only updated options)
build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39
-D WLED_RELEASE_NAME=\"ESP32_m5atom\"
[env:sp501e]
board = esp_wroom_02
platform = ${esp8266.platform_wled_default}
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=3 -D BTNPIN=1
-D WLED_RELEASE_NAME=\"ESP8266_sp501e\" -D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
[env:sp511e]
board = esp_wroom_02
platform = ${esp8266.platform_wled_default}
platform = ${common.platform_wled_default}
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
-D WLED_RELEASE_NAME=\"ESP8266_sp511e\" -D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
[env:Athom_RGBCW] ;7w and 5w(GU10) bulbs
board = esp8285
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5
-D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0
-D WLED_RELEASE_NAME=\"ESP8285_Athom_RGBCW\" -D WLED_DISABLE_PARTICLESYSTEM2D
-D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0
lib_deps = ${esp8266.lib_deps}
[env:Athom_15w_RGBCW] ;15w bulb
board = esp8285
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13
-D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT
-D WLED_RELEASE_NAME=\"ESP8285_Athom_15W_RGBCW\" -D WLED_DISABLE_PARTICLESYSTEM2D
-D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT
lib_deps = ${esp8266.lib_deps}
[env:Athom_3Pin_Controller] ;small controller with only data
board = esp8285
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D DATA_PINS=1 -D WLED_DISABLE_INFRARED
-D WLED_RELEASE_NAME=\"ESP8285_Athom_3Pin\" -D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
[env:Athom_4Pin_Controller] ; With clock and data interface
board = esp8285
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D DATA_PINS=1 -D WLED_DISABLE_INFRARED
-D WLED_RELEASE_NAME=\"ESP8285_Athom_4Pin\" -D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
[env:Athom_5Pin_Controller] ;Analog light strip controller
board = esp8285
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED
-D WLED_RELEASE_NAME=\"ESP8285_Athom_5Pin\" -D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
[env:MY9291]
board = esp01_1m
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
custom_usermods = ${env:esp01_1m_full.custom_usermods} MY9291
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA -D WLED_RELEASE_NAME=\"ESP8266_MY9291\" -D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_DISABLE_OTA -D USERMOD_MY9291
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
@@ -502,33 +487,60 @@ lib_deps = ${esp8266.lib_deps}
[env:codm-controller-0_6]
board = esp_wroom_02
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_CODM06_2MB\" -D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags}
lib_deps = ${esp8266.lib_deps}
[env:codm-controller-0_6-rev2]
board = esp_wroom_02
platform = ${esp8266.platform_wled_default}
platform_packages = ${esp8266.platform_packages}
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_CODM06R2_4MB\" -D WLED_DISABLE_PARTICLESYSTEM2D
build_flags = ${common.build_flags} ${esp8266.build_flags}
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
# EleksTube-IPS
# See usermods/EleksTube_IPS/platformio_override.ini.sample
# ------------------------------------------------------------------------------
[env:elekstube_ips]
extends = esp32 ;; use default esp32 platform
board = esp32dev
upload_speed = 921600
custom_usermods = ${env:esp32dev.custom_usermods} RTC EleksTube_IPS
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
-D DATA_PINS=12
-D RLYPIN=27
-D BTNPIN=34
-D PIXEL_COUNTS=6
# Display config
-D ST7789_DRIVER
-D TFT_WIDTH=135
-D TFT_HEIGHT=240
-D CGRAM_OFFSET
-D TFT_SDA_READ
-D TFT_MOSI=23
-D TFT_SCLK=18
-D TFT_DC=25
-D TFT_RST=26
-D SPI_FREQUENCY=40000000
-D USER_SETUP_LOADED
monitor_filters = esp32_exception_decoder
# ------------------------------------------------------------------------------
# Usermod examples
# ------------------------------------------------------------------------------
# 433MHz RF remote example: see usermods/usermod_v2_RF433/platformio_override.ini.sample
# 433MHz RF remote example for esp32dev
[env:esp32dev_usermod_RF433]
extends = env:esp32dev
custom_usermods =
${env:esp32dev.custom_usermods}
RF433
# External usermod from a git repository.
# The library's `library.json` must include `"build": {"libArchive": false}`.
@@ -542,4 +554,102 @@ custom_usermods =
https://github.com/wled/wled-usermod-example.git#main
# ------------------------------------------------------------------------------
# Hub75 examples
# ------------------------------------------------------------------------------
# Note: some panels may experience ghosting with default full brightness. use -D WLED_HUB75_MAX_BRIGHTNESS=239 or lower to fix it.
[env:esp32dev_hub75]
board = esp32dev
upload_speed = 921600
platform = ${esp32_idf_V4.platform}
platform_packages =
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_hub75\"
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D WLED_DEBUG_BUS
; -D WLED_DEBUG
-D SR_DMTYPE=1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
lib_deps = ${esp32_idf_V4.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
custom_usermods = audioreactive
[env:esp32dev_hub75_forum_pinout]
extends = env:esp32dev_hub75
build_flags = ${common.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_hub75_forum_pinout\"
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins
-D WLED_DEBUG_BUS
-D SR_DMTYPE=1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
; -D WLED_DEBUG
[env:adafruit_matrixportal_esp32s3]
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
board = adafruit_matrixportal_esp32s3_wled ; modified board definition: removed flash section that causes FS erase on upload
;; adafruit recommends to use arduino-esp32 2.0.14
;;platform = espressif32@ ~6.5.0
;;platform_packages = platformio/framework-arduinoespressif32 @ 3.20014.231204 ;; arduino-esp32 2.0.14
platform = ${esp32s3.platform}
platform_packages =
upload_speed = 921600
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8M_qspi\"
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this (sets lower TX power)
-D WLED_WATCHDOG_TIMEOUT=0
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
-D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3
-D WLED_DEBUG_BUS
-D SR_DMTYPE=1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
lib_deps = ${esp32s3.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
board_build.partitions = ${esp32.large_partitions} ;; standard bootloader and 8MB Flash partitions
;; board_build.partitions = tools/partitions-8MB_spiffs-tinyuf2.csv ;; supports adafruit UF2 bootloader
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
custom_usermods = audioreactive
[env:esp32S3_PSRAM_HUB75]
;; MOONHUB HUB75 adapter board (lilygo T7-S3 with 16MB flash and PSRAM)
board = lilygo-t7-s3
platform = ${esp32s3.platform}
platform_packages =
upload_speed = 921600
build_unflags = ${common.build_unflags}
${Speed_Flags.build_unflags} ;; optional: removes "-Os" so we can override with "-O2" in build_flags
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"esp32S3_16MB_PSRAM_HUB75\"
${Speed_Flags.build_flags} ;; optional: -O2 -> optimize for speed instead of size
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this (sets lower TX power)
-D WLED_WATCHDOG_TIMEOUT=0
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
-D WLED_DEBUG_BUS
-D LEDPIN=14 -D BTNPIN=0 -D RLYPIN=15 -D IRPIN=-1 -D AUDIOPIN=-1 ;; defaults that avoid pin conflicts with HUB75
-D SR_DMTYPE=1 -D I2S_SDPIN=10 -D I2S_CKPIN=11 -D I2S_WSPIN=12 -D MCLK_PIN=-1 ;; I2S mic
lib_deps = ${esp32s3.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
;;board_build.partitions = ${esp32.large_partitions} ;; for 8MB flash
board_build.partitions = ${esp32.extreme_partitions} ;; for 16MB flash
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
custom_usermods = audioreactive
+47 -63
View File
@@ -7,97 +7,81 @@
<a href="https://kno.wled.ge"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a>
<a href="https://github.com/Aircoookie/WLED-App"><img src="https://img.shields.io/badge/app-wled-blue.svg?style=flat-square"></a>
<a href="https://gitpod.io/#https://github.com/wled-dev/WLED"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?style=flat-square&logo=gitpod"></a>
</p>
</p>
# Welcome to WLED! ✨
A fast and feature-rich firmware for ESP32 microcontrollers to control addressable LEDs — from simple strips to large 2D matrices and HUB75 panels.
A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!
Originally created by [Aircoookie](https://github.com/Aircoookie), now maintained by a community of contributors.
Originally created by [Aircoookie](https://github.com/Aircoookie)
## ⚙️ Features
- WS2812FX library with more than 100 special effects
- FastLED noise effects and 50 palettes
- Modern UI with color, effect and segment controls
- Segments to set different effects and colors to user defined parts of the LED string
- Settings page - configuration via the network
- Access Point and station mode - automatic failsafe AP
- [Up to 10 LED outputs](https://kno.wled.ge/features/multi-strip/#esp32) per instance
- Support for RGBW strips
- Up to 250 user presets to save and load colors/effects easily, supports cycling through them.
- Presets can be used to automatically execute API calls
- Nightlight function (gradually dims down)
- Full OTA software updateability (HTTP + ArduinoOTA), password protectable
- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods)
- Configurable Auto Brightness limit for safe operation
- Filesystem-based config for easier backup of presets and settings
### Effects & Visuals
- [**200+ built-in effects**](https://kno.wled.ge/features/effects/) including classic animations, audio-reactive, and 2D/matrix effects
- [50+ color palettes](https://kno.wled.ge/features/palettes/) plus a built-in **custom palette editor** (PixelForge)
- [**2D LED matrix support**](https://kno.wled.ge/advanced/mapping/) with dedicated 2D effects and flexible panel mapping
- [**HUB75 RGB matrix panel support**](https://kno.wled.ge/advanced/HUB75/) (ESP32)
- [**AudioReactive**](https://kno.wled.ge/advanced/audio-reactive/) effects — included by default, responding to sound via microphone, line-in, or network audio source
- Effect blending for smooth transitions between animations
- Antialiased drawing functions for smooth graphics
### Segments & Control
- [**Segments**](https://kno.wled.ge/features/segments/) — apply different effects, colors and palettes to independent parts of your LED setup simultaneously
- Up to **250 presets** to save and recall colors, effects and segment configurations — supports [playlists](https://kno.wled.ge/features/presets/) for automated cycling
- Nightlight function with configurable dimming curve
- Configurable **Auto Brightness Limiter** (per output) for safe operation
### Hardware Support
- **ESP32** (all variants: original, S2, S3, C3)
- [**Up to 17 LED outputs**](https://kno.wled.ge/features/multi-strip/) on ESP32 using parallel I2S + RMT
- [Addressable LED support](https://kno.wled.ge/basics/compatible-led-strips/): WS2812B, WS2811, WS2815, SK6812, WS2805, TM1914, APA102, WS2801, LPD8806, and many more
- RGBW, [RGB+CCT](https://kno.wled.ge/features/cct/) and white-only strips
- PWM outputs for analog LEDs and dimmers
- [**Ethernet** support](https://kno.wled.ge/features/ethernet-lan/) for a wide range of boards (QuinLED, LILYGO, Olimex, and more)
- Filesystem-based config for easy backup and restore of presets and settings
- Full OTA firmware updates (HTTP + ArduinoOTA), password-protectable
### Connectivity & Integrations
- **WLED app** for [Android](https://play.google.com/store/apps/details?id=ca.cgagnier.wlednativeandroid) and [iOS](https://apps.apple.com/gb/app/wled-native/id6446207239)
- [JSON](https://kno.wled.ge/interfaces/json-api/) and [HTTP request](https://kno.wled.ge/interfaces/http-api/) APIs
- **Multi-WiFi** — connect to up to 3 networks with automatic AP fallback
- **ESP-NOW** wireless sync between devices (no WiFi router required)
- [**MQTT**](https://kno.wled.ge/interfaces/mqtt/) with Home Assistant discovery
- [**E1.31, Art-Net**](https://kno.wled.ge/interfaces/e1.31-dmx/), [DDP](https://kno.wled.ge/interfaces/ddp/) and [TPM2.net](https://kno.wled.ge/interfaces/udp-realtime/) for DMX/professional lighting control
- [UDP realtime sync](https://kno.wled.ge/interfaces/udp-notifier/) across multiple WLED devices
- Alexa voice control (on/off, brightness, color)
- [Philips Hue sync](https://kno.wled.ge/interfaces/philips-hue/)
- [diyHue](https://github.com/diyhue/diyHue) and [Hyperion](https://github.com/hyperion-project/hyperion.ng) integration
- [Adalight / TPM2](https://kno.wled.ge/interfaces/serial/) (PC ambilight via serial)
- [Infrared remote control](https://kno.wled.ge/interfaces/infrared/) (24-key RGB, receiver required)
- Timers and schedules (NTP time sync, full timezone and DST support)
### Developer-Friendly
- **Usermod system** — extend WLED with community or custom modules without modifying core code
- Large and active [usermod library](https://kno.wled.ge/advanced/community-usermods/) including AudioReactive, temperature sensors, rotary encoders, displays, and much more
- Well-documented [JSON API](https://kno.wled.ge/interfaces/json-api/)
- Licensed under the **EUPL v1.2**
## 💡 Supported light control interfaces
- WLED app for [Android](https://play.google.com/store/apps/details?id=ca.cgagnier.wlednativeandroid) and [iOS](https://apps.apple.com/gb/app/wled-native/id6446207239)
- JSON and HTTP request APIs
- MQTT
- E1.31, Art-Net, DDP and TPM2.net
- [diyHue](https://github.com/diyhue/diyHue) (Wled is supported by diyHue, including Hue Sync Entertainment under udp. Thanks to [Gregory Mallios](https://github.com/gmallios))
- [Hyperion](https://github.com/hyperion-project/hyperion.ng)
- UDP realtime
- Alexa voice control (including dimming and color)
- Sync to Philips hue lights
- Adalight (PC ambilight via serial) and TPM2
- Sync color of multiple WLED devices (UDP notifier)
- Infrared remotes (24-key RGB, receiver required)
- Simple timers/schedules (time from NTP, timezones/DST supported)
## 📲 Quick start guide and documentation
See the [documentation at kno.wled.ge](https://kno.wled.ge)!
See the [documentation on our official site](https://kno.wled.ge)!
[Tutorials and getting-started guides](https://kno.wled.ge/basics/tutorials/) to help you get your project running quickly.
[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials and tools to help you get your new project up and running!
## 🖼️ User interface
<img src="/images/macbook-pro-space-gray-on-the-wooden-table.jpg" width="50%"><img src="/images/walking-with-iphone-x.jpg" width="50%">
## 💾 Compatible hardware
See the [compatible hardware list](https://kno.wled.ge/basics/compatible-hardware) on the wiki.
See [here](https://kno.wled.ge/basics/compatible-hardware)!
## ✌️ Other
Licensed under the [EUPL v1.2](https://raw.githubusercontent.com/wled-dev/WLED/main/LICENSE).
Credits to all [contributors](https://kno.wled.ge/about/contributors/)!
CORS proxy by [Corsfix](https://corsfix.com/).
Licensed under the EUPL v1.2 license
Credits [here](https://kno.wled.ge/about/contributors/)!
CORS proxy by [Corsfix](https://corsfix.com/)
Join the Discord server to discuss everything about WLED!
<a href="https://discord.gg/QAh7wJHrRM"><img src="https://discordapp.com/api/guilds/473448917040758787/widget.png?style=banner2" width="25%"></a>
Check out the WLED [Discourse forum](https://wled.discourse.group)!
Check out the WLED [Discourse forum](https://wled.discourse.group)!
If you'd like to reach the original creator privately: [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com).
You can also send me mails to [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com), but please, only do so if you want to talk to me privately.
If WLED brightens up your day, you can [send a gift to Aircoookie via PayPal](https://paypal.me/aircoookie).
If WLED really brightens up your day, you can [![](https://img.shields.io/badge/send%20me%20a%20small%20gift-paypal-blue.svg?style=flat-square)](https://paypal.me/aircoookie)
---
*Disclaimer:*
*Disclaimer:*
If you are prone to photosensitive epilepsy, we recommend you do **not** use this software.
If you still want to try, avoid strobe, lightning or noise modes and high effect speed settings.
If you are prone to photosensitive epilepsy, we recommended you do **not** use this software.
If you still want to try, don't use strobe, lighting or noise modes or high effect speed settings.
As per the EUPL license, I assume no liability for any damage to you or any other person or equipment.
As per the EUPL license, no liability is assumed for any damage to you or any other person or equipment.
+6 -12
View File
@@ -119,7 +119,7 @@ describe('Script', () => {
async function checkIfFileWasNewlyCreated(file) {
const modifiedTime = fs.statSync(file).mtimeMs;
assert(Date.now() - modifiedTime < 850, file + ' was not modified');
assert(Date.now() - modifiedTime < 500, file + ' was not modified');
}
async function testFileModification(sourceFilePath, resultFile) {
@@ -129,7 +129,7 @@ describe('Script', () => {
// modify file
fs.appendFileSync(sourceFilePath, ' ');
// delay for 1 second to ensure the modified time is different
await new Promise(resolve => setTimeout(resolve, 1400));
await new Promise(resolve => setTimeout(resolve, 1000));
// run script cdata.js again and wait for it to finish
await execPromise('node tools/cdata.js');
@@ -175,19 +175,13 @@ describe('Script', () => {
});
it('a settings file changes', async () => {
await testFileModification(path.join(dataPath, 'settings_leds.htm'), 'html_settings.h');
await testFileModification(path.join(dataPath, 'settings_leds.htm'), 'html_ui.h');
});
it('common.js changes', async () => {
await testFileModification(path.join(dataPath, 'common.js'), 'html_settings.h');
it('the favicon changes', async () => {
await testFileModification(path.join(dataPath, 'favicon.ico'), 'html_ui.h');
});
// this testcase currently fails - might be due to npm updates (maybe "faking" a favicon.ico change is harder now), or a real regression
// see https://github.com/wled/WLED/issues/5581
// it('the favicon changes', async () => {
// await testFileModification(path.join(dataPath, 'favicon.ico'), 'html_other.h');
// });
it('cdata.js changes', async () => {
await testFileModification('tools/cdata.js', 'html_ui.h');
});
@@ -215,4 +209,4 @@ describe('Script', () => {
assert(secondRunTime < firstRunTime / 2, 'html_*.h files were rebuilt');
});
});
});
});
+10
View File
@@ -0,0 +1,10 @@
/* ESP32 linker script fragment to add dynamic array section to binary */
SECTIONS
{
.dynarray :
{
. = ALIGN(0x10);
KEEP(*(SORT_BY_INIT_PRIORITY(.dynarray.*)))
} > default_rodata_seg
}
INSERT AFTER .flash.rodata;
+2 -10
View File
@@ -141,16 +141,8 @@ discover_devices() {
exit 1
fi
# Map avahi responses to strings separated by 0x1F (unit separator), deduplicated
mapfile -t raw_devices < <(
avahi-browse _wled._tcp --terminate -r -p |
awk -F';' '
/^=/ {
key = $7 "\x1F" $8 "\x1F" $9
if (!seen[key]++) print key
}
'
)
# Map avahi responses to strings seperated by 0x1F (unit separator)
mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7"\x1F"$8"\x1F"$9}')
local devices_array=()
for device in "${raw_devices[@]}"; do
+11 -12
View File
@@ -28,19 +28,18 @@ class ADS1115Usermod : public Usermod {
}
void loop() {
if (!isEnabled || strip.isUpdating() || millis() - lastTime <= loopInterval)
return;
if (isEnabled && millis() - lastTime > loopInterval) {
lastTime = millis();
lastTime = millis();
// If we don't have new data, skip this iteration.
if (!ads.conversionComplete()) {
return;
}
// If we don't have new data, skip this iteration.
if (!ads.conversionComplete()) {
return;
updateResult();
moveToNextChannel();
startReading();
}
updateResult();
moveToNextChannel();
startReading();
}
void addToJsonInfo(JsonObject& root)
@@ -70,8 +69,6 @@ class ADS1115Usermod : public Usermod {
{
JsonObject top = root.createNestedObject(F("ADC ADS1115"));
top[F("Loop Interval")] = loopInterval;
for (uint8_t i = 0; i < channelsCount; i++) {
ChannelSettings* settingsPtr = &(channelSettings[i]);
JsonObject channel = top.createNestedObject(settingsPtr->settingName);
@@ -82,6 +79,8 @@ class ADS1115Usermod : public Usermod {
channel[F("Offset")] = settingsPtr->offset;
channel[F("Decimals")] = settingsPtr->decimals;
}
top[F("Loop Interval")] = loopInterval;
}
bool readFromConfig(JsonObject& root)
+2 -2
View File
@@ -2,7 +2,7 @@
"name": "ADS1115_v2",
"build": { "libArchive": false },
"dependencies": {
"Adafruit BusIO": "https://github.com/adafruit/Adafruit_BusIO#1.17.4",
"Adafruit ADS1X15": "https://github.com/adafruit/Adafruit_ADS1X15#2.6.2"
"Adafruit BusIO": "https://github.com/adafruit/Adafruit_BusIO#1.13.2",
"Adafruit ADS1X15": "https://github.com/adafruit/Adafruit_ADS1X15#2.4.0"
}
}
+5 -24
View File
@@ -1,29 +1,10 @@
# ADS1115 Usermod
# ADS1115 16-Bit ADC with four inputs
Reads values from an ADS1115 16-bit ADC and exposes them in the `Info` tab.
This usermod will read from an ADS1115 ADC. The voltages are displayed in the Info section of the web UI.
## Features
- Reads values from an ADS1115 over I2C.
- Supports 8 ADS1115 input modes:
- 4 single-ended inputs (`AIN0` to `AIN3`)
- 4 differential pairs (`AIN0-AIN1`, `AIN0-AIN3`, `AIN1-AIN3`, `AIN2-AIN3`)
- Per-channel configuration in the Usermod settings:
- Enable/disable
- Display name
- Units
- Multiplier and offset
- Decimal precision
- Configurable measurement loop interval.
- Publishes configured channel values to the `Info` tab.
Configuration is performed via the Usermod menu. There are no parameters to set in code!
## Compatibility
- Requires an ADS1115 module connected via I2C.
- Works on targets with I2C support.
- Default ADC gain is `1x` (input range `+/-4.096V`).
## Installation
## Installation
- Add `ADS1115` to `custom_usermods` in your `platformio.ini` (or `platformio_override.ini`).
## Author
- Dima Zhemkov [@dima-zhemkov](https://github.com/dima-zhemkov)
Add 'ADS1115' to `custom_usermods` in your platformio environment.
@@ -0,0 +1,5 @@
[env:aht10_example]
extends = env:esp32dev
build_flags =
${common.build_flags} ${esp32.build_flags}
; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal
@@ -25,8 +25,6 @@ class Animated_Staircase : public Usermod {
unsigned int topMaxDist = 50; // default maximum measured distance in cm, top
unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom
bool togglePower = false; // toggle power on/off with staircase on/off
bool topAPinInvert = false; // invert output of top sensor
bool bottomAPinInvert = false; // invert output of bottom sensor
/* runtime variables */
bool initDone = false;
@@ -93,8 +91,6 @@ class Animated_Staircase : public Usermod {
static const char _topEchoCm[];
static const char _bottomEchoCm[];
static const char _togglePower[];
static const char _topPIRorTrigger_pin_invert[];
static const char _bottomPIRorTrigger_pin_invert[];
void publishMqtt(bool bottom, const char* state) {
#ifndef WLED_DISABLE_MQTT
@@ -160,12 +156,6 @@ class Animated_Staircase : public Usermod {
return pulseIn(echoPin, HIGH, maxTimeUs) > 0;
}
bool readPIRPin(int8_t pin, bool invert) {
if (pin < 0) return false;
bool v = digitalRead(pin);
return invert ? !v : v;
}
bool checkSensors() {
bool sensorChanged = false;
@@ -174,15 +164,15 @@ class Animated_Staircase : public Usermod {
bottomSensorRead = bottomSensorWrite ||
(!useUSSensorBottom ?
(bottomPIRorTriggerPin<0 ? false : readPIRPin(bottomPIRorTriggerPin, bottomAPinInvert)) :
(bottomPIRorTriggerPin<0 ? false : digitalRead(bottomPIRorTriggerPin)) :
ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxDist*59) // cm to us
);
topSensorRead = topSensorWrite ||
(!useUSSensorTop ?
(topPIRorTriggerPin<0 ? false : readPIRPin(topPIRorTriggerPin, topAPinInvert)) :
(topPIRorTriggerPin<0 ? false : digitalRead(topPIRorTriggerPin)) :
ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxDist*59) // cm to us
);
if (bottomSensorRead != bottomSensorState) {
bottomSensorState = bottomSensorRead; // change previous state
sensorChanged = true;
@@ -449,20 +439,18 @@ class Animated_Staircase : public Usermod {
if (staircase.isNull()) {
staircase = root.createNestedObject(FPSTR(_name));
}
staircase[FPSTR(_enabled)] = enabled;
staircase[FPSTR(_segmentDelay)] = segment_delay_ms;
staircase[FPSTR(_onTime)] = on_time_ms / 1000;
staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop;
staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin;
staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1;
staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom;
staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin;
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
staircase[FPSTR(_topEchoCm)] = topMaxDist;
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
staircase[FPSTR(_togglePower)] = togglePower;
staircase[FPSTR(_topPIRorTrigger_pin_invert)] = topAPinInvert;
staircase[FPSTR(_bottomPIRorTrigger_pin_invert)] = bottomAPinInvert;
staircase[FPSTR(_enabled)] = enabled;
staircase[FPSTR(_segmentDelay)] = segment_delay_ms;
staircase[FPSTR(_onTime)] = on_time_ms / 1000;
staircase[FPSTR(_useTopUltrasoundSensor)] = useUSSensorTop;
staircase[FPSTR(_topPIRorTrigger_pin)] = topPIRorTriggerPin;
staircase[FPSTR(_topEcho_pin)] = useUSSensorTop ? topEchoPin : -1;
staircase[FPSTR(_useBottomUltrasoundSensor)] = useUSSensorBottom;
staircase[FPSTR(_bottomPIRorTrigger_pin)] = bottomPIRorTriggerPin;
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
staircase[FPSTR(_topEchoCm)] = topMaxDist;
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
staircase[FPSTR(_togglePower)] = togglePower;
DEBUG_PRINTLN(F("Staircase config saved."));
}
@@ -474,13 +462,11 @@ class Animated_Staircase : public Usermod {
bool readFromConfig(JsonObject& root) {
bool oldUseUSSensorTop = useUSSensorTop;
bool oldUseUSSensorBottom = useUSSensorBottom;
bool oldTopAPinInvert = topAPinInvert;
bool oldBottomAPinInvert = bottomAPinInvert;
int8_t oldTopAPin = topPIRorTriggerPin;
int8_t oldTopBPin = topEchoPin;
int8_t oldBottomAPin = bottomPIRorTriggerPin;
int8_t oldBottomBPin = bottomEchoPin;
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
@@ -499,12 +485,10 @@ class Animated_Staircase : public Usermod {
useUSSensorTop = top[FPSTR(_useTopUltrasoundSensor)] | useUSSensorTop;
topPIRorTriggerPin = top[FPSTR(_topPIRorTrigger_pin)] | topPIRorTriggerPin;
topEchoPin = top[FPSTR(_topEcho_pin)] | topEchoPin;
topAPinInvert = top[FPSTR(_topPIRorTrigger_pin_invert)] | topAPinInvert;
useUSSensorBottom = top[FPSTR(_useBottomUltrasoundSensor)] | useUSSensorBottom;
bottomPIRorTriggerPin = top[FPSTR(_bottomPIRorTrigger_pin)] | bottomPIRorTriggerPin;
bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
bottomAPinInvert = top[FPSTR(_bottomPIRorTrigger_pin_invert)] | bottomAPinInvert;
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
@@ -570,15 +554,14 @@ const char Animated_Staircase::_segmentDelay[] PROGMEM = "segment-d
const char Animated_Staircase::_onTime[] PROGMEM = "on-time-s";
const char Animated_Staircase::_useTopUltrasoundSensor[] PROGMEM = "useTopUltrasoundSensor";
const char Animated_Staircase::_topPIRorTrigger_pin[] PROGMEM = "topPIRorTrigger_pin";
const char Animated_Staircase::_topPIRorTrigger_pin_invert[] PROGMEM = "topPIRorTrigger_pin_invert";
const char Animated_Staircase::_topEcho_pin[] PROGMEM = "topEcho_pin";
const char Animated_Staircase::_useBottomUltrasoundSensor[] PROGMEM = "useBottomUltrasoundSensor";
const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIRorTrigger_pin";
const char Animated_Staircase::_bottomPIRorTrigger_pin_invert[] PROGMEM = "bottomPIRorTrigger_pin_invert";
const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin";
const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm";
const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm";
const char Animated_Staircase::_togglePower[] PROGMEM = "toggle-on-off";
static Animated_Staircase animated_staircase;
REGISTER_USERMOD(animated_staircase);
@@ -8,13 +8,13 @@
; USERMOD_DHT_MQTT - publish measurements to the MQTT broker
; USERMOD_DHT_STATS - For debug, report delay stats
[env:esp8266_2m_usermod_dht_C]
extends = env:esp8266_2m
custom_usermods = ${env:esp8266_2m.custom_usermods} DHT
build_flags = ${env:esp8266_2m.build_flags} -D USERMOD_DHT_CELSIUS
[env:d1_mini_usermod_dht_C]
extends = env:d1_mini
custom_usermods = ${env:d1_mini.custom_usermods} DHT
build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT_CELSIUS
[env:esp32dev_LEDPIN_16_usermod_dht_C]
extends = env:esp32dev
custom_usermods = ${env:esp32dev.custom_usermods} DHT
build_flags = ${env:esp32dev.build_flags} -D LEDPIN=16 -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS
[env:custom32_LEDPIN_16_usermod_dht_C]
extends = env:custom32_LEDPIN_16
custom_usermods = ${env:custom32_LEDPIN_16.custom_usermods} DHT
build_flags = ${env:custom32_LEDPIN_16.build_flags} -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS
+2 -2
View File
@@ -114,7 +114,7 @@ class MyExampleUsermod : public Usermod {
void loop() override {
// if usermod is disabled or called during strip updating just exit
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
if (!enabled || (strip.isUpdating() && (millis() - lastTime < 200))) return; // adjust "200" (in millisecond) to your needs - prevents starvation with very long strips
if (!enabled || strip.isUpdating()) return;
// do your magic here
if (millis() - lastTime > 1000) {
@@ -176,7 +176,7 @@ class MyExampleUsermod : public Usermod {
JsonObject usermod = root[FPSTR(_name)];
if (!usermod.isNull()) {
// expect JSON usermod data in usermod name object: {"ExampleUsermod:{"user0":10}"}
userVar0 = usermod["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value (userVar0 is defined in wled.h)
userVar0 = usermod["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
}
// you can as well check WLED state JSON keys
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "EleksTube_IPS",
"name:": "EleksTube_IPS",
"build": { "libArchive": false },
"dependencies": {
"TFT_eSPI" : "2.5.33"
@@ -1,5 +1,4 @@
{
"name": "Fix_unreachable_netservices_v2",
"build": { "libArchive": false },
"platforms": ["espressif8266"]
}
@@ -77,7 +77,7 @@ public:
}
if (m_updateConfig)
{
serializeConfigToFS();
serializeConfig();
m_updateConfig = false;
}
}
+35 -114
View File
@@ -1,38 +1,14 @@
#include "wled.h"
#include <INA226_WE.h>
#ifndef INA226_ADDRESS
#define INA226_ADDRESS 0x40 // Default I2C address for INA226
#endif
#ifndef INA226_CHECK_INTERVAL_MS
#define INA226_CHECK_INTERVAL_MS 60000 // Default check interval in milliseconds
#endif
#define DEFAULT_CHECKINTERVAL INA226_CHECK_INTERVAL_MS
#define DEFAULT_CHECKINTERVAL 60000
#define DEFAULT_INASAMPLES 128
#define DEFAULT_INASAMPLESENUM AVERAGE_128
#define DEFAULT_INACONVERSIONTIME 1100
#define DEFAULT_INACONVERSIONTIMEENUM CONV_TIME_1100
// Compile-time defaults for shunt resistor (micro-ohms), current range (mA), and current offset (mA)
// These can be overridden via -D flags in platformio.ini / platformio_override.ini
#ifndef INA226_SHUNT_MICRO_OHMS
#define INA226_SHUNT_MICRO_OHMS 1000000 // 1 Ohm = 1,000,000 μΩ
#endif
#ifndef INA226_DEFAULT_CURRENT_RANGE
#define INA226_DEFAULT_CURRENT_RANGE 1000 // 1000 mA = 1 A
#endif
#ifndef INA226_CURRENT_OFFSET_MA
#define INA226_CURRENT_OFFSET_MA 0 // No offset by default
#endif
#ifndef INA226_ENABLED_DEFAULT
#define INA226_ENABLED_DEFAULT false
#endif
// A packed version of all INA settings enums and their human friendly counterparts packed into a 32 bit structure
// Some values are shifted and need to be preprocessed before usage
struct InaSettingLookup
@@ -105,11 +81,10 @@ private:
uint16_t _settingInaSamples : 11; // Number of samples for averaging, max 1024
uint8_t _i2cAddress;
uint32_t _checkIntervalMs; // milliseconds, user settings is in seconds
float _decimalFactor; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..)
uint32_t _shuntResistorUOhm; // Shunt resistor value in micro-ohms (μΩ)
uint16_t _currentRangeMa; // Expected maximum current in milliamps
int16_t _currentOffsetMa; // Current offset in milliamps, subtracted from readings
uint16_t _checkInterval; // milliseconds, user settings is in seconds
float _decimalFactor; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..)
uint16_t _shuntResistor; // Shunt resistor value in milliohms
uint16_t _currentRange; // Expected maximum current in milliamps
uint8_t _lastStatus = 0;
float _lastCurrent = 0;
@@ -118,7 +93,7 @@ private:
float _lastShuntVoltage = 0;
bool _lastOverflow = false;
#ifndef WLED_DISABLE_MQTT
#ifndef WLED_MQTT_DISABLE
float _lastCurrentSent = 0;
float _lastVoltageSent = 0;
float _lastPowerSent = 0;
@@ -143,11 +118,9 @@ private:
_ina226 = new INA226_WE(_i2cAddress);
if (!_ina226->init())
{
DEBUG_PRINTLN(F("INA226: init failed!"));
DEBUG_PRINTLN(F("INA226 initialization failed!"));
return;
}
DEBUG_PRINTF_P(PSTR("INA226: addr=0x%02X shunt=%luμΩ range=%umA offset=%dmA\n"),
_i2cAddress, _shuntResistorUOhm, _currentRangeMa, _currentOffsetMa);
_ina226->setCorrectionFactor(1.0);
uint16_t tmpShort = _settingInaSamples;
@@ -156,7 +129,7 @@ private:
tmpShort = _settingInaConversionTimeUs << 2;
_ina226->setConversionTime(getConversionTimeEnum(tmpShort));
if (_checkIntervalMs >= 20000)
if (_checkInterval >= 20000)
{
_isTriggeredOperationMode = true;
_ina226->setMeasureMode(TRIGGERED);
@@ -167,11 +140,7 @@ private:
_ina226->setMeasureMode(CONTINUOUS);
}
_ina226->setResistorRange(static_cast<float>(_shuntResistorUOhm) / 1000000.0f, static_cast<float>(_currentRangeMa) / 1000.0f);
DEBUG_PRINTF_P(PSTR("INA226: mode=%s interval=%lums samples=%u convTime=%uμs\n"),
_isTriggeredOperationMode ? "triggered" : "continuous",
_checkIntervalMs, _settingInaSamples, _settingInaConversionTimeUs << 2);
_ina226->setResistorRange(static_cast<float>(_shuntResistor) / 1000.0, static_cast<float>(_currentRange) / 1000.0);
}
void fetchAndPushValues()
@@ -181,19 +150,17 @@ private:
if (_lastStatus != 0)
return;
float current = truncateDecimals((_ina226->getCurrent_mA() - _currentOffsetMa) / 1000.0f);
float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0);
float voltage = truncateDecimals(_ina226->getBusVoltage_V());
float power = truncateDecimals(_ina226->getBusPower() / 1000.0f);
float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_mV());
float power = truncateDecimals(_ina226->getBusPower() / 1000.0);
float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V());
bool overflow = _ina226->overflow;
#ifndef WLED_DISABLE_MQTT
mqttPublishIfChanged(F("current"), _lastCurrentSent, current, 0.01f);
mqttPublishIfChanged(F("voltage"), _lastVoltageSent, voltage, 0.01f);
mqttPublishIfChanged(F("power"), _lastPowerSent, power, 0.1f);
// Publish in V for backward compatibility
float shuntVoltageV = shuntVoltage / 1000.0f;
mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltageV, 0.01f);
mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f);
mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow);
#endif
@@ -202,9 +169,6 @@ private:
_lastPower = power;
_lastShuntVoltage = shuntVoltage;
_lastOverflow = overflow;
DEBUG_PRINTF_P(PSTR("INA226: %.3fA %.2fV %.2fW shunt=%.2fmV%s\n"),
current, voltage, power, shuntVoltage, overflow ? " OVF" : "");
}
void handleTriggeredMode(unsigned long currentTime)
@@ -224,7 +188,7 @@ private:
}
else
{
if (currentTime - _lastLoopCheck >= _checkIntervalMs)
if (currentTime - _lastLoopCheck >= _checkInterval)
{
// Start a measurement and use isBusy() later to determine when it is done
_ina226->startSingleMeasurementNoWait();
@@ -237,7 +201,7 @@ private:
void handleContinuousMode(unsigned long currentTime)
{
if (currentTime - _lastLoopCheck >= _checkIntervalMs)
if (currentTime - _lastLoopCheck >= _checkInterval)
{
_lastLoopCheck = currentTime;
fetchAndPushValues();
@@ -251,23 +215,19 @@ private:
return;
char topic[128];
auto buildTopic = [&](const char *suffix) {
snprintf_P(topic, sizeof(topic), PSTR("%s/%s"), mqttDeviceTopic, suffix);
};
buildTopic("current");
snprintf_P(topic, 127, "%s/current", mqttDeviceTopic);
mqttCreateHassSensor(F("Current"), topic, F("current"), F("A"));
buildTopic("voltage");
snprintf_P(topic, 127, "%s/voltage", mqttDeviceTopic);
mqttCreateHassSensor(F("Voltage"), topic, F("voltage"), F("V"));
buildTopic("power");
snprintf_P(topic, 127, "%s/power", mqttDeviceTopic);
mqttCreateHassSensor(F("Power"), topic, F("power"), F("W"));
buildTopic("shunt_voltage");
snprintf_P(topic, 127, "%s/shunt_voltage", mqttDeviceTopic);
mqttCreateHassSensor(F("Shunt Voltage"), topic, F("voltage"), F("V"));
buildTopic("overflow");
snprintf_P(topic, 127, "%s/overflow", mqttDeviceTopic);
mqttCreateHassBinarySensor(F("Overflow"), topic);
}
@@ -355,23 +315,14 @@ public:
UsermodINA226()
{
// Default values
_settingEnabled = INA226_ENABLED_DEFAULT;
_settingInaSamples = DEFAULT_INASAMPLES;
_settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME >> 2; // stored shifted to fit 12-bit field
_settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME;
_i2cAddress = INA226_ADDRESS;
_checkIntervalMs = DEFAULT_CHECKINTERVAL;
_checkInterval = DEFAULT_CHECKINTERVAL;
_decimalFactor = 100;
_shuntResistorUOhm = INA226_SHUNT_MICRO_OHMS;
_currentRangeMa = INA226_DEFAULT_CURRENT_RANGE;
_currentOffsetMa = INA226_CURRENT_OFFSET_MA;
_mqttPublish = false;
_mqttPublishAlways = false;
_mqttHomeAssistant = false;
_initDone = false;
_isTriggeredOperationMode = false;
_measurementTriggered = false;
_shuntResistor = 1000;
_currentRange = 1000;
}
void setup()
@@ -448,7 +399,7 @@ public:
JsonArray jsonCurrent = user.createNestedArray(F("Current"));
JsonArray jsonVoltage = user.createNestedArray(F("Voltage"));
JsonArray jsonPower = user.createNestedArray(F("Power"));
JsonArray jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage Drop"));
JsonArray jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage"));
JsonArray jsonOverflow = user.createNestedArray(F("Overflow"));
if (_lastLoopCheck == 0)
@@ -481,7 +432,7 @@ public:
jsonPower.add(F("W"));
jsonShuntVoltage.add(_lastShuntVoltage);
jsonShuntVoltage.add(F("mV"));
jsonShuntVoltage.add(F("V"));
jsonOverflow.add(_lastOverflow ? F("true") : F("false"));
}
@@ -491,13 +442,12 @@ public:
JsonObject top = root.createNestedObject(FPSTR(_name));
top[F("Enabled")] = _settingEnabled;
top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress);
top[F("CheckInterval")] = _checkIntervalMs / 1000;
top[F("CheckInterval")] = _checkInterval / 1000;
top[F("INASamples")] = _settingInaSamples;
top[F("INAConversionTime")] = _settingInaConversionTimeUs << 2;
top[F("Decimals")] = log10f(_decimalFactor);
top[F("ShuntResistor")] = static_cast<float>(_shuntResistorUOhm) / 1000.0f;
top[F("CurrentRange")] = _currentRangeMa;
top[F("CurrentOffset")] = _currentOffsetMa;
top[F("ShuntResistor")] = _shuntResistor;
top[F("CurrentRange")] = _currentRange;
#ifndef WLED_DISABLE_MQTT
top[F("MqttPublish")] = _mqttPublish;
top[F("MqttPublishAlways")] = _mqttPublishAlways;
@@ -507,17 +457,6 @@ public:
DEBUG_PRINTLN(F("INA226 config saved."));
}
void appendConfigData() override
{
oappend(F("addInfo('INA226:CheckInterval',1,'seconds');"));
oappend(F("addInfo('INA226:INASamples',1,'samples (1-1024)');"));
oappend(F("addInfo('INA226:INAConversionTime',1,'&micro;s');"));
oappend(F("addInfo('INA226:Decimals',1,'(0-5)');"));
oappend(F("addInfo('INA226:ShuntResistor',1,'m&Omega;');"));
oappend(F("addInfo('INA226:CurrentRange',1,'mA');"));
oappend(F("addInfo('INA226:CurrentOffset',1,'mA');"));
}
bool readFromConfig(JsonObject &root) override
{
JsonObject top = root[FPSTR(_name)];
@@ -533,12 +472,12 @@ public:
configComplete = false;
configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress);
if (getJsonValue(top[F("CheckInterval")], _checkIntervalMs))
if (getJsonValue(top[F("CheckInterval")], _checkInterval))
{
if (1 <= _checkIntervalMs && _checkIntervalMs <= 600)
_checkIntervalMs *= 1000;
if (1 <= _checkInterval && _checkInterval <= 600)
_checkInterval *= 1000;
else
_checkIntervalMs = DEFAULT_CHECKINTERVAL;
_checkInterval = DEFAULT_CHECKINTERVAL;
}
else
configComplete = false;
@@ -572,26 +511,8 @@ public:
else
configComplete = false;
float shuntMilliOhms;
if (getJsonValue(top[F("ShuntResistor")], shuntMilliOhms))
{
if (shuntMilliOhms > 0)
_shuntResistorUOhm = static_cast<uint32_t>(shuntMilliOhms * 1000.0f + 0.5f);
else
_shuntResistorUOhm = INA226_SHUNT_MICRO_OHMS;
}
else
configComplete = false;
if (getJsonValue(top[F("CurrentRange")], _currentRangeMa))
{
if (_currentRangeMa == 0 || _currentRangeMa > 20000)
_currentRangeMa = INA226_DEFAULT_CURRENT_RANGE;
}
else
configComplete = false;
if (!getJsonValue(top[F("CurrentOffset")], _currentOffsetMa))
_currentOffsetMa = INA226_CURRENT_OFFSET_MA; // Use compile-time default if missing from config
configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor);
configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange);
#ifndef WLED_DISABLE_MQTT
if (getJsonValue(top[F("MqttPublish")], tmpBool))
+1 -31
View File
@@ -18,7 +18,6 @@ The following settings can be configured in the Usermod Menu:
- **Decimals**: Number of decimals in the output.
- **ShuntResistor**: Shunt resistor value in milliohms. An R100 shunt resistor should be written as "100", while R010 should be "10".
- **CurrentRange**: Expected maximum current in milliamps (e.g., 5 A = 5000 mA).
- **CurrentOffset**: Current offset in milliamps, subtracted from raw readings. Useful for compensating a consistent bias in the sensor. Default is 0.
- **MqttPublish**: Enable or disable MQTT publishing.
- **MqttPublishAlways**: Publish always, regardless if there is a change.
- **MqttHomeAssistantDiscovery**: Enable Home Assistant discovery.
@@ -64,33 +63,4 @@ extends = env:esp32dev
custom_usermods = ${env:esp32dev.custom_usermods} INA226
build_flags = ${env:esp32dev.build_flags}
; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal
```
### Compile-time Defaults
Several parameters can be overridden at compile time via `-D` build flags. This is useful for setting board-specific defaults so the device works correctly on first boot without manual configuration.
| Build Flag | Default | Unit | Description |
|---|---|---|---|
| `INA226_ADDRESS` | `0x40` | — | I2C address of the INA226 |
| `INA226_SHUNT_MICRO_OHMS` | `1000000` | μΩ | Shunt resistor value (1 000 000 μΩ = 1 Ω) |
| `INA226_DEFAULT_CURRENT_RANGE` | `1000` | mA | Expected maximum current (1000 mA = 1 A) |
| `INA226_CURRENT_OFFSET_MA` | `0` | mA | Current offset subtracted from readings |
| `INA226_CHECK_INTERVAL_MS` | `60000` | ms | Default interval between readings on first boot |
| `INA226_ENABLED_DEFAULT` | `false` | — | Enable the usermod on first boot |
Example for a board with a 2.888 mΩ effective shunt, 10 A range, -118 mA offset, 1 second polling, and enabled by default:
```ini
[env:my_board]
extends = env:esp32dev
custom_usermods = ${env:esp32dev.custom_usermods} INA226
build_flags = ${env:esp32dev.build_flags}
-D INA226_ENABLED_DEFAULT=true
-D INA226_SHUNT_MICRO_OHMS=2888
-D INA226_DEFAULT_CURRENT_RANGE=10000
-D INA226_CURRENT_OFFSET_MA=-118
-D INA226_CHECK_INTERVAL_MS=1000
```
All compile-time defaults can still be changed at runtime through the Usermod settings page.
```
@@ -0,0 +1,6 @@
[env:ina226_example]
extends = env:esp32dev
custom_usermods = ${env:esp32dev.custom_usermods} INA226_v2
build_flags =
${env:esp32dev.build_flags}
; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal
@@ -1,5 +1,6 @@
; Options
; -------
; USERMOD_SN_PHOTORESISTOR - define this to have this user mod included wled00\usermods_list.cpp
; USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds
; USERMOD_SN_PHOTORESISTOR_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 20 seconds
; USERMOD_SN_PHOTORESISTOR_REFERENCE_VOLTAGE - the voltage supplied to the sensor, defaults to 5v
@@ -7,10 +8,9 @@
; USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE - the resistor size, defaults to 10000.0 (10K hms)
; USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE - the offset value to report on, defaults to 25
;
[env:usermod_sn_photoresistor_esp8266_2m]
extends = env:esp8266_2m
custom_usermods = ${env:esp8266_2m.custom_usermods} SN_Photoresistor
[env:usermod_sn_photoresistor_d1_mini]
extends = env:d1_mini
build_flags =
${env:esp8266_2m.build_flags}
-D USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL=60
lib_deps = ${env:esp8266_2m.lib_deps}
${common.build_flags_esp8266}
-D USERMOD_SN_PHOTORESISTOR
lib_deps = ${env.lib_deps}
@@ -1,4 +1,4 @@
{
"name": "ST7789_display",
"name:": "ST7789_display",
"build": { "libArchive": false }
}
@@ -0,0 +1,8 @@
[env:esp32dev]
build_flags = ${common.build_flags_esp32}
; PIN defines - uncomment and change, if needed:
; -D LEDPIN=2
-D BTNPIN=35
; -D IRPIN=4
; -D RLYPIN=12
; -D RLYMDE=1
@@ -0,0 +1,5 @@
; Options
; -------
; USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds
;
+42 -57
View File
@@ -140,7 +140,7 @@ static uint8_t binNum = 8; // Used to select the bin for FFT based bea
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // always use integer FFT on ESP32-S2 and ESP32-C3
#endif
#endif // UM_AUDIOREACTIVE_USE_ARDUINO_FFT
#endif
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
using FFTsampleType = float;
@@ -758,8 +758,6 @@ class AudioReactive : public Usermod {
private:
#ifdef ARDUINO_ARCH_ESP32
static constexpr uint8_t SR_DMTYPE_NETWORK_ONLY = 254;
#ifndef AUDIOPIN
int8_t audioPin = -1;
#else
@@ -881,9 +879,6 @@ class AudioReactive : public Usermod {
#endif
static const char _digitalmic[];
static const char _addPalettes[];
static const char _palName0[];
static const char _palName1[];
static const char _palName2[];
static const char UDP_SYNC_HEADER[];
static const char UDP_SYNC_HEADER_v1[];
@@ -1300,12 +1295,7 @@ class AudioReactive : public Usermod {
size_t packetSize = fftUdp.parsePacket();
#ifdef ARDUINO_ARCH_ESP32
if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET)))
#if ESP_IDF_VERSION_MAJOR < 5
fftUdp.flush(); // discard invalid packets (too small or too big) - only works on esp32
#else
fftUdp.clear(); // function was renamed in newer frameworks
#endif
if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET))) fftUdp.flush(); // discard invalid packets (too small or too big) - only works on esp32
#endif
if ((packetSize > 5) && (packetSize <= UDPSOUND_MAX_PACKET)) {
//DEBUGSR_PRINTLN("Received UDP Sync Packet");
@@ -1449,7 +1439,7 @@ class AudioReactive : public Usermod {
break;
#endif
case SR_DMTYPE_NETWORK_ONLY: // dummy "network receive only" mode
case 254: // dummy "network receive only" mode
if (audioSource) delete audioSource; audioSource = nullptr;
disableSoundProcessing = true;
audioSyncEnabled = 2; // force udp sound receive mode
@@ -1466,25 +1456,19 @@ class AudioReactive : public Usermod {
}
delay(250); // give microphone enough time to initialise
if (!audioSource && (dmType != SR_DMTYPE_NETWORK_ONLY)) enabled = false;// audio failed to initialise
if (!audioSource && (dmType != 254)) enabled = false;// audio failed to initialise
#endif
if (enabled) onUpdateBegin(false); // create FFT task, and initialize network
#ifdef ARDUINO_ARCH_ESP32
if (audioSource && FFT_Task == nullptr) enabled = false; // FFT task creation failed
if (FFT_Task == nullptr) enabled = false; // FFT task creation failed
if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync
#ifdef WLED_DEBUG
#define AR_INIT_DEBUG_PRINT DEBUG_PRINTLN
#else
#define AR_INIT_DEBUG_PRINT DEBUGSR_PRINTLN
#endif
if (dmType == SR_DMTYPE_NETWORK_ONLY) {
AR_INIT_DEBUG_PRINT(F("AR: No sound input driver configured - network receive only."));
} else {
AR_INIT_DEBUG_PRINT(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
}
#undef AR_INIT_DEBUG_PRINT
#ifdef WLED_DEBUG
DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
#else
DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
#endif
disableSoundProcessing = true;
}
#endif
@@ -1538,9 +1522,14 @@ class AudioReactive : public Usermod {
// We cannot wait indefinitely before processing audio data
if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice
// suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET, DDP, DMX)
// exception: sound input is still needed when useMainSegmentOnly - other segments are still running with local input.
if (realtimeMode && !realtimeOverride && !useMainSegmentOnly) {
// suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET)
if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed
&&( (realtimeMode == REALTIME_MODE_GENERIC)
||(realtimeMode == REALTIME_MODE_E131)
||(realtimeMode == REALTIME_MODE_UDP)
||(realtimeMode == REALTIME_MODE_ADALIGHT)
||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG)
if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled"
DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended."));
@@ -1617,11 +1606,7 @@ class AudioReactive : public Usermod {
have_new_sample = receiveAudioData();
if (have_new_sample) last_UDPTime = millis();
#ifdef ARDUINO_ARCH_ESP32
#if ESP_IDF_VERSION_MAJOR < 5
else fftUdp.flush(); // Flush udp input buffers if we haven't read it - avoids hickups in receive mode. Does not work on 8266.
#else
else fftUdp.clear(); // function was renamed in newer frameworks
#endif
#endif
lastTime = millis();
}
@@ -1726,7 +1711,7 @@ class AudioReactive : public Usermod {
);
}
micDataReal = 0.0f; // just to be sure
if (enabled) disableSoundProcessing = false; // allows FFT_Task to run at least once, even when loop() might disable again
if (enabled) disableSoundProcessing = false;
updateIsRunning = init;
}
@@ -1966,10 +1951,14 @@ class AudioReactive : public Usermod {
}
#endif
}
if (palettes > 0 && root.containsKey(F("rmcpal"))) {
// handle removal of custom palettes from JSON call so we don't break things
removeAudioPalettes();
}
}
void onStateChange(uint8_t callMode) override {
if (initDone && enabled && addPalettes && palettes==0) {
if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()<WLED_MAX_CUSTOM_PALETTES) {
// if palettes were removed during JSON call re-add them
createAudioPalettes();
}
@@ -2134,9 +2123,6 @@ class AudioReactive : public Usermod {
uiScript.print(F("addOption(dd,'Generic PDM',5);"));
#endif
uiScript.print(F("addOption(dd,'ES8388',6);"));
uiScript.print(F("addOption(dd,'None - network receive only',"));
uiScript.print(SR_DMTYPE_NETWORK_ONLY);
uiScript.print(F(");"));
uiScript.print(F("dd=addDropdown(ux,'config:AGC');"));
uiScript.print(F("addOption(dd,'Off',0);"));
@@ -2201,21 +2187,24 @@ class AudioReactive : public Usermod {
void AudioReactive::removeAudioPalettes(void) {
DEBUG_PRINTLN(F("Removing audio palettes."));
palettes -= (int8_t)removeUsermodPalettes(_name);
if (palettes < 0) palettes = 0; // safeguard
while (palettes>0) {
customPalettes.pop_back();
DEBUG_PRINTLN(palettes);
palettes--;
}
DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size());
}
void AudioReactive::createAudioPalettes(void) {
DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size());
if (palettes) return;
DEBUG_PRINTLN(F("Adding audio palettes."));
static const char *const palNames[MAX_PALETTES] PROGMEM = {_palName0, _palName1, _palName2};
for (int i=0; i<MAX_PALETTES; i++) {
if (usermodPalettes.size() < WLED_MAX_USERMOD_PALETTES) {
usermodPalettes.push_back({CRGBPalette16(CRGB(BLACK)), _name, (uint8_t)i, palNames[i]}); // start black, filled each loop by fillAudioPalettes()
for (int i=0; i<MAX_PALETTES; i++)
if (customPalettes.size() < WLED_MAX_CUSTOM_PALETTES) {
customPalettes.push_back(CRGBPalette16(CRGB(BLACK)));
palettes++;
DEBUG_PRINTLN(palettes);
} else break;
}
}
// credit @netmindz ar palette, adapted for usermod @blazoncek
@@ -2249,10 +2238,9 @@ CRGB AudioReactive::getCRGBForBand(int x, int pal) {
void AudioReactive::fillAudioPalettes() {
if (!palettes) return;
// Scan by name pointer identity to find the palettes we added, palIndex = 0/1/2... selects the getCRGBForBand variant, matching how the entries were created.
for (auto &ump : usermodPalettes) {
if (ump.name != _name) continue;
const int pal = ump.palIndex;
size_t lastCustPalette = customPalettes.size();
if (int(lastCustPalette) >= palettes) lastCustPalette -= palettes;
for (int pal=0; pal<palettes; pal++) {
uint8_t tcp[16]; // Needs to be 4 times however many colors are being used.
// 3 colors = 12, 4 colors = 16, etc.
@@ -2260,26 +2248,26 @@ void AudioReactive::fillAudioPalettes() {
tcp[1] = 0;
tcp[2] = 0;
tcp[3] = 0;
CRGB rgb = getCRGBForBand(1, pal);
tcp[4] = 1; // anchor of first color
tcp[5] = rgb.r;
tcp[6] = rgb.g;
tcp[7] = rgb.b;
rgb = getCRGBForBand(128, pal);
tcp[8] = 128;
tcp[9] = rgb.r;
tcp[10] = rgb.g;
tcp[11] = rgb.b;
rgb = getCRGBForBand(255, pal);
tcp[12] = 255; // anchor of last color - must be 255
tcp[13] = rgb.r;
tcp[14] = rgb.g;
tcp[15] = rgb.b;
ump.palette.loadDynamicGradientPalette(tcp);
customPalettes[lastCustPalette+pal].loadDynamicGradientPalette(tcp);
}
}
@@ -2294,10 +2282,7 @@ const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel";
const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
#endif
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic";
const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes";
const char AudioReactive::_palName0[] PROGMEM = "Ratio";
const char AudioReactive::_palName1[] PROGMEM = "Hue";
const char AudioReactive::_palName2[] PROGMEM = "Spectrum";
const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes";
const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure
const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature
@@ -13,7 +13,7 @@ board_build.partitions = ${esp32.large_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"T-QT-PRO-8MB_dice\"
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=T-QT-PRO-8MB
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
@@ -75,7 +75,7 @@ board_build.partitions = ${esp32.large_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_qspi_dice\"
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_qspi
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
@@ -105,7 +105,7 @@ lib_deps = ${esp32s3.lib_deps}
# https://github.com/wled-dev/WLED/issues/1382
; [env:esp32dev_dice]
; extends = env:esp32dev
; build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_dice\"
; build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32
; ; Enable Pixels dice mod
; -D USERMOD_PIXELS_DICE_TRAY
; lib_deps = ${esp32.lib_deps}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "pov_display",
"name:": "pov_display",
"build": { "libArchive": false},
"platforms": ["espressif32"]
}
-6
View File
@@ -143,13 +143,7 @@ void ShtUsermod::appendDeviceToMqttDiscoveryMessage(JsonDocument& root) {
device[F("ids")] = escapedMac.c_str();
device[F("name")] = serverDescription;
device[F("sw")] = versionString;
// AI: below section was generated by an AI
#ifdef ARDUINO_ARCH_ESP32
device[F("mdl")] = ESP.getChipModel();
#else
device[F("mdl")] = F("ESP8266");
#endif
// AI: end
device[F("mf")] = F("espressif");
}
@@ -2,10 +2,10 @@
default_envs = esp32dev_fld
[env:esp32dev_fld]
extends = env:esp32dev
custom_usermods = ${env:esp32dev.custom_usermods} four_line_display_ALT
extends = env:esp32dev_V4
custom_usermods = ${env:esp32dev_V4.custom_usermods} four_line_display_ALT
build_flags =
${env:esp32dev.build_flags}
${env:esp32dev_V4.build_flags}
-D FLD_TYPE=SH1106
-D I2CSCLPIN=27
-D I2CSDAPIN=26
@@ -2,10 +2,10 @@
default_envs = esp32dev_re
[env:esp32dev_re]
extends = env:esp32dev
custom_usermods = ${env:esp32dev.custom_usermods} rotary_encoder_ui_ALT
extends = env:esp32dev_V4
custom_usermods = ${env:esp32dev_V4.custom_usermods} rotary_encoder_ui_ALT
build_flags =
${env:esp32dev.build_flags}
${env:esp32dev_V4.build_flags}
-D USERMOD_ROTARY_ENCODER_GPIO=INPUT
-D ENCODER_DT_PIN=21
-D ENCODER_CLK_PIN=23
+16 -19
View File
@@ -701,9 +701,7 @@ void dissolve(uint32_t color) {
unsigned i = hw_random16(SEGLEN);
if (SEGENV.aux0) { //dissolve to primary/palette
if (pixels[i] == SEGCOLOR(1)) {
uint32_t c = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color;
if (SEGMENT.check2 && c == SEGCOLOR(1)) c ^= 0x00000001; // change the color slightly so the effect doesn't get stuck in Complete mode if color is same as bkg color
pixels[i] = c;
pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color;
break; //only spawn 1 new pixel per frame
}
} else { //dissolve to secondary
@@ -2345,7 +2343,7 @@ void mode_colortwinkle() {
unsigned index = i >> 3;
unsigned bitNum = i & 0x07;
bitWrite(SEGENV.data[index], bitNum, true);
SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, hw_random8(), gamma8inv(64), NOBLEND)); // note on gamma8inv: inverting results in non-linear brightness fade as originally designed
SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, hw_random8(), 64, NOBLEND));
break; //only spawn 1 new pixel per frame per 50 LEDs
}
}
@@ -2616,7 +2614,7 @@ static CRGBW twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
unsigned hue = slowcycle8 - salt;
CRGBW c;
if (bright > 0) {
c = ColorFromPalette(SEGPALETTE, hue, gamma8inv(bright), NOBLEND); // note on gamma8inv: inverting results in non-linear brightness fade as originally designed
c = ColorFromPalette(SEGPALETTE, hue, bright, NOBLEND);
if (!SEGMENT.check1) {
// This code takes a pixel, and if its in the 'fading down'
// part of the cycle, it adjusts the color a little bit like the
@@ -2651,18 +2649,19 @@ static void twinklefox_base(bool cat)
if (SEGMENT.speed > 100) SEGENV.aux0 = 3 + ((255 - SEGMENT.speed) >> 3);
else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1);
// Set up the background color, "bg". Note: using gamma invert for brightness as the FX was written without any gamma correction, it will dim down too much now
// Set up the background color, "bg".
CRGBW bg = SEGCOLOR(1);
unsigned bglight = bg.getRGBaverage();
unsigned bglight = bg.getAverageLight();
if (bglight > 64) {
bg = color_fade(bg, gamma8inv(16), true); // very bright, so scale to 1/16th
bg = color_fade(bg, 16, true); // very bright, so scale to 1/16th
} else if (bglight > 16) {
bg = color_fade(bg, gamma8inv(64), true); // not that bright, so scale to 1/4
bg = color_fade(bg, 64, true); // not that bright, so scale to 1/4th
} else {
bg = color_fade(bg, gamma8inv(86), true); // dim, scale to 1/3rd
bg = color_fade(bg, 86, true); // dim, scale to 1/3rd.
}
bg = gamma32inv(bg); // need to invert gamma as the FX was written without any gamma correction and it will dim down too much otherwise
bglight = bg.getRGBaverage(); // update after scaling
unsigned backgroundBrightness = bg.getAverageLight();
for (unsigned i = 0; i < SEGLEN; i++) {
@@ -2679,8 +2678,8 @@ static void twinklefox_base(bool cat)
// on the "brightness = f( time )" idea.
CRGBW c = twinklefox_one_twinkle(myclock30, myunique8, cat);
unsigned cbright = c.getRGBaverage();
int deltabright = cbright - bglight;
unsigned cbright = c.getAverageLight();
int deltabright = cbright - backgroundBrightness;
if (deltabright >= 32 || (bg==0)) {
// If the new pixel is significantly brighter than the background color,
// use the new color.
@@ -5452,7 +5451,6 @@ void mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://na
}
}
}
return;
}
// Repeat detection
@@ -6836,7 +6834,7 @@ void mode_gravcenter_base(unsigned mode) {
uint8_t gravity = 8 - SEGMENT.speed/32;
int offset = 1;
if(mode == 2) offset = 0; // Gravimeter
if (tempsamp >= gravcen->topLED + offset) gravcen->topLED = tempsamp-offset;
if (tempsamp >= gravcen->topLED) gravcen->topLED = tempsamp-offset;
else if (gravcen->gravityCounter % gravity == 0) gravcen->topLED--;
if(mode == 1) { //Gravcentric
@@ -8732,7 +8730,7 @@ void mode_particleperlin(void) {
PartSys->update(); // update and render
}
static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5";
static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5,o3=1";
/*
Particle smashing down like meteors and exploding as they hit the ground, has many parameters to play with
@@ -9838,7 +9836,6 @@ void mode_particleFireworks1D(void) {
PartSys->setColorByPosition(false); // disable
for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles
int idx = PartSys->sprayEmit(PartSys->sources[0]); // emit a particle
if (idx < 0) break; // no more particles available
if(SEGMENT.custom3 > 23) {
if(SEGMENT.custom3 == 31) { // highest slider value
PartSys->setColorByAge(SEGMENT.check1); // color by age if colorful mode is enabled
@@ -9863,7 +9860,7 @@ void mode_particleFireworks1D(void) {
PartSys->applyFriction(1); // apply friction to all particles
PartSys->update(); // update and render
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
if (PartSys->particles[i].ttl > 20) PartSys->particles[i].ttl -= 20; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan
else PartSys->particles[i].ttl = 0;
@@ -9913,7 +9910,7 @@ void mode_particleSparkler(void) {
PartSys->sources[i].source.ttl = 400; // replenish its life (setting it perpetual uses more code)
PartSys->sources[i].sat = SEGMENT.custom1; // color saturation
if (SEGMENT.speed == 255) // random position at highest speed setting
PartSys->sources[i].source.x = hw_random(PartSys->maxX);
PartSys->sources[i].source.x = hw_random16(PartSys->maxX);
else
PartSys->particleMoveUpdate(PartSys->sources[i].source, PartSys->sources[i].sourceFlags, &sparklersettings); //move sparkler
}
-1
View File
@@ -879,7 +879,6 @@ class WS2812FX {
printSize(), // prints memory usage for strip components
#endif
finalizeInit(), // initialises strip components
updatePixelBuffer(), // (re)allocate memory for _pixels[]
service(), // executes effect functions when due and calls strip.show()
setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K)
setBrightness(uint8_t b, bool direct = false), // sets strip brightness
+17 -100
View File
@@ -228,19 +228,12 @@ void Segment::resetIfRequired() {
void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
// there is one randomly generated palette (1) followed by 4 palettes created from segment colors (2-5)
// those are followed by 7 fastled palettes (6-12) and 59 gradient palettes (13-71)
// then come user custom palettes (IDs <=200) and usermod palettes (IDs 201-255), both growing downward from their respective base IDs
// then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
// palette 0 is a varying palette depending on effect and may be replaced by segment's color if so
// instructed in color_from_palette()
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode(), differs depending on effect
const int umCount = usermodPalettes.size();
const int custCount = customPalettes.size();
if (pal >= FIXED_PALETTE_COUNT) {
if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range (IDs 201-255)
if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= umCount) pal = 0;
} else { // custom range
if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= custCount) pal = 0;
}
}
if (pal >= FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette
//default palette. Differs depending on effect
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
switch (pal) {
case 0: //default palette. Exceptions for specific effects above
targetPalette = PartyColors_gc22;
@@ -274,10 +267,8 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
}
break;}
default: //progmem palettes
if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod palette
targetPalette = usermodPalettes[WLED_USERMOD_PALETTE_ID_BASE - pal].palette;
} else if (pal >= FIXED_PALETTE_COUNT) { // user custom palette
targetPalette = customPalettes[WLED_CUSTOM_PALETTE_ID_BASE - pal];
if (pal > 255 - customPalettes.size()) {
targetPalette = customPalettes[255-pal]; // we checked bounds above
} else if (pal < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) { // palette 6 - 12, fastled palettes
targetPalette = *fastledPalettes[pal - DYNAMIC_PALETTE_COUNT];
} else {
@@ -594,13 +585,7 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
}
Segment &Segment::setPalette(uint8_t pal) {
if (pal >= FIXED_PALETTE_COUNT) {
if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range
if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= (int)usermodPalettes.size()) pal = 0;
} else { // custom range
if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= (int)customPalettes.size()) pal = 0;
}
}
if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette
if (pal != palette) {
//DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change (no need to copy segment)
@@ -1117,7 +1102,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) const {
* Rotates the color in HSV space, where pos is H. (0=0deg, 256=360deg)
*/
uint32_t Segment::color_wheel(uint8_t pos) const {
if (palette) return color_from_palette(pos, false, true, 0); // color_wheel is a continuous (moving) wheel, so wrap end->start (restores pre-0.16 behaviour)
if (palette) return color_from_palette(pos, false, false, 0); // only wrap if "always wrap" is set
uint8_t w = W(getCurrentColor(0));
CRGBW rgb;
rgb = CHSV32(static_cast<uint16_t>(pos << 8), 255, 255);
@@ -1272,17 +1257,11 @@ void WS2812FX::finalizeInit() {
deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
// allocate frame buffer after matrix has been set up (gaps!)
updatePixelBuffer();
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), getFreeHeapSize());
}
// update global _pixels[] buffer to match getLengthTotal() note: if allocation fails, WLED will not render anything
void WS2812FX::updatePixelBuffer() {
uint32_t requiredMem = getLengthTotal() * sizeof(uint32_t);
p_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
// use PSRAM if available: there is no measurable perfomance impact between PSRAM and DRAM on S2/S3 with QSPI PSRAM for this buffer
_pixels = static_cast<uint32_t*>(allocate_buffer(requiredMem, BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), requiredMem);
_pixels = static_cast<uint32_t*>(allocate_buffer(getLengthTotal() * sizeof(uint32_t), BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), getFreeHeapSize());
}
void WS2812FX::service() {
@@ -1388,7 +1367,6 @@ static uint8_t _dummy (uint8_t a, uint8_t b) { return a; } // dummy (same as
void WS2812FX::blendSegment(const Segment &topSegment) const {
typedef uint8_t(*FuncType)(uint8_t, uint8_t);
// function pointer array: fill with _dummy if using special case: avoid OOB access and always provide a valid path
// note: making the function array static const uses more ram and comes at no significant speed gain
FuncType funcs[] = {
_dummy, _dummy, _dummy, _subtract,
_difference, _average, _dummy, _divide,
@@ -1420,71 +1398,14 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
const size_t matrixSize = Segment::maxWidth * Segment::maxHeight;
const size_t startIndx = XY(topSegment.start, topSegment.startY);
const size_t stopIndx = startIndx + length;
const unsigned progress = topSegment.progress();
const unsigned progInv = 0xFFFFU - progress;
uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE
uint8_t cct = topSegment.currentCCT();
if (gammaCorrectCol) opacity = gamma8inv(opacity); // use inverse gamma on brightness for correct color scaling after gamma correction (see #5343 for details)
const Segment *segO = topSegment.getOldSegment();
const bool hasGrouping = topSegment.groupLength() != 1;
Segment::setClippingRect(0, 0); // disable clipping by default
// fast path: handle the default case - no transitions, no grouping/spacing, no mirroring, no CCT
if (!segO && blendingStyle == TRANSITION_FADE && !hasGrouping && !topSegment.mirror && !topSegment.mirror_y) {
if (isMatrix && stopIndx <= matrixSize && !_pixelCCT) {
#ifndef WLED_DISABLE_2D
// Calculate pointer steps to avoid 'if' and 'XY()' inside loops
int x_inc = 1;
int y_inc = Segment::maxWidth;
int start_offset = XY(topSegment.start, topSegment.startY);
// adjust starting position and steps based on Reverse/Transpose
// note: transpose is handled in separate loop so it is still fast and no branching is needed in default path
if (!topSegment.transpose) {
if (topSegment.reverse) { start_offset += (width - 1); x_inc = -1; }
if (topSegment.reverse_y) { start_offset += (height - 1) * Segment::maxWidth; y_inc = -Segment::maxWidth; }
for (int y = 0; y < height; y++) {
uint32_t* pRow = &_pixels[start_offset + y * y_inc];
const int y_width = y * width;
for (int x = 0; x < width; x++) {
uint32_t* p = pRow + x * x_inc;
uint32_t c_a = topSegment.getPixelColorRaw(x + y_width);
*p = color_blend(*p, segblend(c_a, *p), opacity);
}
}
} else { // transposed
for (int y = 0; y < height; y++) {
const int px = topSegment.reverse ? (height - y - 1) : y; // source pixel: swap y into x, reverse if needed
for (int x = 0; x < width; x++) {
const int py = topSegment.reverse_y ? (width - x - 1) : x; // source pixel: swap x into y, reverse if needed
const uint32_t c_a = topSegment.getPixelColorRaw(px + py * height); // height = virtual width
const size_t idx = XY(topSegment.start + x, topSegment.startY + y); // write logical (non swapped) pixel coordinate
_pixels[idx] = color_blend(_pixels[idx], segblend(c_a, _pixels[idx]), opacity);
}
}
}
return;
#endif
} else if (!isMatrix) {
// 1D fast path, include CCT as it is more common on 1D setups
uint32_t* strip = _pixels;
int start = topSegment.start;
int off = topSegment.offset;
for (int i = 0; i < length; i++) {
uint32_t c_a = topSegment.getPixelColorRaw(i);
int p = topSegment.reverse ? (length - i - 1) : i;
int idx = start + p + off;
if (idx >= topSegment.stop) idx -= length;
strip[idx] = color_blend(strip[idx], segblend(c_a, strip[idx]), opacity);
if (_pixelCCT) _pixelCCT[idx] = cct;
}
return;
}
}
// slow path: handle transitions, grouping/spacing, segments with clipping and CCT pixels
Segment::setClippingRect(0, 0); // disable clipping by default
const unsigned progress = topSegment.progress();
const unsigned progInv = 0xFFFFU - progress;
const unsigned dw = (blendingStyle==TRANSITION_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1;
const unsigned dh = (blendingStyle==TRANSITION_OUTSIDE_IN ? progInv : progress) * height / 0xFFFFU + 1;
const unsigned orgBS = blendingStyle;
@@ -1545,6 +1466,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
#ifndef WLED_DISABLE_2D
const int nCols = topSegment.virtualWidth();
const int nRows = topSegment.virtualHeight();
const Segment *segO = topSegment.getOldSegment();
const int oCols = segO ? segO->virtualWidth() : nCols;
const int oRows = segO ? segO->virtualHeight() : nRows;
@@ -1640,8 +1562,8 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
}
#endif
} else {
// 1D Slow Path
const int nLen = topSegment.virtualLength();
const Segment *segO = topSegment.getOldSegment();
const int oLen = segO ? segO->virtualLength() : nLen;
const auto setMirroredPixel = [&](int i, uint32_t c, uint8_t o) {
@@ -1721,7 +1643,7 @@ void WS2812FX::show() {
if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) {
// clear frame buffer
memset(_pixels, 0, sizeof(uint32_t) * totalLen);
for (size_t i = 0; i < totalLen; i++) _pixels[i] = BLACK; // memset(_pixels, 0, sizeof(uint32_t) * getLengthTotal());
// blend all segments into (cleared) buffer
for (Segment &seg : _segments) if (seg.isActive() && (seg.on || seg.isInTransition())) {
blendSegment(seg); // blend segment's buffer into frame buffer
@@ -2086,13 +2008,10 @@ bool WS2812FX::deserializeMap(unsigned n) {
customMappingSize = 0; // prevent use of mapping if anything goes wrong
currentLedmap = 0;
if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI)
uint32_t lengthTotalBefore = strip.getLengthTotal();
if (!isFile && n==0 && isMatrix) {
// 2D panel support creates its own ledmap (on the fly) if a ledmap.json does not exist
setUpMatrix();
if (strip.getLengthTotal() != lengthTotalBefore)
strip.updatePixelBuffer(); // allocate _pixels[] to match new length
return false;
}
@@ -2167,8 +2086,6 @@ bool WS2812FX::deserializeMap(unsigned n) {
}
releaseJSONBufferLock();
if (strip.getLengthTotal() != lengthTotalBefore)
strip.updatePixelBuffer(); // allocate _pixels[] to match new length
return (customMappingSize > 0);
}
+6 -26
View File
@@ -813,7 +813,6 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
// mxconfig.driver = HUB75_I2S_CFG::ICN2038S; // experimental - use specific shift register driver
// mxconfig.driver = HUB75_I2S_CFG::FM6124; // try this driver in case you panel stays dark, or when colors look too pastel
// Other possible shiftreg drivers: HUB75_I2S_CFG::FM6126A, HUB75_I2S_CFG::ICN2038S, HUB75_I2S_CFG::MBI5124, HUB75_I2S_CFG::DP3246
// mxconfig.latch_blanking = 3;
// mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // experimental - 5MHZ should be enugh, but colours looks slightly better at 10MHz
@@ -825,13 +824,8 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
mxconfig.chain_length = max((uint8_t) 1, min(chainLength, (uint8_t) 4));
if (mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) {
#if defined(BOARD_HAS_PSRAM) // limitation to one panel only applies to boards without PSRAM
if (!psramFound() || ESP.getPsramSize() == 0) // PSRAM sanity check
#endif
{
DEBUGBUS_PRINTLN(F("WARNING, only single panel can be used of 64 pixel boards due to memory"));
mxconfig.chain_length = 1;
}
DEBUGBUS_PRINTLN(F("WARNING, only single panel can be used of 64 pixel boards due to memory"));
mxconfig.chain_length = 1;
}
if (bc.type == TYPE_HUB75MATRIX_HS) {
@@ -841,7 +835,6 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
_isVirtual = true;
mxconfig.mx_width = min((uint8_t) 64, panelWidth) * 2;
mxconfig.mx_height = min((uint8_t) 64, panelHeight) / 2;
mxconfig.driver = HUB75_I2S_CFG::FM6124; // use FM6124 for "outdoor" 4-scan panels - workaround until we can make the driver user-configurable
} else {
DEBUGBUS_PRINTLN("Unknown type");
return;
@@ -865,14 +858,6 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - Matrix Portal S3 config");
mxconfig.gpio = { 42, 41, 40, 38, 39, 37, 45, 36, 48, 35, 21, 47, 14, 2 };
#elif defined(HD_WF2_PINOUT) // Huidu HD-WF2 ESP32-S3 (no PSRAM)
// https://www.aliexpress.com/item/1005002258734810.html
// https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA/issues/433
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - HD-WF2 S3 config");
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
mxconfig.gpio = { 2, 6, 10, 3, 7, 11, 39, 38, 37, 36, 21, 33, 35, 34 };
#elif defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)// ESP32-S3 with PSRAM
#if defined(MOONHUB_S3_PINOUT)
@@ -881,12 +866,6 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
mxconfig.gpio = { 1, 5, 6, 7, 13, 9, 16, 48, 47, 21, 38, 8, 4, 18 };
#elif defined(WAVESHARE_S3_PINOUT)
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - Waveshare S3 with PSRAM, Waveshare pinout");
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
mxconfig.gpio = {4, 5, 6, 7, 15, 16, 18, 8, 3, 42, 9, 40, 2, 41};
#else
DEBUGBUS_PRINTLN("MatrixPanel_I2S_DMA - S3 with PSRAM");
// HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
@@ -1003,8 +982,10 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
}
setBitArray(_ledsDirty, _len, false); // reset dirty bits
// create LEDs buffer (initialized to BLACK), prefer DRAM if enough heap is available (faster in case global _pixels buffer is in PSRAM as not both will fit the cache)
_ledBuffer = static_cast<CRGB*>(allocate_buffer(_len * sizeof(CRGB), BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR));
if (mxconfig.double_buff == false) {
// create LEDs buffer (initialized to BLACK), prefer DRAM if enough heap is available (faster in case global _pixels buffer is in PSRAM as not both will fit the cache)
_ledBuffer = static_cast<CRGB*>(allocate_buffer(_len * sizeof(CRGB), BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR));
}
}
PANEL_CHAIN_TYPE chainType = CHAIN_NONE; // default for quarter-scan panels that do not use chaining
@@ -1097,7 +1078,6 @@ uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const {
void BusHub75Matrix::setBrightness(uint8_t b) {
_bri = b;
if (!_valid || !display) return;
display->setBrightness(_bri);
}
+1 -1
View File
@@ -171,7 +171,7 @@ class Bus {
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 5 : is2Pin(type) + 1; } // credit @PaoloTK; for HUB75 the 5 slots store config params (panelW, panelH, chain, rows, cols), not GPIO pins
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 5 : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static constexpr bool hasRGB(uint8_t type) {
return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF);
+11 -21
View File
@@ -30,10 +30,8 @@ static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTy
// Pins provided < pins required -> always invalid
// Pins provided = pins required -> always valid
// Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated
// HUB75 types use their pin slots for config params, not GPIO - skip GPIO pin validation for them
return Bus::isHub75(types[numTypes-1]) ? true :
(sumPinsRequired(types, numTypes) > numPins) ? false :
(numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0;
return (sumPinsRequired(types, numTypes) > numPins) ? false :
(numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0;
}
@@ -49,7 +47,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
//int rev_major = doc["rev"][0]; // 1
//int rev_minor = doc["rev"][1]; // 0
long vid = doc[F("vid")] | VERSION; // 2605010 note: "vid" can be used to detect an update from older versions but only on first call, it is written to the new VID after buses are initialized
//long vid = doc[F("vid")]; // 2010020
JsonObject id = doc["id"];
getStringFromJson(cmDNS, id[F("mdns")], 33);
@@ -86,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"];
@@ -695,18 +694,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonArray timersArray = tm["ins"];
if (!timersArray.isNull()) {
clearTimers();
bool legacySunriseLoaded = false; // migration flag: pre 16.0 used hour=255 for both sunrise & sunset (type determined by array position)
for (JsonObject timer : timersArray) {
uint8_t h = timer[F("hour")] | 0;
// legacy migration for pre 16.0 (vid < 2605010): first occurrence = sunrise, second occurrence = sunset
if (vid < 2605010 && h == 255) {
if (legacySunriseLoaded) {
h = TH_SUNSET; // second "255" entry is actually sunset
} else {
legacySunriseLoaded = true;
}
}
int8_t m = timer[F("min")] | 0;
uint8_t p = timer[F("macro")] | 0;
uint8_t dow = timer[F("dow")] | 127;
@@ -802,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>();
@@ -1275,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;
}
@@ -1285,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);
+1 -13
View File
@@ -269,10 +269,7 @@ void loadCustomPalettes() {
char fileName[32];
sprintf_P(fileName, PSTR("/palette%d.json"), index);
if (WLED_FS.exists(fileName)) {
// add gray placeholders to preserve palette IDs for subsequent slots (is omitted in UI but shown in cpal.htm)
for (unsigned g = 0; g < emptyPaletteGap; g++)
customPalettes.push_back(CRGBPalette16(CRGB(128, 128, 128)));
emptyPaletteGap = 0; // reset gap counter if file exists
emptyPaletteGap = 0; // reset gap counter if file exists
DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName);
if (readObjectFromFile(fileName, nullptr, &pDoc)) {
JsonArray pal = pDoc[F("palette")];
@@ -312,15 +309,6 @@ void loadCustomPalettes() {
}
}
size_t removeUsermodPalettes(const char *name) {
size_t before = usermodPalettes.size();
for (int i = usermodPalettes.size() - 1; i >= 0; i--) {
if (usermodPalettes[i].name == name)
usermodPalettes.erase(usermodPalettes.begin() + i);
}
return before - usermodPalettes.size();
}
// convert HSV (16bit hue) to RGB (32bit with white = 0), optimized for speed
WLED_O2_ATTR void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb) {
unsigned p, q, t;
+1 -17
View File
@@ -61,20 +61,9 @@ void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange,int32_t valueC
[[gnu::hot, gnu::pure]] uint32_t ColorFromPalette(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette();
// Palette registered by a usermod at fixed IDs (255, 254, 253... 201).
// Display name is "name: palName" (if palName non-null) or falls back to "name index" (e.g. "AudioReactive 1"), see util.cpp
struct UsermodPalette {
CRGBPalette16 palette;
const char *name; // PROGMEM base name string (must not be nullptr), this name is used in removeUsermodPalettes()
uint8_t palIndex; // index of the palette for a usermod
const char *palName; // optional PROGMEM display name; if set, shown as "name: palName" (e.g. "AudioReactive: Audio Responsive Hue"), otherwise falls back to "name index"
};
void loadCustomPalettes();
size_t removeUsermodPalettes(const char *name); // remove all entries from usermodPalettes whose name pointer matches 'name'
extern std::vector<CRGBPalette16> customPalettes;
extern std::vector<UsermodPalette> usermodPalettes;
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodPalettes.size() + customPalettes.size(); }
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); }
void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb);
void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb);
@@ -198,11 +187,6 @@ struct CRGBW {
uint8_t getAverageLight() const {
return (r + g + b + w) >> 2;
}
// get the average of the R, G, B values
uint8_t getRGBaverage() const {
return ((r + g + b) * 21846) >> 16; // x*21846>>16 is equal to "divide by 3"
}
};
inline CHSV32::CHSV32(const CRGBW& rgb) {
+5 -22
View File
@@ -10,16 +10,8 @@ constexpr size_t FASTLED_PALETTE_COUNT = 7; // 6-12 = sizeof(fastledPalettes)
constexpr size_t GRADIENT_PALETTE_COUNT = 59; // 13-72 = sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]);
constexpr size_t DYNAMIC_PALETTE_COUNT = 6; // 0- 5 = dynamic palettes (0=default(virtual),1=random,2=primary,3=primary+secondary,4=primary+secondary+tertiary,5=primary+secondary(+tertiary if not black)
constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT + GRADIENT_PALETTE_COUNT; // total number of fixed palettes
// Palette ID space layout (palette IDs are uint8_t, 0-255):
// 0 .. FIXED_PALETTE_COUNT-1 : fixed built-in palettes
// 72 .. WLED_CUSTOM_PALETTE_ID_BASE(200) : user custom palettes (index 0 = ID 200, growing downward)
// 201.. WLED_USERMOD_PALETTE_ID_BASE(255): usermod-registered palettes (index 0 = ID 255, growing downward)
constexpr uint8_t WLED_USERMOD_PALETTE_ID_BASE = 255; // highest ID for usermod palettes
constexpr uint8_t WLED_CUSTOM_PALETTE_ID_BASE = 200; // highest ID for user custom palettes
constexpr size_t WLED_MAX_USERMOD_PALETTES = WLED_USERMOD_PALETTE_ID_BASE - WLED_CUSTOM_PALETTE_ID_BASE; // 55 slots (IDs 201-255)
#ifndef ESP8266
#define WLED_MAX_CUSTOM_PALETTES (WLED_CUSTOM_PALETTE_ID_BASE - FIXED_PALETTE_COUNT + 1) // 129 slots (IDs 72-200)
#define WLED_MAX_CUSTOM_PALETTES (255 - FIXED_PALETTE_COUNT) // allow up to 255 total palettes, user is warned about stability issues when adding more than 10
#else
#define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10
#endif
@@ -165,14 +157,9 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define WLED_MAX_PANELS 18 // must not be more than 32
// Usermod IDs
// A unique ID is only required when a usermod needs one or more of:
// 1. Inter-usermod communication: UsermodManager::lookup(mod_id) or getUMData(..., mod_id)
// 2. Pin ownership via pinManager: PinOwner enum entries map to these IDs (see pin_manager.h)
// 3. Identification in JSON info: addToJsonInfo emits each mod's ID into the "um" array
// If none of the above apply, omit getId() (or return USERMOD_ID_UNSPECIFIED) and do NOT add an entry here.
#define USERMOD_ID_RESERVED 0 //Unused. Reserved; may indicate no usermod present
#define USERMOD_ID_UNSPECIFIED 1 //Default for usermods that do not require a unique ID
//Usermod IDs
#define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present
#define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID
#define USERMOD_ID_EXAMPLE 2 //Usermod "usermod_v2_example.h"
#define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h"
#define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h"
@@ -395,7 +382,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define BTN_TYPE_TOUCH_SWITCH 9
//Ethernet board types
#define WLED_NUM_ETH_TYPES 16
#define WLED_NUM_ETH_TYPES 14
#define WLED_ETH_NONE 0
@@ -412,9 +399,6 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define WLED_ETH_ESP32_POE_WROVER 11
#define WLED_ETH_LILYGO_T_POE_PRO 12
#define WLED_ETH_GLEDOPTO 13
#define WLED_ETH_QUINLED_V4_UNOQUAD 14
#define WLED_ETH_QUINLED_V4_OCTA 15
//Hue error codes
#define HUE_ERROR_INACTIVE 0
@@ -497,7 +481,6 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define JSON_LOCK_LEDGAP 20
#define JSON_LOCK_LEDMAP_ENUM 21
#define JSON_LOCK_REMOTE 22
#define JSON_LOCK_OTA 23
// Timer mode types
#define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness
+1 -136
View File
@@ -200,7 +200,7 @@ function sendDDP(ws, start, len, colors) {
let pkt = new Uint8Array(11 + dLen); // DDP header is 10 bytes, plus 1 byte for WLED websocket protocol indicator
pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1
pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0
pkt[2] = 0x00; // upper nibble is reserved, lower nibble is sequence number, if set to 0 no sequence checking is done (if enabled)
pkt[2] = 0x00; // reserved
pkt[3] = 0x0B; // RGB, 8bit per channel
pkt[4] = 0x01; // destination id (not used but 0x01 is default output)
pkt[5] = (off >> 24) & 255; // DDP protocol 4-7 is offset
@@ -222,138 +222,3 @@ function sendDDP(ws, start, len, colors) {
}
return true;
}
// Pin utilities
function getOwnerName(o,t,n) {
// Use firmware-provided name if available
if(n) return n;
if(!o) return "System"; // no owner provided
if(o===0x85){ return getBtnTypeName(t); } // button pin
return "UM #"+o;
}
function getBtnTypeName(t) {
var n=["None","Reserved","Push","Push Inv","Switch","PIR","Touch","Analog","Analog Inv","Touch Switch"];
var label = n[t] || "?";
return 'Button <span style="font-size:10px;color:#888">'+label+'</span>';
}
function getCaps(p,c) {
var r=[];
// Use touch info from settings endpoint
if(d.touch && d.touch.includes(p)) r.push("Touch");
if(d.ro_gpio && d.ro_gpio.includes(p)) r.push("Input Only");
// Use other caps from JSON (Analog, Boot, Input Only)
if(c&0x02) r.push("Analog");
if(c&0x08) r.push("Flash Boot");
if(c&0x10) r.push("Bootstrap");
return r.length?r.join(", "):"-";
}
// Fetch GPIO caps (/settings/s.js?p=11) then pin occupancy (/json/pins) with retry.
// Caches result in d.pinsData. Calls cb() when ready (or on failure).
// If page already loaded its own s.js (d.max_gpio set), skips caps load and goes straight to pins fetch.
function fetchPinInfo(cb, retries=5) {
if (d.pinsData) { cb&&cb(); return; }
var done=false, fr=retries;
function doFetch() {
fetch(getURL('/json/pins'))
.then(r=>r.json())
.then(j=>{ if(!done){done=true; d.pinsData=j.pins||[]; cb&&cb();} })
.catch(()=>{ fr-->0 ? setTimeout(doFetch,100) : (!done&&(done=true,d.pinsData=[],cb&&cb())); });
}
if (d.max_gpio) { doFetch(); return; }
// Load GPIO caps from s.js?p=11 first (sets d.rsvd/ro_gpio/max_gpio/touch/adc/um_p)
d.max_gpio=50; d.rsvd=[]; d.ro_gpio=[]; d.touch=[]; d.adc=[]; d.um_p=[];
var cr=retries;
function tryCaps() {
var s=cE("script"); s.src=getURL('/settings/s.js?p=11');
d.body.appendChild(s);
s.onload=function(){ GetV(); doFetch(); };
s.onerror=function(){ cr-->0 ? setTimeout(tryCaps,100) : doFetch(); };
}
tryCaps();
}
// Pin dropdown utilities
// Create or rebuild a pin <select> from an <input> or existing <select>
// name: form field name, requirement flags bitmask: 1=output, 2=touch, 4=ADC
function makePinSelect(name, flags) {
let el = gN(name);
if (!el) return null;
let v = parseInt(el.value);
if (isNaN(v)) v = -1;
let sel;
if (el.tagName === "SELECT") {
sel = el;
while (sel.lastChild) sel.lastChild.remove();
} else {
sel = cE('select');
sel.classList.add("pin");
sel.name = el.name;
if (el.required) sel.required = true;
let oc = el.getAttribute("onchange");
if (oc) sel.setAttribute("onchange", oc);
el.parentElement.replaceChild(sel, el);
}
let hasV = false;
for (let j = -1; j < (d.max_gpio||0); j++) {
if (j > -1 && d.rsvd && d.rsvd.includes(j)) continue;
if (j > -1 && (flags & 1) && d.ro_gpio && d.ro_gpio.includes(j)) continue;
if (j > -1 && (flags & 2) && (!d.touch || !d.touch.includes(j))) continue;
if (j > -1 && (flags & 4) && (!d.adc || !d.adc.includes(j))) continue;
let pInfo = d.pinsData && d.pinsData.find(p => p.p === j);
let used = j > -1 && pInfo && pInfo.a && j !== v;
let txt = j === -1 ? "unused" : `${j}`;
if (used) txt += ` (${getOwnerName(pInfo.o, pInfo.t, pInfo.n)})`;
// if (j > -1 && d.ro_gpio && d.ro_gpio.includes(j)) txt += " (R/O)"; // read only pins note: removed as pin is not shown for outputs
let opt = cE("option");
opt.value = j;
opt.text = txt;
sel.appendChild(opt);
if (j === v) { opt.selected = true; hasV = true; }
else if (used) opt.disabled = true;
}
// Safety for invalid pins currently saved
if (!hasV && v >= 0) {
let opt = cE("option");
opt.value = v; opt.text = v + " ⚠"; opt.selected = true;
sel.insertBefore(opt, sel.options[1]);
}
sel.dataset.val = v;
return sel;
}
// Convert pin <select> back to <input type="number">
function unmakePinSelect(name) {
let sel = gN(name);
if (!sel || sel.tagName !== "SELECT") return null;
let inp = cE('input');
inp.type = "number";
inp.name = sel.name;
inp.value = sel.value;
inp.className = "s";
if (sel.required) inp.required = true;
let oc = sel.getAttribute("onchange");
if (oc) inp.setAttribute("onchange", oc);
sel.parentElement.replaceChild(inp, sel);
return inp;
}
// Add option to select, auto-select matching data-val
function addOption(sel, txt, val) {
if (!sel) return null;
let opt = cE("option");
opt.value = val;
opt.text = txt;
sel.appendChild(opt);
if (sel.dataset.val !== undefined) {
for (let i = 0; i < sel.options.length; i++) {
if (sel.options[i].value == sel.dataset.val) { sel.selectedIndex = i; break; }
}
}
return opt;
}
+23 -19
View File
@@ -5,7 +5,7 @@
<title>WLED Palette Editor</title>
<!--* <link rel="stylesheet" href="style.css">
<script src="common.js"></script>
<script src="iro.js"></script>
<script src="iro.js"></script>
<link rel="icon" href="data:,"> *-->
</head>
<body>
@@ -71,8 +71,6 @@
let copyColor = '#000';
let ws = null;
let maxCol; // max colors to send out in one chunk, ESP8266 is limited to ~50 (500 bytes), ESP32 can do ~128 (1340 bytes)
let _applySeq = 0; // incremented each time applyLED fires; used to cancel stale in-flight previews
let _httpQueue = [], _httpRun = 0;
// load external resources in sequence to avoid 503 errors if heap is low, repeats indefinitely until loaded
(function loadFiles() {
@@ -331,7 +329,17 @@
});
draw();
}
/*
function rndPal() {
gr.innerHTML = '';
addMk(0, rndHex(), 1);
const cnt = Math.floor(Math.random() * 3) + 2, pos = new Set();
while (pos.size < cnt) pos.add(Math.floor(Math.random() * 254) + 1);
[...pos].sort((a,b)=>a-b).forEach(t => addMk(t, rndHex()));
addMk(255, rndHex(), 1);
}*/
// convert hsl to hex using canvas
function hslToHex(h, s, l) {
let ctx = cE("canvas").getContext("2d");
@@ -460,11 +468,7 @@
rm.className = 'sml';
rm.title = 'Delete palette';
rm.innerHTML = '&#10006;';
rm.onclick = () => {
requestJson({rmcpal:i}); // send remove command
setTimeout(refr, 500); // slight delay to allow ESP to process deletion before fetching updated list
localStorage.removeItem('wledPalx'); // invalidate main UI cache
};
rm.onclick = () => { requestJson({rmcpal:i}); setTimeout(refr, 500); };
const name = isEmpty(p.palette) ? 'Empty slot' : 'Custom' + i;
const css = isEmpty(p.palette) ? '#666' : cssArr(p.palette);
@@ -588,7 +592,7 @@
// download external palettes, these were hand picked from cpt-city (http://seaviewsensing.com/pub/cpt-city/)
// all palettes are licensed "free to use", converted to WLED JSON format by @dedehai
function fetchExt() {
fetch('https://wled.github.io/wled-web-tools/cpt_city_selection.json')
fetch('https://dedehai.github.io/cpt_city_selection.json')
.then(r => { if (!r.ok) throw new Error(); return r.json(); })
.then(data => {
try { localStorage.setItem('wledCptCityJson', JSON.stringify(data)); } catch(e) {}
@@ -617,16 +621,21 @@
async function requestJson(cmd)
{
if (ws && ws.readyState == 1 && ws.bufferedAmount < 32768) {
if (ws && ws.readyState == 1) {
try {
ws.send(JSON.stringify(cmd));
await new Promise(r => setTimeout(r, 15)); // short delay to give ESP time to process (fewer packets dropped)
return 1;
} catch (e) {}
}
// HTTP fallback
if (_httpQueue.length >= 5) return -1; // queue full; applyLED cancels stale queues before sending
if (!window._httpQueue) {
window._httpQueue = [];
window._httpRun = 0;
}
if (_httpQueue.length >= 5) {
return Promise.resolve(-1); // reject if too many queued requests
}
return new Promise(resolve => {
_httpQueue.push({ cmd, resolve });
(async function run() {
@@ -641,7 +650,7 @@
cache: 'no-store'
});
} catch (e) {}
await new Promise(r => setTimeout(r, 120)); // delay between requests (go slow, this is the http fallback if WS fails)
await new Promise(r => setTimeout(r, 120));
q.resolve(0);
}
_httpRun = 0;
@@ -653,12 +662,8 @@
async function applyLED()
{
if (!palCache.length) return;
const seq = ++_applySeq;
// discard pending HTTP chunks from any previous preview so stale data doesn't drain slowly
while (_httpQueue.length) _httpQueue.shift().resolve(-1); // resolve dropped entries so their awaiters can observe the seq change and exit
try {
let st = await (await fetch(getURL('/json/state'), { cache: 'no-store' })).json();
if (seq !== _applySeq) return; // superseded by a newer preview request
if (!st.seg || !st.seg.length) return;
// get selected segments, use main segment if none selected
@@ -675,7 +680,6 @@
arr.push(palCache[len > 1 ? Math.round(i * 255 / (len - 1)) : 0]);
// send colors in chunks
for (let j = 0; j < arr.length; j += maxCol) {
if (seq !== _applySeq) return; // superseded mid-send
let chunk = [s.start + j, ...arr.slice(j, j + maxCol)];
await requestJson({ seg: { id: s.id, i: chunk } });
}
+1 -1
View File
@@ -147,7 +147,7 @@ p {
font-size: 16px;
}
.fs1 {
font-size: 32px;
font-size: 48px;
}
.fs2 {
font-size: 28px;
+1 -15
View File
@@ -9,24 +9,10 @@
<link rel="stylesheet" href="style.css"></head>
<body>
<div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs:&nbsp;26)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs:&nbsp;25)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: 16</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-search"></span>
<span class="mls"> i-search</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e0a1" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe0a1;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="search, magnifier" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-pixelforge"></span>
-1
View File
@@ -11,7 +11,6 @@
<glyph unicode="&#xe04c;" glyph-name="presets" d="M704 704c131.413 0 234.667-103.253 234.667-234.667 0-161.28-145.067-292.693-364.8-491.946l-61.867-56.32-61.867 55.893c-219.733 199.68-364.8 331.093-364.8 492.373 0 131.414 103.254 234.667 234.667 234.667 74.24 0 145.493-34.56 192-89.173 46.507 54.613 117.76 89.173 192 89.173zM516.267 40.533c203.093 183.894 337.066 305.494 337.066 428.8 0 85.334-64 149.334-149.333 149.334-65.707 0-129.707-42.24-151.893-100.694h-79.787c-22.613 58.454-86.613 100.694-152.32 100.694-85.333 0-149.333-64-149.333-149.334 0-123.306 133.973-244.906 337.066-428.8l4.267-4.266z" />
<glyph unicode="&#xe066;" glyph-name="info" d="M512 746.667c235.52 0 426.667-191.147 426.667-426.667s-191.147-426.667-426.667-426.667-426.667 191.147-426.667 426.667 191.147 426.667 426.667 426.667zM554.667 106.667v256h-85.334v-256h85.334zM554.667 448v85.333h-85.334v-85.333h85.334z" />
<glyph unicode="&#xe08f;" glyph-name="power" d="M554.667 704v-426.667h-85.334v426.667h85.334zM760.747 611.413c82.773-70.4 135.253-174.506 135.253-291.413 0-212.053-171.947-384-384-384s-384 171.947-384 384c0 116.907 52.48 221.013 135.253 291.413l60.16-60.16c-66.986-54.613-110.080-137.813-110.080-231.253 0-165.12 133.547-298.667 298.667-298.667s298.667 133.547 298.667 298.667c0 93.44-43.094 176.64-110.507 230.827z" />
<glyph unicode="&#xe0a1;" glyph-name="search" d="M902.213 27.99l-197.073 167.615c-20.373 18.336-42.161 26.752-59.761 25.941 46.52 54.492 74.622 125.188 74.622 202.455 0 172.313-139.687 312-312 312-172.311 0-312-139.687-312-312s139.687-312 312-312c77.266 0 147.962 28.1 202.455 74.624-0.812-17.6 7.605-39.388 25.941-59.761l167.615-197.073c28.698-31.887 75.58-34.574 104.178-5.976s25.913 75.48-5.974 104.178zM408 216c-114.874 0-208 93.125-208 208s93.125 208 208 208 208-93.125 208-208-93.125-208-208-208z" />
<glyph unicode="&#xe0a2;" glyph-name="settings" d="M816.64 280.064l85.504-67.584c8.192-6.144 10.24-16.896 5.12-26.112l-81.92-141.824c-5.12-9.216-15.872-12.8-25.088-9.216l-101.888 40.96c-20.992-15.872-44.032-29.696-69.12-39.936l-15.36-108.544c-1.024-10.24-9.728-17.408-19.968-17.408h-163.84c-10.24 0-18.432 7.168-20.48 17.408l-15.36 108.544c-25.088 10.24-47.616 23.552-69.12 39.936l-101.888-40.96c-9.216-3.072-19.968 0-25.088 9.216l-81.92 141.824c-4.608 8.704-2.56 19.968 5.12 26.112l86.528 67.584c-2.048 12.8-3.072 26.624-3.072 39.936s1.536 27.136 3.584 39.936l-86.528 67.584c-8.192 6.144-10.24 16.896-5.12 26.112l81.92 141.824c5.12 9.216 15.872 12.8 25.088 9.216l101.888-40.96c20.992 15.872 44.032 29.696 69.12 39.936l15.36 108.544c1.536 10.24 9.728 17.408 19.968 17.408h163.84c10.24 0 18.944-7.168 20.48-17.408l15.36-108.544c25.088-10.24 47.616-23.552 69.12-39.936l101.888 40.96c9.216 3.072 19.968 0 25.088-9.216l81.92-141.824c4.608-8.704 2.56-19.968-5.12-26.112l-86.528-67.584c2.048-12.8 3.072-26.112 3.072-39.936s-1.024-27.136-2.56-39.936zM512 166.4c84.48 0 153.6 69.12 153.6 153.6s-69.12 153.6-153.6 153.6-153.6-69.12-153.6-153.6 69.12-153.6 153.6-153.6z" />
<glyph unicode="&#xe0e8;" glyph-name="eye" d="M512 640c213.333 0 395.52-132.693 469.333-320-73.813-187.307-256-320-469.333-320s-395.52 132.693-469.333 320c73.813 187.307 256 320 469.333 320zM512 106.667c117.76 0 213.333 95.573 213.333 213.333s-95.573 213.333-213.333 213.333-213.333-95.573-213.333-213.333 95.573-213.333 213.333-213.333zM512 448c70.827 0 128-57.173 128-128s-57.173-128-128-128-128 57.173-128 128 57.173 128 128 128z" />
<glyph unicode="&#xe116;" glyph-name="sync" d="M512 661.333c188.587 0 341.333-152.746 341.333-341.333 0-66.987-19.626-129.28-52.906-181.76l-62.294 62.293c19.2 35.414 29.867 76.374 29.867 119.467 0 141.227-114.773 256-256 256v-128l-170.667 170.667 170.667 170.666v-128zM512 64v128l170.667-170.667-170.667-170.666v128c-188.587 0-341.333 152.746-341.333 341.333 0 66.987 19.626 129.28 52.906 181.76l62.294-62.293c-19.2-35.414-29.867-76.374-29.867-119.467 0-141.227 114.773-256 256-256z" />

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
+3 -6
View File
@@ -1,9 +1,9 @@
@font-face {
font-family: 'wled122';
src:
url('fonts/wled122.ttf?2tjc6') format('truetype'),
url('fonts/wled122.woff?2tjc6') format('woff'),
url('fonts/wled122.svg?2tjc6#wled122') format('svg');
url('fonts/wled122.ttf?yzxblb') format('truetype'),
url('fonts/wled122.woff?yzxblb') format('woff'),
url('fonts/wled122.svg?yzxblb#wled122') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@@ -24,9 +24,6 @@
-moz-osx-font-smoothing: grayscale;
}
.i-search:before {
content: "\e0a1";
}
.i-pixelforge:before {
content: "\e900";
}
+1 -1
View File
@@ -1,6 +1,6 @@
@font-face {
font-family: "WIcons";
src: url(data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAtwAA0AAAAAFzwAAAsYAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGhgGYACEHhEICp8QmBQLQAABNgIkA3wEIAWDGweCQBueEhEVqmUn+JmQuYW8aYRI8pyRytrpySK/jJBk9v+nbf19MKDMoI0Jwi77VwXMwsTIUSbEKvy5Veevw/8NG9XGRiobEQNstPsjEh74P+bfFxu1zQc2m+Aa+1eIVHCCRR1PoA82/wK/+v91bBjrxhAh4ktd/2kus/ntTjFbOEASKufInVMvm8JMpgyzruVsD5AssAcF1PIhpKsOUGhW6IGMOOvPDZlh7zJjzjrI26wbrOpZbwBAVfDQuPpH/fDP0TubwXIgpEa+JEjFsKsLM0KLQAQE14LrALAQORH0B0lBAopZoRo0lge+QsIkt7t+gH1ODaQKTBViUo7GA2pIRP8bMf8CyVQwgRDVMu155fOe5wufb3n+5YugFxkvrC9jXua83PHy4MuLo6+NJo1WjC4aXf5K8sr3K+Q2AIDnqTcbt7CFHniq7lfKm8oR5WblLABlM+L3CD7iA8VLxT0AxURRLf9RPiq/DiA/kPvLpQUwrWTSrYAbx6aq0GK5CHxuIQmcHz2lvxChFCIiUFFHKRJF8CoU/zMIYJApEPQffEglRnqtOgL79YqvGCYRhokEXIhkfaAnREw0kRC6CCNxkuxFQzw93OKS1xQKDBMlauQ1UrlcGhaKYZjEUyJRSqViQtRxRbSQ1FPtK/eKlmJsMY5hKgXG5TMXt6G2rDgyAMcjgoMR7poV+WDcbVxqvTsDvAORS+QQM2wZw3diBoGL38dToxuyzsk4nYkHkCVu770sO2Fz5JxyEU0u4z1XZVFkceJWXocPaTOA+92Io/YOLXvTRjscz0HAUTw/CLcyTucgZGFdrosTzUJc0IhwmBFxIUHvjr52qVjv4YUrN7ri+rW113ngEvY7yq4QtoprrqSDyOKsnsOs2IGfciFL4gFn+VXcWnqZj9/HvBpXLrdaRu++oO3foumwpo7cjd2ALMq997KOEraU4Tsx64GTD9xOPzSHXr48kFq2LIhZdmycVzhuXaHidowEmWhkxcpgdsUaZFkbvGorAleulNEr7ZFQBGFbruK2D8tMtL58WSC1fDVwqwLbbK4CKQbOkQsT5J+h+bzToxWcZVyF5zrfbEuz34vbiCwRQ3czj+DW5GO3deuAUwzeyTjcaHMA929BJ7K43rSVXXFcNw8cr6tbuxVZYoecmSM4MCPAAk0h62CVTLUrrnbVpsJvmDE8haaEtPppMQK24RCfXnfKVQLUAy/Ht7py69c7YsiTzrGEyTjsSIGLTv2Ugi+m0KUCTszXkmLkqMCLzykJcKEUeQkJgReLCMA5BNkNdBZQqM90YRZnfcA7YMjsRKx5A0gVV+Zqy/DDG60nkeUUYTsB3HEpdJomq5Px4nk8hLqL46/V3PI1iOqb5TsvEXNoCvA+gyOGRQRLE722LHtdTSzEuazpx3jdQKGtlOucGKNpB9r47WaOh1XHgVstq+BNHM9IQb3tlJAnne14goWMhJH+i7BmugtHUyiGXWmT0cu5OSANzUJCYD5TUGfV5qgybt8d2ptjQRNvkoyxHvBgBjy0U3Vsk5EmGwnAV20Zw46YwcwQ38CaOeKMHWq9mutb0IwI6BjVUp2ddFcP295j7qa66k0xbB3FkDRVy9Kd5touxjilliqq6Dl3au+49vURZaWX8eZYoN7q2qvjAjkH0R2y0Mz6TsrXxBkxwBed54FzzKFMHW2NTGejqZ5mWIahKJqaw6ywBkYxrJQKwkHWp1k6ahkno5fbgtmVlqA53LJAahePS607kGVlMNvvIrxs24EhPeV1lGUFElruEYTtDoBmRUWcO5BS19UMu9dsoRl6xRrnmhVw6lTC5s0/hEUnZq/a2TK1QF+YW1vucrEq1BfkaufOVdmJ0WE/JB0J6V1xUVF2k+n2FRWFXz9ge+bOjc2j6LwEOp0gidFOMOSxVcX2PPi6sclkcH9dXzWmSjVliqrMqK9neChRNQJrYn0xTR8w6D7N0KD56GfoFm40fbWQG1u26hMdqShSGHp7DWUmSb5xxSgUoyJ1n6zKNcYt/MoekjWjWD+tZ6nqYfV6Vl8wTV8c3UwUpE/k53d++613ZKQ5M//jmRTFKj9/5scGAInwr840R072vnlz53n2nIHKnvadXdtKzhD9ptX+JppBtmop95BUD6EKjbGptfPGv2rvjvpGHen9psduz22euz183owM+OfOaudtfcxiC5nCUCGOB0hodycVSuIn5TY05KovAu4+sjS4NGgW9fXXYYYJJwQkgs2Tll9cE6ib0LZa/LtO97t4ddsEHXZMLZVSXp6idgfuZSUPHjY1Da4vSQxv/PjpV9+qUvQ5BsO90RRuz6NkN0dr18R2G6C7f7fZfYqztKOYOTcE6ajlKbX2+u+T439nX3AH1FQVOyF+8gSP9Cq18eB1toItjLiuzUbxrJtjZUZFqbysZ6JhnEFd48l+0dxU09Brys839Zpqav6CjRAjgxyyyC9jhMRXGQCjvDhQmxpuD6dhgrQabZCmjysVs4fui/zjK0yVm7PSVOVwDxjCYJoEYg3r6MxMlYjyTFAMBSXrB33EwwergBt51BdGhYfRYeGGZ0SRAUoa2AWp2bKKqcbQ/EOGstEKsLxkK1SVqomTVF1RBcsSUKLSGmfHPHjwi4GpMre2FhczfAFj+CVwjoGUzo358yjz8uWVzx9XhNqhqJTRewZDjj6l2lYFHyd5T0lnZ4n614H7FFkUZgjrbXzytFgnFn/a1qarDKPCpR+aZ34oDS8K72ArdrzMNX0ZczW/HBhOrd6TyQ1/zHz2mj3OZJgXJ/SNXa6NG9mxadMvB6uqBODKrr7W2XlcDVFJRgPdjwYGhE51s4pqFIYp02xeCyJI0uChfBZZNaQuu9fc9W+uaJY22C59cruX1jNmkuyeptdP7/YRZsaCtemeaTcDyn17ZeW1qByao9275jJzl+4YHPaKyooP+lkdj1rKxVlUdJ5FFukzcvTe5vNQ8FrLIT2wg+Su9wXBHdX+1ZDSpyF/UXLmx94E9Lru5wr5wlWPTdg1F4G+i3Nn7e7ff38Nzt1+MY3bK1eBcUn9Iia2dJE/GUAyTa3gYUkJW03vYqnvva9uXWv7zbugvrmgoAYPq0j5MWA/3poasufX3zQxBH3lnzdTbbNzDclpZDVkvh758opB67FhwgnhTwydEWYPVbWpTXN4/uwRn9KB0v5hn3mzo49A+rLgx5/2qPb4LVny44+wvz+Smtq/bWtfH6Mi/OKgfvqxX9VvMo2oRiAn4J4LDeuGS0pUyv+kIz4Swy7FJWNuxXfjpooPdW3a/RvtQpHwjPaJdgVn+vw/rrjo/8rvSa6/ewdbfbJ/8yQw2NwGgrtZOCqMnqQnanm2tqWDAGsBIGWJKym6KTP0RAMwI+FgRhNHbTSJtNS/CdZpJCqQYkMOJW0qh9JiktoPV/fkRGhP1FClRpLdCCiqvQ0BwmoRD74QCVgUCVkeYax+uAhhphDHbFklLszd3/EoS8g7ASMBCyEhiyeMlQ4XCdImxMY+6Awu2TqY4S3duiRIlAhmbHd3JSReou4oNJuWqm03GfxIeUvA2xpaC6a/l8mmeN0E71BJECsNXL7sYUJsWhCy2KuYOgtzTr4TIG/XlhdY4x1mUSnTaYIqtrCiWHDPZKW9+aKyzgl/7/MoAAQUd6oEKIIgCEEYMCRCYuSBPJFkA/DIpMgLeSMf5Iv8kD8KQIFIhoJQMApBoSgMhSM5UnjIae98IFEfwn4XmrpvPULP5216LPpmNzJ0VXR2N7PD5G3zhL4UvaObbOiS7kvP5y2Ejm6OiiZiCjYBAAAA);
src: url(data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAsAAA0AAAAAFlgAAAqqAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGhgGYACEGhEICp08lnkLPgABNgIkA3gEIAWDGweCNhvxEVGUcFI3wBeFsYOmlCFXadeSCl4PGhMTwyMh0q9d2MXuDaeszCMkmT3Abd0Eu2ijAIMUa1IDbaQmRj/wndtnJB+d8BHN/+ZKv+zJJpUCCAsMA5IcArBbtlteAg6ToYi3nPp6KxH97fd9OQgssMYTSymghAPMMODmLNpvv/P8BPzeodosVKppyCRNZE0QEqlTCp0SqP9T4O4gAMzzFuTJg2RPa6/23s/f4IYKREKfr6tTc/cLu7dh2JTwmhJdUiSLQqZVQFvmy6mScazQAwlZ7apjDAOl7l8dYEyN5azo7xRYCTCz7gCAzIa7hoI38uBn9/NfQMIrA5RCyCOfOtya0oEneAKP2+M8AEzujgX5QIQYkXEhC5nk4BVC6f6L4cmN4YazURxLPmVQjD4XkFWhNcfmv38+EMNisJkOyOKfgx6n/2z9efLjZY9fPol6EvJEdaY7I5y1zu3Ok64kl58r7bcprplPfZ+GvELuPwEAiGmvZJPj8ErdT9kXF+1jV7AvsG3seNY31uuFw5m/LLgKwNzGLGd8mO+cfw6A8S5jCsM/9wfEH8iWrJEYBLUxMHfsLJpcHQqzOuDEFhQjM1otoVvVg94O/zMIoCJtI1ACwThSfr8yQL1KvQ5rAApCJOJJKBSl4cdB3IwhcY7A5i3/hNDuIJq7NmfVLJNq2Z1hACMTkEpSDwPzGMtL6Qj7EFl8BemVw4zAppSRHW5ZhSxVZIZwKIDXduoxP57T0cgYeukZbC1afoHHq6/OwUSERJEC0lcLGXjp0QKyd7tOLYzdaXLTFHYixavtddgQ0YyI6xbZbLleW+DKSDGxqrvjTWIRtNNgOF6yGYc0ZhihH0R1vR9WuWn2Q4pkWdcmW0QsbEIYzglYJKxhzbvPBSWhn9uiMsuraZ3jiQ75dBgpD4mW9tgSdSHFzLzEcnLiNDvb59n9lVxzrObWObWDviOG3Dwt5RZCKdLLyl04L0+xvKG6aEG0nJFTM6AcuXROdpzmFJCcH9+uWfmohYxDH0Nxk+nRN4ZT3uJW3O32b9GChl57lSFlYeur0F6s+ve/cC8GeUHLy5CeTZoB7XGeFaxDWspDQ9CBaXdnUZU9hGerGTqIgUtgQxhFauojOOdYXo78csyahwycYlRk/FVxQdrYrQc7r1tJQJv1+Xi5FbW+xPCwj5pLicU1YATAPRM9hVc9RfdxWc2300x9lIgM3K/9xgtYHI8miESYICECeMSQt3EtAdq7jhUlLE2CiYgNqUeZNrzc9nLTTg+EeckP9Kz28vnwTeoolOtCGyF5WOonuVZNPkHX/RKff2/l48rnCUbIfJZad59cYhSwkWPEJUQkRvZrYUMVbAKCS6jB/bp7M2ItABfEMpgBinhBFLgze5jkAlW62xjORdV67XqlRsPsObLU7cI+/4ss97HdGJ2iXMrTFMuRTzAe2SISYd9NlE6rZmS4ahqS+8GKTA7ZuWs9YGQfYGQHdUqbXcy+iQC2aiEDhkdLTkhvpoYOmp6tTc6yvgVbEIGdkoPu2sV275V27N23h7awKFxyUm5n1CGxXfscu7nrlINyF7v00vEyotuwG5If4LpYtazK+s3xmi4bpC2UoPNVnRa9JubCZj3+jg4Zl+iGnds38V2bNqxnXOKcUkYv8Vw1vppL4+lMDDMok9jqbFmxHE1LeYp/Sc6O03odj1droeRpqckiE7Qa4jB+nO5OlVyIymtCtJdACJKcTKe3Kct4DL+2knGWW/gpzKXr191XULH0Ay1NmD9ndUMvJaoqrCq5dStqFaosxPyr8/N902gfWD5BcFtmaqreo8wxq1T1+g6+d8iQDLnRJBeYZP4jf/MEBpHR0Lj1zmvSecXw/+vqjLhyTs+enLSoujoiRy3LDbIhvmtxCTAzTZPZBNzr683+Pi7U/TOZjE+Z8yHfzlQzMbsdS4t1ulIwTTJN6/hj5nBM5GevHDFhfTVob+tnthVHUVyu6o1q8GeQCn5TYowqQ4a0asLK33fsSX2zLCVo473WZ4XPWu/gTUr4n1nSfH38mHmqzKpYCucxNo9yXJO1toU1NYX8GuAm7EXRRVH9ja9f0zCPBxUQoNvXeb64MoLftWmu23d39+9eBU1d+UObPKOkpETCw4F7hvbO3brNG0u0Qnrt6B9fveVI0AIMu+aSkOtc3VrSJG5IwMsAv2Rwvfs6ObS5xyXIGfFGlW5cxjv/b4+s7/gTclsCLce7ZvXo6i3rJxi2P9ln4irW+XW89OtSmD3FBmYRo9jaDUvEEip98Bf1mytr7BaFwmJXXVf/AsfRQx8c8MBdywDCjkgAM7s2GDeXXEdyeRSPy4viEmSqzesYgTclp1nKvv50S/kNN+Me01EF0wbWprFZyoBXWACDKu3Cljz17p1WbIZ7xFwjnWai0bGQqsZQK2xf3jggsrSXIVaxQ5EaS2GoE0/jlHG6deccNaU4PqGWZWrG4+588wUzl9saGzWaiLzKjH1B/XEQ4LgwcYIvPn16iYkW1K9gpBLXayyhAJWUWWu2o4jXaVtbtfzXgfuQTk3DaPbaBw817l7OvamJX0Yz0gPOtn4jx9N79MYQbCTF84i+sxz6kXTj3MYcbvkx56XzGsMoWng/EOvWrcWLo2/Jki/by8srSCjHsse7du1fBqtFNQfTTAMOYnfw+6srmZgvttlWFUunU+SoXWtJpU4qtduaqVndnxftCHhw2c5Qs43pa9cbRfu0y1Nt5oTN6hPvfS+w6LgjvexcaoGJZO76IeYh02unz5FWVjqiKer9q78ieyU0Da60eLSoAM296/BJHbMKCIXs4Xs17vLgTs35ikkIrh9BLc4dTXAxNvU5UvV1Vb7bhkO4BhD/9lGHO+/fn4NjlwtHhQO8BSSK5a9HRtGUqfZwnbmeTb2rTbpb764lHTY8Ydt87VtQbHW9UlkZn5WaPRqobxB3qLN+/cb18J+f+dNROn5AISbO1lVAbseul2ewdd4vjwdVkzC2L02fKWdJE3fnxAH7JhVtSF4/EDxhQNoukP0c++bTOk5j6JfTPn6EbndfYOD6FcsJIgKUob1Inz6u5zRZLPsWD0IB4t4DWzCg1XLY/wIGg30NHTTauPsJKVtSOtJ9O2/rYgfF03zzHqybNYqD/yx4tforP6Ld9vAr2ybl/3yIRTcdrwzuetFFSSMAH0LMxI2+fkDdCcDYJyA3ipitETmBOLi8EZmJSOpOPFq/DTxMGhrE3JLs83kymayp5Uh8Ms2xDiHtOJqBLNjEdz8eyLwgrYDkpX6syTp5sNVEYdFEZesHeyLOS68ey57lZy682pmLOqOJ4wcS2GwSmTfZWPLgMWbCdumm7N32YP5QDH110k4bAfiCL0Df065NIHyl/q626c2Y16wHeIviHYE4G+iT5oGtK/bUXlddcGyeJwQBPKxxgIKM7PhKE0/2uuQ+juqSmmzG3PDQFXfqjwMpWpmyPLpjTQbA8zda3OddU9za9W/xDBTYht7SfiikklBMEosFGw5ceGBX1J+TRABBhBBGBFHEEIeCD40EkkghjYx77NI+y02QY4JeWJYom4tVXCrlMg1XCDMwWSeBQMpFORkyRSehUM1EmQphXMqVogyVtJNIKOEiERcruUyTmZOVJOkkzsrJEGRl5WR5AgA=);
}
:root {
+19 -109
View File
@@ -25,7 +25,6 @@ var pN = "", pI = 0, pNum = 0;
var pmt = 1, pmtLS = 0;
var lastinfo = {};
var isM = false, mw = 0, mh=0;
var bsOpts = null; // blending style options snapshot, used for dynamic filtering based on matrix mode (iOS compatibility)
var ws, wsRpt=0;
var cfg = {
theme:{base:"dark", bg:{url:"", rnd: false, rndGrayscale: false, rndBlur: false}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
@@ -661,14 +660,12 @@ function parseInfo(i) {
mw = i.leds.matrix ? i.leds.matrix.w : 0;
mh = i.leds.matrix ? i.leds.matrix.h : 0;
isM = mw>0 && mh>0;
if (!bsOpts) bsOpts = Array.from(gId('bs').options).map(o => o.cloneNode(true)); // snapshot all options on first call
const bsSel = gId('bs');
// note: style.display='none' for option elements is not supported on all browsers (notably iOS)
bsSel.replaceChildren(...bsOpts.filter(o => isM || o.dataset.type !== "2D").map(o => o.cloneNode(true))); // allow all in matrix mode, filter 2D blends otherwise
if (!isM) {
gId("filter2D").classList.add('hide'); // hide 2D effects in non-matrix mode
gId("filter2D").classList.add('hide');
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';});
} else {
gId("filter2D").classList.remove('hide');
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';});
}
gId("updBt").style.display = (i.opt & 1) ? '':'none';
// if (i.noaudio) {
@@ -989,39 +986,21 @@ function populatePalettes()
);
}
gId('pallist').innerHTML=html;
// append usermod palettes (fixed ID space: 255 down to 201)
// append custom palettes (when loading for the 1st time)
let li = lastinfo;
if (!isEmpty(li) && li.umpalcount && li.umpalnames) {
for (let j = 0; j < li.umpalcount; j++) {
if (!isEmpty(li) && li.cpalcount) {
for (let j = 0; j<li.cpalcount; j++) {
let div = d.createElement("div");
gId('pallist').appendChild(div);
div.outerHTML = generateListItemHtml(
'palette',
255-j,
li.umpalnames[j],
'~ Custom '+j+' ~',
'setPalette',
`<div class="lstIprev" style="${genPalPrevCss(255-j)}"></div>`
);
}
}
// append user custom palettes (fixed ID space: 200 down to FIXED_PALETTE_COUNT+1)
if (!isEmpty(li) && li.cpalcount) {
for (let j = 0; j < li.cpalcount; j++) {
const id = 200 - j;
const pd = palettesData[id];
if (pd && pd.length === 16 && pd.every(e => e[1] === 128 && e[2] === 128 && e[3] === 128)) continue; // skip gray gap-placeholder entries
let div = d.createElement("div");
gId('pallist').appendChild(div);
div.outerHTML = generateListItemHtml(
'palette',
id,
'~ Custom '+j+' ~',
'setPalette',
`<div class="lstIprev" style="${genPalPrevCss(id)}"></div>`
);
}
}
updateSelectedPalette(selectedPal); // update selection after adding usermod and custom palettes
}
function redrawPalPrev()
@@ -1478,9 +1457,7 @@ function readState(s,command=false)
tr = s.transition;
gId('tt').value = tr/10;
const bsSel = gId('bs');
bsSel.value = s.bs || 0; // assign blending style
if (!bsSel.value) bsSel.value = 0; // fall back to Fade if option does not exist
gId('bs').value = s.bs || 0;
if (tr===0) gId('bsp').classList.add('hide')
else gId('bsp').classList.remove('hide')
@@ -2835,7 +2812,7 @@ function loadPalettesData() {
if (lsPalData) {
try {
var d = JSON.parse(lsPalData);
if (d && d.vid == lastinfo.vid && d.pcount == lastinfo.palcount) {
if (d && d.vid == lastinfo.vid) {
palettesData = d.p;
redrawPalPrev();
return resolve();
@@ -2847,8 +2824,7 @@ function loadPalettesData() {
getPalettesData(0, () => {
localStorage.setItem("wledPalx", JSON.stringify({
p: palettesData,
vid: lastinfo.vid,
pcount: lastinfo.palcount // total palette count, refresh cache if it changes
vid: lastinfo.vid
}));
redrawPalPrev();
setTimeout(resolve, 99); // delay optional
@@ -3437,23 +3413,13 @@ function showVersionUpgradePrompt(info, oldVersion, newVersion) {
function reportUpgradeEvent(info, oldVersion, alwaysReport) {
showToast('Reporting upgrade...');
const IR_TYPES = {
0: null, // not configured — omit field entirely
1: "24-key", // white 24-key remote
2: "24-key-ct", // white 24-key with CW, WW, CT+, CT- keys
3: "40-key", // blue 40-key remote
4: "44-key", // white 44-key remote
5: "21-key", // white 21-key remote
6: "6-key", // black 6-key learning remote
7: "9-key", // 9-key remote
8: "json-remote", // ir.json configurable remote
};
// Reuse the info argument and fetch only /json/cfg (serialize requests to avoid 503s on low-heap devices)
const infoData = info;
fetch(getURL('/json/cfg'), {method: 'get'})
.then(res => res.ok ? res.json() : Promise.reject(new Error('Failed to fetch /json/cfg')))
.then(cfgData => {
// Fetch fresh data from /json/info endpoint as requested
fetch(getURL('/json/info'), {
method: 'get'
})
.then(res => res.json())
.then(infoData => {
// Map to UpgradeEventRequest structure per OpenAPI spec
// Required fields: deviceId, version, previousVersion, releaseName, chip, ledCount, isMatrix, bootloaderSHA256
const upgradeData = {
@@ -3468,58 +3434,13 @@ function reportUpgradeEvent(info, oldVersion, alwaysReport) {
brand: infoData.brand, // Device brand (always present)
product: infoData.product, // Product name (always present)
flashSize: infoData.flash, // Flash size (always present)
repo: infoData.repo, // GitHub repository (always present)
fsUsed: infoData.fs?.u, // Filesystem used space in kB
fsTotal: infoData.fs?.t, // Filesystem total space in kB
// LED hardware
busCount: cfgData.hw?.led?.ins?.length ?? 1,
busTypes: (cfgData.hw?.led?.ins ?? []).map(b => busTypeToString(b.type)),
matrixWidth: infoData.leds?.matrix?.w,
matrixHeight: infoData.leds?.matrix?.h,
ledFeatures: [
...(infoData.leds?.lc & 0x02 ? ["rgbw"] : []),
...(infoData.leds?.lc & 0x04 ? ["cct"] : []),
...((infoData.leds?.maxpwr ?? 0) > 0 ? ["abl"] : []),
...(cfgData.hw?.led?.cr ? ["cct-from-rgb"] : []),
...(cfgData.hw?.led?.cct ? ["white-balance"] : []),
...((cfgData.light?.gc?.col ?? 1.0) > 1.0 || (cfgData.light?.gc?.bri ?? 1.0) > 1.0 ? ["gamma"] : []),
...(cfgData.light?.aseg ? ["auto-segments"] : []),
...((cfgData.light?.nl?.mode ?? 0) > 0 ? ["nightlight"] : []),
],
// peripherals (note: i2c/spi may reflect board defaults, not user-configured hardware)
peripherals: [
...((cfgData.hw?.relay?.pin ?? -1) >= 0 ? ["relay"] : []),
...((cfgData.hw?.btn?.ins ?? []).filter(b => b.type !== 0).length > 0 ? ["buttons"] : []),
...((cfgData.eth?.type ?? 0) > 0 ? ["ethernet"] : []),
...((cfgData.if?.live?.dmx?.inputRxPin ?? 0) > 0 ? ["dmx-input"] : []),
...((cfgData.hw?.ir?.type ?? 0) > 0 ? ["ir-remote"] : []),
],
buttonCount: (cfgData.hw?.btn?.ins ?? []).filter(b => b.type !== 0).length,
// integrations
integrations: [
...(cfgData.if?.hue?.en ? ["hue"] : []),
...(cfgData.if?.mqtt?.en ? ["mqtt"] : []),
...(cfgData.if?.va?.alexa ? ["alexa"] : []),
...(cfgData.if?.sync?.send?.en ? ["wled-sync"] : []),
...(cfgData.nw?.espnow ? ["esp-now"] : []),
...(cfgData.if?.sync?.espnow ? ["esp-now-sync"] : []),
],
// usermods
usermods: Object.keys(cfgData.um ?? {}),
usermodIds: infoData.um ?? [],
};
// IR remote — only include if configured
const irType = IR_TYPES[cfgData.hw?.ir?.type ?? 0];
if (irType) upgradeData.irRemoteType = irType;
repo: infoData.repo // GitHub repository (always present)
};
// Add optional fields if available
if (infoData.psrSz !== undefined) upgradeData.psramSize = infoData.psrSz; // Total PSRAM size in MB; can be 0
// Note: partitionSizes not currently available in /json/info endpoint
// Make AJAX call to postUpgradeEvent API
return fetch('https://usage.wled.me/api/usage/upgrade', {
@@ -3550,17 +3471,6 @@ function reportUpgradeEvent(info, oldVersion, alwaysReport) {
});
}
function busTypeToString(t) {
if (t === 0) return "none";
if (t === 40) return "on-off";
if (t >= 16 && t <= 39) return "digital"; // WS2812, SK6812, etc.
if (t >= 41 && t <= 47) return "pwm"; // analog RGB/CCT/single
if (t >= 48 && t <= 63) return "digital-spi"; // APA102, WS2801, etc.
if (t >= 64 && t <= 71) return "hub75"; // HUB75 matrix panels
if (t >= 80 && t <= 95) return "network"; // DDP, E1.31, ArtNet
return "unknown";
}
function updateVersionInfo(version, neverAsk, alwaysReport) {
const versionInfo = {
version: version,
+2 -2
View File
@@ -424,7 +424,7 @@ let fL; // file list
const classics=['console_font_4x6.wbf','console_font_5x12.wbf','console_font_5x8.wbf','console_font_6x8.wbf','console_font_7x9.wbf']; // classic WLED fonts list
let pT = []; // local tools list from JSON
let wv = [0, 0]; // wled version [major, minor], updated in fsMem(), used to check tool compatibility
const remoteURL = 'https://wled.github.io/wled-web-tools/pftools.json'; // tools list
const remoteURL = 'https://dedehai.github.io/pftools.json'; // Change to your actual repo
const toolsjson = 'pftools.json';
// note: the pftools.json must use major.minor for tool versions (e.g. 0.95 or 1.1), otherwise the update check won't work
// also the code assumes that the tool url points to a gz file
@@ -498,7 +498,7 @@ function segLoad(){
}).catch(console.error);
}
/* which seg is showing image fx (ID 53) */
/* which seg is showing image fx 53 */
function curImgSeg(){
const sel=getId('seg');
for(let i=0;i<sel.options.length;i++){
+6 -7
View File
@@ -9,21 +9,20 @@
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
// note: loading style.css here causes ugly white button flashes so @import url("style.css") below inlines it for this page (only costs ~30bytes of flash)
l.onload = () => {
// load style.css then initialize
l.onload = () => loadResources(['style.css'], () => {
getLoc();
loadJS(getURL('/settings/s.js?p=0'), false);
};
});
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
</script>
<style>
@import url("style.css");
@import url("style.css");
body {
height: 100px;
margin: 0 auto;
max-width: 520px;
margin: 0;
}
html {
--h: 9vh;
@@ -31,7 +30,7 @@
button {
display: block;
border-radius: var(--h);
font-size: clamp(1rem, 6vw, 2rem);
font-size: 6vmin;
height: var(--h);
width: calc(100% - 40px);
margin: 2vh auto 0;
+112 -102
View File
@@ -7,7 +7,6 @@
<style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script>
var maxD=1,maxI2S=0,maxRMT=0,maxA=1,chipID=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=4; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var hasPSRAM=false; // set from /json/info on page load
var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;}
// these functions correspond to C macros found in const.h
@@ -39,10 +38,6 @@
function S() {
getLoc();
if(localStorage.getItem('ASc')==='true') d.Sf.AS.checked=true;
fetch(getURL('/json/info'))
.then(r => r.ok ? r.json() : {})
.then(info => { hasPSRAM = Number(info.psrSz ?? 0) > 0 || Number(info.psram ?? 0) > 0; })
.catch(() => {});
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
d.ledTypes = [/*{i:22,c:1,t:"D",n:"WS2812"},{i:42,c:6,t:"AA",n:"PWM CCT"}*/]; // filled from GetV()
d.um_p = [];
@@ -54,7 +49,7 @@
setABL();
d.Sf.addEventListener("submit", trySubmit);
if (d.um_p[0]==-1) d.um_p.shift();
fetchPinInfo(pinDropdowns);
pinDropdowns();
}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/leds');
}
@@ -75,10 +70,9 @@
function isS2() { return chipID == 2; }
function isS3() { return chipID == 3; }
function is32() { return chipID == 4; }
function pinsOK() {
var ok = true;
var nList = Array.from(d.Sf.querySelectorAll("#mLC input[name^=L], #mLC select.pin[name^=L]")); // include both input types (numeric & dropdown)
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
nList.forEach((LC,i)=>{
if (!ok) return; // prevent iteration after conflict
let nm = LC.name.substring(0,2); // field name : /L./
@@ -90,28 +84,19 @@
}
// ignore IP address
if (isNet(t)) return;
// LED pin(s) must be assigned for non-virtual types
let pIdx = parseInt(LC.name.charAt(1)); // determine pin index (0 for L0, 1 for L1, etc.)
if (pIdx < numPins(t) && !isVir(t) && (LC.value === "" || LC.value === "-1")) {
LC.focus();
ok = false;
return;
}
//check for pin conflicts
if (LC.value!="" && LC.value!="-1") {
let p = d.rsvd.concat(d.um_p); // used pin array
//d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
// only non-LED selects (buttons, IR, relay) — LED pins check against each other below
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(!/^L[0-4]/.test(e.name) && e.value>-1)p.push(parseInt(e.value));})
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
if (p.some((e)=>e==parseInt(LC.value))) {
alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);
if (LC.tagName==="SELECT") LC.value="-1"; else LC.value="";
LC.value="";
LC.focus();
ok = false;
return;
} else if (d.ro_gpio.some((e)=>e==parseInt(LC.value))) {
alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);
if (LC.tagName==="SELECT") LC.value="-1"; else LC.value="";
LC.value="";
LC.focus();
ok = false;
return;
@@ -122,9 +107,10 @@
let m = nList[j].name.substring(2,3); // bus number (0-Z)
let t2 = parseInt(gN("LT"+m).value, 10);
if (isVir(t2)) continue;
if (nList[j].value!="" && nList[j].value!="-1" && LC.value==nList[j].value) {
if (nList[j].value!="" && nList[i].value==nList[j].value) {
alert(`Pin conflict between ${LC.name}/${nList[j].name}!`);
if (nList[j].tagName==="SELECT") nList[j].value="-1"; else { nList[j].value=""; nList[j].focus(); }
nList[j].value="";
nList[j].focus();
ok = false;
return;
}
@@ -152,7 +138,7 @@
if (isHub75(t)) {
let p = parseInt(d.Sf["L2"+n].value)||1, r = parseInt(d.Sf["L3"+n].value)||1, c = parseInt(d.Sf["L4"+n].value)||1, h = parseInt(d.Sf["L1"+n].value)||1;
if (r*c !== p) {alert(`HUB75 error: panels≠rows×cols`); e.stopPropagation(); return false;}
if (h >= 64 && p > 1 && !hasPSRAM) {alert(`HUB75 error: height >= 64, only single panel allowed`); e.stopPropagation(); return false;}
if (h >= 64 && p > 1) {alert(`HUB75 error: height >= 64, only single panel allowed`); e.stopPropagation(); return false;}
if(isS3()) {
alert("HUB75 changes require a reboot"); // TODO: only throw this if panel config changed?
}
@@ -317,28 +303,14 @@
gId("p0d"+n).innerText = p0d;
gId("p1d"+n).innerText = p1d;
gId("off"+n).innerText = off;
// convert pin fields so show/hide applies to the correct element type
for (let p=0; p<5; p++) {
let nm2 = "L"+p+n;
let el = d.Sf[nm2];
if (!el) continue;
if (isVir(t) || isHub75(t)) {
if (el.tagName === "SELECT") unmakePinSelect(nm2); // see common.js
} else {
if (el.tagName === "INPUT" && el.type === "number") {
makePinSelect(nm2, 1); // see common.js
d.pinUpdPending = true;
}
}
}
// show/hide secondary pins on whatever element type now exists
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 4*isHub75(t);
// secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off)
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 4*isHub75(t); // fixes network pins to 4
for (let p=1; p<5; p++) {
var LK = d.Sf["L"+p+n];
if (!LK) continue;
LK.style.display = (p < pins) ? "inline" : "none";
LK.required = (p < pins);
if (p >= pins) LK.value = (LK.tagName === "SELECT") ? "-1" : "";
if (p >= pins) LK.value="";
}
}
@@ -443,6 +415,25 @@
LC.style.color="#fff";
return; // do not check conflicts
}
// check for pin conflicts & color fields
if (nm.search(/^L[0-4]/) == 0) // pin fields
if (LC.value!="" && LC.value!="-1") {
let p = d.rsvd.concat(d.um_p); // used pin array
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
for (j=0; j<nList.length; j++) {
if (i==j) continue;
let n2 = nList[j].name.substring(0,2); // field name : /L./
if (n2.search(/^L[0-4]/) == 0) { // pin fields
let m = nList[j].name.substring(2,3); // bus number (0-Z)
let t2 = parseInt(gN("LT"+m).value, 10);
if (isVir(t2)) continue;
if (nList[j].value!="" && nList[j].value!="-1") p.push(parseInt(nList[j].value,10)); // add current pin
}
}
// now check for conflicts
if (p.some((e)=>e==parseInt(LC.value))) LC.style.color = "red";
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
} else LC.style.color = "#fff";
});
// Use helper function to calculate channel usage
@@ -519,11 +510,6 @@
gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none';
gId('fpsWarn').style.display = (d.Sf.FR.value == 0) || (d.Sf.FR.value >= 80) ? 'block':'none';
gId('fpsHigh').style.display = (d.Sf.FR.value >= 80) ? 'block':'none';
if (d.pinUpdPending) {
d.pinUpdPending = false;
d.Sf.querySelectorAll("select.pin").forEach((e) => { pinUpd(e); });
}
}
function lastEnd(i) {
@@ -685,18 +671,11 @@ Swap: <select id="xw${s}" name="XW${s}">
gId("com_rem").style.display = (i>0) ? "inline":"none";
}
// get pin dropdown flags for button type: touch=2, ADC=4, any=0
function btnPinFlags(t) { return (t==6||t==9) ? 2 : (t==7||t==8) ? 4 : 0; }
function btnPinDd(s) {
let t = parseInt(d.Sf["BE"+s].value);
makePinSelect("BT"+s, btnPinFlags(t));
d.Sf.querySelectorAll("select.pin").forEach(e => pinUpd(e));
}
function addBtn(i,p,t) {
var b = gId("btns");
var s = chrID(i);
var c = `<div id="btn${i}">#${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" min="-1" max="${d.max_gpio}" class="xs" value="${p}">`;
c += `&nbsp;<select name="BE${s}" onchange="btnPinDd('${s}')">`
c += `&nbsp;<select name="BE${s}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
c += `<option value="2" ${t==2?"selected":""}>Pushbutton</option>`;
c += `<option value="3" ${t==3?"selected":""}>Push inverted</option>`;
@@ -826,70 +805,101 @@ Swap: <select id="xw${s}" name="XW${s}">
}
}
function pinDropdowns() {
// Rebuild LED GPIO selects now that d.pinsData is available.
d.Sf.querySelectorAll(".iST input.s[name^=L], .iST select.pin[name^=L]").forEach((e) => {
let n = e.name.substring(2, 3);
let t = parseInt(d.Sf["LT"+n].value, 10);
if (!isVir(t) && !isHub75(t)) makePinSelect(e.name, 1);
});
// IR (any pin including input-only)
let irSel = makePinSelect("IR", 0);
if (irSel) irSel.onchange = function() { UI(); pinUpd(this); };
// Relay (output required)
let rlSel = makePinSelect("RL", 1);
if (rlSel) rlSel.onchange = function() { UI(); pinUpd(this); };
// Buttons (flags depend on button type: touch=2, ADC=4)
gId("btns").querySelectorAll('input[type="number"], select.pin[name^=BT]').forEach((e) => {
let s = e.name.substring(2);
let t = parseInt(d.Sf["BE"+s]?.value) || 0;
let bSel = makePinSelect(e.name, btnPinFlags(t));
if (bSel) bSel.onchange = function() { UI(); pinUpd(this); };
});
// cross-update all pin selects
d.Sf.querySelectorAll("select.pin").forEach((e) => { pinUpd(e); });
// add dataset values for remaining LED GPIO inputs (virtual/HUB75)
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i) => {
if (i.value !== "" && parseInt(i.value, 10) >= 0)
let fields = ["IR","RL"]; // IR & relay
gId("btns").querySelectorAll('input[type="number"]').forEach((e)=>{fields.push(e.name);}) // buttons
for (let i of d.Sf.elements) {
if (i.type === "number" && fields.includes(i.name)) { //select all pin select elements
let v = parseInt(i.value);
let sel = addDropdown(i.name,0);
for (var j = -1; j < d.max_gpio; j++) {
if (d.rsvd.includes(j)) continue;
let foundPin = d.um_p.indexOf(j);
let txt = (j === -1) ? "unused" : `${j}`;
if (foundPin >= 0 && j !== v) txt += ` used`; // already reserved pin
if (d.ro_gpio.includes(j)) txt += " (R/O)";
let opt = addOption(sel, txt, j);
if (j === v) opt.selected = true; // this is "our" pin
else if (d.um_p.includes(j) && j > -1) opt.disabled = true; // someone else's pin
}
}
}
// update select options
d.Sf.querySelectorAll("select.pin").forEach((e)=>{pinUpd(e);});
// add dataset values for LED GPIO pins
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{
if (i.value!=="" && i.value>=0)
i.dataset.val = i.value;
});
}
function pinUpd(e) {
// update changed select options across all pin selects
// update changed select options across all usermods
let oldV = parseInt(e.dataset.val);
e.dataset.val = e.value;
let label = /^L[0-4].$/.test(e.name) ? 'LED' : e.name;
let txt = e.name;
let pins = [];
// collect LED bus pin values from remaining inputs (virtual/HUB75)
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i) => {
let busN = i.name.substring(2, 3);
let t = parseInt(d.Sf["LT"+busN].value, 10);
let p = parseInt(i.name.charAt(1), 10);
if (isVir(t) || isHub75(t) || p >= numPins(t)) return;
if (i.value !== "" && parseInt(i.value, 10) >= 0) pins.push(i.value);
});
// collect LED bus pin values from selects
d.Sf.querySelectorAll(".iST select.pin[name^=L]").forEach((s) => {
if (s !== e && parseInt(s.value) >= 0)
pins.push(s.value);
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{
if (i.value!=="" && i.value>=0 && i.max<255)
pins.push(i.value);
});
let selects = d.Sf.querySelectorAll("select.pin");
for (let sel of selects) {
if (sel == e) continue;
Array.from(sel.options).forEach((i) => {
if (i.value == sel.dataset.val) return; // skip sel's own selected pin
if (sel == e) continue
Array.from(sel.options).forEach((i)=>{
let led = pins.includes(i.value);
if (!(i.value == oldV || i.value == e.value || led)) return;
if (i.value == -1) { i.text = "unused"; return; }
i.text = i.value;
if (i.value == oldV) { i.disabled = false; }
if (i.value == e.value || led) {
i.disabled = true;
i.text += ` ${led ? 'LED' : label}`;
if (!(i.value==oldV || i.value==e.value || led)) return;
if (i.value == -1) {
i.text = "unused";
return
}
//if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)"; // read only pin note: removed as pin is not shown for outputs
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value || led) {
i.disabled = true;
i.text += ` ${led?'LED':txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
});
}
}
// https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option
function addDropdown(field) {
let sel = cE('select');
sel.classList.add("pin");
let inp = d.getElementsByName(field)[0];
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName
let v = inp.value;
let n = inp.name;
// copy the existing input element's attributes to the new select element
for (var i = 0; i < inp.attributes.length; ++ i) {
var att = inp.attributes[i];
// type and value don't apply, so skip them
// ** you might also want to skip style, or others -- modify as needed **
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') {
sel.setAttribute(att.name, att.value);
}
}
sel.setAttribute("data-val", v);
sel.setAttribute("onchange", "pinUpd(this)");
// finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp);
return sel;
}
return null;
}
function addOption(sel,txt,val) {
if (sel===null) return; // select object missing
let opt = cE("option");
opt.value = val;
opt.text = txt;
sel.appendChild(opt);
for (let i=0; i<sel.childNodes.length; i++) {
let c = sel.childNodes[i];
if (c.value == sel.dataset.val) sel.selectedIndex = i;
}
return opt;
}
// calculate channel usage across all buses
function getDuse() {
let rmtUsed = 0, i2sUsed = 0;
+34 -1
View File
@@ -16,11 +16,44 @@
var pinsTimer=null, gpioInfo={};
function S() {
getLoc();
fetchPinInfo(()=>{ loadPins(); pinsTimer=setInterval(loadPins,250); });
loadJS(getURL('/settings/s.js?p=11'), false, ()=>{
d.um_p = [];
d.rsvd = [];
d.ro_gpio = [];
d.max_gpio = 50;
d.touch = [];
}, ()=>{
// Load extended GPIO info and start pin polling
loadPins();
pinsTimer = setInterval(loadPins, 250);
});
}
function B(){window.open(getURL('/settings'),'_self');} // back button
function getOwnerName(o,t,n) {
// Use firmware-provided name if available
if(n) return n;
if(!o) return "System"; // no owner provided
if(o===0x85){ return getBtnTypeName(t); } // button pin
return "UM #"+o;
}
function getBtnTypeName(t) {
var n=["None","Reserved","Push","Push Inv","Switch","PIR","Touch","Analog","Analog Inv","Touch Switch"];
var label = n[t] || "?";
return 'Button <span style="font-size:10px;color:#888">'+label+'</span>';
}
function getCaps(p,c) {
var r=[];
// Use touch info from settings endpoint
if(d.touch && d.touch.includes(p)) r.push("Touch");
if(d.ro_gpio && d.ro_gpio.includes(p)) r.push("Input Only");
// Use other caps from JSON (Analog, Boot, Input Only)
if(c&0x02) r.push("Analog");
if(c&0x08) r.push("Flash Boot");
if(c&0x10) r.push("Bootstrap");
return r.length?r.join(", "):"-";
}
function loadPins() {
fetch(getURL('/json/pins'),{method:'get'})
.then(r=>r.json())
+1 -1
View File
@@ -62,7 +62,7 @@
</div>
<div class="sec" id="OTA">
<h3>Software Update</h3>
<button type="button" onclick="U()">Update WLED</button><br>
<button type="button" onclick="U()">Manual OTA Update</button><br>
<div id="aOTA">Enable ArduinoOTA: <input type="checkbox" name="AO"></div>
Only allow update from same network/WiFi: <input type="checkbox" name="SU"><br>
<i class="warn">&#9888; If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.<br>
+10 -30
View File
@@ -42,30 +42,12 @@
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();}
function S(){
getLoc();
loadJS(getURL('/settings/s.js?p=4'), false, undefined, ()=>{SetVal(); fetchPinInfo(pinDropdowns);}); // If we set async false, file is loaded and executed, then next statement is processed
loadJS(getURL('/settings/s.js?p=4'), false, undefined, ()=>{SetVal();}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/sync');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
const dmxNs = ["IDMR","IDMT","IDME"];
let dmxOwnPins; // track dmx pins to update dropdown restrictions on pin change
function pinDropdowns() {
dmxNs.forEach((n,i) => { const s = makePinSelect(n, i?1:0); if (s) s.onchange = dmxPinUpd; }); // note on the i?1:0 -> RX pin is allowed input only but TX/EN pins must be GPIO
dmxOwnPins = dmxNs.map(n => parseInt(gN(n)?.value??-1));
dmxPinUpd();
}
function dmxPinUpd() {
const vs = dmxNs.map(n => parseInt(gN(n)?.value??-1));
dmxNs.forEach((n,i) => {
for (const o of gN(n)?.options??[]) {
const p = parseInt(o.value); if (p<0) continue;
// unlock old selected pin, lock newly selected one
o.disabled = (p!==vs[i] && !dmxOwnPins.includes(p) && !!d.pinsData?.find(pi=>pi.p===p)?.a) || vs.some((v,j)=>j!==i&&v===p);
if (!o.disabled) o.text = String(p); // clear owner name i.e. (DMX input)
}
});
}
function hideDMXInput(){gId("dmxInput").style.display="none";}
function hideNoDMXInput(){gId("dmxInputOff").style.display="none";}
function hideNoDMXOutput(){gId("dmxOnOffOutput").style.display="none";}
@@ -144,8 +126,8 @@ Send notifications on direct change: <input type="checkbox" name="SD"><br>
Send notifications on button press or IR: <input type="checkbox" name="SB"><br>
Send Alexa notifications: <input type="checkbox" name="SA"><br>
Send Philips Hue change notifications: <input type="checkbox" name="SH"><br>
UDP packet retransmissions: <input name="UR" type="number" min="0" max="30" class="d5" required><br>
<i class="warn">Reboot required to apply changes. </i>
UDP packet retransmissions: <input name="UR" type="number" min="0" max="30" class="d5" required><br><br>
<i>Reboot required to apply changes. </i>
</div>
<div class="sec">
<h3>Instance List</h3>
@@ -157,7 +139,7 @@ Make this instance discoverable: <input type="checkbox" name="NB">
Receive UDP realtime: <input type="checkbox" name="RD"><br>
Use main segment only: <input type="checkbox" name="MO"><br>
Respect LED Maps: <input type="checkbox" name="RLM"><br><br>
<h4>Network DMX input</h4><br>
<i>Network DMX input</i><br>
Type:
<select name=DI onchange="SP(); adj();">
<option value=5568>E1.31 (sACN)</option>
@@ -192,14 +174,12 @@ Force max brightness: <input type="checkbox" name="FB"><br>
Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required>
<div id="dmxInput">
<br>
<h4>Wired DMX Input</h4>
DMX RX Pin: <input name="IDMR" type="number" min="-1" max="99">RO<br/>
DMX TX Pin: <input name="IDMT" type="number" min="-1" max="99">DI<br/>
DMX Enable Pin: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/>
<h4>Wired DMX Input Pins</h4>
DMX RX: <input name="IDMR" type="number" min="-1" max="99">RO<br/>
DMX TX: <input name="IDMT" type="number" min="-1" max="99">DI<br/>
DMX Enable: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/>
DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/>
<i class="warn">Reboot required to apply changes.</i>
</div>
</div>
<div id="dmxInputOff">
<br><i class="warn">This firmware build does not include DMX Input support. <br></i>
</div>
@@ -243,7 +223,7 @@ Device Topic: <input type="text" name="MD" maxlength="32"><br>
Group Topic: <input type="text" name="MG" maxlength="32"><br>
Publish on button press: <input type="checkbox" name="BM"><br>
Retain brightness & color messages: <input type="checkbox" name="RT"><br>
<i class="warn">Reboot required to apply changes. </i><a href="https://kno.wled.ge/interfaces/mqtt/" target="_blank">MQTT info</a>
<i>Reboot required to apply changes. </i><a href="https://kno.wled.ge/interfaces/mqtt/" target="_blank">MQTT info</a>
</div>
</div>
<div class="sec">
+24 -89
View File
@@ -190,7 +190,7 @@
}
}
function pMP() { // populateMacroPresets
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
var fields = ['A0','A1','MC','MN'];
for (var f of fields) {
var inp = gN(f);
@@ -219,38 +219,13 @@
rPS(sel, presetOpts, "data-preset");
}
}
function bAO() { // buildAnalogOptions: analog functions + per-segment opacity (segment 0 included; MD=0 => segment 0)
var o = '<optgroup label="Analog Functions"><option value="250">Global brightness (250)</option><option value="249">Effect speed (249)</option><option value="248">Effect intensity (248)</option><option value="247">Palette (247)</option><option value="200">Primary color hue (200)</option></optgroup><optgroup label="Analog Segment Opacity">';
for (var j=0; j<=32; j++) o += `<option value="${j}">Segment ${j} opacity</option>`;
o += '</optgroup>';
return o;
}
function isAnalogBtn(t) { return t==7 || t==8; } // BTN_TYPE_ANALOG / BTN_TYPE_ANALOG_INVERTED
function isSwitchBtn(t) { return t==4 || t==5 || t==9; } // BTN_TYPE_SWITCH / BTN_TYPE_PIR_SENSOR / BTN_TYPE_TOUCH_SWITCH
function btnTypeName(t) { // mirrors the button type dropdown on the LED settings page
switch (+t) {
case 2: return 'Pushbutton';
case 3: return 'Push inverted';
case 4: return 'Switch';
case 5: return 'PIR sensor';
case 6: return 'Touch';
case 7: return 'Analog';
case 8: return 'Analog inverted';
case 9: return 'Touch (switch)';
default: return 'Disabled';
}
}
function rBPO() { // refreshButtonPresetOptions
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
var analogOpts = bAO();
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
var container = gId("macros");
if (!container) return;
// analog buttons only have an MD select (MP/ML are hidden 0 inputs); MD uses analog options, never presets
var sels = container.querySelectorAll('select[name^="MP"],select[name^="ML"],select[name^="MD"]');
for (var sel of sels) {
var bb = sel.closest ? sel.closest(".bb") : null;
var t = bb ? parseInt(bb.getAttribute("data-btype")||"0",10) : 0;
rPS(sel, isAnalogBtn(t) ? analogOpts : presetOpts, "data-preset");
rPS(sel, presetOpts, "data-preset");
}
}
function Wd()
@@ -271,73 +246,33 @@
if (d.Sf.LTR.value==="S") { d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); }
if (d.Sf.LNR.value==="W") { d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); }
}
function addRow(i,p,l,d,t) {
if (t===undefined) t = 0;
function addRow(i,p,l,d) {
var b = String.fromCharCode((i<10?48:55)+i);
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
var typeName = btnTypeName(t);
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
var buttonBlock = document.createElement('div');
buttonBlock.className = 'bb';
buttonBlock.setAttribute('data-btype', t); // read back by rBPO() to rebuild selects correctly
if (isAnalogBtn(t)) {
// analog buttons: MD holds the function/segment; short/long press are unused (firmware defaults missing MP/ML to 0)
buttonBlock.innerHTML = `
<div class="bh">Analog ${i} - ${typeName}</div>
<div class="bs">
<div class="ba">
<label>Analog function</label>
<select name="MD${b}" class="s" required>${bAO()}</select>
</div>
buttonBlock.innerHTML = `
<div class="bh">Button ${i}</div>
<div class="bs">
<div class="ba">
<label>Push/Switch</label>
<select name="MP${b}" class="s" required>${presetOpts}</select>
</div>
<hr style="width:100%;margin:8px 0 0 0;">
`;
sPSV(buttonBlock.querySelector('select[name="MD'+b+'"]'), String(d), "data-preset");
} else if (isSwitchBtn(t)) {
// switches: MP fires on On->Off, ML on Off->On; double press (MD) is unused (firmware defaults missing MD to 0)
buttonBlock.innerHTML = `
<div class="bh">Switch ${i} - ${typeName}</div>
<div class="bs">
<div class="ba">
<label>On → Off</label>
<select name="MP${b}" class="s" required>${presetOpts}</select>
</div>
<div class="ba">
<label>Off → On</label>
<select name="ML${b}" class="s" required>${presetOpts}</select>
</div>
<div class="ba">
<label>Short (on→off)</label>
<select name="ML${b}" class="s" required>${presetOpts}</select>
</div>
<hr style="width:100%;margin:8px 0 0 0;">
`;
var switchSels = buttonBlock.querySelectorAll("select");
var switchVals = [String(p), String(l)];
for (var si=0; si<switchSels.length; si++) {
sPSV(switchSels[si], switchVals[si], "data-preset");
}
} else {
// pushbuttons: short (MP), long (ML) and double (MD) press
buttonBlock.innerHTML = `
<div class="bh">Button ${i} - ${typeName}</div>
<div class="bs">
<div class="ba">
<label>Short press</label>
<select name="MP${b}" class="s" required>${presetOpts}</select>
</div>
<div class="ba">
<label>Long press</label>
<select name="ML${b}" class="s" required>${presetOpts}</select>
</div>
<div class="ba">
<label>Double press</label>
<select name="MD${b}" class="s" required>${presetOpts}</select>
</div>
<div class="ba">
<label>Long (off→on)</label>
<select name="MD${b}" class="s" required>${presetOpts}</select>
</div>
<hr style="width:100%;margin:8px 0 0 0;">
`;
var buttonSels = buttonBlock.querySelectorAll("select");
var buttonVals = [String(p), String(l), String(d)];
for (var si=0; si<buttonSels.length; si++) {
sPSV(buttonSels[si], buttonVals[si], "data-preset");
}
</div>
<hr style="width:100%;margin:8px 0 0 0;">
`;
var buttonSels = buttonBlock.querySelectorAll("select");
var buttonVals = [String(p), String(l), String(d)];
for (var si=0; si<buttonSels.length; si++) {
sPSV(buttonSels[si], buttonVals[si], "data-preset");
}
gId("macros").appendChild(buttonBlock);
}
+33 -10
View File
@@ -7,6 +7,7 @@
<style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script>
var umCfg = {};
var pins = [], pinO = [], owner;
var urows;
var numM = 0;
// load common.js with retry on error
@@ -31,6 +32,7 @@
})
.then(json => {
umCfg = json.um;
getPins(json);
urows="";
if (isO(umCfg)) {
for (const [k,o] of Object.entries(umCfg)) {
@@ -48,9 +50,11 @@
d.ro_gpio = [];
d.extra = [];
}, ()=>{
for (let r of d.rsvd) { pins.push(r); pinO.push("rsvd"); } // reserved pins
if (d.um_p[0]==-1) d.um_p.shift(); // remove filler
d.Sf.SDA.max = d.Sf.SCL.max = d.Sf.MOSI.max = d.Sf.SCLK.max = d.Sf.MISO.max = d.max_gpio;
fetchPinInfo(pinDD); // load pin occupancy, then convert INPUT to SELECT for pins
//for (let i of d.getElementsByTagName("input")) if (i.type === "number" && i.name.replace("[]","").substr(-3) === "pin") i.max = d.max_gpio;
pinDD(); // convert INPUT to SELECT for pins
}); // If we set async false, file is loaded and executed, then next statement is processed
})
.catch((error)=>{
@@ -86,7 +90,28 @@
}
*/
}
function getPins(o) {
if (isO(o)) {
for (const [k,v] of Object.entries(o)) {
if (isO(v)) {
let oldO = owner; // keep parent name
owner = k;
getPins(v);
owner = oldO;
continue;
}
if (k.replace("[]","").substr(-3)=="pin") {
if (Array.isArray(v)) {
for (var i=0; i<v.length; i++) if (v[i]>=0) { pins.push(v[i]); pinO.push(owner); }
} else {
if (v>=0) { pins.push(v); pinO.push(owner); }
}
} else if (Array.isArray(v)) {
for (var i=0; i<v.length; i++) getPins(v[i]);
}
}
}
}
function initCap(s) {
if (typeof s !== 'string') return '';
// https://www.freecodecamp.org/news/how-to-capitalize-words-in-javascript/
@@ -136,17 +161,15 @@
if (i.type === "number" && (i.name.includes("pin") || ["SDA","SCL","MOSI","MISO","SCLK"].includes(i.name))) { //select all pin select elements
let v = parseInt(i.value);
let sel = addDD(i.name,0);
if (!sel) continue;
for (var j = -1; j < d.max_gpio; j++) {
if (j > -1 && d.rsvd && d.rsvd.includes(j)) continue;
let pInfo = d.pinsData && d.pinsData.find(p => p.p === j);
let used = j > -1 && pInfo && pInfo.a && j !== v; // add owner info if used and not "our" pin
if (d.rsvd.includes(j)) continue;
let foundPin = pins.indexOf(j);
let txt = (j === -1) ? "unused" : `${j}`;
if (used) txt += ` (${getOwnerName(pInfo.o, pInfo.t, pInfo.n)})`;
if (j > -1 && d.ro_gpio && d.ro_gpio.includes(j)) txt += " (input only)"; // read-only pin
if (foundPin >= 0 && j !== v) txt += ` ${pinO[foundPin]=="if"?"global":pinO[foundPin]}`; // already reserved pin
if (d.ro_gpio.includes(j)) txt += " (R/O)";
let opt = addO(sel, txt, j);
if (j === v) opt.selected = true; // this is "our" pin
else if (used) opt.disabled = true; // someone else's pin
else if (pins.includes(j)) opt.disabled = true; // someone else's pin
}
let um = i.name.split(":")[0];
d.extra.forEach((o)=>{
@@ -181,7 +204,7 @@
i.disabled = true;
i.text += ` ${txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (input only)"; // read-only pin
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
}
});
}
+2 -4
View File
@@ -236,10 +236,8 @@ Static subnet mask:<br>
<option value="11">ESP32-POE-WROVER</option>
<option value="7">KIT-VE</option>
<option value="12">LILYGO T-POE Pro</option>
<option value="4">QuinLED Uno/Quad</option>
<option value="8">QuinLED Octa & T-ETH-POE</option>
<option value="14">QuinLED v4 Uno/Quad</option>
<option value="15">QuinLED v4 Octa</option>
<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option>
<option value="4">QuinLED-ESP32</option>
<option value="10">Serg74-ETH32</option>
<option value="5">TwilightLord-ESP32</option>
<option value="3">WESP32</option>
+9 -71
View File
@@ -7,7 +7,6 @@
<script>
function B() { window.history.back(); }
var cnfr = false;
var deviceInfo = null;
function cR() {
if (!cnfr) {
var bt = gId('rev');
@@ -23,30 +22,15 @@
fetch(getURL('/json/info'))
.then(response => response.json())
.then(data => {
deviceInfo = data;
document.querySelector('.installed-version').textContent = `${data.brand} ${data.ver} (${data.vid})`;
const repoUrl = data.repo && data.repo !== "unknown" ? "https://github.com/" + data.repo + "/releases/latest" : null;
document.querySelector('.release-name').textContent = data.release;
// assemble update URL and update release badge
if (repoUrl) {
if (data.repo && data.repo !== "unknown") {
const repoUrl = "https://github.com/" + data.repo + "/releases/latest";
const badgeUrl = "https://img.shields.io/github/release/" + data.repo + ".svg?style=flat-square";
document.querySelector('.release-repo').href = repoUrl;
document.querySelector('.release-badge').src = badgeUrl;
toggle('release-download'); // only show release download item after receiving a valid data.repo
// fetch latest release from GitHub to build direct bin download link
fetch(`https://api.github.com/repos/${data.repo}/releases/latest`)
.then(r => r.json())
.then(gh => {
const ver = gh.tag_name.replace(/^v/, '');
const suffix = `_${ver}_${data.release}.bin`;
const asset = gh.assets.find(a => a.name.endsWith(suffix));
if (asset) {
const binUrl = asset.browser_download_url.replace(/^https?:\/\/[^/]+/, 'https://download.wled.me');
const el = document.querySelector('.release-name');
el.innerHTML = `<a href="${binUrl}">${data.release}</a>`;
}
})
.catch(() => {}); // silently ignore if GitHub API unavailable
} else {
gId('Norelease-download').classList.add("hide"); // repo invalid -> hide everything
}
@@ -76,48 +60,6 @@
gId('bootupd').classList.add("hide");
toggle('upd');
}
async function autoUpdate() {
const btn = gId('autoUpdBtn');
const status = gId('autoUpdStatus');
btn.disabled = true;
status.textContent = '';
try {
const info = deviceInfo;
if (!info || !info.repo || info.repo === 'unknown') {
status.textContent = 'No release repository available for this build.';
btn.disabled = false;
return;
}
status.textContent = 'Checking GitHub for latest release...';
const ghResp = await fetch(`https://api.github.com/repos/${info.repo}/releases/latest`);
if (!ghResp.ok) throw new Error(`GitHub API error: ${ghResp.status}`);
const ghRelease = await ghResp.json();
const releaseVer = ghRelease.tag_name.replace(/^v/, '');
const assetSuffix = `_${releaseVer}_${info.release}.bin`;
const asset = ghRelease.assets.find(a => a.name.endsWith(assetSuffix));
if (!asset) {
const available = ghRelease.assets.map(a => a.name).join(', ');
status.textContent = `Firmware not found (*${assetSuffix}). Available: ${available}`;
btn.disabled = false;
return;
}
status.textContent = `Downloading ${asset.name} (${Math.round(asset.size/1024)} KB)...`;
const fwUrl = asset.browser_download_url.replace(/^https?:\/\/[^/]+/, 'https://download.wled.me');
const fwResp = await fetch(fwUrl);
if (!fwResp.ok) throw new Error(`Download failed: ${fwResp.status}`);
const blob = await fwResp.blob();
const file = new File([blob], asset.name, {type: 'application/octet-stream'});
const dt = new DataTransfer();
dt.items.add(file);
document.querySelector('input[name="update"]').files = dt.files;
status.textContent = `Downloaded ${asset.name}. Uploading to device...`;
hideforms();
gId('upd').submit();
} catch(e) {
status.textContent = 'Auto-update failed: ' + e.message;
btn.disabled = false;
}
}
</script>
<style>
@import url("style.css");
@@ -135,25 +77,21 @@
<p>
Installed version: <span class="sip installed-version">Loading...</span><br>
Release: <span class="sip release-name">Loading...</span><br>
<span id="Norelease-download">Latest release: Checking...<br></span>
<span id="Norelease-download">Latest binary: Checking...<br></span>
<span id="release-download" class="hide">
Latest version: <a class="release-repo" href="https://github.com/wled/WLED/releases/latest" target="_blank" rel="noopener noreferrer"
Download the latest binary: <a class="release-repo" href="https://github.com/wled/WLED/releases/latest" target="_blank" rel="noopener noreferrer"
style="vertical-align: text-bottom; display: inline-flex;">
<img class="release-badge" alt="View latest"></a><br>
<button type="button" id="autoUpdBtn" onclick="autoUpdate()">Auto update</button>
<span id="autoUpdStatus" style="display:block;margin-top:4px;font-size:13px;"></span>
<img class="release-badge" alt="badge"></a><br> <!-- start with an empty placeholder, src will be filled after fetching /json/info -->
</span>
</p>
<hr class="sml">
<h3>Manual upload</h3>
<input type="hidden" name="skipValidation" value="" id="sV">
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
<input type='checkbox' onchange="sV.value=checked?1:''" id="skipValidation">
<label for='skipValidation'>Ignore firmware validation</label><br>
<button type="submit">Upload</button><br>
<span>
<button type="submit">Update WLED!</button><br>
<span id="rev">
<hr class="sml">
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
<button type="button" onclick="cR()">Revert update</button><br>
</span>
</div>
</form>
@@ -169,4 +107,4 @@
<div id="Noupd" class="hide sec"><b>Updating...</b><br>Please do not close or refresh the page :)</div>
<p><button id="backbtn" type="button" onclick="B()">Back</button></p>
</body>
</html>
</html>
+2 -2
View File
@@ -122,7 +122,7 @@ bool DMXInput::installDriver()
return true;
}
void DMXInput::init(int8_t rxPin, int8_t txPin, int8_t enPin, uint8_t inputPortNum)
void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum)
{
#ifdef WLED_ENABLE_DMX_OUTPUT
@@ -142,7 +142,7 @@ void DMXInput::init(int8_t rxPin, int8_t txPin, int8_t enPin, uint8_t inputPortN
return;
}
if (rxPin >= 0 && enPin >= 0 && txPin >= 0) {
if (rxPin > 0 && enPin > 0 && txPin > 0) {
const managed_pin_type pins[] = {
{(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false.
+5 -5
View File
@@ -12,7 +12,7 @@
class DMXInput
{
public:
void init(int8_t rxPin, int8_t txPin, int8_t enPin, uint8_t inputPortNum);
void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum);
void update();
/**disable dmx receiver (do this before disabling the cache)*/
@@ -54,9 +54,9 @@ private:
friend void dmxReceiverTask(void * context);
uint8_t inputPortNum = 255;
int8_t rxPin = -1;
int8_t txPin = -1;
int8_t enPin = -1;
uint8_t rxPin = 255;
uint8_t txPin = 255;
uint8_t enPin = 255;
/// is written to by the dmx receive task.
byte dmxdata[DMX_PACKET_SIZE];
@@ -72,5 +72,5 @@ private:
TaskHandle_t task;
/// Guards access to dmxData
std::mutex dmxDataLock;
};
+11
View File
@@ -20,4 +20,15 @@ Macros for generating a "dynamic array", a static array of objects declared in d
#define DYNARRAY_END(array_name) array_name##_end
#define DYNARRAY_LENGTH(array_name) (&DYNARRAY_END(array_name)[0] - &DYNARRAY_BEGIN(array_name)[0])
#ifdef ESP8266
// ESP8266 linker script cannot be extended with a unique section for dynamic arrays.
// We instead pack them in the ".dtors" section, as it's sorted and uploaded to the flash
// (but will never be used in the embedded system)
#define DYNARRAY_SECTION ".dtors"
#else /* ESP8266 */
// Use a unique named section; the linker script must be extended to ensure it's correctly placed.
#define DYNARRAY_SECTION ".dynarray"
#endif
+15 -44
View File
@@ -5,7 +5,7 @@
#define MAX_CHANNELS_PER_UNIVERSE 512
// forward declarations
static void handleDDPPacket(e131_packet_t* p, size_t packetLen);
static void handleDDPPacket(e131_packet_t* p);
static void handleArtnetPollReply(IPAddress ipAddress);
static void prepareArtnetPollReply(ArtPollReply *reply);
static void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t portAddress);
@@ -17,31 +17,19 @@ static void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16
//DDP protocol support, called by handleE131Packet
//handles RGB data only
static void handleDDPPacket(e131_packet_t* p, size_t packetLen) {
static void handleDDPPacket(e131_packet_t* p) {
static bool ddpSeenPush = false; // have we seen a push yet?
int lastPushSeq = e131LastSequenceNumber[0];
if (packetLen < DDP_HEADER_LEN) return; // too short to safely read any DDP header fields
// reject unsupported color data types (only RGB and RGBW are supported)
//uint8_t maskedType = p->dataType & 0x3F; // mask out custom and reserved flags, only type bits are relevant
//if (maskedType != DDP_TYPE_RGB24 && maskedType != DDP_TYPE_RGBW32) return;
if (p->dataType != DDP_TYPE_RGB24 && p->dataType != DDP_TYPE_RGBW32) return;
// note: for maximum compatibility we do not reject unknonw or malformed data types but simply default to RGB24 and check there is enough data available in the packet to do so
// also we assume 8bit per channel and currently do not support other bit depths
// reject status and config packets (not implemented)
if (p->destination == DDP_ID_STATUS || p->destination == DDP_ID_CONFIG) return;
// reject control, status and config packets (not implemented)
if (p->destination == DDP_ID_CONTROL || p->destination == DDP_ID_STATUS || p->destination == DDP_ID_CONFIG) return;
// reject query and response packets (not implemented)
if (p->flags & (DDP_FLAGS_QUERY | DDP_FLAGS_REPLY)) return;
bool push = p->flags & DDP_FLAGS_PUSH; // push flag means "render now"
if (!push && (p->flags & DDP_FLAGS_STORAGE)) return; // reject "from storage" flag but still let the push flag pass if set along with it
//reject late packets belonging to previous frame (assuming 4 packets max. before push, if more are used and packets are very late, they are still accepted)
//reject late packets belonging to previous frame (assuming 4 packets max. before push)
if (e131SkipOutOfSequence && lastPushSeq) {
int sn = p->sequenceNum & 0xF; // sequence number is 4 bits, 1-15, 0 means unused
int sn = p->sequenceNum & 0xF;
if (sn) {
if (lastPushSeq > 5) {
if (sn > (lastPushSeq -5) && sn < lastPushSeq) return;
@@ -51,8 +39,7 @@ static void handleDDPPacket(e131_packet_t* p, size_t packetLen) {
}
}
unsigned ddpChannelsPerLed = 3; // default to RGB
if ((p->dataType & 0b00111000)>>3 == 0b011) ddpChannelsPerLed = 4; // RGBW data type (see DDP protocol definition)
unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel)
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
start += DMXAddress / ddpChannelsPerLed;
@@ -62,14 +49,8 @@ static void handleDDPPacket(e131_packet_t* p, size_t packetLen) {
unsigned c = 0;
if (p->flags & DDP_FLAGS_TIME) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
// ensure the received packet is at least as long as the header claims
if (packetLen < DDP_HEADER_LEN + c + dataLen) {
DEBUG_PRINTLN(F("DDP packet incomplete"));
return;
}
unsigned numLeds = stop - start; // stop >= start is guaranteed
unsigned maxDataIndex = numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
if (maxDataIndex > dataLen) {
DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting."));
return;
@@ -84,6 +65,7 @@ static void handleDDPPacket(e131_packet_t* p, size_t packetLen) {
}
}
bool push = p->flags & DDP_FLAGS_PUSH;
ddpSeenPush |= push;
if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display
e131NewData = true;
@@ -93,7 +75,7 @@ static void handleDDPPacket(e131_packet_t* p, size_t packetLen) {
}
//E1.31 and Art-Net protocol support
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen){
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
int uni = 0, dmxChannels = 0;
uint8_t* e131_data = nullptr;
@@ -101,33 +83,24 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_
if (protocol == P_ARTNET)
{
if (packetLen < 10) return; // need at least art_opcode (offset 8, 2 bytes)
if (p->art_opcode == ARTNET_OPCODE_OPPOLL) {
handleArtnetPollReply(clientIP);
return;
}
if (packetLen < 18) return; // need art_length (offset 16, 2 bytes) for DMX data
uni = p->art_universe;
dmxChannels = htons(p->art_length);
const int artNetMaxData = (packetLen >= 18) ? (int)(packetLen - 18) : 0; // art_data at offset 18; clamp so e131_data[dmxChannels] stays in bounds
if (dmxChannels > artNetMaxData) dmxChannels = artNetMaxData;
if (dmxChannels > MAX_CHANNELS_PER_UNIVERSE) dmxChannels = MAX_CHANNELS_PER_UNIVERSE;
e131_data = p->art_data;
seq = p->art_sequence_number;
mde = REALTIME_MODE_ARTNET;
} else if (protocol == P_E131) {
if (packetLen < 126) return; // need up to property_values[0] (offset 125) and property_value_count (offset 123)
// Ignore PREVIEW data (E1.31: 6.2.6)
if ((p->options & 0x80) != 0) return;
dmxChannels = htons(p->property_value_count) - 1; // on malformed packets, this can become negative, checked below
dmxChannels = htons(p->property_value_count) - 1;
// DMX level data is zero start code. Ignore everything else. (E1.11: 8.5)
if (dmxChannels <= 0 || p->property_values[0] != 0) return;
if (dmxChannels == 0 || p->property_values[0] != 0) return;
uni = htons(p->universe);
e131_data = p->property_values;
seq = p->sequence_number;
const int e131MaxData = (packetLen > 126) ? (int)(packetLen - 126) : 0; // property_values at offset 125; clamp so e131_data[dmxChannels] stays in bounds
if (dmxChannels > e131MaxData) dmxChannels = e131MaxData;
if (dmxChannels > MAX_CHANNELS_PER_UNIVERSE) dmxChannels = MAX_CHANNELS_PER_UNIVERSE;
if (e131Priority != 0) {
if (p->priority < e131Priority ) return;
// track highest priority & skip all lower priorities
@@ -136,17 +109,15 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_
}
} else { //DDP
realtimeIP = clientIP;
handleDDPPacket(p, packetLen);
handleDDPPacket(p);
return;
}
#ifdef WLED_ENABLE_DMX
// does not act on out-of-order packets yet
if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) {
// Art-Net: art_data is 0-indexed (channel 1 at index 0)
// E1.31: property_values[0] is start code, (channel 1 at index 1)
for (uint16_t i = 1; i <= dmxChannels; i++)
dmx.write(i, mde == REALTIME_MODE_ARTNET ? e131_data[i-1] : e131_data[i]);
dmx.write(i, e131_data[i]);
dmx.update();
}
#endif
+6 -5
View File
@@ -100,7 +100,7 @@ void initDMXInput();
void handleDMXInput();
//e131.cpp
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen);
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol);
void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses);
// void handleArtnetPollReply(IPAddress ipAddress); // local function, only used in e131.cpp
// void prepareArtnetPollReply(ArtPollReply* reply); // local function, only used in e131.cpp
@@ -176,6 +176,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray arr);
void serializeModeData(JsonArray fxdata);
void serializePins(JsonObject root);
void serveJson(AsyncWebServerRequest* request);
#ifdef WLED_ENABLE_JSONLIVE
@@ -524,10 +525,10 @@ inline size_t getFreeHeapSize() { return ESP.getFreeHeap(); } // returns free he
inline size_t getContiguousFreeHeap() { return ESP.getMaxFreeBlockSize(); } // returns largest contiguous free block
#endif
#define BFRALLOC_NOBYTEACCESS (1 << 0) // ESP32 has 32bit accessible DRAM (usually ~50kB free) that must not be byte-accessed
#define BFRALLOC_PREFER_DRAM (1 << 1) // prefer DRAM over PSRAM (can still use PSRAM for larger allocations if DRAM is starting to run low)
#define BFRALLOC_ENFORCE_DRAM (1 << 2) // use DRAM only, no PSRAM allowed
#define BFRALLOC_PREFER_PSRAM (1 << 3) // prefer PSRAM over DRAM (can still use DRAM if there is loads of free DRAM to optimize speed)
#define BFRALLOC_ENFORCE_PSRAM (1 << 4) // use PSRAM if available, falls back to DRAM if PSRAM fails
#define BFRALLOC_PREFER_DRAM (1 << 1) // prefer DRAM over PSRAM
#define BFRALLOC_ENFORCE_DRAM (1 << 2) // use DRAM only, no PSRAM
#define BFRALLOC_PREFER_PSRAM (1 << 3) // prefer PSRAM over DRAM
#define BFRALLOC_ENFORCE_PSRAM (1 << 4) // use PSRAM if available, otherwise uses DRAM
#define BFRALLOC_CLEAR (1 << 5) // clear allocated buffer after allocation
void *allocate_buffer(size_t size, uint32_t type);
+8 -3
View File
@@ -355,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() {
+7 -6
View File
@@ -116,8 +116,8 @@ void handleImprovPacket() {
return;
}
} else if (packetByte > 9) { //RPC data
if (packetByte > 137) return; //prevent buffer overflow
rpcData[packetByte - 10] = next;
if (packetByte > 137) return; //prevent buffer overflow
}
}
}
@@ -238,8 +238,8 @@ void handleImprovWifiScan() {
bool isOpen = WiFi.encryptionType(i) == WIFI_AUTH_OPEN;
#endif
char ssidStr[33] = {'\0'};
strlcpy(ssidStr, WiFi.SSID(i).c_str(), sizeof(ssidStr));
char ssidStr[33];
strcpy(ssidStr, WiFi.SSID(i).c_str());
const char *str[3] = {ssidStr, rssiStr, isOpen ? "NO":"YES"};
sendImprovRPCResult(ImprovRPCType::Request_Scan, 3, str);
}
@@ -258,13 +258,14 @@ static void parseWiFiCommand(char* rpcData) {
unsigned ssidLen = rpcData[1];
if (ssidLen > len -1 || ssidLen > 32) return;
memset(multiWiFi[0].clientSSID, 0, sizeof(multiWiFi[0].clientSSID));
memset(multiWiFi[0].clientSSID, 0, 32);
memcpy(multiWiFi[0].clientSSID, rpcData+2, ssidLen);
memset(multiWiFi[0].clientPass, 0, sizeof(multiWiFi[0].clientPass));
memset(multiWiFi[0].clientPass, 0, 64);
if (len > ssidLen +1) {
unsigned passLen = rpcData[2+ssidLen];
memcpy(multiWiFi[0].clientPass, rpcData+3+ssidLen, min(size_t(passLen), sizeof(multiWiFi[0].clientPass)));
memset(multiWiFi[0].clientPass, 0, 64);
memcpy(multiWiFi[0].clientPass, rpcData+3+ssidLen, passLen);
}
sendImprovStateResponse(0x03); //provisioning
+28 -109
View File
@@ -520,9 +520,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (root["win"].isNull() && getVal(root["ps"], presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) {
DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr);
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
// async load from file system (only preset ID was specified)
// avoid propogating CALL_MODE_INIT, which may cause accidental recursion
applyPreset(presetCycCurr, callMode == CALL_MODE_INIT ? CALL_MODE_DIRECT_CHANGE : callMode);
applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified)
return stateResponse;
} else presetCycCurr = currentPreset; // restore presetCycCurr
}
@@ -763,7 +761,6 @@ void serializeInfo(JsonObject root)
case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break;
case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break;
case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break;
case REALTIME_MODE_DMX: root["lm"] = F("DMX"); break;
}
root[F("lip")] = realtimeIP[0] == 0 ? "" : realtimeIP.toString();
@@ -776,18 +773,8 @@ void serializeInfo(JsonObject root)
root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = getPaletteCount();
root[F("cpalcount")] = customPalettes.size(); // number of user custom palettes (includes gray placeholders)
root[F("umpalcount")] = usermodPalettes.size(); // number of usermod-registered palettes
root[F("cpalcount")] = customPalettes.size(); // number of custom palettes
root[F("cpalmax")] = WLED_MAX_CUSTOM_PALETTES; // maximum number of custom palettes
// send usermod palette names so the UI can label them correctly
if (usermodPalettes.size() > 0) {
JsonArray umpalnames = root.createNestedArray(F("umpalnames"));
for (size_t j = 0; j < usermodPalettes.size(); j++) {
char buf[34];
extractModeName(WLED_USERMOD_PALETTE_ID_BASE - j, JSON_palette_names, buf, sizeof(buf) - 1);
umpalnames.add(buf);
}
}
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
@@ -958,28 +945,19 @@ void serializePalettes(JsonObject root, int page)
#endif
const int customPalettesCount = customPalettes.size();
const int umPalettesCount = usermodPalettes.size();
const int palettesCount = FIXED_PALETTE_COUNT; // palettesCount is number of palettes, not palette index
const int maxPage = (palettesCount + umPalettesCount + customPalettesCount) / itemPerPage;
const int maxPage = (palettesCount + customPalettesCount) / itemPerPage;
if (page > maxPage) page = maxPage;
const int start = itemPerPage * page;
int end = min(start + itemPerPage, palettesCount + umPalettesCount + customPalettesCount);
int end = min(start + itemPerPage, palettesCount + customPalettesCount);
root[F("m")] = maxPage; // inform caller how many pages there are
JsonObject palettes = root.createNestedObject("p");
for (int i = start; i < end; i++) {
// compute the palette ID for this sequential index
int paletteId;
if (i >= palettesCount + umPalettesCount) // user custom palette (IDs 200, 199, ...)
paletteId = WLED_CUSTOM_PALETTE_ID_BASE - (i - palettesCount - umPalettesCount);
else if (i >= palettesCount) // usermod palette (IDs 255, 254, ...)
paletteId = WLED_USERMOD_PALETTE_ID_BASE - (i - palettesCount);
else
paletteId = i; // fixed palette
JsonArray curPalette = palettes.createNestedArray(String(paletteId));
JsonArray curPalette = palettes.createNestedArray(String(i >= palettesCount ? 255 - i + palettesCount : i));
switch (i) {
case 0: //default palette
setPaletteColors(curPalette, PartyColors_gc22);
@@ -1008,13 +986,9 @@ void serializePalettes(JsonObject root, int page)
curPalette.add("c1");
break;
default:
if (i >= palettesCount + umPalettesCount) { // user custom palettes (lowest IDs in the custom range)
int custIdx = i - palettesCount - umPalettesCount;
setPaletteColors(curPalette, customPalettes[custIdx]);
} else if (i >= palettesCount) { // usermod palettes (IDs 255, 254, ...)
int umIdx = i - palettesCount;
setPaletteColors(curPalette, usermodPalettes[umIdx].palette);
} else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes
if (i >= palettesCount) // custom palettes
setPaletteColors(curPalette, customPalettes[i - palettesCount]);
else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i - DYNAMIC_PALETTE_COUNT]);
else {
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - (DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT)])), sizeof(tcp));
@@ -1186,6 +1160,21 @@ void serializePins(JsonObject root)
}
}
// deserializes mode data string into JsonArray
void serializeModeData(JsonArray fxdata)
{
char lineBuffer[256];
for (size_t i = 0; i < strip.getModeCount(); i++) {
strncpy_P(lineBuffer, strip.getModeData(i), sizeof(lineBuffer)/sizeof(char)-1);
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
if (lineBuffer[0] != 0) {
char* dataPtr = strchr(lineBuffer,'@');
if (dataPtr) fxdata.add(dataPtr+1);
else fxdata.add("");
}
}
}
// deserializes mode names string into JsonArray
// also removes effect data extensions (@...) from deserialised names
void serializeModeNames(JsonArray arr)
@@ -1202,78 +1191,6 @@ void serializeModeNames(JsonArray arr)
}
}
// Writes a JSON-escaped string (with surrounding quotes) into dest[0..maxLen-1].
// Returns bytes written, or 0 if the buffer was too small.
static size_t writeJSONString(uint8_t* dest, size_t maxLen, const char* src) {
size_t pos = 0;
auto emit = [&](char c) -> bool {
if (pos >= maxLen) return false;
dest[pos++] = (uint8_t)c;
return true;
};
if (!emit('"')) return 0;
for (const char* p = src; *p; ++p) {
char esc = ARDUINOJSON_NAMESPACE::EscapeSequence::escapeChar(*p);
if (esc) {
if (!emit('\\') || !emit(esc)) return 0;
} else {
if (!emit(*p)) return 0;
}
}
if (!emit('"')) return 0;
return pos;
}
// Writes ,"<escaped_src>" into dest[0..maxLen-1] (no null terminator).
// Returns bytes written, or 0 if the buffer was too small.
static size_t writeJSONStringElement(uint8_t* dest, size_t maxLen, const char* src) {
if (maxLen == 0) return 0;
dest[0] = ',';
size_t n = writeJSONString(dest + 1, maxLen - 1, src);
if (n == 0) return 0;
return 1 + n;
}
// Generate a streamed JSON response for the mode data
// This uses sendChunked to send the reply in blocks based on how much fit in the outbound
// packet buffer, minimizing the required state (ie. just the next index to send). This
// allows us to send an arbitrarily large response without using any significant amount of
// memory (so no worries about buffer limits).
void respondModeData(AsyncWebServerRequest* request) {
size_t fx_index = 0;
request->sendChunked(FPSTR(CONTENT_TYPE_JSON),
[fx_index](uint8_t* data, size_t len, size_t) mutable {
size_t bytes_written = 0;
char lineBuffer[256];
while (fx_index < strip.getModeCount()) {
strncpy_P(lineBuffer, strip.getModeData(fx_index), sizeof(lineBuffer)-1); // Copy to stack buffer for strchr
if (lineBuffer[0] != 0) {
lineBuffer[sizeof(lineBuffer)-1] = '\0'; // terminate string (only needed if strncpy filled the buffer)
const char* dataPtr = strchr(lineBuffer,'@'); // Find '@', if there is one
size_t mode_bytes = writeJSONStringElement(data, len, dataPtr ? dataPtr + 1 : "");
if (mode_bytes == 0) break; // didn't fit; break loop and try again next packet
if (fx_index == 0) *data = '[';
data += mode_bytes;
len -= mode_bytes;
bytes_written += mode_bytes;
}
++fx_index;
}
if ((fx_index == strip.getModeCount()) && (len >= 1)) {
*data = ']';
++bytes_written;
++fx_index; // we're really done
}
return bytes_written;
});
}
// Global buffer locking response helper class (to make sure lock is released when AsyncJsonResponse is destroyed)
class LockedJsonResponse: public AsyncJsonResponse {
bool _holding_lock;
@@ -1301,7 +1218,7 @@ class LockedJsonResponse: public AsyncJsonResponse {
void serveJson(AsyncWebServerRequest* request)
{
enum class json_target {
all, state, info, state_info, nodes, effects, palettes, networks, config, pins
all, state, info, state_info, nodes, effects, palettes, fxdata, networks, config, pins
};
json_target subJson = json_target::all;
@@ -1312,7 +1229,7 @@ void serveJson(AsyncWebServerRequest* request)
else if (url.indexOf(F("nodes")) > 0) subJson = json_target::nodes;
else if (url.indexOf(F("eff")) > 0) subJson = json_target::effects;
else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes;
else if (url.indexOf(F("fxda")) > 0) { respondModeData(request); return; }
else if (url.indexOf(F("fxda")) > 0) subJson = json_target::fxdata;
else if (url.indexOf(F("net")) > 0) subJson = json_target::networks;
else if (url.indexOf(F("cfg")) > 0) subJson = json_target::config;
else if (url.indexOf(F("pins")) > 0) subJson = json_target::pins;
@@ -1337,7 +1254,7 @@ void serveJson(AsyncWebServerRequest* request)
}
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)
// make sure you delete "response" if no "request->send(response);" is made
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::fxdata || subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
JsonVariant lDoc = response->getRoot();
@@ -1353,6 +1270,8 @@ void serveJson(AsyncWebServerRequest* request)
serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break;
case json_target::effects:
serializeModeNames(lDoc); break;
case json_target::fxdata:
serializeModeData(lDoc); break;
case json_target::networks:
serializeNetworks(lDoc); break;
case json_target::config:
+1 -43
View File
@@ -87,7 +87,7 @@ const ethernet_settings ethernetBoards[] = {
// ESP32-ETHERNET-KIT-VE
{
1, // eth_address,
0, // eth_address,
5, // eth_power,
23, // eth_mdc,
18, // eth_mdio,
@@ -155,34 +155,8 @@ const ethernet_settings ethernetBoards[] = {
ETH_PHY_LAN8720, // eth_type,
ETH_CLOCK_GPIO0_IN // eth_clk_mode
},
// WLED_ETH_QUINLED_V4 (14) - QuinLED Dig-Uno/Quad v4
{
0, // eth_address
-1, // eth_power
7, // eth_mdc
8, // eth_mdio
ETH_PHY_LAN8720, // eth_type
ETH_CLOCK_GPIO0_IN // eth_clk_mode
},
// WLED_ETH_QUINLED_OCTA_V4 (15) - QuinLED Dig-Octa 32-8L v4
{
0, // eth_address
-1, // eth_power
23, // eth_mdc
18, // eth_mdio
ETH_PHY_LAN8720, // eth_type
ETH_CLOCK_GPIO0_IN // eth_clk_mode
},
};
// sanity checks for ethernet config table and WLED_ETH_DEFAULT
static_assert((sizeof(ethernetBoards)/sizeof(ethernetBoards[0])) == WLED_NUM_ETH_TYPES, "WLED_NUM_ETH_TYPES does not match size of ethernetBoards[] table.");
#ifdef WLED_ETH_DEFAULT
static_assert(((WLED_ETH_DEFAULT) >= WLED_ETH_NONE) && ((WLED_ETH_DEFAULT) < WLED_NUM_ETH_TYPES), "WLED_ETH_DEFAULT is out of range.");
#endif
bool initEthernet()
{
static bool successfullyConfiguredEthernet = false;
@@ -448,26 +422,10 @@ void WiFiEvent(WiFiEvent_t event)
}
break;
#ifdef ARDUINO_ARCH_ESP32
case ARDUINO_EVENT_WIFI_READY:
DEBUG_PRINTLN(F("WiFi-E: driver ready."));
break;
case ARDUINO_EVENT_WIFI_SCAN_DONE:
// also triggered when connected to selected SSID
DEBUG_PRINTLN(F("WiFi-E: SSID scan completed."));
break;
case ARDUINO_EVENT_WIFI_STA_START:
DEBUG_PRINTLN(F("WiFi-E: STA Started"));
break;
case ARDUINO_EVENT_WIFI_STA_STOP:
DEBUG_PRINTLN(F("WiFi-E: STA Stopped"));
break;
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
DEBUG_PRINTLN(F("WiFi-E: STA authentication mode change."));
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
DEBUG_PRINTLN(F("WiFi-E: IP address lost."));
break;
case ARDUINO_EVENT_WIFI_AP_START:
DEBUG_PRINTLN(F("WiFi-E: AP Started"));
break;
+79 -222
View File
@@ -6,8 +6,10 @@
#include <esp_ota_ops.h>
#include <esp_flash.h>
#include <mbedtls/sha256.h>
#endif
// Platform-specific metadata locations
#ifdef ESP32
constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata
#define UPDATE_ERROR errorString
@@ -29,43 +31,16 @@ constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
#elif defined(ESP8266)
constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset
#define UPDATE_ERROR getErrorString
#define SUPPORT_GZIPPED_OTA
#endif
#ifdef SUPPORT_GZIPPED_OTA
#include <uzlib.h> // ArduinoUZlib library: gzipped firmware uploads are decoded by eboot at boot,
// so the upload stream itself can arrive compressed; we decompress just enough
// to find the metadata for OTA validation.
#endif
constexpr size_t METADATA_SEARCH_RANGE = 512; // bytes
// State structure for update process
namespace {
struct UpdateContext {
// State flags
// FUTURE: the flags could be replaced by a state machine
bool replySent = false;
bool needsRestart = false;
bool updateStarted = false;
bool uploadComplete = false;
bool releaseCheckPassed = false;
String errorMessage;
// Buffer to hold block data across posts, if needed
std::vector<uint8_t> releaseMetadataBuffer;
#ifdef SUPPORT_GZIPPED_OTA
bool gzipDetected = false; // upload is a gzip stream (eboot will decompress at boot)
#endif
};
}
/**
* Check if OTA should be allowed based on release compatibility using custom description
* @param binaryData Pointer to binary file data (not modified)
* @param dataSize Size of binary data in bytes
* @param errorMessage Buffer to store error message if validation fails
* @param errorMessage Buffer to store error message if validation fails
* @param errorMessageLen Maximum length of error message buffer
* @return true if OTA should proceed, false if it should be blocked
*/
@@ -92,104 +67,20 @@ static bool validateOTA(const uint8_t* binaryData, size_t dataSize, char* errorM
}
}
#ifdef SUPPORT_GZIPPED_OTA
constexpr size_t GZIP_DECOMPRESSED_SIZE = METADATA_OFFSET + METADATA_SEARCH_RANGE;
// We use the JSON buffer to hold the decompressed metadata, so we need to ensure it's large enough
static_assert(JSON_BUFFER_SIZE >= GZIP_DECOMPRESSED_SIZE, "JSON_BUFFER_SIZE must be at least GZIP_DECOMPRESSED_SIZE to support gzipped OTA updates");
struct UpdateContext {
// State flags
// FUTURE: the flags could be replaced by a state machine
bool replySent = false;
bool needsRestart = false;
bool updateStarted = false;
bool uploadComplete = false;
bool releaseCheckPassed = false;
String errorMessage;
// File-static flash source context for the uzlib source_read_cb.
// The 256-byte read buffer must be 4-byte aligned for ESP.flashRead().
static struct {
uint32_t flashBase;
uint32_t flashOffset;
uint8_t buf[256] __attribute__((aligned(4)));
} s_gzipFlashSrc;
// Buffer to hold block data across posts, if needed
std::vector<uint8_t> releaseMetadataBuffer;
};
static int gzip_flash_read_cb(struct uzlib_uncomp* d)
{
if (!ESP.flashRead(s_gzipFlashSrc.flashBase + s_gzipFlashSrc.flashOffset,
reinterpret_cast<uint32_t*>(s_gzipFlashSrc.buf),
sizeof(s_gzipFlashSrc.buf))) {
return -1;
}
s_gzipFlashSrc.flashOffset += sizeof(s_gzipFlashSrc.buf);
d->source = s_gzipFlashSrc.buf;
d->source_limit = s_gzipFlashSrc.buf + sizeof(s_gzipFlashSrc.buf);
return *d->source++;
}
static bool unzipFlash(uint32_t flashBase, uint8_t* outBuf, char* errorMessage, size_t errorMessageLen)
{
uzlib_uncomp uncomp_ctx; // large, but we can get away with this since we're on the system stack
s_gzipFlashSrc.flashBase = flashBase;
s_gzipFlashSrc.flashOffset = 0;
uncomp_ctx.source = s_gzipFlashSrc.buf;
uncomp_ctx.source_limit = s_gzipFlashSrc.buf; // empty: first byte triggers callback
uncomp_ctx.source_read_cb = gzip_flash_read_cb;
uzlib_uncompress_init(&uncomp_ctx, nullptr, 0);
bool ok = false;
if (uzlib_gzip_parse_header(&uncomp_ctx) != TINF_OK) {
strncpy_P(errorMessage, PSTR("Invalid gzip header in OTA firmware"), errorMessageLen - 1);
errorMessage[errorMessageLen - 1] = '\0';
} else {
uncomp_ctx.dest_start = uncomp_ctx.dest = outBuf;
uncomp_ctx.dest_limit = outBuf + GZIP_DECOMPRESSED_SIZE;
int res = TINF_OK;
while (res == TINF_OK && uncomp_ctx.dest < uncomp_ctx.dest_limit) res = uzlib_uncompress(&uncomp_ctx);
if (uncomp_ctx.dest >= uncomp_ctx.dest_limit) {
ok = true;
} else {
strncpy_P(errorMessage, PSTR("Not enough decompressed data for validation"), errorMessageLen - 1);
errorMessage[errorMessageLen - 1] = '\0';
}
}
return ok;
}
static void validateGzippedOTA(UpdateContext* context) {
// Compute the flash address to read from based on the expected update size and the location of the FS partition, and then decompress just enough of the firmware to find the metadata for validation.
extern uint32_t _FS_start;
const size_t updateSize = Update.size();
// Logic borrowed from ESP8266 Updater.cpp
const size_t roundedSize = (updateSize + FLASH_SECTOR_SIZE - 1) & ~(FLASH_SECTOR_SIZE - 1);
const uintptr_t fsPhysAddr = (uintptr_t)&_FS_start - 0x40200000;
const uint32_t flashBase = (fsPhysAddr > roundedSize) ? (fsPhysAddr - roundedSize) : 0;
uint8_t* compressionBuf = static_cast<uint8_t*>(pDoc->memoryPool().buffer()); // borrow JSON buffer
char errorMessage[128];
bool ok = unzipFlash(flashBase, compressionBuf, errorMessage, sizeof(errorMessage));
if (!ok) {
DEBUG_PRINTF_P(PSTR("OTA unzip failed: %s\n"), errorMessage);
context->errorMessage = errorMessage;
return;
}
ok = validateOTA(compressionBuf, GZIP_DECOMPRESSED_SIZE, errorMessage, sizeof(errorMessage));
if (!ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
}
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed (gzipped)"));
context->releaseCheckPassed = true;
// Write the final block
if (!Update.hasError()) {
const auto& buf = context->releaseMetadataBuffer;
if (Update.write(const_cast<uint8_t*>(buf.data()), buf.size()) != buf.size()) {
DEBUG_PRINTF_P(PSTR("OTA write failed on final chunk: %s\n"), Update.UPDATE_ERROR());
}
}
DEBUG_PRINTLN(F("OTA Update End")); // for symmetry with the non-gzip path
context->uploadComplete = true; // Final block was passed to Update.write()
}
#endif
static void endOTA(AsyncWebServerRequest *request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
@@ -225,7 +116,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
{
#ifdef ESP8266
Update.runAsync(true);
#endif
#endif
if (Update.isRunning()) {
request->send(503);
@@ -237,7 +128,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
WLED::instance().disableWatchdog();
#endif
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
strip.suspend();
backupConfig(); // backup current config in case the update ends badly
strip.resetSegments(); // free as much memory as you can
@@ -250,15 +141,15 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
context->releaseCheckPassed = true;
DEBUG_PRINTLN(F("OTA validation skipped by user"));
}
// Begin update with the firmware size from content length
size_t updateSize = request->contentLength() > 0 ? request->contentLength() : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
if (!Update.begin(updateSize)) {
if (!Update.begin(updateSize)) {
context->errorMessage = Update.UPDATE_ERROR();
DEBUG_PRINTF_P(PSTR("OTA Failed to begin: %s\n"), context->errorMessage.c_str());
return false;
}
context->updateStarted = true;
return true;
}
@@ -267,7 +158,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
// Returns true if successful, false on failure.
bool initOTA(AsyncWebServerRequest *request) {
// Allocate update context
UpdateContext* context = new (std::nothrow) UpdateContext {};
UpdateContext* context = new (std::nothrow) UpdateContext {};
if (context) {
request->_tempObject = context;
request->onDisconnect([=]() { endOTA(request); }); // ensures we restart on failure
@@ -283,43 +174,29 @@ void setOTAReplied(AsyncWebServerRequest *request) {
context->replySent = true;
};
// Returns the OTA outcome as an OTAResultStatus plus an optional error string.
// TryAgain: JSON lock busy; caller must call deferResponse() and retry.
// Replied: response already sent; no action needed.
// Ready: result available; empty string = success (200), non-empty = failure (500).
std::pair<OTAResultStatus, String> getOTAResult(AsyncWebServerRequest* request) {
// Returns pointer to error message, or nullptr if OTA was successful.
std::pair<bool, String> getOTAResult(AsyncWebServerRequest* request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return { OTAResultStatus::Ready, F("OTA context unexpectedly missing") };
if (context->replySent) return { OTAResultStatus::Replied, {} };
#ifdef SUPPORT_GZIPPED_OTA
if (context->gzipDetected && !context->errorMessage.length()) {
JSONBufferGuard jsonGuard(JSON_LOCK_OTA);
if (!jsonGuard) return { OTAResultStatus::TryAgain, {} }; // busy — caller will deferResponse()
validateGzippedOTA(context); // Stores error in context->errorMessage if there's a problem
context->gzipDetected = false; // all done
}
#endif
if (context->errorMessage.length()) return { OTAResultStatus::Ready, context->errorMessage };
if (!context) return { true, F("OTA context unexpectedly missing") };
if (context->replySent) return { false, {} };
if (context->errorMessage.length()) return { true, context->errorMessage };
if (context->updateStarted) {
// Release the OTA context now.
endOTA(request);
if (Update.hasError()) {
return { OTAResultStatus::Ready, Update.UPDATE_ERROR() };
return { true, Update.UPDATE_ERROR() };
} else {
return { OTAResultStatus::Ready, {} };
return { true, {} };
}
}
// Should never happen
return { OTAResultStatus::Ready, F("Internal software failure") };
return { true, F("Internal software failure") };
}
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal)
{
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
@@ -331,89 +208,69 @@ void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data,
if (index == 0) {
if (!beginOTA(request, context)) return;
#ifdef SUPPORT_GZIPPED_OTA
if (len >= 2 && data[0] == 0x1F && data[1] == 0x8B) {
DEBUG_PRINTLN(F("OTA: gzipped firmware detected"));
context->gzipDetected = true;
}
#endif
}
// Perform plain-firmware validation as data arrives.
// Gzip validation is deferred to the end of the upload; skip this path for gzip.
if (!context->releaseCheckPassed
#ifdef SUPPORT_GZIPPED_OTA
&& !context->gzipDetected
#endif
) {
if ((index+len) > METADATA_OFFSET) {
// Current chunk contains the metadata offset
size_t availableDataAfterOffset = (index + len) - METADATA_OFFSET;
// Perform validation if we haven't done it yet and we have reached the metadata offset
if (!context->releaseCheckPassed && (index+len) > METADATA_OFFSET) {
// Current chunk contains the metadata offset
size_t availableDataAfterOffset = (index + len) - METADATA_OFFSET;
DEBUG_PRINTF_P(PSTR("OTA metadata check: %d in buffer, %d received, %d available\n"), context->releaseMetadataBuffer.size(), len, availableDataAfterOffset);
DEBUG_PRINTF_P(PSTR("OTA metadata check: %d in buffer, %d received, %d available\n"), context->releaseMetadataBuffer.size(), len, availableDataAfterOffset);
if (availableDataAfterOffset >= METADATA_SEARCH_RANGE) {
// We have enough data to validate, one way or another
const uint8_t* search_data = data;
size_t search_len = len;
// If we have saved data, use that instead
if (context->releaseMetadataBuffer.size()) {
// Add this data
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
search_data = context->releaseMetadataBuffer.data();
search_len = context->releaseMetadataBuffer.size();
}
// Do the checking
char errorMessage[128];
bool OTA_ok = validateOTA(search_data, search_len, errorMessage, sizeof(errorMessage));
// Release buffer if there was one
context->releaseMetadataBuffer = decltype(context->releaseMetadataBuffer){};
if (!OTA_ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
} else {
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
context->releaseCheckPassed = true;
}
} else {
// Store the data we just got for next pass
if (availableDataAfterOffset >= METADATA_SEARCH_RANGE) {
// We have enough data to validate, one way or another
const uint8_t* search_data = data;
size_t search_len = len;
// If we have saved data, use that instead
if (context->releaseMetadataBuffer.size()) {
// Add this data
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
search_data = context->releaseMetadataBuffer.data();
search_len = context->releaseMetadataBuffer.size();
}
// Do the checking
char errorMessage[128];
bool OTA_ok = validateOTA(search_data, search_len, errorMessage, sizeof(errorMessage));
// Release buffer if there was one
context->releaseMetadataBuffer = decltype(context->releaseMetadataBuffer){};
if (!OTA_ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
} else {
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
context->releaseCheckPassed = true;
}
} else {
// Store the data we just got for next pass
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
}
}
#ifdef SUPPORT_GZIPPED_OTA
// Gzip: stash the final chunk and defer validation to getOTAResult(), where
// deferResponse() can be used if the JSON lock is not yet available.
if (isFinal && context->gzipDetected && !context->releaseCheckPassed) {
context->releaseMetadataBuffer.assign(data, data + len);
return; // getOTAResult() will validate, write this chunk, and complete the update
}
#endif
// Check if validation was still pending (shouldn't happen normally)
// This is done before writing the last chunk, so endOTA can abort
// This is done before writing the last chunk, so endOTA can abort
if (isFinal && !context->releaseCheckPassed) {
DEBUG_PRINTLN(F("OTA failed: Validation never completed"));
// Don't write the last chunk to the updater: this will trip an error later
context->errorMessage = F("Release check data never arrived?");
return;
}
// Write chunk data to OTA update (only if release check passed or still pending)
if (!Update.hasError()) {
if (Update.write(data, len) != len) {
DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.UPDATE_ERROR());
}
}
if (isFinal) {
if(isFinal) {
DEBUG_PRINTLN(F("OTA Update End"));
// Upload complete
context->uploadComplete = true;
}
}
@@ -474,7 +331,7 @@ public:
return true; // needs more bytes for the header
}
//DEBUG_PRINTF("BLS parsed segment [%08X %08X=%d], segment count %d, is %d\n", segmentHeader.load_addr, segmentHeader.data_len, segmentHeader.data_len, segmentsLeft, imageSize);
//DEBUG_PRINTF("BLS parsed segment [%08X %08X=%d], segment count %d, is %d\n", segmentHeader.load_addr, segmentHeader.data_len, segmentHeader.data_len, segmentsLeft, imageSize);
// Validate segment size
if (segmentHeader.data_len > BOOTLOADER_SIZE) {
@@ -488,16 +345,16 @@ public:
--segmentsLeft;
if (segmentsLeft == 0) {
// all done, actually; we don't need to read any more
// Round up to nearest 16 bytes.
// Always add 1 to account for the checksum byte.
imageSize = ((imageSize/ 16) + 1) * 16;
//DEBUG_PRINTF("BLS complete, is %d\n", imageSize);
//DEBUG_PRINTF("BLS complete, is %d\n", imageSize);
return false;
}
}
}
// If we don't have enough bytes ...
if (len < segmentHeader.data_len) {
//DEBUG_PRINTF("Needs more bytes\n");
@@ -536,12 +393,12 @@ static uint8_t bootloaderSHA256Cache[32];
/**
* Calculate and cache the bootloader SHA256 digest
* Reads the bootloader from flash and computes SHA256 hash
*
* Strictly speaking, most bootloader images already contain a hash at the end of the image;
*
* Strictly speaking, most bootloader images already contain a hash at the end of the image;
* we could in theory just read it. The trouble is that we have to parse the structure anyways
* to find the actual endpoint, so we might as well always calculate it ourselves rather than
* handle a special case if the hash isn't stored.
*
*
*/
static void calculateBootloaderSHA256() {
// Calculate SHA256
@@ -680,7 +537,7 @@ static bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String& b
if (imageHeader.hash_appended == 1) {
actualBootloaderSize += 32;
}
if (actualBootloaderSize > len) {
// Same as above
bootloaderErrorMsg = "Too small";
@@ -778,7 +635,7 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) {
context->bytesBuffered = 0;
return true;
#endif
#endif
}
// Set bootloader OTA replied flag
+2 -9
View File
@@ -1,7 +1,6 @@
// WLED OTA update interface
#include <Arduino.h>
#ifdef ESP8266
#include <Updater.h>
#else
@@ -33,18 +32,12 @@ bool initOTA(AsyncWebServerRequest *request);
*/
void setOTAReplied(AsyncWebServerRequest *request);
/**
* Retrieve the OTA result.
* @param request Pointer to web request object
* @return OTAResultStatus indicating result state; string with error message if the update failed.
* @return bool indicating if a reply is necessary; string with error message if the update failed.
*/
enum class OTAResultStatus {
TryAgain, // caller must deferResponse() and retry - need additional resources (JSON lock) to complete validation
Replied, // response already sent; no action needed
Ready, // result available; send response based on error string
};
std::pair<OTAResultStatus, String> getOTAResult(AsyncWebServerRequest *request);
std::pair<bool, String> getOTAResult(AsyncWebServerRequest *request);
/**
* Process a block of OTA data. This is a passthrough of an ArUploadHandlerFunction.
+1 -1
View File
@@ -43,7 +43,7 @@ enum struct PinOwner : uint8_t {
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == DMX output, hard-coded to IO2
DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial
+2 -2
View File
@@ -194,8 +194,8 @@ void handlePresets()
changePreset = true;
} else {
if (!fdo["seg"].isNull() || !fdo["on"].isNull() || !fdo["bri"].isNull() || !fdo["nl"].isNull() || !fdo["ps"].isNull() || !fdo[F("playlist")].isNull()) changePreset = true;
if (!(tmpMode == CALL_MODE_INIT || (tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~'))))
fdo.remove("ps"); // remove load request for presets to prevent recursive crash (if not called by boot preset or button which contains preset cycling string "1~5~")
if (!(tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~')))
fdo.remove("ps"); // remove load request for presets to prevent recursive crash (if not called by button and contains preset cycling string "1~5~")
deserializeState(fdo, CALL_MODE_NO_NOTIFY, tmpPreset); // may change presetToApply by calling applyPreset()
}
if (!errorFlag && tmpPreset < 255 && changePreset) currentPreset = tmpPreset;
+1 -1
View File
@@ -212,7 +212,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char ld[4] = "LD"; ld[2] = offset+s; ld[3] = 0; //driver type (RMT=0, I2S=1)
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
if (!request->hasArg(lp)) {
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s);
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
break;
}
for (int i = 0; i < 5; i++) {
+25 -32
View File
@@ -21,10 +21,10 @@
#include "../network/Network.h"
#include <string.h>
// E1.17 ACN Packet Identifier "ASC-E1.17"
// E1.17 ACN Packet Identifier
const byte ESPAsyncE131::ACN_ID[12] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
// Art-Net Packet Identifier "Art-Net"
// Art-Net Packet Identifier
const byte ESPAsyncE131::ART_ID[8] = { 0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00 };
// Constructor
@@ -99,43 +99,36 @@ bool ESPAsyncE131::initMulticast(uint16_t port, uint16_t universe, uint8_t n) {
void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
bool error = false;
uint8_t protocol = P_ARTNET;
const size_t pktLen = _packet.length();
uint8_t protocol = P_E131;
e131_packet_t *sbuff = reinterpret_cast<e131_packet_t *>(_packet.data());
// E1.31 packet identifier (ACN_ID = "ASC-E1.17"), need at least 16 bytes to safely read acn_id (offset 4, length 12).
if (pktLen >= 16) {
if (!memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
protocol = P_E131;
}
if (protocol == P_ARTNET) {
if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id)))
error = true; //not ART_ID = "Art-Net"
if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX && sbuff->art_opcode != ARTNET_OPCODE_OPPOLL)
error = true; //not a DMX or poll packet
} else { //E1.31 error handling
if (pktLen < 126) { // need up to property_values[0] at offset 125
error = true;
} else {
if (htonl(sbuff->root_vector) != ESPAsyncE131::VECTOR_ROOT)
error = true;
if (htonl(sbuff->frame_vector) != ESPAsyncE131::VECTOR_FRAME)
error = true;
if (sbuff->dmp_vector != ESPAsyncE131::VECTOR_DMP)
error = true;
if (sbuff->property_values[0] != 0)
error = true;
}
}
//E1.31 packet identifier ("ACS-E1.17")
if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
protocol = P_ARTNET;
if (protocol == P_ARTNET) {
if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id)))
error = true; //not "Art-Net"
if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX && sbuff->art_opcode != ARTNET_OPCODE_OPPOLL)
error = true; //not a DMX or poll packet
} else { //E1.31 error handling
if (htonl(sbuff->root_vector) != ESPAsyncE131::VECTOR_ROOT)
error = true;
if (htonl(sbuff->frame_vector) != ESPAsyncE131::VECTOR_FRAME)
error = true;
if (sbuff->dmp_vector != ESPAsyncE131::VECTOR_DMP)
error = true;
if (sbuff->property_values[0] != 0)
error = true;
}
if (error && _packet.localPort() == DDP_DEFAULT_PORT) { //DDP packet
error = false;
protocol = P_DDP;
}
if (!error) {
_callback(sbuff, _packet.remoteIP(), protocol, pktLen);
_callback(sbuff, _packet.remoteIP(), protocol);
}
}
+8 -13
View File
@@ -55,24 +55,19 @@ typedef struct ip_addr ip4_addr_t;
#define DDP_FLAGS_VER 0xc0 // version mask
#define DDP_FLAGS_VER1 0x40 // version=1
#define DDP_FLAGS_PUSH 0x01
#define DDP_FLAGS_QUERY 0x02 // unsupported - used by XLights for auto-discovery
#define DDP_FLAGS_REPLY 0x04 // unsupported - response packet from another display
#define DDP_FLAGS_STORAGE 0x08 // unsupported - show data from a storage unit instead of from packet data field. Data field defines storage unit (by name, number, URL or whatever mechanism wanted).
#define DDP_FLAGS_QUERY 0x02
#define DDP_FLAGS_REPLY 0x04
#define DDP_FLAGS_STORAGE 0x08
#define DDP_FLAGS_TIME 0x10
#define DDP_CHANNELS_PER_PACKET 1440 // 480 leds
#define DDP_TYPE_RGB24 0x0B // 00 001 011 (RGB , 8 bits per channel, 3 channels)
#define DDP_TYPE_RGBW32 0x1B // 00 011 011 (RGBW, 8 bits per channel, 4 channels)
#define DDP_TYPE_LEGACY 0x01 // 00 000 001 legacy RGB 8bit definition
#define DDP_TYPE_UNDEF 0x00 // type and bit depth undefined
// DDP Source or Destination ID (header byte 3)
#define DDP_ID_DISPLAY 1 // default output device
#define DDP_ID_CONTROL 246 // JSON control (not implemented)
#define DDP_ID_CONFIG 250 // JSON config (not implemented)
#define DDP_ID_STATUS 251 // JSON status (not implemented)
#define DDP_ID_ALL 255 // all devices
#define DDP_ID_DISPLAY 1
#define DDP_ID_CONFIG 250
#define DDP_ID_STATUS 251
#define ARTNET_OPCODE_OPDMX 0x5000
#define ARTNET_OPCODE_OPPOLL 0x2000
@@ -217,7 +212,7 @@ typedef union {
} ArtPollReply;
// new packet callback
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen);
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol);
class ESPAsyncE131 {
private:
@@ -272,4 +267,4 @@ class E131Priority {
}
};
#endif // ESPASYNCE131_H_
#endif // ESPASYNCE131_H_
+4 -10
View File
@@ -85,7 +85,6 @@ private:
IPAddress ipMulti;
uint32_t mac24; //bottom 24 bits of mac
String escapedMac=""; //lowercase mac address
String bridgeId=""; //uppercase EUI-64 bridge ID (16 hex chars)
//private member functions
const char* modeString(EspalexaColorMode m)
@@ -298,13 +297,13 @@ private:
snprintf_P(buf, sizeof(buf), PSTR("HTTP/1.1 200 OK\r\n"
"EXT:\r\n"
"CACHE-CONTROL: max-age=86400\r\n" // SSDP_INTERVAL
"CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL
"LOCATION: http://%s:80/description.xml\r\n"
"SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber
"hue-bridgeid: %s\r\n"
"ST: urn:schemas-upnp-org:device:Basic:1\r\n" // _deviceType
"USN: uuid:2f402f80-da50-11e1-9b23-%s::urn:schemas-upnp-org:device:Basic:1\r\n" // _uuid::_deviceType
"\r\n"),s,bridgeId.c_str(),escapedMac.c_str());
"ST: urn:schemas-upnp-org:device:basic:1\r\n" // _deviceType
"USN: uuid:2f402f80-da50-11e1-9b23-%s::upnp:rootdevice\r\n" // _uuid::_deviceType
"\r\n"),s,escapedMac.c_str(),escapedMac.c_str());
espalexaUdp.beginPacket(espalexaUdp.remoteIP(), espalexaUdp.remotePort());
#ifdef ARDUINO_ARCH_ESP32
@@ -334,11 +333,6 @@ public:
escapedMac.replace(":", "");
escapedMac.toLowerCase();
// Compute EUI-64 bridge ID from MAC-48: insert standard "FFFE" padding between
// the first 6 hex chars (OUI/manufacturer) and last 6 hex chars (device), then uppercase
bridgeId = escapedMac.substring(0, 6) + "fffe" + escapedMac.substring(6);
bridgeId.toUpperCase();
String macSubStr = escapedMac.substring(6, 12);
mac24 = strtol(macSubStr.c_str(), 0, 16);
+3 -7
View File
@@ -6,7 +6,7 @@
#define UDP_SEG_SIZE 36
#define SEG_OFFSET (41)
static constexpr size_t WLEDPACKETSIZE = 41+(WS2812FX::getMaxSegments()*UDP_SEG_SIZE); // make sure this is known at compile-time
#define WLEDPACKETSIZE (41+(WS2812FX::getMaxSegments()*UDP_SEG_SIZE)+0)
#define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
@@ -268,7 +268,6 @@ static void parseNotifyPacket(const uint8_t *udpIn) {
size_t inactiveSegs = 0;
for (size_t i = 0; i < numSrcSegs && i < WS2812FX::getMaxSegments(); i++) {
unsigned ofs = 41 + i*udpIn[40]; //start of segment offset byte
if (ofs + 36 > UDP_IN_MAXSIZE) break; // avoid reading outside of array
unsigned id = udpIn[0 +ofs];
DEBUG_PRINTF_P(PSTR("UDP segment received: %u\n"), id);
if (id > strip.getSegmentsNum()) break;
@@ -500,7 +499,7 @@ void handleNotifications()
packetSize = rgbUdp.parsePacket();
if (packetSize) {
if (!receiveDirect) return;
if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return; // packetSize must not exceed buffersize (UDP_IN_MAXSIZE)
if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return;
realtimeIP = rgbUdp.remoteIP();
DEBUG_PRINTLN(rgbUdp.remoteIP());
uint8_t lbuf[packetSize];
@@ -588,9 +587,7 @@ void handleNotifications()
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
unsigned totalLen = strip.getLengthTotal();
// Clamp to prevent buffer overread: loop accesses up to udpIn[tpmPayloadFrameSize + 5]
size_t currentPayloadFrameSize = (packetSize >= 5) ? min(tpmPayloadFrameSize, uint16_t(packetSize - 5)) : 0;
for (size_t i = 6; i < currentPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
}
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
@@ -807,7 +804,6 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const
// write the header
/*0*/ddpUdp.write(flags);
// TODO: sequence number should be 1-15 as 0 means "unused", it has no bad consequences other than out of sequence packet may be accepted
/*1*/ddpUdp.write(sequenceNumber++ & 0x0F); // sequence may be unnecessary unless we are sending twice (as requested in Sync settings)
/*2*/ddpUdp.write(isRGBW ? DDP_TYPE_RGBW32 : DDP_TYPE_RGB24);
/*3*/ddpUdp.write(DDP_ID_DISPLAY);
+5 -34
View File
@@ -317,36 +317,10 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
} else return 0;
}
if (src == JSON_palette_names) {
if (mode > WLED_CUSTOM_PALETTE_ID_BASE) {
// usermod palette (IDs 201-255)
uint8_t umIdx = WLED_USERMOD_PALETTE_ID_BASE - mode;
if (umIdx >= usermodPalettes.size()) {
dest[0] = '\0'; // empty string if requested index is out of bounds
return 0;
}
const UsermodPalette &ump = usermodPalettes[umIdx];
char base[33];
strncpy_P(base, ump.name, sizeof(base) - 1);
base[sizeof(base) - 1] = '\0';
if (ump.palName) {
// usermod supplied a specific display name — prefix with the usermod name (e.g. "AudioReactive: Hue")
char palName[33];
strncpy_P(palName, ump.palName, sizeof(palName) - 1);
palName[sizeof(palName) - 1] = '\0';
snprintf(dest, maxLen + 1, "%s: %s", base, palName);
} else {
// fallback: "UMName index" (e.g. "AudioReactive 1")
snprintf(dest, maxLen + 1, "%s %u", base, (unsigned)ump.palIndex);
}
return strlen(dest);
}
if (mode >= FIXED_PALETTE_COUNT && mode <= WLED_CUSTOM_PALETTE_ID_BASE) {
// user custom palette (IDs FIXED_PALETTE_COUNT up to WLED_CUSTOM_PALETTE_ID_BASE=200)
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
dest[maxLen] = '\0';
return strlen(dest);
}
if (src == JSON_palette_names && mode > 255-customPalettes.size()) {
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
dest[maxLen] = '\0';
return strlen(dest);
}
unsigned qComma = 0;
@@ -920,7 +894,7 @@ void *allocate_buffer(size_t size, uint32_t type) {
buffer = p_malloc(size); // prefer PSRAM
}
else if (type & BFRALLOC_ENFORCE_PSRAM)
buffer = p_malloc(size); // use PSRAM if available, fall back to DRAM if not (safeguard for boards without PSRAM #5629)
buffer = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // use PSRAM only, otherwise return nullptr
buffer = validateFreeHeap(buffer);
#endif
if (buffer && (type & BFRALLOC_CLEAR))
@@ -1296,9 +1270,6 @@ String computeSHA1(const String& input) {
#ifdef ESP32
#include "esp_adc_cal.h"
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,4,7) // backwards compatibility patch
#define ADC_ATTEN_DB_12 ADC_ATTEN_DB_11
#endif
String generateDeviceFingerprint() {
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint
esp_chip_info_t chip_info;

Some files were not shown because too many files have changed in this diff Show More