mirror of
https://github.com/wled/WLED.git
synced 2026-07-01 09:11:31 +00:00
Compare commits
549 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03547c344f | |||
| 8d7f8ebad1 | |||
| 5ca3279c65 | |||
| dbeb1d6880 | |||
| 11a958a1e2 | |||
| c04a506206 | |||
| f9ea136070 | |||
| 227723df0f | |||
| 0609047134 | |||
| 6dcffe29a1 | |||
| fc1a2d7403 | |||
| 7db593ae79 | |||
| 2b950159ba | |||
| 314c4ea546 | |||
| 8893a3d749 | |||
| b03bdb35e1 | |||
| 67fa2ba096 | |||
| 6afa4ecee1 | |||
| dba9201209 | |||
| 221964e9a5 | |||
| 3a46f44495 | |||
| 360853bc28 | |||
| 99c3ea0ba9 | |||
| 56b3345f6f | |||
| 12a4c246e6 | |||
| 4beef70e95 | |||
| 2457ea27f0 | |||
| d36701cb72 | |||
| fa5d1e0e36 | |||
| 0fb417ec6f | |||
| 79cbe606d7 | |||
| 9f13570091 | |||
| 53a77a10a2 | |||
| 765adca942 | |||
| f763144b3c | |||
| ae4b2b3ef2 | |||
| 8dfd2cf312 | |||
| 19530ce535 | |||
| 9b1f509d77 | |||
| 55cee99a74 | |||
| 4efb02de58 | |||
| a4b14c0191 | |||
| d8e3578084 | |||
| 30abc01c0c | |||
| dea6ec0e4c | |||
| dfd78f4903 | |||
| 5f7910742e | |||
| 3a5026886f | |||
| 1dbceed9dc | |||
| be264fb210 | |||
| 7bd387444d | |||
| 174ff022cb | |||
| bd0ebf90c0 | |||
| bf948fadb5 | |||
| 0a1a7fc26f | |||
| 659f698144 | |||
| d884a3e692 | |||
| 82c4ce580e | |||
| bb495f7879 | |||
| 6bd90abac7 | |||
| d6482e99bb | |||
| c399d712c9 | |||
| fd162cee43 | |||
| 43278a5d0f | |||
| bd8a16a6f3 | |||
| 73898ddf5d | |||
| cdf1f80a81 | |||
| 1825ce7fec | |||
| b4747a827b | |||
| e2708be4a9 | |||
| 79bfc8b223 | |||
| f98e19c965 | |||
| 5d00dd0394 | |||
| beaa5001b6 | |||
| 33fe82c207 | |||
| 36fa42d7ea | |||
| 555d0cfbf7 | |||
| 090ab59a9c | |||
| c0a90ea470 | |||
| 7a37579ef8 | |||
| 58bf4c83e9 | |||
| 69c2b2d5c2 | |||
| 830f5df465 | |||
| ce249aef64 | |||
| ff97ad5fe0 | |||
| 68b61f0831 | |||
| 6cdf650535 | |||
| 0a0800d98d | |||
| 0f34973ee5 | |||
| 72aee0680c | |||
| 86db5010f8 | |||
| 584f9aba16 | |||
| 5aa41c49bd | |||
| cc26179e56 | |||
| 6ae22c9eb4 | |||
| 33e6aee880 | |||
| 67f654e99c | |||
| decfad483a | |||
| 1ec2579f0d | |||
| 2d3c8c9920 | |||
| 1046de21a9 | |||
| b7f9f71568 | |||
| f68f91d2c9 | |||
| 59898a9b8f | |||
| 3d864b178d | |||
| 6186e32814 | |||
| 372577cb3d | |||
| 0a190271e5 | |||
| d337c8a313 | |||
| 91bcfc14ad | |||
| 42f4bcb8a9 | |||
| 8e501d9c91 | |||
| 0f30c54ca6 | |||
| 741037a242 | |||
| f5ef3f91ce | |||
| a1a22ec7b1 | |||
| 6641724616 | |||
| 722d6e8cdc | |||
| 8e94cf5bc4 | |||
| e6458922e1 | |||
| 4ba60a606f | |||
| f58187e579 | |||
| 8793db356f | |||
| b185a0216d | |||
| 560eb3763a | |||
| 6531a0804d | |||
| d5eb95028e | |||
| 37623edfc1 | |||
| 34a35d02a5 | |||
| cfe7bad45d | |||
| 0cd709723a | |||
| aa6e719129 | |||
| 979c724c91 | |||
| 665d66f45e | |||
| 367d64f41f | |||
| 59c5991d2d | |||
| 8935997228 | |||
| c8f50e6f5a | |||
| 83065c15ae | |||
| 88994e95be | |||
| f0e182bd29 | |||
| c05097351d | |||
| 57cca612db | |||
| 8204e1bafe | |||
| 8039b71b76 | |||
| 1edfc81259 | |||
| e321514e4f | |||
| b0b9fc58da | |||
| 360333c022 | |||
| 0b7f77b2f7 | |||
| 05959f94a8 | |||
| 3e085024a0 | |||
| bef897f679 | |||
| dba7fcaaff | |||
| 8aa5501571 | |||
| 41fd877adb | |||
| d66258f53b | |||
| aaf76cc4d6 | |||
| 7872c633f3 | |||
| ccd40e1f27 | |||
| d466cab664 | |||
| c74b988c90 | |||
| fe14c3104b | |||
| 47af19519f | |||
| aea18fa87b | |||
| e0b0b2a475 | |||
| 93e3e2ed02 | |||
| d00dbacca6 | |||
| 988254bb9f | |||
| dd397fe673 | |||
| 7c57de901d | |||
| a4359fa57f | |||
| 0c3ff97e16 | |||
| a871f110f5 | |||
| 49f653c621 | |||
| 64d57f51d5 | |||
| eaffac61c9 | |||
| 0466daa552 | |||
| 88032d5e4b | |||
| 9161672b43 | |||
| 45cdfde61a | |||
| 120da32d69 | |||
| eb60ff91e4 | |||
| 449069a344 | |||
| d7d4e7dfb4 | |||
| f16ca9c8ae | |||
| 5f219e60b8 | |||
| 18a9986d1d | |||
| 7a71efe916 | |||
| a535c56b4d | |||
| e37707d520 | |||
| fd6f568023 | |||
| 02e593da0b | |||
| e8d481720a | |||
| f4f4978eeb | |||
| 49a63fc15e | |||
| 1bc71f2352 | |||
| 70b0aeac95 | |||
| d20dbeb204 | |||
| 69d494ad40 | |||
| 8aa4beeaea | |||
| 012fbdd6e9 | |||
| 01076c8237 | |||
| 0fe9e3d3a2 | |||
| cd0b85a8a2 | |||
| 01328a65c1 | |||
| a2d970e155 | |||
| cb666cedc5 | |||
| 5e0a0a7561 | |||
| 3af2ae5f2f | |||
| 259bf3c0f8 | |||
| 2481aab860 | |||
| ac96ee7568 | |||
| 2b32918db0 | |||
| 5e49a1cffb | |||
| 910caae463 | |||
| 35ce05a73b | |||
| ba377d7c29 | |||
| 1cce33e0d3 | |||
| 6d7c1d0bbd | |||
| 96510614a3 | |||
| 1185886eab | |||
| a94a3f7003 | |||
| 144f1f13a8 | |||
| 5a7aa8d8a8 | |||
| 42b91f2122 | |||
| a966c41bc2 | |||
| 36ebcb50f7 | |||
| 3149a80dcf | |||
| bab31833f0 | |||
| 48b0ba0643 | |||
| 0198a614b9 | |||
| 14f2ca4223 | |||
| 7932a249e0 | |||
| 474e1fe2e5 | |||
| 7aec31f039 | |||
| e2dd6303ea | |||
| 7242e9d842 | |||
| ab0cde110f | |||
| c735feb90a | |||
| a6faf942f1 | |||
| 370e4c8e2f | |||
| 8e90242e95 | |||
| 97dacb6a04 | |||
| 061920bb9c | |||
| 97704e08de | |||
| a51aec6f19 | |||
| c21ed0714f | |||
| a2dce56809 | |||
| 036f5199e5 | |||
| 1914e4ee3b | |||
| 9c072ec921 | |||
| cf4dfe958e | |||
| 2cccbd175c | |||
| cb61c4fe7d | |||
| 4a0a4c632b | |||
| 5b2ae15d8d | |||
| 44ddc21530 | |||
| 063592c845 | |||
| 4955af0a11 | |||
| 9df5d056b9 | |||
| 3486e3bcbb | |||
| 91eb69643d | |||
| 48b27d12ac | |||
| 78c1051cbb | |||
| 2302863386 | |||
| 7560811480 | |||
| 12f6fbc005 | |||
| 875711d38e | |||
| 9b9474282d | |||
| 1284aba5d0 | |||
| fa6fc4f0b6 | |||
| f7d9461d1c | |||
| 7153fd8fe4 | |||
| 35c1f415c3 | |||
| cf03f1a16e | |||
| 5be517b519 | |||
| 630533d125 | |||
| a78d39ff1e | |||
| 7722faf9d7 | |||
| a67f590ee8 | |||
| 964c8cee66 | |||
| 9d1f36c553 | |||
| ad921f031f | |||
| 3d2fac0ad4 | |||
| 65d7528af9 | |||
| 77a16f667a | |||
| a454b31c9d | |||
| 848f188bf6 | |||
| ee8b019462 | |||
| 2f402818a5 | |||
| 7193790253 | |||
| 1f536eed10 | |||
| b36ff623fb | |||
| 10066ff608 | |||
| 0b57cbcc17 | |||
| e47b08938f | |||
| b63f2251f0 | |||
| f4fbdbf367 | |||
| 795c6893be | |||
| 97e1b8ee05 | |||
| be92bacc87 | |||
| e04a156e78 | |||
| b541c1d284 | |||
| 39335675c1 | |||
| 8f951f5068 | |||
| 0f538eb155 | |||
| 3cb3ff5223 | |||
| e53500f042 | |||
| c312f51050 | |||
| 7670246a3b | |||
| e362891b8f | |||
| dfba1e97e6 | |||
| 61f3c6c379 | |||
| 9a07784c67 | |||
| 33ffbd5927 | |||
| 0629720821 | |||
| daed02723f | |||
| 3bd5f03140 | |||
| 96cd51aa77 | |||
| 72b47710af | |||
| dd048b8c23 | |||
| 40b4d55edf | |||
| 2a57177a2d | |||
| 868fbf3714 | |||
| 51af4ecb08 | |||
| 758bdd537c | |||
| e834de2212 | |||
| 3a2674a1f1 | |||
| eeeb93cdc5 | |||
| 58239ced02 | |||
| 4f7b574d22 | |||
| b222940ad1 | |||
| 0d539c524c | |||
| 02fc15ffd1 | |||
| 541eb72d67 | |||
| e0164fc502 | |||
| 9e6c927d50 | |||
| 49f2208a4e | |||
| bae62d2caf | |||
| 493f0fe952 | |||
| 2af98d49a1 | |||
| e3bebc0674 | |||
| fc2ffed61c | |||
| 1f4cadc629 | |||
| 27a6fadce9 | |||
| 1a91db1f01 | |||
| d4b0360f8b | |||
| 80e81eeccc | |||
| fc313e1e96 | |||
| 511ff789b5 | |||
| 8f44f1b395 | |||
| 19f7cb19a9 | |||
| b4194b3a32 | |||
| 6b7ecb969b | |||
| f1fe20d73e | |||
| 4e81f272b8 | |||
| b3e7e7e8c0 | |||
| 8fcdbe698f | |||
| 82723a03aa | |||
| dcf0a2aaea | |||
| 85cff89bf0 | |||
| c76cb5cc9a | |||
| fb877adcb2 | |||
| 2ce38700b1 | |||
| d3b8d9635e | |||
| 491546bad0 | |||
| 3abd8c7149 | |||
| 21233894ff | |||
| aadabe9885 | |||
| 5c9803db4b | |||
| ed0fadd80c | |||
| 0c6c66b35b | |||
| 908e17bbfc | |||
| 2802f8cba6 | |||
| 2820331b5e | |||
| 9959f75a8e | |||
| a425b97a93 | |||
| cffc57e578 | |||
| acc98d47e0 | |||
| 6c0c2b3ff8 | |||
| fb8c575def | |||
| 1bda0bf639 | |||
| dfcec5c94b | |||
| f2cbf0cc50 | |||
| 3de117ba23 | |||
| 967f6ab2bd | |||
| 993ab49b15 | |||
| f3f736d399 | |||
| 7ce497b1f7 | |||
| c96e28924f | |||
| c1dddeafc4 | |||
| 4117d31cc0 | |||
| 5e073ce76c | |||
| deaae112a3 | |||
| ede8a14421 | |||
| 46a1b2f190 | |||
| 55eee941f7 | |||
| f8a6790e3c | |||
| e046316545 | |||
| b730ce8850 | |||
| f8df68cf82 | |||
| 703b90fe88 | |||
| a8f9573fc3 | |||
| 3243c88055 | |||
| a39285a927 | |||
| 08ca41b96d | |||
| c6739d380f | |||
| 1009855e89 | |||
| 546384bbba | |||
| 71cf78be59 | |||
| 4f9c48fd39 | |||
| cd911ae0db | |||
| d823ab4a94 | |||
| edf0cbed4a | |||
| 7014d4f98b | |||
| ed6b82b2f6 | |||
| ab65e9fb5f | |||
| b128585618 | |||
| f02e6b6c02 | |||
| d3251c57f9 | |||
| b72568f5c8 | |||
| 7b99f65341 | |||
| 6e70d0c45e | |||
| e104529329 | |||
| 576e82bcbd | |||
| 5b46b9cb21 | |||
| d8616e69bf | |||
| 8159768e81 | |||
| 7370c100aa | |||
| d7ab1f4691 | |||
| 21d0af1094 | |||
| 9d63296783 | |||
| 1800a32caf | |||
| be49f34a00 | |||
| 9232bd4d76 | |||
| 3c24681881 | |||
| 6b9f90c1dc | |||
| b30951e674 | |||
| 0920f0fdcf | |||
| 8b98a7ac9e | |||
| e9b1a53149 | |||
| 20dd750b9b | |||
| b860a1ad28 | |||
| a3a3547fa5 | |||
| a1a190510a | |||
| b2e0c243b7 | |||
| 16262ee424 | |||
| 6cefd14176 | |||
| 83c37f8245 | |||
| f0acc626bd | |||
| 1a025f5636 | |||
| 4069260e61 | |||
| 1c09d34d12 | |||
| 692c812934 | |||
| 9484b42b40 | |||
| 6dff1d2cb1 | |||
| 929777507c | |||
| d76bd5fe59 | |||
| 1b5f10ff6d | |||
| 97b2c7a44f | |||
| 56beb3795e | |||
| 68a8812942 | |||
| d2e6a568ba | |||
| 0b248abd3b | |||
| a57e895bb9 | |||
| 5137768a8f | |||
| 35706f0940 | |||
| 11f5be1861 | |||
| 8268c4a948 | |||
| 01b1c51653 | |||
| cf09c0dd1e | |||
| 2f267a8a14 | |||
| 4ebbd866c4 | |||
| d2c13135b6 | |||
| e4d6848d1b | |||
| c92abb8dfc | |||
| ec44fd167a | |||
| d3e92f26c0 | |||
| f88fd073a3 | |||
| 9f12301128 | |||
| 7b54be849f | |||
| 29c7b50727 | |||
| 5d54597ffc | |||
| 0b27bc37bd | |||
| bc4b6b6a2b | |||
| 8ea41cf2d2 | |||
| 5ef41e7d3b | |||
| 2245e0e0df | |||
| 5ecf76c1aa | |||
| e891c5c36a | |||
| c096c5bb8f | |||
| c73935dd36 | |||
| 3f441f0605 | |||
| 2df4c58de8 | |||
| 1de36ca0da | |||
| 70235450e8 | |||
| 62ca377060 | |||
| b2d659120e | |||
| 45d105ed4d | |||
| 71920659e1 | |||
| 83b541e1ba | |||
| 2c7923e17d | |||
| a55209415c | |||
| 6961059220 | |||
| 1bc63cac61 | |||
| 152bfd1543 | |||
| 7e4661609c | |||
| ecfcd0da19 | |||
| 79e2128e71 | |||
| 9743775fb5 | |||
| 9f3e53a516 | |||
| b51e80f5dd | |||
| d632cf8e8c | |||
| 7c8b8fd0f3 | |||
| fa5aa586ec | |||
| db65e30ad5 | |||
| cf195af7c8 | |||
| 0f1055826b | |||
| 96491255f1 | |||
| 36eaca6cb2 | |||
| 0653854904 | |||
| 51e1f4fc04 | |||
| 2af23fc0cd | |||
| ce84a57a0d | |||
| 8cad34e14a | |||
| 93821efb90 | |||
| c5e2ec72bd | |||
| 86679edd1f | |||
| 7a0325f88f | |||
| 44a483f454 | |||
| 41878f01f9 | |||
| 906d4560a7 | |||
| 5205110960 | |||
| 2f8882ee56 | |||
| d4bafec0f3 | |||
| 446c04380e | |||
| 7daada1f3e | |||
| 68dff2d392 | |||
| ec22c50813 | |||
| 81c92257da | |||
| e4815d1ff5 | |||
| bc19133e73 | |||
| 03a9d9e56f | |||
| 2c0259f214 | |||
| 1de8c2e79b | |||
| 07ab6aa3ea | |||
| ba2b182bb3 | |||
| 965e794094 |
@@ -0,0 +1,259 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
#
|
||||
# CodeRabbit configuration — references existing guideline files to avoid
|
||||
# duplicating conventions. See:
|
||||
# .github/copilot-instructions.md — project overview & general rules
|
||||
# docs/cpp.instructions.md — C++ coding conventions
|
||||
# docs/web.instructions.md — Web UI coding conventions
|
||||
# docs/cicd.instructions.md — GitHub Actions / CI-CD conventions
|
||||
# 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
|
||||
# not see it and these settings will have no effect.
|
||||
|
||||
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.
|
||||
|
||||
Key rules: 2-space indentation (no tabs), camelCase functions/variables,
|
||||
PascalCase classes, UPPER_CASE macros. No C++ exceptions — use return codes and debug macros.
|
||||
|
||||
Hot-path optimization guidelines (attributes, uint_fast types, caching,
|
||||
unsigned range checks) apply from pixel set/get operations and strip.show() downward —
|
||||
NOT to effect functions in FX.cpp, which have diverse contributor styles.
|
||||
|
||||
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.
|
||||
|
||||
Key rules: indent HTML and JavaScript with tabs, CSS with tabs.
|
||||
Files here are built into wled00/html_*.h and wled00/js_*.h by tools/cdata.js — never
|
||||
edit those generated headers directly.
|
||||
|
||||
# ── 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.
|
||||
They must never be manually edited or committed. Flag any PR that
|
||||
includes changes to these files.
|
||||
|
||||
- path: "wled00/js_*.h"
|
||||
instructions: >
|
||||
These files are auto-generated from wled00/data/ by tools/cdata.js.
|
||||
They must never be manually edited or committed. Flag any PR that
|
||||
includes changes to these files.
|
||||
|
||||
- path: "usermods/**"
|
||||
instructions: >
|
||||
Usermods are community add-ons.
|
||||
Each usermod lives in its own directory under usermods/ and is implemented
|
||||
as a .cpp file with a dedicated library.json file to manage dependencies.
|
||||
Follow the same C++ conventions as the core firmware (docs/cpp.instructions.md).
|
||||
|
||||
# ── 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.
|
||||
|
||||
Key rules: 2-space indentation, descriptive name: on every workflow/job/step.
|
||||
Third-party actions must be pinned to a specific version tag — branch pins
|
||||
such as @main or @master are not allowed. Declare explicit permissions: blocks
|
||||
scoped to least privilege. Never interpolate github.event.* values directly
|
||||
into run: steps — pass them through an env: variable to prevent script
|
||||
injection. Do not use pull_request_target unless fully justified.
|
||||
|
||||
- path: "**/*.instructions.md"
|
||||
instructions: |
|
||||
This file contains both AI-facing rules and human-only reference sections.
|
||||
Human-only sections are enclosed in `<!-- HUMAN_ONLY_START -->` /
|
||||
`<!-- HUMAN_ONLY_END -->` HTML comment markers and should not be used as
|
||||
actionable review criteria.
|
||||
|
||||
When this file is modified in a PR, perform the following alignment check:
|
||||
1. For each `<!-- HUMAN_ONLY_START --> ... <!-- HUMAN_ONLY_END -->` block,
|
||||
verify that its examples and guidance are consistent with (and do not
|
||||
contradict) the AI-facing rules stated in the same file.
|
||||
2. Flag any HUMAN_ONLY section whose content has drifted from the surrounding
|
||||
AI-facing rules due to edits introduced in this PR.
|
||||
3. If new AI-facing rules were added without updating a related HUMAN_ONLY
|
||||
reference section, note this as a suggestion (not a required fix).
|
||||
|
||||
# ── 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
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"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"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,7 +42,8 @@
|
||||
},
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"platformio.platformio-ide"
|
||||
"platformio.platformio-ide",
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -52,7 +52,7 @@
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "bash -i -c 'nvm install && npm ci'",
|
||||
"postCreateCommand": "",
|
||||
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
|
||||
@@ -5,7 +5,9 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please quickly search existing issues first before submitting a bug.
|
||||
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.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
@@ -59,12 +61,13 @@ body:
|
||||
options:
|
||||
- ESP8266
|
||||
- ESP32
|
||||
- ESP32-S3
|
||||
- ESP32 with ethernet
|
||||
- ESP32-S2
|
||||
- ESP32-S3
|
||||
- ESP32-C3
|
||||
- Other
|
||||
- ESP32-C6 (experimental)
|
||||
- ESP32-C5 (experimental)
|
||||
- ESP32-C6 (experimental)
|
||||
- ESP32-P4 (experimental)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
applyTo: "**"
|
||||
---
|
||||
# Agent-Mode Build & Test Instructions
|
||||
|
||||
Detailed build workflow, timeouts, and troubleshooting for making code changes in agent mode. Always reference these instructions first when running builds or validating changes.
|
||||
|
||||
## Build Timing and Timeouts
|
||||
|
||||
Use these timeout values when running builds:
|
||||
|
||||
| Command | Typical Time | Minimum Timeout | Notes |
|
||||
|---|---|---|---|
|
||||
| `npm run build` | ~3 s | 30 s | Web UI → `wled00/html_*.h` `wled00/js_*.h` headers |
|
||||
| `npm test` | ~40 s | 2 min | Validates build system |
|
||||
| `npm run dev` | continuous | — | Watch mode, auto-rebuilds on changes |
|
||||
| `pio run -e <env>` | 15–20 min | 30 min | First build downloads toolchains; subsequent builds are faster |
|
||||
|
||||
**NEVER cancel long-running builds.** PlatformIO downloads and compilation require patience.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Code Style Summary
|
||||
- **C++** files in `wled00/` and `usermods/`: 2-space indentation (no tabs), camelCase functions/variables, PascalCase classes, UPPER_CASE macros. No C++ exceptions — use return codes and debug macros.
|
||||
- **Web UI** files in `wled00/data`: indent HTML and JavaScript with tabs, CSS with tabs.
|
||||
- **CI/CD workflows** in `.github/workflows`: 2-space indentation, descriptive `name:` on every workflow/job/step. Third-party actions must be pinned to a specific version tag — branch pins such as `@main` or `@master` are not allowed. SHA pinning recommended.
|
||||
|
||||
### Web UI Changes
|
||||
|
||||
1. Edit files in `wled00/data/`
|
||||
2. Run `npm run build` to regenerate `wled00/html_*.h` `wled00/js_*.h` headers
|
||||
3. Test with local HTTP server (see Manual Testing below)
|
||||
4. Run `npm test` to validate
|
||||
|
||||
### Firmware Changes
|
||||
|
||||
1. Edit files in `wled00/` (but **never** `html_*.h` and `js_*.h` files)
|
||||
2. Ensure web UI is built first: `npm run build`
|
||||
3. Build firmware: `pio run -e esp32dev` (set timeout ≥ 30 min)
|
||||
4. Flash to device: `pio run -e [target] --target upload`
|
||||
|
||||
### Combined Web + Firmware Changes
|
||||
|
||||
1. Always build web UI first
|
||||
2. Test web interface manually
|
||||
3. Then build and test firmware
|
||||
|
||||
|
||||
## Before Finishing Work - Testing
|
||||
|
||||
**You MUST complete ALL of these before marking work as done:**
|
||||
|
||||
1. **Run tests**: `npm test` — must pass
|
||||
2. **Build firmware**: `pio run -e esp32dev` — must succeed after source code changes, **never skip this step**.
|
||||
- Set timeout to 30+ minutes, **never cancel**
|
||||
- Choose `esp32dev` as a common, representative environment
|
||||
- If the build fails, fix the issue before proceeding
|
||||
3. **For web UI changes**: manually test the interface (see below)
|
||||
|
||||
If any step fails, fix the issue. **Do NOT mark work complete with failing builds or tests.**
|
||||
|
||||
## Manual Web UI Testing
|
||||
|
||||
Start a local server:
|
||||
|
||||
```sh
|
||||
cd wled00/data && python3 -m http.server 8080
|
||||
# Open http://localhost:8080/index.htm
|
||||
```
|
||||
|
||||
Test these scenarios after every web UI change:
|
||||
|
||||
- **Load**: `index.htm` loads without JavaScript errors (check browser console)
|
||||
- **Navigation**: switching between main page and settings pages works
|
||||
- **Color controls**: color picker and brightness controls function correctly
|
||||
- **Effects**: effect selection and parameter changes work
|
||||
- **Settings**: form submission and validation work
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Build Issues
|
||||
|
||||
| Problem | Solution |
|
||||
|---|---|
|
||||
| Missing `html_*.h` | Run `npm ci; npm run build` |
|
||||
| Web UI looks broken | Check browser console for JS errors |
|
||||
| PlatformIO network errors | Retry — downloads can be flaky |
|
||||
| Node.js version mismatch | Ensure Node.js 20+ (check `.nvmrc`) |
|
||||
|
||||
### Recovery Steps
|
||||
|
||||
- **Force web UI rebuild**: `npm run build -- -f`
|
||||
- **Clear generated files**: `rm -f wled00/html_*.h wled00/js_*.h` then `npm run build`
|
||||
- **Clean PlatformIO build artifacts**: `pio run --target clean`
|
||||
- **Reinstall Node deps**: `rm -rf node_modules && npm ci`
|
||||
|
||||
## CI/CD Validation
|
||||
|
||||
The GitHub Actions CI workflow will:
|
||||
1. Install Node.js and Python dependencies
|
||||
2. Run `npm test`
|
||||
3. Build web UI (automatic via PlatformIO)
|
||||
4. Compile firmware for **all** `default_envs` targets
|
||||
|
||||
**To ensure CI success, always validate locally:**
|
||||
- Run `npm test` and ensure it passes
|
||||
- Run `pio run -e esp32dev` (or another common firmware environment, see next section) and ensure it completes successfully
|
||||
- If either fails locally, it WILL fail in CI
|
||||
|
||||
Match this workflow in local development to catch failures before pushing.
|
||||
|
||||
## Important Reminders
|
||||
|
||||
- Always **commit source code**
|
||||
- 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`
|
||||
+72
-161
@@ -4,94 +4,78 @@ WLED is a fast and feature-rich implementation of an ESP32 and ESP8266 webserver
|
||||
|
||||
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
||||
|
||||
## Working Effectively
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
### Initial Setup
|
||||
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
|
||||
- Install dependencies: `npm ci` (takes ~5 seconds)
|
||||
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
|
||||
## Setup
|
||||
|
||||
### Build and Test Workflow
|
||||
- **ALWAYS build web UI first**: `npm run build` -- takes 3 seconds. NEVER CANCEL.
|
||||
- **Run tests**: `npm test` -- takes 40 seconds. NEVER CANCEL. Set timeout to 2+ minutes.
|
||||
- **Development mode**: `npm run dev` -- monitors file changes and auto-rebuilds web UI
|
||||
- **Hardware firmware build**: `pio run -e [environment]` -- takes 15+ minutes. NEVER CANCEL. Set timeout to 30+ minutes.
|
||||
- Node.js 20+ (see `.nvmrc`)
|
||||
- Install dependencies: `npm ci`
|
||||
- PlatformIO (required only for firmware compilation): `pip install -r requirements.txt`
|
||||
|
||||
### Build Process Details
|
||||
The build has two main phases:
|
||||
1. **Web UI Generation** (`npm run build`):
|
||||
- Processes files in `wled00/data/` (HTML, CSS, JS)
|
||||
- Minifies and compresses web content
|
||||
- Generates `wled00/html_*.h` files with embedded web content
|
||||
- **CRITICAL**: Must be done before any hardware build
|
||||
## Build and Test
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
2. **Hardware Compilation** (`pio run`):
|
||||
- Compiles C++ firmware for various ESP32/ESP8266 targets
|
||||
- Common environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`
|
||||
- List all targets: `pio run --list-targets`
|
||||
| Command | Purpose | Typical Time |
|
||||
|---|---|---|
|
||||
| `npm run build` | Build web UI → generates `wled00/html_*.h` and `wled00/js_*.h` headers | ~3 s |
|
||||
| `npm test` | Run test suite | ~40 s |
|
||||
| `npm run dev` | Watch mode — auto-rebuilds web UI on file changes | — |
|
||||
| `pio run -e <env>` | Build firmware for a hardware target | 15–20 min |
|
||||
|
||||
## Before Finishing Work
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
**CRITICAL: You MUST complete ALL of these steps before marking your work as complete:**
|
||||
- **Always run `npm run build` before any `pio run`** (and run `npm ci` first on fresh clones or when lockfile/dependencies change).
|
||||
- The web UI build generates required `wled00/html_*.h` and `wled00/js_*.h` headers for firmware compilation.
|
||||
- **Build firmware to validate code changes**: `pio run -e esp32dev` — must succeed, never skip this step.
|
||||
- Common firmware environments: `nodemcuv2`, `esp32dev`, `esp8266_2m`, `esp32c3dev`, `esp32s3dev_8MB_opi`
|
||||
|
||||
1. **Run the test suite**: `npm test` -- Set timeout to 2+ minutes. NEVER CANCEL.
|
||||
- All tests MUST pass
|
||||
- If tests fail, fix the issue before proceeding
|
||||
For detailed build timeouts, development workflows, troubleshooting, and validation steps, see [agent-build.instructions.md](agent-build.instructions.md).
|
||||
|
||||
2. **Build at least one hardware environment**: `pio run -e esp32dev` -- Set timeout to 30+ minutes. NEVER CANCEL.
|
||||
- Choose `esp32dev` as it's a common, representative environment
|
||||
- See "Hardware Compilation" section above for the full list of common environments
|
||||
- The build MUST complete successfully without errors
|
||||
- If the build fails, fix the issue before proceeding
|
||||
- **DO NOT skip this step** - it validates that firmware compiles with your changes
|
||||
### Usermod Guidelines
|
||||
|
||||
3. **For web UI changes only**: Manually test the interface
|
||||
- See "Manual Testing Scenarios" section below
|
||||
- Verify the UI loads and functions correctly
|
||||
- New custom effects can be added into the user_fx usermod. Read the [user_fx documentation](https://github.com/wled/WLED/blob/main/usermods/user_fx/README.md) for guidance.
|
||||
- Other usermods may be based on the [EXAMPLE usermod](https://github.com/wled/WLED/tree/main/usermods/EXAMPLE). Never edit the example, always create a copy!
|
||||
- New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/main/wled00/const.h#L160).
|
||||
- To activate a usermod, a custom build configuration should be used. Add the usermod name to `custom_usermods`.
|
||||
|
||||
**If any of these validation steps fail, you MUST fix the issues before finishing. Do NOT mark work as complete with failing builds or tests.**
|
||||
|
||||
## Validation and Testing
|
||||
|
||||
### Web UI Testing
|
||||
- **ALWAYS validate web UI changes manually**:
|
||||
- Start local server: `cd wled00/data && python3 -m http.server 8080`
|
||||
- Open `http://localhost:8080/index.htm` in browser
|
||||
- Test basic functionality: color picker, effects, settings pages
|
||||
- **Check for JavaScript errors** in browser console
|
||||
|
||||
### Code Validation
|
||||
- **No automated linting configured** - follow existing code style in files you edit
|
||||
- **Code style**: Use tabs for web files (.html/.css/.js), spaces (2 per level) for C++ files
|
||||
- **Language**: The repository language is English (british, american, canadian, or australian). If you find other languages, suggest a translation into English.
|
||||
- **C++ formatting available**: `clang-format` is installed but not in CI
|
||||
- **Always run tests before finishing**: `npm test`
|
||||
- **MANDATORY: Always run a hardware build before finishing** (see "Before Finishing Work" section below)
|
||||
|
||||
### Manual Testing Scenarios
|
||||
After making changes to web UI, always test:
|
||||
- **Load main interface**: Verify index.htm loads without errors
|
||||
- **Navigation**: Test switching between main page and settings pages
|
||||
- **Color controls**: Verify color picker and brightness controls work
|
||||
- **Effects**: Test effect selection and parameter changes
|
||||
- **Settings**: Test form submission and validation
|
||||
|
||||
## Common Tasks
|
||||
## Project Structure Overview
|
||||
|
||||
### Project Branch / Release Structure
|
||||
```
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
```text
|
||||
main # Main development trunk (daily/nightly) 17.0.0-dev
|
||||
├── V5 # special branch: code rework for esp-idf 5.5.x (unstable)
|
||||
├── V5-C6 # special branch: integration of new MCU types: esp32-c5, esp32-c6, esp32-p4 (unstable)
|
||||
16_x # current beta, preparations for next release 16.0.0
|
||||
0_15_x # maintainance (bugfixes only) for current release 0.15.4
|
||||
(tag) v0.14.4 # previous version 0.14.4 (no maintainance)
|
||||
(tag) v0.13.3 # old version 0.13.3 (no maintainance)
|
||||
16_x # maintenance for release 16.x.y
|
||||
0_15_x # maintenance (bugfixes only) for current release 0.15.4
|
||||
(tag) v0.14.4 # previous version 0.14.4 (no maintenance)
|
||||
(tag) v0.13.3 # old version 0.13.3 (no maintenance)
|
||||
(tag) v0. ... . ... # historical versions 0.12.x and before
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
- ``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
|
||||
```
|
||||
|
||||
tl;dr:
|
||||
* Firmware source: `wled00/` (C++). Web UI source: `wled00/data/`. Build targets: `platformio.ini`.
|
||||
* Auto-generated headers: `wled00/html_*.h` and `wled00/js_*.h` — **never edit or commit**.
|
||||
* ArduinoJSON + AsyncJSON: `wled00/src/dependencies/json` (included via `wled.h`). CI/CD: `.github/workflows/`.
|
||||
* Usermods: `usermods/` (C++, with individual library.json).
|
||||
* Contributor docs: `docs/` (coding guidelines, etc).
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
Detailed overview:
|
||||
|
||||
```text
|
||||
wled00/ # Main firmware source (C++) "WLED core"
|
||||
├── data/ # Web interface files
|
||||
│ ├── index.htm # Main UI
|
||||
@@ -109,109 +93,36 @@ platformio_override.sample.ini # examples for custom build configurations - entr
|
||||
# platformio_override.ini is _not_ stored in the WLED repository!
|
||||
usermods/ # User-contributed addons to the WLED core, maintained by individual contributors (C++, with individual library.json)
|
||||
package.json # Node.js dependencies and scripts, release identification
|
||||
pio-scripts/ # Build tools (platformio)
|
||||
pio-scripts/ # Build tools (PlatformIO)
|
||||
tools/ # Build tools (Node.js), partition files, and generic utilities
|
||||
├── cdata.js # Web UI build script
|
||||
└── cdata-test.js # Test suite
|
||||
docs/ # Contributor docs, coding guidelines
|
||||
.github/workflows/ # CI/CD pipelines
|
||||
```
|
||||
|
||||
### Key Files and Their Purpose
|
||||
- `wled00/data/index.htm` - Main web interface
|
||||
- `wled00/data/settings*.htm` - Configuration pages
|
||||
- `tools/cdata.js` - Converts web files to C++ headers
|
||||
- `wled00/wled.h` - Main firmware configuration
|
||||
- `platformio.ini` - Hardware build targets and settings
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
## General Guidelines
|
||||
|
||||
### Development Workflow (instructions for agent mode)
|
||||
1. **For web UI changes**:
|
||||
- Edit files in `wled00/data/`
|
||||
- Run `npm run build` to regenerate headers
|
||||
- Test with local HTTP server
|
||||
- Run `npm test` to validate build system
|
||||
|
||||
2. **For firmware changes**:
|
||||
- Edit files in `wled00/` (but NOT `html_*.h` files)
|
||||
- Ensure web UI is built first (`npm run build`)
|
||||
- Build firmware: `pio run -e [target]`
|
||||
- Flash to device: `pio run -e [target] --target upload`
|
||||
|
||||
3. **For both web and firmware**:
|
||||
- Always build web UI first
|
||||
- Test web interface manually
|
||||
- Build and test firmware if making firmware changes
|
||||
|
||||
#### Adding a new usermod
|
||||
- New custom effects can be added into the user_fx usermod. Read the [user_fx documentation](https://github.com/wled/WLED/blob/main/usermods/user_fx/README.md) for guidance.
|
||||
- Other usermods may be based on the [EXAMPLE usermod](https://github.com/wled/WLED/tree/main/usermods/EXAMPLE). Never edit the example, always create a copy!
|
||||
- New usermod IDs can be added into [wled00/const.h](https://github.com/wled/WLED/blob/746df240119b585d8c8fa85e6aac7ed707947ea0/wled00/const.h#L160).
|
||||
- to activate a usermod, a custom build configuration should be used. Add the usermod name to ``custom_usermods``.
|
||||
|
||||
## Build Timing and Timeouts
|
||||
|
||||
**IMPORTANT: Use these timeout values when running builds:**
|
||||
|
||||
- **Web UI build** (`npm run build`): 3 seconds typical - Set timeout to 30 seconds minimum
|
||||
- **Test suite** (`npm test`): 40 seconds typical - Set timeout to 120 seconds (2 minutes) minimum
|
||||
- **Hardware builds** (`pio run -e [target]`): 15-20 minutes typical for first build - Set timeout to 1800 seconds (30 minutes) minimum
|
||||
- Subsequent builds are faster due to caching
|
||||
- First builds download toolchains and dependencies which takes significant time
|
||||
- **NEVER CANCEL long-running builds** - PlatformIO downloads and compilation require patience
|
||||
|
||||
**When validating your changes before finishing, you MUST wait for the hardware build to complete successfully. Set the timeout appropriately and be patient.**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Build fails with missing html_*.h**: Run `npm run build` first
|
||||
- **Web UI looks broken**: Check browser console for JavaScript errors
|
||||
- **PlatformIO network errors**: Try again, downloads can be flaky
|
||||
- **Node.js version issues**: Ensure Node.js 20+ is installed (check `.nvmrc`)
|
||||
|
||||
### When Things Go Wrong
|
||||
- **Clear generated files**: `rm -f wled00/html_*.h` then rebuild
|
||||
- **Force web UI rebuild**: `npm run build -- --force` or `npm run build -- -f`
|
||||
- **Clean PlatformIO cache**: `pio run --target clean`
|
||||
- **Reinstall dependencies**: `rm -rf node_modules && npm install`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Always commit source files**
|
||||
- **Web UI re-built is part of the platformio firmware compilation**
|
||||
- **do not commit generated html_*.h files**
|
||||
- **DO NOT edit `wled00/html_*.h` files** - they are auto-generated. If needed, modify Web UI files in `wled00/data/`.
|
||||
- **Test web interface manually after any web UI changes**
|
||||
- **Repository language is English.** Suggest translations for non-English content.
|
||||
- **Use VS Code with PlatformIO extension** for best development experience.
|
||||
- **Never edit or commit** `wled00/html_*.h` and `wled00/js_*.h` — auto-generated from `wled00/data/`.
|
||||
- If updating Web UI files in `wled00/data/`, **make use of common functions in `wled00/data/common.js` whenever possible**.
|
||||
- **When unsure, say so.** Gather more information rather than guessing.
|
||||
- **Acknowledge good patterns** when you see them. Summarize good practices as part of your review - positive feedback always helps.
|
||||
- **Acknowledge good patterns** when you see them. 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.
|
||||
- **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.
|
||||
- If updating Web UI files in `wled00/data/`, **make use of common functions availeable in `wled00/data/common.js` where possible**.
|
||||
- **Use VS Code with PlatformIO extension for best development experience**
|
||||
- **Hardware builds require appropriate ESP32/ESP8266 development board**
|
||||
- **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.
|
||||
|
||||
## CI/CD Pipeline
|
||||
Refer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.
|
||||
|
||||
**The GitHub Actions CI workflow will:**
|
||||
1. Installs Node.js and Python dependencies
|
||||
2. Runs `npm test` to validate build system (MUST pass)
|
||||
3. Builds web UI with `npm run build` (automatically run by PlatformIO)
|
||||
4. Compiles firmware for ALL hardware targets listed in `default_envs` (MUST succeed for all)
|
||||
5. Uploads build artifacts
|
||||
### Pull Request Expectations
|
||||
|
||||
**To ensure CI success, you MUST locally:**
|
||||
- Run `npm test` and ensure it passes
|
||||
- Run `pio run -e esp32dev` (or another common environment from "Hardware Compilation" section) and ensure it completes successfully
|
||||
- If either fails locally, it WILL fail in CI
|
||||
|
||||
**Match this workflow in your local development to ensure CI success. Do not mark work complete until you have validated builds locally.**
|
||||
|
||||
## 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.
|
||||
- Make sure AI-generated code is well documented.
|
||||
|
||||
## 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.
|
||||
- **Document your changes in the PR.** Every pull request description should include a summary of *what* changed and *why*. Link to related issues where applicable. Provide screenshots to showcase new features.
|
||||
- **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.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
; ----------------------------------------------------------------------------
|
||||
; 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
|
||||
@@ -3,7 +3,15 @@ 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:
|
||||
@@ -11,6 +19,9 @@ 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'
|
||||
@@ -35,6 +46,9 @@ 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:
|
||||
@@ -51,8 +65,8 @@ jobs:
|
||||
~/.platformio/.cache
|
||||
~/.buildcache
|
||||
build_output
|
||||
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') }}-
|
||||
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') }}-
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
@@ -63,9 +77,21 @@ 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: firmware-${{ matrix.environment }}
|
||||
name: ${{ steps.artifact_name.outputs.name }}
|
||||
path: |
|
||||
build_output/release/*.bin
|
||||
build_output/release/*_ESP02*.bin.gz
|
||||
|
||||
@@ -7,6 +7,9 @@ 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
|
||||
@@ -26,12 +29,12 @@ jobs:
|
||||
uses: janheinrichmerker/action-github-changelog-generator@v2.4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sinceTag: v0.15.0
|
||||
sinceTag: v16.0.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@main
|
||||
uses: andelf/nightly-release@5834076edc55cc05975561c9722043f072ac5c26
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -5,10 +5,15 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
|
||||
wled_build:
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
release: true
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
@@ -18,6 +23,14 @@ 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
|
||||
@@ -27,12 +40,9 @@ 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: Create draft release
|
||||
- name: Update release description
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body: ${{ steps.changelog.outputs.changelog }}
|
||||
draft: True
|
||||
files: |
|
||||
*.bin
|
||||
*.bin.gz
|
||||
|
||||
|
||||
+110
-11
@@ -4,33 +4,51 @@ 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.pull_request.head.repo.full_name != github.repository
|
||||
if: github.event_name == 'pull_request' && 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:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
- name: Install PlatformIO
|
||||
run: pip install -r requirements.txt
|
||||
- name: Get default environments
|
||||
fetch-depth: 0
|
||||
- name: Get changed usermod environments
|
||||
id: envs
|
||||
run: |
|
||||
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
|
||||
# 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
|
||||
outputs:
|
||||
usermods: ${{ steps.envs.outputs.usermods }}
|
||||
|
||||
|
||||
build:
|
||||
# Only run for pull requests from forks (not from branches within wled/WLED)
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
# 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 != '[]'
|
||||
name: Build Enviornments
|
||||
runs-on: ubuntu-latest
|
||||
needs: get_usermod_envs
|
||||
@@ -71,4 +89,85 @@ jobs:
|
||||
cat platformio_override.ini
|
||||
|
||||
- name: Build firmware
|
||||
run: pio run -e ${{ matrix.environment }}
|
||||
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 }}
|
||||
|
||||
@@ -16,6 +16,7 @@ __pycache__/
|
||||
|
||||
esp01-update.sh
|
||||
platformio_override.ini
|
||||
platformio_release.ini
|
||||
replace_fs.py
|
||||
wled-update.sh
|
||||
|
||||
|
||||
Vendored
+1
@@ -2,6 +2,7 @@
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"pioarduino.pioarduino-ide",
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
# 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 15× slower than DRAM on ESP32 (dual-SPI bus), 3–10× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM.
|
||||
- Consider that ESP32 often crashes when the largest available DRAM chunk gets below 10 KB.
|
||||
|
||||
### Preprocessor / Feature Flags
|
||||
- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)
|
||||
- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.
|
||||
- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.
|
||||
- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`
|
||||
|
||||
### Math Functions
|
||||
- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)
|
||||
- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`
|
||||
- Replace `inoise8` / `inoise16` with `perlin8` / `perlin16`
|
||||
|
||||
### Hot-Path Code (Pixel Pipeline)
|
||||
- Use function attributes: `IRAM_ATTR`, `WLED_O2_ATTR`, `__attribute__((hot))`
|
||||
- Cache class members to locals before loops
|
||||
- Pre-compute invariants outside loops; use reciprocals to avoid division
|
||||
- Unsigned range checks: `if ((uint_fast16_t)(pix - start) < len)`
|
||||
|
||||
### ESP32 Tasks
|
||||
- `delay(1)` in custom FreeRTOS tasks (NOT `yield()`) — feeds IDLE watchdog
|
||||
- Do not use `delay()` in effects (FX.cpp) or hot pixel path
|
||||
|
||||
#### ESP32 Task Synchronization
|
||||
|
||||
- Use FreeRTOS mutexes, semaphores or queues when true concurrent access from multiple FreeRTOS tasks is possible, and race-conditions can lead to unexpected behaviour.
|
||||
- **Avoid `portENTER_CRITICAL()` / `portEXIT_CRITICAL()`**, as these functions stall the complete system and may cause LEDs flickering. Prefer FreeRTOS mutexes, semaphores or queues.
|
||||
- **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.
|
||||
+11
-7
@@ -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](https://github.com/wled/WLED/blob/main/.github/copilot-instructions.md#project-branch--release-structure) (in our AI instructions)
|
||||
* [Instructions for creating usermods](https://github.com/wled/WLED/blob/main/.github/copilot-instructions.md#adding-a-new-usermod) (in our AI instructions)
|
||||
* [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)
|
||||
* 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,7 +30,8 @@ This lets you update your PR if needed, while you can work on other tasks in 'ma
|
||||
|
||||
### Target branch for pull requests
|
||||
|
||||
Please make all PRs against the `main` branch.
|
||||
> [!IMPORTANT]
|
||||
> Please make all PRs against the `main` branch.
|
||||
|
||||
### Describing your PR
|
||||
|
||||
@@ -140,8 +141,6 @@ 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).
|
||||
@@ -167,14 +166,19 @@ 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 a comment like `// This section was AI-generated` for larger chunks
|
||||
- ✅ **Be transparent** - Add comments `// AI: below section was generated by an AI` ... `// AI: end` around larger chunks
|
||||
- ✅ **Use AI for translation** - AI is great for translating comments to English (but verify technical terms!)
|
||||
|
||||
### Code style
|
||||
|
||||
Don't stress too much about style! When in doubt, just match the style in the files you're editing. 😊
|
||||
|
||||
Here are our main guidelines:
|
||||
Our review bot (coderabbit) has learned lots of detailed guides and hints - it will suggest them automatically when you submit a PR for review.
|
||||
If you are curious, these are the detailed guides:
|
||||
* [C++ Coding](docs/cpp.instructions.md)
|
||||
* [WebUi: HTML, JS, CSS](docs/web.instructions.md)
|
||||
|
||||
Below are the main rules used in the WLED repository:
|
||||
|
||||
#### Indentation
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
applyTo: ".github/workflows/*.yml,.github/workflows/*.yaml"
|
||||
---
|
||||
# CI/CD Conventions — GitHub Actions Workflows
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## YAML Style
|
||||
|
||||
- Indent with **2 spaces** (no tabs)
|
||||
- Every workflow, job, and step must have a `name:` field that clearly describes its purpose
|
||||
- Group related steps logically; separate unrelated groups with a blank line
|
||||
- Comments (`#`) are encouraged for non-obvious decisions (e.g., why `fail-fast: false` is set, what a cron expression means)
|
||||
|
||||
## Workflow Structure
|
||||
|
||||
### Triggers
|
||||
|
||||
- Declare `on:` triggers explicitly; avoid bare `on: push` without branch filters on long-running or expensive jobs
|
||||
- Prefer `workflow_call` for shared build logic (see `build.yml`) to avoid duplicating steps across workflows
|
||||
- Document scheduled triggers (`cron:`) with a human-readable comment:
|
||||
|
||||
```yaml
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC daily
|
||||
```
|
||||
|
||||
### Jobs
|
||||
|
||||
- Express all inter-job dependencies with `needs:` — never rely on implicit ordering
|
||||
- Use job `outputs:` + step `id:` to pass structured data between jobs (see `get_default_envs` in `build.yml`)
|
||||
- Set `fail-fast: false` on matrix builds so that a single failing environment does not cancel others
|
||||
|
||||
### Runners
|
||||
|
||||
- Pin to a specific Ubuntu version (`ubuntu-22.04`, `ubuntu-24.04`) rather than `ubuntu-latest` for reproducible builds
|
||||
- Only use `ubuntu-latest` in jobs where exact environment reproducibility is not required (e.g., trivial download/publish steps)
|
||||
|
||||
### Tool and Language Versions
|
||||
|
||||
- Pin tool versions explicitly:
|
||||
```yaml
|
||||
python-version: '3.12'
|
||||
```
|
||||
- Do not rely on the runner's pre-installed tool versions — always install via a versioned setup action
|
||||
|
||||
### Caching
|
||||
|
||||
- Always cache package managers and build tool directories when the job installs dependencies:
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
```
|
||||
- Include the environment name or a relevant identifier in cache keys when building multiple targets
|
||||
|
||||
### Artifacts
|
||||
|
||||
- Name artifacts with enough context to be unambiguous (e.g., `firmware-${{ matrix.environment }}`)
|
||||
- Avoid uploading artifacts that will never be consumed downstream
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
Important: Several current workflows still violate parts of the baseline below - migration is in progress.
|
||||
|
||||
### Permissions — Least Privilege
|
||||
|
||||
Declare explicit `permissions:` blocks. The default token permissions are broad; scope them to the minimum required:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read # for checkout
|
||||
```
|
||||
|
||||
For jobs that publish releases or write to the repository:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: write # create/update releases
|
||||
```
|
||||
|
||||
A common safe baseline for build-only jobs:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read
|
||||
```
|
||||
|
||||
### Supply Chain — Action Pinning
|
||||
|
||||
**Third-party actions** (anything outside the `actions/` and `github/` namespaces) should be pinned to a specific release tag. Branch pins (`@main`, `@master`) are **not allowed** — they can be updated by the action author at any time without notice:
|
||||
|
||||
```yaml
|
||||
# ✅ Acceptable — specific version tag. SHA pinning recommended for more security, as @v2 is still a mutable tag.
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
# ❌ Not acceptable — mutable branch reference
|
||||
uses: andelf/nightly-release@main
|
||||
```
|
||||
|
||||
SHA pinning (e.g., `uses: someorg/some-action@abc1234`) is the most secure option for third-party actions; it is recommended when auditing supply-chain risk is a priority. At minimum, always use a specific version tag.
|
||||
|
||||
**First-party actions** (`actions/checkout`, `actions/cache`, `actions/upload-artifact`, etc.) pinned to a major version tag (e.g., `@v4`) are acceptable because GitHub maintains and audits these.
|
||||
|
||||
When adding a new third-party action:
|
||||
1. Check that the action's repository is actively maintained
|
||||
2. Review the action's source before adding it
|
||||
3. Prefer well-known, widely-used actions over obscure ones
|
||||
|
||||
### Credentials and Secrets
|
||||
|
||||
- Use `${{ secrets.GITHUB_TOKEN }}` for operations within the same repository — it is automatically scoped and rotated
|
||||
- Never commit secrets, tokens, or passwords into workflow files or any tracked file
|
||||
- Never print secrets in `run:` steps, even with `echo` — GitHub masks known secrets but derived values are not automatically masked
|
||||
- Scope secrets to the narrowest step that needs them using `env:` at the step level, not at the workflow level:
|
||||
|
||||
```yaml
|
||||
# ✅ Scoped to the step that needs it
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ❌ Unnecessarily broad
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
- Personal Access Tokens (PATs, stored as repository secrets) should have the minimum required scopes and should be rotated periodically
|
||||
|
||||
### Script Injection
|
||||
|
||||
`${{ }}` expressions are evaluated before the shell script runs. If an expression comes from untrusted input (PR titles, issue bodies, branch names from forks), it can inject arbitrary shell commands.
|
||||
|
||||
**Never** interpolate `github.event.*` values directly into a `run:` step:
|
||||
|
||||
```yaml
|
||||
# ❌ Injection risk — PR title is attacker-controlled
|
||||
- run: echo "${{ github.event.pull_request.title }}"
|
||||
|
||||
# ✅ Safe — value passed through an environment variable
|
||||
- env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
run: echo "$PR_TITLE"
|
||||
```
|
||||
|
||||
This rule applies to any value that originates outside the repository (issue bodies, labels, comments, commit messages from forks).
|
||||
|
||||
### Pull Request Workflows
|
||||
|
||||
- Workflows triggered by `pull_request` from a fork run with **read-only** token permissions and no access to repository secrets — this is intentional and correct
|
||||
- Do not use `pull_request_target` unless you fully understand the security implications; it runs in the context of the base branch and *does* have secret access, making it a common attack surface
|
||||
@@ -0,0 +1,528 @@
|
||||
---
|
||||
applyTo: "**/*.cpp,**/*.h,**/*.hpp,**/*.ino"
|
||||
---
|
||||
# C++ Coding Conventions
|
||||
|
||||
> **Note for AI review tools**: sections enclosed in
|
||||
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
|
||||
> contributor reference material. Do **not** use that content as actionable review
|
||||
> criteria — treat it as background context only.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hiding this reference, to avoid cyclic "include" loops -->
|
||||
See also: [CONTRIBUTING.md](../CONTRIBUTING.md) for general style guidelines that apply to all contributors.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## Formatting
|
||||
|
||||
- Indent with **2 spaces** (no tabs in C++ files)
|
||||
- Opening braces on the same line is preferred (K&R style). Brace on a separate line (Allman style) is acceptable
|
||||
- Single-statement `if` bodies may omit braces: `if (a == b) doStuff(a);`
|
||||
- Space between keyword and parenthesis: `if (...)`, `for (...)`. No space between function name and parenthesis: `doStuff(a)`
|
||||
- No enforced line-length limit; wrap when a line exceeds your editor width
|
||||
|
||||
## Naming
|
||||
|
||||
- **camelCase** for functions and variables: `setValuesFromMainSeg()`, `effectCurrent`
|
||||
- **PascalCase** for classes and structs: `PinManagerClass`, `BusConfig`
|
||||
- **PascalCase** for enum values: `PinOwner::BusDigital`
|
||||
- **UPPER_CASE** for macros and constants: `WLED_MAX_USERMODS`, `DEFAULT_CLIENT_SSID`
|
||||
|
||||
## General
|
||||
|
||||
- Follow the existing style in the file you are editing
|
||||
- If possible, use `static` for local (C-style) variables and functions (keeps the global namespace clean)
|
||||
- Avoid unexplained "magic numbers". Prefer named constants (`constexpr`) or C-style `#define` constants for repeated numbers that have the same meaning
|
||||
- Include `"wled.h"` as the primary project header where needed
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
## Header Guards
|
||||
|
||||
Most headers use `#ifndef` / `#define` guards. Some newer headers add `#pragma once` before the guard:
|
||||
|
||||
```cpp
|
||||
#ifndef WLED_EXAMPLE_H
|
||||
#define WLED_EXAMPLE_H
|
||||
// ...
|
||||
#endif // WLED_EXAMPLE_H
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## Comments
|
||||
|
||||
- `//` for inline comments, `/* ... */` for block comments. Always put a space after `//`
|
||||
- **AI attribution:** When a larger block of code is generated by an AI tool, mark it with an `// AI:` comment so reviewers know to scrutinize it:
|
||||
|
||||
```cpp
|
||||
// AI: below section was generated by an AI
|
||||
void calculateCRC(const uint8_t* data, size_t len) {
|
||||
...
|
||||
}
|
||||
// AI: end
|
||||
```
|
||||
|
||||
Single-line AI-assisted edits do not need the marker — use it when the AI produced a contiguous block that a human did not write line-by-line.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hidden from AI for now, as it created too many "please add a description" review findings in my first tests -->
|
||||
- **Function & feature comments:** Every non-trivial function should have a brief comment above it describing what it does. Include a note about each parameter when the names alone are not self-explanatory:
|
||||
|
||||
```cpp
|
||||
/* *****
|
||||
* Apply gamma correction to a single color channel.
|
||||
* @param value raw 8-bit channel value (0–255)
|
||||
* @param gamma gamma exponent (typically 2.8)
|
||||
* @return corrected 8-bit value
|
||||
***** */
|
||||
uint8_t gammaCorrect(uint8_t value, float gamma);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Short accessor-style functions (getters/setters, one-liners) may skip this if their purpose is obvious from the name.
|
||||
|
||||
## Preprocessor & Feature Flags
|
||||
|
||||
- Prefer compile-time feature flags (`#ifdef` / `#ifndef`) over runtime checks where possible
|
||||
- Platform differentiation: `ARDUINO_ARCH_ESP32` vs `ESP8266`
|
||||
- PSRAM availability: `BOARD_HAS_PSRAM`
|
||||
|
||||
## Error Handling
|
||||
|
||||
- `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` for developer diagnostics (compiled out unless `-D WLED_DEBUG`)
|
||||
- Don't rely on C++ exceptions — use return codes (`-1` / `false` for errors) and global flags (e.g. `errorFlag = ERR_LOW_MEM`). Some builds don't support C++ exceptions.
|
||||
|
||||
## Strings
|
||||
|
||||
- Use `const char*` for temporary/parsed strings
|
||||
- Avoid `String` (Arduino heap-allocated string) in hot paths; acceptable in config/setup code
|
||||
- Use `F("string")` for string constants (major RAM win on ESP8266; mostly overload/type compatibility on ESP32)
|
||||
- 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.
|
||||
On **ESP32**, `PROGMEM` is a no-op and string literals already reside in flash/rodata, so `F()` yields little RAM benefit but remains harmless (it satisfies `__FlashStringHelper*` overloads that some APIs expect).
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
```cpp
|
||||
DEBUG_PRINTLN(F("WS client connected.")); // string stays in flash, not RAM
|
||||
DEBUG_PRINTF_P(PSTR("initE: Ignoring attempt for invalid ethernetType (%d)\n"), ethernetType); // format string stays in flash
|
||||
```
|
||||
|
||||
## Memory
|
||||
|
||||
- **PSRAM-aware allocation**: use `d_malloc()` (prefer DRAM), `p_malloc()` (prefer PSRAM) from `fcn_declare.h`
|
||||
- **Avoid Variable Length Arrays (VLAs)**: FreeRTOS task stacks are typically 2–8 KB. A runtime-sized VLA can silently exhaust the stack. Use fixed-size arrays or heap allocation (`d_malloc` / `p_malloc`). Any VLA must be explicitly justified in source or PR.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
GCC/Clang support VLAs as an extension (they are not part of the C++ standard), so they look like a legitimate feature — but they are allocated on the stack at runtime. On ESP32/ESP8266, a VLA whose size depends on a runtime parameter (segment dimensions, pixel counts, etc.) can silently exhaust the stack and cause the program to behave in unexpected ways or crash.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- **Larger buffers** (LED data, JSON documents) should use PSRAM when available and technically feasible
|
||||
- **Hot-path**: some data should stay in DRAM or IRAM for performance reasons
|
||||
- Memory efficiency matters, but is less critical on boards with PSRAM
|
||||
|
||||
Heap fragmentation is a concern:
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
- Fragmentation can lead to crashes, even when the overall amount of available heap is still good. The C++ runtime doesn't do any "garbage collection".
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- Avoid frequent `d_malloc` and `d_free` inside a function, especially for small sizes.
|
||||
- Avoid frequent creation / destruction of objects.
|
||||
- Allocate buffers early, and try to re-use them.
|
||||
- Instead of incrementally appending to a `String`, reserve the expected max buffer upfront by using the `reserve()` method.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
```cpp
|
||||
String result;
|
||||
result.reserve(65); // pre-allocate to avoid realloc fragmentation
|
||||
```
|
||||
|
||||
```cpp
|
||||
// prefer DRAM; falls back gracefully and enforces MIN_HEAP_SIZE guard
|
||||
_ledsDirty = (byte*) d_malloc(getBitArrayBytes(_len));
|
||||
```
|
||||
|
||||
```cpp
|
||||
_mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation - does not increase size()
|
||||
_modeData.reserve(_modeCount); // allocate memory to prevent initial fragmentation - does not increase size()
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
## `const` and `constexpr`
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
`const` is a promise to the compiler that a value (or object) will not change - a function declared with a `const char* message` parameter is not allowed to modify the content of `message`.
|
||||
This pattern enables optimizations and makes intent clear to reviewers.
|
||||
|
||||
`constexpr` allows to define constants that are *guaranteed* to be evaluated by the compiler (zero run-time costs).
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- For function parameters that are read-only, prefer `const &` or `const`.
|
||||
|
||||
### `const` locals
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
* Adding `const` to a local variable that is only assigned once is optional, but *not* strictly necessary.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
* In hot-path code, `const` on cached locals may help the compiler keep values in registers.
|
||||
```cpp
|
||||
const uint_fast16_t cols = vWidth();
|
||||
const uint_fast16_t rows = vHeight();
|
||||
```
|
||||
|
||||
### `const` references to avoid copies
|
||||
- Pass objects by `const &` (or `&`) instead of copying them implicitly.
|
||||
- Use `const &` (or `&`) inside loops - This avoids constructing temporary objects on every access.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
const auto &m = _mappings[i]; // reference, not a copy (bus_manager.cpp)
|
||||
Segment& sourcesegment = strip.getSegment(sourceid); // alias — avoids creating a temporary Segment instance
|
||||
```
|
||||
|
||||
For function parameters that are read-only, prefer `const &`:
|
||||
```cpp
|
||||
BusManager::add(const BusConfig &bc, bool placeholder) {
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
- Class **Data Members:** Avoid reference data members (`T&` or `const T&`) in a class.
|
||||
A reference member can outlive the object it refers to, causing **dangling reference** bugs that are hard to diagnose. Prefer value storage or use a pointer and document the expected lifetime.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
<!-- hidden from AI for now - codebase is not compliant to this rule (slowly migrating) -->
|
||||
### `constexpr` over `#define`
|
||||
|
||||
- Prefer `constexpr` for compile-time constants. Unlike `#define`, `constexpr` respects scope and type safety, keeping the global namespace clean.
|
||||
|
||||
```cpp
|
||||
// Prefer:
|
||||
constexpr uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
constexpr int WLED_MAX_BUSSES = WLED_MAX_DIGITAL_CHANNELS + WLED_MAX_ANALOG_CHANNELS;
|
||||
|
||||
// Avoid (when possible):
|
||||
#define TWO_CHANNEL_MASK 0x00FF00FF
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### `static_assert` over `#error`
|
||||
|
||||
- Use `static_assert` instead of the C-style `#if … #error … #endif` pattern when validating compile-time constants. It provides a clear message and works with `constexpr` values.
|
||||
- `#define` and `#if ... #else ... #endif` is still needed for conditional-compilation guards and build-flag-overridable values.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
// Prefer:
|
||||
constexpr int WLED_MAX_BUSSES = WLED_MAX_DIGITAL_CHANNELS + WLED_MAX_ANALOG_CHANNELS;
|
||||
static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
|
||||
// Avoid:
|
||||
#if (WLED_MAX_BUSSES > 32)
|
||||
#error "WLED_MAX_BUSSES exceeds hard limit"
|
||||
#endif
|
||||
```
|
||||
|
||||
```cpp
|
||||
// using static_assert() to validate enumerated types (zero cost at runtime)
|
||||
static_assert(0u == static_cast<uint8_t>(PinOwner::None),
|
||||
"PinOwner::None must be zero, so default array initialization works as expected");
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### `static` and `const` class methods
|
||||
|
||||
#### `const` member functions
|
||||
|
||||
Marking a member function `const` tells the compiler that it does not modify the object's state:
|
||||
|
||||
```cpp
|
||||
uint16_t length() const { return _len; }
|
||||
bool isActive() const { return _active; }
|
||||
```
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
Benefits for GCC/Xtensa/RISC-V:
|
||||
- The compiler knows the method cannot write to `this`, so it is free to **keep member values in registers** across the call and avoid reload barriers.
|
||||
- `const` methods can be called on `const` objects and `const` references — essential when passing large objects as `const &` to avoid copying.
|
||||
- `const` allows the compiler to **eliminate redundant loads**: if a caller already has a member value cached, the compiler can prove the `const` call cannot invalidate it.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
Declare getter, query, or inspection methods `const`. If you need to mark a member `mutable` to work around this (e.g. for a cache or counter), document the reason.
|
||||
|
||||
#### `static` member functions
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
A `static` member function has no implicit `this` pointer. This has two distinct advantages:
|
||||
|
||||
1. **Smaller code, faster calls**: no `this` is passed in a register. On Xtensa and RISC-V, this removes one register argument from every call site and prevents the compiler from emitting `this`-preservation code around inlined blocks.
|
||||
2. **Better inlining**: GCC can inline a `static` method with more certainty because it cannot be overridden by a derived class (no virtual dispatch ambiguity) and has no aliasing concern through `this`.
|
||||
|
||||
Use `static` for any method that does not need access to instance members:
|
||||
|
||||
```cpp
|
||||
// Factory / utility — no instance needed:
|
||||
static BusConfig fromJson(JsonObject obj);
|
||||
|
||||
// Pure computation helpers:
|
||||
static uint8_t gamma8(uint8_t val);
|
||||
static uint32_t colorBalance(uint32_t color, uint8_t r, uint8_t g, uint8_t b);
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
`static` communicates intent clearly: a reviewer immediately knows the method is stateless and safe to call without a fully constructed object.
|
||||
|
||||
> **Rule of thumb**: if a method does not read or write any member variable, make it `static`. If it only reads member variables, make it `const`. Note: `static` methods cannot also be `const`-qualified because there is no implicit `this` pointer to be const — just use `static`. Both qualifiers reduce coupling and improve generated code on all ESP32 targets.
|
||||
|
||||
---
|
||||
|
||||
## Hot-Path Optimization
|
||||
|
||||
The hot path is the per-frame pixel pipeline: **Segment → Strip → BusManager → Bus(Digital,HUB75,Network) or PolyBus → LED driver, plus ``WS2812FX::show()`` and below**.
|
||||
Speed is the priority here. The patterns below are taken from existing hot-path code (`FX_fcn.cpp`, `FX_2Dfcn.cpp`, `bus_manager.cpp`, `colors.cpp`) and should be followed when modifying these files.
|
||||
|
||||
Note: `FX.cpp` (effect functions) is written by many contributors and has diverse styles — that is acceptable.
|
||||
|
||||
### Function Attributes
|
||||
|
||||
Stack the appropriate attributes on hot-path functions. Defined in `const.h`:
|
||||
|
||||
| Attribute | Meaning | When to use |
|
||||
|---|---|---|
|
||||
| `__attribute__((hot))` | Branch-prediction hint | hot-path functions with complex logic |
|
||||
| `IRAM_ATTR` | Place in fast IRAM (ESP32) | Critical per-pixel functions (e.g. `BusDigital::setPixelColor`) |
|
||||
| `IRAM_ATTR_YN` | IRAM on ESP32, no-op on ESP8266 | Hot functions that ESP8266 can't fit in IRAM |
|
||||
| `WLED_O2_ATTR` | Force `-O2` optimization | Most hot-path functions |
|
||||
| `WLED_O3_ATTR` | Force `-O3,fast-math` | Innermost color math (e.g. `color_blend`) |
|
||||
| `[[gnu::hot]] inline` | Modern C++ attribute + inline | Header-defined accessors (e.g. `progress()`, `currentBri()`) |
|
||||
|
||||
Note: `WLED_O3_ATTR` sometimes causes performance loss compared to `WLED_O2_ATTR`. Choose optimization levels based on test results.
|
||||
|
||||
Example signature:
|
||||
|
||||
```cpp
|
||||
void IRAM_ATTR_YN WLED_O2_ATTR __attribute__((hot)) Segment::setPixelColor(unsigned i, uint32_t c)
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Cache Members to Locals Before Loops
|
||||
|
||||
Copy class members and virtual-call results to local variables before entering a loop:
|
||||
|
||||
```cpp
|
||||
uint_fast8_t count = numBusses; // avoid repeated member access
|
||||
for (uint_fast8_t i = 0; i < count; i++) {
|
||||
Bus* const b = busses[i]; // const pointer hints to compiler
|
||||
uint_fast16_t bstart = b->getStart();
|
||||
uint_fast16_t blen = b->getLength();
|
||||
...
|
||||
}
|
||||
```
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
### Unsigned Range Check
|
||||
|
||||
Replace two-comparison range tests with a single unsigned subtraction:
|
||||
|
||||
```cpp
|
||||
// Instead of: if (pix >= bstart && pix < bstart + blen)
|
||||
if ((uint_fast16_t)(pix - bstart) < blen) // also catches negative pix via unsigned underflow
|
||||
```
|
||||
|
||||
### Early Returns
|
||||
|
||||
Guard every hot-path function with the cheapest necessary checks first:
|
||||
|
||||
```cpp
|
||||
if (!isActive()) return; // inactive segment
|
||||
if (unsigned(i) >= vLength()) return; // bounds check (catches negative i too)
|
||||
```
|
||||
|
||||
### Avoid Nested Calls — Fast Path / Complex Path
|
||||
|
||||
Avoid calling non-inline functions or making complex decisions inside per-pixel hot-path code. When a function has both a common simple case and a rare complex case, split it into two variants and choose once per frame rather than per pixel.
|
||||
|
||||
General rules:
|
||||
- Keep fast-path functions free of non-inline calls, multi-way branches, and complex switch-case decisions.
|
||||
- Hoist per-frame decisions (e.g. simple vs. complex segment) out of the per-pixel loop.
|
||||
- Code duplication between fast/slow variants is acceptable to keep the fast path lean.
|
||||
|
||||
### Function Pointers to Eliminate Repeated Decisions
|
||||
|
||||
When the same decision (e.g. "which drawing routine?") would be evaluated for every pixel, assign the chosen variant to a function pointer once and let the inner loop call through the pointer. This removes the branch entirely — the calling code (e.g. the GIF decoder loop) only ever invokes one function per frame, with no per-pixel decision.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
`image_loader.cpp` demonstrates the pattern: `calculateScaling()` picks the best drawing callback once per frame based on segment dimensions and GIF size, then passes it to the decoder via `setDrawPixelCallback()`:
|
||||
|
||||
```cpp
|
||||
// calculateScaling() — called once per frame
|
||||
if ((perPixelX < 2) && (perPixelY < 2))
|
||||
decoder.setDrawPixelCallback(drawPixelCallbackDownScale2D); // downscale-only variant
|
||||
else
|
||||
decoder.setDrawPixelCallback(drawPixelCallback2D); // full-scaling variant
|
||||
```
|
||||
|
||||
Each callback is a small, single-purpose function with no internal branching — the decoder's per-pixel loop never re-evaluates which strategy to use.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
### Template Specialization (Advanced)
|
||||
|
||||
Templates can eliminate runtime decisions by generating separate code paths at compile time. For example, a pixel setter could be templated on color order or channel count so the compiler removes dead branches and produces tight, specialized machine code:
|
||||
|
||||
```cpp
|
||||
template<bool hasWhite>
|
||||
void setChannel(uint8_t* out, uint32_t col) {
|
||||
out[0] = R(col); out[1] = G(col); out[2] = B(col);
|
||||
if constexpr (hasWhite) out[3] = W(col); // compiled out when hasWhite==false
|
||||
}
|
||||
```
|
||||
|
||||
Use sparingly — each instantiation duplicates code in flash. On ESP8266 and small-flash ESP32 boards this can exhaust IRAM/flash. Prefer templates only when the hot path is measurably faster and the number of instantiations is small (2–4).
|
||||
|
||||
### RAII Lock-Free Synchronization (Advanced)
|
||||
|
||||
Where contention is rare and the critical section is short, consider replacing mutex-based locking with lock-free techniques using `std::atomic` and RAII scoped guards. A scoped guard sets a flag on construction and clears it on destruction, guaranteeing cleanup even on early return:
|
||||
|
||||
```cpp
|
||||
struct ScopedBusyFlag {
|
||||
std::atomic<bool>& flag;
|
||||
bool acquired;
|
||||
ScopedBusyFlag(std::atomic<bool>& f) : flag(f), acquired(false) {
|
||||
bool expected = false;
|
||||
acquired = flag.compare_exchange_strong(expected, true);
|
||||
}
|
||||
~ScopedBusyFlag() { if (acquired) flag.store(false); }
|
||||
explicit operator bool() const { return acquired; }
|
||||
};
|
||||
|
||||
// Usage
|
||||
static std::atomic<bool> busySending{false};
|
||||
ScopedBusyFlag guard(busySending);
|
||||
if (!guard) return; // another task is already sending
|
||||
// ... do work — flag auto-clears when guard goes out of scope
|
||||
```
|
||||
|
||||
This avoids FreeRTOS semaphore overhead and the risk of forgetting to return a semaphore. There are no current examples of this pattern in the codebase — consult with maintainers before introducing it in new code, to ensure it aligns with the project's synchronization conventions.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Pre-Compute Outside Loops
|
||||
|
||||
Move invariant calculations before the loop. Pre-compute reciprocals to replace division with multiplication.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
```cpp
|
||||
const uint_fast16_t cols = virtualWidth();
|
||||
const uint_fast16_t rows = virtualHeight();
|
||||
uint_fast8_t fadeRate = (255U - rate) >> 1;
|
||||
float mappedRate_r = 1.0f / (float(fadeRate) + 1.1f); // reciprocal — avoid division inside loop
|
||||
```
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Parallel Channel Processing
|
||||
|
||||
Process R+B and W+G channels simultaneously using the two-channel mask pattern:
|
||||
|
||||
```cpp
|
||||
constexpr uint32_t TWO_CHANNEL_MASK = 0x00FF00FF;
|
||||
uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK;
|
||||
uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK;
|
||||
return rb | wg;
|
||||
```
|
||||
|
||||
### Bit Shifts Over Division (mainly for RISC-V boards)
|
||||
|
||||
ESP32 and ESP32-S3 (Xtensa core) have a fast "integer divide" instruction, so manual shifts rarely help.
|
||||
On RISC-V targets (ESP32-C3/C6/P4) and ESP8266, prefer explicit bit-shifts for power-of-two arithmetic — the compiler does **not** always convert divisions to shifts.
|
||||
Always use unsigned operands for right shifts; signed right-shift is implementation-defined.
|
||||
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
On RISC-V-based boards (ESP32-C3, ESP32-C6, ESP32-C5) explicit shifts can be beneficial.
|
||||
```cpp
|
||||
position >> 3 // instead of position / 8
|
||||
(255U - rate) >> 1 // instead of (255 - rate) / 2
|
||||
i & 0x0007 // instead of i % 8
|
||||
```
|
||||
|
||||
**Important**: The bit-shifted expression should be unsigned. On some MCUs, "signed right-shift" is implemented by an "arithmetic shift right" that duplicates the sign bit: ``0b1010 >> 1 = 0b1101``.
|
||||
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
### Static Caching for Expensive Computations
|
||||
|
||||
Cache results in static locals when the input rarely changes between calls:
|
||||
|
||||
```cpp
|
||||
static uint16_t lastKelvin = 0;
|
||||
static byte correctionRGB[4] = {255,255,255,0};
|
||||
if (lastKelvin != kelvin) {
|
||||
colorKtoRGB(kelvin, correctionRGB); // expensive — only recalculate when input changes
|
||||
lastKelvin = kelvin;
|
||||
}
|
||||
```
|
||||
|
||||
### Inlining Strategy
|
||||
|
||||
- Move frequently-called small functions to headers for inlining (e.g. `Segment::setPixelColorRaw` is in `FX.h`)
|
||||
- Use `static inline` for file-local helpers
|
||||
|
||||
### Math & Trigonometric Functions
|
||||
|
||||
- WLED uses a custom `fastled_slim` library. The old FastLED trig aliases (`sin8`, `cos8`, `sin16`, `cos16`) **no longer exist and cause a compile error** — use `sin8_t()`, `cos8_t()`, `sin16_t()`, `cos16_t()` instead. For float approximations use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`. Replace FastLED noise aliases (`inoise8`, `inoise16`) with `perlin8`, `perlin16`.
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
|
||||
| ❌ Do not use (compile error) | ✅ Use instead | Source |
|
||||
|---|---|---|
|
||||
| `sin8()`, `cos8()` | `sin8_t()`, `cos8_t()` | `fastled_slim.h` → `wled_math.cpp` |
|
||||
| `sin16()`, `cos16()` | `sin16_t()`, `cos16_t()` | `fastled_slim.h` → `wled_math.cpp` |
|
||||
| `sinf()`, `cosf()` | `sin_approx()`, `cos_approx()` | `wled_math.cpp` |
|
||||
| `atan2f()`, `atan2()` | `atan2_t()` | `wled_math.cpp` |
|
||||
| `sqrt()` on integers | `sqrt32_bw()` | `fcn_declare.h` → `wled_math.cpp` |
|
||||
| `sqrtf()` on floats | `sqrtf()` (acceptable) | — no WLED replacement |
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
---
|
||||
|
||||
## `delay()` vs `yield()` in ESP32 Tasks
|
||||
<!-- HUMAN_ONLY_START -->
|
||||
* On ESP32, `delay(ms)` calls `vTaskDelay(ms / portTICK_PERIOD_MS)`, which **suspends only the calling task**. The FreeRTOS scheduler immediately runs all other ready tasks.
|
||||
* The Arduino `loop()` function runs inside `loopTask`. Calling `delay()` there does *not* block the network stack, audio FFT, LED DMA, nor any other FreeRTOS task.
|
||||
* This differs from ESP8266, where `delay()` stalls the entire system unless `yield()` was called inside.
|
||||
<!-- HUMAN_ONLY_END -->
|
||||
|
||||
- On ESP32, `delay()` is generally allowed, as it helps to efficiently manage CPU usage of all tasks.
|
||||
- On ESP8266, only use `delay()` and `yield()` in the main `loop()` context. If not sure, protect with `if (can_yield()) ...`.
|
||||
- Do *not* use `delay()` in effects (FX.cpp) or in the hot pixel path.
|
||||
- `delay()` on the bus-level is allowed, it might be needed to achieve exact timing in LED drivers.
|
||||
|
||||
### IDLE Watchdog and Custom Tasks on ESP32
|
||||
|
||||
- In arduino-esp32, `yield()` calls `vTaskDelay(0)`, which only switches to tasks at equal or higher priority — the IDLE task (priority 0) is never reached.
|
||||
- **Do not use `yield()` to pace ESP32 tasks or assume it feeds any watchdog**.
|
||||
- **Custom `xTaskCreate()` tasks must call `delay(1)` in their loop, not `yield()`.** Without a real blocking call, the IDLE task is starved. The IDLE watchdog panic is the first visible symptom — but the damage starts earlier: deleted task memory leaks, software timers stop firing, light sleep is disabled, and Wi-Fi/BT idle hooks don't run. Structure custom tasks like this:
|
||||
```cpp
|
||||
// WRONG — IDLE task is never scheduled; yield() does not feed the idle task watchdog.
|
||||
void myTask(void*) {
|
||||
for (;;) {
|
||||
doWork();
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
// CORRECT — delay(1) suspends the task for ≥1 ms, IDLE task runs, IDLE watchdog is fed
|
||||
void myTask(void*) {
|
||||
for (;;) {
|
||||
doWork();
|
||||
delay(1); // DO NOT REMOVE — lets IDLE(0) run and feeds its watchdog
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Prefer blocking FreeRTOS primitives (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) over `delay(1)` polling where precise timing or event-driven behaviour is needed.
|
||||
- **Watchdog note.** WLED disables the Task Watchdog by default (`WLED_WATCHDOG_TIMEOUT 0` in `wled.h`). When enabled, `esp_task_wdt_reset()` is called at the end of each `loop()` iteration. Long blocking operations inside `loop()` — such as OTA downloads or slow file I/O — must call `esp_task_wdt_reset()` periodically, or be restructured so the main loop is not blocked for longer than the configured timeout.
|
||||
|
||||
## Caveats and Pitfalls
|
||||
|
||||
- **LittleFS filenames**: File paths passed to `file.open()` must not exceed 255 bytes (`LFS_NAME_MAX`). Validate constructed paths (e.g., `/ledmap_` + segment name + `.json`) stay within this limit (assume standard configurations, like WLED_MAX_SEGNAME_LEN = 64).
|
||||
|
||||
- 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:
|
||||
uint8_t angle = 40.74f * atan2f(dy, dx); // negative float → uint8_t is UB
|
||||
|
||||
// Correct — cast through int first:
|
||||
// atan2f returns [-π..+π], scaled ≈ [-128..+128] as int; uint8_t wraps negative ints via 2's complement (e.g. -1 → 255)
|
||||
uint8_t angle = int(40.74f * atan2f(dy, dx)); // float→int (defined), int→uint8_t (defined)
|
||||
```
|
||||
@@ -0,0 +1,859 @@
|
||||
---
|
||||
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 39–61). 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 2–8 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 33–37 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 33–37 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.4–4.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.4–4.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.1–5.3 | **13.2** | C++20 (gnu++2b) | Significant warning changes |
|
||||
| 5.4–5.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) | 200–320 KB | Hot-path buffers, task stacks, small allocations |
|
||||
| IRAM | `MALLOC_CAP_EXEC` | Fastest | No | 32–128 KB | Code (automatic via `IRAM_ATTR`) |
|
||||
| PSRAM (SPIRAM) | `MALLOC_CAP_SPIRAM \| MALLOC_CAP_8BIT` | Slower | Chip-dependent | 2–16 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, 3–10× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel 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 13–14 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 1500–3000 µ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 -->
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
applyTo: "wled00/data/**"
|
||||
---
|
||||
# Web UI Coding Conventions
|
||||
|
||||
## Formatting
|
||||
|
||||
- Indent **HTML and JavaScript** with **tabs**
|
||||
- Indent **CSS** with **tabs**
|
||||
|
||||
## JavaScript Style
|
||||
|
||||
- **camelCase** for functions and variables: `gId()`, `selectedFx`, `currentPreset`
|
||||
- Abbreviated helpers are common: `d` for `document`, `gId()` for `getElementById()`
|
||||
|
||||
## Key Files
|
||||
|
||||
- `index.htm` — main interface
|
||||
- `index.js` — functions that manage / update the main interface
|
||||
- `settings*.htm` — configuration pages
|
||||
- `*.css` — stylesheets (inlined during build)
|
||||
- `common.js` — helper functions
|
||||
|
||||
**Reuse shared helpers from `common.js` whenever possible** instead of duplicating utilities in page-local scripts.
|
||||
|
||||
## 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
|
||||
(`wled00/html_*.h`, `wled00/js_*.h`).
|
||||
Run `npm run build` after any change. **Never edit generated headers directly.**
|
||||
@@ -29,6 +29,7 @@ License along with NeoPixel. If not, see
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if !defined(WLED_USE_SHARED_RMT) // V5 fix: don't compile this file on unsupported platforms
|
||||
|
||||
// Use the NeoEspRmtSpeed types from the driver-based implementation
|
||||
#include <NeoPixelBus.h>
|
||||
@@ -467,3 +468,4 @@ typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod;
|
||||
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI)
|
||||
#if defined(__XTENSA__) && defined(ESP32)
|
||||
|
||||
#include <freertos/xtensa_context.h>
|
||||
#include "esp_idf_version.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc.h"
|
||||
|
||||
/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */
|
||||
#ifndef CONFIG_BTDM_CTRL_HLI
|
||||
#if !defined(CONFIG_BTDM_CTRL_HLI) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
|
||||
#include <freertos/xtensa_context.h>
|
||||
#include "soc/soc.h"
|
||||
|
||||
/*
|
||||
Select interrupt based on system check level
|
||||
@@ -258,6 +260,5 @@ _highint_stack_switch:
|
||||
.global ld_include_hli_vectors_rmt
|
||||
ld_include_hli_vectors_rmt:
|
||||
|
||||
|
||||
#endif // CONFIG_BTDM_CTRL_HLI
|
||||
#endif // XTensa
|
||||
@@ -29,7 +29,7 @@ License along with NeoPixel. If not, see
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
|
||||
#include <algorithm>
|
||||
#include "esp_idf_version.h"
|
||||
@@ -504,4 +504,4 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy
|
||||
return rv;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "17.0.0-dev",
|
||||
"version": "17.0.0-devV5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wled",
|
||||
"version": "17.0.0-dev",
|
||||
"version": "17.0.0-devV5",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "17.0.0-dev",
|
||||
"version": "17.0.0-devV5",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
||||
+79
-12
@@ -2,18 +2,85 @@
|
||||
# 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
|
||||
|
||||
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)
|
||||
# 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)
|
||||
else:
|
||||
# For other platforms, put it in last
|
||||
env.Append(LINKFLAGS=[linker_script])
|
||||
# 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)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# FastLED exports some weak cxx symbols as a way of managing integration with C-only
|
||||
# projects that cause it to be preferentially linked instead of unused code being
|
||||
# discarded like other libraries. This causes not only bloat of the final binaries
|
||||
# but can incorrectly invoke some of their driver framework on some platforms.
|
||||
#
|
||||
# Solve this problem by moving the cxx library up in the linker command line, so
|
||||
# that it will be chosen over FastLED's.
|
||||
Import("env")
|
||||
|
||||
if "-lcxx" in env["LIBS"]:
|
||||
env["LIBS"].remove("-lcxx")
|
||||
env["LIBS"].insert(0, "-lcxx")
|
||||
+105
-54
@@ -1,11 +1,12 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path # For OS-agnostic path manipulation
|
||||
from pathlib import Path
|
||||
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 """
|
||||
@@ -13,105 +14,156 @@ def read_lines(p: Path):
|
||||
return f.readlines()
|
||||
|
||||
|
||||
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 _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 check_elf_modules(elf_path: Path, env, module_lib_builders) -> set[str]:
|
||||
""" Check which modules have at least one defined symbol placed in the ELF.
|
||||
""" Check which modules have at least one compilation unit 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 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"]).
|
||||
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.
|
||||
|
||||
Returns the set of build_dir basenames for confirmed modules.
|
||||
"""
|
||||
nm_path = _get_nm_path(env)
|
||||
readelf_path = _get_readelf_path(env)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[nm_path, "--defined-only", "-l", str(elf_path)],
|
||||
[readelf_path, "--debug-dump=info", "--dwarf-depth=1", str(elf_path)],
|
||||
capture_output=True, text=True, errors="ignore", timeout=120,
|
||||
)
|
||||
nm_output = result.stdout
|
||||
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)
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
|
||||
secho(f"WARNING: nm failed ({e}); skipping per-module validation", fg="yellow", err=True)
|
||||
secho(f"WARNING: readelf 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"))
|
||||
|
||||
for line in nm_output.splitlines():
|
||||
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
|
||||
if not remaining:
|
||||
break # all builders matched
|
||||
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
|
||||
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
|
||||
|
||||
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 """
|
||||
# Count the number of entries in the usermods table section
|
||||
return len([x for x in map_file if USERMODS_SECTION in x])
|
||||
""" 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
|
||||
|
||||
|
||||
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['WLED_MODULES']
|
||||
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)
|
||||
|
||||
# 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 linked as WLED optional/user modules")
|
||||
secho(f"INFO: {len(modules)} libraries included 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")
|
||||
secho(f"INFO: {usermod_object_count} usermod object entries found")
|
||||
|
||||
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:
|
||||
@@ -120,7 +172,6 @@ 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'))
|
||||
|
||||
+618
-180
File diff suppressed because it is too large
Load Diff
+110
-220
@@ -5,7 +5,7 @@
|
||||
# Please visit documentation: https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
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
|
||||
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
|
||||
|
||||
#----------
|
||||
# SAMPLE
|
||||
@@ -13,8 +13,8 @@ default_envs = WLED_generic8266_1M, esp32dev_V4_dio80 # put the name(s) of your
|
||||
[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 = ${common.platform_wled_default} # uncomment and change when you want particular platform
|
||||
; platform_packages = ${common.platform_packages}
|
||||
; platform = ${esp8266.platform_wled_default} # uncomment and change when you want particular platform
|
||||
; platform_packages = ${esp8266.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,10 +30,18 @@ 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}
|
||||
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
|
||||
;
|
||||
; *** 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\"
|
||||
;
|
||||
@@ -98,17 +106,17 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; -D WLED_DEBUG_PORT=7868
|
||||
;
|
||||
; Use Autosave usermod and set it to do save after 90s
|
||||
; -D USERMOD_AUTO_SAVE
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} auto_save
|
||||
; -D AUTOSAVE_AFTER_SEC=90
|
||||
;
|
||||
; Use AHT10/AHT15/AHT20 usermod
|
||||
; -D USERMOD_AHT10
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} AHT10_v2
|
||||
;
|
||||
; Use INA226 usermod
|
||||
; -D USERMOD_INA226
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} INA226_v2
|
||||
;
|
||||
; Use 4 Line Display usermod with SPI display
|
||||
; -D USERMOD_FOUR_LINE_DISPLAY
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} four_line_display_ALT
|
||||
; -DFLD_SPI_DEFAULT
|
||||
; -D FLD_TYPE=SSD1306_SPI64
|
||||
; -D FLD_PIN_CLOCKSPI=14
|
||||
@@ -118,22 +126,22 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; -D FLD_PIN_RESET=27
|
||||
;
|
||||
; Use Rotary encoder usermod (in conjunction with 4LD)
|
||||
; -D USERMOD_ROTARY_ENCODER_UI
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} rotary_encoder_ui_ALT
|
||||
; -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
|
||||
; -D USERMOD_DALLASTEMPERATURE
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} Temperature
|
||||
; -D TEMPERATURE_PIN=13
|
||||
;
|
||||
; Use Multi Relay usermod and configure it to use 6 relays and appropriate GPIO
|
||||
; -D USERMOD_MULTI_RELAY
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} 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
|
||||
; -D USERMOD_PIRSWITCH
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} PIR_sensor_switch
|
||||
; -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)
|
||||
@@ -146,12 +154,12 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; -D I2S_CKPIN=19
|
||||
;
|
||||
; Use PWM fan usermod
|
||||
; -D USERMOD_PWM_FAN
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} PWM_fan
|
||||
; -D TACHO_PIN=33
|
||||
; -D PWM_PIN=32
|
||||
;
|
||||
; Use POV Display usermod
|
||||
; -D USERMOD_POV_DISPLAY
|
||||
; custom_usermods = ${env:esp32dev.custom_usermods} pov_display
|
||||
; Use built-in or custom LED as a status indicator (assumes LED is connected to GPIO16)
|
||||
; -D STATUSLED=16
|
||||
;
|
||||
@@ -184,6 +192,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
; 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
|
||||
@@ -193,7 +202,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Optional: build flags for speed, instead of optimising for size.
|
||||
# Example of usage: see [env:esp32S3_PSRAM_HUB75]
|
||||
# Add ${Speed_Flags.build_flags} / ${Speed_Flags.build_unflags} to your own env
|
||||
# in platformio_override.ini to opt in.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
[Speed_Flags]
|
||||
@@ -214,63 +224,60 @@ build_flags =
|
||||
|
||||
[env:esp07]
|
||||
board = esp07
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_ESP07\" #-DWLED_DISABLE_2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:d1_mini]
|
||||
board = d1_mini
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.platform_packages}
|
||||
upload_speed = 921600
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_D1MINI\" #-DWLED_DISABLE_2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
monitor_filters = esp8266_exception_decoder
|
||||
|
||||
[env:heltec_wifi_kit_8]
|
||||
board = d1_mini
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_HT_D1MINI\" #-DWLED_DISABLE_2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:h803wf]
|
||||
board = d1_mini
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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
|
||||
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
|
||||
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.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
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}
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = qio
|
||||
|
||||
[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: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:esp32s2_saola]
|
||||
extends = esp32s2
|
||||
@@ -280,53 +287,51 @@ platform_packages = ${esp32s2.platform_packages}
|
||||
framework = arduino
|
||||
board_build.flash_mode = qio
|
||||
upload_speed = 460800
|
||||
build_flags = ${common.build_flags} ${esp32s2.build_flags}
|
||||
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2_saola\"
|
||||
;-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 = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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
|
||||
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
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp8285_H801]
|
||||
board = esp8285
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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
|
||||
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
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:d1_mini_5CH_Shojo_PCB]
|
||||
board = d1_mini
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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 = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} ${common.debug_flags}
|
||||
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
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:d1_mini_ota]
|
||||
@@ -334,20 +339,23 @@ board = d1_mini
|
||||
upload_protocol = espota
|
||||
# exchange for your WLED IP
|
||||
upload_port = "10.10.1.27"
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_D1MINI_OTA\" #-DWLED_DISABLE_2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:anavi_miracle_controller]
|
||||
board = d1_mini
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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]
|
||||
@@ -358,6 +366,7 @@ 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
|
||||
@@ -374,6 +383,7 @@ 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
|
||||
@@ -381,12 +391,8 @@ 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]
|
||||
@@ -409,75 +415,84 @@ 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 = ${common.platform_wled_default}
|
||||
platform = ${esp8266.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 = ${common.platform_wled_default}
|
||||
platform = ${esp8266.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 = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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 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
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:Athom_15w_RGBCW] ;15w bulb
|
||||
board = esp8285
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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 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
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:Athom_3Pin_Controller] ;small controller with only data
|
||||
board = esp8285
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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 = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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 = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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 = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.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 USERMOD_MY9291
|
||||
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
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -487,60 +502,33 @@ lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:codm-controller-0_6]
|
||||
board = esp_wroom_02
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_2m512k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_CODM06_2MB\" -D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:codm-controller-0_6-rev2]
|
||||
board = esp_wroom_02
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
platform = ${esp8266.platform_wled_default}
|
||||
platform_packages = ${esp8266.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_CODM06R2_4MB\" -D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
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 for esp32dev
|
||||
[env:esp32dev_usermod_RF433]
|
||||
extends = env:esp32dev
|
||||
custom_usermods =
|
||||
${env:esp32dev.custom_usermods}
|
||||
RF433
|
||||
# 433MHz RF remote example: see usermods/usermod_v2_RF433/platformio_override.ini.sample
|
||||
|
||||
# External usermod from a git repository.
|
||||
# The library's `library.json` must include `"build": {"libArchive": false}`.
|
||||
@@ -554,102 +542,4 @@ 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
|
||||
@@ -7,81 +7,97 @@
|
||||
<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 implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!
|
||||
A fast and feature-rich firmware for ESP32 microcontrollers to control addressable LEDs — from simple strips to large 2D matrices and HUB75 panels.
|
||||
|
||||
Originally created by [Aircoookie](https://github.com/Aircoookie)
|
||||
Originally created by [Aircoookie](https://github.com/Aircoookie), now maintained by a community of contributors.
|
||||
|
||||
## ⚙️ 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
|
||||
|
||||
## 💡 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)
|
||||
### 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**
|
||||
|
||||
## 📲 Quick start guide and documentation
|
||||
|
||||
See the [documentation on our official site](https://kno.wled.ge)!
|
||||
See the [documentation at kno.wled.ge](https://kno.wled.ge)!
|
||||
|
||||
[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!
|
||||
[Tutorials and getting-started guides](https://kno.wled.ge/basics/tutorials/) to help you get your project running quickly.
|
||||
|
||||
## 🖼️ 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 [here](https://kno.wled.ge/basics/compatible-hardware)!
|
||||
See the [compatible hardware list](https://kno.wled.ge/basics/compatible-hardware) on the wiki.
|
||||
|
||||
## ✌️ Other
|
||||
|
||||
Licensed under the EUPL v1.2 license
|
||||
Credits [here](https://kno.wled.ge/about/contributors/)!
|
||||
CORS proxy by [Corsfix](https://corsfix.com/)
|
||||
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/).
|
||||
|
||||
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)!
|
||||
|
||||
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 you'd like to reach the original creator privately: [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com).
|
||||
|
||||
If WLED really brightens up your day, you can [](https://paypal.me/aircoookie)
|
||||
If WLED brightens up your day, you can [send a gift to Aircoookie via PayPal](https://paypal.me/aircoookie).
|
||||
|
||||
---
|
||||
|
||||
*Disclaimer:*
|
||||
*Disclaimer:*
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
As per the EUPL license, no liability is assumed for any damage to you or any other person or equipment.
|
||||
|
||||
+11
-13
@@ -1,20 +1,20 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# This file is autogenerated by pip-compile with Python 3.13
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile requirements.in
|
||||
# pip-compile
|
||||
#
|
||||
ajsonrpc==1.2.0
|
||||
# via platformio
|
||||
anyio==4.8.0
|
||||
anyio==4.10.0
|
||||
# via starlette
|
||||
bottle==0.13.2
|
||||
bottle==0.13.4
|
||||
# via platformio
|
||||
certifi==2025.1.31
|
||||
certifi==2025.8.3
|
||||
# via requests
|
||||
charset-normalizer==3.4.1
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
click==8.1.8
|
||||
click==8.1.7
|
||||
# via
|
||||
# platformio
|
||||
# uvicorn
|
||||
@@ -30,9 +30,9 @@ idna==3.10
|
||||
# requests
|
||||
marshmallow==3.26.1
|
||||
# via platformio
|
||||
packaging==24.2
|
||||
packaging==25.0
|
||||
# via marshmallow
|
||||
platformio==6.1.17
|
||||
platformio==6.1.18
|
||||
# via -r requirements.in
|
||||
pyelftools==0.32
|
||||
# via platformio
|
||||
@@ -44,15 +44,13 @@ semantic-version==2.10.0
|
||||
# via platformio
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
starlette==0.45.3
|
||||
starlette==0.46.2
|
||||
# via platformio
|
||||
tabulate==0.9.0
|
||||
# via platformio
|
||||
typing-extensions==4.12.2
|
||||
# via anyio
|
||||
urllib3==2.5.0
|
||||
# via requests
|
||||
uvicorn==0.34.0
|
||||
uvicorn==0.34.3
|
||||
# via platformio
|
||||
wsproto==1.2.0
|
||||
# via platformio
|
||||
|
||||
+12
-6
@@ -119,7 +119,7 @@ describe('Script', () => {
|
||||
|
||||
async function checkIfFileWasNewlyCreated(file) {
|
||||
const modifiedTime = fs.statSync(file).mtimeMs;
|
||||
assert(Date.now() - modifiedTime < 500, file + ' was not modified');
|
||||
assert(Date.now() - modifiedTime < 850, 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, 1000));
|
||||
await new Promise(resolve => setTimeout(resolve, 1400));
|
||||
|
||||
// run script cdata.js again and wait for it to finish
|
||||
await execPromise('node tools/cdata.js');
|
||||
@@ -175,13 +175,19 @@ describe('Script', () => {
|
||||
});
|
||||
|
||||
it('a settings file changes', async () => {
|
||||
await testFileModification(path.join(dataPath, 'settings_leds.htm'), 'html_ui.h');
|
||||
await testFileModification(path.join(dataPath, 'settings_leds.htm'), 'html_settings.h');
|
||||
});
|
||||
|
||||
it('the favicon changes', async () => {
|
||||
await testFileModification(path.join(dataPath, 'favicon.ico'), 'html_ui.h');
|
||||
it('common.js changes', async () => {
|
||||
await testFileModification(path.join(dataPath, 'common.js'), 'html_settings.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');
|
||||
});
|
||||
@@ -209,4 +215,4 @@ describe('Script', () => {
|
||||
assert(secondRunTime < firstRunTime / 2, 'html_*.h files were rebuilt');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/* 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;
|
||||
+10
-2
@@ -141,8 +141,16 @@ discover_devices() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 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}')
|
||||
# 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
|
||||
}
|
||||
'
|
||||
)
|
||||
|
||||
local devices_array=()
|
||||
for device in "${raw_devices[@]}"; do
|
||||
|
||||
@@ -28,18 +28,19 @@ class ADS1115Usermod : public Usermod {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (isEnabled && millis() - lastTime > loopInterval) {
|
||||
lastTime = millis();
|
||||
if (!isEnabled || strip.isUpdating() || millis() - lastTime <= loopInterval)
|
||||
return;
|
||||
|
||||
// If we don't have new data, skip this iteration.
|
||||
if (!ads.conversionComplete()) {
|
||||
return;
|
||||
}
|
||||
lastTime = millis();
|
||||
|
||||
updateResult();
|
||||
moveToNextChannel();
|
||||
startReading();
|
||||
// If we don't have new data, skip this iteration.
|
||||
if (!ads.conversionComplete()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateResult();
|
||||
moveToNextChannel();
|
||||
startReading();
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
@@ -69,6 +70,8 @@ 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);
|
||||
@@ -79,8 +82,6 @@ class ADS1115Usermod : public Usermod {
|
||||
channel[F("Offset")] = settingsPtr->offset;
|
||||
channel[F("Decimals")] = settingsPtr->decimals;
|
||||
}
|
||||
|
||||
top[F("Loop Interval")] = loopInterval;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "ADS1115_v2",
|
||||
"build": { "libArchive": false },
|
||||
"dependencies": {
|
||||
"Adafruit BusIO": "https://github.com/adafruit/Adafruit_BusIO#1.13.2",
|
||||
"Adafruit ADS1X15": "https://github.com/adafruit/Adafruit_ADS1X15#2.4.0"
|
||||
"Adafruit BusIO": "https://github.com/adafruit/Adafruit_BusIO#1.17.4",
|
||||
"Adafruit ADS1X15": "https://github.com/adafruit/Adafruit_ADS1X15#2.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
# ADS1115 16-Bit ADC with four inputs
|
||||
# ADS1115 Usermod
|
||||
|
||||
This usermod will read from an ADS1115 ADC. The voltages are displayed in the Info section of the web UI.
|
||||
Reads values from an ADS1115 16-bit ADC and exposes them in the `Info` tab.
|
||||
|
||||
Configuration is performed via the Usermod menu. There are no parameters to set in code!
|
||||
## 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.
|
||||
|
||||
## Installation
|
||||
## Compatibility
|
||||
- Requires an ADS1115 module connected via I2C.
|
||||
- Works on targets with I2C support.
|
||||
- Default ADC gain is `1x` (input range `+/-4.096V`).
|
||||
|
||||
Add 'ADS1115' to `custom_usermods` in your platformio environment.
|
||||
## Installation
|
||||
- Add `ADS1115` to `custom_usermods` in your `platformio.ini` (or `platformio_override.ini`).
|
||||
|
||||
## Author
|
||||
- Dima Zhemkov [@dima-zhemkov](https://github.com/dima-zhemkov)
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
[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,6 +25,8 @@ 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;
|
||||
@@ -91,6 +93,8 @@ 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
|
||||
@@ -156,6 +160,12 @@ 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;
|
||||
|
||||
@@ -164,15 +174,15 @@ class Animated_Staircase : public Usermod {
|
||||
|
||||
bottomSensorRead = bottomSensorWrite ||
|
||||
(!useUSSensorBottom ?
|
||||
(bottomPIRorTriggerPin<0 ? false : digitalRead(bottomPIRorTriggerPin)) :
|
||||
(bottomPIRorTriggerPin<0 ? false : readPIRPin(bottomPIRorTriggerPin, bottomAPinInvert)) :
|
||||
ultrasoundRead(bottomPIRorTriggerPin, bottomEchoPin, bottomMaxDist*59) // cm to us
|
||||
);
|
||||
topSensorRead = topSensorWrite ||
|
||||
(!useUSSensorTop ?
|
||||
(topPIRorTriggerPin<0 ? false : digitalRead(topPIRorTriggerPin)) :
|
||||
(topPIRorTriggerPin<0 ? false : readPIRPin(topPIRorTriggerPin, topAPinInvert)) :
|
||||
ultrasoundRead(topPIRorTriggerPin, topEchoPin, topMaxDist*59) // cm to us
|
||||
);
|
||||
|
||||
|
||||
if (bottomSensorRead != bottomSensorState) {
|
||||
bottomSensorState = bottomSensorRead; // change previous state
|
||||
sensorChanged = true;
|
||||
@@ -439,18 +449,20 @@ 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(_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;
|
||||
DEBUG_PRINTLN(F("Staircase config saved."));
|
||||
}
|
||||
|
||||
@@ -462,11 +474,13 @@ 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));
|
||||
@@ -485,10 +499,12 @@ 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)
|
||||
@@ -554,14 +570,15 @@ 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);
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "wled.h"
|
||||
#ifdef WLED_DISABLE_MQTT
|
||||
#if defined(USERMOD_DHT_MQTT) && defined(WLED_DISABLE_MQTT)
|
||||
#error "This user mod requires MQTT to be enabled."
|
||||
#endif
|
||||
|
||||
@@ -246,4 +246,4 @@ class UsermodDHT : public Usermod {
|
||||
|
||||
|
||||
static UsermodDHT dht;
|
||||
REGISTER_USERMOD(dht);
|
||||
REGISTER_USERMOD(dht);
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
; USERMOD_DHT_MQTT - publish measurements to the MQTT broker
|
||||
; USERMOD_DHT_STATS - For debug, report delay stats
|
||||
|
||||
[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: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: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
|
||||
[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
|
||||
|
||||
@@ -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()) return;
|
||||
if (!enabled || (strip.isUpdating() && (millis() - lastTime < 200))) return; // adjust "200" (in millisecond) to your needs - prevents starvation with very long strips
|
||||
|
||||
// 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 = usermod["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value (userVar0 is defined in wled.h)
|
||||
}
|
||||
// you can as well check WLED state JSON keys
|
||||
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name:": "EleksTube_IPS",
|
||||
"name": "EleksTube_IPS",
|
||||
"build": { "libArchive": false },
|
||||
"dependencies": {
|
||||
"TFT_eSPI" : "2.5.33"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "Fix_unreachable_netservices_v2",
|
||||
"build": { "libArchive": false },
|
||||
"platforms": ["espressif8266"]
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
}
|
||||
if (m_updateConfig)
|
||||
{
|
||||
serializeConfig();
|
||||
serializeConfigToFS();
|
||||
m_updateConfig = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,38 @@
|
||||
#include "wled.h"
|
||||
#include <INA226_WE.h>
|
||||
|
||||
#ifndef INA226_ADDRESS
|
||||
#define INA226_ADDRESS 0x40 // Default I2C address for INA226
|
||||
#endif
|
||||
|
||||
#define DEFAULT_CHECKINTERVAL 60000
|
||||
#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_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
|
||||
@@ -81,10 +105,11 @@ private:
|
||||
uint16_t _settingInaSamples : 11; // Number of samples for averaging, max 1024
|
||||
|
||||
uint8_t _i2cAddress;
|
||||
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
|
||||
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
|
||||
|
||||
uint8_t _lastStatus = 0;
|
||||
float _lastCurrent = 0;
|
||||
@@ -93,7 +118,7 @@ private:
|
||||
float _lastShuntVoltage = 0;
|
||||
bool _lastOverflow = false;
|
||||
|
||||
#ifndef WLED_MQTT_DISABLE
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
float _lastCurrentSent = 0;
|
||||
float _lastVoltageSent = 0;
|
||||
float _lastPowerSent = 0;
|
||||
@@ -118,9 +143,11 @@ private:
|
||||
_ina226 = new INA226_WE(_i2cAddress);
|
||||
if (!_ina226->init())
|
||||
{
|
||||
DEBUG_PRINTLN(F("INA226 initialization failed!"));
|
||||
DEBUG_PRINTLN(F("INA226: init 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;
|
||||
@@ -129,7 +156,7 @@ private:
|
||||
tmpShort = _settingInaConversionTimeUs << 2;
|
||||
_ina226->setConversionTime(getConversionTimeEnum(tmpShort));
|
||||
|
||||
if (_checkInterval >= 20000)
|
||||
if (_checkIntervalMs >= 20000)
|
||||
{
|
||||
_isTriggeredOperationMode = true;
|
||||
_ina226->setMeasureMode(TRIGGERED);
|
||||
@@ -140,7 +167,11 @@ private:
|
||||
_ina226->setMeasureMode(CONTINUOUS);
|
||||
}
|
||||
|
||||
_ina226->setResistorRange(static_cast<float>(_shuntResistor) / 1000.0, static_cast<float>(_currentRange) / 1000.0);
|
||||
_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);
|
||||
}
|
||||
|
||||
void fetchAndPushValues()
|
||||
@@ -150,17 +181,19 @@ private:
|
||||
if (_lastStatus != 0)
|
||||
return;
|
||||
|
||||
float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0);
|
||||
float current = truncateDecimals((_ina226->getCurrent_mA() - _currentOffsetMa) / 1000.0f);
|
||||
float voltage = truncateDecimals(_ina226->getBusVoltage_V());
|
||||
float power = truncateDecimals(_ina226->getBusPower() / 1000.0);
|
||||
float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V());
|
||||
float power = truncateDecimals(_ina226->getBusPower() / 1000.0f);
|
||||
float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_mV());
|
||||
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);
|
||||
mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f);
|
||||
// Publish in V for backward compatibility
|
||||
float shuntVoltageV = shuntVoltage / 1000.0f;
|
||||
mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltageV, 0.01f);
|
||||
mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow);
|
||||
#endif
|
||||
|
||||
@@ -169,6 +202,9 @@ 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)
|
||||
@@ -188,7 +224,7 @@ private:
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentTime - _lastLoopCheck >= _checkInterval)
|
||||
if (currentTime - _lastLoopCheck >= _checkIntervalMs)
|
||||
{
|
||||
// Start a measurement and use isBusy() later to determine when it is done
|
||||
_ina226->startSingleMeasurementNoWait();
|
||||
@@ -201,7 +237,7 @@ private:
|
||||
|
||||
void handleContinuousMode(unsigned long currentTime)
|
||||
{
|
||||
if (currentTime - _lastLoopCheck >= _checkInterval)
|
||||
if (currentTime - _lastLoopCheck >= _checkIntervalMs)
|
||||
{
|
||||
_lastLoopCheck = currentTime;
|
||||
fetchAndPushValues();
|
||||
@@ -215,19 +251,23 @@ private:
|
||||
return;
|
||||
|
||||
char topic[128];
|
||||
snprintf_P(topic, 127, "%s/current", mqttDeviceTopic);
|
||||
auto buildTopic = [&](const char *suffix) {
|
||||
snprintf_P(topic, sizeof(topic), PSTR("%s/%s"), mqttDeviceTopic, suffix);
|
||||
};
|
||||
|
||||
buildTopic("current");
|
||||
mqttCreateHassSensor(F("Current"), topic, F("current"), F("A"));
|
||||
|
||||
snprintf_P(topic, 127, "%s/voltage", mqttDeviceTopic);
|
||||
buildTopic("voltage");
|
||||
mqttCreateHassSensor(F("Voltage"), topic, F("voltage"), F("V"));
|
||||
|
||||
snprintf_P(topic, 127, "%s/power", mqttDeviceTopic);
|
||||
buildTopic("power");
|
||||
mqttCreateHassSensor(F("Power"), topic, F("power"), F("W"));
|
||||
|
||||
snprintf_P(topic, 127, "%s/shunt_voltage", mqttDeviceTopic);
|
||||
buildTopic("shunt_voltage");
|
||||
mqttCreateHassSensor(F("Shunt Voltage"), topic, F("voltage"), F("V"));
|
||||
|
||||
snprintf_P(topic, 127, "%s/overflow", mqttDeviceTopic);
|
||||
buildTopic("overflow");
|
||||
mqttCreateHassBinarySensor(F("Overflow"), topic);
|
||||
}
|
||||
|
||||
@@ -315,14 +355,23 @@ public:
|
||||
UsermodINA226()
|
||||
{
|
||||
// Default values
|
||||
_settingEnabled = INA226_ENABLED_DEFAULT;
|
||||
_settingInaSamples = DEFAULT_INASAMPLES;
|
||||
_settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME;
|
||||
_settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME >> 2; // stored shifted to fit 12-bit field
|
||||
|
||||
_i2cAddress = INA226_ADDRESS;
|
||||
_checkInterval = DEFAULT_CHECKINTERVAL;
|
||||
_checkIntervalMs = DEFAULT_CHECKINTERVAL;
|
||||
_decimalFactor = 100;
|
||||
_shuntResistor = 1000;
|
||||
_currentRange = 1000;
|
||||
_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;
|
||||
}
|
||||
|
||||
void setup()
|
||||
@@ -399,7 +448,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"));
|
||||
JsonArray jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage Drop"));
|
||||
JsonArray jsonOverflow = user.createNestedArray(F("Overflow"));
|
||||
|
||||
if (_lastLoopCheck == 0)
|
||||
@@ -432,7 +481,7 @@ public:
|
||||
jsonPower.add(F("W"));
|
||||
|
||||
jsonShuntVoltage.add(_lastShuntVoltage);
|
||||
jsonShuntVoltage.add(F("V"));
|
||||
jsonShuntVoltage.add(F("mV"));
|
||||
|
||||
jsonOverflow.add(_lastOverflow ? F("true") : F("false"));
|
||||
}
|
||||
@@ -442,12 +491,13 @@ public:
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[F("Enabled")] = _settingEnabled;
|
||||
top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress);
|
||||
top[F("CheckInterval")] = _checkInterval / 1000;
|
||||
top[F("CheckInterval")] = _checkIntervalMs / 1000;
|
||||
top[F("INASamples")] = _settingInaSamples;
|
||||
top[F("INAConversionTime")] = _settingInaConversionTimeUs << 2;
|
||||
top[F("Decimals")] = log10f(_decimalFactor);
|
||||
top[F("ShuntResistor")] = _shuntResistor;
|
||||
top[F("CurrentRange")] = _currentRange;
|
||||
top[F("ShuntResistor")] = static_cast<float>(_shuntResistorUOhm) / 1000.0f;
|
||||
top[F("CurrentRange")] = _currentRangeMa;
|
||||
top[F("CurrentOffset")] = _currentOffsetMa;
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
top[F("MqttPublish")] = _mqttPublish;
|
||||
top[F("MqttPublishAlways")] = _mqttPublishAlways;
|
||||
@@ -457,6 +507,17 @@ 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,'µs');"));
|
||||
oappend(F("addInfo('INA226:Decimals',1,'(0-5)');"));
|
||||
oappend(F("addInfo('INA226:ShuntResistor',1,'mΩ');"));
|
||||
oappend(F("addInfo('INA226:CurrentRange',1,'mA');"));
|
||||
oappend(F("addInfo('INA226:CurrentOffset',1,'mA');"));
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject &root) override
|
||||
{
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
@@ -472,12 +533,12 @@ public:
|
||||
configComplete = false;
|
||||
|
||||
configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress);
|
||||
if (getJsonValue(top[F("CheckInterval")], _checkInterval))
|
||||
if (getJsonValue(top[F("CheckInterval")], _checkIntervalMs))
|
||||
{
|
||||
if (1 <= _checkInterval && _checkInterval <= 600)
|
||||
_checkInterval *= 1000;
|
||||
if (1 <= _checkIntervalMs && _checkIntervalMs <= 600)
|
||||
_checkIntervalMs *= 1000;
|
||||
else
|
||||
_checkInterval = DEFAULT_CHECKINTERVAL;
|
||||
_checkIntervalMs = DEFAULT_CHECKINTERVAL;
|
||||
}
|
||||
else
|
||||
configComplete = false;
|
||||
@@ -511,8 +572,26 @@ public:
|
||||
else
|
||||
configComplete = false;
|
||||
|
||||
configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor);
|
||||
configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange);
|
||||
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
|
||||
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
if (getJsonValue(top[F("MqttPublish")], tmpBool))
|
||||
|
||||
@@ -18,6 +18,7 @@ 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.
|
||||
@@ -63,4 +64,33 @@ 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.
|
||||
@@ -1,6 +0,0 @@
|
||||
[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
|
||||
@@ -129,11 +129,14 @@ class PWMFanUsermod : public Usermod {
|
||||
if (pwmChannel == 255) { //no more free LEDC channels
|
||||
deinitPWMfan(); return;
|
||||
}
|
||||
// configure LED PWM functionalitites
|
||||
// configure LED PWM functionalitites - ESP-IDF 5.x API
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ledcAttachChannel(pwmPin, 25000, 8, pwmChannel); // New API: ledcAttach(pin, freq, resolution, channel); keep PinManager channel ownership consistent
|
||||
#else
|
||||
ledcSetup(pwmChannel, 25000, 8);
|
||||
// attach the channel to the GPIO to be controlled
|
||||
ledcAttachPin(pwmPin, pwmChannel);
|
||||
#endif
|
||||
#endif
|
||||
DEBUG_PRINTLN(F("Fan PWM sucessfully initialized."));
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,6 +1,5 @@
|
||||
; 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
|
||||
@@ -8,9 +7,10 @@
|
||||
; 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_d1_mini]
|
||||
extends = env:d1_mini
|
||||
[env:usermod_sn_photoresistor_esp8266_2m]
|
||||
extends = env:esp8266_2m
|
||||
custom_usermods = ${env:esp8266_2m.custom_usermods} SN_Photoresistor
|
||||
build_flags =
|
||||
${common.build_flags_esp8266}
|
||||
-D USERMOD_SN_PHOTORESISTOR
|
||||
lib_deps = ${env.lib_deps}
|
||||
${env:esp8266_2m.build_flags}
|
||||
-D USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL=60
|
||||
lib_deps = ${env:esp8266_2m.lib_deps}
|
||||
@@ -196,7 +196,7 @@ class St7789DisplayUsermod : public Usermod {
|
||||
|
||||
// Check if values which are shown on display changed from the last time.
|
||||
if ((((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) ||
|
||||
(knownIp != (apActive ? IPAddress(4, 3, 2, 1) : Network.localIP())) ||
|
||||
(knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WLEDNetwork.localIP())) ||
|
||||
(knownBrightness != bri) ||
|
||||
(knownEffectSpeed != strip.getMainSegment().speed) ||
|
||||
(knownEffectIntensity != strip.getMainSegment().intensity) ||
|
||||
@@ -225,7 +225,7 @@ class St7789DisplayUsermod : public Usermod {
|
||||
#else
|
||||
knownSsid = WiFi.SSID();
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WLEDNetwork.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name:": "ST7789_display",
|
||||
"name": "ST7789_display",
|
||||
"build": { "libArchive": false }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
[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
|
||||
@@ -1,5 +0,0 @@
|
||||
; Options
|
||||
; -------
|
||||
; USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds
|
||||
;
|
||||
|
||||
@@ -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
|
||||
#endif // UM_AUDIOREACTIVE_USE_ARDUINO_FFT
|
||||
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
using FFTsampleType = float;
|
||||
@@ -758,6 +758,8 @@ 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
|
||||
@@ -874,11 +876,14 @@ class AudioReactive : public Usermod {
|
||||
static const char _dynamics[];
|
||||
static const char _frequency[];
|
||||
static const char _inputLvl[];
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32) // analog mic is only supported on classic esp32
|
||||
static const char _analogmic[];
|
||||
#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[];
|
||||
|
||||
@@ -1295,7 +1300,12 @@ class AudioReactive : public Usermod {
|
||||
|
||||
size_t packetSize = fftUdp.parsePacket();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET))) fftUdp.flush(); // discard invalid packets (too small or too big) - only works on 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
|
||||
#endif
|
||||
if ((packetSize > 5) && (packetSize <= UDPSOUND_MAX_PACKET)) {
|
||||
//DEBUGSR_PRINTLN("Received UDP Sync Packet");
|
||||
@@ -1366,7 +1376,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
// Reset I2S peripheral for good measure
|
||||
i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
delay(100);
|
||||
periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3
|
||||
#endif
|
||||
@@ -1374,15 +1384,15 @@ class AudioReactive : public Usermod {
|
||||
useBandPassFilter = false; // filter cuts lowest and highest frequency bands from FFT result (use on very noisy mic inputs)
|
||||
useMicFilter = true; // filter fixes aliasing to base & highest frequency bands and reduces noise floor (recommended for all mic inputs)
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone
|
||||
#endif
|
||||
|
||||
switch (dmType) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// stub cases for not-yet-supported I2S modes on other ESP32 chips
|
||||
case 0: //ADC analog
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
case 5: //PDM Microphone
|
||||
#endif
|
||||
#endif
|
||||
@@ -1411,7 +1421,7 @@ class AudioReactive : public Usermod {
|
||||
delay(100);
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
case 5:
|
||||
DEBUGSR_PRINT(F("AR: Generic PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT));
|
||||
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/4.0f);
|
||||
@@ -1428,7 +1438,7 @@ class AudioReactive : public Usermod {
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ADC over I2S is only possible on "classic" ESP32
|
||||
case 0:
|
||||
DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only)."));
|
||||
@@ -1439,7 +1449,7 @@ class AudioReactive : public Usermod {
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 254: // dummy "network receive only" mode
|
||||
case SR_DMTYPE_NETWORK_ONLY: // dummy "network receive only" mode
|
||||
if (audioSource) delete audioSource; audioSource = nullptr;
|
||||
disableSoundProcessing = true;
|
||||
audioSyncEnabled = 2; // force udp sound receive mode
|
||||
@@ -1456,19 +1466,25 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
delay(250); // give microphone enough time to initialise
|
||||
|
||||
if (!audioSource && (dmType != 254)) enabled = false;// audio failed to initialise
|
||||
if (!audioSource && (dmType != SR_DMTYPE_NETWORK_ONLY)) enabled = false;// audio failed to initialise
|
||||
#endif
|
||||
if (enabled) onUpdateBegin(false); // create FFT task, and initialize network
|
||||
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (FFT_Task == nullptr) enabled = false; // FFT task creation failed
|
||||
if (audioSource && 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
|
||||
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
|
||||
#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
|
||||
disableSoundProcessing = true;
|
||||
}
|
||||
#endif
|
||||
@@ -1522,14 +1538,9 @@ 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)
|
||||
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
|
||||
{
|
||||
// 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) {
|
||||
#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."));
|
||||
@@ -1606,7 +1617,11 @@ 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();
|
||||
}
|
||||
@@ -1711,7 +1726,7 @@ class AudioReactive : public Usermod {
|
||||
);
|
||||
}
|
||||
micDataReal = 0.0f; // just to be sure
|
||||
if (enabled) disableSoundProcessing = false;
|
||||
if (enabled) disableSoundProcessing = false; // allows FFT_Task to run at least once, even when loop() might disable again
|
||||
updateIsRunning = init;
|
||||
}
|
||||
|
||||
@@ -1951,14 +1966,10 @@ 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 && customPalettes.size()<WLED_MAX_CUSTOM_PALETTES) {
|
||||
if (initDone && enabled && addPalettes && palettes==0) {
|
||||
// if palettes were removed during JSON call re-add them
|
||||
createAudioPalettes();
|
||||
}
|
||||
@@ -2006,7 +2017,7 @@ class AudioReactive : public Usermod {
|
||||
top[FPSTR(_addPalettes)] = addPalettes;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
JsonObject amic = top.createNestedObject(FPSTR(_analogmic));
|
||||
amic["pin"] = audioPin;
|
||||
#endif
|
||||
@@ -2065,16 +2076,16 @@ class AudioReactive : public Usermod {
|
||||
configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes);
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin);
|
||||
#else
|
||||
audioPin = -1; // MCU does not support analog mic
|
||||
#endif
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType);
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM
|
||||
#endif
|
||||
#endif
|
||||
@@ -2112,17 +2123,20 @@ class AudioReactive : public Usermod {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uiScript.print(F("uxp=ux+':digitalmic:pin[]';")); // uxp = shortcut for AudioReactive:digitalmic:pin[]
|
||||
uiScript.print(F("dd=addDropdown(ux,'digitalmic:type');"));
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
uiScript.print(F("addOption(dd,'Generic Analog',0);"));
|
||||
#endif
|
||||
uiScript.print(F("addOption(dd,'Generic I2S',1);"));
|
||||
uiScript.print(F("addOption(dd,'ES7243',2);"));
|
||||
uiScript.print(F("addOption(dd,'SPH0654',3);"));
|
||||
uiScript.print(F("addOption(dd,'Generic I2S with Mclk',4);"));
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61)
|
||||
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);"));
|
||||
@@ -2155,7 +2169,7 @@ class AudioReactive : public Usermod {
|
||||
uiScript.print(F("addInfo(uxp,0,'<i>sd/data/dout</i>','I2S SD');"));
|
||||
uiScript.print(F("addInfo(uxp,1,'<i>ws/clk/lrck</i>','I2S WS');"));
|
||||
uiScript.print(F("addInfo(uxp,2,'<i>sck/bclk</i>','I2S SCK');"));
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
uiScript.print(F("addInfo(uxp,3,'<i>only use -1, 0, 1 or 3</i>','I2S MCLK');"));
|
||||
#else
|
||||
uiScript.print(F("addInfo(uxp,3,'<i>master clock</i>','I2S MCLK');"));
|
||||
@@ -2187,24 +2201,21 @@ class AudioReactive : public Usermod {
|
||||
|
||||
void AudioReactive::removeAudioPalettes(void) {
|
||||
DEBUG_PRINTLN(F("Removing audio palettes."));
|
||||
while (palettes>0) {
|
||||
customPalettes.pop_back();
|
||||
DEBUG_PRINTLN(palettes);
|
||||
palettes--;
|
||||
}
|
||||
DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size());
|
||||
palettes -= (int8_t)removeUsermodPalettes(_name);
|
||||
if (palettes < 0) palettes = 0; // safeguard
|
||||
}
|
||||
|
||||
void AudioReactive::createAudioPalettes(void) {
|
||||
DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size());
|
||||
if (palettes) return;
|
||||
DEBUG_PRINTLN(F("Adding audio palettes."));
|
||||
for (int i=0; i<MAX_PALETTES; i++)
|
||||
if (customPalettes.size() < WLED_MAX_CUSTOM_PALETTES) {
|
||||
customPalettes.push_back(CRGBPalette16(CRGB(BLACK)));
|
||||
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()
|
||||
palettes++;
|
||||
DEBUG_PRINTLN(palettes);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
// credit @netmindz ar palette, adapted for usermod @blazoncek
|
||||
@@ -2238,9 +2249,10 @@ CRGB AudioReactive::getCRGBForBand(int x, int pal) {
|
||||
|
||||
void AudioReactive::fillAudioPalettes() {
|
||||
if (!palettes) return;
|
||||
size_t lastCustPalette = customPalettes.size();
|
||||
if (int(lastCustPalette) >= palettes) lastCustPalette -= palettes;
|
||||
for (int pal=0; pal<palettes; pal++) {
|
||||
// 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;
|
||||
uint8_t tcp[16]; // Needs to be 4 times however many colors are being used.
|
||||
// 3 colors = 12, 4 colors = 16, etc.
|
||||
|
||||
@@ -2248,26 +2260,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;
|
||||
|
||||
customPalettes[lastCustPalette+pal].loadDynamicGradientPalette(tcp);
|
||||
ump.palette.loadDynamicGradientPalette(tcp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2278,11 +2290,14 @@ const char AudioReactive::_config[] PROGMEM = "config";
|
||||
const char AudioReactive::_dynamics[] PROGMEM = "dynamics";
|
||||
const char AudioReactive::_frequency[] PROGMEM = "frequency";
|
||||
const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel";
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
|
||||
#endif
|
||||
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic";
|
||||
const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes";
|
||||
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::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
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
|
||||
// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents
|
||||
// and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32C61) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265)
|
||||
// there are two things in these MCUs that could lead to problems with audio processing:
|
||||
// * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x)
|
||||
// * single core, so FFT task might slow down other things like LED updates
|
||||
#if !defined(SOC_I2S_NUM) || (SOC_I2S_NUM < 1)
|
||||
#error This audio reactive usermod does not support ESP32-C2 or ESP32-C3.
|
||||
#error This audio reactive usermod does not support your MCU yet.
|
||||
#else
|
||||
#warning This audio reactive usermod does not support ESP32-C2 and ESP32-C3.
|
||||
#warning This audio reactive usermod does not support your MCU yet.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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
|
||||
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"T-QT-PRO-8MB_dice\"
|
||||
-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
|
||||
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_qspi_dice\"
|
||||
-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
|
||||
; build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_dice\"
|
||||
; ; Enable Pixels dice mod
|
||||
; -D USERMOD_PIXELS_DICE_TRAY
|
||||
; lib_deps = ${esp32.lib_deps}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name:": "pov_display",
|
||||
"name": "pov_display",
|
||||
"build": { "libArchive": false},
|
||||
"platforms": ["espressif32"]
|
||||
}
|
||||
|
||||
@@ -234,11 +234,11 @@ class QuinLEDAnPentaUsermod : public Usermod
|
||||
|
||||
bool oledCheckForNetworkChanges()
|
||||
{
|
||||
if (lastKnownNetworkConnected != Network.isConnected() || lastKnownIp != Network.localIP()
|
||||
if (lastKnownNetworkConnected != WLEDNetwork.isConnected() || lastKnownIp != WLEDNetwork.localIP()
|
||||
|| lastKnownWiFiConnected != WiFi.isConnected() || lastKnownSsid != WiFi.SSID()
|
||||
|| lastKnownApActive != apActive || lastKnownApSsid != apSSID || lastKnownApPass != apPass || lastKnownApChannel != apChannel) {
|
||||
lastKnownNetworkConnected = Network.isConnected();
|
||||
lastKnownIp = Network.localIP();
|
||||
lastKnownNetworkConnected = WLEDNetwork.isConnected();
|
||||
lastKnownIp = WLEDNetwork.localIP();
|
||||
lastKnownWiFiConnected = WiFi.isConnected();
|
||||
lastKnownSsid = WiFi.SSID();
|
||||
lastKnownApActive = apActive;
|
||||
|
||||
@@ -143,7 +143,13 @@ 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");
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ String HttpPullLightControl::generateUniqueId() {
|
||||
unsigned char shaResult[20]; // SHA1 produces a hash of 20 bytes (which is 40 HEX characters)
|
||||
mbedtls_sha1_context ctx;
|
||||
mbedtls_sha1_init(&ctx);
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
status = mbedtls_sha1_starts_ret(&ctx);
|
||||
if (status != 0) {
|
||||
DEBUG_PRINTLN(F("Error starting SHA1 checksum calculation"));
|
||||
@@ -83,6 +84,21 @@ String HttpPullLightControl::generateUniqueId() {
|
||||
if (status != 0) {
|
||||
DEBUG_PRINTLN(F("Error finishing SHA1 checksum calculation"));
|
||||
}
|
||||
#else
|
||||
// function names have changed in esp-idf V5
|
||||
status = mbedtls_sha1_starts(&ctx);
|
||||
if (status != 0) {
|
||||
DEBUG_PRINTLN(F("Error starting SHA1 checksum calculation"));
|
||||
}
|
||||
status = mbedtls_sha1_update(&ctx, reinterpret_cast<const unsigned char*>(input.c_str()), input.length());
|
||||
if (status != 0) {
|
||||
DEBUG_PRINTLN(F("Error feeding update buffer into ongoing SHA1 checksum calculation"));
|
||||
}
|
||||
status = mbedtls_sha1_finish(&ctx, shaResult);
|
||||
if (status != 0) {
|
||||
DEBUG_PRINTLN(F("Error finishing SHA1 checksum calculation"));
|
||||
}
|
||||
#endif
|
||||
mbedtls_sha1_free(&ctx);
|
||||
|
||||
// Convert the Hash to a hexadecimal string
|
||||
|
||||
+3
-3
@@ -2,10 +2,10 @@
|
||||
default_envs = esp32dev_fld
|
||||
|
||||
[env:esp32dev_fld]
|
||||
extends = env:esp32dev_V4
|
||||
custom_usermods = ${env:esp32dev_V4.custom_usermods} four_line_display_ALT
|
||||
extends = env:esp32dev
|
||||
custom_usermods = ${env:esp32dev.custom_usermods} four_line_display_ALT
|
||||
build_flags =
|
||||
${env:esp32dev_V4.build_flags}
|
||||
${env:esp32dev.build_flags}
|
||||
-D FLD_TYPE=SH1106
|
||||
-D I2CSCLPIN=27
|
||||
-D I2CSDAPIN=26
|
||||
@@ -264,7 +264,7 @@ void FourLineDisplayUsermod::setup() {
|
||||
// interfaces here
|
||||
void FourLineDisplayUsermod::connected() {
|
||||
knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() :
|
||||
knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP();
|
||||
knownIp = WLEDNetwork.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : WLEDNetwork.localIP();
|
||||
networkOverlay(PSTR("NETWORK INFO"),7000);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -2,10 +2,10 @@
|
||||
default_envs = esp32dev_re
|
||||
|
||||
[env:esp32dev_re]
|
||||
extends = env:esp32dev_V4
|
||||
custom_usermods = ${env:esp32dev_V4.custom_usermods} rotary_encoder_ui_ALT
|
||||
extends = env:esp32dev
|
||||
custom_usermods = ${env:esp32dev.custom_usermods} rotary_encoder_ui_ALT
|
||||
build_flags =
|
||||
${env:esp32dev_V4.build_flags}
|
||||
${env:esp32dev.build_flags}
|
||||
-D USERMOD_ROTARY_ENCODER_GPIO=INPUT
|
||||
-D ENCODER_DT_PIN=21
|
||||
-D ENCODER_CLK_PIN=23
|
||||
+47
-44
@@ -701,7 +701,9 @@ void dissolve(uint32_t color) {
|
||||
unsigned i = hw_random16(SEGLEN);
|
||||
if (SEGENV.aux0) { //dissolve to primary/palette
|
||||
if (pixels[i] == SEGCOLOR(1)) {
|
||||
pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color;
|
||||
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;
|
||||
break; //only spawn 1 new pixel per frame
|
||||
}
|
||||
} else { //dissolve to secondary
|
||||
@@ -1059,7 +1061,7 @@ void mode_traffic_light(void) {
|
||||
case 0: SEGMENT.setPixelColor(i, 0x00FF0000); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break;
|
||||
case 1: SEGMENT.setPixelColor(i, 0x00FF0000); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed)); SEGMENT.setPixelColor(i+1, 0x00EECC00); break;
|
||||
case 2: SEGMENT.setPixelColor(i+2, 0x0000FF00); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break;
|
||||
case 3: SEGMENT.setPixelColor(i+1, 0x00EECC00); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed));break;
|
||||
case 3: SEGMENT.setPixelColor(i+1, gamma32inv(0x00EECC00)); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed));break; // gamma inversion to restore original pre 16.0 looks
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2343,7 +2345,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(), 64, NOBLEND));
|
||||
SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, hw_random8(), gamma8inv(64), NOBLEND)); // note on gamma8inv: inverting results in non-linear brightness fade as originally designed
|
||||
break; //only spawn 1 new pixel per frame per 50 LEDs
|
||||
}
|
||||
}
|
||||
@@ -2614,7 +2616,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, bright, NOBLEND);
|
||||
c = ColorFromPalette(SEGPALETTE, hue, gamma8inv(bright), NOBLEND); // note on gamma8inv: inverting results in non-linear brightness fade as originally designed
|
||||
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
|
||||
@@ -2649,19 +2651,18 @@ 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".
|
||||
// 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
|
||||
CRGBW bg = SEGCOLOR(1);
|
||||
unsigned bglight = bg.getAverageLight();
|
||||
unsigned bglight = bg.getRGBaverage();
|
||||
if (bglight > 64) {
|
||||
bg = color_fade(bg, 16, true); // very bright, so scale to 1/16th
|
||||
bg = color_fade(bg, gamma8inv(16), true); // very bright, so scale to 1/16th
|
||||
} else if (bglight > 16) {
|
||||
bg = color_fade(bg, 64, true); // not that bright, so scale to 1/4th
|
||||
bg = color_fade(bg, gamma8inv(64), true); // not that bright, so scale to 1/4
|
||||
} else {
|
||||
bg = color_fade(bg, 86, true); // dim, scale to 1/3rd.
|
||||
bg = color_fade(bg, gamma8inv(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
|
||||
|
||||
unsigned backgroundBrightness = bg.getAverageLight();
|
||||
bglight = bg.getRGBaverage(); // update after scaling
|
||||
|
||||
for (unsigned i = 0; i < SEGLEN; i++) {
|
||||
|
||||
@@ -2678,8 +2679,8 @@ static void twinklefox_base(bool cat)
|
||||
// on the "brightness = f( time )" idea.
|
||||
CRGBW c = twinklefox_one_twinkle(myclock30, myunique8, cat);
|
||||
|
||||
unsigned cbright = c.getAverageLight();
|
||||
int deltabright = cbright - backgroundBrightness;
|
||||
unsigned cbright = c.getRGBaverage();
|
||||
int deltabright = cbright - bglight;
|
||||
if (deltabright >= 32 || (bg==0)) {
|
||||
// If the new pixel is significantly brighter than the background color,
|
||||
// use the new color.
|
||||
@@ -4181,14 +4182,14 @@ void mode_pacifica()
|
||||
uint32_t nowOld = strip.now;
|
||||
|
||||
CRGBPalette16 pacifica_palette_1 =
|
||||
{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
|
||||
0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50 };
|
||||
{ 0x002229, 0x001E2F, 0x001934, 0x001938, 0x00143F, 0x001443, 0x00B047, 0x00B04C, // note: palettes are gamma inverted using gamma 2.0 to get closer to pre 16.0 looks
|
||||
0x00004F, 0x000054, 0x000062, 0x00006F, 0x00007A, 0x000085, 0x47938A, 0x64D08E };
|
||||
CRGBPalette16 pacifica_palette_2 =
|
||||
{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
|
||||
0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F };
|
||||
{ 0x002229, 0x001E2F, 0x001934, 0x001938, 0x00143F, 0x001443, 0x00B047, 0x00B04C,
|
||||
0x00004F, 0x000054, 0x000062, 0x00006F, 0x00007A, 0x000085, 0x369B90, 0x4FDC9B };
|
||||
CRGBPalette16 pacifica_palette_3 =
|
||||
{ 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33,
|
||||
0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF };
|
||||
{ 0x00142C, 0x00193B, 0x002247, 0x002551, 0x002C5A, 0x002F63, 0x00346B, 0x003671,
|
||||
0x003B78, 0x003F7F, 0x00478E, 0x004D9C, 0x0054A9, 0x005AB4, 0x3F7FDC, 0x5A9CFF };
|
||||
|
||||
if (SEGMENT.palette) {
|
||||
pacifica_palette_1 = SEGPALETTE;
|
||||
@@ -4239,10 +4240,10 @@ void mode_pacifica()
|
||||
c += CRGB(overage, overage2, qadd8(overage2, overage2));
|
||||
}
|
||||
|
||||
//deepen the blues and greens
|
||||
c.blue = scale8(c.blue, 145);
|
||||
c.green = scale8(c.green, 200);
|
||||
c |= CRGB( 2, 5, 7);
|
||||
//deepen the blues and greens note: no longer needed with proper gamma in 16.0
|
||||
//c.blue = scale8(c.blue, 145);
|
||||
//c.green = scale8(c.green, 200);
|
||||
//c |= CRGB( 2, 5, 7);
|
||||
|
||||
SEGMENT.setPixelColor(i, c);
|
||||
}
|
||||
@@ -4372,8 +4373,8 @@ void mode_noisepal(void) { // Slow noise pale
|
||||
SEGENV.step = strip.now;
|
||||
|
||||
unsigned baseI = hw_random8();
|
||||
//palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)), CHSV(baseI+128, 255, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 192, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 255, hw_random8(128,255)));
|
||||
palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)), CHSV(baseI+128, 255, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 192, hw_random8(128,255)), CHSV(baseI+hw_random8(92), 255, hw_random8(128,255)));
|
||||
uint32_t minBri = gamma8inv(128); // use gamma inversion on min brightness value to restore pre 16.0 looks (more brilliant palettes)
|
||||
palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(minBri,255)), CHSV(baseI+128, 255, hw_random8(minBri,255)), CHSV(baseI+hw_random8(92), 192, hw_random8(minBri,255)), CHSV(baseI+hw_random8(92), 255, hw_random8(minBri,255)));
|
||||
}
|
||||
|
||||
//EVERY_N_MILLIS(10) { //(don't have to time this, effect function is only called every 24ms)
|
||||
@@ -4758,10 +4759,10 @@ void mode_tv_simulator(void) {
|
||||
tvSimulator->actualColorB = temp[n ];
|
||||
}
|
||||
}
|
||||
// Apply gamma correction, further expand to 16/16/16
|
||||
nr = (uint8_t)gamma8(tvSimulator->actualColorR) * 257; // New R/G/B
|
||||
ng = (uint8_t)gamma8(tvSimulator->actualColorG) * 257;
|
||||
nb = (uint8_t)gamma8(tvSimulator->actualColorB) * 257;
|
||||
// expand to 16 bit
|
||||
nr = (uint8_t)(tvSimulator->actualColorR) * 257; // New R/G/B
|
||||
ng = (uint8_t)(tvSimulator->actualColorG) * 257;
|
||||
nb = (uint8_t)(tvSimulator->actualColorB) * 257;
|
||||
|
||||
if (SEGENV.aux0 == 0) { // initialize next iteration
|
||||
SEGENV.aux0 = 1;
|
||||
@@ -5451,6 +5452,7 @@ void mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://na
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Repeat detection
|
||||
@@ -5718,7 +5720,7 @@ void mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Ada
|
||||
SEGENV.step = 0;
|
||||
}
|
||||
|
||||
uint8_t fade = map(SEGMENT.custom1, 0, 255, 50, 250); // equals trail size
|
||||
uint8_t fade = map(SEGMENT.custom1, 0, 255, 30, 250); // equals trail size
|
||||
uint8_t speed = (256-SEGMENT.speed) >> map(min(rows, 150), 0, 150, 0, 3); // slower speeds for small displays
|
||||
|
||||
uint32_t spawnColor;
|
||||
@@ -5727,8 +5729,8 @@ void mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Ada
|
||||
spawnColor = SEGCOLOR(0);
|
||||
trailColor = SEGCOLOR(1);
|
||||
} else {
|
||||
spawnColor = RGBW32(175,255,175,0);
|
||||
trailColor = RGBW32(27,130,39,0);
|
||||
spawnColor = RGBW32(gamma8inv(175), gamma8inv(255), gamma8inv(175), 0); // use gamma inversion to restor original pre 16.0 looks
|
||||
trailColor = RGBW32(gamma8inv(27), gamma8inv(130), gamma8inv(39), 0);
|
||||
}
|
||||
|
||||
bool emptyScreen = true;
|
||||
@@ -6834,7 +6836,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) gravcen->topLED = tempsamp-offset;
|
||||
if (tempsamp >= gravcen->topLED + offset) gravcen->topLED = tempsamp-offset;
|
||||
else if (gravcen->gravityCounter % gravity == 0) gravcen->topLED--;
|
||||
|
||||
if(mode == 1) { //Gravcentric
|
||||
@@ -7003,8 +7005,8 @@ static const char _data_FX_MODE_MIDNOISE[] PROGMEM = "Midnoise@Fade rate,Max. le
|
||||
//////////////////////
|
||||
// I am the god of hellfire. . . Volume (only) reactive fire routine. Oh, look how short this is.
|
||||
void mode_noisefire(void) { // Noisefire. By Andrew Tuline.
|
||||
CRGBPalette16 myPal = CRGBPalette16(CHSV(0,255,2), CHSV(0,255,4), CHSV(0,255,8), CHSV(0, 255, 8), // Fire palette definition. Lower value = darker.
|
||||
CHSV(0, 255, 16), CRGB::Red, CRGB::Red, CRGB::Red,
|
||||
CRGBPalette16 myPal = CRGBPalette16(CHSV(0,255,20), CHSV(0,255,30), CHSV(0,255,40), CHSV(0, 255, 44), // Fire palette definition. Lower value = darker.
|
||||
CHSV(0, 255, 64), CRGB::Red, CRGB::Red, CRGB::Red,
|
||||
CRGB::DarkOrange, CRGB::DarkOrange, CRGB::Orange, CRGB::Orange,
|
||||
CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow);
|
||||
|
||||
@@ -7240,7 +7242,7 @@ void mode_DJLight(void) { // Written by ??? Adapted by Will Ta
|
||||
if (SEGENV.aux0 != secondHand) { // Triggered millis timing.
|
||||
SEGENV.aux0 = secondHand;
|
||||
|
||||
CRGB color = CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2); // 16-> 15 as 16 is out of bounds
|
||||
CRGB color = CRGB(gamma8inv(fftResult[15]/2), gamma8inv(fftResult[5]/2), gamma8inv(fftResult[0]/2)); // apply gamma inversion to restor pre 16.0 looks
|
||||
SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // TODO - Update
|
||||
|
||||
// if SEGLEN equals 1 these loops won't execute
|
||||
@@ -7320,7 +7322,7 @@ void mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung.
|
||||
uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t
|
||||
unsigned b = 255 * intensity;
|
||||
if (b > 255) b = 255;
|
||||
color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED
|
||||
color = CHSV(i, 240, gamma8inv(b)); // use gamma inversion on brightness to restore pre 16.0 looks
|
||||
}
|
||||
|
||||
// shift the pixels one pixel up
|
||||
@@ -7411,7 +7413,7 @@ void mode_freqwave(void) { // Freqwave. By Andreas Pleschung.
|
||||
int lowerLimit = 80 + 3 * SEGMENT.custom1;
|
||||
uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t
|
||||
unsigned b = min(255.0f, 255.0f * intensity);
|
||||
color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED
|
||||
color = CHSV(i, 240, gamma8inv(b)); // use gamma inversion on brightness to restore pre 16.0 looks
|
||||
}
|
||||
|
||||
SEGMENT.setPixelColor(SEGLEN/2, color);
|
||||
@@ -7518,7 +7520,7 @@ void mode_waterfall(void) { // Waterfall. By: Andrew Tuline
|
||||
|
||||
unsigned k = SEGLEN-1;
|
||||
if (samplePeak) {
|
||||
pixels[k] = (uint32_t)CRGB(CHSV(92,92,92));
|
||||
pixels[k] = (uint32_t)CRGB(CHSV(92,92,gamma8inv(92))); // use gamma inversion on brightness to restore pre 16.0 looks
|
||||
} else {
|
||||
pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (uint8_t)my_magnitude);
|
||||
}
|
||||
@@ -7629,7 +7631,7 @@ void mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Ta
|
||||
int v = map(fftResult[band % 16], 0, 255, 10, 255);
|
||||
for (int w = 0; w < barWidth; w++) {
|
||||
int xpos = (barWidth * b) + w;
|
||||
SEGMENT.setPixelColorXY(xpos, 0, CHSV(hue, 255, v));
|
||||
SEGMENT.setPixelColorXY(xpos, 0, CHSV(hue, 255, gamma8inv(v))); // use gamma inversion on brightness to restore original pre 16.0 looks
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7978,7 +7980,7 @@ void mode_2Doctopus() {
|
||||
byte radius = rMap[XY(x,y)].radius;
|
||||
//CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8_t(sin8_t((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1)));
|
||||
unsigned intensity = sin8_t(sin8_t((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1));
|
||||
intensity = map((intensity*intensity) & 0xFFFF, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display
|
||||
//intensity = map((intensity*intensity) & 0xFFFF, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display -> no longer needed with proper gamma correction
|
||||
SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity));
|
||||
}
|
||||
}
|
||||
@@ -8730,7 +8732,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,o3=1";
|
||||
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";
|
||||
|
||||
/*
|
||||
Particle smashing down like meteors and exploding as they hit the ground, has many parameters to play with
|
||||
@@ -9836,6 +9838,7 @@ 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
|
||||
@@ -9860,7 +9863,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;
|
||||
@@ -9910,7 +9913,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_random16(PartSys->maxX);
|
||||
PartSys->sources[i].source.x = hw_random(PartSys->maxX);
|
||||
else
|
||||
PartSys->particleMoveUpdate(PartSys->sources[i].source, PartSys->sources[i].sourceFlags, &sparklersettings); //move sparkler
|
||||
}
|
||||
|
||||
+8
-4
@@ -61,10 +61,12 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
|
||||
#define WLED_FPS 42
|
||||
#define FRAMETIME_FIXED (1000/WLED_FPS)
|
||||
#define FRAMETIME strip.getFrameTime()
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#define MIN_FRAME_DELAY 3 // S2/C3 are slower than normal esp32, and only have one core
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if (SOC_CPU_CORES_NUM < 2)
|
||||
#define MIN_FRAME_DELAY 3 // S2/C3/C6/C5 are slower than normal esp32, and only have one core
|
||||
#else
|
||||
#define MIN_FRAME_DELAY 2 // classic esp32/S3/P4: minimum wait between repaints, to keep other functions like WiFi alive
|
||||
#endif
|
||||
#else
|
||||
#define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY
|
||||
#endif
|
||||
@@ -618,6 +620,7 @@ class Segment {
|
||||
DEBUGFX_PRINTLN();
|
||||
#endif
|
||||
clearName();
|
||||
stopTransition(); // deallocate "_t" (transition) and with it "_segOld" note: _segOld has _t=null, see copy constructor
|
||||
#ifdef WLED_ENABLE_GIF
|
||||
endImagePlayback(this);
|
||||
#endif
|
||||
@@ -879,6 +882,7 @@ 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
|
||||
|
||||
+149
-34
@@ -29,9 +29,7 @@
|
||||
19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]}
|
||||
*/
|
||||
|
||||
#if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES
|
||||
#error "Max segments must be at least max number of busses!"
|
||||
#endif
|
||||
static_assert(MAX_NUM_SEGMENTS >= WLED_MAX_BUSSES, "Max segments must be at least max number of busses!");
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -98,7 +96,7 @@ Segment& Segment::operator= (const Segment &orig) {
|
||||
if (this != &orig) {
|
||||
// clean destination
|
||||
if (name) { p_free(name); name = nullptr; }
|
||||
if (_t) stopTransition(); // also erases _t
|
||||
stopTransition(); // delete _t
|
||||
deallocateData();
|
||||
p_free(pixels);
|
||||
pixels = nullptr;
|
||||
@@ -131,7 +129,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept {
|
||||
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
|
||||
if (this != &orig) {
|
||||
if (name) { p_free(name); name = nullptr; } // free old name
|
||||
if (_t) stopTransition(); // also erases _t
|
||||
stopTransition(); // delete _t
|
||||
deallocateData(); // free old runtime data
|
||||
p_free(pixels); // free old pixel buffer
|
||||
// move source data
|
||||
@@ -228,12 +226,19 @@ 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 the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
|
||||
// then come user custom palettes (IDs <=200) and usermod palettes (IDs 201-255), both growing downward from their respective base IDs
|
||||
// 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 >= 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()
|
||||
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;
|
||||
}
|
||||
}
|
||||
switch (pal) {
|
||||
case 0: //default palette. Exceptions for specific effects above
|
||||
targetPalette = PartyColors_gc22;
|
||||
@@ -267,8 +272,10 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
}
|
||||
break;}
|
||||
default: //progmem palettes
|
||||
if (pal > 255 - customPalettes.size()) {
|
||||
targetPalette = customPalettes[255-pal]; // we checked bounds above
|
||||
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];
|
||||
} else if (pal < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) { // palette 6 - 12, fastled palettes
|
||||
targetPalette = *fastledPalettes[pal - DYNAMIC_PALETTE_COUNT];
|
||||
} else {
|
||||
@@ -281,6 +288,8 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
}
|
||||
|
||||
// starting a transition has to occur before change so we get current values 1st
|
||||
// note: _t is the temporary segment that holds the values transitioned from (palette, colors, brightness,...) and the current segment holds the "to" values
|
||||
// if this is a non FADE transition or an FX change, the _oldSegment is created which is a full copy of the segment before the change
|
||||
void Segment::startTransition(uint16_t dur, bool segmentCopy) {
|
||||
if (dur == 0 || !isActive()) {
|
||||
if (isInTransition()) _t->_dur = 0;
|
||||
@@ -290,15 +299,42 @@ void Segment::startTransition(uint16_t dur, bool segmentCopy) {
|
||||
if (segmentCopy && !_t->_oldSegment) {
|
||||
// already in transition but segment copy requested and not yet created
|
||||
_t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings
|
||||
_t->_start = millis(); // restart countdown
|
||||
_t->_start = millis(); // restart transition timer
|
||||
_t->_dur = dur;
|
||||
_t->_prevPaletteBlends = 0;
|
||||
_t->_prevPaletteBlends = 0; // reset palette blends
|
||||
if (_t->_oldSegment) {
|
||||
_t->_oldSegment->palette = _t->_palette; // restore original palette and colors (from start of transition)
|
||||
_t->_oldSegment->palette = _t->_palette; // restore original palette, colors, brightness and CCT (from start of transition)
|
||||
for (unsigned i = 0; i < NUM_COLORS; i++) _t->_oldSegment->colors[i] = _t->_colors[i];
|
||||
_t->_oldSegment->opacity = _t->_bri;
|
||||
_t->_oldSegment->cct = _t->_cct;
|
||||
// if already partway through a FADE transition, set old segment's colors to current blend to avoid jumping back to original colors
|
||||
if (_t->_progress > 0) {
|
||||
// already in a transition, see comment below
|
||||
for (unsigned i = 0; i < NUM_COLORS; i++) _t->_oldSegment->colors[i] = color_blend16(_t->_colors[i], colors[i], _t->_progress);
|
||||
_t->_oldSegment->opacity = currentBri(); // update "original" brightness note: _t->_progress is updated in updateTransitionProgress() so still valid here
|
||||
_t->_oldSegment->cct = currentCCT(); // update "original" CCT (reduces jump)
|
||||
}
|
||||
DEBUGFX_PRINTF_P(PSTR("-- Updated transition with segment copy: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels);
|
||||
if (!_t->_oldSegment->isActive()) stopTransition();
|
||||
}
|
||||
} else if (_t->_progress > 0) {
|
||||
// already in a transition: capture the current visual blend as the new "from" state so the incoming change does not cause a visible jump.
|
||||
// _palT already holds the intermediate blended palette and will continue blending toward the new target (see beginDraw()), so no palette action needed.
|
||||
// initial version by @blazoncek (https://github.com/blazoncek/WLED/commit/40d9812)
|
||||
for (unsigned i = 0; i < NUM_COLORS; i++) _t->_colors[i] = color_blend16(_t->_colors[i], colors[i], _t->_progress);
|
||||
_t->_bri = currentBri(); // update "original" brightness note: _t->_progress is updated in updateTransitionProgress() so still valid here
|
||||
_t->_cct = currentCCT(); // update "original" CCT (reduces jump)
|
||||
// restart transition timer only if a pure FADE transition, otherwise let the FX change or non-FADE transition finish
|
||||
// this avoids a re-start of the transition if color or brightness is changed during an ongoing FX or non-FADE transition
|
||||
if (blendingStyle == TRANSITION_FADE) {
|
||||
if (_t->_oldSegment != nullptr) {
|
||||
if (_t->_oldSegment->mode != mode)
|
||||
return; // do not reset transition if this is an FX change, note: the disadvantage is that colors still jump in that case
|
||||
}
|
||||
_t->_start = millis();
|
||||
_t->_dur = dur;
|
||||
_t->_prevPaletteBlends = 0;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -324,6 +360,7 @@ void Segment::startTransition(uint16_t dur, bool segmentCopy) {
|
||||
}
|
||||
|
||||
void Segment::stopTransition() {
|
||||
if (_t == nullptr) return; // no ongoing transition
|
||||
DEBUG_PRINTF_P(PSTR("-- Stopping transition: S=%p T(%p) O[%p]\n"), this, _t, _t->_oldSegment);
|
||||
delete _t;
|
||||
_t = nullptr;
|
||||
@@ -356,7 +393,7 @@ uint8_t Segment::currentBri() const {
|
||||
if (prog < 0xFFFFU) {
|
||||
// this will blend opacity in new mode if style is FADE (single effect call)
|
||||
if (blendingStyle == TRANSITION_FADE) curBri = (prog * curBri + _t->_bri * (0xFFFFU - prog)) / 0xFFFFU;
|
||||
else curBri = Segment::isPreviousMode() ? _t->_bri : curBri;
|
||||
else curBri = Segment::isPreviousMode() ? _t->_bri : curBri;
|
||||
}
|
||||
return curBri;
|
||||
}
|
||||
@@ -443,7 +480,7 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
|
||||
|
||||
DEBUGFX_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d [%d,%d]\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y, (int)grp, (int)spc);
|
||||
markForReset();
|
||||
if (_t) stopTransition(); // we can't use transition if segment dimensions changed
|
||||
stopTransition(); // we can't use transition if segment dimensions changed
|
||||
stateChanged = true; // send UDP/WS broadcast
|
||||
|
||||
// apply change immediately
|
||||
@@ -585,7 +622,13 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
|
||||
}
|
||||
|
||||
Segment &Segment::setPalette(uint8_t pal) {
|
||||
if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette
|
||||
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 != 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)
|
||||
@@ -1102,7 +1145,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, false, 0); // only wrap if "always wrap" is set
|
||||
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)
|
||||
uint8_t w = W(getCurrentColor(0));
|
||||
CRGBW rgb;
|
||||
rgb = CHSV32(static_cast<uint16_t>(pos << 8), 255, 255);
|
||||
@@ -1161,7 +1204,7 @@ void WS2812FX::finalizeInit() {
|
||||
BusManager::removeAll();
|
||||
// TODO: ideally we would free everything segment related here to reduce fragmentation (pixel buffers, ledamp, segments, etc) but that somehow leads to heap corruption if touchig any of the buffers.
|
||||
unsigned digitalCount = 0;
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_HAS_PARALLEL_I2S)
|
||||
// validate the bus config: count I2S buses and check if they meet requirements
|
||||
unsigned i2sBusCount = 0;
|
||||
|
||||
@@ -1203,7 +1246,7 @@ void WS2812FX::finalizeInit() {
|
||||
unsigned busMemUsage = bus.memUsage(); // does not include DMA/RMT buffer but includes pixel buffers (segment buffer + global buffer)
|
||||
mem += busMemUsage;
|
||||
// estimate maximum I2S memory usage (only relevant for digital non-2pin busses when I2S is enabled)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
bool usesI2S = (bus.iType & 0x01) == 0; // I2S bus types are even numbered, can't use bus.driverType == 1 as getI() may have defaulted to RMT
|
||||
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && usesI2S) {
|
||||
#ifdef NPB_CONF_4STEP_CADENCE
|
||||
@@ -1257,11 +1300,17 @@ 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(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());
|
||||
_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);
|
||||
}
|
||||
|
||||
void WS2812FX::service() {
|
||||
@@ -1341,8 +1390,8 @@ static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t
|
||||
static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; }
|
||||
static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); }
|
||||
static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; }
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE)
|
||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3/C5 but slightly less accurate
|
||||
#else
|
||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1]
|
||||
#endif
|
||||
@@ -1352,7 +1401,7 @@ static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; }
|
||||
static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255
|
||||
static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||
static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE)
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a))) + ((2 * a * b + 256) << 8)) >> 16; } // Pegtop's formula (1 - 2a)b^2
|
||||
#else
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) + 255 * 2 * a * b) / (255 * 255); } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||
@@ -1367,6 +1416,7 @@ 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,
|
||||
@@ -1398,14 +1448,71 @@ 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)
|
||||
|
||||
Segment::setClippingRect(0, 0); // disable clipping by default
|
||||
const Segment *segO = topSegment.getOldSegment();
|
||||
const bool hasGrouping = topSegment.groupLength() != 1;
|
||||
|
||||
// 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;
|
||||
@@ -1466,7 +1573,6 @@ 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;
|
||||
|
||||
@@ -1562,8 +1668,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) {
|
||||
@@ -1643,7 +1749,7 @@ void WS2812FX::show() {
|
||||
|
||||
if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) {
|
||||
// clear frame buffer
|
||||
for (size_t i = 0; i < totalLen; i++) _pixels[i] = BLACK; // memset(_pixels, 0, sizeof(uint32_t) * getLengthTotal());
|
||||
memset(_pixels, 0, sizeof(uint32_t) * totalLen);
|
||||
// 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
|
||||
@@ -1658,6 +1764,9 @@ void WS2812FX::show() {
|
||||
int oldCCT = Bus::getCCT(); // store original CCT value (since it is global)
|
||||
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1)
|
||||
if (cctFromRgb) BusManager::setSegmentCCT(-1);
|
||||
// use color gamma correction if enabled, not in realtime mode with gamma disabled or currently overriding RT mode
|
||||
bool useGammaCorrection = gammaCorrectCol && !(realtimeMode && arlsDisableGammaCorrection && !realtimeOverride);
|
||||
|
||||
for (size_t i = 0; i < totalLen; i++) {
|
||||
// when correctWB is true setSegmentCCT() will convert CCT into K with which we can then
|
||||
// correct/adjust RGB value according to desired CCT value, it will still affect actual WW/CW ratio
|
||||
@@ -1666,8 +1775,8 @@ void WS2812FX::show() {
|
||||
}
|
||||
|
||||
uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32)
|
||||
if (c > 0 && !(realtimeMode && arlsDisableGammaCorrection))
|
||||
c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
|
||||
if (c > 0 && useGammaCorrection)
|
||||
c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
|
||||
BusManager::setPixelColor(getMappedPixelIndex(i), c);
|
||||
}
|
||||
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
|
||||
@@ -2008,10 +2117,13 @@ 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;
|
||||
}
|
||||
|
||||
@@ -2059,6 +2171,7 @@ bool WS2812FX::deserializeMap(unsigned n) {
|
||||
int index = atoi(number);
|
||||
if (index < 0 || index > 65535) index = 0xFFFF; // prevent integer wrap around
|
||||
customMappingTable[customMappingSize++] = index;
|
||||
if (end != nullptr) break; // array closing ']' was in this chunk; stop before atoi() coerces trailing JSON keys into bogus entries
|
||||
if (customMappingSize >= getLengthTotal()) break;
|
||||
} else break; // there was nothing to read, stop
|
||||
}
|
||||
@@ -2086,6 +2199,8 @@ bool WS2812FX::deserializeMap(unsigned n) {
|
||||
}
|
||||
|
||||
releaseJSONBufferLock();
|
||||
if (strip.getLengthTotal() != lengthTotalBefore)
|
||||
strip.updatePixelBuffer(); // allocate _pixels[] to match new length
|
||||
return (customMappingSize > 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -500,7 +500,7 @@ void ParticleSystem2D::applyGravity(PSparticle &part) {
|
||||
// note: a coefficient smaller than 0 will speed them up (this is a feature, not a bug), coefficient larger than 255 inverts the speed, so don't do that
|
||||
void ParticleSystem2D::applyFriction(PSparticle &part, const int32_t coefficient) {
|
||||
// note: not checking if particle is dead can be done by caller (or can be omitted)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE) // use bitshifts with rounding instead of division (2x faster)
|
||||
int32_t friction = 256 - coefficient;
|
||||
part.vx = ((int32_t)part.vx * friction + (((int32_t)part.vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts
|
||||
part.vy = ((int32_t)part.vy * friction + (((int32_t)part.vy >> 31) & 0xFF)) >> 8;
|
||||
@@ -514,7 +514,7 @@ void ParticleSystem2D::applyFriction(PSparticle &part, const int32_t coefficient
|
||||
// apply friction to all particles
|
||||
// note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways
|
||||
void ParticleSystem2D::applyFriction(const int32_t coefficient) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE) // use bitshifts with rounding instead of division (2x faster)
|
||||
int32_t friction = 256 - coefficient;
|
||||
for (uint32_t i = 0; i < usedParticles; i++) {
|
||||
particles[i].vx = ((int32_t)particles[i].vx * friction + (((int32_t)particles[i].vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts
|
||||
@@ -927,7 +927,7 @@ void WLED_O2_ATTR ParticleSystem2D::collideParticles(PSparticle &particle1, PSpa
|
||||
int32_t surfacehardness = max(collisionHardness, (int32_t)PS_P_MINSURFACEHARDNESS >> 1); // if particles are soft, the impulse must stay above a limit or collisions slip through at higher speeds, 170 seems to be a good value
|
||||
int32_t impulse = (((((-dotProduct) << 15) / distanceSquared) * surfacehardness) >> 8); // note: inverting before bitshift corrects for asymmetry in right-shifts (is slightly faster)
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE) // use bitshifts with rounding instead of division (2x faster)
|
||||
int32_t ximpulse = (impulse * dx + ((dx >> 31) & 0x7FFF)) >> 15; // note: extracting sign bit and adding rounding value to correct for asymmetry in right shifts
|
||||
int32_t yimpulse = (impulse * dy + ((dy >> 31) & 0x7FFF)) >> 15;
|
||||
#else
|
||||
@@ -955,7 +955,7 @@ void WLED_O2_ATTR ParticleSystem2D::collideParticles(PSparticle &particle1, PSpa
|
||||
if (collisionHardness < PS_P_MINSURFACEHARDNESS && (SEGMENT.call & 0x07) == 0) { // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely and stop sloshing around)
|
||||
const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS);
|
||||
// Note: could call applyFriction, but this is faster and speed is key here
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE) // use bitshifts with rounding instead of division (2x faster)
|
||||
particle1.vx = ((int32_t)particle1.vx * coeff + (((int32_t)particle1.vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts
|
||||
particle1.vy = ((int32_t)particle1.vy * coeff + (((int32_t)particle1.vy >> 31) & 0xFF)) >> 8;
|
||||
particle2.vx = ((int32_t)particle2.vx * coeff + (((int32_t)particle2.vx >> 31) & 0xFF)) >> 8;
|
||||
@@ -1408,7 +1408,7 @@ void ParticleSystem1D::applyGravity(PSparticle1D &part, PSparticleFlags1D &partF
|
||||
// slow down particle by friction, the higher the speed, the higher the friction. a high friction coefficient slows them more (255 means instant stop)
|
||||
// note: a coefficient smaller than 0 will speed them up (this is a feature, not a bug), coefficient larger than 255 inverts the speed, so don't do that
|
||||
void ParticleSystem1D::applyFriction(int32_t coefficient) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE) // use bitshifts with rounding instead of division (2x faster)
|
||||
int32_t friction = 256 - coefficient;
|
||||
for (uint32_t i = 0; i < usedParticles; i++) {
|
||||
if (particles[i].ttl)
|
||||
@@ -1699,7 +1699,7 @@ void WLED_O2_ATTR ParticleSystem1D::collideParticles(uint32_t partIdx1, uint32_t
|
||||
}
|
||||
int32_t surfacehardness = max(collisionHardness, (int32_t)PS_P_MINSURFACEHARDNESS_1D); // if particles are soft, the impulse must stay above a limit or collisions slip through
|
||||
// Calculate new velocities after collision note: not using dot product like in 2D as impulse is purely speed depnedent
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE) // use bitshifts with rounding instead of division (2x faster)
|
||||
int32_t impulse = (dv * surfacehardness + ((dv >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts
|
||||
#else // division is faster on ESP32, S2 and S3
|
||||
int32_t impulse = (dv * surfacehardness) / 255;
|
||||
@@ -1720,7 +1720,7 @@ void WLED_O2_ATTR ParticleSystem1D::collideParticles(uint32_t partIdx1, uint32_t
|
||||
|
||||
if (collisionHardness < PS_P_MINSURFACEHARDNESS_1D && (SEGMENT.call & 0x07) == 0) { // if particles are soft, they become 'sticky' i.e. apply some friction
|
||||
const uint32_t coeff = collisionHardness + (250 - PS_P_MINSURFACEHARDNESS_1D);
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
#if !defined(WLED_HAVE_FAST_int_DIVIDE) // use bitshifts with rounding instead of division (2x faster)
|
||||
particles[partIdx1].vx = ((int32_t)particles[partIdx1].vx * coeff + (((int32_t)particles[partIdx1].vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts
|
||||
particles[partIdx2].vx = ((int32_t)particles[partIdx2].vx * coeff + (((int32_t)particles[partIdx2].vx >> 31) & 0xFF)) >> 8;
|
||||
#else // division is faster on ESP32, S2 and S3
|
||||
|
||||
+1
-1
@@ -55,4 +55,4 @@ struct NodeStruct
|
||||
};
|
||||
typedef std::map<uint8_t, NodeStruct> NodesMap;
|
||||
|
||||
#endif // WLED_NODESTRUCT_H
|
||||
#endif // WLED_NODESTRUCT_H
|
||||
+71
-25
@@ -351,26 +351,25 @@ void BusDigital::setColorOrder(uint8_t colorOrder) {
|
||||
// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056
|
||||
std::vector<LEDType> BusDigital::getLEDTypes() {
|
||||
return {
|
||||
{TYPE_WS2812_RGB, "D", PSTR("WS281x")},
|
||||
{TYPE_WS2812_RGB, "D", PSTR("WS281x RGB")},
|
||||
{TYPE_WS2811_400KHZ, "D", PSTR("400kHz RGB")},
|
||||
{TYPE_TM1829, "D", PSTR("TM1829 RGB")},
|
||||
{TYPE_UCS8903, "D", PSTR("UCS8903 RGB")},
|
||||
{TYPE_APA106, "D", PSTR("APA106/PL9823 RGB")},
|
||||
{TYPE_TM1914, "D", PSTR("TM1914 RGB")},
|
||||
{TYPE_SK6812_RGBW, "D", PSTR("SK6812/WS2814 RGBW")},
|
||||
{TYPE_TM1814, "D", PSTR("TM1814")},
|
||||
{TYPE_WS2811_400KHZ, "D", PSTR("400kHz")},
|
||||
{TYPE_TM1829, "D", PSTR("TM1829")},
|
||||
{TYPE_UCS8903, "D", PSTR("UCS8903")},
|
||||
{TYPE_APA106, "D", PSTR("APA106/PL9823")},
|
||||
{TYPE_TM1914, "D", PSTR("TM1914")},
|
||||
{TYPE_FW1906, "D", PSTR("FW1906 GRBCW")},
|
||||
{TYPE_UCS8904, "D", PSTR("UCS8904 RGBW")},
|
||||
{TYPE_WS2805, "D", PSTR("WS2805 RGBCW")},
|
||||
{TYPE_SM16825, "D", PSTR("SM16825 RGBCW")},
|
||||
{TYPE_TM1814, "D", PSTR("TM1814 RGBW")},
|
||||
{TYPE_FW1906, "D", PSTR("FW1906/WS2811 RGBCCT")},
|
||||
{TYPE_WS2805, "D", PSTR("WS2805 RGBCCT")},
|
||||
{TYPE_SM16825, "D", PSTR("SM16825 RGBCCT")},
|
||||
{TYPE_WS2812_1CH_X3, "D", PSTR("WS2811 White")},
|
||||
//{TYPE_WS2812_2CH_X3, "D", PSTR("WS281x CCT")}, // not implemented
|
||||
{TYPE_WS2812_WWA, "D", PSTR("WS281x WWA")}, // amber ignored
|
||||
{TYPE_WS2801, "2P", PSTR("WS2801")},
|
||||
{TYPE_APA102, "2P", PSTR("APA102")},
|
||||
{TYPE_LPD8806, "2P", PSTR("LPD8806")},
|
||||
{TYPE_LPD6803, "2P", PSTR("LPD6803")},
|
||||
{TYPE_P9813, "2P", PSTR("PP9813")},
|
||||
{TYPE_WS2801, "2P", PSTR("WS2801 RGB")},
|
||||
{TYPE_APA102, "2P", PSTR("APA102 RGB")},
|
||||
{TYPE_LPD8806, "2P", PSTR("LPD8806 RGB")},
|
||||
{TYPE_LPD6803, "2P", PSTR("LPD6803 RGB")},
|
||||
{TYPE_P9813, "2P", PSTR("P9813 RGB")},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -453,8 +452,13 @@ BusPwm::BusPwm(const BusConfig &bc)
|
||||
pinMode(_pins[i], OUTPUT);
|
||||
#else
|
||||
unsigned channel = _ledcStart + i;
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit
|
||||
ledcAttachPin(_pins[i], channel);
|
||||
#else
|
||||
ledcAttachChannel(_pins[i], _frequency, _depth - (dithering*4), channel);
|
||||
// LEDC timer reset credit @dedehai
|
||||
#endif
|
||||
// LEDC timer reset credit @dedehai
|
||||
uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup()
|
||||
ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift)
|
||||
@@ -586,10 +590,23 @@ void BusPwm::show() {
|
||||
unsigned ch = channel%8; // group channel
|
||||
// directly write to LEDC struct as there is no HAL exposed function for dithering
|
||||
// duty has 20 bit resolution with 4 fractional bits (24 bits in total)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32C61) || defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
// TODO: we need a full rewrite of the "analog LEDs" driver!
|
||||
// see https://github.com/wled/WLED/pull/5048#discussion_r2794185845
|
||||
|
||||
// the .duty_init.duty member seems to only affect fade operations, and its necessary to also trigger an update with
|
||||
// LEDC.channel_group[gr].channel[ch].conf0.para_up = 1;
|
||||
// --> research latest (V5.5.x) esp-idf documentation on how to set the duty cycle registers (by API calls?).
|
||||
// https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32c5/api-reference/peripherals/ledc.html#_CPPv424ledc_set_duty_and_update11ledc_mode_t14ledc_channel_t8uint32_t8uint32_t
|
||||
// LEDC.channel_group[gr].channel[ch].duty_init.duty = duty << ((!dithering)*4); // C5 LEDC struct uses duty_init, but requires additional steps to activate
|
||||
// TODO: find out if / how dithering support can be implemented on P4
|
||||
ledc_set_duty_and_update((ledc_mode_t)gr, (ledc_channel_t)ch, duty >> bitShift, hPoint >> bitShift);
|
||||
#else
|
||||
LEDC.channel_group[gr].channel[ch].duty.duty = duty << ((!dithering)*4); // lowest 4 bits are used for dithering, shift by 4 bits if not using dithering
|
||||
LEDC.channel_group[gr].channel[ch].hpoint.hpoint = hPoint >> bitShift; // hPoint is at _depth resolution (needs shifting if dithering)
|
||||
ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch);
|
||||
#endif
|
||||
#endif // ESP32C5
|
||||
#endif // 8266
|
||||
|
||||
if (!_reversed) hPoint += duty;
|
||||
hPoint += deadTime; // offset to cascade the signals
|
||||
@@ -611,7 +628,7 @@ std::vector<LEDType> BusPwm::getLEDTypes() {
|
||||
{TYPE_ANALOG_2CH, "AA", PSTR("PWM CCT")},
|
||||
{TYPE_ANALOG_3CH, "AAA", PSTR("PWM RGB")},
|
||||
{TYPE_ANALOG_4CH, "AAAA", PSTR("PWM RGBW")},
|
||||
{TYPE_ANALOG_5CH, "AAAAA", PSTR("PWM RGB+CCT")},
|
||||
{TYPE_ANALOG_5CH, "AAAAA", PSTR("PWM RGBCCT")},
|
||||
//{TYPE_ANALOG_6CH, "AAAAAA", PSTR("PWM RGB+DCCT")}, // unimplementable ATM
|
||||
};
|
||||
}
|
||||
@@ -624,7 +641,11 @@ void BusPwm::deallocatePins() {
|
||||
#ifdef ESP8266
|
||||
digitalWrite(_pins[i], LOW); //turn off PWM interrupt
|
||||
#else
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
if (_ledcStart < WLED_MAX_ANALOG_CHANNELS) ledcDetachPin(_pins[i]);
|
||||
#else
|
||||
if (_ledcStart < WLED_MAX_ANALOG_CHANNELS) ledcDetach(_pins[i]);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -745,7 +766,7 @@ size_t BusNetwork::getPins(uint8_t* pinArray) const {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
void BusNetwork::resolveHostname() {
|
||||
static std::shared_ptr<AsyncDNS> DNSlookup; // TODO: make this dynamic? requires to handle the callback properly
|
||||
if (Network.isConnected()) {
|
||||
if (WLEDNetwork.isConnected()) {
|
||||
IPAddress clnt;
|
||||
if (strlen(cmDNS) > 0) {
|
||||
clnt = MDNS.queryHost(_hostname);
|
||||
@@ -813,6 +834,7 @@ 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
|
||||
@@ -824,8 +846,13 @@ 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)) {
|
||||
DEBUGBUS_PRINTLN(F("WARNING, only single panel can be used of 64 pixel boards due to memory"));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (bc.type == TYPE_HUB75MATRIX_HS) {
|
||||
@@ -835,6 +862,7 @@ 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;
|
||||
@@ -858,6 +886,14 @@ 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)
|
||||
@@ -866,6 +902,12 @@ 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};
|
||||
@@ -982,10 +1024,8 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc.
|
||||
}
|
||||
setBitArray(_ledsDirty, _len, false); // reset dirty bits
|
||||
|
||||
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));
|
||||
}
|
||||
// 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
|
||||
@@ -1078,6 +1118,7 @@ uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const {
|
||||
|
||||
void BusHub75Matrix::setBrightness(uint8_t b) {
|
||||
_bri = b;
|
||||
if (!_valid || !display) return;
|
||||
display->setBrightness(_bri);
|
||||
}
|
||||
|
||||
@@ -1266,6 +1307,10 @@ void BusManager::removeAll() {
|
||||
// since I2S outputs are known only during config of buses, lets just assume RMT is used for digital buses
|
||||
// unused RMT channels should have no effect
|
||||
void BusManager::esp32RMTInvertIdle() {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32C61) || defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
// ESP32-C5/C6/P4 use shared RMT method - idle level inversion not supported
|
||||
return;
|
||||
#else
|
||||
bool idle_out;
|
||||
unsigned rmt = 0;
|
||||
unsigned u = 0;
|
||||
@@ -1283,6 +1328,7 @@ void BusManager::esp32RMTInvertIdle() {
|
||||
rmt_set_idle_level(ch, idle_out, lvl);
|
||||
u++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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
|
||||
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 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);
|
||||
@@ -184,10 +184,9 @@ class Bus {
|
||||
type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW; // network types with white channel
|
||||
}
|
||||
static constexpr bool hasCCT(uint8_t type) {
|
||||
return type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA ||
|
||||
return type == TYPE_WS2812_WWA || type == TYPE_SM16825 ||
|
||||
type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH ||
|
||||
type == TYPE_FW1906 || type == TYPE_WS2805 ||
|
||||
type == TYPE_SM16825;
|
||||
type == TYPE_FW1906 || type == TYPE_WS2805;
|
||||
}
|
||||
static constexpr bool isTypeValid(uint8_t type) { return (type > 15 && type < 128); }
|
||||
static constexpr bool isDigital(uint8_t type) { return (type >= TYPE_DIGITAL_MIN && type <= TYPE_DIGITAL_MAX) || is2Pin(type); }
|
||||
|
||||
+33
-25
@@ -5,6 +5,13 @@
|
||||
//#define NPB_CONF_4STEP_CADENCE
|
||||
#include "NeoPixelBus.h"
|
||||
|
||||
#include "wled_boards.h" // pull in board-specific capability defines
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C5
|
||||
// see https://github.com/wled/WLED/pull/5048#issuecomment-3937888182
|
||||
#warning "second output on -C5 may fail, until NPB channel handling is fixed"
|
||||
#endif
|
||||
|
||||
//Hardware SPI Pins
|
||||
#define P_8266_HS_MOSI 13
|
||||
#define P_8266_HS_CLK 14
|
||||
@@ -50,7 +57,7 @@
|
||||
#define I_8266_U1_UCS_4 26
|
||||
#define I_8266_DM_UCS_4 27
|
||||
#define I_8266_BB_UCS_4 28
|
||||
//FW1906 GRBCW
|
||||
//FW1906 GRBCCT
|
||||
#define I_8266_U0_FW6_5 29
|
||||
#define I_8266_U1_FW6_5 30
|
||||
#define I_8266_DM_FW6_5 31
|
||||
@@ -60,7 +67,7 @@
|
||||
#define I_8266_U1_APA106_3 34
|
||||
#define I_8266_DM_APA106_3 35
|
||||
#define I_8266_BB_APA106_3 36
|
||||
//WS2805 (RGBCW)
|
||||
//WS2805 (RGBCCT)
|
||||
#define I_8266_U0_2805_5 37
|
||||
#define I_8266_U1_2805_5 38
|
||||
#define I_8266_DM_2805_5 39
|
||||
@@ -70,7 +77,7 @@
|
||||
#define I_8266_U1_TM1914_3 42
|
||||
#define I_8266_DM_TM1914_3 43
|
||||
#define I_8266_BB_TM1914_3 44
|
||||
//SM16825 (RGBCW)
|
||||
//SM16825 (RGBCCT)
|
||||
#define I_8266_U0_SM16825_5 45
|
||||
#define I_8266_U1_SM16825_5 46
|
||||
#define I_8266_DM_SM16825_5 47
|
||||
@@ -98,19 +105,19 @@
|
||||
//UCS8904 (RGBW)
|
||||
#define I_32_RN_UCS_4 25
|
||||
#define I_32_I2_UCS_4 26
|
||||
//FW1906 GRBCW
|
||||
//FW1906 GRBCCT 6 color channels
|
||||
#define I_32_RN_FW6_5 29
|
||||
#define I_32_I2_FW6_5 30
|
||||
//APA106
|
||||
#define I_32_RN_APA106_3 33
|
||||
#define I_32_I2_APA106_3 34
|
||||
//WS2805 (RGBCW)
|
||||
//WS2805 (RGBCCT)
|
||||
#define I_32_RN_2805_5 37
|
||||
#define I_32_I2_2805_5 38
|
||||
//TM1914 (RGB)
|
||||
#define I_32_RN_TM1914_3 41
|
||||
#define I_32_I2_TM1914_3 42
|
||||
//SM16825 (RGBCW)
|
||||
//SM16825 (RGBCCT)
|
||||
#define I_32_RN_SM16825_5 45
|
||||
#define I_32_I2_SM16825_5 46
|
||||
|
||||
@@ -180,12 +187,12 @@
|
||||
#define B_8266_U1_APA106_3 NeoPixelBus<NeoRbgFeature, NeoEsp8266Uart1Apa106Method> //3 chan, esp8266, gpio2
|
||||
#define B_8266_DM_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266DmaApa106Method> //3 chan, esp8266, gpio3
|
||||
#define B_8266_BB_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangApa106Method> //3 chan, esp8266, bb (any pin but 16)
|
||||
//FW1906 GRBCW
|
||||
//FW1906 GRBCCT
|
||||
#define B_8266_U0_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart0Ws2813Method> //esp8266, gpio1
|
||||
#define B_8266_U1_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Uart1Ws2813Method> //esp8266, gpio2
|
||||
#define B_8266_DM_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266Dma800KbpsMethod> //esp8266, gpio3
|
||||
#define B_8266_BB_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp8266BitBang800KbpsMethod> //esp8266, bb
|
||||
//WS2805 GRBCW
|
||||
//WS2805 GRBCCT
|
||||
#define B_8266_U0_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart0Ws2805Method> //esp8266, gpio1
|
||||
#define B_8266_U1_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266Uart1Ws2805Method> //esp8266, gpio2
|
||||
#define B_8266_DM_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp8266DmaWs2805Method> //esp8266, gpio3
|
||||
@@ -195,7 +202,7 @@
|
||||
#define B_8266_U1_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266Uart1Tm1914Method>
|
||||
#define B_8266_DM_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266DmaTm1914Method>
|
||||
#define B_8266_BB_TM1914_3 NeoPixelBus<NeoRgbTm1914Feature, NeoEsp8266BitBangTm1914Method>
|
||||
//Sm16825 (RGBWC)
|
||||
//Sm16825 (RGBCCT)
|
||||
#define B_8266_U0_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart0Ws2813Method>
|
||||
#define B_8266_U1_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Uart1Ws2813Method>
|
||||
#define B_8266_DM_SM16825_5 NeoPixelBus<NeoRgbwcSm16825eFeature, NeoEsp8266Dma800KbpsMethod>
|
||||
@@ -231,7 +238,7 @@
|
||||
typedef NeoEsp32I2s0Apa106Method X1Apa106Method;
|
||||
typedef NeoEsp32I2s0Ws2805Method X1Ws2805Method;
|
||||
typedef NeoEsp32I2s0Tm1914Method X1Tm1914Method;
|
||||
#elif !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#elif !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) && !defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
// regular ESP32 will use I2S1
|
||||
typedef NeoEsp32I2s1Ws2812xMethod X1Ws2812xMethod;
|
||||
typedef NeoEsp32I2s1Sk6812Method X1Sk6812Method;
|
||||
@@ -245,7 +252,9 @@
|
||||
#endif
|
||||
|
||||
// RMT driver selection
|
||||
#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv)
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
#define NeoEsp32RmtMethod(x) NeoEsp32RmtX ## x ## Method
|
||||
#elif !defined(WLED_USE_SHARED_RMT) && !defined(__riscv)
|
||||
#include <NeoEsp32RmtHIMethod.h>
|
||||
#define NeoEsp32RmtMethod(x) NeoEsp32RmtHIN ## x ## Method
|
||||
#else
|
||||
@@ -285,11 +294,11 @@
|
||||
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(Apa106)>
|
||||
#define B_32_I2_APA106_3 NeoPixelBus<NeoGrbFeature, X1Apa106Method>
|
||||
#define B_32_IP_APA106_3 NeoPixelBus<NeoGrbFeature, X8Apa106Method> // parallel I2S
|
||||
//FW1906 GRBCW
|
||||
//FW1906 GRBCCT 6 color channels
|
||||
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtMethod(Ws2812x)>
|
||||
#define B_32_I2_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X1800KbpsMethod>
|
||||
#define B_32_IP_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X8800KbpsMethod> // parallel I2S
|
||||
//WS2805 RGBWC
|
||||
//WS2805 RGBCCT
|
||||
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtMethod(Ws2805)>
|
||||
#define B_32_I2_2805_5 NeoPixelBus<NeoGrbwwFeature, X1Ws2805Method>
|
||||
#define B_32_IP_2805_5 NeoPixelBus<NeoGrbwwFeature, X8Ws2805Method> // parallel I2S
|
||||
@@ -297,7 +306,7 @@
|
||||
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtMethod(Tm1914)>
|
||||
#define B_32_I2_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X1Tm1914Method>
|
||||
#define B_32_IP_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X8Tm1914Method> // parallel I2S
|
||||
//Sm16825 (RGBWC)
|
||||
//Sm16825 (RGBCCT)
|
||||
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtMethod(Ws2812x)>
|
||||
#define B_32_I2_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X1Ws2812xMethod>
|
||||
#define B_32_IP_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X8Ws2812xMethod> // parallel I2S
|
||||
@@ -358,6 +367,7 @@ class PolyBus {
|
||||
#ifdef ESP8266
|
||||
dotStar_strip->Begin();
|
||||
#else
|
||||
if (miso == -1) miso = 127; // note: in arduino core, -1 means "default" not "none", passing 127 as the MISO pin is a workaround to prevent SPI.begin() assign the default pin, see #5670
|
||||
if (sck == -1 && mosi == -1) dotStar_strip->Begin();
|
||||
else dotStar_strip->Begin(sck, miso, mosi, ss);
|
||||
#endif
|
||||
@@ -453,7 +463,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: beginTM1914<B_32_RN_TM1914_3*>(busPtr); break;
|
||||
case I_32_RN_SM16825_5: (static_cast<B_32_RN_SM16825_5*>(busPtr))->Begin(); break;
|
||||
// I2S1 bus or parellel buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast<B_32_IP_NEO_3*>(busPtr))->Begin(); else (static_cast<B_32_I2_NEO_3*>(busPtr))->Begin(); break;
|
||||
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->Begin(); else (static_cast<B_32_I2_NEO_4*>(busPtr))->Begin(); break;
|
||||
case I_32_I2_400_3: if (_useParallelI2S) (static_cast<B_32_IP_400_3*>(busPtr))->Begin(); else (static_cast<B_32_I2_400_3*>(busPtr))->Begin(); break;
|
||||
@@ -551,7 +561,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)_rmtChannel++); break;
|
||||
case I_32_RN_SM16825_5: busPtr = new B_32_RN_SM16825_5(len, pins[0], (NeoBusChannel)_rmtChannel++); break;
|
||||
// I2S1 bus or paralell buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: if (_useParallelI2S) busPtr = new B_32_IP_NEO_3(len, pins[0]); else busPtr = new B_32_I2_NEO_3(len, pins[0]); break;
|
||||
case I_32_I2_NEO_4: if (_useParallelI2S) busPtr = new B_32_IP_NEO_4(len, pins[0]); else busPtr = new B_32_I2_NEO_4(len, pins[0]); break;
|
||||
case I_32_I2_400_3: if (_useParallelI2S) busPtr = new B_32_IP_400_3(len, pins[0]); else busPtr = new B_32_I2_400_3(len, pins[0]); break;
|
||||
@@ -650,7 +660,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: (static_cast<B_32_RN_TM1914_3*>(busPtr))->Show(consistent); break;
|
||||
case I_32_RN_SM16825_5: (static_cast<B_32_RN_SM16825_5*>(busPtr))->Show(consistent); break;
|
||||
// I2S1 bus or paralell buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast<B_32_IP_NEO_3*>(busPtr))->Show(consistent); else (static_cast<B_32_I2_NEO_3*>(busPtr))->Show(consistent); break;
|
||||
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->Show(consistent); else (static_cast<B_32_I2_NEO_4*>(busPtr))->Show(consistent); break;
|
||||
case I_32_I2_400_3: if (_useParallelI2S) (static_cast<B_32_IP_400_3*>(busPtr))->Show(consistent); else (static_cast<B_32_I2_400_3*>(busPtr))->Show(consistent); break;
|
||||
@@ -746,7 +756,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: return (static_cast<B_32_RN_TM1914_3*>(busPtr))->CanShow(); break;
|
||||
case I_32_RN_SM16825_5: return (static_cast<B_32_RN_SM16825_5*>(busPtr))->CanShow(); break;
|
||||
// I2S1 bus or paralell buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: if (_useParallelI2S) return (static_cast<B_32_IP_NEO_3*>(busPtr))->CanShow(); else return (static_cast<B_32_I2_NEO_3*>(busPtr))->CanShow(); break;
|
||||
case I_32_I2_NEO_4: if (_useParallelI2S) return (static_cast<B_32_IP_NEO_4*>(busPtr))->CanShow(); else return (static_cast<B_32_I2_NEO_4*>(busPtr))->CanShow(); break;
|
||||
case I_32_I2_400_3: if (_useParallelI2S) return (static_cast<B_32_IP_400_3*>(busPtr))->CanShow(); else return (static_cast<B_32_I2_400_3*>(busPtr))->CanShow(); break;
|
||||
@@ -868,7 +878,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: (static_cast<B_32_RN_TM1914_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
|
||||
case I_32_RN_SM16825_5: (static_cast<B_32_RN_SM16825_5*>(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break;
|
||||
// I2S1 bus or paralell buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast<B_32_IP_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_NEO_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
|
||||
case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast<B_32_IP_NEO_4*>(busPtr))->SetPixelColor(pix, col); else (static_cast<B_32_I2_NEO_4*>(busPtr))->SetPixelColor(pix, col); break;
|
||||
case I_32_I2_400_3: if (_useParallelI2S) (static_cast<B_32_IP_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast<B_32_I2_400_3*>(busPtr))->SetPixelColor(pix, RgbColor(col)); break;
|
||||
@@ -965,7 +975,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: col = (static_cast<B_32_RN_TM1914_3*>(busPtr))->GetPixelColor(pix); break;
|
||||
case I_32_RN_SM16825_5: { Rgbww80Color c = (static_cast<B_32_RN_SM16825_5*>(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W
|
||||
// I2S1 bus or paralell buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: col = (_useParallelI2S) ? (static_cast<B_32_IP_NEO_3*>(busPtr))->GetPixelColor(pix) : (static_cast<B_32_I2_NEO_3*>(busPtr))->GetPixelColor(pix); break;
|
||||
case I_32_I2_NEO_4: col = (_useParallelI2S) ? (static_cast<B_32_IP_NEO_4*>(busPtr))->GetPixelColor(pix) : (static_cast<B_32_I2_NEO_4*>(busPtr))->GetPixelColor(pix); break;
|
||||
case I_32_I2_400_3: col = (_useParallelI2S) ? (static_cast<B_32_IP_400_3*>(busPtr))->GetPixelColor(pix) : (static_cast<B_32_I2_400_3*>(busPtr))->GetPixelColor(pix); break;
|
||||
@@ -1080,7 +1090,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: delete (static_cast<B_32_RN_TM1914_3*>(busPtr)); break;
|
||||
case I_32_RN_SM16825_5: delete (static_cast<B_32_RN_SM16825_5*>(busPtr)); break;
|
||||
// I2S1 bus or paralell buses
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: if (_useParallelI2S) delete (static_cast<B_32_IP_NEO_3*>(busPtr)); else delete (static_cast<B_32_I2_NEO_3*>(busPtr)); break;
|
||||
case I_32_I2_NEO_4: if (_useParallelI2S) delete (static_cast<B_32_IP_NEO_4*>(busPtr)); else delete (static_cast<B_32_I2_NEO_4*>(busPtr)); break;
|
||||
case I_32_I2_400_3: if (_useParallelI2S) delete (static_cast<B_32_IP_400_3*>(busPtr)); else delete (static_cast<B_32_I2_400_3*>(busPtr)); break;
|
||||
@@ -1180,7 +1190,7 @@ class PolyBus {
|
||||
case I_32_RN_TM1914_3: size += (static_cast<B_32_RN_TM1914_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_32_RN_SM16825_5: size += (static_cast<B_32_RN_SM16825_5*>(busPtr))->PixelsSize()*2; break;
|
||||
// I2S1 bus or paralell buses (front + DMA; DMA = front * cadence, aligned to 4 bytes) not: for parallel I2S only the largest bus counts for DMA memory, this is not done correctly here, also assumes 3-step cadence
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S)
|
||||
case I_32_I2_NEO_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_NEO_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_NEO_3*>(busPtr))->PixelsSize()*4; break;
|
||||
case I_32_I2_NEO_4: size += (_useParallelI2S) ? (static_cast<B_32_IP_NEO_4*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_NEO_4*>(busPtr))->PixelsSize()*4; break;
|
||||
case I_32_I2_400_3: size += (_useParallelI2S) ? (static_cast<B_32_IP_400_3*>(busPtr))->PixelsSize()*4 : (static_cast<B_32_I2_400_3*>(busPtr))->PixelsSize()*4; break;
|
||||
@@ -1260,7 +1270,7 @@ class PolyBus {
|
||||
case I_32_RN_2805_5 : size = (size + 2*count)*2; break; // 5 channels
|
||||
case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; // 16bit, 5 channels
|
||||
// I2S bus or paralell I2S buses (1x front, does not include DMA buffer which is front*cadence, a bit(?) more for LCD)
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(WLED_HAS_PARALLEL_I2S) || defined(CONFIG_IDF_TARGET_ESP32)
|
||||
case I_32_I2_NEO_3 : // fallthrough
|
||||
case I_32_I2_400_3 : // fallthrough
|
||||
case I_32_I2_TM2_3 : // fallthrough
|
||||
@@ -1315,7 +1325,6 @@ class PolyBus {
|
||||
if (offset > 3) offset = 3;
|
||||
switch (busType) {
|
||||
case TYPE_WS2812_1CH_X3:
|
||||
case TYPE_WS2812_2CH_X3:
|
||||
case TYPE_WS2812_RGB:
|
||||
case TYPE_WS2812_WWA:
|
||||
t = I_8266_U0_NEO_3 + offset; break;
|
||||
@@ -1358,7 +1367,6 @@ class PolyBus {
|
||||
// Now determine actual bus type with the chosen offset
|
||||
switch (busType) {
|
||||
case TYPE_WS2812_1CH_X3:
|
||||
case TYPE_WS2812_2CH_X3:
|
||||
case TYPE_WS2812_RGB:
|
||||
case TYPE_WS2812_WWA:
|
||||
t = I_32_RN_NEO_3 + offset; break;
|
||||
|
||||
+3
-1
@@ -109,7 +109,7 @@ bool isButtonPressed(uint8_t b)
|
||||
break;
|
||||
case BTN_TYPE_TOUCH:
|
||||
case BTN_TYPE_TOUCH_SWITCH:
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32C61) && !defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
#ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt)
|
||||
if (touchInterruptGetLastStatus(pin)) return true;
|
||||
#else
|
||||
@@ -396,7 +396,9 @@ void handleOnOff(bool forceOff)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt)
|
||||
void IRAM_ATTR touchButtonISR()
|
||||
{
|
||||
// used for ESP32 S2 and S3: nothing to do, ISR is just used to update registers of HAL driver
|
||||
}
|
||||
#endif
|
||||
+33
-4
@@ -30,8 +30,10 @@ 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
|
||||
return (sumPinsRequired(types, numTypes) > numPins) ? false :
|
||||
(numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0;
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +49,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")]; // 2010020
|
||||
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
|
||||
|
||||
JsonObject id = doc["id"];
|
||||
getStringFromJson(cmDNS, id[F("mdns")], 33);
|
||||
@@ -157,9 +159,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
noWifiSleep = !(wifi[F("sleep")] | !noWifiSleep); // inverted
|
||||
//noWifiSleep = !noWifiSleep;
|
||||
CJSON(force802_3g, wifi[F("phy")]); //force phy mode g?
|
||||
#ifdef SOC_WIFI_SUPPORT_5G
|
||||
CJSON(wifiBandMode, wifi[F("band")]);
|
||||
if (wifiBandMode < WIFI_BAND_MODE_2G_ONLY || wifiBandMode > WIFI_BAND_MODE_AUTO) wifiBandMode = WIFI_BAND_MODE_AUTO;
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
CJSON(txPower, wifi[F("txpwr")]);
|
||||
txPower = min(max((int)txPower, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_19_5dBm);
|
||||
#if defined(ARDUINO_ARCH_ESP32) && (ESP_IDF_VERSION_MAJOR > 4)
|
||||
txPower = min(max((int)txPower, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_21dBm); // V5 allows WIFI_POWER_21dBm = 84 ... WIFI_POWER_MINUS_1dBm = -4
|
||||
#else
|
||||
txPower = min(max((int)txPower, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_19_5dBm);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
JsonObject hw = doc[F("hw")];
|
||||
@@ -659,6 +669,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
|
||||
JsonObject if_ntp = interfaces[F("ntp")];
|
||||
CJSON(ntpEnabled, if_ntp["en"]);
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C5 // ToDO: esp32-c5 crashes on NTP requests, see https://github.com/wled/WLED/pull/5048/changes#r3003182550
|
||||
if (ntpEnabled) { DEBUG_PRINTLN("NTP disabled on -C5, as it leads to crashes"); }
|
||||
// assert failed: udp_new_ip_type /IDF/components/lwip/lwip/src/core/udp.c:1278 (Required to lock TCPIP core functionality!)
|
||||
ntpEnabled = false; // --> disable NTP support, until the crash is resolved
|
||||
#warning "enabling NTP lead to crashes on -C5. NTP disabled"
|
||||
#endif
|
||||
getStringFromJson(ntpServerName, if_ntp[F("host")], 33); // "1.wled.pool.ntp.org"
|
||||
CJSON(currentTimezone, if_ntp[F("tz")]);
|
||||
CJSON(utcOffsetSecs, if_ntp[F("offset")]);
|
||||
@@ -693,8 +709,18 @@ 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;
|
||||
@@ -897,6 +923,9 @@ void serializeConfig(JsonObject root) {
|
||||
JsonObject wifi = root.createNestedObject(F("wifi"));
|
||||
wifi[F("sleep")] = !noWifiSleep;
|
||||
wifi[F("phy")] = force802_3g;
|
||||
#ifdef SOC_WIFI_SUPPORT_5G
|
||||
wifi[F("band")] = wifiBandMode;
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
wifi[F("txpwr")] = txPower;
|
||||
#endif
|
||||
|
||||
+13
-1
@@ -269,7 +269,10 @@ void loadCustomPalettes() {
|
||||
char fileName[32];
|
||||
sprintf_P(fileName, PSTR("/palette%d.json"), index);
|
||||
if (WLED_FS.exists(fileName)) {
|
||||
emptyPaletteGap = 0; // reset gap counter if file exists
|
||||
// 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
|
||||
DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName);
|
||||
if (readObjectFromFile(fileName, nullptr, &pDoc)) {
|
||||
JsonArray pal = pDoc[F("palette")];
|
||||
@@ -309,6 +312,15 @@ 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;
|
||||
|
||||
+17
-1
@@ -61,9 +61,20 @@ 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;
|
||||
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); }
|
||||
extern std::vector<UsermodPalette> usermodPalettes;
|
||||
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodPalettes.size() + customPalettes.size(); }
|
||||
|
||||
void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb);
|
||||
void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb);
|
||||
@@ -187,6 +198,11 @@ 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) {
|
||||
|
||||
+62
-19
@@ -10,8 +10,16 @@ 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 (255 - FIXED_PALETTE_COUNT) // allow up to 255 total palettes, user is warned about stability issues when adding more than 10
|
||||
#define WLED_MAX_CUSTOM_PALETTES (WLED_CUSTOM_PALETTE_ID_BASE - FIXED_PALETTE_COUNT + 1) // 129 slots (IDs 72-200)
|
||||
#else
|
||||
#define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10
|
||||
#endif
|
||||
@@ -65,8 +73,18 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
|
||||
#if !defined(LEDC_CHANNEL_MAX) || !defined(LEDC_SPEED_MODE_MAX)
|
||||
#include "driver/ledc.h" // needed for analog/LEDC channel counts
|
||||
#endif
|
||||
#define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM
|
||||
|
||||
// define -> constexpr to avoid preprocessor errors and enum arithmetic warnings from newer compilers
|
||||
#ifdef WLED_MAX_ANALOG_CHANNELS
|
||||
#undef WLED_MAX_ANALOG_CHANNELS // avoid clash between macro name and constexpr constant
|
||||
#endif
|
||||
constexpr size_t WLED_MAX_ANALOG_CHANNELS = static_cast<size_t>(LEDC_CHANNEL_MAX) * static_cast<size_t>(LEDC_SPEED_MODE_MAX);
|
||||
|
||||
// ToDO: check if the complete logic below can be replaced by SOC_CAPS_.. macros
|
||||
// SOC_I2S_NUM, SOC_RMT_CHANNELS_PER_GROUP, etc
|
||||
// see https://github.com/wled/WLED/pull/5048#issuecomment-3868678248
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#define WLED_MAX_RMT_CHANNELS 2 // ESP32-C3 has 2 RMT output channels
|
||||
#define WLED_MAX_I2S_CHANNELS 0 // I2S not supported by NPB
|
||||
//#define WLED_MAX_ANALOG_CHANNELS 6
|
||||
@@ -82,20 +100,32 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
|
||||
//#define WLED_MAX_ANALOG_CHANNELS 8
|
||||
#define WLED_PLATFORM_ID 3 // used in UI to distinguish ESP type in UI, needs a proper fix!
|
||||
#else
|
||||
#define WLED_MAX_RMT_CHANNELS 8 // ESP32 has 8 RMT output channels
|
||||
#define WLED_MAX_I2S_CHANNELS 8 // I2S parallel output supported by NPB
|
||||
//#define WLED_MAX_ANALOG_CHANNELS 16
|
||||
#define WLED_PLATFORM_ID 4 // used in UI to distinguish ESP type in UI, needs a proper fix!
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32) // classic esp32
|
||||
#define WLED_MAX_RMT_CHANNELS 8 // ESP32 has 8 RMT output channels
|
||||
#define WLED_MAX_I2S_CHANNELS 8 // I2S parallel output supported by NPB
|
||||
//#define WLED_MAX_ANALOG_CHANNELS 16
|
||||
#define WLED_PLATFORM_ID 4 // used in UI to distinguish ESP type in UI, needs a proper fix!
|
||||
#else // all other risc-v based boards: same as C3
|
||||
#define WLED_MAX_RMT_CHANNELS 2 // ESP32-C3 has 2 RMT output channels
|
||||
#define WLED_MAX_I2S_CHANNELS 0 // I2S not supported by NPB
|
||||
//#define WLED_MAX_ANALOG_CHANNELS 6
|
||||
#define WLED_PLATFORM_ID 1 // used in UI to distinguish ESP types - falls back to "C3" until we have a proper fix!
|
||||
#endif
|
||||
#endif
|
||||
#define WLED_MAX_TIMERS 64 // maximum number of timers
|
||||
#define WLED_MAX_DIGITAL_CHANNELS (WLED_MAX_RMT_CHANNELS + WLED_MAX_I2S_CHANNELS)
|
||||
#ifndef WLED_MAX_DIGITAL_CHANNELS
|
||||
#define WLED_MAX_DIGITAL_CHANNELS (WLED_MAX_RMT_CHANNELS + WLED_MAX_I2S_CHANNELS)
|
||||
#else
|
||||
#warning "buildenv overrides WLED_MAX_DIGITAL_CHANNELS - please check that the value is correct"
|
||||
#endif
|
||||
#endif
|
||||
// WLED_MAX_BUSSES was used to define the size of busses[] array which is no longer needed
|
||||
// instead it will help determine max number of buses that can be defined at compile time
|
||||
#ifdef WLED_MAX_BUSSES
|
||||
#undef WLED_MAX_BUSSES
|
||||
#endif
|
||||
#define WLED_MAX_BUSSES (WLED_MAX_DIGITAL_CHANNELS+WLED_MAX_ANALOG_CHANNELS)
|
||||
// define -> constexpr to align with definition of WLED_MAX_ANALOG_CHANNELS
|
||||
constexpr size_t WLED_MAX_BUSSES = WLED_MAX_DIGITAL_CHANNELS + WLED_MAX_ANALOG_CHANNELS;
|
||||
static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
|
||||
// Maximum number of pins per output. 5 for RGBCCT analog LEDs.
|
||||
@@ -157,9 +187,14 @@ 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
|
||||
#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
|
||||
// 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
|
||||
#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"
|
||||
@@ -306,7 +341,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#define TYPE_DIGITAL_MIN 16 // first usable digital type
|
||||
#define TYPE_WS2812_1CH 18 //white-only chips (1 channel per IC) (unused)
|
||||
#define TYPE_WS2812_1CH_X3 19 //white-only chips (3 channels per IC)
|
||||
#define TYPE_WS2812_2CH_X3 20 //CCT chips (1st IC controls WW + CW of 1st zone and CW of 2nd zone, 2nd IC controls WW of 2nd zone and WW + CW of 3rd zone)
|
||||
//#define TYPE_WS2812_2CH_X3 20 // use FW1906
|
||||
#define TYPE_WS2812_WWA 21 //amber + warm + cold white
|
||||
#define TYPE_WS2812_RGB 22
|
||||
#define TYPE_GS8608 23 //same driver as WS2812, but will require signal 2x per second (else displays test pattern)
|
||||
@@ -382,7 +417,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 14
|
||||
#define WLED_NUM_ETH_TYPES 16
|
||||
|
||||
|
||||
#define WLED_ETH_NONE 0
|
||||
@@ -399,6 +434,9 @@ 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
|
||||
@@ -456,6 +494,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
|
||||
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
|
||||
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
|
||||
#define ERR_REBOOT_NEEDED 98 // reboot needed after changing hardware setting
|
||||
#define ERR_POWEROFF_NEEDED 99 // power-cycle needed after changing hardware setting
|
||||
|
||||
// JSON buffer lock owners
|
||||
#define JSON_LOCK_UNKNOWN 255
|
||||
@@ -481,6 +521,7 @@ 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
|
||||
@@ -517,8 +558,10 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#define MAX_LEDS 1536 //can't rely on memory limit to limit this to 1536 LEDs
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#define MAX_LEDS 2048 //due to memory constraints S2
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32C61)
|
||||
#define MAX_LEDS 4096
|
||||
#else
|
||||
#define MAX_LEDS 16384
|
||||
#define MAX_LEDS 16384 // classic esp32, S3 and P4 can take more
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -527,15 +570,15 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#ifdef ESP8266
|
||||
#define MAX_LED_MEMORY (8*1024)
|
||||
#else
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C61)
|
||||
#ifndef BOARD_HAS_PSRAM
|
||||
#define MAX_LED_MEMORY (28*1024) // S2 has ~170k of free heap after boot, using 28k is the absolute limit to keep WLED functional
|
||||
#else
|
||||
#define MAX_LED_MEMORY (48*1024) // with PSRAM there is more wiggle room as buffers get moved to PSRAM when needed (prioritize functionality over speed)
|
||||
#endif
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
#define MAX_LED_MEMORY (192*1024) // S3 has ~330k of free heap after boot
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
#define MAX_LED_MEMORY (100*1024) // C3 has ~240k of free heap after boot, even with 8000 LEDs configured (2D) there is 30k of contiguous heap left
|
||||
#else
|
||||
#define MAX_LED_MEMORY (85*1024) // ESP32 has ~160k of free heap after boot and an additional 64k of 32bit access memory that is used for pixel buffers
|
||||
@@ -651,7 +694,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#endif
|
||||
|
||||
// Defaults pins, type and counts to configure LED output
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32C61) || defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
#ifdef WLED_ENABLE_DMX
|
||||
#define DEFAULT_LED_PIN 1
|
||||
#warning "Compiling with DMX. The default LED pin has been changed to pin 1."
|
||||
|
||||
+136
-1
@@ -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; // reserved
|
||||
pkt[2] = 0x00; // upper nibble is reserved, lower nibble is sequence number, if set to 0 no sequence checking is done (if enabled)
|
||||
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,3 +222,138 @@ 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;
|
||||
}
|
||||
|
||||
+19
-23
@@ -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,6 +71,8 @@
|
||||
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() {
|
||||
@@ -329,17 +331,7 @@
|
||||
});
|
||||
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");
|
||||
@@ -468,7 +460,11 @@
|
||||
rm.className = 'sml';
|
||||
rm.title = 'Delete palette';
|
||||
rm.innerHTML = '✖';
|
||||
rm.onclick = () => { requestJson({rmcpal:i}); setTimeout(refr, 500); };
|
||||
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
|
||||
};
|
||||
|
||||
const name = isEmpty(p.palette) ? 'Empty slot' : 'Custom' + i;
|
||||
const css = isEmpty(p.palette) ? '#666' : cssArr(p.palette);
|
||||
@@ -592,7 +588,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://dedehai.github.io/cpt_city_selection.json')
|
||||
fetch('https://wled.github.io/wled-web-tools/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) {}
|
||||
@@ -621,21 +617,16 @@
|
||||
|
||||
async function requestJson(cmd)
|
||||
{
|
||||
if (ws && ws.readyState == 1) {
|
||||
if (ws && ws.readyState == 1 && ws.bufferedAmount < 32768) {
|
||||
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) {}
|
||||
}
|
||||
|
||||
if (!window._httpQueue) {
|
||||
window._httpQueue = [];
|
||||
window._httpRun = 0;
|
||||
}
|
||||
if (_httpQueue.length >= 5) {
|
||||
return Promise.resolve(-1); // reject if too many queued requests
|
||||
}
|
||||
|
||||
// HTTP fallback
|
||||
if (_httpQueue.length >= 5) return -1; // queue full; applyLED cancels stale queues before sending
|
||||
return new Promise(resolve => {
|
||||
_httpQueue.push({ cmd, resolve });
|
||||
(async function run() {
|
||||
@@ -650,7 +641,7 @@
|
||||
cache: 'no-store'
|
||||
});
|
||||
} catch (e) {}
|
||||
await new Promise(r => setTimeout(r, 120));
|
||||
await new Promise(r => setTimeout(r, 120)); // delay between requests (go slow, this is the http fallback if WS fails)
|
||||
q.resolve(0);
|
||||
}
|
||||
_httpRun = 0;
|
||||
@@ -662,8 +653,12 @@
|
||||
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
|
||||
@@ -680,6 +675,7 @@
|
||||
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 } });
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ p {
|
||||
font-size: 16px;
|
||||
}
|
||||
.fs1 {
|
||||
font-size: 48px;
|
||||
font-size: 32px;
|
||||
}
|
||||
.fs2 {
|
||||
font-size: 28px;
|
||||
|
||||
@@ -9,10 +9,24 @@
|
||||
<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: 25)</small></h1>
|
||||
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs: 26)</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="" 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>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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
@@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: 'wled122';
|
||||
src:
|
||||
url('fonts/wled122.ttf?yzxblb') format('truetype'),
|
||||
url('fonts/wled122.woff?yzxblb') format('woff'),
|
||||
url('fonts/wled122.svg?yzxblb#wled122') format('svg');
|
||||
url('fonts/wled122.ttf?2tjc6') format('truetype'),
|
||||
url('fonts/wled122.woff?2tjc6') format('woff'),
|
||||
url('fonts/wled122.svg?2tjc6#wled122') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
@@ -24,6 +24,9 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.i-search:before {
|
||||
content: "\e0a1";
|
||||
}
|
||||
.i-pixelforge:before {
|
||||
content: "\e900";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: "WIcons";
|
||||
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=);
|
||||
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);
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
+110
-19
@@ -25,6 +25,7 @@ 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:""}},
|
||||
@@ -660,12 +661,14 @@ 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');
|
||||
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';});
|
||||
gId("filter2D").classList.add('hide'); // hide 2D effects in non-matrix mode
|
||||
} 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) {
|
||||
@@ -718,6 +721,7 @@ ${urows===""?'':'<tr><td colspan=2><hr style="height:1px;border-width:0;color:gr
|
||||
${i.opt&0x100?inforow("Debug","<button class=\"btn btn-xs\" onclick=\"requestJson({'debug':"+(i.opt&0x0080?"false":"true")+"});\"><i class=\"icons "+(i.opt&0x0080?"on":"off")+"\"></i></button>"):''}
|
||||
${inforow("Build",i.vid)}
|
||||
${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")}
|
||||
${i.wifi.band?inforow("WiFi band",i.wifi.band + " (Ch " + i.wifi.channel + ")"):""}
|
||||
${inforow("Uptime",getRuntimeStr(i.uptime))}
|
||||
${inforow("Time",i.time)}
|
||||
${inforow("Free heap",(i.freeheap/1024).toFixed(1)," kB")}
|
||||
@@ -986,21 +990,39 @@ function populatePalettes()
|
||||
);
|
||||
}
|
||||
gId('pallist').innerHTML=html;
|
||||
// append custom palettes (when loading for the 1st time)
|
||||
// append usermod palettes (fixed ID space: 255 down to 201)
|
||||
let li = lastinfo;
|
||||
if (!isEmpty(li) && li.cpalcount) {
|
||||
for (let j = 0; j<li.cpalcount; j++) {
|
||||
if (!isEmpty(li) && li.umpalcount && li.umpalnames) {
|
||||
for (let j = 0; j < li.umpalcount; j++) {
|
||||
let div = d.createElement("div");
|
||||
gId('pallist').appendChild(div);
|
||||
div.outerHTML = generateListItemHtml(
|
||||
'palette',
|
||||
255-j,
|
||||
'~ Custom '+j+' ~',
|
||||
li.umpalnames[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()
|
||||
@@ -1457,7 +1479,9 @@ function readState(s,command=false)
|
||||
|
||||
tr = s.transition;
|
||||
gId('tt').value = tr/10;
|
||||
gId('bs').value = s.bs || 0;
|
||||
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
|
||||
if (tr===0) gId('bsp').classList.add('hide')
|
||||
else gId('bsp').classList.remove('hide')
|
||||
|
||||
@@ -2812,7 +2836,7 @@ function loadPalettesData() {
|
||||
if (lsPalData) {
|
||||
try {
|
||||
var d = JSON.parse(lsPalData);
|
||||
if (d && d.vid == lastinfo.vid) {
|
||||
if (d && d.vid == lastinfo.vid && d.pcount == lastinfo.palcount) {
|
||||
palettesData = d.p;
|
||||
redrawPalPrev();
|
||||
return resolve();
|
||||
@@ -2824,7 +2848,8 @@ function loadPalettesData() {
|
||||
getPalettesData(0, () => {
|
||||
localStorage.setItem("wledPalx", JSON.stringify({
|
||||
p: palettesData,
|
||||
vid: lastinfo.vid
|
||||
vid: lastinfo.vid,
|
||||
pcount: lastinfo.palcount // total palette count, refresh cache if it changes
|
||||
}));
|
||||
redrawPalPrev();
|
||||
setTimeout(resolve, 99); // delay optional
|
||||
@@ -3413,13 +3438,23 @@ 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
|
||||
};
|
||||
|
||||
// Fetch fresh data from /json/info endpoint as requested
|
||||
fetch(getURL('/json/info'), {
|
||||
method: 'get'
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(infoData => {
|
||||
// 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 => {
|
||||
// Map to UpgradeEventRequest structure per OpenAPI spec
|
||||
// Required fields: deviceId, version, previousVersion, releaseName, chip, ledCount, isMatrix, bootloaderSHA256
|
||||
const upgradeData = {
|
||||
@@ -3434,13 +3469,58 @@ 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)
|
||||
};
|
||||
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;
|
||||
|
||||
// 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', {
|
||||
@@ -3471,6 +3551,17 @@ 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,
|
||||
|
||||
@@ -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://dedehai.github.io/pftools.json'; // Change to your actual repo
|
||||
const remoteURL = 'https://wled.github.io/wled-web-tools/pftools.json'; // tools list
|
||||
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 53 */
|
||||
/* which seg is showing image fx (ID 53) */
|
||||
function curImgSeg(){
|
||||
const sel=getId('seg');
|
||||
for(let i=0;i<sel.options.length;i++){
|
||||
@@ -1195,7 +1195,7 @@ async function imgPlay(url,name){
|
||||
on:true,
|
||||
seg: cur!==null && cur!==tgt
|
||||
? [{id:cur,fx:0,n:""},{id:tgt,fx:53,frz:false,sx:128,n:name}]
|
||||
: {id:tgt,fx:53,frz:false,sx:128,n:name}
|
||||
: {id:tgt,fx:53,frz:false,sx:128,ix:0,n:name}
|
||||
};
|
||||
const r=await fetch(getURL('/json/state'),{method:'POST',body:JSON.stringify(j)});
|
||||
const out=await r.json();
|
||||
|
||||
@@ -9,20 +9,21 @@
|
||||
(function loadFiles() {
|
||||
const l = document.createElement('script');
|
||||
l.src = 'common.js';
|
||||
// load style.css then initialize
|
||||
l.onload = () => loadResources(['style.css'], () => {
|
||||
// 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 = () => {
|
||||
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;
|
||||
margin: 0 auto;
|
||||
max-width: 520px;
|
||||
}
|
||||
html {
|
||||
--h: 9vh;
|
||||
@@ -30,7 +31,7 @@
|
||||
button {
|
||||
display: block;
|
||||
border-radius: var(--h);
|
||||
font-size: 6vmin;
|
||||
font-size: clamp(1rem, 6vw, 2rem);
|
||||
height: var(--h);
|
||||
width: calc(100% - 40px);
|
||||
margin: 2vh auto 0;
|
||||
|
||||
+100
-110
@@ -7,6 +7,7 @@
|
||||
<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
|
||||
@@ -38,6 +39,10 @@
|
||||
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 = [];
|
||||
@@ -49,7 +54,7 @@
|
||||
setABL();
|
||||
d.Sf.addEventListener("submit", trySubmit);
|
||||
if (d.um_p[0]==-1) d.um_p.shift();
|
||||
pinDropdowns();
|
||||
fetchPinInfo(pinDropdowns);
|
||||
}); // If we set async false, file is loaded and executed, then next statement is processed
|
||||
if (loc) d.Sf.action = getURL('/settings/leds');
|
||||
}
|
||||
@@ -70,9 +75,10 @@
|
||||
function isS2() { return chipID == 2; }
|
||||
function isS3() { return chipID == 3; }
|
||||
function is32() { return chipID == 4; }
|
||||
|
||||
function pinsOK() {
|
||||
var ok = true;
|
||||
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
|
||||
var nList = Array.from(d.Sf.querySelectorAll("#mLC input[name^=L], #mLC select.pin[name^=L]")); // include both input types (numeric & dropdown)
|
||||
nList.forEach((LC,i)=>{
|
||||
if (!ok) return; // prevent iteration after conflict
|
||||
let nm = LC.name.substring(0,2); // field name : /L./
|
||||
@@ -84,19 +90,28 @@
|
||||
}
|
||||
// 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
|
||||
//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));})
|
||||
if (p.some((e)=>e==parseInt(LC.value))) {
|
||||
alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);
|
||||
LC.value="";
|
||||
if (LC.tagName==="SELECT") LC.value="-1"; else 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.`);
|
||||
LC.value="";
|
||||
if (LC.tagName==="SELECT") LC.value="-1"; else LC.value="";
|
||||
LC.focus();
|
||||
ok = false;
|
||||
return;
|
||||
@@ -107,10 +122,9 @@
|
||||
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[i].value==nList[j].value) {
|
||||
if (nList[j].value!="" && nList[j].value!="-1" && LC.value==nList[j].value) {
|
||||
alert(`Pin conflict between ${LC.name}/${nList[j].name}!`);
|
||||
nList[j].value="";
|
||||
nList[j].focus();
|
||||
if (nList[j].tagName==="SELECT") nList[j].value="-1"; else { nList[j].value=""; nList[j].focus(); }
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
@@ -138,7 +152,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) {alert(`HUB75 error: height >= 64, only single panel allowed`); e.stopPropagation(); return false;}
|
||||
if (h >= 64 && p > 1 && !hasPSRAM) {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?
|
||||
}
|
||||
@@ -303,14 +317,28 @@
|
||||
gId("p0d"+n).innerText = p0d;
|
||||
gId("p1d"+n).innerText = p1d;
|
||||
gId("off"+n).innerText = off;
|
||||
// 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
|
||||
// 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);
|
||||
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="";
|
||||
if (p >= pins) LK.value = (LK.tagName === "SELECT") ? "-1" : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,25 +443,6 @@
|
||||
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
|
||||
@@ -510,6 +519,11 @@
|
||||
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) {
|
||||
@@ -671,11 +685,18 @@ 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 += ` <select name="BE${s}">`
|
||||
c += ` <select name="BE${s}" onchange="btnPinDd('${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>`;
|
||||
@@ -805,101 +826,70 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
}
|
||||
}
|
||||
function pinDropdowns() {
|
||||
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)
|
||||
// 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)
|
||||
i.dataset.val = i.value;
|
||||
});
|
||||
}
|
||||
function pinUpd(e) {
|
||||
// update changed select options across all usermods
|
||||
// update changed select options across all pin selects
|
||||
let oldV = parseInt(e.dataset.val);
|
||||
e.dataset.val = e.value;
|
||||
let txt = e.name;
|
||||
let label = /^L[0-4].$/.test(e.name) ? 'LED' : e.name;
|
||||
let pins = [];
|
||||
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{
|
||||
if (i.value!=="" && i.value>=0 && i.max<255)
|
||||
pins.push(i.value);
|
||||
// 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);
|
||||
});
|
||||
let selects = d.Sf.querySelectorAll("select.pin");
|
||||
for (let sel of selects) {
|
||||
if (sel == e) continue
|
||||
Array.from(sel.options).forEach((i)=>{
|
||||
if (sel == e) continue;
|
||||
Array.from(sel.options).forEach((i) => {
|
||||
if (i.value == sel.dataset.val) return; // skip sel's own selected pin
|
||||
let led = pins.includes(i.value);
|
||||
if (!(i.value==oldV || i.value==e.value || led)) return;
|
||||
if (i.value == -1) {
|
||||
i.text = "unused";
|
||||
return
|
||||
}
|
||||
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) {
|
||||
if (i.value == oldV) { i.disabled = false; }
|
||||
if (i.value == e.value || led) {
|
||||
i.disabled = true;
|
||||
i.text += ` ${led?'LED':txt}`;
|
||||
i.text += ` ${led ? 'LED' : label}`;
|
||||
}
|
||||
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
|
||||
//if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)"; // read only pin note: removed as pin is not shown for outputs
|
||||
});
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
|
||||
@@ -16,44 +16,11 @@
|
||||
var pinsTimer=null, gpioInfo={};
|
||||
function S() {
|
||||
getLoc();
|
||||
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);
|
||||
});
|
||||
fetchPinInfo(()=>{ 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())
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</div>
|
||||
<div class="sec" id="OTA">
|
||||
<h3>Software Update</h3>
|
||||
<button type="button" onclick="U()">Manual OTA Update</button><br>
|
||||
<button type="button" onclick="U()">Update WLED</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">⚠ If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.<br>
|
||||
|
||||
@@ -42,12 +42,30 @@
|
||||
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();}); // 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(); fetchPinInfo(pinDropdowns);}); // 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";}
|
||||
@@ -126,8 +144,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><br>
|
||||
<i>Reboot required to apply changes. </i>
|
||||
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>
|
||||
</div>
|
||||
<div class="sec">
|
||||
<h3>Instance List</h3>
|
||||
@@ -139,7 +157,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>
|
||||
<i>Network DMX input</i><br>
|
||||
<h4>Network DMX input</h4><br>
|
||||
Type:
|
||||
<select name=DI onchange="SP(); adj();">
|
||||
<option value=5568>E1.31 (sACN)</option>
|
||||
@@ -174,12 +192,14 @@ 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">
|
||||
<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/>
|
||||
<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/>
|
||||
DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/>
|
||||
</div>
|
||||
<i class="warn">Reboot required to apply changes.</i>
|
||||
</div>
|
||||
<div id="dmxInputOff">
|
||||
<br><i class="warn">This firmware build does not include DMX Input support. <br></i>
|
||||
</div>
|
||||
@@ -223,7 +243,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>Reboot required to apply changes. </i><a href="https://kno.wled.ge/interfaces/mqtt/" target="_blank">MQTT info</a>
|
||||
<i class="warn">Reboot required to apply changes. </i><a href="https://kno.wled.ge/interfaces/mqtt/" target="_blank">MQTT info</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sec">
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
}
|
||||
}
|
||||
function pMP() { // populateMacroPresets
|
||||
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
||||
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
|
||||
var fields = ['A0','A1','MC','MN'];
|
||||
for (var f of fields) {
|
||||
var inp = gN(f);
|
||||
@@ -219,13 +219,38 @@
|
||||
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</option>' + sortedPresetOptions;
|
||||
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
|
||||
var analogOpts = bAO();
|
||||
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) {
|
||||
rPS(sel, presetOpts, "data-preset");
|
||||
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");
|
||||
}
|
||||
}
|
||||
function Wd()
|
||||
@@ -246,33 +271,73 @@
|
||||
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) {
|
||||
function addRow(i,p,l,d,t) {
|
||||
if (t===undefined) t = 0;
|
||||
var b = String.fromCharCode((i<10?48:55)+i);
|
||||
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
||||
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
|
||||
var typeName = btnTypeName(t);
|
||||
var buttonBlock = document.createElement('div');
|
||||
buttonBlock.className = 'bb';
|
||||
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>
|
||||
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>
|
||||
</div>
|
||||
<div class="ba">
|
||||
<label>Short (on→off)</label>
|
||||
<select name="ML${b}" class="s" required>${presetOpts}</select>
|
||||
<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>
|
||||
<div class="ba">
|
||||
<label>Long (off→on)</label>
|
||||
<select name="MD${b}" class="s" required>${presetOpts}</select>
|
||||
<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>
|
||||
</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");
|
||||
<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);
|
||||
}
|
||||
|
||||
+108
-34
@@ -7,7 +7,6 @@
|
||||
<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
|
||||
@@ -32,7 +31,6 @@
|
||||
})
|
||||
.then(json => {
|
||||
umCfg = json.um;
|
||||
getPins(json);
|
||||
urows="";
|
||||
if (isO(umCfg)) {
|
||||
for (const [k,o] of Object.entries(umCfg)) {
|
||||
@@ -49,12 +47,11 @@
|
||||
d.rsvd = [];
|
||||
d.ro_gpio = [];
|
||||
d.extra = [];
|
||||
d.a_pins = []; // analog pins array
|
||||
}, ()=>{
|
||||
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;
|
||||
//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
|
||||
fetchPinInfo(pinDD); // load pin occupancy, then convert INPUT to SELECT for pins
|
||||
}); // If we set async false, file is loaded and executed, then next statement is processed
|
||||
})
|
||||
.catch((error)=>{
|
||||
@@ -90,28 +87,7 @@
|
||||
}
|
||||
*/
|
||||
}
|
||||
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/
|
||||
@@ -161,15 +137,17 @@
|
||||
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 (d.rsvd.includes(j)) continue;
|
||||
let foundPin = pins.indexOf(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
|
||||
let txt = (j === -1) ? "unused" : `${j}`;
|
||||
if (foundPin >= 0 && j !== v) txt += ` ${pinO[foundPin]=="if"?"global":pinO[foundPin]}`; // already reserved pin
|
||||
if (d.ro_gpio.includes(j)) txt += " (R/O)";
|
||||
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
|
||||
let opt = addO(sel, txt, j);
|
||||
if (j === v) opt.selected = true; // this is "our" pin
|
||||
else if (pins.includes(j)) opt.disabled = true; // someone else's pin
|
||||
else if (used) opt.disabled = true; // someone else's pin
|
||||
}
|
||||
let um = i.name.split(":")[0];
|
||||
d.extra.forEach((o)=>{
|
||||
@@ -204,7 +182,7 @@
|
||||
i.disabled = true;
|
||||
i.text += ` ${txt}`;
|
||||
}
|
||||
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
|
||||
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (input only)"; // read-only pin
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -217,7 +195,11 @@
|
||||
um += ":"+fld;
|
||||
} else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class
|
||||
let arr = d.getElementsByName(um);
|
||||
let idx = arr[0].type==="hidden"?1:0; // ignore hidden field
|
||||
if (!arr || arr.length === 0) {
|
||||
console.log("addDD: No elements found for name:", um);
|
||||
return null; // no elements found
|
||||
}
|
||||
let idx = (arr[0] && arr[0].type==="hidden")?1:0; // ignore hidden field
|
||||
if (arr.length > 1+idx) {
|
||||
// we have array of values (usually pins)
|
||||
for (let i of arr) {
|
||||
@@ -279,6 +261,98 @@
|
||||
e.preventDefault();
|
||||
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
|
||||
}
|
||||
|
||||
// TODO: rename this function, needs to be in sync with the now out of tree mod
|
||||
function aOpt(name,el) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if it's actually a select element with options
|
||||
if (!select.options || !select.options.length) return;
|
||||
|
||||
for (let i=0; i<select.options.length; i++) {
|
||||
let c = select.options[i];
|
||||
let found = false;
|
||||
for (let jj=0; jj<d.a_pins.length; jj++) if (d.a_pins[jj] == c.value) found = true; //value -1 or analog pins
|
||||
if (c.value != -1 && !found) {
|
||||
select.removeChild(c);
|
||||
i--; //decrease i by one because the index has been adjusted
|
||||
}
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-add-remove-options/
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-remove-items-from-a-select-conditionally/
|
||||
}
|
||||
}
|
||||
function rOpt(name,el,txt,val) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if element has childNodes
|
||||
if (!select.childNodes || !select.childNodes.length) return;
|
||||
|
||||
for (let i=0; i<select.childNodes.length; i++) {
|
||||
let c = select.childNodes[i];
|
||||
if (c.value == val) c.text = txt;
|
||||
}
|
||||
}
|
||||
function xOpt(name,el,txt,val) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if element has childNodes
|
||||
if (!select.childNodes || !select.childNodes.length) return;
|
||||
|
||||
for (let i=0; i<select.childNodes.length; i++) {
|
||||
let c = select.childNodes[i];
|
||||
if (c.value == val) c.text += txt;
|
||||
}
|
||||
}
|
||||
function dOpt(name,el,valFrom,valTo) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if it's actually a select element with options
|
||||
if (!select.options || !select.options.length) return;
|
||||
|
||||
for (let i=0; i<select.options.length; i++) {
|
||||
let c = select.options[i];
|
||||
if (c.value >= valFrom && c.value <= valTo) {
|
||||
select.removeChild(c);
|
||||
i--; //decrease i by one because the index has been adjusted
|
||||
}
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-add-remove-options/
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-remove-items-from-a-select-conditionally/
|
||||
}
|
||||
}
|
||||
function dRO(name,el) {
|
||||
// Initialize d.ro_gpio if not already set
|
||||
if (!d.ro_gpio) d.ro_gpio = [];
|
||||
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if it's actually a select element with options
|
||||
if (!select.options || !select.options.length) return;
|
||||
|
||||
// console.log("dRO", name, el, obj, "s", select, d.ro_gpio);
|
||||
for (let i=0; i<select.options.length; i++) {
|
||||
let c = select.options[i];
|
||||
// console.log("dRO option", c, c.value, d.ro_gpio.includes(c.value));
|
||||
for (let j=0; j<d.ro_gpio.length; j++) if (d.ro_gpio[j] == c.value) c.disabled=true; //if (d.ro_gpio.includes(c.value))
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
const option = cE("option");
|
||||
|
||||
option.setAttribute("value", networks[i].ssid);
|
||||
option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`; // [${networks[i].bssid.replaceAll(':','')}]
|
||||
option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm, Ch ${networks[i].channel})`; // [${networks[i].bssid.replaceAll(':','')}]
|
||||
|
||||
if (networks[i].ssid === input.value) {
|
||||
option.setAttribute("selected", "selected");
|
||||
@@ -236,8 +236,10 @@ 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="8">QuinLED-Dig-Octa & T-ETH-POE</option>
|
||||
<option value="4">QuinLED-ESP32</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="10">Serg74-ETH32</option>
|
||||
<option value="5">TwilightLord-ESP32</option>
|
||||
<option value="3">WESP32</option>
|
||||
@@ -276,7 +278,15 @@ Static subnet mask:<br>
|
||||
Disable WiFi sleep: <input type="checkbox" name="WS"><br>
|
||||
<i>Disabling WiFi sleep increases power consumption<br>
|
||||
but can help with connectivity issues and sync.</i><br><br>
|
||||
<div id="tx">Max. TX power: <select name="TX">
|
||||
<div id="bm">WiFi band: <select name="BM">
|
||||
<option value="1">2.4 GHz only</option>
|
||||
<option value="2">5 GHz only</option>
|
||||
<option value="3">Auto (both)</option>
|
||||
</select><br></div>
|
||||
<div id="tx">Max. TX power: <select name="TX"> <!-- V5 framework allows 84 = 21dBm ... -4 = Minus_1dBm -->
|
||||
<option value="84">21 dBm</option>
|
||||
<option value="82">20.5 dBm</option>
|
||||
<option value="80">20 dBm</option>
|
||||
<option value="78">19.5 dBm</option>
|
||||
<option value="76">19 dBm</option>
|
||||
<option value="74">18.5 dBm</option>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user