Compare commits

..

549 Commits

Author SHA1 Message Date
Frank Möhle 03547c344f Merge pull request #5048 from wled/V5-C6
🚀 Add new MCU support (-C6,-C5,-P4) to the ESP-IDF V5 builds
2026-06-23 21:17:21 +02:00
Frank 8d7f8ebad1 cleanup: add some ToDO comments, disable WLED_DEBUG 2026-06-23 21:06:49 +02:00
Frank 5ca3279c65 add ToDO comments for open items 2026-06-23 20:37:42 +02:00
Frank dbeb1d6880 add warning that ethernet on P4 is not supported yet. 2026-06-23 19:53:25 +02:00
Frank 11a958a1e2 remove UTF-8 BOM from Espalexa.h
cleaning
2026-06-23 00:35:16 +02:00
Frank c04a506206 post-merge cleanup
* Remove UTF-8 BOM chars from bus_wrapper.h
* add ESP32-C6 to the LEDC V5 API path
2026-06-23 00:29:13 +02:00
Frank f9ea136070 -P4 does not support esp-now
due to lack of "native" on-chip wifi radio
2026-06-23 00:01:47 +02:00
Frank 227723df0f Merge branch 'V5' into V5-C6 2026-06-22 23:30:48 +02:00
Frank 0609047134 use snprintf for robustness reasons
sprintf can overwrite past the end of buffer.
2026-06-22 23:29:14 +02:00
Frank 6dcffe29a1 Update PWM_fan.cpp
request specific ledc PWM channel in V5 builds
2026-06-22 23:10:44 +02:00
Frank fc1a2d7403 Update ST7789_display.cpp
use WLEDNetwork consistently
2026-06-22 23:03:55 +02:00
Frank 7db593ae79 Update usermod_v2_HttpPullLightControl.cpp
use new SHA1 function names in V5 builds
2026-06-22 22:58:32 +02:00
Frank Möhle 2b950159ba Update wled00/dmx_input.cpp
fix double "to "

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-22 22:49:48 +02:00
Frank 314c4ea546 Merge branch 'V5' into V5-C6 2026-06-22 22:27:55 +02:00
Frank 8893a3d749 Merge branch 'main' into V5 2026-06-22 22:27:00 +02:00
Frank Möhle b03bdb35e1 DHT usermod: only abort compilation when USERMOD_DHT_MQTT and WLED_DISABLE_MQTT 2026-06-22 22:26:21 +02:00
Frank Möhle 67fa2ba096 DHT usermod: only abort compilation when USERMOD_DHT_MQTT and WLED_DISABLE_MQTT 2026-06-22 22:22:37 +02:00
Damian Schneider 6afa4ecee1 Fix brighness not being applied properly in nightlight mode (#5625)
* Fix brighness not being applied properly in nightlight mode
* hacky fix also for color fade, needs proper fix later
2026-06-22 21:28:22 +02:00
Frank Möhle dba9201209 Minor clarifications in AGENTS.md 2026-06-22 20:53:25 +02:00
Evan Coonrod 221964e9a5 Fix ledmap parser reading past end of map array (#5693)
an old-format ledmap.json file (example `{"map":[0,1,...,15],"width":4,"height":4}`) could lead to the ledmap parser scanning past the end of the JSON array. for incomplete ledmaps (not recommended!) this could cause trailing pixels being mapped to physical pixel 0.
2026-06-21 19:30:20 +02:00
Frank Möhle 3a46f44495 remove audioreactive from esp01_1m_full_160 build
* AR needs 7kb program size, bringing us too close to the size limit (99.8% used)
* all other esp01_1m builds don't have audioreactive support either
2026-06-20 16:50:25 +02:00
Will Tatam 360853bc28 Add missing javascript function from WLED-MM 2026-06-20 15:29:25 +01:00
Will Tatam 99c3ea0ba9 Add missing javascript function from WLED-MM 2026-06-20 15:28:58 +01:00
Will Tatam 56b3345f6f Pull new ERR_ values into upstream 2026-06-20 15:28:33 +01:00
Will Tatam 12a4c246e6 Merge branch 'V5' into V5-C6 2026-06-20 13:36:02 +01:00
Will Tatam 4beef70e95 Merge branch 'main' into V5 2026-06-20 12:38:56 +01:00
Will Tatam 2457ea27f0 Merge commit 'bf948fadb' into V5
# Conflicts:
#	platformio.ini
#	wled00/util.cpp
2026-06-20 12:38:23 +01:00
Will Tatam d36701cb72 Merge commit 'd00dbacca' into V5
# Conflicts:
#	platformio.ini
2026-06-20 12:35:23 +01:00
Will Tatam fa5d1e0e36 Merge commit 'dd397fe67' into V5
# Conflicts:
#	wled00/wled.h
2026-06-20 12:17:02 +01:00
Damian Schneider 0fb417ec6f Prevent arduino core from assigning default MISO pin (#5672)
* add invalid dummy pin if miso is undefined to prevent default fallback
2026-06-19 19:25:00 +02:00
Damian Schneider 79cbe606d7 fix color jump if changing mid transition (#5601)
* fix color jump if changing mid transition
2026-06-19 19:22:33 +02:00
Damian Schneider 9f13570091 apply gamma during realtime override (#5666) 2026-06-19 19:20:25 +02:00
Damian Schneider 53a77a10a2 Restore pre 16.0 looks on several FX (#5684)
* restore pre 16.0 looks on several FX
2026-06-19 19:16:51 +02:00
Damian Schneider 765adca942 Fix LED glitches on long strips for C3 (#5688)
* increase wait time, remove unnecessary FPS check
2026-06-19 17:25:30 +02:00
Damian Schneider f763144b3c bugfix in segment deconstructor (#5687)
* bugfix in segment deconstructor
* make stopTransition() nullpointer safe
2026-06-19 17:23:12 +02:00
Damian Schneider ae4b2b3ef2 Rename LED Types: CCT instead of CW (#5612)
* rename CW LED descriptions to more commonly used CCT
* reorder, make naming more consistent
* unify LED type naming
* remove TYPE_WS2812_2CH_X3 as it is not implemented and same as FW1906
2026-06-18 12:43:15 +02:00
Frank 8dfd2cf312 fix example buildenvs in platformio_override.sample.ini
* all buildenvs need WLED_RELEASE_NAME
* 8266 needs either WLED_DISABLE_PARTICLESYSTEM1D or WLED_DISABLE_PARTICLESYSTEM2D
* removed obsolete esp32 "V4" builds
* removed duplicate buildenvs that are 100% the same as standard buildenvs
2026-06-16 17:18:33 +02:00
Frank 19530ce535 fix parse error in platformio_override.sample.ini
added custom_usermods to [8266], so user env can reference it
2026-06-16 16:05:39 +02:00
Benjam Welker 9b1f509d77 Analog button fix (#5659)
* add analog button options

* fix issues with duplicate options for digital vs analog

* dynamic button type options
2026-06-15 21:24:27 +02:00
Damian Schneider 55cee99a74 Update pixel buffer after changing matrix dimensions (#5675)
* update pixel buffer after changing matrix dimensions
2026-06-15 21:08:18 +02:00
Will Tatam 4efb02de58 docs: refresh README to reflect current feature set and project state (#5626)
* docs: refresh README to reflect current feature set and project state

* docs: fix HUB75 support note — available on all ESP32 variants, not just S3

* docs: mention network audio sources in AudioReactive feature description

* docs: remove ESP8266 references — EOL platform, not recommended for new installs

* docs: make PayPal donation link clearly attributed to Aircoookie

* docs: add cross-references to kno.wled.ge docs throughout feature list
2026-06-14 08:17:54 +01:00
Will Tatam a4b14c0191 ci: discover and build custom PlatformIO envs from usermods platformi… (#5649)
* ci: discover and build custom PlatformIO envs from usermods platformio_override.ini.sample files

Adds two new jobs to the Usermod CI workflow to address issue #5648:
- get_custom_build_envs: scans all usermods/*/platformio_override.ini.sample files
  and emits a matrix of {usermod, env} pairs by extracting [env:*] section names
- build_custom: builds each discovered environment by copying the .ini.sample as
  platformio_override.ini and running pio run -e <env>

This allows PRs introducing usermods with custom build environments (such as
pixels_dice_tray) to have those environments validated in CI without committing
platformio_override.ini to the repository.

* ci: consolidate usermod build envs into per-usermod platformio_override.ini.sample files

Move usermod-specific PlatformIO environments out of the root
platformio_override.sample.ini and into dedicated files within each
usermod's own directory, making them discoverable by CI:


* run on push

* Fix AHT10_v2 example

* no d1_mini env

* no d1_mini env

* ci: filter usermod matrix to only build changed usermod directories

Instead of building every usermod with a library.json on each PR, use
git diff to identify which usermods/ subdirectories were actually touched
and intersect that with the known-good library.json list.

This reduces CI time significantly for PRs that only modify one or two
usermods (previously every PR triggered ~40 usermods × 4 chipsets).

Also removes the unnecessary PlatformIO install from get_usermod_envs
(the step only uses shell/jq, not pio) and adds fetch-depth: 0 to
ensure the base branch is available for the diff.

* SN_Photoresistor

* fix(ci): use PR base SHA for diff and guard jobs against push events

github.base_ref is empty on push events, causing 'ambiguous argument
origin/...HEAD'. Fix by:
- Adding github.event_name == 'pull_request' guard to both jobs so
  they never run on push events where pull_request context is absent
- Replacing origin/${{ github.base_ref }}...HEAD with
  ${{ github.event.pull_request.base.sha }} HEAD which uses the
  concrete base commit SHA provided by GitHub directly

* ESP32 builds all V4

* fix: rename/fix platformio_override sample files across usermods

- Rename *.ini (gitignored) and *.sample.ini (wrong extension order) to
  the correct platformio_override.ini.sample convention so CI discovers them
- Fix AHT10_v2: custom_usermods AHT10 → AHT10_v2 (match library.json name)
- Fix INA226_v2: custom_usermods INA226 → INA226_v2 (both envs)
- Fix TTGO-T-Display: replace direct [env:esp32dev] override with a named
  env that extends esp32dev; add note that library.json is absent so
  custom_usermods is not available for this usermod

Affected usermods: AHT10_v2, DHT, INA226_v2, SN_Photoresistor,
TTGO-T-Display, Temperature, four_line_display_ALT, rotary_encoder_ui_ALT

* perf: filter custom build matrix to changed usermods on PRs

On pull_request events, get_custom_build_envs now only scans usermod
directories that changed in the PR (matching the behaviour of the
get_usermod_envs job). On push events (e.g. merging a PR) it still
scans all usermods to validate the full set.

* fix release name

* fix release name

* fix: resolve 8 CI build failures in custom usermod environments

- BME280_v2: fix default_envs typo (usermod_bme280_esp8266_2m →
  usermod_esp8266_2m to match actual [env:] section name)
- DHT: replace extends env:custom32_LEDPIN_16 (not in this fork) with
  env:esp32dev + -D LEDPIN=16; rename env accordingly
- SN_Photoresistor: replace ${common.build_flags_esp8266} (key absent in
  this fork) with ${env:esp8266_2m.build_flags} / lib_deps equivalent
- sht: guard ESP.getChipModel() behind #ifdef ARDUINO_ARCH_ESP32 — method
  does not exist on ESP8266 (fixes custom_esp8266_2m_usermod_sht build)
- EleksTube_IPS: delete platformio_override.ini.sample — library.json is
  disabled so the env cannot build; delete rather than ship a broken sample
- pixels_dice_tray: exclude from build_custom matrix (same as standard
  matrix) — BLE library incompatibility with current IDF causes build failure
- workflow: add PWM_fan / BME68X_v2 to exclusion list in get_custom_build_envs
  for consistency with get_usermod_envs

* Do not add platformio_override.ini.sample just because example was in readme

---------

Co-authored-by: Frank Möhle <91616163+softhack007@users.noreply.github.com>
2026-06-12 08:58:09 +01:00
Frank Möhle d8e3578084 disable fb infer in coderabbit tools list 2026-06-09 14:12:50 +02:00
Frank Möhle 30abc01c0c Remove outdated Wiki link from CONTRIBUTING.md
Removed link to WIKI for submitting a PR. The information in the wiki is outdated, and it contradicts the "no force-push" rule.
2026-06-08 17:42:40 +02:00
Frank Möhle dea6ec0e4c Update links in CONTRIBUTING.md to reference AGENTS.md 2026-06-08 10:29:55 +02:00
Frank Möhle dfd78f4903 Clarify branch maintenance status in instructions
Updated branch descriptions for clarity and maintenance status.
2026-06-08 10:23:57 +02:00
Frank Möhle 5f7910742e Clarify repository language requirement
Emphasize that the repository language is English for all aspects including code, comments, and documentation.
2026-06-05 22:22:46 +02:00
Frank Möhle 3a5026886f Disable auto_apply_labels in coderabbit configuration
has produced too many random-looking labels..
2026-06-05 00:26:20 +02:00
Frank Möhle 1dbceed9dc Update recommendations for synchronization methods
added std::atomic
2026-06-04 23:50:53 +02:00
Frank Möhle be264fb210 Update verification instructions for AI-generated code
limit "existence" check to preprocessor macros etc.
If a global variable or function does not exist, this would lead to a compilation error.
2026-06-04 16:42:17 +02:00
Frank Möhle 7bd387444d Merge pull request #5665 from wled/hub75_gamma_hotfix
HUB75 hotfix: prevents double-applying color corrections (WLED gamma + HUB75 internal CIE correction), by disabling driver-internal color corrections.
2026-06-04 15:03:25 +02:00
Frank 174ff022cb HUB75 hotfix: prevent double-applying gamma correction
disables the driver-internal CIE color correction.

partial solution for #5664
2026-06-04 14:53:20 +02:00
Will Tatam bd0ebf90c0 Merge pull request #5657 from Will-wastelander/main
Adding support for Waveshare ESP32-S3-RGB-Matrix
2026-06-02 19:52:36 +01:00
Frank bf948fadb5 platformio: allow building with espressif framework (instead of tasmota)
* add necessary platform and platform_packages entries (commented out)
* compatibility patch for ADC_ATTEN_DB_11
2026-05-31 22:19:07 +02:00
Damian Schneider 0a1a7fc26f update version name to Kagayaki 2026-05-31 20:36:49 +02:00
Dimitry 659f698144 Refactor ADS1115 usermod loop logic and update documentation (#5655) 2026-06-01 06:29:21 +02:00
Copilot d884a3e692 Pin andelf/nightly-release tospecific commit SHA instead of @main branch (#5386)
The nightly release GitHub Action is pinned from the mutable main branch to a specific immutable commit SHA

we should update the pin when v2 comes out.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
Co-authored-by: Frank Möhle <91616163+softhack007@users.noreply.github.com>
2026-05-30 23:38:04 +02:00
Frank 82c4ce580e move new buildenv into HUB75 section 2026-05-30 22:06:00 +02:00
Frank bb495f7879 buildenv bugfix, better release_name
* un-set previous RELEASE_NAME before setting a new one
* align REALEASE_NAME with naming scheme used by other HUB75 builds
2026-05-30 22:02:24 +02:00
Will 6bd90abac7 Clean up build flags in platformio.ini
Removed unused build unflags.
2026-05-30 12:37:41 -07:00
Will d6482e99bb Remove upload settings from waveshare_esp32s3_32MB_hub75
Removed upload speed and port settings for the waveshare_esp32s3_32MB_hub75 environment.
2026-05-30 12:20:02 -07:00
Will c399d712c9 Remove unused build flags from platformio.ini
Removed flags for fast functions and CIE1931 from build configuration.
2026-05-30 12:19:10 -07:00
Will fd162cee43 Modify build flags in platformio.ini for ESP32-S3
Updated build flags for the waveshare_esp32s3_32MB_hub75 environment, removing some flags and adding new ones.
2026-05-30 08:28:34 -07:00
Frank Möhle 43278a5d0f Update frequency details for usermod loop()
Clarified frequency of calls for usermod loop() based on system load.
2026-05-30 13:04:20 +02:00
Will bd8a16a6f3 Update upload port and build flags in platformio.ini 2026-05-29 21:14:18 -07:00
Will 73898ddf5d Add new environment for Waveshare ESP32-S3 hub75
https://www.waveshare.com/esp32-s3-rgb-matrix.htm
https://docs.waveshare.com/ESP32-S3-RGB-Matrix
2026-05-29 21:00:20 -07:00
Will cdf1f80a81 Add support for Waveshare S3 Matrix Driver board
https://www.waveshare.com/esp32-s3-rgb-matrix.htm
https://docs.waveshare.com/ESP32-S3-RGB-Matrix
2026-05-29 20:55:36 -07:00
Frank Möhle 1825ce7fec Remove duplicate sentence in secure code instructions
Removed redundant text regarding destination capacity and length safety.
2026-05-30 02:35:56 +02:00
Frank Möhle b4747a827b Restructure usermod documentation for AI, and clarify details 2026-05-30 02:26:59 +02:00
Frank e2708be4a9 bugfix
don't modify tpmPayloadFrameSize, its a global value.
2026-05-30 01:21:13 +02:00
Frank 79bfc8b223 harden UDP packet parsing with bounds checks
some offsets are calculated from the incoming packet (untrusted ingress) - make sure that these do not lead to out-of-bounds array indices.
2026-05-30 01:15:07 +02:00
Frank f98e19c965 more robustness for improv - gracefully handle broken input 2026-05-30 00:16:07 +02:00
Frank Möhle 5d00dd0394 Update caveats and pitfalls in cpp.instructions.md
WLED-MM backport: Clarify operator precedence in C/C++.
2026-05-29 20:26:34 +02:00
Frank Möhle beaa5001b6 small update 2026-05-29 19:54:52 +02:00
Frank Möhle 33fe82c207 Modify verification step for numerical accuracy
small clarifications:
* not only applicable to fixed-point
* added sentence to explain why this rule is needed.
2026-05-29 19:53:52 +02:00
Frank Möhle 36fa42d7ea Update auth checks for '/reset' endpoint clarification
Clarified the handling of the '/reset' endpoint in the auth checks section - not security critical because it will not alter any user data.
2026-05-29 18:30:15 +02:00
Jordan W. Cobb 555d0cfbf7 animated staircase: support for inverted logic PIR pins (#5300)
* Added support for inverting logic of PIR pins
* Fixed indentations and removed whitespace
* Added newline to EOF
2026-05-29 16:54:23 +02:00
Frank Möhle 090ab59a9c HUB75: use FM6124 driver for 4-scan panels, remove 64x64 limitation if board has PSRAM (partial solution for #5628) (#5647)
* HUB75: use FM6124 driver for 4-scan panels
* Remove 1-panel limit if the board has PSRAM
2026-05-29 16:48:00 +02:00
kaibae19 c0a90ea470 fix(FX): restore palette wrap in color_wheel() (regression since 0.15.x) (#5646)
color_wheel() historically called color_from_palette() with moving=true, so
under the default palette blend mode (paletteBlend=0, "wrap if moving") the
palette's end->start seam was interpolated. Commit ee9ac947 changed that call
to moving=false, dropping the wrap; effects that scroll a palette (e.g. the
Palette effect with Retro Clown) then shows the unblended seam travelling
visibly along the strip light-by-light.

Restore the original moving=true (per maintainer review). This fixes the seam
for the Palette effect and every other color_wheel() caller under the default
blend mode, without adding any new function parameters.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 16:39:42 +02:00
Benjam Welker 7a37579ef8 fix button label regression (#5636)
* fix button label regression, also add switch notation
2026-05-29 16:32:27 +02:00
Copilot 58bf4c83e9 add secure coding guides for AI reviews (#5572)
Added security review guidelines and a short checklist covering critical security areas including buffer safety, input validation, authentication, secure defaults, and protection against common vulnerabilities. Refined rule wording and priorities to better fit WLED’s technical constraints and realistic deployment model.

The lists are based on the OWASP "top 10" from https://github.com/github/awesome-copilot/blob/main/instructions/security-and-owasp.instructions.md, and on lessons learned from past reviews.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-29 13:59:35 +02:00
Frank 69c2b2d5c2 human-readable wifi events (WLED_DEBUG)
added some missing event names.
2026-05-27 23:55:23 +02:00
Frank 830f5df465 extra debug info for wifi disconnect / teardown / setTxPower
should help with debugging connection problems.
2026-05-27 23:25:43 +02:00
Frank ce249aef64 small addition to anti-slop rules
AI tools often delete code, remove comments, revert recent changes, or modify files that are out-of-scope w.r.t. the PR objectives.
2026-05-27 20:37:34 +02:00
Will Miles ff97ad5fe0 Merge pull request #5644 from DedeHai/overridesample_fixes
change -D USERMOD_x to custom_usermods = xxx
2026-05-26 16:13:51 -04:00
Frank 68b61f0831 adding coderabbit checks for typical AI slop 2026-05-25 22:17:08 +02:00
Frank 6cdf650535 update usermod guidelines, update example with "be nice but not too nice" pattern 2026-05-25 21:38:03 +02:00
Frank 0a0800d98d move some generic AI rules from copilot-instructions.md to AGENTS.md 2026-05-25 21:37:18 +02:00
Frank Möhle 0f34973ee5 Remove 'V5_C6' from the list of base branches
the V5-C6 branch will be merged back into V5 soon.
2026-05-23 23:39:56 +02:00
Will Tatam 72aee0680c Fix revert button #5574 2026-05-23 19:34:16 +01:00
Will Miles 86db5010f8 Merge pull request #5638 from willmmiles/devcontainer-completeness
Update devcontainer spec
2026-05-23 14:28:53 -04:00
Frank Möhle 584f9aba16 Document branch and release structure in AGENTS.md
Added branch and release structure information to AGENTS.md.
2026-05-23 15:00:25 +02:00
Frank Möhle 5aa41c49bd clarify scope of agents.md
Introduction added
2026-05-23 14:49:47 +02:00
Damian Schneider cc26179e56 change -D USERMOD_x to custom_usermods = 2026-05-23 09:30:56 +02:00
Damian Schneider 6ae22c9eb4 Merge pull request #5635 from DedeHai/twinkleFX_fixes
Make twinkle FX look more like it was in the past
2026-05-22 07:53:28 +02:00
Damian Schneider 33e6aee880 add inverse gamma for original looks 2026-05-22 07:52:05 +02:00
Frank Möhle 67f654e99c Update AGENTS.md with ESP32 task synchronization guidelines
* Added guidelines for ESP32 task synchronization
* Clarified that PRs must not create file listed in `.gitignore`
2026-05-21 21:55:28 +02:00
Will Miles decfad483a Update devcontainer spec 2026-05-20 21:40:06 +00:00
Frank 1ec2579f0d coding guides: clatify how to check for PSRAM
psramFound() alone is not definite -> nust also check that PSRAM size > 0 to be sure

https://github.com/wled/WLED/issues/5629#issuecomment-4497230168
2026-05-20 12:38:03 +02:00
Damian Schneider 2d3c8c9920 Merge pull request #5631 from DedeHai/PSRAM_fallback
use DRAM as fallback if PSRAM fails
2026-05-20 08:36:29 +02:00
Damian Schneider 1046de21a9 Make twinkle FX look more like it was in the past 2026-05-20 08:35:11 +02:00
Damian Schneider b7f9f71568 use DRAM as fallback if PSRAM fails 2026-05-19 17:24:09 +02:00
Frank f68f91d2c9 fix esp32s3_4M_none buildenv
tasmota core does not support "dio" mode on S3
2026-05-19 12:43:48 +02:00
Frank Möhle 59898a9b8f Add esp32s3dev_8MB_none to platformio_release.ini.template 2026-05-19 12:20:08 +02:00
Frank Möhle 3d864b178d Add new environments for ESP32-S3 without PSRAM
esp32s3_8MB_none, esp32s3_4M_none
2026-05-19 12:07:34 +02:00
Frank Möhle 6186e32814 Add PSRAM guidelines to AGENTS.md
Added PSRAM guidelines including availability checks, DMA compatibility, fragmentation considerations, and performance recommendations.
2026-05-19 01:23:38 +02:00
Frank Möhle 372577cb3d Update PSRAM guidelines in esp-idf instructions
Clarify PSRAM availability check instructions and usage.
2026-05-19 01:07:03 +02:00
Frank Möhle 0a190271e5 Revise bug report template instructions
Updated bug report template to discourage AI-generated error analyses.
2026-05-16 21:15:27 +02:00
Logan Davis d337c8a313 Fix KIT-VE PHY address from 0 to 1 (#5618) 2026-05-16 07:33:37 +02:00
Frank Möhle 91bcfc14ad Audioreactive bugfix: auto-suspend in all realtime modes, but stay active when "Use main segment only" (#5599)
* fixes a minor problem with newer realtime modes (DDP, TPM2NET and DMX) not causing auto-suspend of sound processing.
* ensure that AR audio stays active when "Use main segment only" (other segments are still controlled locally)
* small update for better compatibility with V5 builds
2026-05-15 20:59:44 +02:00
Will Miles 42f4bcb8a9 Merge pull request #5573 from smitty078/boot-preset-handling
Apply boot preset in setup() so it's guaranteed to be active before the first frame is rendered.
2026-05-14 21:43:09 -04:00
smitty078 8e501d9c91 Do not reassign callMode, revert whitespace error 2026-05-13 16:20:51 -04:00
Damian Schneider 0f30c54ca6 less restrictive DDP header acceptance, add safety checks to all protocols (#5547)
* less restrictive DDP header acceptance, add safety checks to all protocols

* another check for malformed packet

* clamp dmxChannels to MAX_CHANNELS_PER_UNIVERSE

* bugfix in data length calculation

* bugfix in parsePacket(): accept short artned packets
2026-05-13 19:19:05 +02:00
smitty078 741037a242 Fix missing semi-colon and another error. Thanks coderabbit 2026-05-12 23:21:10 -04:00
smitty078 f5ef3f91ce Final conflict resolution 2026-05-12 23:05:52 -04:00
redpandadev a1a22ec7b1 Merge branch 'wled:main' into boot-preset-handling-new 2026-05-12 22:50:54 -04:00
smitty078 6641724616 Consistency check to eliminate unneccesary conflicts 2026-05-12 22:47:56 -04:00
smitty078 722d6e8cdc Numerous changes based on conversation 2026-05-12 22:46:41 -04:00
Frank Möhle 8e94cf5bc4 Merge pull request #5608 from wled/softhack007-patch-1
Add sanity checks for ethernet config table and WLED_ETH_DEFAULT
2026-05-12 13:56:38 +02:00
Frank Möhle e6458922e1 Add sanity checks for ethernet config table and WLED_ETH_DEFAULT
Small helper for integrators by compile-time sanity checks:
* WLED_NUM_ETH_TYPES is aligned with the boards table
* WLED_ETH_DEFAULT is aligned with the boards table

-> can be merged into 17.0.0, 16.0.0, and 0.15.x branch
2026-05-12 13:43:47 +02:00
Joeboyc2 4ba60a606f fix(Fix_unreachable_netservices_v2): v16 compatibility fixes (#5590)
Two issues prevented this usermod from building under v16:

1. library.json is missing "build": {"libArchive": false}, causing the
   build system to reject it with an error before compilation begins.

2. serializeConfig() no longer has a no-argument overload in v16; the
   working replacement is serializeConfigToFS() which creates the JSON
   document internally and writes to the filesystem.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 11:29:01 +02:00
recliq f58187e579 Fixed top-LED update logic in grav* audioreactive effects (#5602)
Fix topLED fallrate not responding to "Rate of fall" slider.
2026-05-11 19:44:39 +02:00
smitty078 8793db356f revert whitespace correction to avoid conflicts with other PRs 2026-05-11 12:56:38 -04:00
redpandadev b185a0216d Merge branch 'wled:main' into boot-preset-handling 2026-05-10 22:16:16 -04:00
Frank Möhle 560eb3763a Update accessibility section in web instructions
Clarify target browser/platform combinations for WLED web UI.
2026-05-10 19:38:19 +02:00
Frank Möhle 6531a0804d Add accessibility guidelines for WLED web UI
Added section on accessibility and interaction for the WLED web UI.
2026-05-10 19:02:28 +02:00
Frank Möhle d5eb95028e coderabbit: Add base branches to auto_review configuration 2026-05-10 14:45:49 +02:00
Will Tatam 37623edfc1 docs: clarify when usermod IDs are required
Add explicit guidance explaining that a unique USERMOD_ID_* is only
needed when a usermod uses inter-mod communication, pin ownership via
pinManager, or needs to be identifiable in JSON info output. Updates
both AGENTS.md and the const.h comment block to reflect this.
2026-05-09 10:26:10 +01:00
Will Tatam 34a35d02a5 Remove usermods from release 2026-05-09 10:26:10 +01:00
smitty078 cfe7bad45d call releaseJSONBufferLock() before returning so that the JSON buffer doesn't get accidentally locked during setup 2026-05-09 00:01:29 -04:00
smitty078 0cd709723a Changes to implementation based on discussion. 2026-05-08 23:02:16 -04:00
Frank Möhle aa6e719129 Disable favicon test case temporarily
Comment out the favicon test case due to failures and potential regression.
2026-05-08 18:47:56 +02:00
Frank 979c724c91 repair CI test jdata.js
* fix two wrong target files for changes
* slightly increase wait times (for slower FS writes on windows)
* added a new testcase for changes to common.js
2026-05-08 18:34:50 +02:00
Will Tatam 665d66f45e Merge pull request #5577 from wled/copilot/esp02-confirm-smallest-bin
Add esp8266_2m_min: minimal ESP02 build for OTA recovery
2026-05-07 20:35:24 +01:00
Will Tatam 367d64f41f no need to build esp8266_2m_min for PRs 2026-05-07 20:32:20 +01:00
copilot-swe-agent[bot] 59c5991d2d Add esp8266_2m_min minimal build env for ESP02 OTA recovery
Agent-Logs-Url: https://github.com/wled/WLED/sessions/9d593605-e26a-4bea-b5dd-913d174d5942

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2026-05-07 17:13:37 +00:00
copilot-swe-agent[bot] 8935997228 Initial plan 2026-05-07 17:08:32 +00:00
redpandadev c8f50e6f5a Add TODO for boot preset and playlist scenarios
Add TODO comments for handling boot presets and playlists.
2026-05-07 10:24:24 -04:00
redpandadev 83065c15ae Initial implementation to apply the same improvements to boot playlist
TODO:

set up logic handling inside handlePresets() and anywhere else required to implement behavior that is described in comments here.
2026-05-07 10:18:22 -04:00
redpandadev 88994e95be Merge branch 'wled:main' into boot-preset-handling 2026-05-07 02:37:15 -04:00
smitty078 f0e182bd29 Fix boot preset live data override handling 2026-05-07 02:33:12 -04:00
Will Tatam c05097351d Merge pull request #5561 from netmindz/auto-ota
Automatically download the right bin for the latest release and install
2026-05-05 17:55:35 +01:00
Will Tatam 57cca612db Change 'Latest release' to 'Latest version' in update.htm 2026-05-05 17:49:07 +01:00
Frank Möhle 8204e1bafe Add guideline for providing verifiable citations
Anti-hallucination rule: Emphasize the importance of providing verifiable citations in analyses or recommendations.
2026-05-05 14:48:55 +02:00
Will Tatam 8039b71b76 Improve update page UX and rename OTA button for consistency
- Release name on update page links directly to the matching bin on download.wled.me
- Badge links to GitHub releases page as 'View latest'
- Auto-update button renamed to 'Auto update'
- Manual upload section gets its own subheading; submit button renamed to 'Upload'
- 'Manual OTA Update' button in security settings renamed to 'Update WLED' to match info menu wording
2026-05-04 12:15:36 +01:00
Will Tatam 1edfc81259 Automatically download the right bin for the latest release and install 2026-05-04 11:21:09 +01:00
Will Tatam e321514e4f Update changelog generator to use tag v16.0.0 2026-05-03 22:56:54 +01:00
BobLoeffler68 b0b9fc58da Fixes issue in Dissolve when a palette has same color pixel as the current background color when Complete is enabled (#5453)
* Fixes issue in Dissolve when a palette has same color pixel as the current background color when Complete mode is enabled

---------

Co-authored-by: Damian Schneider <daedae@gmx.ch>
2026-05-03 21:27:53 +02:00
Will Tatam 360333c022 Merge pull request #5557 from DedeHai/settings_maxwidth
Add max-width to settings overview page
2026-05-03 18:57:57 +01:00
Will Tatam 0b7f77b2f7 Merge pull request #5553 from intermittech/add-quinled-v4-ethernet-profiles
Add Ethernet profiles for QuinLED Dig-Uno/Quad v4 and Dig-Octa 32-8L v4
2026-05-03 18:54:30 +01:00
Will Tatam 05959f94a8 Fix formatting of WLED_ETH_QUINLED_V4 defines 2026-05-03 18:45:13 +01:00
Will Tatam 3e085024a0 Merge pull request #5558 from netmindz/hub75-release
ci: build HUB75 envs in release workflow
2026-05-03 18:42:56 +01:00
Will Tatam bef897f679 Merge pull request #5548 from DedeHai/custom_and_AR_palettefix
Adding usermod palettes and fix UI palette display
2026-05-03 18:42:16 +01:00
Will Tatam dba7fcaaff Merge pull request #5550 from willmmiles/fw-validation-gzip
Validate gzipped firmware (on ESP8266)
2026-05-03 18:40:37 +01:00
Will Tatam 8aa5501571 Fix AudioReactive palette names to avoid duplication with usermod prefix
Since the display format is now "UMName: palName", shorten the palette
names to just "Ratio", "Hue", and "Spectrum" — rendering as
"AudioReactive: Ratio" etc. rather than "AudioReactive: Audio Responsive Ratio".
2026-05-03 14:27:08 +01:00
Will Tatam 41fd877adb Use usermod name prefix for usermod-supplied palette names
Palette display names now follow the format "UMName: palName" (e.g.
"AudioReactive: Audio Responsive Hue") when a usermod supplies a
specific palName, keeping the usermod name as a consistent prefix.
Fallback when palName is nullptr remains "UMName index".

Update AudioReactive palette names to the full descriptive names from
the original WLED-MM port: "Audio Responsive Ratio", "Audio Responsive
Hue", and "Audio Responsive Spectrum".
2026-05-03 14:24:27 +01:00
Will Tatam d66258f53b Merge pull request #5514 from wled/ar-network-only
Fix incomplete support for Audio Source - Network only - 8266 Audio Reactive
2026-05-03 14:04:30 +01:00
Will Tatam aaf76cc4d6 Merge branch 'main' into ar-network-only 2026-05-03 13:48:20 +01:00
Will Tatam 7872c633f3 Add name to usermod palette 2026-05-03 13:40:31 +01:00
Will Tatam ccd40e1f27 move platformio_release.ini.template to .github/ for cleaner project root 2026-05-03 13:02:30 +01:00
Will Tatam d466cab664 Skip GPIO validation for HUB75 types in pin checks and clarify slot usage in getNumberOfPins logic 2026-05-03 12:50:38 +01:00
Will Tatam c74b988c90 Update platformio configurations for 16MB ESP32-S3 MoonHub (HUB75) board 2026-05-03 12:48:31 +01:00
Will Tatam fe14c3104b Add support for HD-WF2 ESP32-S3 (HUB75 without PSRAM) 2026-05-03 12:47:23 +01:00
Dag Liodden 47af19519f Update and improve INA226 usermod (#5483)
* Make INA226 usermod configurable with build args

Update INA226 default check interval to 1 second

Initialize all INA226 bit fields in constructor

_settingEnabled was uninitialized, defaulting to false for static
objects, which silently disabled the usermod. Default it to true
and explicitly initialize all other bit fields.

Add more serial debug output to INA226 usermod

Log configuration parameters and operating mode on init, and
print each measurement reading to serial when WLED_DEBUG is enabled.

Show shunt voltage drop in mV instead of V

With low-value shunt resistors the voltage drop rounds to 0.00V.
Read in mV, rename label to "Shunt Voltage Drop", and update the
MQTT topic and HA discovery accordingly.

Add units to INA226 config UI and support decimal mΩ shunt values

Add appendConfigData() to show units (seconds, mΩ, mA, μs, etc.)
next to each config field. Store ShuntResistor as float milliohms
so values like 4.8 mΩ can be entered directly. Old integer configs
remain backward compatible.

USe uint32_t for checkIntervalMs to avoid overflow

Add basic config validation for INA226

Shift conversion time into 12-bit field. This _actually_ doesn't matter
as we always init the struct later.

Make INA226 enabled by default be a build flag

Revert to backwards compatible MQTT topic

Increae check frequency to 1/s

* Make check interval default configurable with build flag

* Fix old typo in MQTT guard

* Exmplicit floats

* Remove legacy usermod flag

* Replace magic number and repeated code with a lambda
2026-05-03 08:17:39 +02:00
Will Tatam aea18fa87b fix(ui): derive hasPSRAM from psrSz capability, not free bytes
Free PSRAM (info.psram) can be transiently 0 on PSRAM-capable boards,
which would wrongly reject multi-panel HUB75 setups in the LED settings
validation. Prefer info.psrSz (total PSRAM size) and fall back to
info.psram only if psrSz is unavailable.

Suggested in PR #5558 review.
2026-05-02 15:10:24 +01:00
Will Tatam e0b0b2a475 Relax panel limit for devices with PSRAM 2026-05-02 14:59:56 +01:00
Will Tatam 93e3e2ed02 ci: build HUB75 envs in release workflow
Move the four HUB75 envs from platformio_override.sample.ini into
platformio.ini (refactored with a shared [hub75] section and extends=
to deduplicate), and gate them behind a release-only config template
so nightly/PR CI keep building only the standard env matrix.

The release workflow now copies platformio_release.ini.template into
place, which extends default_envs with the HUB75 envs.
2026-05-02 14:00:13 +01:00
Will Tatam d00dbacca6 Merge pull request #5544 from netmindz/platformio-de-duplication
refactor platformio.ini to eliminate copy-paste duplication
2026-05-02 13:23:48 +01:00
Will Tatam 988254bb9f Move ESP8266 platform definitions from [common] to [esp8266]
Per review feedback: platform_wled_default, platform_packages, and the
arduino_core_* aliases are ESP8266-specific and don't belong in [common].
Note: this is a breaking change for custom platformio_override.ini files
that reference ${common.platform_wled_default} or ${common.platform_packages};
those should be updated to use the ${esp8266.*} prefix.
2026-05-02 13:16:23 +01:00
Will Tatam dd397fe673 Merge pull request #5546 from netmindz/node24
ci: opt into Node.js 24 for GitHub Actions runners
2026-05-02 13:08:08 +01:00
Will Tatam 7c57de901d Merge pull request #5545 from netmindz/pr-asset-naming
Name CI artifacts by WLED_RELEASE_NAME instead of PlatformIO env
2026-05-02 13:04:42 +01:00
Damian Schneider a4359fa57f align settings overview page width with rest of config pages 2026-05-02 08:05:38 +02:00
Intermittent Technology / QuinLED.info 0c3ff97e16 Merge branch 'main' into add-quinled-v4-ethernet-profiles 2026-05-01 22:39:50 +02:00
intermittech a871f110f5 On branch add-quinled-v4-ethernet-profiles
Changes to be committed:
	modified:   wled00/data/settings_wifi.htm
	modified:   wled00/network.cpp
2026-05-01 22:36:15 +02:00
Damian Schneider 49f653c621 auto-migration for sunrise/sunset legacy config (#5555)
* add auto-migration for sunris/sunset legacy config
2026-05-01 14:43:15 +02:00
Damian Schneider 64d57f51d5 bump VERSION 2026-05-01 13:58:32 +02:00
Intermittent Technology / QuinLED.info eaffac61c9 Update wled00/const.h
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-01 02:05:55 +02:00
intermittech 0466daa552 Add Ethernet profiles for QuinLED Dig-Uno/Quad v4 and Dig-Octa 32-8L v4 2026-04-30 22:38:15 +02:00
Frank Möhle 88032d5e4b Fix typo
Corrected a typo in the word 'architecture'.
2026-04-29 21:15:13 +02:00
Frank Möhle 9161672b43 Update documentation on docs/ folder usage
Clarified purpose of the `docs/` folder for developer information.
2026-04-29 21:13:55 +02:00
Will Miles 45cdfde61a Validate gzipped firmware on ESP8266
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 22:02:06 -04:00
Damian Schneider 120da32d69 remove unneeded check 2026-04-26 22:42:07 +02:00
Damian Schneider eb60ff91e4 fix typo 2026-04-26 22:35:12 +02:00
Damian Schneider 449069a344 rename removeUsermodPalettes 2026-04-26 22:32:18 +02:00
Damian Schneider d7d4e7dfb4 apply default pal before bounds check 2026-04-26 22:25:47 +02:00
Damian Schneider f16ca9c8ae rename and bugfixes 2026-04-26 22:22:04 +02:00
Will Tatam 5f219e60b8 ci: opt into Node.js 24 for GitHub Actions runners
Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true across all affected workflows
to avoid deprecation warnings ahead of the June 2026 forced migration.
2026-04-26 10:33:06 +01:00
Will Tatam 18a9986d1d Relax artifact name regex to support forks with different project names 2026-04-26 10:30:18 +01:00
Will Tatam 7a71efe916 Name CI artifacts by WLED_RELEASE_NAME instead of PlatformIO env
PR build artifact zips are now named firmware-<RELEASE_NAME> (e.g.
firmware-ESP32.zip) rather than firmware-<pio_env> (e.g.
firmware-esp32dev.zip), making it easier for users to identify the
correct build when testing PRs.
2026-04-26 10:26:53 +01:00
Will Tatam a535c56b4d Fix the build time estimate 2026-04-26 10:17:06 +01:00
Will Tatam e37707d520 refactor platformio.ini to eliminate copy-paste duplication
Centralise platform, platform_packages, build_unflags, lib_deps and
monitor_filters into the per-chipset abstract sections ([esp8266],
[esp32], [esp32s2], [esp32c3], [esp32s3]) and have concrete envs
inherit via extends rather than repeating the same values.

- [esp8266]: add platform, platform_packages, monitor_filters
- [esp32]: add monitor_filters
- [esp32s2]: add monitor_filters
- [esp32c3]: add monitor_filters
- [esp32s3]: add upload_speed, monitor_filters
- esp8266 envs (nodemcuv2, esp8266_2m, esp01_1m_full): extends = esp8266
- esp32dev: extends = esp32
- esp32dev_8M, esp32dev_16M: extends = env:esp32dev
- esp32_eth, usermods: extends = esp32 / env:esp32dev
- esp32_wrover: drop redundant build_unflags, lib_deps (already extends esp32_idf_V4)
- esp32c3dev: drop redundant platform, platform_packages, framework,
  build_unflags, lib_deps, board_build.partitions (already extends esp32c3)
- esp32s3 envs: extends = esp32s3
- lolin_s2_mini: extends = esp32s2

No functional changes. Verified with pio run across all five chipset
families (ESP8266, ESP32, C3, S3, S2).
2026-04-26 10:11:33 +01:00
Damian Schneider fd6f568023 add usermod palettes to fix AR palette indexing 2026-04-26 11:11:14 +02:00
Damian Schneider 02e593da0b move pixelforge json list and cpt city palettes to wled repo (#5541)
also removed commented code
2026-04-26 10:41:06 +02:00
Damian Schneider e8d481720a fix refresh of custom palettes 2026-04-26 09:29:10 +02:00
Damian Schneider f4f4978eeb add placeholder to keep custom palette ID's consistent (#5537) 2026-04-25 20:15:27 +02:00
Damian Schneider 49a63fc15e cleanup of PR #5503: remove unnecessary changes (#5536) 2026-04-25 20:08:37 +02:00
Will Tatam 1bc71f2352 Merge pull request #5503 from RobertoD91/main
fix for serial port on esp32 (#5501)
2026-04-25 17:12:39 +01:00
Will Tatam 70b0aeac95 Merge pull request #5473 from DedeHai/pin_dropdowns
Use drop-downs for pins
2026-04-25 17:08:22 +01:00
Damian Schneider d20dbeb204 fix indentation 2026-04-22 09:56:57 +02:00
Brandon502 69d494ad40 GoL missing return. (#5523) 2026-04-22 06:11:59 +02:00
copilot-swe-agent[bot] 8aa4beeaea Refactor AR init debug logging for network-only sentinel
Agent-Logs-Url: https://github.com/wled/WLED/sessions/85b2a158-f6ba-45cf-88af-0d51b9202cab

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2026-04-21 07:55:17 +00:00
copilot-swe-agent[bot] 012fbdd6e9 Fix network-only dmType handling and debug diagnostics
Agent-Logs-Url: https://github.com/wled/WLED/sessions/85b2a158-f6ba-45cf-88af-0d51b9202cab

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2026-04-21 07:50:57 +00:00
Frank 01076c8237 Merge branch 'V5' into V5-C6 2026-04-20 20:38:30 +02:00
Frank 0fe9e3d3a2 (experimental) two usermods that _do_ compile with V5 2026-04-20 20:08:57 +02:00
Frank cd0b85a8a2 Merge branch 'main' into V5 2026-04-20 19:44:07 +02:00
Damian Schneider 01328a65c1 Fix blending style options list filter for iOS (#5513)
* fix blending style options list filter for iOS
* fix default fallback
2026-04-20 19:06:33 +02:00
Damian Schneider a2d970e155 better packet queuing & pacing for custom palette live preview (#5515)
* better packet queuing / pacing for custom palette live preview
* fix leak
* remove window prefix from variables
2026-04-20 18:58:09 +02:00
Frank Möhle cb666cedc5 Highlight target branch for pull requests
Emphasize the importance of targeting the main branch for PRs.
2026-04-20 15:23:06 +02:00
Frank Möhle 5e0a0a7561 Update rules for PR descriptions and change logs
Added a guideline to prevent agents from overwriting PR descriptions (see https://github.com/orgs/community/discussions/187027).
2026-04-20 14:19:21 +02:00
Frank Möhle 3af2ae5f2f Merge pull request #5516 from kilrah/fix_dmx_ident
Add identifier string for DMX realtime mode
2026-04-19 18:36:25 +02:00
Kilrah 259bf3c0f8 Add identifier string for DMX realtime mode
(cherry picked from commit b4ae421fc3)
2026-04-18 16:47:06 +02:00
Will Tatam 2481aab860 Clean up redundant preprocessor directive in audio_reactive.cpp 2026-04-18 08:55:01 +01:00
Will Tatam ac96ee7568 Add audioreactive custom_usermod to multiple ESP8266 environments in platformio.ini now that we support network only option for 8266 2026-04-18 08:31:36 +01:00
Will Tatam 2b32918db0 Enhance audio reactive logic with improved input validation and new "network receive only" option. 2026-04-18 08:30:50 +01:00
Damian Schneider 5e49a1cffb bugfixes in PS effects
- fix PS Sparkler for large setups: need 32bit random position, 16bit is not enough
- fix PS Fireworks 1D: need to `break` if no particles are available or it can lead to stalls on large setups
- do not use collisions by default PS Fuzzy Noise: its very slow on larger setups
2026-04-17 15:26:57 +02:00
Damian Schneider 910caae463 use fast path in blendSegment to bump up FPS (#5464)
* use fast path in blendSegment to bump up FPS
2026-04-16 07:45:00 +02:00
Will Miles 35ce05a73b Merge pull request #5502 from willmmiles/dynarray-fixes
Dynarray fixes - fixes usermods on C3s
2026-04-15 20:18:07 -04:00
Frank Möhle ba377d7c29 Disable docstrings and unit tests in .coderabbit.yaml
Disable docstring generation and unit tests in CodeRabbit configuration.
2026-04-13 23:12:01 +02:00
Roberto 1cce33e0d3 fix for serial port on esp32 (#5501) 2026-04-13 18:47:18 +02:00
Will Miles 6d7c1d0bbd Improve usermod validation warnings 2026-04-13 01:38:30 -04:00
Damian Schneider 96510614a3 mask out flag bits in DDP type for compatibility 2026-04-12 19:49:45 +02:00
Damian Schneider 1185886eab add search icon back(#5500) 2026-04-12 19:07:16 +02:00
Will Miles a94a3f7003 Fix up usermod validation again
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 11:38:23 -04:00
Will Miles 144f1f13a8 dynarray: Support ESP-IDF 2026-04-12 11:38:23 -04:00
Will Miles 5a7aa8d8a8 validate_modules: Improve performance
Use readelf instead of nm for great speed.
2026-04-12 11:38:23 -04:00
Will Miles 42b91f2122 Fix usermod count for LTO 2026-04-12 11:38:23 -04:00
Will Miles a966c41bc2 dynarray: Directly apply linker fix
Rather than append a linker file, we edit the upstream supplied ones to
add our section to the binaries.  Works better on all platforms.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-12 11:38:23 -04:00
Will Tatam 36ebcb50f7 Merge pull request #5493 from LordMike/patch-2
Refactor wled-tools discover_devices for deduplication and clarity
2026-04-12 08:15:24 +01:00
Frank Möhle 3149a80dcf Refine early return guard clause in error handling
Update error handling guard clause to include time check.

The simpler "!enabled || strip.isUpdating()" pattern regularly causes usermods to stop responding when many LEDs are driven.
2026-04-12 00:21:48 +02:00
Frank Möhle bab31833f0 Update naming conventions in cpp.instructions.md
Add PascalCase convention for enum values.
2026-04-12 00:09:22 +02:00
Frank Möhle 48b0ba0643 Update AI markers and string storage recommendations
Clarified usage of AI markers and added advice on string storage for ESP platforms.
2026-04-11 23:58:30 +02:00
Frank Möhle 0198a614b9 Fix formatting of AI attribution instructions
markdown was wrong
2026-04-11 23:52:19 +02:00
Frank Möhle 14f2ca4223 Use same AI: comment as suggested in other agent instructions
Exact comment text
2026-04-11 23:50:53 +02:00
Will Tatam 7932a249e0 Add AGENTS.md 2026-04-11 11:35:20 +01:00
Will Miles 474e1fe2e5 Serialize fxdata without ArduinoJSON (#5461)
* Serialize fxdata without ArduinoJSON

Eliminates size limit.  Fixes #5458

* Fix off-by-one in fxdata serializer

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Remove declaration of removed function

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Factor out streaming JSON primitives

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 12:26:48 +02:00
Will Tatam 7aec31f039 Upload then add notes 2026-04-11 09:19:50 +01:00
Will Tatam e2dd6303ea Merge pull request #5465 from netmindz/usage-extended
New extended data for usage report
2026-04-11 08:20:09 +01:00
Will Tatam 7242e9d842 remove i2c and spi 2026-04-11 08:19:36 +01:00
Will Tatam ab0cde110f apply code review feedback 2026-04-11 08:16:10 +01:00
Frank Möhle c735feb90a Merge branch 'main' into usage-extended 2026-04-11 08:52:16 +02:00
Frank Möhle a6faf942f1 Revise AI transparency guidelines in CONTRIBUTING.md
Updated transparency guideline for AI-generated code comments.
2026-04-10 21:52:33 +02:00
Will Tatam 370e4c8e2f Merge pull request #5489 from wled/copilot/fix-wled-discovery-issues
Fix Alexa/Hue discovery by correcting SSDP response to match UPnP spec
2026-04-10 18:25:31 +01:00
Frank 8e90242e95 coderabbit tinkering
* disable poem
* disable docstring tool (confusing for users)
* don't review if title contains "WIP"
* auto-assign labels to the PR
2026-04-10 16:55:28 +02:00
Michael Bisbjerg 97dacb6a04 Undo space removals 2026-04-10 16:20:51 +02:00
Frank 061920bb9c adding an (exhaustive) reference guide for esp-idf
this guide is only for reference and reading - right now the content is not liked for AI agents - neither coderabbit nor copilot will automaticially use the file.
2026-04-10 13:56:26 +02:00
Michael Bisbjerg 97704e08de Refactor discover_devices for deduplication and clarity
Refactor discover_devices function to deduplicate device entries and improve readability.
2026-04-10 12:08:42 +02:00
Frank Möhle a51aec6f19 Fix typo in pull request expectations section 2026-04-10 02:49:28 +02:00
Frank Möhle c21ed0714f Fix formatting of platformio.ini modification requirement 2026-04-10 02:44:15 +02:00
Frank Möhle a2dce56809 Update pull request expectations: approval needed to modify platformio.ini
Clarify approval requirement for modifications to platformio.ini
2026-04-10 02:43:40 +02:00
Frank Möhle 036f5199e5 Clean up PR review instructions in .coderabbit.yaml
Removed not working instructions for PR review practices.
2026-04-10 02:32:38 +02:00
Frank Möhle 1914e4ee3b Add reminder for clear pull request descriptions
Emphasized the requirement for clear pull request descriptions.
It's already mentioned 8n the general instructions file, but copilot still was overwriting the iinial description (good) with a delta-checklist on rework (bad).
2026-04-09 18:46:30 +02:00
copilot-swe-agent[bot] 9c072ec921 fix: restore lost inline comments on CACHE-CONTROL and USN lines
Agent-Logs-Url: https://github.com/wled/WLED/sessions/545fc8ab-87c8-4cf5-aa9d-f99915b68beb

Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
2026-04-09 15:10:40 +00:00
Frank Möhle cf4dfe958e Update build instructions with comment preservation reminder
Added a reminder to preserve or update comments after modifying source code files.
2026-04-09 12:27:57 +02:00
copilot-swe-agent[bot] 2cccbd175c docs: improve EUI-64 bridge ID comment per code review
Agent-Logs-Url: https://github.com/wled/WLED/sessions/c3fb311b-1349-48b1-abfd-6a4aca8c7a77

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2026-04-09 05:57:45 +00:00
copilot-swe-agent[bot] cb61c4fe7d fix: improve Alexa/Hue discovery SSDP response compatibility
Fix SSDP M-SEARCH response in Espalexa to match real Philips Hue
bridge behavior and UPnP spec, addressing Alexa discovery failures:

1. Fix ST header capitalization: basic:1 → Basic:1 (UPnP spec)
2. Fix USN format to include full device type URN
3. Fix hue-bridgeid to use uppercase EUI-64 format (16 hex chars)
4. Increase CACHE-CONTROL max-age from 100s to 86400 (24 hours)

Fixes #5488, relates to #4875

Agent-Logs-Url: https://github.com/wled/WLED/sessions/c3fb311b-1349-48b1-abfd-6a4aca8c7a77

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2026-04-09 05:54:20 +00:00
copilot-swe-agent[bot] 4a0a4c632b Initial plan 2026-04-09 05:47:55 +00:00
Frank Möhle 5b2ae15d8d Update ESP32 options in bug issue template 2026-04-09 01:38:30 +02:00
Frank Möhle 44ddc21530 Update bug issue template with new options 2026-04-09 01:34:48 +02:00
Frank Möhle 063592c845 Refine PR review guidelines in .coderabbit.yaml
clarify "limit to top5"
a few typos fixed
2026-04-09 00:53:46 +02:00
Frank Möhle 4955af0a11 Limit suggested improvements to 'Top 5' in instructions
Updated the instructions to limit suggested improvements to 'Top 5' in multiple sections.
2026-04-09 00:43:19 +02:00
Frank Möhle 9df5d056b9 Remove redundancy in feedback guideline
the "summarize good practices" is now moved in .coderabbit.yaml. Removing it here to prevent "logic looping" and distraction.
2026-04-09 00:35:16 +02:00
Frank Möhle 3486e3bcbb Enhance PR review instructions in .coderabbit.yaml
Extend guidelines for PR reviews : including summarizing good practices and prioritizing improvements.
2026-04-09 00:25:09 +02:00
Frank Möhle 91eb69643d Enhance PR review guidelines in .coderabbit.yaml
Added guidelines for PR reviews and improvement suggestions.
2026-04-09 00:08:41 +02:00
Copilot 48b27d12ac docs: add canonical feature-flag list to copilot-instructions to catch misspellings (#5487)
Misspelled WLED_ENABLE_* / WLED_DISABLE_* flags are silently ignored by the preprocessor, causing features to be incorrectly included or excluded with no compiler warning (e.g. WLED_IR_DISABLE instead of WLED_DISABLE_INFRARED).

This PR adds an AI rule that enforces checking of feature flags against a curated list.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
2026-04-08 23:05:12 +02:00
Frank Möhle 78c1051cbb hiding reference to contributing.md from AI
contributing.md makes reference out to this file again
2026-04-08 21:28:29 +02:00
Frank Möhle 2302863386 copilot-instructions rework, new C++ coding guide for AI reviews (#5480)
* comprehensive C++, Web UI, and CI/CD conventions, a condensed setup/build guide, and a new agent-mode build/test workflow with ordered commands, timeouts, validation gates, manual web validation, and troubleshooting steps.

* repository-level AI review/configuration rules, workflow best-practices, safeguards to detect and flag edits to generated web assets, and alignment checks linking AI-facing rules with human-only reference sections.

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-04-08 21:06:48 +02:00
Damian Schneider 7560811480 bugfix in pixelforge (no blur by default), fix glitch in animated gifs for C3 2026-04-07 23:29:31 +02:00
Frank Möhle 12f6fbc005 robustness: Rewind file pointer before writing initial data
if the presets.json file initially contains ``{}`` (valid JSON, could be created by user edit), we need to first rewind the file. Otherwise the result would be ``{}{"0":{}}`` (ivalid JSON)
2026-04-05 22:11:00 +02:00
Damian Schneider 875711d38e allow "unused" as pin option 2026-04-04 18:16:01 +02:00
Damian Schneider 9b9474282d add note for clarification 2026-04-04 18:07:38 +02:00
Damian Schneider 1284aba5d0 add pin dropdowns for DMX input 2026-04-04 17:58:31 +02:00
Damian Schneider fa6fc4f0b6 cosmetics 2026-04-04 16:37:32 +02:00
Damian Schneider f7d9461d1c bugfixes 2026-04-04 16:20:54 +02:00
Damian Schneider 7153fd8fe4 remove default R/O, add comments 2026-04-03 16:51:07 +02:00
Will Tatam 35c1f415c3 Merge branch 'main' into usage-extended 2026-04-03 15:35:08 +01:00
Will Tatam cf03f1a16e Refactor hardware data mapping with structured arrays for LED features, peripherals, and integrations 2026-04-03 15:16:49 +01:00
Damian Schneider 5be517b519 use new common.js pin dropdown functions on UM page 2026-04-03 14:34:34 +02:00
Damian Schneider 630533d125 fix pin checks for LED bus 2026-04-03 11:40:07 +02:00
Damian Schneider a78d39ff1e move pin fetching to common.js, fixes 2026-04-03 10:00:29 +02:00
Damian Schneider 7722faf9d7 revert all changes to usermod page 2026-04-03 09:41:57 +02:00
Frank a67f590ee8 allow override for WLED_MAX_DIGITAL_CHANNELS
only one usecase:

WLED_MAX_DIGITAL_CHANNELS=0 on C5/C6 will prevent very frequent RMT timeout warnings.
2026-03-31 19:52:01 +02:00
Damian Schneider 964c8cee66 move pin info naming to common.js, update dropdown construction (WIP) 2026-03-31 19:35:06 +02:00
Damian Schneider 9d1f36c553 improvements, still not fully working 2026-03-31 19:03:22 +02:00
Damian Schneider ad921f031f initial implementation 2026-03-31 10:23:50 +02:00
Will Tatam 3d2fac0ad4 New extended data for usage report 2026-03-30 20:18:42 +01:00
Frank 65d7528af9 buildenv for P4 with 32MB flash 2026-03-30 16:41:10 +02:00
Frank 77a16f667a P4 buildenv cleanup
* removed unnecessary arduino.memory_type
* removed board_build.f_cpu (set in board manifest.json)
* added example debug flags
2026-03-30 15:58:30 +02:00
Frank a454b31c9d restore lost build_flag for esp32dev_debug 2026-03-30 11:58:10 +02:00
Frank 848f188bf6 restore lost build_flag for esp32dev_debug 2026-03-30 11:57:41 +02:00
Frank ee8b019462 Merge branch 'V5' into V5-C6 2026-03-30 11:52:55 +02:00
Frank 2f402818a5 Merge branch 'main' into V5 2026-03-30 11:52:08 +02:00
Frank 7193790253 upgrade missing metadata workaround 2026-03-30 00:50:15 +02:00
Frank 1f536eed10 Merge branch 'V5' into V5-C6 2026-03-30 00:47:42 +02:00
Frank b36ff623fb Merge branch 'main' into V5 2026-03-30 00:44:31 +02:00
Frank 10066ff608 wifi[band] fallback
show "not connected" instead of "unknown"
2026-03-29 23:03:21 +02:00
Frank 0b57cbcc17 update strapping pin info for -C61 2026-03-29 22:34:28 +02:00
Frank e47b08938f minor improvements
* use WLED_HAVE_RTC_MEMORY_HEAP
* fix typo in comment
2026-03-29 16:04:49 +02:00
Frank b63f2251f0 moving S3 buildenv back where it used to be 2026-03-29 15:44:48 +02:00
Frank f4fbdbf367 Merge branch 'V5' into V5-C6 2026-03-29 15:34:11 +02:00
Frank 795c6893be ensure consistent inheritance of lib_ignore 2026-03-29 14:19:47 +02:00
Frank 97e1b8ee05 typo's in comments 2026-03-29 14:08:44 +02:00
Frank be92bacc87 post-merge fixes 2026-03-29 13:49:38 +02:00
Frank e04a156e78 Merge branch 'main' into V5 2026-03-29 13:30:21 +02:00
Frank b541c1d284 Merge branch 'V5' into V5-C6 2026-03-28 11:07:21 +01:00
Frank Möhle 39335675c1 Clarify instruction on gathering information when unsure 2026-03-28 11:06:49 +01:00
Frank Möhle 8f951f5068 Update copilot instructions for clarity and guidance
Added instructions for handling uncertainty and providing references.
2026-03-28 11:06:49 +01:00
Will Tatam 0f538eb155 do not keep prompting if there was an error, e.g no wifi 2026-03-28 11:06:49 +01:00
Frank 3cb3ff5223 V5 build 2603281 2026-03-28 02:20:31 +01:00
Frank e53500f042 V5 build 2603281 2026-03-28 02:19:39 +01:00
Frank c312f51050 revive device fingerprint in V5
* mbedtls_sha1_... functions have different names
* use CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
* legacy adc calibration API is not supported on new MCUs (-C5, -C6, -C61, -P4)
2026-03-28 01:25:43 +01:00
Will Tatam 7670246a3b do not keep prompting if there was an error, e.g no wifi 2026-03-28 00:22:46 +00:00
Frank e362891b8f revive device fingerprint in V5
* mbedtls_sha1_... functions have different names
* use CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
* legacy adc calibration API is not supported on new MCUs (-C5, -C6, -C61, -P4)
2026-03-28 00:54:18 +01:00
Frank dfba1e97e6 C5: NTP temporarily disabled as it causes crashes
assert failed: udp_new_ip_type udp.c:1278 (Required to lock TCPIP core functionality!)
2026-03-27 21:26:48 +01:00
Frank 61f3c6c379 C5: AP mode needs WIFI_BAND_MODE_AUTO 2026-03-27 21:22:44 +01:00
Frank 9a07784c67 tiny change
debug output
2026-03-27 20:19:03 +01:00
Frank 33ffbd5927 clarify debug output
esp-idf, not esp32
2026-03-27 20:18:24 +01:00
Frank 0629720821 Use WLED_BOARD as the single node-type source of truth.
collapse to one assignment, and stop drifting when new boards might be added.
2026-03-27 19:47:59 +01:00
Frank daed02723f add pin info for new MCU's
based on data sheets, still to be validated on each board
2026-03-27 19:37:20 +01:00
Frank 3bd5f03140 C6 workaround for " WLED_VERSION was not set"
temporary
2026-03-27 19:34:22 +01:00
Frank 96cd51aa77 prelim. pin validation for -C61
not tested, just a first impression from datasheets
2026-03-27 18:25:34 +01:00
Frank 72b47710af C5 buildenv cleanup
no special overrides remaining
c5 build compiles with the standard "CORE3" NeoPixelBus
2026-03-27 16:56:04 +01:00
Frank dd048b8c23 fix for C5 build
ToDO: check if the special library list is still needed on -C5
2026-03-27 15:44:57 +01:00
Frank 40b4d55edf prefer softhack no_fastled (temporary)
temporary replacement until WLED has its own fastled-slim ready.

The softhack007 fork version is proven-in-use on the fork from @troyhacks, and completly removes any global functions or ISRs when compiling with -D FASTLED_NO_FASTLED
2026-03-27 15:35:53 +01:00
Frank 2a57177a2d revert UI hack 2026-03-27 14:42:42 +01:00
Frank 868fbf3714 revert hack in set_metadata.py 2026-03-27 14:39:02 +01:00
Frank 51af4ecb08 Merge branch 'V5' into V5-C6 2026-03-27 14:35:23 +01:00
Frank 758bdd537c replace preprocessor #error with static_assert
ensures that constexpr constants are handled properly.
2026-03-27 13:27:35 +01:00
Frank e834de2212 make WLED_MAX_BUSSES constexpr
prevent macro expansion problems, because  WLED_MAX_ANALOG_CHANNELS is a constexpr now.
2026-03-27 13:20:23 +01:00
Frank 3a2674a1f1 toDO: clarify if the ipv6 DNS workaround is still necessary 2026-03-27 12:26:07 +01:00
Frank eeeb93cdc5 Merge branch 'main' into V5 2026-03-27 12:19:56 +01:00
Will Miles 58239ced02 Fix RMT bus drivers for v5
Use a simpler shim in bus wrapper, and clean up the HI driver to
indicate that it's not v5 compatible (yet).
2026-03-26 18:43:04 -04:00
Will Miles 4f7b574d22 dynarray: Directly apply linker fix
Rather than append a linker file, we edit the upstream supplied ones to
add our section to the binaries.  Works better on all platforms.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-26 18:41:59 -04:00
Will Miles b222940ad1 Env cleanup 2026-03-26 18:41:59 -04:00
Will Miles 0d539c524c Merge branch 'main' into V5 2026-03-25 19:29:45 -04:00
Frank 02fc15ffd1 Merge branch 'main' into V5 2026-03-11 18:15:09 +01:00
Frank 541eb72d67 Merge branch 'main' into V5 2026-03-10 21:19:30 +01:00
Frank Möhle e0164fc502 Add esp32-C61 bootloader offset
C61 uses the same offset as C6
2026-03-10 20:05:45 +01:00
Frank Möhle 9e6c927d50 Fix preprocessor directive syntax for ESP32 2026-03-10 19:11:50 +01:00
Frank 49f2208a4e Merge branch 'main' into V5 2026-03-10 19:07:06 +01:00
Frank Möhle bae62d2caf Use C3 defaults for all new MCUs (C5, C6, P4) 2026-03-10 19:06:14 +01:00
Will Tatam 493f0fe952 Merge branch 'main' into V5 2026-03-08 16:21:55 +00:00
Frank 2af98d49a1 build_unflags should always come from same origin as platform
fixes inconsistencies
2026-03-02 11:57:20 +01:00
Frank e3bebc0674 build_unflags should always come from same origin as platform
fixes inconsistencies
2026-03-02 11:51:57 +01:00
Frank fc2ffed61c -alpha-V5 --> -alphaV5
version tag renaming
2026-03-01 23:31:27 +01:00
Frank 1f4cadc629 -alpha-V5 --> -alphaV5
version tag renaming
2026-03-01 23:31:04 +01:00
Frank 27a6fadce9 bugfix: prevent array bounds violations due to short WS payload data
Guard against zero-length binary payloads before dereferencing data[0] or data[1]
2026-03-01 22:37:51 +01:00
Frank 1a91db1f01 bugfix: prevent array bounds violations due to short WS payload data
Guard against zero-length binary payloads before dereferencing data[0] or data[1]
2026-03-01 22:35:41 +01:00
Frank d4b0360f8b bugfix: right shift on signed char is unsafe
Fix signed-byte handling in SHA256 hex conversion.

When bootloaderSHA256Cache bytes >= 0x80 are assigned to a signed char, they sign-extend to negative values. Right-shifting negative values produces incorrect results (e.g., 0xFF becomes -1, which shifts incorrectly). This causes wrong hex digit output for non-ASCII hash bytes.
2026-03-01 22:10:37 +01:00
Frank 80e81eeccc bugfix: right shift on signed char is unsafe
Fix signed-byte handling in SHA256 hex conversion.

When bootloaderSHA256Cache bytes >= 0x80 are assigned to a signed char, they sign-extend to negative values. Right-shifting negative values produces incorrect results (e.g., 0xFF becomes -1, which shifts incorrectly). This causes wrong hex digit output for non-ASCII hash bytes.
2026-03-01 22:10:11 +01:00
Frank fc313e1e96 pioarduino 55.03.37 (-C5 and -P4 only) 2026-03-01 21:58:27 +01:00
Frank 511ff789b5 Merge branch 'V5' into V5-C6 2026-03-01 21:48:12 +01:00
Frank 8f44f1b395 bump build number to 2603011 2026-03-01 21:30:22 +01:00
Frank 19f7cb19a9 AsyncTCP @ 3.4.7 2026-03-01 21:29:52 +01:00
Frank b4194b3a32 upgrade to latest V5 tasmota platform
solves "Don't know how to build from a source file with suffix `.o'." build errors
2026-03-01 21:05:44 +01:00
Will Tatam 6b7ecb969b Merge pull request #5401 from wled/dependabot/npm_and_yarn/minimatch-10.2.4
Bump minimatch from 10.2.2 to 10.2.4
2026-03-01 20:52:46 +01:00
Will Tatam f1fe20d73e Merge pull request #5128 from willmmiles/cleanup-bootloader-sha
Cleanup bootloader SHA256 calculation from #4984
2026-03-01 20:52:09 +01:00
Will Tatam 4e81f272b8 Merge pull request #5393 from wled/dependabot/npm_and_yarn/multi-8e762eef80
Bump minimatch and nodemon
2026-03-01 20:52:09 +01:00
Will Tatam b3e7e7e8c0 Merge pull request #5357 from netmindz/idf_4_4_8
Upgrade platform to 4.4.8
2026-03-01 20:52:09 +01:00
Frank 8fcdbe698f Merge branch 'V5' into V5-C6 2026-02-28 21:26:20 +01:00
Frank 82723a03aa Update package-lock.json
align version with package.json
2026-02-28 21:23:17 +01:00
Frank Möhle dcf0a2aaea Update package.json with SemVer compliant version string
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-28 21:08:55 +01:00
Frank 85cff89bf0 dependancies update
* switch to FastLED "no fastled" fork - removes RMT driver conflicts, reduces firmware size
* update to latest "CORE3" NeoPixelBus
2026-02-28 20:53:45 +01:00
Frank c76cb5cc9a dependancies update
* switch to FastLED "no fastled" fork - removes RMT driver conflicts, reduces firmware size
* update to latest "CORE3" NeoPixelBus
2026-02-28 20:37:47 +01:00
Frank fb877adcb2 P4 buildenv update
${common.build_flags} was missing
2026-02-26 16:37:03 +01:00
Frank 2ce38700b1 P4 does not have on-chip WiFi, use ETH MAC instead 2026-02-25 11:52:47 +01:00
Frank d3b8d9635e more typos and comment corrections 2026-02-25 01:09:19 +01:00
Frank 491546bad0 typo's 2026-02-25 00:56:03 +01:00
Frank 3abd8c7149 P4: auto-update esp-hosted WiFi coprocessor 2026-02-25 00:43:07 +01:00
Frank 21233894ff P4 buildenv update 2026-02-25 00:41:41 +01:00
Frank aadabe9885 credits 2026-02-24 21:04:31 +01:00
Frank 5c9803db4b P4 buildenv update 2026-02-24 21:01:37 +01:00
Frank ed0fadd80c esp32-P4 pins (will clean up later) 2026-02-24 20:57:47 +01:00
Frank 0c6c66b35b remove touchButtonISR() on boards that don't suport it
fixes compiler warning related to ignored attributes

wled00/button.cpp:399:31: warning: ignoring attribute 'section (".iram1.1")' because it conflicts with previous 'section (".iram1.0")' [-Wattributes]
  399 | void IRAM_ATTR touchButtonISR()
2026-02-24 18:40:46 +01:00
Frank 908e17bbfc 16.0-V5alpha --> 16.0-alpha-V5 2026-02-24 11:24:24 +01:00
Frank 2802f8cba6 adding monitor_filters 2026-02-23 23:14:25 +01:00
Frank 2820331b5e 16.0-V5alpha --> 16.0-alpha-V5 2026-02-23 23:12:51 +01:00
Frank 9959f75a8e (experimental) buildenv for P4
it compiles, but not yet usable !!!
2026-02-23 23:11:58 +01:00
Frank a425b97a93 minor buildenv cleanup 2026-02-23 22:22:36 +01:00
Frank Möhle cffc57e578 make LED settings work, by pretending to be a c3
temporary workaround, to prevent that more than the possible 2 digital outputs are configured by users
2026-02-20 20:41:34 +01:00
Frank acc98d47e0 Merge branch 'V5' into V5-C6 2026-02-14 23:40:40 +01:00
Frank 6c0c2b3ff8 version bump 16.0-V5alpha
for V5 branch binaries
2026-02-14 23:39:52 +01:00
Frank fb8c575def change version scheme to Major.minor, drop leading "0."
as agreed in #5249
2026-02-14 23:35:49 +01:00
Frank 1bda0bf639 Merge branch 'V5' into V5-C6 2026-02-12 19:11:49 +01:00
Frank Möhle dfcec5c94b use correct function
Update ledcAttach to ledcAttachChannel
2026-02-12 18:16:44 +01:00
Frank Möhle f2cbf0cc50 Add channel parameter to ledcAttach function
keep same behaviour as in previous V4 code
2026-02-12 18:10:40 +01:00
Frank 3de117ba23 typo 2026-02-12 11:16:41 +01:00
Frank 967f6ab2bd Merge branch 'V5' into V5-C6 2026-02-12 11:10:40 +01:00
Frank 993ab49b15 Merge branch 'main' into V5 2026-02-12 11:07:36 +01:00
Frank Möhle f3f736d399 espalexa: Cleanup dummy macro for 8266 2026-02-11 17:19:26 +01:00
Frank 7ce497b1f7 fix broken ifdef chain in wled.h 2026-02-11 17:01:17 +01:00
Frank c96e28924f espalexa: better handling of fake ESP_IDF_VERSION_VAL 2026-02-11 16:39:03 +01:00
Frank c1dddeafc4 ermove duplicate extern references
these variables are defined in wled.h, which is included already.
2026-02-11 12:09:23 +01:00
Frank 4117d31cc0 add esp-idf managed folders to .gitignore 2026-02-11 10:53:26 +01:00
Frank 5e073ce76c add esp-idf managed folders to .gitignore 2026-02-11 10:48:33 +01:00
Frank deaae112a3 missed one 2026-02-11 01:52:33 +01:00
Frank ede8a14421 ifdef fixes
* shortened another mega-ifdef
* added missing CONFIG_IDF_TARGET_ESP32C61
2026-02-11 00:57:09 +01:00
Frank 46a1b2f190 clarification
according to espressif docs, this is "maximum TX power" not "TX power"
2026-02-11 00:34:16 +01:00
Frank 55eee941f7 support higher Tx power (V5 only)
allow max Tx power = 21 dBm if framework supports it.
If not supported, silently clamp to 19.5 dBm.
2026-02-11 00:28:28 +01:00
Frank f8a6790e3c 5Ghz wifi scanning
* remove last scan results when changing band mode
* set band mode before starting a new scan
2026-02-10 23:56:22 +01:00
Frank e046316545 add channel to wifi scan result list (wifi settings) 2026-02-10 23:47:34 +01:00
Frank b730ce8850 removing another mega guard 2026-02-10 22:25:04 +01:00
Frank f8df68cf82 tiny change 2026-02-10 22:18:33 +01:00
Frank 703b90fe88 Merge branch 'V5' into V5-C6 2026-02-10 21:47:51 +01:00
Frank a8f9573fc3 nitpick
* remove duplicated -D WLED_DISABLE_INFRARED
* fix indentation in WLED::initConnection()
2026-02-10 21:40:43 +01:00
Frank 3243c88055 Merge branch 'main' into V5 2026-02-10 21:26:18 +01:00
Jonathan Davey a39285a927 Add WiFi band preference for dual-band ESP32 chips (#5367)
Adds a user-configurable "WiFi band" dropdown to the WiFi settings
page: 2.4 GHz only, 5 GHz only, or Auto (both). Defaults to Auto.

Uses WiFi.setBandMode() from the Arduino ESP32 core (requires
IDF 5.4.2+), guarded by #ifdef SOC_WIFI_SUPPORT_5G. Replaces
the hardcoded WIFI_BAND_MODE_AUTO call in V5-C6 with a
user-selectable setting.

Changes:
- wled.h: new wifiBandMode variable (default WIFI_BAND_MODE_AUTO)
- cfg.cpp: serialize/deserialize with input validation (1-3 range)
- set.cpp: handle BM form param with validation, trigger reconnect
- xml.cpp: populate dropdown value, hide on non-5G chips
- wled.cpp: pass wifiBandMode to WiFi.setBandMode() in initConnection()
- settings_wifi.htm: dropdown in Experimental section

Tested on ESP32-C5 hardware - all three modes work correctly.
2026-02-10 21:06:02 +01:00
Frank 08ca41b96d expand chip-specific "if defined" to C61 and P4 (future support)
Main objective is to fnd out how far away we are from a build for the -P4. I'll clean up the "if def" mess later.
2026-02-10 19:55:36 +01:00
Frank c6739d380f ToDo: adjust Wi-Fi stregth to match value allowed by V5 framework
change from uint8_t to int8_t (also verify backwards compatibility for V4)

V5 allows WIFI_POWER_21dBm = 84 ... WIFI_POWER_MINUS_1dBm = -4. Also check if the UI can handle it.
2026-02-10 18:54:19 +01:00
Frank 1009855e89 shorten some more ifdef chains in wled.h
plus: allow LOLIN_WIFI_FIX on all boards
2026-02-10 18:52:56 +01:00
Frank 546384bbba fixing minor errors 2026-02-10 16:54:52 +01:00
Frank 71cf78be59 Merge branch 'V5' into V5-C6 2026-02-10 16:51:03 +01:00
Frank 4f9c48fd39 consistency fix 2026-02-10 16:36:06 +01:00
Frank cd911ae0db oopsie 2026-02-10 16:24:40 +01:00
Frank d823ab4a94 replace some very long if defined() chains with flags from wled_boards.h
proof of concept.
2026-02-10 16:21:37 +01:00
Frank edf0cbed4a adding wled_boards.h to reduce #if defined() .. clutter
This should be the central place to define per-board capability flags
2026-02-10 16:20:16 +01:00
Frank 7014d4f98b nitpick: fixing another case of "comparing unsigned to integer is undefined behaviour"
the new GCC is a bit more picky than older compiler versions
2026-02-10 14:20:18 +01:00
Frank ed6b82b2f6 repeat WiFi.setBandMode() in initConnection()
lesson learned: repeat Wifi.setBandMode() after each start of STA interface
2026-02-10 13:30:00 +01:00
Frank ab65e9fb5f C5: change NeoPixelBus reference to "wled-c5-stable" tag 2026-02-10 13:11:09 +01:00
Frank b128585618 fix compiler warning "comparing signed with unsigned is undefined behaviour" 2026-02-10 12:53:51 +01:00
Frank f02e6b6c02 proper wifi channel & band reporting
replace educated guess (ch > 33 -> 5Ghz) with direct info from Wifi Class
2026-02-10 12:53:04 +01:00
Frank d3251c57f9 Use Wifi class method to activate 5Ghz Wifi
mixing esp-idf and arduino-syle WiFi = problems
2026-02-10 11:50:01 +01:00
Frank b72568f5c8 ESP32-C5 does not support analog dithering (yet)
use official esp-idf API function (no dithering)
2026-02-10 11:47:39 +01:00
Frank Möhle 7b99f65341 Fix lost build_unflags in esp32dev_debug 2026-02-09 22:20:37 +01:00
Frank 6e70d0c45e clarification 2026-02-09 19:36:43 +01:00
Frank e104529329 move 5Ghz init after WiFi.mode(WIFI_STA)
* setting the band mode fails if the driver is not initialized yet
* added error checking
2026-02-09 19:31:36 +01:00
Frank 576e82bcbd report errors from 5Ghz wifi config 2026-02-09 19:03:33 +01:00
Frank 5b46b9cb21 experimental: enable 5Ghz WiFi on C5
does not always work
2026-02-09 17:02:52 +01:00
Frank d8616e69bf C5/C6 minor updates
* testing code to fake missing DeviceID (commented out)
* remove WLED_DISABLE_WEBSOCKETS
* use correct board.json for C5 "N8R4" (8MB with PSRAM)
2026-02-09 16:57:20 +01:00
Frank 8159768e81 ToDO: research how to set PWM duty cycle and hpoint for dithering
latest esp-idf 5.5.x, including C5
2026-02-09 12:19:32 +01:00
Frank Möhle 7370c100aa remove dead (and dangerous) mutex macros in bus_manager.cpp (#5364)
these macros are
* not used any more
* dangerous because they don't time out
* dangerous because they use an internal mutex of the ledc driver (not part of the LEDC API)
2026-02-09 11:51:12 +01:00
Frank d7ab1f4691 Merge branch 'V5' into V5-C6 2026-02-09 00:57:26 +01:00
Frank 21d0af1094 Merge branch 'main' into V5 2026-02-09 00:56:41 +01:00
Frank 9d63296783 fix debug delay condition in wled.cpp
simplify and correct the condition. it was never correct to assume that all C3/S2 need a delay, because boards with UART-to-USB chip don't need any wait.
2026-02-09 00:16:42 +01:00
Frank 1800a32caf align index.js with chip types from nodestruct.h
with some "future support" for P4 and C61
2026-02-09 00:11:34 +01:00
Frank be49f34a00 add include guard for esp_mac.h 2026-02-09 00:08:03 +01:00
Frank 9232bd4d76 correct straping pin comments in pinManager 2026-02-08 20:26:10 +01:00
Frank 3c24681881 ifdef guards alignment
* Add ESP32-C6 to the fast-path division guards
* exclude C6 and P4 from from digitalCount tracking (I2S memory calculation)
2026-02-08 20:20:28 +01:00
Frank 6b9f90c1dc remove outdated patched libs for C6 2026-02-08 19:15:10 +01:00
Frank b30951e674 C6 buildenv cleanup
* re-enable alexa
* remove duplicate WLED_DISABLE_ESPNOW
2026-02-08 19:11:16 +01:00
Frank 0920f0fdcf extend some chip type guards for C6 and P4 2026-02-08 19:00:28 +01:00
Frank 8b98a7ac9e future support: adding C61 and P4 to esp32RMTInvertIdle() guard 2026-02-08 18:20:54 +01:00
Frank e9b1a53149 PSRAM guard bugfix
accidentally used IDF_TARGET_.. instead of CONFIG_IDF_TARGET_..
2026-02-08 18:17:03 +01:00
Frank 20dd750b9b alexa build error was already solved in V5 branch 2026-02-08 17:49:06 +01:00
Frank b860a1ad28 fix incorrect PSRAM warning on C5 and C6 2026-02-08 17:43:27 +01:00
Frank a3a3547fa5 adding [env:esp32c5dev_8MB_qspi]
experimental buildenv for C5 with PSRAM and 8MB flash
2026-02-08 17:42:55 +01:00
Frank a1a190510a simplify C5 buildenv
* removed flags that are inherited as-is from [esp32c5]
* slightly restructured buildenv
2026-02-08 17:33:18 +01:00
Frank b2e0c243b7 simplify and correct some long ifdef chains
* C5: allow BOARD_HAS_PSRAM
* assign MIN_FRAME_DELAY based on number of CPU cores
2026-02-08 17:07:59 +01:00
Frank 16262ee424 platformio.ini: moving [esp32c5] upwards
so its between the generic sections for S3 and C6
2026-02-08 16:50:37 +01:00
Frank 6cefd14176 fix C5 build error
adjustment needed due to modified lib_deps hierarchy in V5
2026-02-08 16:43:39 +01:00
Frank 83c37f8245 Merge branch 'V5' into V5-C6 2026-02-08 16:14:24 +01:00
Frank Möhle f0acc626bd Merge pull request #5355 from jonny190/ESP32-C5-V5
Add experimental ESP32-C5 support (WiFi 6 dual-band)
2026-02-08 15:54:36 +01:00
Jonny Davey 1a025f5636 Fix MAC address on ESP32-C5 and update NeoPixelBus fork
- Fix WiFi.macAddress() returning all zeros on ESP32-C5 by falling back
  to esp_read_mac() when WiFi netif is not yet created
- Update NeoPixelBus fork to latest with channel ctor overload fix
- Enable WLED_DEBUG, remove WLED_DISABLE_WEBSOCKETS for C5 build
2026-02-08 11:20:29 +00:00
Frank 4069260e61 remove double WLED_USE_SHARED_RMT in V5 core buildenv 2026-02-08 03:44:12 +01:00
Frank 1c09d34d12 bugfix 2026-02-08 03:39:32 +01:00
Frank 692c812934 buxfixes for generic S3 buildenv 2026-02-08 03:34:12 +01:00
Frank 9484b42b40 adding 8266 regression testing build
to check if 8266 code was broken accidentally
2026-02-08 03:26:22 +01:00
Frank 6dff1d2cb1 more V4 vs V5 compatibility fixes 2026-02-08 03:25:20 +01:00
Frank 929777507c 8266 dummy macro ESP_IDF_VERSION_VAL 2026-02-08 02:23:47 +01:00
Frank d76bd5fe59 fix esp32dev_V4 build
* disable "temporary shim for NeoPixelBus CORE3" for V4
* better way to prevent compiling NeoEsp32RmtHIMethod
2026-02-08 01:54:47 +01:00
Frank 1b5f10ff6d temporarily remove esp_dmx from V4 2026-02-08 01:40:55 +01:00
Frank 97b2c7a44f bugfix:implicit capture of 'this' via '[=]' is deprecated in C++20 2026-02-08 01:40:05 +01:00
Frank 56beb3795e add esp32dev_V4, fix compatibility issues with V4, fix for deprecated udp.flush()
* add esp32dev_V4 for regression checking w.r.t. V4 framework
* bring back lost V4 code
* udp.flush() is deprecated --> udp.clear()
2026-02-08 01:24:47 +01:00
Frank 68a8812942 adding WLED_USE_SHARED_RMT for all V5 builds
plus
- re-enabled one S3 and one S2 build
- disabled c++20 deprecation warnings
- minor indentation fix
2026-02-08 00:35:59 +01:00
Jonny Davey d2e6a568ba Use NeoPixelBus fork with ESP32-C5 bit-bang and I2S fixes
Replace upstream NeoPixelBus with jonny190/NeoPixelBus fork (d4bbe3f)
which adds C5 to the I2S exclusion guard and fixes the bit-bang LED
output method (cycle counter CSR 0xC00, GPIO register types).

Spell out C5 lib_deps explicitly to avoid pulling both the upstream
and fork NeoPixelBus, which causes duplicate compilation errors.

Upstream PR: https://github.com/netmindz/NeoPixelBus/pull/1
2026-02-07 21:52:22 +00:00
Will Tatam 0b248abd3b TODO: disabled NeoEsp32RmtMethodIsr 2026-02-07 19:36:34 +00:00
Will Tatam a57e895bb9 Merge branch 'main' into V5 2026-02-07 19:28:12 +00:00
Jonny Davey 5137768a8f Address PR review: pin pioarduino, fix I2S1 remap, simplify arch check 2026-02-07 18:13:12 +00:00
Will Tatam 35706f0940 Apply suggestion from @coderabbitai[bot] for WiFi band
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-07 17:52:08 +00:00
Jonny Davey 11f5be1861 Removed Leftover file 2026-02-07 17:02:22 +00:00
Jonny Davey 8268c4a948 Applied changes based on comments on PR 2026-02-07 16:16:11 +00:00
Jonny Davey 01b1c51653 Add experimental ESP32-C5 support with WiFi 6 dual-band
ESP32-C5 is a RISC-V based SoC with WiFi 6 (2.4GHz + 5GHz) support.
Treats C5 similarly to C3 for hardware capabilities (2 RMT channels,
no parallel I2S for LEDs, similar GPIO restrictions).

Changes across 25 files:
- Add CONFIG_IDF_TARGET_ESP32C5 conditionals alongside C3/C6 checks
- Add [esp32c5] and [env:esp32c5dev] build environments in platformio.ini
- Uses pioarduino platform (Tasmota framework lacks C5 Arduino libs)
- Exclude esp_dmx library (doesn't support C5 UART registers yet)
- Fix LEDC register access for IDF 5.5.0 (duty_init vs duty)
- Guard mbedtls_sha1_shim for IDF 5.5.0 (SHA1 built-in)
- Add C5 chip identification (ID: 0x0017) in OTA and web UI

Known limitations:
- RMT LED output shows flush timeouts (needs NeoPixelBus C5 support)
- WiFi AP confirmed working on hardware
- esp_dmx/DMX input disabled
- Experimental: requires pioarduino community platform

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 13:37:50 +00:00
Frank Möhle cf09c0dd1e Comment out '#pragma warning' suppression flag
Comment out the warning suppression for '#pragma warning' messages.
2026-02-07 01:32:05 +01:00
Frank Möhle 2f267a8a14 Merge pull request #5114 from karan-vk/esp32c6-fixes
Fix ESP32-C6 GPIO pin validation and improve runtime compatibility
2026-01-10 02:12:15 +01:00
Karan Vijayakumar 4ebbd866c4 Use Aircoookie/ESPAsyncWebServer v2.4.2 instead of softhack007 fork
- Upgrade to newer ESPAsyncWebServer that has deferResponse() and queue features
- Remove deferResponse() wrapper function from fcn_declare.h
- Remove AsyncWebServer constructor conditional from wled.h
- Revert json.cpp, set.cpp, wled_server.cpp to use native deferResponse()

This addresses @willmmiles feedback to use the proper library instead of workarounds.
2025-12-10 02:44:57 +09:00
Karan Vijayakumar d2c13135b6 Fully revert .vscode/extensions.json to base branch 2025-12-10 02:44:01 +09:00
Karan Vijayakumar e4d6848d1b Revert .vscode/extensions.json 2025-12-10 02:44:01 +09:00
Karan Vijayakumar c92abb8dfc Address PR comments: cleanup and safety improvements
- Revert VSCode extension changes
- Remove unused QuickDebug dependency
- Revert GifDecoder hash to short form
- Harden selectedWiFi index calculation in network.cpp
2025-12-10 02:44:00 +09:00
Karan Vijayakumar ec44fd167a Add ESP32-C6 pin validation for strapping and USB-JTAG pins 2025-12-10 02:41:34 +09:00
Karan Vijayakumar d3e92f26c0 Fix ESP32-C6 runtime crash and improve compatibility
- Fix IPAddress comparison with 0U causing null pointer dereference in initConnection()
- Add bounds checking for selectedWiFi index before array access
- Add ESP32-C6 conditional for touch sensor exclusion (C6 has no capacitive touch)
- Add deferResponse() wrapper for C6's patched ESPAsyncWebServer
- Add early return in esp32RMTInvertIdle() for C6 (no RMT support)
- Add ESP32-C6 BitBang bus creation in bus_wrapper.h
- Update platformio.ini with proper C6 build flags and dependencies
- Use patched FastLED, AsyncTCP, and ESPAsyncWebServer forks for C6 support
2025-12-10 02:41:34 +09:00
Frank f88fd073a3 temporary workaround for #5145 2025-12-02 00:00:31 +01:00
Frank 9f12301128 esp32c6 inherits build_unflags and lib_ignore from V5 buildenv 2025-12-01 23:59:50 +01:00
Frank 7b54be849f Merge branch 'V5' into V5-C6 2025-12-01 23:53:35 +01:00
Frank 29c7b50727 fix stupid warning for c files 2025-12-01 23:48:15 +01:00
Frank 5d54597ffc change dependancy logic for proper inheritance of build_unflags and lib_ignore
* esp32_idf_V5 has the main build_unflags and lib_ignore
* next level (esp32, esp32s3, esp32s2, esp32c3) inherits from esp32_idf_V5
* user buildenvs _only_ inherit from (esp32, esp32s3, esp32s2, esp32c3)
2025-12-01 23:47:37 +01:00
Frank 0b27bc37bd fix compiler warning for WLED_MAX_ANALOG_CHANNELS
in V5, LEDC_CHANNEL_MAX and LEDC_SPEED_MODE_MAX are enum constants, and the compiler does not allow a macro where enums are implicitly treated as int.
Solution: change into constexpr with static type cast => still computed by the compiler and does not create any local variable.
2025-12-01 23:41:12 +01:00
Frank bc4b6b6a2b Merge branch 'V5' into V5-C6 2025-12-01 20:58:59 +01:00
copilot-swe-agent[bot] 8ea41cf2d2 Remove push trigger, only run usermods CI for external fork PRs
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-12-01 20:53:44 +01:00
copilot-swe-agent[bot] 5ef41e7d3b Fix usermods.yml to only run for external fork PRs
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-12-01 20:53:44 +01:00
Frank 2245e0e0df optional: newer tasmota core 2025-12-01 20:53:08 +01:00
Frank 5ecf76c1aa Merge branch 'V5' into V5-C6 2025-12-01 20:30:22 +01:00
Frank e891c5c36a Merge branch 'main' into V5 2025-12-01 20:26:44 +01:00
Frank c096c5bb8f fix for old-style WLED_RELEASE_NAME 2025-12-01 17:53:04 +01:00
Frank c73935dd36 small fix
missing ||
2025-12-01 17:36:33 +01:00
Frank 3f441f0605 guess what ... more "C6 is like C3" ifdefs needed 2025-12-01 17:31:41 +01:00
Frank 2df4c58de8 disable touch button code on C6
similar to C3
2025-12-01 17:22:16 +01:00
Frank 1de36ca0da more "C6 is like C3" adjustments 2025-12-01 17:15:04 +01:00
Frank 70235450e8 treat C6 similar to C3
trying to reduce compile errors in bus_wrapper.h
... plus some preparation for P4 support
2025-12-01 17:03:44 +01:00
Frank 62ca377060 remove special FASTLED_NO_FASTLED hack
this was a temporary hack needed one year ago; FastLED should be compatible with -C6 now.
2025-12-01 16:29:51 +01:00
Frank b2d659120e disable QuickEspNow - seems incompatible with -C6
also disabled some core debug flags
2025-12-01 15:56:57 +01:00
Frank 45d105ed4d Merge branch 'V5' into V5-C6 2025-12-01 15:41:07 +01:00
Frank 71920659e1 temporarily disable getDeviceId() feature for V5 builds
lots of deprecated functions and esp-idf structures ... Disabled until we find a way to make this work in V5
2025-12-01 15:18:56 +01:00
Frank 83b541e1ba fix broken indentation in platformio.ini, disable usermod builds
hoping that this will finally solve errors in the standard builds
2025-12-01 14:58:51 +01:00
Frank 2c7923e17d fix name clash in dmx_input.cpp
versionString was clashing with a structure defined in wled.h => build errors.
2025-12-01 14:43:33 +01:00
Frank a55209415c Refactor software version label handling in DMX input
revert changes imported from main branch
2025-12-01 14:02:24 +01:00
Frank 6961059220 minor changes for consistency with other build envs 2025-12-01 01:58:12 +01:00
Frank 1bc63cac61 use the same lib_deps as the "main V5" branch 2025-12-01 01:37:57 +01:00
Frank 152bfd1543 Merge branch 'V5' into V5-C6 2025-12-01 01:23:15 +01:00
Frank 7e4661609c Merge branch 'main' into V5 2025-12-01 00:59:29 +01:00
Frank ecfcd0da19 fix compiler warning "ESP32 redefined", add buildenv sanity check
* fix for warning: "ESP32" redefined
* wled.h: add sanity check to catch conflicting / missing defines
2025-12-01 00:41:29 +01:00
Frank 79e2128e71 add flag to disable the new arduino-esp32 RMT leds driver
causes conflicts with NeoPixelBus
2025-12-01 00:21:14 +01:00
Frank 9743775fb5 bring back lost esp_idf_v4 section (from main)
for back-to-back testing, and for compatibility with user-specific platformio_override.ini
2025-12-01 00:19:59 +01:00
Will Tatam 9f3e53a516 disable 8266 builds for now 2025-11-08 16:39:31 +00:00
Will Tatam b51e80f5dd Safety check: ensure multiWiFi is never empty to prevent crashes during serialization 2025-11-08 16:28:19 +00:00
Will Tatam d632cf8e8c improve esp32_dev env 2025-11-08 15:02:33 +00:00
Will Tatam 7c8b8fd0f3 NeoPixelBus CORE3 2025-11-08 15:02:09 +00:00
Frank fa5aa586ec Update platformio.ini
* more debug output
* added my own fork of FastLED ( looks like more bugs to solve ....)
2025-11-08 14:17:43 +00:00
Will Tatam db65e30ad5 use esp32_idf_V5.platform 2025-11-08 14:02:47 +00:00
Will Tatam cf195af7c8 build only esp32c6dev_4MB 2025-11-08 13:54:39 +00:00
Frank 0f1055826b ESP32-C6 buildenv
due to Arduino 3.0.1, a few updated libraries are required:
* Tasmota Platform - official platfomio lacks arduino support for C6
* FastLED (latest + C6 build patches)
* AsyncTCP (latest + C6 build patches)
* AsyncWebServer (latest + C6 build patches)
* NeoPixelBus (lastest)
2025-11-08 13:41:08 +00:00
Will Tatam 96491255f1 Disable other envs to save CI build time 2025-11-08 13:33:18 +00:00
Will Tatam 36eaca6cb2 NeoEsp32RmtHI does not support V5 2025-11-08 13:24:39 +00:00
Will Tatam 0653854904 NeoEsp32RmtHI does not support V5 2025-11-08 13:03:55 +00:00
Will Tatam 51e1f4fc04 NeoEsp32RmtHI does not support V5 2025-11-08 12:49:52 +00:00
Will Tatam 2af23fc0cd AsyncTCP 3.4.7 does not support V5 2025-11-08 12:49:27 +00:00
Will Tatam ce84a57a0d NeoEsp32RmtHI does not support V5 2025-11-08 12:44:26 +00:00
Will Tatam 8cad34e14a Merge branch 'main' into V5 2025-11-08 12:19:01 +00:00
Will Tatam 93821efb90 disable usermods build for now 2025-11-08 12:11:48 +00:00
Will Tatam c5e2ec72bd WLED_DISABLE_INFRARED for 8266 2025-11-08 12:08:50 +00:00
Will Tatam 86679edd1f ESP-IDF fix for PWM_fan.cpp 2025-11-08 12:07:49 +00:00
Will Tatam 7a0325f88f WLED_DISABLE_INFRARED for 8266 2025-11-08 12:02:25 +00:00
Will Tatam 44a483f454 WLED_DISABLE_INFRARED for 8266 2025-11-08 11:55:01 +00:00
Will Tatam 41878f01f9 WLED_DISABLE_INFRARED for 8266 2025-11-08 11:51:08 +00:00
Will Tatam 906d4560a7 fix for pin usage logger 2025-11-08 11:43:38 +00:00
Will Tatam 5205110960 mbedtls_sha1_shim.cpp not required for 8266 2025-11-08 11:40:00 +00:00
Will Tatam 2f8882ee56 WLED_DISABLE_INFRARED for 8266 2025-11-08 11:39:02 +00:00
Will Tatam d4bafec0f3 WLED_DISABLE_MQTT for 8266 2025-11-08 11:27:33 +00:00
Will Tatam 446c04380e fix for esp32_eth 2025-11-08 11:20:03 +00:00
Will Tatam 7daada1f3e fix for esp32_eth 2025-11-08 11:18:25 +00:00
Will Tatam 68dff2d392 mbedtls_sha1 workaround 2025-11-08 10:53:41 +00:00
Will Tatam ec22c50813 Update DMX Input for latest esp_dmx 2025-11-08 10:38:19 +00:00
Will Tatam 81c92257da Update PlatformIO 2025-08-16 12:45:36 +01:00
Will Tatam e4815d1ff5 Update PlatformIO 2025-08-16 12:36:41 +01:00
Will Tatam bc19133e73 Update PlatformIO 2025-08-16 12:33:25 +01:00
Will Tatam 03a9d9e56f ledc changes for versions 2.X (based on ESP-IDF 4.4) to version 3.0 (based on ESP-IDF 5.1) - https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html 2025-08-16 12:05:41 +01:00
Will Tatam 2c0259f214 Rename Network to WLEDNetwork to prevent clash 2025-08-16 11:57:50 +01:00
Will Tatam 1de8c2e79b Remove local copy of MQTT and disable for now in build 2025-08-16 11:57:20 +01:00
Will Tatam 07ab6aa3ea Rename Network to WLEDNetwork to prevent clash 2025-08-14 18:59:06 +01:00
Will Tatam ba2b182bb3 Update platformio.ini for V5 2025-08-14 18:26:12 +01:00
Will Tatam 965e794094 Rename Network to WLEDNetwork to prevent clash 2025-08-14 18:25:39 +01:00
138 changed files with 6674 additions and 1754 deletions
+259
View File
@@ -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
+14
View File
@@ -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"
}
}
}
+9 -9
View File
@@ -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"
+7 -4
View File
@@ -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
+120
View File
@@ -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>` | 1520 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
View File
@@ -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 | 1520 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.
+45
View File
@@ -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
+30 -4
View File
@@ -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
+5 -2
View File
@@ -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:
+14 -4
View File
@@ -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
View File
@@ -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 }}
+1
View File
@@ -16,6 +16,7 @@ __pycache__/
esp01-update.sh
platformio_override.ini
platformio_release.ini
replace_fs.py
wled-update.sh
+1
View File
@@ -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": [
+267
View File
@@ -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), 310× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM.
- Consider that ESP32 often crashes when the largest 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
View File
@@ -10,8 +10,8 @@ We'll work with you to refine your contribution, but we'll also push back if som
Here are a few suggestions to make it easier for you to contribute:
### Important Developer Infos
* [Project Structure, Files and Directories](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
+162
View File
@@ -0,0 +1,162 @@
---
applyTo: ".github/workflows/*.yml,.github/workflows/*.yaml"
---
# CI/CD Conventions — GitHub Actions Workflows
> **Note for AI review tools**: sections enclosed in
> `<!-- HUMAN_ONLY_START -->` / `<!-- HUMAN_ONLY_END -->` HTML comments contain
> contributor reference material. Do **not** use that content as actionable review
> criteria — treat it as background context only.
<!-- HUMAN_ONLY_START -->
## YAML Style
- Indent with **2 spaces** (no tabs)
- Every workflow, job, and step must have a `name:` field that clearly describes its purpose
- Group related steps logically; separate unrelated groups with a blank line
- Comments (`#`) are encouraged for non-obvious decisions (e.g., why `fail-fast: false` is set, what a cron expression means)
## Workflow Structure
### Triggers
- Declare `on:` triggers explicitly; avoid bare `on: push` without branch filters on long-running or expensive jobs
- Prefer `workflow_call` for shared build logic (see `build.yml`) to avoid duplicating steps across workflows
- Document scheduled triggers (`cron:`) with a human-readable comment:
```yaml
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC daily
```
### Jobs
- Express all inter-job dependencies with `needs:` — never rely on implicit ordering
- Use job `outputs:` + step `id:` to pass structured data between jobs (see `get_default_envs` in `build.yml`)
- Set `fail-fast: false` on matrix builds so that a single failing environment does not cancel others
### Runners
- Pin to a specific Ubuntu version (`ubuntu-22.04`, `ubuntu-24.04`) rather than `ubuntu-latest` for reproducible builds
- Only use `ubuntu-latest` in jobs where exact environment reproducibility is not required (e.g., trivial download/publish steps)
### Tool and Language Versions
- Pin tool versions explicitly:
```yaml
python-version: '3.12'
```
- Do not rely on the runner's pre-installed tool versions — always install via a versioned setup action
### Caching
- Always cache package managers and build tool directories when the job installs dependencies:
```yaml
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
```
- Include the environment name or a relevant identifier in cache keys when building multiple targets
### Artifacts
- Name artifacts with enough context to be unambiguous (e.g., `firmware-${{ matrix.environment }}`)
- Avoid uploading artifacts that will never be consumed downstream
<!-- HUMAN_ONLY_END -->
---
## Security
Important: Several current workflows still violate parts of the baseline below - migration is in progress.
### Permissions — Least Privilege
Declare explicit `permissions:` blocks. The default token permissions are broad; scope them to the minimum required:
```yaml
permissions:
contents: read # for checkout
```
For jobs that publish releases or write to the repository:
```yaml
permissions:
contents: write # create/update releases
```
A common safe baseline for build-only jobs:
```yaml
permissions:
contents: read
```
### Supply Chain — Action Pinning
**Third-party actions** (anything outside the `actions/` and `github/` namespaces) should be pinned to a specific release tag. Branch pins (`@main`, `@master`) are **not allowed** — they can be updated by the action author at any time without notice:
```yaml
# ✅ Acceptable — specific version tag. SHA pinning recommended for more security, as @v2 is still a mutable tag.
uses: softprops/action-gh-release@v2
# ❌ Not acceptable — mutable branch reference
uses: andelf/nightly-release@main
```
SHA pinning (e.g., `uses: someorg/some-action@abc1234`) is the most secure option for third-party actions; it is recommended when auditing supply-chain risk is a priority. At minimum, always use a specific version tag.
**First-party actions** (`actions/checkout`, `actions/cache`, `actions/upload-artifact`, etc.) pinned to a major version tag (e.g., `@v4`) are acceptable because GitHub maintains and audits these.
When adding a new third-party action:
1. Check that the action's repository is actively maintained
2. Review the action's source before adding it
3. Prefer well-known, widely-used actions over obscure ones
### Credentials and Secrets
- Use `${{ secrets.GITHUB_TOKEN }}` for operations within the same repository — it is automatically scoped and rotated
- Never commit secrets, tokens, or passwords into workflow files or any tracked file
- Never print secrets in `run:` steps, even with `echo` — GitHub masks known secrets but derived values are not automatically masked
- Scope secrets to the narrowest step that needs them using `env:` at the step level, not at the workflow level:
```yaml
# ✅ Scoped to the step that needs it
- name: Create release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
# ❌ Unnecessarily broad
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
- Personal Access Tokens (PATs, stored as repository secrets) should have the minimum required scopes and should be rotated periodically
### Script Injection
`${{ }}` expressions are evaluated before the shell script runs. If an expression comes from untrusted input (PR titles, issue bodies, branch names from forks), it can inject arbitrary shell commands.
**Never** interpolate `github.event.*` values directly into a `run:` step:
```yaml
# ❌ Injection risk — PR title is attacker-controlled
- run: echo "${{ github.event.pull_request.title }}"
# ✅ Safe — value passed through an environment variable
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "$PR_TITLE"
```
This rule applies to any value that originates outside the repository (issue bodies, labels, comments, commit messages from forks).
### Pull Request Workflows
- Workflows triggered by `pull_request` from a fork run with **read-only** token permissions and no access to repository secrets — this is intentional and correct
- Do not use `pull_request_target` unless you fully understand the security implications; it runs in the context of the base branch and *does* have secret access, making it a common attack surface
+528
View File
@@ -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 (0255)
* @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 28 KB. A runtime-sized VLA can silently exhaust the stack. Use fixed-size arrays or heap allocation (`d_malloc` / `p_malloc`). Any VLA must be explicitly justified in source or PR.
<!-- HUMAN_ONLY_START -->
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 (24).
### 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)
```
+859
View File
@@ -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 3961). Follow this pattern when adding new chip-specific branches:
<!-- HUMAN_ONLY_START -->
```cpp
#if defined(CONFIG_IDF_TARGET_ESP32)
// classic ESP32 path
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
// S3-specific path
#elif defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4)
// RISC-V common path
#else
#warning "Untested chip — review peripheral availability"
#endif
```
<!-- HUMAN_ONLY_END -->
### Guidelines
- **Always test on the actual chip** before claiming support. Simulators and cross-compilation can hide peripheral differences.
- **Prefer `#elif` chains** over nested `#ifdef` for readability.
- **Do not use `CONFIG_IDF_TARGET_*` for feature detection.** Use `SOC_*` capability macros instead (see next section). For example, use `SOC_I2S_SUPPORTS_ADC` instead of `CONFIG_IDF_TARGET_ESP32` to check for I2S ADC support.
- When a feature must be disabled on certain chips, use explicit `static_assert()` or `#warning` directives so the build clearly reports what is missing.
---
## Hardware Capability Detection: `SOC_*` Macros
`SOC_*` macros (from `soc/soc_caps.h`) describe what the current chip supports. They are the correct way to check for peripheral features — they stay accurate when new chips are added, unlike `CONFIG_IDF_TARGET_*` checks.
<!-- HUMAN_ONLY_START -->
### Important `SOC_*` macros used in WLED
| Macro | Type | Used in | Purpose |
|---|---|---|---|
| `SOC_I2S_NUM` | `int` | `audio_source.h` | Number of I2S peripherals (1 or 2) |
| `SOC_I2S_SUPPORTS_ADC` | `bool` | `usermods/audioreactive/audio_source.h` | I2S ADC sampling mode (ESP32 only) |
| `SOC_I2S_SUPPORTS_APLL` | `bool` | `usermods/audioreactive/audio_source.h` | Audio PLL for precise sample rates |
| `SOC_I2S_SUPPORTS_PDM_RX` | `bool` | `usermods/audioreactive/audio_source.h` | PDM microphone input |
| `SOC_ADC_MAX_BITWIDTH` | `int` | `util.cpp` | ADC resolution (12 or 13 bits). Renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH` in IDF v5 |
| `SOC_ADC_CHANNEL_NUM(unit)` | `int` | `pin_manager.cpp` | ADC channels per unit |
| `SOC_UART_NUM` | `int` | `dmx_input.cpp` | Number of UART peripherals |
| `SOC_DRAM_LOW` / `SOC_DRAM_HIGH` | `addr` | `util.cpp` | DRAM address boundaries for validation |
<!-- HUMAN_ONLY_END -->
### Key pitfall
`SOC_ADC_MAX_BITWIDTH` (ADC resolution 12 or 13 bits) was renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH` in IDF v5.
<!-- HUMAN_ONLY_START -->
### Less commonly used but valuable
| Macro | Purpose |
|---|---|
| `SOC_RMT_TX_CANDIDATES_PER_GROUP` | Number of RMT TX channels (varies 28 by chip) |
| `SOC_LEDC_CHANNEL_NUM` | Number of LEDC (PWM) channels |
| `SOC_GPIO_PIN_COUNT` | Total GPIO pin count |
| `SOC_DAC_SUPPORTED` | Whether the chip has a DAC (ESP32/S2 only) |
| `SOC_SPIRAM_SUPPORTED` | Whether PSRAM interface exists |
| `SOC_CPU_CORES_NUM` | Core count (1 or 2) — useful for task pinning decisions |
<!-- HUMAN_ONLY_END -->
### Best practices
```cpp
// Good: feature-based detection
#if SOC_I2S_SUPPORTS_PDM_RX
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
#else
#warning "PDM microphones not supported on this chip"
#endif
// Avoid: chip-name-based detection
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
// happens to be correct today, but breaks when a new chip adds PDM support
#endif
```
<!-- HUMAN_ONLY_START -->
### PSRAM capability macros
For PSRAM presence, mode, and DMA access patterns:
| Macro | Meaning |
|---|---|
| `CONFIG_SPIRAM` / `BOARD_HAS_PSRAM` | PSRAM is present in the build configuration |
| `CONFIG_SPIRAM_MODE_QUAD` | Quad-SPI PSRAM (standard, used on ESP32 classic and some S2/S3 boards) |
| `CONFIG_SPIRAM_MODE_OCT` | Octal-SPI PSRAM — 8 data lines, DTR mode. Used on ESP32-S3 with octal PSRAM (e.g. N8R8 / N16R8 modules). Reserves GPIO 3337 for the PSRAM bus — **do not allocate these pins** when this macro is defined. `wled.cpp` uses this to gate GPIO reservation. |
| `CONFIG_SPIRAM_MODE_HEX` | Hex-SPI (16-line) PSRAM — future interface on ESP32-P4 running at up to 200 MHz. Used in `json.cpp` to report the PSRAM mode. |
| `CONFIG_SOC_PSRAM_DMA_CAPABLE` | PSRAM buffers can be used with DMA (ESP32-S3 with octal PSRAM) |
| `CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT` | SPI flash and PSRAM on separate buses (no speed contention) |
<!-- HUMAN_ONLY_END -->
#### Detecting octal/hex flash
On ESP32-S3 modules with OPI flash (e.g. N8R8 modules where the SPI flash itself runs in Octal-PI mode), the build system sets:
| Macro | Meaning |
|---|---|
| `CONFIG_ESPTOOLPY_FLASHMODE_OPI` | Octal-PI flash mode. On S3, implies GPIO 3337 are used by the flash/PSRAM interface — the same GPIO block as octal PSRAM. `wled.cpp` uses `CONFIG_ESPTOOLPY_FLASHMODE_OPI \|\| (CONFIG_SPIRAM_MODE_OCT && BOARD_HAS_PSRAM)` to decide whether to reserve these GPIOs. `json.cpp` uses this to report the flash mode string as `"🚀OPI"`. |
| `CONFIG_ESPTOOLPY_FLASHMODE_HEX` | Hex flash mode (ESP32-P4). Reported as `"🚀🚀HEX"` in `json.cpp`. |
**Pattern used in WLED** (from `wled.cpp`) to reserve the octal-bus GPIOs on S3:
```cpp
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#if CONFIG_ESPTOOLPY_FLASHMODE_OPI || (CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM))
// S3: GPIO 33-37 are used by the octal PSRAM/flash bus
managed_pin_type pins[] = { {33, true}, {34, true}, {35, true}, {36, true}, {37, true} };
pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM);
#endif
#endif
```
---
## ESP-IDF Version Conditionals
<!-- HUMAN_ONLY_START -->
### Checking the IDF version
```cpp
#include <esp_idf_version.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// IDF v5+ code path
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
// IDF v4.4+ code path
#else
// Legacy IDF v3/v4.x path
#endif
```
### Key ESP-IDF version thresholds for WLED
| Version | What changed |
|---|---|
| **4.0.0** | Filesystem API (`SPIFFS`/`LittleFS`), GPIO driver overhaul |
| **4.2.0** | ADC/GPIO API updates; `esp_adc_cal` introduced |
| **4.4.0** | I2S driver refactored (legacy API remains); `adc_deprecated.h` headers appear for newer targets |
| **4.4.44.4.8** | Known I2S channel-swap regression on ESP32 (workaround in `audio_source.h`) |
| **5.0.0** | **Major breaking changes** — RMT, I2S, ADC, SPI flash APIs replaced (see migration section) |
| **5.1.0** | Matter protocol support; new `esp_flash` API stable |
| **5.3+** | arduino-esp32 v3.x compatibility; C6/P4 support |
<!-- HUMAN_ONLY_END -->
### Guidelines
- When adding a version guard, **always include a comment** explaining *what* changed and *why* the guard is needed.
- Avoid version ranges that silently break — prefer `>=` over exact version matches.
- Known regressions should use explicit range guards:
```cpp
// IDF 4.4.44.4.8 swapped I2S left/right channels (fixed in 4.4.9)
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 4)) && \
(ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 8))
#define I2S_CHANNELS_SWAPPED
#endif
```
---
## Migrating from ESP-IDF v4.4.x to v5.x
The jump from IDF v4.4 (arduino-esp32 v2.x) to IDF v5.x (arduino-esp32 v3.x) is the largest API break in ESP-IDF history. This section documents the critical changes and recommended migration patterns based on the upstream WLED `V5-C6` branch (`https://github.com/wled/WLED/tree/V5-C6`). Note: WLED has not yet migrated to IDF v5 — these patterns prepare for the future migration.
<!-- HUMAN_ONLY_START -->
### Compiler changes
IDF v5.x ships a much newer GCC toolchain. Key versions:
| ESP-IDF | GCC | C++ default | Notes |
|---|---|---|---|
| 4.4.x (current) | **8.4.0** | C++17 (gnu++17) | Xtensa + RISC-V |
| 5.15.3 | **13.2** | C++20 (gnu++2b) | Significant warning changes |
| 5.45.5 | **14.2** | C++23 (gnu++2b) | Latest; stricter diagnostics |
Notable behavioral differences:
| Change | Impact | Action |
|---|---|---|
| Stricter `-Werror=enum-conversion` | Implicit int-to-enum casts now error | Use explicit `static_cast<>` or typed enums |
| C++20/23 features available | `consteval`, `concepts`, `std::span`, `std::expected` | Use judiciously — ESP8266 builds still require GCC 10.x with C++17 |
| `-Wdeprecated-declarations` enforced | Deprecated API calls become warnings/errors | Migrate to new APIs (see below) |
| `-Wdangling-reference` (GCC 13+) | Warns when a reference binds to a temporary that will be destroyed | Fix the lifetime issue; do not suppress the warning |
| `-fno-common` default (GCC 12+) | Duplicate tentative definitions across translation units cause linker errors | Use `extern` declarations in headers, define in exactly one `.cpp` |
| RISC-V codegen improvements | C3/C6/P4 benefit from better register allocation | No action needed — automatic |
### C++ language features: GCC 8 → GCC 14
The jump from GCC 8.4 to GCC 14.2 spans six major compiler releases. This section lists features that become available and patterns that need updating.
#### Features safe to use after migration
These work in GCC 13+/14+ but **not** in GCC 8.4. Guard with `#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)` if the code must compile on both IDF v4 and v5.
| Feature | Standard | Example | Benefit |
|---|---|---|---|
| Designated initializers (C++20) | C++20 | `gpio_config_t cfg = { .mode = GPIO_MODE_OUTPUT };` | Already used as a GNU extension in GCC 8; becomes standard and portable in C++20 |
| `[[likely]]` / `[[unlikely]]` | C++20 | `if (err != ESP_OK) [[unlikely]] { ... }` | Hints for branch prediction; useful in hot paths |
| `[[nodiscard("reason")]]` | C++20 | `[[nodiscard("leak if ignored")]] void* allocBuffer();` | Enforces checking return values — helpful for `esp_err_t` wrappers |
| `std::span<T>` | C++20 | `void process(std::span<uint8_t> buf)` | Safe, non-owning view of contiguous memory — replaces raw pointer + length pairs |
| `consteval` | C++20 | `consteval uint32_t packColor(...)` | Guarantees compile-time evaluation; useful for color constants |
| `constinit` | C++20 | `constinit static int counter = 0;` | Prevents static initialization order fiasco |
| Concepts / `requires` | C++20 | `template<typename T> requires std::integral<T>` | Clearer constraints than SFINAE; improves error messages |
| Three-way comparison (`<=>`) | C++20 | `auto operator<=>(const Version&) const = default;` | Less boilerplate for comparable types |
| `std::bit_cast` | C++20 | `float f = std::bit_cast<float>(uint32_val);` | Type-safe reinterpretation — replaces `memcpy` or `union` tricks |
| `if consteval` | C++23 | `if consteval { /* compile-time */ } else { /* runtime */ }` | Cleaner than `std::is_constant_evaluated()` |
| `std::expected<T, E>` | C++23 | `std::expected<int, esp_err_t> readSensor()` | Monadic error handling — cleaner than returning error codes |
| `std::to_underlying` | C++23 | `auto val = std::to_underlying(myEnum);` | Replaces `static_cast<int>(myEnum)` |
#### Features already available in GCC 8 (C++17)
These work on both IDF v4.4 and v5.x — prefer them now:
| Feature | Example | Notes |
|---|---|---|
| `if constexpr` | `if constexpr (sizeof(T) == 4) { ... }` | Compile-time branching; already used in WLED |
| `std::optional<T>` | `std::optional<uint8_t> pin;` | Nullable value without sentinel values like `-1` |
| `std::string_view` | `void log(std::string_view msg)` | Non-owning, non-allocating string reference |
| Structured bindings | `auto [err, value] = readSensor();` | Useful with `std::pair` / `std::tuple` returns |
| Fold expressions | `(addSegment(args), ...);` | Variadic template expansion |
| Inline variables | `inline constexpr int MAX_PINS = 50;` | Avoids ODR issues with header-defined constants |
| `[[maybe_unused]]` | `[[maybe_unused]] int debug_only = 0;` | Suppresses unused-variable warnings cleanly |
| `[[fallthrough]]` | `case 1: doA(); [[fallthrough]]; case 2:` | Documents intentional switch fallthrough |
| Nested namespaces | `namespace wled::audio { }` | Shorter than nested `namespace` blocks |
#### Patterns that break or change behavior
| Pattern | GCC 8 behavior | GCC 14 behavior | Fix |
|---|---|---|---|
| `int x; enum E e = x;` | Warning (often ignored) | Error with `-Werror=enum-conversion` | `E e = static_cast<E>(x);` |
| `int g;` in two `.cpp` files | Both compile, linker merges (tentative definition) | Error: multiple definitions (`-fno-common`) | `extern int g;` in header, `int g;` in one `.cpp` |
| `const char* ref = std::string(...).c_str();` | Silent dangling pointer | Warning (`-Wdangling-reference`) | Extend lifetime: store the `std::string` in a local variable |
| `register int x;` | Accepted (ignored) | Warning or error (`register` removed in C++17) | Remove `register` keyword |
| Narrowing in aggregate init | Warning | Error | Use explicit cast or wider type |
| Implicit `this` capture in lambdas | Accepted in `[=]` | Deprecated warning; error in C++20 mode | Use `[=, this]` or `[&]` |
<!-- HUMAN_ONLY_END -->
#### Recommendations
- **Do not raise the minimum C++ standard yet.** WLED must still build on IDF v4.4 (GCC 8.4, C++17). Use `#if __cplusplus > 201703L` to gate C++20 features.
- **Mark intentional fallthrough** with `[[fallthrough]]` — GCC 14 warns on unmarked fallthrough by default.
<!-- HUMAN_ONLY_START -->
- **Prefer `std::optional` over sentinel values** (e.g., `-1` for "no pin") in new code — it works on both compilers.
- **Use `std::string_view`** for read-only string parameters instead of `const char*` or `const String&` — zero-copy and works on GCC 8+.
- **Avoid raw `union` type punning** — prefer `memcpy` (GCC 8) or `std::bit_cast` (GCC 13+) for strict-aliasing safety.
<!-- HUMAN_ONLY_END -->
### Deprecated and removed APIs
#### RMT (Remote Control Transceiver)
The legacy `rmt_*` functions are removed in IDF v5. Do not introduce new legacy RMT calls.
<!-- HUMAN_ONLY_START -->
The new API is channel-based:
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|---|---|---|
| `rmt_config()` + `rmt_driver_install()` | `rmt_new_tx_channel()` / `rmt_new_rx_channel()` | Channels are now objects |
| `rmt_write_items()` | `rmt_transmit()` with encoder | Requires `rmt_encoder_t` |
| `rmt_set_idle_level()` | Configure in channel config | Set at creation time |
| `rmt_item32_t` | `rmt_symbol_word_t` | Different struct layout |
<!-- HUMAN_ONLY_END -->
**WLED impact**: NeoPixelBus LED output and IR receiver both use legacy RMT. The upstream `V5-C6` branch adds `-D WLED_USE_SHARED_RMT` and disables IR until the library is ported.
#### I2S (Inter-IC Sound)
Legacy `i2s_driver_install()` + `i2s_read()` API is deprecated. When touching audio source code, wrap legacy I2S init and reading in `#if ESP_IDF_VERSION_MAJOR < 5` / `#else`.
<!-- HUMAN_ONLY_START -->
The new API uses channel handles:
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|---|---|---|
| `i2s_driver_install()` | `i2s_channel_init_std_mode()` | Separate STD/PDM/TDM modes |
| `i2s_set_pin()` | Pin config in `i2s_std_gpio_config_t` | Set at init time |
| `i2s_read()` | `i2s_channel_read()` | Uses channel handle |
| `i2s_set_clk()` | `i2s_channel_reconfig_std_clk()` | Reconfigure running channel |
| `i2s_config_t` | `i2s_std_config_t` | Separate config for each mode |
**Migration pattern** (from Espressif examples):
```cpp
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/i2s_std.h"
i2s_chan_handle_t rx_handle;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_new_channel(&chan_cfg, NULL, &rx_handle);
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(22050),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = { .din = GPIO_NUM_32, .mclk = I2S_GPIO_UNUSED, ... },
};
i2s_channel_init_std_mode(rx_handle, &std_cfg);
i2s_channel_enable(rx_handle);
#else
// Legacy i2s_driver_install() path
#endif
```
<!-- HUMAN_ONLY_END -->
**WLED impact**: The audioreactive usermod (`audio_source.h`) heavily uses legacy I2S. Migration requires rewriting the `I2SSource` class for channel-based API.
<!-- HUMAN_ONLY_START -->
#### ADC (Analog-to-Digital Converter)
Legacy `adc1_get_raw()` and `esp_adc_cal_*` are deprecated:
| IDF v4 (legacy) | IDF v5 (new) | Notes |
|---|---|---|
| `adc1_config_width()` + `adc1_get_raw()` | `adc_oneshot_new_unit()` + `adc_oneshot_read()` | Object-based API |
| `esp_adc_cal_characterize()` | `adc_cali_create_scheme_*()` | Calibration is now scheme-based |
| `adc_continuous_*` (old) | `adc_continuous_*` (restructured) | Config struct changes |
#### SPI Flash
| IDF v4 (legacy) | IDF v5 (new) |
|---|---|
| `spi_flash_read()` | `esp_flash_read()` |
| `spi_flash_write()` | `esp_flash_write()` |
| `spi_flash_erase_range()` | `esp_flash_erase_region()` |
WLED already has a compatibility shim in `ota_update.cpp` that maps old names to new ones.
#### GPIO
| IDF v4 (legacy) | IDF v5 (recommended) |
|---|---|
| `gpio_pad_select_gpio()` | `esp_rom_gpio_pad_select_gpio()` (or use `gpio_config()`) |
| `gpio_set_direction()` + `gpio_set_pull_mode()` | `gpio_config()` with `gpio_config_t` struct |
### Features disabled in IDF v5 builds
The upstream `V5-C6` branch explicitly disables features with incompatible library dependencies:
```ini
# platformio.ini [esp32_idf_V5]
-D WLED_DISABLE_INFRARED # IR library uses legacy RMT
-D WLED_DISABLE_MQTT # AsyncMqttClient incompatible with IDF v5
-D ESP32_ARDUINO_NO_RGB_BUILTIN # Prevents RMT driver conflict with built-in LED
-D WLED_USE_SHARED_RMT # Use new shared RMT driver for NeoPixel output
```
<!-- HUMAN_ONLY_END -->
### Migration checklist for new code
1. **Never use a removed API without a version guard.** Always provide both old and new paths, or disable the feature on IDF v5.
2. **Test on both IDF v4.4 and v5.x builds** if the code must be backward-compatible.
3. **Prefer the newer API** when writing new code — wrap the old API in an `#else` block.
4. **Mark migration TODOs** with `// TODO(idf5):` so they are easy to find later.
---
## Memory Management: `heap_caps_*` Best Practices
ESP32 has multiple memory regions with different capabilities. Using the right allocator is critical for performance and stability.
<!-- HUMAN_ONLY_START -->
### Memory regions
| Region | Flag | Speed | DMA | Size | Use for |
|---|---|---|---|---|---|
| DRAM | `MALLOC_CAP_INTERNAL \| MALLOC_CAP_8BIT` | Fast | Yes (ESP32) | 200320 KB | Hot-path buffers, task stacks, small allocations |
| IRAM | `MALLOC_CAP_EXEC` | Fastest | No | 32128 KB | Code (automatic via `IRAM_ATTR`) |
| PSRAM (SPIRAM) | `MALLOC_CAP_SPIRAM \| MALLOC_CAP_8BIT` | Slower | Chip-dependent | 216 MB | Large buffers, JSON documents, image data |
| RTC RAM | `MALLOC_CAP_RTCRAM` | Moderate | No | 8 KB | Data surviving deep sleep; small persistent buffers |
<!-- HUMAN_ONLY_END -->
### WLED allocation wrappers
WLED provides convenience wrappers with automatic fallback. **Always prefer these over raw `heap_caps_*` calls**:
| Function | Allocation preference | Use case |
|---|---|---|
| `d_malloc(size)` | RTC → DRAM → PSRAM | General-purpose; prefers fast memory |
| `d_calloc(n, size)` | Same as `d_malloc`, zero-initialized | Arrays, structs |
| `p_malloc(size)` | PSRAM → DRAM | Large buffers; prefers abundant memory |
| `p_calloc(n, size)` | Same as `p_malloc`, zero-initialized | Large arrays |
| `d_malloc_only(size)` | RTC → DRAM (no PSRAM fallback) | DMA buffers, time-critical data |
### PSRAM guidelines
- **Check availability**: test availability with `psramFound() && ESP.getPsramSize() > 0` before assuming PSRAM is present. Never rely on `BOARD_HAS_PSRAM`only.
- **DMA compatibility**: on ESP32 (classic), PSRAM buffers are **not DMA-capable** — use `d_malloc_only()` to allocate DMA buffers in DRAM only. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.
- **JSON documents**: use the `PSRAMDynamicJsonDocument` allocator (defined in `wled.h`) to put large JSON documents in PSRAM:
```cpp
PSRAMDynamicJsonDocument doc(16384); // allocated in PSRAM if available
```
- **Fragmentation**: PSRAM allocations fragment less than DRAM because the region is larger. But avoid mixing small and large allocations in PSRAM — small allocations waste the MMU page granularity.
- **Heap validation**: use `d_measureHeap()` and `d_measureContiguousFreeHeap()` to monitor remaining DRAM. Allocations that would drop free DRAM below `MIN_HEAP_SIZE` should go to PSRAM instead.
- **Performance**: Keep hot-path data in DRAM. Prefer PSRAM for capacity-oriented buffers and monitor contiguous DRAM headroom.
<!-- HUMAN_ONLY_START -->
PSRAM access is up to 15× slower than DRAM on ESP32, 310× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel at 80 MHz (120 MHz is possible with CONFIG_SPIRAM_SPEED_120M, which requires enabling experimental ESP-IDF features). On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM. Keep hot-path data in DRAM regardless, but consider that ESP32 often crashes when the largest DRAM chunk gets below 10 KB.
<!-- HUMAN_ONLY_END -->
<!-- HUMAN_ONLY_START -->
### Pattern: preference-based allocation
When you need a buffer that works on boards with or without PSRAM:
```cpp
// Prefer PSRAM for large buffers, fall back to DRAM
uint8_t* buf = (uint8_t*)heap_caps_malloc_prefer(bufSize, 2,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, // first choice: PSRAM
MALLOC_CAP_DEFAULT); // fallback: any available
// Or simply:
uint8_t* buf = (uint8_t*)p_malloc(bufSize);
```
<!-- HUMAN_ONLY_END -->
---
## I2S Audio: Best Practices
The audioreactive usermod uses I2S for microphone input. Key patterns:
### Port selection
```cpp
constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0;
// I2S_NUM_1 has limitations: no MCLK routing, no ADC support, no PDM support
```
Always use `I2S_NUM_0` unless you have a specific reason and have verified support on all target chips.
### DMA buffer tuning
DMA buffer size controls latency vs. reliability:
| Scenario | `dma_buf_count` | `dma_buf_len` | Latency | Notes |
|---|---|---|---|---|
| With HUB75 matrix | 18 | 128 | ~100 ms | Higher count prevents I2S starvation during matrix DMA |
| Without PSRAM | 24 | 128 | ~140 ms | More buffers compensate for slower interrupt response |
| Default | 8 | 128 | ~46 ms | Acceptable for most setups |
### Interrupt priority
Choose interrupt priority based on coexistence with other drivers:
```cpp
#ifdef WLED_ENABLE_HUB75MATRIX
.intr_alloc_flags = ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1, // level 1 (lowest) to avoid starving HUB75
#else
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_LEVEL3, // accept level 2 or 3 (allocator picks available)
#endif
```
### APLL (Audio PLL) usage
The ESP32 has an audio PLL for precise sample rates. Rules:
- Enable APLL when an MCLK pin is provided and precision matters.
- **Disable APLL** when Ethernet or HUB75 is active — they also use the APLL.
- APLL is broken on ESP32 revision 0 silicon.
- Not all chips have APLL — gate with `SOC_I2S_SUPPORTS_APLL`.
```cpp
#if !defined(SOC_I2S_SUPPORTS_APLL)
_config.use_apll = false;
#elif defined(WLED_USE_ETHERNET) || defined(WLED_ENABLE_HUB75MATRIX)
_config.use_apll = false; // APLL conflict
#endif
```
### PDM microphone caveats
- Not supported on ESP32-C3 (`SOC_I2S_SUPPORTS_PDM_RX` not defined).
- ESP32-S3 PDM has known issues: sample rate at 50% of expected, very low amplitude.
- **16-bit data width**: Espressif's IDF documentation states that in PDM mode the data unit width is always 16 bits, regardless of the configured `bits_per_sample`.
- See [espressif/esp-idf#8660](https://github.com/espressif/esp-idf/issues/8660) for the upstream issue.
- **Flag `bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT` in PDM mode** — this causes the S3 low-amplitude symptom.
- No clock pin (`I2S_CKPIN = -1`) triggers PDM mode in WLED.
---
## HUB75 LED Matrix: Best Practices
WLED uses the `ESP32-HUB75-MatrixPanel-I2S-DMA` library for HUB75 matrix output.
### Chip-specific panel limits
```cpp
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)
maxChainLength = 6; // S3 + PSRAM: up to 6 panels (DMA-capable PSRAM)
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
maxChainLength = 2; // S2: limited DMA channels
#else
maxChainLength = 4; // Classic ESP32: default
#endif
```
### Color depth vs. pixel count
The driver dynamically reduces color depth for larger displays to stay within DMA buffer limits:
| Pixel count | Color depth | Bits per pixel |
|---|---|---|
| ≤ `MAX_PIXELS_10BIT` | 10-bit (30-bit color) | High quality (experimental) |
| ≤ `MAX_PIXELS_8BIT` | 8-bit (24-bit color) | Full quality |
| ≤ `MAX_PIXELS_6BIT` | 6-bit (18-bit color) | Slight banding |
| ≤ `MAX_PIXELS_4BIT` | 4-bit (12-bit color) | Visible banding |
| larger | 3-bit (9-bit color) | Minimal color range |
### Resource conflicts
- **APLL**: HUB75 I2S DMA uses the APLL. Disable APLL in the audio I2S driver when HUB75 is active.
- **I2S peripheral**: HUB75 uses `I2S_NUM_1` (or `I2S_NUM_0` on single-I2S chips). Audio must use the other port.
- **Pin count**: HUB75 requires 1314 GPIO pins. On ESP32-S2 this severely limits remaining GPIO.
- **Reboot required**: on ESP32-S3, changing HUB75 driver options requires a full reboot — the I2S DMA cannot be reconfigured at runtime.
---
<!-- HUMAN_ONLY_START -->
## GPIO Best Practices
### Prefer `gpio_config()` over individual calls
```cpp
// Preferred: single struct-based configuration
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << pin),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
// Avoid: multiple separate calls (more error-prone, deprecated in IDF v5)
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
gpio_set_pull_mode(pin, GPIO_FLOATING);
```
<!-- HUMAN_ONLY_END -->
### Pin manager integration
Always allocate pins through WLED's `pinManager` before using GPIO APIs:
```cpp
if (!pinManager.allocatePin(myPin, true, PinOwner::UM_MyUsermod)) {
return; // pin in use by another module
}
// Now safe to configure
```
---
## Timer Best Practices
### Microsecond timing
For high-resolution timing, prefer `esp_timer_get_time()` (microsecond resolution, 64-bit) over `millis()` or `micros()`.
<!-- HUMAN_ONLY_START -->
```cpp
#include <esp_timer.h>
int64_t now_us = esp_timer_get_time(); // monotonic, not affected by NTP
```
> **Note**: In arduino-esp32, both `millis()` and `micros()` are thin wrappers around `esp_timer_get_time()` — they share the same monotonic clock source. Prefer the direct call when you need the full 64-bit value or ISR-safe access without truncation:
> ```cpp
> // arduino-esp32 internals (cores/esp32/esp32-hal-misc.c):
> // unsigned long micros() { return (unsigned long)(esp_timer_get_time()); }
> // unsigned long millis() { return (unsigned long)(esp_timer_get_time() / 1000ULL); }
> ```
<!-- HUMAN_ONLY_END -->
<!-- HUMAN_ONLY_START -->
### Periodic timers
For periodic tasks with sub-millisecond precision, use `esp_timer`:
```cpp
esp_timer_handle_t timer;
esp_timer_create_args_t args = {
.callback = myCallback,
.arg = nullptr,
.dispatch_method = ESP_TIMER_TASK, // run in timer task (not ISR)
.name = "my_timer",
};
esp_timer_create(&args, &timer);
esp_timer_start_periodic(timer, 1000); // 1 ms period
```
<!-- HUMAN_ONLY_END -->
Always prefer `ESP_TIMER_TASK` dispatch over `ESP_TIMER_ISR` unless you need ISR-level latency — ISR callbacks have severe restrictions (no logging, no heap allocation, no FreeRTOS API calls).
### Precision waiting: coarse delay then spin-poll
When waiting for a precise future deadline (e.g., FPS limiting, protocol timing), avoid spinning the entire duration — that wastes CPU and starves other tasks. Instead, yield to FreeRTOS while time allows, then spin only for the final window.
<!-- HUMAN_ONLY_START -->
```cpp
// Wait until 'target_us' (a micros() / esp_timer_get_time() timestamp)
long time_to_wait = (long)(target_us - micros());
// Coarse phase: yield to FreeRTOS while we have more than ~2 ms remaining.
// vTaskDelay(1) suspends the task for one RTOS tick, letting other task run freely.
while (time_to_wait > 2000) {
vTaskDelay(1);
time_to_wait = (long)(target_us - micros());
}
// Fine phase: busy-poll the last ≤2 ms for microsecond accuracy.
// micros() wraps esp_timer_get_time() so this is low-overhead.
while ((long)(target_us - micros()) > 0) { /* spin */ }
```
<!-- HUMAN_ONLY_END -->
> The threshold (2000 µs as an example) should be at least one RTOS tick (default 1 ms on ESP32) plus some margin. A value of 15003000 µs works well in practice.
---
## ADC Best Practices
<!-- HUMAN_ONLY_START -->
### Version-aware ADC code
ADC is one of the most fragmented APIs across IDF versions:
```cpp
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// IDF v5: oneshot driver
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
adc_oneshot_unit_handle_t adc_handle;
adc_oneshot_unit_init_cfg_t unit_cfg = { .unit_id = ADC_UNIT_1 };
adc_oneshot_new_unit(&unit_cfg, &adc_handle);
#else
// IDF v4: legacy driver
#include "driver/adc.h"
#include "esp_adc_cal.h"
adc1_config_width(ADC_WIDTH_BIT_12);
int raw = adc1_get_raw(ADC1_CHANNEL_0);
#endif
```
<!-- HUMAN_ONLY_END -->
### Bit width portability
Not all chips have 12-bit ADC. `SOC_ADC_MAX_BITWIDTH` reports the maximum resolution (12 or 13 bits). Note that in IDF v5, this macro was renamed to `CONFIG_SOC_ADC_RTC_MAX_BITWIDTH`. Write version-aware guards:
```cpp
// IDF v4: SOC_ADC_MAX_BITWIDTH IDF v5: CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
#if defined(CONFIG_SOC_ADC_RTC_MAX_BITWIDTH) // IDF v5+
#define MY_ADC_MAX_BITWIDTH CONFIG_SOC_ADC_RTC_MAX_BITWIDTH
#elif defined(SOC_ADC_MAX_BITWIDTH) // IDF v4
#define MY_ADC_MAX_BITWIDTH SOC_ADC_MAX_BITWIDTH
#else
#define MY_ADC_MAX_BITWIDTH 12 // safe fallback
#endif
#if MY_ADC_MAX_BITWIDTH == 13
adc1_config_width(ADC_WIDTH_BIT_13); // ESP32-S2
#else
adc1_config_width(ADC_WIDTH_BIT_12); // ESP32, S3, C3, etc.
#endif
```
WLED's `util.cpp` uses the IDF v4 form (`SOC_ADC_MAX_BITWIDTH`) — this will need updating when the codebase migrates to IDF v5.
---
<!-- HUMAN_ONLY_START -->
## RMT Best Practices
### Current usage in WLED
RMT drives NeoPixel LED output (via NeoPixelBus) and IR receiver input. Both use the legacy API that is removed in IDF v5.
### Migration notes
- The upstream `V5-C6` branch uses `-D WLED_USE_SHARED_RMT` to switch to the new RMT driver for NeoPixel output.
- IR is disabled on IDF v5 until the IR library is ported.
- New chips (C6, P4) have different RMT channel counts — use `SOC_RMT_TX_CANDIDATES_PER_GROUP` to check availability.
- The new RMT API requires an "encoder" object (`rmt_encoder_t`) to translate data formats — this is more flexible but requires more setup code.
<!-- HUMAN_ONLY_END -->
---
## Espressif Best Practices (from official examples)
### Error handling
Always check `esp_err_t` return values. Use `ESP_ERROR_CHECK()` in initialization code, but handle errors gracefully in runtime code.
<!-- HUMAN_ONLY_START -->
```cpp
// Initialization — crash early on failure
ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &config, 0, nullptr));
// Runtime — log and recover
esp_err_t err = i2s_read(I2S_NUM_0, buf, len, &bytes_read, portMAX_DELAY);
if (err != ESP_OK) {
DEBUGSR_PRINTF("I2S read failed: %s\n", esp_err_to_name(err));
return;
}
```
<!-- HUMAN_ONLY_END -->
For situations between these two extremes — where you want the `ESP_ERROR_CHECK` formatted log message (file, line, error name) but must not abort — use `ESP_ERROR_CHECK_WITHOUT_ABORT()`.
<!-- HUMAN_ONLY_START -->
```cpp
// Logs in the same format as ESP_ERROR_CHECK, but returns the error code instead of aborting.
// Useful for non-fatal driver calls where you want visibility without crashing.
esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_set_clk(AR_I2S_PORT, rate, bits, ch));
if (err != ESP_OK) return; // handle as needed
```
<!-- HUMAN_ONLY_END -->
### Logging
WLED uses its own logging macros — **not** `ESP_LOGx()`. For application-level code, always use the WLED macros defined in `wled.h`:
| Macro family | Defined in | Controlled by | Use for |
|---|---|---|---|
| `DEBUG_PRINT` / `DEBUG_PRINTLN` / `DEBUG_PRINTF` | `wled.h` | `WLED_DEBUG` build flag | Development/diagnostic output; compiled out in release builds |
All of these wrap `Serial` output through the `DEBUGOUT` / `DEBUGOUTLN` / `DEBUGOUTF` macros.
**Exception — low-level driver code**: When writing code that interacts directly with ESP-IDF APIs (e.g., I2S initialization, RMT setup), use `ESP_LOGx()` macros instead. They support tag-based filtering and compile-time log level control:
<!-- HUMAN_ONLY_START -->
```cpp
static const char* TAG = "my_module";
ESP_LOGI(TAG, "Initialized with %d buffers", count);
ESP_LOGW(TAG, "PSRAM not available, falling back to DRAM");
ESP_LOGE(TAG, "Failed to allocate %u bytes", size);
```
<!-- HUMAN_ONLY_END -->
### Task creation and pinning
<!-- HUMAN_ONLY_START -->
On dual-core chips (ESP32, S3, P4), pin latency-sensitive tasks to a specific core:
```cpp
xTaskCreatePinnedToCore(
audioTask, // function
"audio", // name
4096, // stack size
nullptr, // parameter
5, // priority (higher = more important)
&audioTaskHandle, // handle
0 // core ID (0 = protocol core, 1 = app core)
);
```
<!-- HUMAN_ONLY_END -->
Guidelines:
- Pin network/protocol tasks to core 0 (where Wi-Fi runs).
- Pin real-time tasks (audio, LED output) to core 1.
- On single-core chips (S2, C3, C5, C6), only core 0 exists — pinning to core 1 will fail. Use `SOC_CPU_CORES_NUM > 1` guards or `tskNO_AFFINITY`.
- Use `SOC_CPU_CORES_NUM` to conditionally pin tasks:
```cpp
#if SOC_CPU_CORES_NUM > 1
xTaskCreatePinnedToCore(audioTask, "audio", 4096, nullptr, 5, &handle, 1);
#else
xTaskCreate(audioTask, "audio", 4096, nullptr, 5, &handle);
#endif
```
**Tip: use xTaskCreateUniversal()** - from arduino-esp32 - to avoid the conditional on `SOC_CPU_CORES_NUM`. This function has the same signature as ``xTaskCreatePinnedToCore()``, but automatically falls back to ``xTaskCreate()`` on single-core MCUs.
### `delay()`, `yield()`, and the IDLE task
FreeRTOS on ESP32 is **preemptive** — all tasks are scheduled by priority regardless of `yield()` calls. This is fundamentally different from ESP8266 cooperative multitasking.
<!-- HUMAN_ONLY_START -->
| Call | What it does | Reaches IDLE (priority 0)? |
|---|---|---|
| `delay(ms)` / `vTaskDelay(ticks)` | Suspends calling task; scheduler runs all other ready tasks | ✅ Yes |
| `yield()` / `vTaskDelay(0)` | Hint to switch to tasks at **equal or higher** priority only | ❌ No |
| `taskYIELD()` | Same as `vTaskDelay(0)` | ❌ No |
| Blocking API (`xQueueReceive`, `ulTaskNotifyTake`, `vTaskDelayUntil`) | Suspends task until event or timeout; IDLE runs freely | ✅ Yes |
<!-- HUMAN_ONLY_END -->
**`delay()` in `loopTask` is safe.** Arduino's `loop()` runs inside `loopTask`. Calling `delay()` suspends only `loopTask` — all other FreeRTOS tasks (Wi-Fi stack, audio FFT, LED DMA) continue uninterrupted on either core.
**`yield()` does not yield to IDLE.** Any task that loops with only `yield()` calls will starve the IDLE task, causing the IDLE watchdog to fire. Always use `delay(1)` (or a blocking FreeRTOS call) in tight task loops. Note: WLED redefines `yield()` as an empty macro on ESP32 WLEDMM_FASTPATH builds.
#### Why the IDLE task is not optional
<!-- HUMAN_ONLY_START -->
The FreeRTOS IDLE task (one per core on dual-core ESP32 and ESP32-S3; single instance on single-core chips) is not idle in the casual sense — it performs essential system housekeeping:
- **Frees deleted task memory**: when a task calls `vTaskDelete()`, the IDLE task reclaims its TCB and stack. Without IDLE running, deleted tasks leak memory permanently.
- **Runs the idle hook**: when `configUSE_IDLE_HOOK = 1`, the IDLE task calls `vApplicationIdleHook()` on every iteration — some ESP-IDF components register low-priority background work here.
- **Implements tickless idle / light sleep**: on battery-powered devices, IDLE is the entry point for low-power sleep. A permanently starved IDLE task disables light sleep entirely.
- **Runs registered idle hooks**: ESP-IDF components register callbacks via `esp_register_freertos_idle_hook()` (e.g., Wi-Fi background maintenance, Bluetooth housekeeping). These only fire when IDLE runs.
<!-- HUMAN_ONLY_END -->
In short: **starving IDLE corrupts memory cleanup, breaks background activities, disables low-power sleep, and prevents Wi-Fi/BT maintenance.** The IDLE watchdog panic is a symptom — the real damage happens before the watchdog fires.
### Watchdog management
Long-running operations may trigger the task watchdog. Feed it explicitly:
```cpp
#include <esp_task_wdt.h>
esp_task_wdt_reset(); // feed the watchdog in long loops
```
For tasks that intentionally block for extended periods, consider subscribing/unsubscribing from the TWDT:
```cpp
esp_task_wdt_delete(NULL); // remove current task from TWDT (IDF v4.4)
// ... long blocking operation ...
esp_task_wdt_add(NULL); // re-register
```
> **IDF v5 note**: In IDF v5, `esp_task_wdt_add()` and `esp_task_wdt_delete()` require an explicit `TaskHandle_t`. Use `xTaskGetCurrentTaskHandle()` instead of `NULL`.
<!-- HUMAN_ONLY_START -->
---
## Quick Reference: IDF v4 → v5 API Mapping
| Component | IDF v4 Header | IDF v5 Header | Key Change |
|---|---|---|---|
| I2S | `driver/i2s.h` | `driver/i2s_std.h` | Channel-based API |
| ADC (oneshot) | `driver/adc.h` | `esp_adc/adc_oneshot.h` | Unit/channel handles |
| ADC (calibration) | `esp_adc_cal.h` | `esp_adc/adc_cali.h` | Scheme-based calibration |
| RMT | `driver/rmt.h` | `driver/rmt_tx.h` / `rmt_rx.h` | Encoder-based transmit |
| SPI Flash | `spi_flash.h` | `esp_flash.h` | `esp_flash_*` functions |
| GPIO | `driver/gpio.h` | `driver/gpio.h` | `gpio_pad_select_gpio()` removed |
| Timer | `driver/timer.h` | `driver/gptimer.h` | General-purpose timer handles |
| PCNT | `driver/pcnt.h` | `driver/pulse_cnt.h` | Handle-based API |
<!-- HUMAN_ONLY_END -->
+58
View File
@@ -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.
+227
View File
@@ -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.
+36
View File
@@ -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
+6 -5
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+12
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+110 -220
View File
@@ -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
+63 -47
View File
@@ -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://img.shields.io/badge/send%20me%20a%20small%20gift-paypal-blue.svg?style=flat-square)](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
View File
@@ -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
View File
@@ -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');
});
});
});
});
-10
View File
@@ -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
View File
@@ -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
+12 -11
View File
@@ -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 -2
View File
@@ -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"
}
}
+24 -5
View File
@@ -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);
+2 -2
View File
@@ -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
+2 -2
View File
@@ -114,7 +114,7 @@ class MyExampleUsermod : public Usermod {
void loop() override {
// if usermod is disabled or called during strip updating just exit
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
if (!enabled || strip.isUpdating()) 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 -1
View File
@@ -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;
}
}
+114 -35
View File
@@ -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,'&micro;s');"));
oappend(F("addInfo('INA226:Decimals',1,'(0-5)');"));
oappend(F("addInfo('INA226:ShuntResistor',1,'m&Omega;');"));
oappend(F("addInfo('INA226:CurrentRange',1,'mA');"));
oappend(F("addInfo('INA226:CurrentOffset',1,'mA');"));
}
bool readFromConfig(JsonObject &root) override
{
JsonObject top = root[FPSTR(_name)];
@@ -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))
+31 -1
View File
@@ -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
+5 -2
View File
@@ -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."));
}
@@ -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}
+2 -2
View File
@@ -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
;
+72 -57
View File
@@ -140,7 +140,7 @@ static uint8_t binNum = 8; // Used to select the bin for FFT based bea
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // always use integer FFT on ESP32-S2 and ESP32-C3
#endif
#endif
#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
+3 -3
View File
@@ -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 -1
View File
@@ -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;
+6
View File
@@ -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
@@ -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);
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+7 -7
View File
@@ -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
View File
@@ -55,4 +55,4 @@ struct NodeStruct
};
typedef std::map<uint8_t, NodeStruct> NodesMap;
#endif // WLED_NODESTRUCT_H
#endif // WLED_NODESTRUCT_H
+71 -25
View File
@@ -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
+3 -4
View File
@@ -171,7 +171,7 @@ class Bus {
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 5 : is2Pin(type) + 1; } // credit @PaoloTK
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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -200,7 +200,7 @@ function sendDDP(ws, start, len, colors) {
let pkt = new Uint8Array(11 + dLen); // DDP header is 10 bytes, plus 1 byte for WLED websocket protocol indicator
pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1
pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0
pkt[2] = 0x00; // 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
View File
@@ -5,7 +5,7 @@
<title>WLED Palette Editor</title>
<!--* <link rel="stylesheet" href="style.css">
<script src="common.js"></script>
<script src="iro.js"></script>
<script src="iro.js"></script>
<link rel="icon" href="data:,"> *-->
</head>
<body>
@@ -71,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 = '&#10006;';
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 } });
}
+1 -1
View File
@@ -147,7 +147,7 @@ p {
font-size: 16px;
}
.fs1 {
font-size: 48px;
font-size: 32px;
}
.fs2 {
font-size: 28px;
+15 -1
View File
@@ -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:&nbsp;25)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> wled122 <small class="fgc1">(Glyphs:&nbsp;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="&#xe0a1;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="search, magnifier" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="i-pixelforge"></span>
+1
View File
@@ -11,6 +11,7 @@
<glyph unicode="&#xe04c;" glyph-name="presets" d="M704 704c131.413 0 234.667-103.253 234.667-234.667 0-161.28-145.067-292.693-364.8-491.946l-61.867-56.32-61.867 55.893c-219.733 199.68-364.8 331.093-364.8 492.373 0 131.414 103.254 234.667 234.667 234.667 74.24 0 145.493-34.56 192-89.173 46.507 54.613 117.76 89.173 192 89.173zM516.267 40.533c203.093 183.894 337.066 305.494 337.066 428.8 0 85.334-64 149.334-149.333 149.334-65.707 0-129.707-42.24-151.893-100.694h-79.787c-22.613 58.454-86.613 100.694-152.32 100.694-85.333 0-149.333-64-149.333-149.334 0-123.306 133.973-244.906 337.066-428.8l4.267-4.266z" />
<glyph unicode="&#xe066;" glyph-name="info" d="M512 746.667c235.52 0 426.667-191.147 426.667-426.667s-191.147-426.667-426.667-426.667-426.667 191.147-426.667 426.667 191.147 426.667 426.667 426.667zM554.667 106.667v256h-85.334v-256h85.334zM554.667 448v85.333h-85.334v-85.333h85.334z" />
<glyph unicode="&#xe08f;" glyph-name="power" d="M554.667 704v-426.667h-85.334v426.667h85.334zM760.747 611.413c82.773-70.4 135.253-174.506 135.253-291.413 0-212.053-171.947-384-384-384s-384 171.947-384 384c0 116.907 52.48 221.013 135.253 291.413l60.16-60.16c-66.986-54.613-110.080-137.813-110.080-231.253 0-165.12 133.547-298.667 298.667-298.667s298.667 133.547 298.667 298.667c0 93.44-43.094 176.64-110.507 230.827z" />
<glyph unicode="&#xe0a1;" glyph-name="search" d="M902.213 27.99l-197.073 167.615c-20.373 18.336-42.161 26.752-59.761 25.941 46.52 54.492 74.622 125.188 74.622 202.455 0 172.313-139.687 312-312 312-172.311 0-312-139.687-312-312s139.687-312 312-312c77.266 0 147.962 28.1 202.455 74.624-0.812-17.6 7.605-39.388 25.941-59.761l167.615-197.073c28.698-31.887 75.58-34.574 104.178-5.976s25.913 75.48-5.974 104.178zM408 216c-114.874 0-208 93.125-208 208s93.125 208 208 208 208-93.125 208-208-93.125-208-208-208z" />
<glyph unicode="&#xe0a2;" glyph-name="settings" d="M816.64 280.064l85.504-67.584c8.192-6.144 10.24-16.896 5.12-26.112l-81.92-141.824c-5.12-9.216-15.872-12.8-25.088-9.216l-101.888 40.96c-20.992-15.872-44.032-29.696-69.12-39.936l-15.36-108.544c-1.024-10.24-9.728-17.408-19.968-17.408h-163.84c-10.24 0-18.432 7.168-20.48 17.408l-15.36 108.544c-25.088 10.24-47.616 23.552-69.12 39.936l-101.888-40.96c-9.216-3.072-19.968 0-25.088 9.216l-81.92 141.824c-4.608 8.704-2.56 19.968 5.12 26.112l86.528 67.584c-2.048 12.8-3.072 26.624-3.072 39.936s1.536 27.136 3.584 39.936l-86.528 67.584c-8.192 6.144-10.24 16.896-5.12 26.112l81.92 141.824c5.12 9.216 15.872 12.8 25.088 9.216l101.888-40.96c20.992 15.872 44.032 29.696 69.12 39.936l15.36 108.544c1.536 10.24 9.728 17.408 19.968 17.408h163.84c10.24 0 18.944-7.168 20.48-17.408l15.36-108.544c25.088-10.24 47.616-23.552 69.12-39.936l101.888 40.96c9.216 3.072 19.968 0 25.088-9.216l81.92-141.824c4.608-8.704 2.56-19.968-5.12-26.112l-86.528-67.584c2.048-12.8 3.072-26.112 3.072-39.936s-1.024-27.136-2.56-39.936zM512 166.4c84.48 0 153.6 69.12 153.6 153.6s-69.12 153.6-153.6 153.6-153.6-69.12-153.6-153.6 69.12-153.6 153.6-153.6z" />
<glyph unicode="&#xe0e8;" glyph-name="eye" d="M512 640c213.333 0 395.52-132.693 469.333-320-73.813-187.307-256-320-469.333-320s-395.52 132.693-469.333 320c73.813 187.307 256 320 469.333 320zM512 106.667c117.76 0 213.333 95.573 213.333 213.333s-95.573 213.333-213.333 213.333-213.333-95.573-213.333-213.333 95.573-213.333 213.333-213.333zM512 448c70.827 0 128-57.173 128-128s-57.173-128-128-128-128 57.173-128 128 57.173 128 128 128z" />
<glyph unicode="&#xe116;" glyph-name="sync" d="M512 661.333c188.587 0 341.333-152.746 341.333-341.333 0-66.987-19.626-129.28-52.906-181.76l-62.294 62.293c19.2 35.414 29.867 76.374 29.867 119.467 0 141.227-114.773 256-256 256v-128l-170.667 170.667 170.667 170.666v-128zM512 64v128l170.667-170.667-170.667-170.666v128c-188.587 0-341.333 152.746-341.333 341.333 0 66.987 19.626 129.28 52.906 181.76l62.294-62.293c-19.2-35.414-29.867-76.374-29.867-119.467 0-141.227 114.773-256 256-256z" />

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
+6 -3
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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")+"\">&#xe08f;</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,
+3 -3
View File
@@ -424,7 +424,7 @@ let fL; // file list
const classics=['console_font_4x6.wbf','console_font_5x12.wbf','console_font_5x8.wbf','console_font_6x8.wbf','console_font_7x9.wbf']; // classic WLED fonts list
let pT = []; // local tools list from JSON
let wv = [0, 0]; // wled version [major, minor], updated in fsMem(), used to check tool compatibility
const remoteURL = 'https://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();
+7 -6
View File
@@ -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
View File
@@ -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 += `&nbsp;<select name="BE${s}">`
c += `&nbsp;<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;
+1 -34
View File
@@ -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())
+1 -1
View File
@@ -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">&#9888; If you are using multiple VLANs (i.e. IoT or guest network) either set PIN or disable this option.<br>
+30 -10
View File
@@ -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">
+89 -24
View File
@@ -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
View File
@@ -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>
+14 -4
View File
@@ -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