Compare commits

...

141 Commits

Author SHA1 Message Date
Frank Möhle 1795797e0e Merge branch 'main' into remove-v1-usermod 2026-05-29 15:01:16 +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
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
Roberto 1cce33e0d3 fix for serial port on esp32 (#5501) 2026-04-13 18:47:18 +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
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
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 0a21869362 Remove V1 usermod 2025-08-09 15:22:41 +01:00
69 changed files with 2133 additions and 900 deletions
+148 -9
View File
@@ -6,6 +6,8 @@
# 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
@@ -24,6 +26,11 @@ reviews:
# sequence_diagrams: false
auto_review:
enabled: true
base_branches:
- main
- 16_x
- 0_15_x
- V5
ignore_title_keywords:
- WIP
@@ -31,7 +38,7 @@ reviews:
- path: "**/*.{cpp,h,hpp,ino}"
instructions: >
Follow the C++ coding conventions documented in docs/cpp.instructions.md
and the general project guidelines in .github/copilot-instructions.md.
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.
@@ -39,8 +46,37 @@ reviews:
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.
# disabled - the below instruction has no effect
# When initially reviewing a PR, summarize good practices (top 5) and create a prioritized list of suggested improvements (focus on major ones).
When reviewing PRs labeled "AI" or when source code appears to be AI-generated, perform these additional checks:
1. VERIFY all referenced constants, flags, and functions 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 fixed-point arithmetic.
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: >
@@ -49,8 +85,21 @@ reviews:
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.
# disabled - the below instruction has no effect
# When initially reviewing a PR, summarize good practices (top 5) and create a prioritized list of suggested improvements (focus on major ones).
# ── 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: >
@@ -70,8 +119,33 @@ reviews:
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).
# disabled - the below instruction has no effect
# When initially reviewing a PR, summarize good practices (top 6) and create a prioritized list of suggested improvements (skip minor ones).
# ── 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: >
@@ -83,8 +157,6 @@ reviews:
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.
# disabled - the below instruction has no effect
# When initially reviewing a PR, summarize good practices (top 6) and create a prioritized list of suggested improvements.
- path: "**/*.instructions.md"
instructions: |
@@ -102,6 +174,73 @@ reviews:
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:
+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"
+3 -1
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:
-12
View File
@@ -120,20 +120,8 @@ docs/ # Contributor docs, coding guidelines
Refer to `docs/cpp.instructions.md` and `docs/web.instructions.md` for language-specific conventions, and `docs/cicd.instructions.md` for GitHub Actions workflows.
### Attribution for AI-generated code
Using AI-generated code can hide the source of the inspiration / knowledge / sources it used.
- Document attribution of inspiration / knowledge / sources used in the code, e.g. link to GitHub repositories or other websites describing the principles / algorithms used.
- When a larger block of code is generated by an AI tool, embed it into `// AI: below section was generated by an AI` ... `// AI: end` comments (see C++ guidelines).
- Every non-trivial AI-generated function should have a brief comment describing what it does. Explain parameters when their names alone are not self-explanatory.
- AI-generated code must be well documented with meaningful comments that explain intent, assumptions, and non-obvious logic. Do not rephrase source code; explain concepts and reasoning.
### Pull Request Expectations
- **No force-push on open PRs.** Once a pull request is open and being reviewed, do not force-push (`git push --force`) to the branch. Force-pushing rewrites history that reviewers may have already commented on, making it impossible to track incremental changes. Use regular commits or `git merge` to incorporate feedback; the branch will be squash-merged when it is accepted.
- **Modifications to ``platformio.ini`` MUST be approved explicitly** by a *maintainer* or *WLED organisation Member*. Modifications to the global build environment may break github action builds. Always flag them.
- **Document your changes in the PR.** Every pull request should include a clear description of *what* changed and *why*. If the change affects user-visible behavior, describe the expected impact. Link to related issues where applicable. Provide screenshots to showcase new features.
### Supporting Reviews and Discussions
- **For "is it worth doing?" debates** about proposed reliability, safety, or data-integrity mechanisms (CRC checks, backups, power-loss protection): suggest a software **FMEA** (Failure Mode and Effects Analysis).
Clarify the main feared events, enumerate failure modes, assess each mitigation's effectiveness per failure mode, note common-cause failures, and rate credibility for the typical WLED use case.
+45
View File
@@ -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
+4 -1
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,7 +29,7 @@ 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'
+5
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
+4 -1
View File
@@ -4,7 +4,10 @@ on:
pull_request:
paths:
- usermods/**
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
get_usermod_envs:
+1
View File
@@ -16,6 +16,7 @@ __pycache__/
esp01-update.sh
platformio_override.ini
platformio_release.ini
replace_fs.py
wled-update.sh
+102 -22
View File
@@ -1,10 +1,13 @@
# AGENTS.md — WLED Coding Agent Reference
# 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/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
@@ -14,8 +17,8 @@ See also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.m
| `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) | 30 min |
| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 30 min |
| `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.
@@ -25,7 +28,7 @@ required C headers for firmware compilation.
Tests use Node.js built-in test runner (`node:test`). The single test file is
`tools/cdata-test.js`. Run it with:
```sh
```bash
npm test # runs all tests via `node --test`
node --test tools/cdata-test.js # run just that file directly
```
@@ -39,7 +42,7 @@ target environments. Always build after code changes: `pio run -e esp32dev`.
### Recovery / Troubleshooting
```sh
```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
@@ -48,7 +51,7 @@ 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)
@@ -61,6 +64,19 @@ 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
@@ -70,6 +86,10 @@ docs/ # Coding convention docs
- 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 |
|---|---|---|
@@ -105,16 +125,24 @@ docs/ # Coding convention docs
- No VLAs — use fixed arrays or heap allocation
- Call `reserve()` on strings/vectors to pre-allocate and avoid fragmentation
#### ESP32 PSRAM guidelines
- **Check availability**: Test chip availability with `psramFound() && ESP.getPsramSize() > 0` before assuming PSRAM is present. Never rely on `BOARD_HAS_PSRAM`only.
- **DMA compatibility**: on ESP32 (classic), PSRAM buffers are **not DMA-capable**. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), PSRAM buffers *can* be used with DMA when `CONFIG_SOC_PSRAM_DMA_CAPABLE` is defined.
- **Fragmentation**: PSRAM allocations fragment less than DRAM because the region is larger. But avoid mixing small and large allocations in PSRAM — small allocations waste the MMU page granularity.
- **Performance**: Prefer DRAM (or IRAM) for hot-path data that is *frequently* used. Prefer PSRAM for capacity-oriented buffers where slightly slower access times can be tolerated.
Background Info:
- PSRAM access is up to 18× slower than DRAM on ESP32 (dual-SPI bus), 310× slower than DRAM on ESP32-S3/-S2 with quad-SPI bus. On ESP32-S3 with octal PSRAM (`CONFIG_SPIRAM_MODE_OCT`), the penalty is smaller (~2×) because the 8-line DTR bus can transfer 8 bits in parallel. On ESP32-P4 with hex PSRAM (`CONFIG_SPIRAM_MODE_HEX`), the 16-line bus runs at 200 MHz which brings it on-par with DRAM.
- Consider that ESP32 often crashes when the largest DRAM chunk gets below 10 KB.
### Preprocessor / Feature Flags
- Feature toggling: `WLED_DISABLE_*` and `WLED_ENABLE_*` flags (exact names matter!)
- `WLED_DISABLE_*`: `2D`, `ADALIGHT`, `ALEXA`, `MQTT`, `OTA`, `INFRARED`, `WEBSOCKETS`, etc.
- `WLED_ENABLE_*`: `DMX`, `GIF`, `HUB75MATRIX`, `JSONLIVE`, `WEBSOCKETS`, etc.
- Platform: `ARDUINO_ARCH_ESP32`, `ESP8266`, `CONFIG_IDF_TARGET_ESP32S3`
### Comments
- `//` for inline (always space after), `/* */` for block comments
- AI-generated blocks: mark with `// AI: below section was generated by an AI` / `// AI: end`
### Math Functions
- Use `sin8_t()`, `cos8_t()` — NOT `sin8()`, `cos8()` (removed, won't compile)
- Use `sin_approx()` / `cos_approx()` instead of `sinf()` / `cosf()`
@@ -130,6 +158,13 @@ docs/ # Coding convention docs
- `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 RAII as an alternative to mutexes or semaphores.
## Web UI Code Style (wled00/data/)
- **Tab indentation** for HTML, JS, and CSS
@@ -148,18 +183,37 @@ class MyUsermod : public Usermod {
bool enabled = false;
static const char _name[];
public:
void setup() override { /* ... */ }
void loop() override { /* ... */ }
void addToConfig(JsonObject& root) override { /* ... */ }
bool readFromConfig(JsonObject& root) override { /* ... */ }
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);
```
- Add usermod IDs to `wled00/const.h`
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/).
### 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, down to 1-3 times/sec during FS activity or during high workload from effects and other usermods.
### 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`.
- Add usermod IDs to `wled00/const.h` **only when a unique ID is required** (see above)
- Activate via `custom_usermods` in platformio build config
- Base new usermods on `usermods/EXAMPLE/` (never edit the example directly)
- Store repeated strings as `static const char[] PROGMEM`
@@ -167,6 +221,7 @@ REGISTER_USERMOD(myUsermod);
## 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`)
@@ -175,10 +230,35 @@ No automated linting is configured. Match existing code style in files you edit.
## General Rules
- Repository language is English
- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`
- Repository language is English.
- 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
- Changes to `platformio.ini` require maintainer approval
- Remove dead/unused code — justify or delete it
- Verify feature-flag spelling exactly (misspellings are silently ignored by preprocessor)
- 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.
+1 -1
View File
@@ -413,7 +413,7 @@ WLED provides convenience wrappers with automatic fallback. **Always prefer thes
### PSRAM guidelines
- **Check availability**: always test `psramFound()` before assuming PSRAM is present.
- **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
+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.
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 standards-based UDP multicast/broadcast paths solely for lacking authentication when authentication is not defined in the protocol specification.
### FW6: Fail-open behavior after parse or allocation errors
- **Severity**: IMPORTANT
- On error, reject update and preserve safe previous state.
- Explicitly check parse status (`DeserializationError error = deserializeJson(...); if (error) return/reject;`) and avoid silently applying unsafe zero/default values to safety-relevant fields (for example LED count and pin assignment).
### FW7: Heap churn in hot paths
- **Severity**: IMPORTANT
- Avoid repeated dynamic allocation in render/effect loops; prefer pre-allocation and reuse.
- Flag allocation patterns in loop and ISR-adjacent paths that can trigger fragmentation or timing instability.
### FW8: Unsafe use of `String` in performance-critical paths
- **Severity**: IMPORTANT
- In hot paths, avoid repeated `String` growth; reserve or use fixed buffers.
- Flag repeated `String` concatenation inside loop-heavy or ISR-adjacent code.
### FW9: Unsafe feature flag names
- **Severity**: IMPORTANT
- Verify all new `WLED_ENABLE_*`/`WLED_DISABLE_*` names are valid known flags; typos silently alter build behavior.
### FW10: OTA integrity verification (without TLS requirement)
- **Severity**: IMPORTANT
- OTA update flows should validate firmware integrity using the checksum/hash/signature mechanism available in the firmware/platform implementation.
- Do not require TLS/certificate pinning as a mandatory review criterion.
- In OTA paths (`Update.begin()`, `Update.write()`, and related flows), flag flashing without integrity verification.
### FW11: FreeRTOS task stack and recursion safety
- **Severity**: IMPORTANT
- In `xTaskCreate`/`xTaskCreatePinnedToCore` tasks that process `String`/JSON-heavy data, verify stack-size sufficiency and avoid unbounded recursion.
### FW12: mDNS and hostname sanitization
- **Severity**: IMPORTANT
- For `MDNS.begin()`, `MDNS.addService()`, and `ArduinoOTA.setHostname()`, ensure user-provided hostnames are RFC-compliant (letters/digits/hyphen, no leading/trailing hyphen) and clamped to 63 characters.
### FW13: Outbound URL validation (no HTTPS requirement)
- **Severity**: SUGGESTION
- When using user-provided URL strings with `HTTPClient.begin()`/equivalent, validate scheme/format and constrain host targets (allowlist or equivalent policy).
- Do not require HTTPS/TLS as a baseline review rule.
### FW14: Optional unicast UDP source filtering
- **Severity**: SUGGESTION
- For unicast UDP receive paths, prefer optional user-configurable source filtering.
- Do not require this for multicast/broadcast protocol flows.
## Web UI Security (`wled00/data/*`, OWASP A01/A02/A05)
### WEB1: DOM XSS through `innerHTML`
- **Severity**: CRITICAL
- Prefer `textContent`; if HTML is required, sanitize trusted content path explicitly.
### WEB2: Dynamic code execution
- **Severity**: CRITICAL
- Reject `eval`, `new Function`, and string-based timer execution.
### WEB3: `postMessage` without origin validation
- **Severity**: IMPORTANT
- Require strict origin allowlist checks before processing payloads.
### WEB4: Unsafe redirects/navigation
- **Severity**: IMPORTANT
- Do not navigate directly from untrusted query/input without relative-path or allowlist checks.
### WEB5: Client-only validation
- **Severity**: IMPORTANT
- UI validation is not sufficient; equivalent firmware-side validation is required.
### WEB6: Direct DOM insertion from fetched/config data
- **Severity**: IMPORTANT
- Treat fetched and config-derived strings as untrusted unless proven otherwise.
### WEB7: CSRF checks for state-changing HTTP routes (advisory)
- **Severity**: SUGGESTION
- For state-changing HTTP routes (for example `/json/state`, `/win`), prefer `Origin`/`Referer` header validation as low-cost defense-in-depth for deployments that are not directly internet-exposed.
- Treat this as advisory only, since some legitimate clients may omit these headers.
## Secrets and Logging (OWASP A04/A09/A10)
### SEC1: Hardcoded secrets and credentials
- **Severity**: CRITICAL
- Reject committed API keys, passwords, tokens, private keys, or test backdoors with potential security impact.
### SEC2: Sensitive values in logs
- **Severity**: CRITICAL
- Do not log passwords, tokens, Wi-Fi keys, auth headers, or full sensitive payloads.
### SEC3: Insecure defaults
- **Severity**: IMPORTANT
- Reject new default credentials or insecure auto-enable behavior for privileged functions.
- For setup/onboarding flows, require first-change behavior for default credentials where applicable.
### SEC4: Overly detailed error responses
- **Severity**: IMPORTANT
- Avoid exposing stack traces or internal details to API/UI consumers.
### SEC5: Credential exposure in API/config responses
- **Severity**: IMPORTANT
- Flag API/config serialization that exposes password-like fields (for example Wi-Fi/AP/MQTT passwords) to unauthenticated or untrusted clients.
### SEC6: Security-relevant event logging coverage
- **Severity**: SUGGESTION
- Prefer explicit logging for auth failures, OTA attempts, config resets, and AP activation events, without logging secret values.
## Supply Chain and CI/CD (OWASP A03/A08)
### SC1: New dependency risk
- **Severity**: IMPORTANT
- Review new npm/pip/PlatformIO dependencies for legitimacy, pinning, and known vulnerabilities.
### SC2: Workflow permission hardening regressions
- **Severity**: IMPORTANT
- Check for broad `permissions`, unpinned third-party actions, or unsafe secret exposure.
- Flag mutable third-party action refs (`@main`, `@master`, broad tags) where SHA pinning is expected by project policy.
- Flag overly broad permissions such as `write-all` without clear need.
### SC3: Script injection in workflows
- **Severity**: IMPORTANT
- Avoid direct interpolation of untrusted `${{ github.event.* }}` values in `run` commands.
## Reviewer Checklist
- [ ] No new memory-safety hazards (bounds, overflow, unsafe copies/format strings)
- [ ] External input is validated and range-clamped at ingress points (HTTP/JSON, WebSocket, UDP, TCP, serial, ESP-NOW)
- [ ] State-changing API paths enforce auth policy
- [ ] OTA paths enforce integrity verification (without requiring TLS baseline)
- [ ] Suggested rule patterns are checked where relevant (UDP bounds, hostname sanitization, workflow pinning/permissions)
- [ ] Web UI changes avoid unsafe DOM execution/injection patterns
- [ ] No secrets added; no sensitive logging introduced
- [ ] Error handling remains fail-safe and non-leaky
- [ ] Dependency/workflow changes are supply-chain safe
- [ ] Feature-flag names are valid and not typoed
## AI Review Behavior
- Prefer concrete, file/line-specific findings over generic guidance.
- Prioritize **CRITICAL** and **IMPORTANT** findings.
- Skip irrelevant framework checks not used by WLED.
- If control-flow trust is unclear, ask for clarification instead of guessing.
+6
View File
@@ -23,6 +23,12 @@ applyTo: "wled00/data/**"
**Reuse shared helpers from `common.js` whenever possible** instead of duplicating utilities in page-local scripts.
## Accessibility & Interaction
The WLED web UI targets commonly used browser/platform combinations: desktop browsers on Mac and PC (primarily pointer-driven, touch rare),
and touch-only devices (phones, tablets). If possible, keep the UI accessible to users with disabilities.
Full keyboard operability is not a strict requirement - adding keyboard shortcuts should be a case-by-case decision.
## Build Integration
Files in this directory are processed by `tools/cdata.js` into generated headers
+201 -122
View File
@@ -30,6 +30,7 @@ default_envs = nodemcuv2
esp32s3dev_16MB_opi
esp32s3dev_8MB_opi
esp32s3dev_8MB_qspi
esp32s3dev_8MB_none
esp32s3_4M_qspi
usermods
@@ -38,42 +39,9 @@ data_dir = ./wled00/data
build_cache_dir = ~/.buildcache
extra_configs =
platformio_override.ini
platformio_release.ini
[common]
# ------------------------------------------------------------------------------
# PLATFORM:
# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266
#
# arduino core 2.6.3 = platformIO 2.3.2
# arduino core 2.7.0 = platformIO 2.5.0
# ------------------------------------------------------------------------------
arduino_core_2_6_3 = espressif8266@2.3.3
arduino_core_2_7_4 = espressif8266@2.6.2
arduino_core_3_0_0 = espressif8266@3.0.0
arduino_core_3_0_2 = espressif8266@3.2.0
arduino_core_3_1_0 = espressif8266@4.1.0
arduino_core_3_1_2 = espressif8266@4.2.1
# Development platforms
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
# Platform to use for ESP8266
platform_wled_default = ${common.arduino_core_3_1_2}
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
#platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502
platformio/tool-esptool #@ ~1.413.0
platformio/tool-esptoolpy #@ ~1.30000.0
## previous platform for 8266, in case of problems with the new one
## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_0_2, which does not support Ucs890x
;; platform_wled_default = ${common.arduino_core_3_0_2}
;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
;; platformio/toolchain-xtensa @ ~2.40802.200502
;; platformio/tool-esptool @ ~1.413.0
;; platformio/tool-esptoolpy @ ~1.30000.0
# ------------------------------------------------------------------------------
# FLAGS: DEBUG
# esp8266 : see https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
@@ -192,6 +160,41 @@ lib_deps =
extra_scripts = ${scripts_defaults.extra_scripts}
[esp8266]
# ------------------------------------------------------------------------------
# PLATFORM:
# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266
#
# arduino core 2.6.3 = platformIO 2.3.2
# arduino core 2.7.0 = platformIO 2.5.0
# ------------------------------------------------------------------------------
arduino_core_2_6_3 = espressif8266@2.3.3
arduino_core_2_7_4 = espressif8266@2.6.2
arduino_core_3_0_0 = espressif8266@3.0.0
arduino_core_3_0_2 = espressif8266@3.2.0
arduino_core_3_1_0 = espressif8266@4.1.0
arduino_core_3_1_2 = espressif8266@4.2.1
# Development platforms
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
# Platform to use for ESP8266
platform_wled_default = ${esp8266.arduino_core_3_1_2}
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
#platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502
platformio/tool-esptool #@ ~1.413.0
platformio/tool-esptoolpy #@ ~1.30000.0
## previous platform for 8266, in case of problems with the new one
## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_0_2, which does not support Ucs890x
;; platform_wled_default = ${esp8266.arduino_core_3_0_2}
;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
;; platformio/toolchain-xtensa @ ~2.40802.200502
;; platformio/tool-esptool @ ~1.413.0
;; platformio/tool-esptoolpy @ ~1.30000.0
platform = ${esp8266.platform_wled_default}
build_unflags = ${common.build_unflags}
build_flags =
-DESP8266
@@ -221,8 +224,11 @@ lib_deps =
ESPAsyncTCP @ 1.2.2
ESPAsyncUDP
ESP8266PWM
https://github.com/tignioj/ArduinoUZlib.git#20aff95cd80c141f80bdbf66895409a0046d2c2f
${env.lib_deps}
monitor_filters = esp8266_exception_decoder
;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48
build_flags_compat =
-DESP8266
@@ -251,6 +257,7 @@ lib_deps_compat =
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.9
https://github.com/blazoncek/QuickESPNow.git#optional-debug
https://github.com/tignioj/ArduinoUZlib.git#20aff95cd80c141f80bdbf66895409a0046d2c2f
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
[esp32_all_variants]
@@ -269,6 +276,7 @@ platform_packages =
build_unflags = ${common.build_unflags}
build_flags = ${esp32_idf_V4.build_flags}
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
@@ -321,6 +329,7 @@ build_flags = -g
lib_deps =
${esp32_idf_V4.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
monitor_filters = esp32_exception_decoder
[esp32c3]
;; generic definitions for all ESP32-C3 boards
@@ -340,6 +349,7 @@ lib_deps =
${esp32_idf_V4.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[esp32s3]
;; generic definitions for all ESP32-S3 boards
@@ -359,6 +369,8 @@ build_flags = -g
lib_deps =
${esp32_idf_V4.lib_deps}
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
upload_speed = 921600
monitor_filters = esp32_exception_decoder
# ------------------------------------------------------------------------------
@@ -366,15 +378,12 @@ board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8
# ------------------------------------------------------------------------------
[env:nodemcuv2]
extends = esp8266
board = nodemcuv2
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
custom_usermods = audioreactive
[env:nodemcuv2_compat]
extends = env:nodemcuv2
@@ -384,6 +393,7 @@ platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9
custom_usermods = audioreactive
[env:nodemcuv2_160]
extends = env:nodemcuv2
@@ -393,15 +403,13 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
custom_usermods = audioreactive
[env:esp8266_2m]
extends = esp8266
board = esp_wroom_02
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\"
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PARTICLESYSTEM1D
lib_deps = ${esp8266.lib_deps}
custom_usermods = audioreactive
[env:esp8266_2m_compat]
extends = env:esp8266_2m
@@ -411,6 +419,7 @@ platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
[env:esp8266_2m_160]
extends = env:esp8266_2m
@@ -420,18 +429,37 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive
[env:esp8266_2m_min]
;; Minimal-feature build for ESP02 (2MB flash).
;; Use this to recover from a failed OTA: flash via serial, then OTA-upload the regular esp8266_2m binary.
;; OTA is intentionally kept enabled. All other optional features are stripped to minimise binary size.
extends = esp8266
board = esp_wroom_02
board_build.ldscript = ${common.ldscript_2m512k}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_min\"
-D WLED_DISABLE_ALEXA
-D WLED_DISABLE_HUESYNC
-D WLED_DISABLE_INFRARED
-D WLED_DISABLE_MQTT
-D WLED_DISABLE_ADALIGHT
-D WLED_DISABLE_LOXONE
-D WLED_DISABLE_WEBSOCKETS
-D WLED_DISABLE_ESPNOW
-D WLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PIXELFORGE
-D WLED_DISABLE_IMPROV_WIFISCAN
[env:esp01_1m_full]
extends = esp8266
board = esp01_1m
platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PIXELFORGE
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full_compat]
extends = env:esp01_1m_full
@@ -454,15 +482,11 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
custom_usermods = audioreactive
[env:esp32dev]
extends = esp32
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
custom_usermods = audioreactive
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
@@ -474,50 +498,33 @@ build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_DEBUG\"
[env:esp32dev_8M]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
extends = env:esp32dev
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.large_partitions}
board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608
; board_build.f_flash = 80000000L
board_build.flash_mode = dio
[env:esp32dev_16M]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
extends = env:esp32dev
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.extreme_partitions}
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.f_flash = 80000000L
board_build.flash_mode = dio
[env:esp32_eth]
extends = esp32
board = esp32-poe
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
-D SR_DMTYPE=-1 -D AUDIOPIN=-1 -D I2S_SDPIN=-1 -D I2S_WSPIN=-1 -D I2S_CKPIN=-1 -D MCLK_PIN=-1 ;; force AR to not allocate any PINs at startup
-D DATA_PINS=4 ;; default led pin = 16 conflicts with pins used for ethernet
; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only => uncomment if your board uses ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
@@ -528,20 +535,14 @@ board_build.f_flash = 80000000L
board_build.flash_mode = qio
board_build.partitions = ${esp32.extended_partitions}
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\"
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
-DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html
-D DATA_PINS=25
lib_deps = ${esp32_idf_V4.lib_deps}
[env:esp32c3dev]
extends = esp32c3
platform = ${esp32c3.platform}
platform_packages = ${esp32c3.platform_packages}
framework = arduino
board = esp32-c3-devkitm-1
board_build.partitions = ${esp32.default_partitions}
custom_usermods = audioreactive
build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\"
-D WLED_WATCHDOG_TIMEOUT=0
@@ -549,8 +550,6 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=
-DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB
;-DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip
upload_speed = 460800
build_unflags = ${common.build_unflags}
lib_deps = ${esp32c3.lib_deps}
board_build.flash_mode = dio ; safe default, required for OTA updates to 0.16 from older version which used dio (must match the bootloader!)
[env:esp32c3dev_qio]
@@ -560,45 +559,34 @@ board_build.flash_mode = qio ; qio is faster and works on almost all boards (som
[env:esp32s3dev_16MB_opi]
;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
extends = esp32s3
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\"
-D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.extreme_partitions}
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32s3dev_8MB_opi]
;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
extends = esp32s3
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\"
-D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.large_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32s3dev_8MB_qspi]
;; generic ESP32-S3 board with 8MB FLASH and PSRAM (memory_type: qio_qspi). Try this one if esp32s3dev_8MB_opi does not work on your board
@@ -611,18 +599,27 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
;; -DLOLIN_WIFI_FIX ;; uncomment if you have WiFi connectivity problems
monitor_filters = esp32_exception_decoder
[env:esp32s3dev_8MB_none]
;; ESP32-S3 development board, 8MB FLASH, no PSRAM
extends = esp32s3
board = esp32-s3-devkitc-1 ;; generic dev board
custom_usermods = audioreactive
build_unflags = ${esp32s3.build_unflags} -DBOARD_HAS_PSRAM ;; make sure PSRAM support is removed
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_none\"
-D WLED_WATCHDOG_TIMEOUT=0
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
-D ARDUINO_USB_CDC_ON_BOOT=1 ;; -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
board_build.f_flash = 80000000L
board_build.flash_mode = qio
[env:esp32S3_wroom2]
;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1
;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi)
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
extends = esp32s3
board = esp32s3camlcd ;; this is the only standard board with "opi_opi"
board_build.arduino.memory_type = opi_opi
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\"
-D WLED_WATCHDOG_TIMEOUT=0
-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
@@ -632,12 +629,9 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
-D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1
;;-D WLED_DEBUG
-D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.extreme_partitions}
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
monitor_filters = esp32_exception_decoder
[env:esp32S3_wroom2_32MB]
;; For ESP32-S3 WROOM-2 with 32MB Flash, and >= 8MB PSRAM (memory_type: opi_opi)
@@ -654,36 +648,42 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
board_build.partitions = tools/WLED_ESP32_32MB.csv
board_upload.flash_size = 32MB
board_upload.maximum_size = 33554432
monitor_filters = esp32_exception_decoder
[env:esp32s3_4M_qspi]
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
platform = ${esp32s3.platform}
platform_packages = ${esp32s3.platform_packages}
upload_speed = 921600
extends = esp32s3
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\"
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
lib_deps = ${esp32s3.lib_deps}
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
[env:esp32s3_4M_none]
;; ESP32-S3 with 4MB FLASH, no PSRAM
extends = esp32s3
board = esp32-s3-devkitc-1
custom_usermods = audioreactive
build_unflags = ${esp32s3.build_unflags} -D BOARD_HAS_PSRAM ;; make sure PSRAM support is removed
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_none\"
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
[env:lolin_s2_mini]
platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages}
extends = esp32s2
board = lolin_s2_mini
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = qio
board_build.f_flash = 80000000L
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\"
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MSC_ON_BOOT=0
@@ -698,18 +698,97 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=
-D HW_PIN_DATASPI=11
-D HW_PIN_MISOSPI=9
; -D STATUSLED=15
lib_deps = ${esp32s2.lib_deps}
[env:usermods]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
extends = env:esp32dev
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_USERMODS\"
-DTOUCH_CS=9
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dio
custom_usermods = * ; Expands to all usermods in usermods folder
board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat
# ------------------------------------------------------------------------------
# Hub75 examples
# ------------------------------------------------------------------------------
# Note: some panels may experience ghosting with default full brightness. use -D WLED_HUB75_MAX_BRIGHTNESS=239 or lower to fix it.
[hub75]
;; Shared values for all HUB75 build envs.
;; Core HUB75 flags - common to every HUB75 build
build_flags =
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D WLED_DEBUG_BUS
-D LED_TYPES=TYPE_HUB75MATRIX_HS
; -D WLED_DEBUG
;; Default I2S mic pins disabled (HUB75 uses GPIOs that would otherwise clash).
;; Envs that wire up a real mic should NOT include this and define I2S_*PIN themselves.
i2s_disable_flags = -D SR_DMTYPE=1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
;; Pinned HUB75 driver libraries
lib_deps = https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#f17fb7fe9d487e9643f919eb5aeedea8d9d1f8d7 ;; 3.0.14
;; Extra flags shared by all S3-based HUB75 builds
s3_build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1 ;; -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this (sets lower TX power)
-D WLED_WATCHDOG_TIMEOUT=0
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
[env:esp32dev_hub75]
extends = env:esp32dev
upload_speed = 921600
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} ${hub75.build_flags} ${hub75.i2s_disable_flags}
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
-D WLED_RELEASE_NAME=\"ESP32_HUB75\"
lib_deps = ${esp32_idf_V4.lib_deps}
${hub75.lib_deps}
[env:esp32dev_hub75_forum_pinout]
extends = env:esp32dev_hub75
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} ${hub75.build_flags} ${hub75.i2s_disable_flags}
-DARDUINO_USB_CDC_ON_BOOT=0
-D WLED_RELEASE_NAME=\"ESP32_HUB75_forum_pinout\"
-D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins
[env:esp32s3dev_4MB_qspi_hub75]
; HD-WF2 - NOTE: this board has NO PSRAM, so BOARD_HAS_PSRAM must not be set
; (BOARD_HAS_PSRAM causes the DMA library to allocate only in SPIRAM, which fails without PSRAM)
extends = env:esp32s3dev_8MB_qspi
board_build.partitions = ${esp32.extended_partitions} ;; 1.65MB firmware, 700KB filesystem - only 4MB flash usable on this board
build_unflags = ${esp32s3.build_unflags} -DBOARD_HAS_PSRAM
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags} ${hub75.i2s_disable_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_HD-WF2\"
-D HD_WF2_PINOUT ;; Huidu HD-WF2 specific GPIO wiring
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
[env:adafruit_matrixportal_esp32s3]
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
extends = env:esp32s3dev_8MB_qspi
board = adafruit_matrixportal_esp32s3_wled ; modified board definition: removed flash section that causes FS erase on upload
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags} ${hub75.i2s_disable_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_Adafruit_Matrixportal\"
-D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
;; board_build.partitions = tools/partitions-8MB_spiffs-tinyuf2.csv ;; supports adafruit UF2 bootloader
[env:esp32s3dev_16MB_opi_hub75]
;; MOONHUB HUB75 adapter board (lilygo T7-S3 with 16MB flash and octal PSRAM)
extends = env:esp32s3dev_8MB_opi
board = lilygo-t7-s3
board_build.partitions = ${esp32.extreme_partitions} ;; for 16MB flash (overrides large_partitions for 8MB)
;; Note: real I2S mic pins are wired here, so we do NOT include ${hub75.i2s_disable_flags}.
build_flags = ${common.build_flags} ${esp32s3.build_flags} ${hub75.build_flags} ${hub75.s3_build_flags}
-D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi_HUB75\"
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
-D LEDPIN=14 -D BTNPIN=0 -D RLYPIN=15 -D IRPIN=-1 -D AUDIOPIN=-1 ;; defaults that avoid pin conflicts with HUB75
-D SR_DMTYPE=1 -D I2S_SDPIN=10 -D I2S_CKPIN=11 -D I2S_WSPIN=12 -D MCLK_PIN=-1 ;; I2S mic
lib_deps = ${esp32s3.lib_deps}
${hub75.lib_deps}
+61 -155
View File
@@ -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.
@@ -33,7 +33,12 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags}
;
; *** To use the below defines/overrides, copy and paste each onto its own line just below build_flags in the section above.
;
; *** Note: on adding custom usermods
; custom_usermods entries are a separate key in the env section (not inside build_flags). Place them on their own line in the env.
; Multiple usermods are separated by a space, the names can be found in the library.json file of the usermod.
; Example: use the default usermods from the esp32dev env and add the Temperature and four_line_display_ALT usermods
; custom_usermods = ${env:esp32dev.custom_usermods} Temperature four_line_display_ALT
;
; Set a release name that may be used to distinguish required binary for flashing
; -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
;
@@ -98,17 +103,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 +123,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 +151,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
;
@@ -193,7 +198,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,8 +220,8 @@ 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}
@@ -223,8 +229,8 @@ 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}
@@ -234,8 +240,8 @@ 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}
@@ -243,8 +249,8 @@ 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
@@ -294,8 +300,8 @@ 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
@@ -303,8 +309,8 @@ 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
@@ -312,8 +318,8 @@ 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.
@@ -322,8 +328,8 @@ 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}
@@ -334,8 +340,8 @@ 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}
@@ -343,8 +349,8 @@ 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
@@ -374,6 +380,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 +388,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]
@@ -412,22 +415,22 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNP
[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
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
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
@@ -436,8 +439,8 @@ 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
@@ -446,8 +449,8 @@ 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
@@ -455,8 +458,8 @@ 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
@@ -464,8 +467,8 @@ 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
@@ -473,11 +476,12 @@ 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
lib_deps = ${esp8266.lib_deps}
# ------------------------------------------------------------------------------
@@ -487,8 +491,8 @@ 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}
@@ -496,8 +500,8 @@ 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}
@@ -554,102 +558,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
+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');
});
});
});
});
+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,4 +1,4 @@
{
"name:": "ST7789_display",
"name": "ST7789_display",
"build": { "libArchive": false }
}
+57 -42
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
@@ -879,6 +881,9 @@ class AudioReactive : public Usermod {
#endif
static const char _digitalmic[];
static const char _addPalettes[];
static const char _palName0[];
static const char _palName1[];
static const char _palName2[];
static const char UDP_SYNC_HEADER[];
static const char UDP_SYNC_HEADER_v1[];
@@ -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");
@@ -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();
}
@@ -2123,6 +2134,9 @@ class AudioReactive : public Usermod {
uiScript.print(F("addOption(dd,'Generic PDM',5);"));
#endif
uiScript.print(F("addOption(dd,'ES8388',6);"));
uiScript.print(F("addOption(dd,'None - network receive only',"));
uiScript.print(SR_DMTYPE_NETWORK_ONLY);
uiScript.print(F(");"));
uiScript.print(F("dd=addDropdown(ux,'config:AGC');"));
uiScript.print(F("addOption(dd,'Off',0);"));
@@ -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);
}
}
@@ -2282,7 +2294,10 @@ const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel";
const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
#endif
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic";
const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes";
const char AudioReactive::_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
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name:": "pov_display",
"name": "pov_display",
"build": { "libArchive": false},
"platforms": ["espressif32"]
}
+15 -13
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
@@ -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.
@@ -5451,6 +5452,7 @@ void mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://na
}
}
}
return;
}
// Repeat detection
@@ -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
+22 -7
View File
@@ -228,12 +228,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 +274,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 {
@@ -585,7 +594,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)
+9
View File
@@ -858,6 +858,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)
@@ -1078,6 +1086,7 @@ uint32_t BusHub75Matrix::getPixelColor(unsigned pix) const {
void BusHub75Matrix::setBrightness(uint8_t b) {
_bri = b;
if (!_valid || !display) return;
display->setBrightness(_bri);
}
+1 -1
View File
@@ -171,7 +171,7 @@ class Bus {
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 5 : is2Pin(type) + 1; } // credit @PaoloTK
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);
+15 -3
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);
@@ -693,8 +695,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;
+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) {
+22 -5
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
@@ -157,9 +165,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"
@@ -382,7 +395,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 +412,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
@@ -481,6 +497,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
+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;
}
+7 -13
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>
@@ -331,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");
@@ -470,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);
@@ -594,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) {}
+27 -8
View File
@@ -666,9 +666,9 @@ function parseInfo(i) {
// note: style.display='none' for option elements is not supported on all browsers (notably iOS)
bsSel.replaceChildren(...bsOpts.filter(o => isM || o.dataset.type !== "2D").map(o => o.cloneNode(true))); // allow all in matrix mode, filter 2D blends otherwise
if (!isM) {
gId("filter2D").classList.add('hide'); // hide 2D effects in non-matrix mode
gId("filter2D").classList.add('hide'); // hide 2D effects in non-matrix mode
} else {
gId("filter2D").classList.remove('hide');
gId("filter2D").classList.remove('hide');
}
gId("updBt").style.display = (i.opt & 1) ? '':'none';
// if (i.noaudio) {
@@ -989,21 +989,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()
@@ -2817,7 +2835,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();
@@ -2829,7 +2847,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
+2 -2
View File
@@ -424,7 +424,7 @@ let fL; // file list
const classics=['console_font_4x6.wbf','console_font_5x12.wbf','console_font_5x8.wbf','console_font_6x8.wbf','console_font_7x9.wbf']; // classic WLED fonts list
let pT = []; // local tools list from JSON
let wv = [0, 0]; // wled version [major, minor], updated in fsMem(), used to check tool compatibility
const remoteURL = 'https://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++){
+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">
+10 -33
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)) {
@@ -50,11 +48,9 @@
d.ro_gpio = [];
d.extra = [];
}, ()=>{
for (let r of d.rsvd) { pins.push(r); pinO.push("rsvd"); } // reserved pins
if (d.um_p[0]==-1) d.um_p.shift(); // remove filler
d.Sf.SDA.max = d.Sf.SCL.max = d.Sf.MOSI.max = d.Sf.SCLK.max = d.Sf.MISO.max = d.max_gpio;
//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 +86,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 +136,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 +181,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
}
});
}
+4 -2
View File
@@ -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>
+71 -9
View File
@@ -7,6 +7,7 @@
<script>
function B() { window.history.back(); }
var cnfr = false;
var deviceInfo = null;
function cR() {
if (!cnfr) {
var bt = gId('rev');
@@ -22,15 +23,30 @@
fetch(getURL('/json/info'))
.then(response => response.json())
.then(data => {
deviceInfo = data;
document.querySelector('.installed-version').textContent = `${data.brand} ${data.ver} (${data.vid})`;
const repoUrl = data.repo && data.repo !== "unknown" ? "https://github.com/" + data.repo + "/releases/latest" : null;
document.querySelector('.release-name').textContent = data.release;
// assemble update URL and update release badge
if (data.repo && data.repo !== "unknown") {
const repoUrl = "https://github.com/" + data.repo + "/releases/latest";
if (repoUrl) {
const badgeUrl = "https://img.shields.io/github/release/" + data.repo + ".svg?style=flat-square";
document.querySelector('.release-repo').href = repoUrl;
document.querySelector('.release-badge').src = badgeUrl;
toggle('release-download'); // only show release download item after receiving a valid data.repo
// fetch latest release from GitHub to build direct bin download link
fetch(`https://api.github.com/repos/${data.repo}/releases/latest`)
.then(r => r.json())
.then(gh => {
const ver = gh.tag_name.replace(/^v/, '');
const suffix = `_${ver}_${data.release}.bin`;
const asset = gh.assets.find(a => a.name.endsWith(suffix));
if (asset) {
const binUrl = asset.browser_download_url.replace(/^https?:\/\/[^/]+/, 'https://download.wled.me');
const el = document.querySelector('.release-name');
el.innerHTML = `<a href="${binUrl}">${data.release}</a>`;
}
})
.catch(() => {}); // silently ignore if GitHub API unavailable
} else {
gId('Norelease-download').classList.add("hide"); // repo invalid -> hide everything
}
@@ -60,6 +76,48 @@
gId('bootupd').classList.add("hide");
toggle('upd');
}
async function autoUpdate() {
const btn = gId('autoUpdBtn');
const status = gId('autoUpdStatus');
btn.disabled = true;
status.textContent = '';
try {
const info = deviceInfo;
if (!info || !info.repo || info.repo === 'unknown') {
status.textContent = 'No release repository available for this build.';
btn.disabled = false;
return;
}
status.textContent = 'Checking GitHub for latest release...';
const ghResp = await fetch(`https://api.github.com/repos/${info.repo}/releases/latest`);
if (!ghResp.ok) throw new Error(`GitHub API error: ${ghResp.status}`);
const ghRelease = await ghResp.json();
const releaseVer = ghRelease.tag_name.replace(/^v/, '');
const assetSuffix = `_${releaseVer}_${info.release}.bin`;
const asset = ghRelease.assets.find(a => a.name.endsWith(assetSuffix));
if (!asset) {
const available = ghRelease.assets.map(a => a.name).join(', ');
status.textContent = `Firmware not found (*${assetSuffix}). Available: ${available}`;
btn.disabled = false;
return;
}
status.textContent = `Downloading ${asset.name} (${Math.round(asset.size/1024)} KB)...`;
const fwUrl = asset.browser_download_url.replace(/^https?:\/\/[^/]+/, 'https://download.wled.me');
const fwResp = await fetch(fwUrl);
if (!fwResp.ok) throw new Error(`Download failed: ${fwResp.status}`);
const blob = await fwResp.blob();
const file = new File([blob], asset.name, {type: 'application/octet-stream'});
const dt = new DataTransfer();
dt.items.add(file);
document.querySelector('input[name="update"]').files = dt.files;
status.textContent = `Downloaded ${asset.name}. Uploading to device...`;
hideforms();
gId('upd').submit();
} catch(e) {
status.textContent = 'Auto-update failed: ' + e.message;
btn.disabled = false;
}
}
</script>
<style>
@import url("style.css");
@@ -77,21 +135,25 @@
<p>
Installed version: <span class="sip installed-version">Loading...</span><br>
Release: <span class="sip release-name">Loading...</span><br>
<span id="Norelease-download">Latest binary: Checking...<br></span>
<span id="Norelease-download">Latest release: Checking...<br></span>
<span id="release-download" class="hide">
Download the latest binary: <a class="release-repo" href="https://github.com/wled/WLED/releases/latest" target="_blank" rel="noopener noreferrer"
Latest version: <a class="release-repo" href="https://github.com/wled/WLED/releases/latest" target="_blank" rel="noopener noreferrer"
style="vertical-align: text-bottom; display: inline-flex;">
<img class="release-badge" alt="badge"></a><br> <!-- start with an empty placeholder, src will be filled after fetching /json/info -->
<img class="release-badge" alt="View latest"></a><br>
<button type="button" id="autoUpdBtn" onclick="autoUpdate()">Auto update</button>
<span id="autoUpdStatus" style="display:block;margin-top:4px;font-size:13px;"></span>
</span>
</p>
<hr class="sml">
<h3>Manual upload</h3>
<input type="hidden" name="skipValidation" value="" id="sV">
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
<input type='checkbox' onchange="sV.value=checked?1:''" id="skipValidation">
<label for='skipValidation'>Ignore firmware validation</label><br>
<button type="submit">Update WLED!</button><br>
<span id="rev">
<button type="submit">Upload</button><br>
<span>
<hr class="sml">
<button type="button" onclick="cR()">Revert update</button><br>
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
</span>
</div>
</form>
@@ -107,4 +169,4 @@
<div id="Noupd" class="hide sec"><b>Updating...</b><br>Please do not close or refresh the page :)</div>
<p><button id="backbtn" type="button" onclick="B()">Back</button></p>
</body>
</html>
</html>
+2 -2
View File
@@ -122,7 +122,7 @@ bool DMXInput::installDriver()
return true;
}
void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum)
void DMXInput::init(int8_t rxPin, int8_t txPin, int8_t enPin, uint8_t inputPortNum)
{
#ifdef WLED_ENABLE_DMX_OUTPUT
@@ -142,7 +142,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo
return;
}
if (rxPin > 0 && enPin > 0 && txPin > 0) {
if (rxPin >= 0 && enPin >= 0 && txPin >= 0) {
const managed_pin_type pins[] = {
{(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false.
+5 -5
View File
@@ -12,7 +12,7 @@
class DMXInput
{
public:
void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum);
void init(int8_t rxPin, int8_t txPin, int8_t enPin, uint8_t inputPortNum);
void update();
/**disable dmx receiver (do this before disabling the cache)*/
@@ -54,9 +54,9 @@ private:
friend void dmxReceiverTask(void * context);
uint8_t inputPortNum = 255;
uint8_t rxPin = 255;
uint8_t txPin = 255;
uint8_t enPin = 255;
int8_t rxPin = -1;
int8_t txPin = -1;
int8_t enPin = -1;
/// is written to by the dmx receive task.
byte dmxdata[DMX_PACKET_SIZE];
@@ -72,5 +72,5 @@ private:
TaskHandle_t task;
/// Guards access to dmxData
std::mutex dmxDataLock;
};
+44 -16
View File
@@ -5,7 +5,7 @@
#define MAX_CHANNELS_PER_UNIVERSE 512
// forward declarations
static void handleDDPPacket(e131_packet_t* p);
static void handleDDPPacket(e131_packet_t* p, size_t packetLen);
static void handleArtnetPollReply(IPAddress ipAddress);
static void prepareArtnetPollReply(ArtPollReply *reply);
static void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t portAddress);
@@ -17,20 +17,31 @@ static void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16
//DDP protocol support, called by handleE131Packet
//handles RGB data only
static void handleDDPPacket(e131_packet_t* p) {
static void handleDDPPacket(e131_packet_t* p, size_t packetLen) {
static bool ddpSeenPush = false; // have we seen a push yet?
int lastPushSeq = e131LastSequenceNumber[0];
if (packetLen < DDP_HEADER_LEN) return; // too short to safely read any DDP header fields
// reject unsupported color data types (only RGB and RGBW are supported)
uint8_t maskedType = p->dataType & 0x3F; // mask out custom and reserved flags, only type bits are relevant
if (maskedType != DDP_TYPE_RGB24 && maskedType != DDP_TYPE_RGBW32) return;
//uint8_t maskedType = p->dataType & 0x3F; // mask out custom and reserved flags, only type bits are relevant
//if (maskedType != DDP_TYPE_RGB24 && maskedType != DDP_TYPE_RGBW32) return;
// reject status and config packets (not implemented)
if (p->destination == DDP_ID_STATUS || p->destination == DDP_ID_CONFIG) return;
// note: for maximum compatibility we do not reject unknonw or malformed data types but simply default to RGB24 and check there is enough data available in the packet to do so
// also we assume 8bit per channel and currently do not support other bit depths
//reject late packets belonging to previous frame (assuming 4 packets max. before push)
// reject control, status and config packets (not implemented)
if (p->destination == DDP_ID_CONTROL || p->destination == DDP_ID_STATUS || p->destination == DDP_ID_CONFIG) return;
// reject query and response packets (not implemented)
if (p->flags & (DDP_FLAGS_QUERY | DDP_FLAGS_REPLY)) return;
bool push = p->flags & DDP_FLAGS_PUSH; // push flag means "render now"
if (!push && (p->flags & DDP_FLAGS_STORAGE)) return; // reject "from storage" flag but still let the push flag pass if set along with it
//reject late packets belonging to previous frame (assuming 4 packets max. before push, if more are used and packets are very late, they are still accepted)
if (e131SkipOutOfSequence && lastPushSeq) {
int sn = p->sequenceNum & 0xF;
int sn = p->sequenceNum & 0xF; // sequence number is 4 bits, 1-15, 0 means unused
if (sn) {
if (lastPushSeq > 5) {
if (sn > (lastPushSeq -5) && sn < lastPushSeq) return;
@@ -40,7 +51,8 @@ static void handleDDPPacket(e131_packet_t* p) {
}
}
unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel)
unsigned ddpChannelsPerLed = 3; // default to RGB
if ((p->dataType & 0b00111000)>>3 == 0b011) ddpChannelsPerLed = 4; // RGBW data type (see DDP protocol definition)
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
start += DMXAddress / ddpChannelsPerLed;
@@ -50,8 +62,14 @@ static void handleDDPPacket(e131_packet_t* p) {
unsigned c = 0;
if (p->flags & DDP_FLAGS_TIME) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
// ensure the received packet is at least as long as the header claims
if (packetLen < DDP_HEADER_LEN + c + dataLen) {
DEBUG_PRINTLN(F("DDP packet incomplete"));
return;
}
unsigned numLeds = stop - start; // stop >= start is guaranteed
unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
unsigned maxDataIndex = numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
if (maxDataIndex > dataLen) {
DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting."));
return;
@@ -66,7 +84,6 @@ static void handleDDPPacket(e131_packet_t* p) {
}
}
bool push = p->flags & DDP_FLAGS_PUSH;
ddpSeenPush |= push;
if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display
e131NewData = true;
@@ -76,7 +93,7 @@ static void handleDDPPacket(e131_packet_t* p) {
}
//E1.31 and Art-Net protocol support
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen){
int uni = 0, dmxChannels = 0;
uint8_t* e131_data = nullptr;
@@ -84,24 +101,33 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
if (protocol == P_ARTNET)
{
if (packetLen < 10) return; // need at least art_opcode (offset 8, 2 bytes)
if (p->art_opcode == ARTNET_OPCODE_OPPOLL) {
handleArtnetPollReply(clientIP);
return;
}
if (packetLen < 18) return; // need art_length (offset 16, 2 bytes) for DMX data
uni = p->art_universe;
dmxChannels = htons(p->art_length);
const int artNetMaxData = (packetLen >= 18) ? (int)(packetLen - 18) : 0; // art_data at offset 18; clamp so e131_data[dmxChannels] stays in bounds
if (dmxChannels > artNetMaxData) dmxChannels = artNetMaxData;
if (dmxChannels > MAX_CHANNELS_PER_UNIVERSE) dmxChannels = MAX_CHANNELS_PER_UNIVERSE;
e131_data = p->art_data;
seq = p->art_sequence_number;
mde = REALTIME_MODE_ARTNET;
} else if (protocol == P_E131) {
if (packetLen < 126) return; // need up to property_values[0] (offset 125) and property_value_count (offset 123)
// Ignore PREVIEW data (E1.31: 6.2.6)
if ((p->options & 0x80) != 0) return;
dmxChannels = htons(p->property_value_count) - 1;
dmxChannels = htons(p->property_value_count) - 1; // on malformed packets, this can become negative, checked below
// DMX level data is zero start code. Ignore everything else. (E1.11: 8.5)
if (dmxChannels == 0 || p->property_values[0] != 0) return;
if (dmxChannels <= 0 || p->property_values[0] != 0) return;
uni = htons(p->universe);
e131_data = p->property_values;
seq = p->sequence_number;
const int e131MaxData = (packetLen > 126) ? (int)(packetLen - 126) : 0; // property_values at offset 125; clamp so e131_data[dmxChannels] stays in bounds
if (dmxChannels > e131MaxData) dmxChannels = e131MaxData;
if (dmxChannels > MAX_CHANNELS_PER_UNIVERSE) dmxChannels = MAX_CHANNELS_PER_UNIVERSE;
if (e131Priority != 0) {
if (p->priority < e131Priority ) return;
// track highest priority & skip all lower priorities
@@ -110,15 +136,17 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
}
} else { //DDP
realtimeIP = clientIP;
handleDDPPacket(p);
handleDDPPacket(p, packetLen);
return;
}
#ifdef WLED_ENABLE_DMX
// does not act on out-of-order packets yet
if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) {
// Art-Net: art_data is 0-indexed (channel 1 at index 0)
// E1.31: property_values[0] is start code, (channel 1 at index 1)
for (uint16_t i = 1; i <= dmxChannels; i++)
dmx.write(i, e131_data[i]);
dmx.write(i, mde == REALTIME_MODE_ARTNET ? e131_data[i-1] : e131_data[i]);
dmx.update();
}
#endif
+5 -10
View File
@@ -100,7 +100,7 @@ void initDMXInput();
void handleDMXInput();
//e131.cpp
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol);
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen);
void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses);
// void handleArtnetPollReply(IPAddress ipAddress); // local function, only used in e131.cpp
// void prepareArtnetPollReply(ArtPollReply* reply); // local function, only used in e131.cpp
@@ -414,11 +414,6 @@ namespace UsermodManager {
// Register usermods by building a static list via a linker section
#define REGISTER_USERMOD(x) DYNARRAY_MEMBER(Usermod*, usermods, um_##x, 1) = &x
//usermod.cpp
void userSetup();
void userConnected();
void userLoop();
//util.cpp
#ifdef ESP8266
#define HW_RND_REGISTER RANDOM_REG32
@@ -524,10 +519,10 @@ inline size_t getFreeHeapSize() { return ESP.getFreeHeap(); } // returns free he
inline size_t getContiguousFreeHeap() { return ESP.getMaxFreeBlockSize(); } // returns largest contiguous free block
#endif
#define BFRALLOC_NOBYTEACCESS (1 << 0) // ESP32 has 32bit accessible DRAM (usually ~50kB free) that must not be byte-accessed
#define BFRALLOC_PREFER_DRAM (1 << 1) // prefer DRAM over PSRAM
#define BFRALLOC_ENFORCE_DRAM (1 << 2) // use DRAM only, no PSRAM
#define BFRALLOC_PREFER_PSRAM (1 << 3) // prefer PSRAM over DRAM
#define BFRALLOC_ENFORCE_PSRAM (1 << 4) // use PSRAM if available, otherwise uses DRAM
#define BFRALLOC_PREFER_DRAM (1 << 1) // prefer DRAM over PSRAM (can still use PSRAM for larger allocations if DRAM is starting to run low)
#define BFRALLOC_ENFORCE_DRAM (1 << 2) // use DRAM only, no PSRAM allowed
#define BFRALLOC_PREFER_PSRAM (1 << 3) // prefer PSRAM over DRAM (can still use DRAM if there is loads of free DRAM to optimize speed)
#define BFRALLOC_ENFORCE_PSRAM (1 << 4) // use PSRAM if available, falls back to DRAM if PSRAM fails
#define BFRALLOC_CLEAR (1 << 5) // clear allocated buffer after allocation
void *allocate_buffer(size_t size, uint32_t type);
+33 -8
View File
@@ -520,7 +520,9 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
if (root["win"].isNull() && getVal(root["ps"], presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) {
DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr);
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified)
// async load from file system (only preset ID was specified)
// avoid propogating CALL_MODE_INIT, which may cause accidental recursion
applyPreset(presetCycCurr, callMode == CALL_MODE_INIT ? CALL_MODE_DIRECT_CHANGE : callMode);
return stateResponse;
} else presetCycCurr = currentPreset; // restore presetCycCurr
}
@@ -774,8 +776,18 @@ void serializeInfo(JsonObject root)
root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = getPaletteCount();
root[F("cpalcount")] = customPalettes.size(); // number of custom palettes
root[F("cpalcount")] = customPalettes.size(); // number of user custom palettes (includes gray placeholders)
root[F("umpalcount")] = usermodPalettes.size(); // number of usermod-registered palettes
root[F("cpalmax")] = WLED_MAX_CUSTOM_PALETTES; // maximum number of custom palettes
// send usermod palette names so the UI can label them correctly
if (usermodPalettes.size() > 0) {
JsonArray umpalnames = root.createNestedArray(F("umpalnames"));
for (size_t j = 0; j < usermodPalettes.size(); j++) {
char buf[34];
extractModeName(WLED_USERMOD_PALETTE_ID_BASE - j, JSON_palette_names, buf, sizeof(buf) - 1);
umpalnames.add(buf);
}
}
JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
@@ -946,19 +958,28 @@ void serializePalettes(JsonObject root, int page)
#endif
const int customPalettesCount = customPalettes.size();
const int umPalettesCount = usermodPalettes.size();
const int palettesCount = FIXED_PALETTE_COUNT; // palettesCount is number of palettes, not palette index
const int maxPage = (palettesCount + customPalettesCount) / itemPerPage;
const int maxPage = (palettesCount + umPalettesCount + customPalettesCount) / itemPerPage;
if (page > maxPage) page = maxPage;
const int start = itemPerPage * page;
int end = min(start + itemPerPage, palettesCount + customPalettesCount);
int end = min(start + itemPerPage, palettesCount + umPalettesCount + customPalettesCount);
root[F("m")] = maxPage; // inform caller how many pages there are
JsonObject palettes = root.createNestedObject("p");
for (int i = start; i < end; i++) {
JsonArray curPalette = palettes.createNestedArray(String(i >= palettesCount ? 255 - i + palettesCount : i));
// compute the palette ID for this sequential index
int paletteId;
if (i >= palettesCount + umPalettesCount) // user custom palette (IDs 200, 199, ...)
paletteId = WLED_CUSTOM_PALETTE_ID_BASE - (i - palettesCount - umPalettesCount);
else if (i >= palettesCount) // usermod palette (IDs 255, 254, ...)
paletteId = WLED_USERMOD_PALETTE_ID_BASE - (i - palettesCount);
else
paletteId = i; // fixed palette
JsonArray curPalette = palettes.createNestedArray(String(paletteId));
switch (i) {
case 0: //default palette
setPaletteColors(curPalette, PartyColors_gc22);
@@ -987,9 +1008,13 @@ void serializePalettes(JsonObject root, int page)
curPalette.add("c1");
break;
default:
if (i >= palettesCount) // custom palettes
setPaletteColors(curPalette, customPalettes[i - palettesCount]);
else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes
if (i >= palettesCount + umPalettesCount) { // user custom palettes (lowest IDs in the custom range)
int custIdx = i - palettesCount - umPalettesCount;
setPaletteColors(curPalette, customPalettes[custIdx]);
} else if (i >= palettesCount) { // usermod palettes (IDs 255, 254, ...)
int umIdx = i - palettesCount;
setPaletteColors(curPalette, usermodPalettes[umIdx].palette);
} else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i - DYNAMIC_PALETTE_COUNT]);
else {
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - (DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT)])), sizeof(tcp));
+43 -1
View File
@@ -87,7 +87,7 @@ const ethernet_settings ethernetBoards[] = {
// ESP32-ETHERNET-KIT-VE
{
0, // eth_address,
1, // eth_address,
5, // eth_power,
23, // eth_mdc,
18, // eth_mdio,
@@ -155,8 +155,34 @@ const ethernet_settings ethernetBoards[] = {
ETH_PHY_LAN8720, // eth_type,
ETH_CLOCK_GPIO0_IN // eth_clk_mode
},
// WLED_ETH_QUINLED_V4 (14) - QuinLED Dig-Uno/Quad v4
{
0, // eth_address
-1, // eth_power
7, // eth_mdc
8, // eth_mdio
ETH_PHY_LAN8720, // eth_type
ETH_CLOCK_GPIO0_IN // eth_clk_mode
},
// WLED_ETH_QUINLED_OCTA_V4 (15) - QuinLED Dig-Octa 32-8L v4
{
0, // eth_address
-1, // eth_power
23, // eth_mdc
18, // eth_mdio
ETH_PHY_LAN8720, // eth_type
ETH_CLOCK_GPIO0_IN // eth_clk_mode
},
};
// sanity checks for ethernet config table and WLED_ETH_DEFAULT
static_assert((sizeof(ethernetBoards)/sizeof(ethernetBoards[0])) == WLED_NUM_ETH_TYPES, "WLED_NUM_ETH_TYPES does not match size of ethernetBoards[] table.");
#ifdef WLED_ETH_DEFAULT
static_assert(((WLED_ETH_DEFAULT) >= WLED_ETH_NONE) && ((WLED_ETH_DEFAULT) < WLED_NUM_ETH_TYPES), "WLED_ETH_DEFAULT is out of range.");
#endif
bool initEthernet()
{
static bool successfullyConfiguredEthernet = false;
@@ -422,10 +448,26 @@ void WiFiEvent(WiFiEvent_t event)
}
break;
#ifdef ARDUINO_ARCH_ESP32
case ARDUINO_EVENT_WIFI_READY:
DEBUG_PRINTLN(F("WiFi-E: driver ready."));
break;
case ARDUINO_EVENT_WIFI_SCAN_DONE:
// also triggered when connected to selected SSID
DEBUG_PRINTLN(F("WiFi-E: SSID scan completed."));
break;
case ARDUINO_EVENT_WIFI_STA_START:
DEBUG_PRINTLN(F("WiFi-E: STA Started"));
break;
case ARDUINO_EVENT_WIFI_STA_STOP:
DEBUG_PRINTLN(F("WiFi-E: STA Stopped"));
break;
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
DEBUG_PRINTLN(F("WiFi-E: STA authentication mode change."));
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
DEBUG_PRINTLN(F("WiFi-E: IP address lost."));
break;
case ARDUINO_EVENT_WIFI_AP_START:
DEBUG_PRINTLN(F("WiFi-E: AP Started"));
break;
+222 -79
View File
@@ -6,10 +6,8 @@
#include <esp_ota_ops.h>
#include <esp_flash.h>
#include <mbedtls/sha256.h>
#endif
// Platform-specific metadata locations
#ifdef ESP32
constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata
#define UPDATE_ERROR errorString
@@ -31,16 +29,43 @@ constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
#elif defined(ESP8266)
constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset
#define UPDATE_ERROR getErrorString
#define SUPPORT_GZIPPED_OTA
#endif
#ifdef SUPPORT_GZIPPED_OTA
#include <uzlib.h> // ArduinoUZlib library: gzipped firmware uploads are decoded by eboot at boot,
// so the upload stream itself can arrive compressed; we decompress just enough
// to find the metadata for OTA validation.
#endif
constexpr size_t METADATA_SEARCH_RANGE = 512; // bytes
// State structure for update process
namespace {
struct UpdateContext {
// State flags
// FUTURE: the flags could be replaced by a state machine
bool replySent = false;
bool needsRestart = false;
bool updateStarted = false;
bool uploadComplete = false;
bool releaseCheckPassed = false;
String errorMessage;
// Buffer to hold block data across posts, if needed
std::vector<uint8_t> releaseMetadataBuffer;
#ifdef SUPPORT_GZIPPED_OTA
bool gzipDetected = false; // upload is a gzip stream (eboot will decompress at boot)
#endif
};
}
/**
* Check if OTA should be allowed based on release compatibility using custom description
* @param binaryData Pointer to binary file data (not modified)
* @param dataSize Size of binary data in bytes
* @param errorMessage Buffer to store error message if validation fails
* @param errorMessage Buffer to store error message if validation fails
* @param errorMessageLen Maximum length of error message buffer
* @return true if OTA should proceed, false if it should be blocked
*/
@@ -67,20 +92,104 @@ static bool validateOTA(const uint8_t* binaryData, size_t dataSize, char* errorM
}
}
struct UpdateContext {
// State flags
// FUTURE: the flags could be replaced by a state machine
bool replySent = false;
bool needsRestart = false;
bool updateStarted = false;
bool uploadComplete = false;
bool releaseCheckPassed = false;
String errorMessage;
#ifdef SUPPORT_GZIPPED_OTA
constexpr size_t GZIP_DECOMPRESSED_SIZE = METADATA_OFFSET + METADATA_SEARCH_RANGE;
// We use the JSON buffer to hold the decompressed metadata, so we need to ensure it's large enough
static_assert(JSON_BUFFER_SIZE >= GZIP_DECOMPRESSED_SIZE, "JSON_BUFFER_SIZE must be at least GZIP_DECOMPRESSED_SIZE to support gzipped OTA updates");
// Buffer to hold block data across posts, if needed
std::vector<uint8_t> releaseMetadataBuffer;
};
// File-static flash source context for the uzlib source_read_cb.
// The 256-byte read buffer must be 4-byte aligned for ESP.flashRead().
static struct {
uint32_t flashBase;
uint32_t flashOffset;
uint8_t buf[256] __attribute__((aligned(4)));
} s_gzipFlashSrc;
static int gzip_flash_read_cb(struct uzlib_uncomp* d)
{
if (!ESP.flashRead(s_gzipFlashSrc.flashBase + s_gzipFlashSrc.flashOffset,
reinterpret_cast<uint32_t*>(s_gzipFlashSrc.buf),
sizeof(s_gzipFlashSrc.buf))) {
return -1;
}
s_gzipFlashSrc.flashOffset += sizeof(s_gzipFlashSrc.buf);
d->source = s_gzipFlashSrc.buf;
d->source_limit = s_gzipFlashSrc.buf + sizeof(s_gzipFlashSrc.buf);
return *d->source++;
}
static bool unzipFlash(uint32_t flashBase, uint8_t* outBuf, char* errorMessage, size_t errorMessageLen)
{
uzlib_uncomp uncomp_ctx; // large, but we can get away with this since we're on the system stack
s_gzipFlashSrc.flashBase = flashBase;
s_gzipFlashSrc.flashOffset = 0;
uncomp_ctx.source = s_gzipFlashSrc.buf;
uncomp_ctx.source_limit = s_gzipFlashSrc.buf; // empty: first byte triggers callback
uncomp_ctx.source_read_cb = gzip_flash_read_cb;
uzlib_uncompress_init(&uncomp_ctx, nullptr, 0);
bool ok = false;
if (uzlib_gzip_parse_header(&uncomp_ctx) != TINF_OK) {
strncpy_P(errorMessage, PSTR("Invalid gzip header in OTA firmware"), errorMessageLen - 1);
errorMessage[errorMessageLen - 1] = '\0';
} else {
uncomp_ctx.dest_start = uncomp_ctx.dest = outBuf;
uncomp_ctx.dest_limit = outBuf + GZIP_DECOMPRESSED_SIZE;
int res = TINF_OK;
while (res == TINF_OK && uncomp_ctx.dest < uncomp_ctx.dest_limit) res = uzlib_uncompress(&uncomp_ctx);
if (uncomp_ctx.dest >= uncomp_ctx.dest_limit) {
ok = true;
} else {
strncpy_P(errorMessage, PSTR("Not enough decompressed data for validation"), errorMessageLen - 1);
errorMessage[errorMessageLen - 1] = '\0';
}
}
return ok;
}
static void validateGzippedOTA(UpdateContext* context) {
// Compute the flash address to read from based on the expected update size and the location of the FS partition, and then decompress just enough of the firmware to find the metadata for validation.
extern uint32_t _FS_start;
const size_t updateSize = Update.size();
// Logic borrowed from ESP8266 Updater.cpp
const size_t roundedSize = (updateSize + FLASH_SECTOR_SIZE - 1) & ~(FLASH_SECTOR_SIZE - 1);
const uintptr_t fsPhysAddr = (uintptr_t)&_FS_start - 0x40200000;
const uint32_t flashBase = (fsPhysAddr > roundedSize) ? (fsPhysAddr - roundedSize) : 0;
uint8_t* compressionBuf = static_cast<uint8_t*>(pDoc->memoryPool().buffer()); // borrow JSON buffer
char errorMessage[128];
bool ok = unzipFlash(flashBase, compressionBuf, errorMessage, sizeof(errorMessage));
if (!ok) {
DEBUG_PRINTF_P(PSTR("OTA unzip failed: %s\n"), errorMessage);
context->errorMessage = errorMessage;
return;
}
ok = validateOTA(compressionBuf, GZIP_DECOMPRESSED_SIZE, errorMessage, sizeof(errorMessage));
if (!ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
}
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed (gzipped)"));
context->releaseCheckPassed = true;
// Write the final block
if (!Update.hasError()) {
const auto& buf = context->releaseMetadataBuffer;
if (Update.write(const_cast<uint8_t*>(buf.data()), buf.size()) != buf.size()) {
DEBUG_PRINTF_P(PSTR("OTA write failed on final chunk: %s\n"), Update.UPDATE_ERROR());
}
}
DEBUG_PRINTLN(F("OTA Update End")); // for symmetry with the non-gzip path
context->uploadComplete = true; // Final block was passed to Update.write()
}
#endif
static void endOTA(AsyncWebServerRequest *request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
@@ -116,7 +225,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
{
#ifdef ESP8266
Update.runAsync(true);
#endif
#endif
if (Update.isRunning()) {
request->send(503);
@@ -128,7 +237,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
WLED::instance().disableWatchdog();
#endif
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
strip.suspend();
backupConfig(); // backup current config in case the update ends badly
strip.resetSegments(); // free as much memory as you can
@@ -141,15 +250,15 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
context->releaseCheckPassed = true;
DEBUG_PRINTLN(F("OTA validation skipped by user"));
}
// Begin update with the firmware size from content length
size_t updateSize = request->contentLength() > 0 ? request->contentLength() : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
if (!Update.begin(updateSize)) {
if (!Update.begin(updateSize)) {
context->errorMessage = Update.UPDATE_ERROR();
DEBUG_PRINTF_P(PSTR("OTA Failed to begin: %s\n"), context->errorMessage.c_str());
return false;
}
context->updateStarted = true;
return true;
}
@@ -158,7 +267,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
// Returns true if successful, false on failure.
bool initOTA(AsyncWebServerRequest *request) {
// Allocate update context
UpdateContext* context = new (std::nothrow) UpdateContext {};
UpdateContext* context = new (std::nothrow) UpdateContext {};
if (context) {
request->_tempObject = context;
request->onDisconnect([=]() { endOTA(request); }); // ensures we restart on failure
@@ -174,29 +283,43 @@ void setOTAReplied(AsyncWebServerRequest *request) {
context->replySent = true;
};
// Returns pointer to error message, or nullptr if OTA was successful.
std::pair<bool, String> getOTAResult(AsyncWebServerRequest* request) {
// Returns the OTA outcome as an OTAResultStatus plus an optional error string.
// TryAgain: JSON lock busy; caller must call deferResponse() and retry.
// Replied: response already sent; no action needed.
// Ready: result available; empty string = success (200), non-empty = failure (500).
std::pair<OTAResultStatus, String> getOTAResult(AsyncWebServerRequest* request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return { true, F("OTA context unexpectedly missing") };
if (context->replySent) return { false, {} };
if (context->errorMessage.length()) return { true, context->errorMessage };
if (!context) return { OTAResultStatus::Ready, F("OTA context unexpectedly missing") };
if (context->replySent) return { OTAResultStatus::Replied, {} };
#ifdef SUPPORT_GZIPPED_OTA
if (context->gzipDetected && !context->errorMessage.length()) {
JSONBufferGuard jsonGuard(JSON_LOCK_OTA);
if (!jsonGuard) return { OTAResultStatus::TryAgain, {} }; // busy — caller will deferResponse()
validateGzippedOTA(context); // Stores error in context->errorMessage if there's a problem
context->gzipDetected = false; // all done
}
#endif
if (context->errorMessage.length()) return { OTAResultStatus::Ready, context->errorMessage };
if (context->updateStarted) {
// Release the OTA context now.
endOTA(request);
if (Update.hasError()) {
return { true, Update.UPDATE_ERROR() };
return { OTAResultStatus::Ready, Update.UPDATE_ERROR() };
} else {
return { true, {} };
return { OTAResultStatus::Ready, {} };
}
}
// Should never happen
return { true, F("Internal software failure") };
return { OTAResultStatus::Ready, F("Internal software failure") };
}
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal)
{
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
@@ -208,69 +331,89 @@ void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data,
if (index == 0) {
if (!beginOTA(request, context)) return;
#ifdef SUPPORT_GZIPPED_OTA
if (len >= 2 && data[0] == 0x1F && data[1] == 0x8B) {
DEBUG_PRINTLN(F("OTA: gzipped firmware detected"));
context->gzipDetected = true;
}
#endif
}
// Perform validation if we haven't done it yet and we have reached the metadata offset
if (!context->releaseCheckPassed && (index+len) > METADATA_OFFSET) {
// Current chunk contains the metadata offset
size_t availableDataAfterOffset = (index + len) - METADATA_OFFSET;
// Perform plain-firmware validation as data arrives.
// Gzip validation is deferred to the end of the upload; skip this path for gzip.
if (!context->releaseCheckPassed
#ifdef SUPPORT_GZIPPED_OTA
&& !context->gzipDetected
#endif
) {
if ((index+len) > METADATA_OFFSET) {
// Current chunk contains the metadata offset
size_t availableDataAfterOffset = (index + len) - METADATA_OFFSET;
DEBUG_PRINTF_P(PSTR("OTA metadata check: %d in buffer, %d received, %d available\n"), context->releaseMetadataBuffer.size(), len, availableDataAfterOffset);
DEBUG_PRINTF_P(PSTR("OTA metadata check: %d in buffer, %d received, %d available\n"), context->releaseMetadataBuffer.size(), len, availableDataAfterOffset);
if (availableDataAfterOffset >= METADATA_SEARCH_RANGE) {
// We have enough data to validate, one way or another
const uint8_t* search_data = data;
size_t search_len = len;
// If we have saved data, use that instead
if (context->releaseMetadataBuffer.size()) {
// Add this data
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
search_data = context->releaseMetadataBuffer.data();
search_len = context->releaseMetadataBuffer.size();
}
if (availableDataAfterOffset >= METADATA_SEARCH_RANGE) {
// We have enough data to validate, one way or another
const uint8_t* search_data = data;
size_t search_len = len;
// Do the checking
char errorMessage[128];
bool OTA_ok = validateOTA(search_data, search_len, errorMessage, sizeof(errorMessage));
// Release buffer if there was one
context->releaseMetadataBuffer = decltype(context->releaseMetadataBuffer){};
if (!OTA_ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
// If we have saved data, use that instead
if (context->releaseMetadataBuffer.size()) {
// Add this data
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
search_data = context->releaseMetadataBuffer.data();
search_len = context->releaseMetadataBuffer.size();
}
// Do the checking
char errorMessage[128];
bool OTA_ok = validateOTA(search_data, search_len, errorMessage, sizeof(errorMessage));
// Release buffer if there was one
context->releaseMetadataBuffer = decltype(context->releaseMetadataBuffer){};
if (!OTA_ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
} else {
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
context->releaseCheckPassed = true;
}
} else {
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
context->releaseCheckPassed = true;
}
} else {
// Store the data we just got for next pass
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
// Store the data we just got for next pass
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
}
}
}
#ifdef SUPPORT_GZIPPED_OTA
// Gzip: stash the final chunk and defer validation to getOTAResult(), where
// deferResponse() can be used if the JSON lock is not yet available.
if (isFinal && context->gzipDetected && !context->releaseCheckPassed) {
context->releaseMetadataBuffer.assign(data, data + len);
return; // getOTAResult() will validate, write this chunk, and complete the update
}
#endif
// Check if validation was still pending (shouldn't happen normally)
// This is done before writing the last chunk, so endOTA can abort
// This is done before writing the last chunk, so endOTA can abort
if (isFinal && !context->releaseCheckPassed) {
DEBUG_PRINTLN(F("OTA failed: Validation never completed"));
// Don't write the last chunk to the updater: this will trip an error later
context->errorMessage = F("Release check data never arrived?");
return;
}
// Write chunk data to OTA update (only if release check passed or still pending)
if (!Update.hasError()) {
if (Update.write(data, len) != len) {
DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.UPDATE_ERROR());
}
}
if(isFinal) {
if (isFinal) {
DEBUG_PRINTLN(F("OTA Update End"));
// Upload complete
context->uploadComplete = true;
}
}
@@ -331,7 +474,7 @@ public:
return true; // needs more bytes for the header
}
//DEBUG_PRINTF("BLS parsed segment [%08X %08X=%d], segment count %d, is %d\n", segmentHeader.load_addr, segmentHeader.data_len, segmentHeader.data_len, segmentsLeft, imageSize);
//DEBUG_PRINTF("BLS parsed segment [%08X %08X=%d], segment count %d, is %d\n", segmentHeader.load_addr, segmentHeader.data_len, segmentHeader.data_len, segmentsLeft, imageSize);
// Validate segment size
if (segmentHeader.data_len > BOOTLOADER_SIZE) {
@@ -345,16 +488,16 @@ public:
--segmentsLeft;
if (segmentsLeft == 0) {
// all done, actually; we don't need to read any more
// Round up to nearest 16 bytes.
// Always add 1 to account for the checksum byte.
imageSize = ((imageSize/ 16) + 1) * 16;
//DEBUG_PRINTF("BLS complete, is %d\n", imageSize);
//DEBUG_PRINTF("BLS complete, is %d\n", imageSize);
return false;
}
}
}
// If we don't have enough bytes ...
if (len < segmentHeader.data_len) {
//DEBUG_PRINTF("Needs more bytes\n");
@@ -393,12 +536,12 @@ static uint8_t bootloaderSHA256Cache[32];
/**
* Calculate and cache the bootloader SHA256 digest
* Reads the bootloader from flash and computes SHA256 hash
*
* Strictly speaking, most bootloader images already contain a hash at the end of the image;
*
* Strictly speaking, most bootloader images already contain a hash at the end of the image;
* we could in theory just read it. The trouble is that we have to parse the structure anyways
* to find the actual endpoint, so we might as well always calculate it ourselves rather than
* handle a special case if the hash isn't stored.
*
*
*/
static void calculateBootloaderSHA256() {
// Calculate SHA256
@@ -537,7 +680,7 @@ static bool verifyBootloaderImage(const uint8_t* &buffer, size_t &len, String& b
if (imageHeader.hash_appended == 1) {
actualBootloaderSize += 32;
}
if (actualBootloaderSize > len) {
// Same as above
bootloaderErrorMsg = "Too small";
@@ -635,7 +778,7 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) {
context->bytesBuffered = 0;
return true;
#endif
#endif
}
// Set bootloader OTA replied flag
+9 -2
View File
@@ -1,6 +1,7 @@
// WLED OTA update interface
#include <Arduino.h>
#ifdef ESP8266
#include <Updater.h>
#else
@@ -32,12 +33,18 @@ bool initOTA(AsyncWebServerRequest *request);
*/
void setOTAReplied(AsyncWebServerRequest *request);
/**
* Retrieve the OTA result.
* @param request Pointer to web request object
* @return bool indicating if a reply is necessary; string with error message if the update failed.
* @return OTAResultStatus indicating result state; string with error message if the update failed.
*/
std::pair<bool, String> getOTAResult(AsyncWebServerRequest *request);
enum class OTAResultStatus {
TryAgain, // caller must deferResponse() and retry - need additional resources (JSON lock) to complete validation
Replied, // response already sent; no action needed
Ready, // result available; send response based on error string
};
std::pair<OTAResultStatus, String> getOTAResult(AsyncWebServerRequest *request);
/**
* Process a block of OTA data. This is a passthrough of an ArUploadHandlerFunction.
+1 -1
View File
@@ -43,7 +43,7 @@ enum struct PinOwner : uint8_t {
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2
DMX = 0x8A, // 'DMX' == DMX output, hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial
+2 -2
View File
@@ -194,8 +194,8 @@ void handlePresets()
changePreset = true;
} else {
if (!fdo["seg"].isNull() || !fdo["on"].isNull() || !fdo["bri"].isNull() || !fdo["nl"].isNull() || !fdo["ps"].isNull() || !fdo[F("playlist")].isNull()) changePreset = true;
if (!(tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~')))
fdo.remove("ps"); // remove load request for presets to prevent recursive crash (if not called by button and contains preset cycling string "1~5~")
if (!(tmpMode == CALL_MODE_INIT || (tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~'))))
fdo.remove("ps"); // remove load request for presets to prevent recursive crash (if not called by boot preset or button which contains preset cycling string "1~5~")
deserializeState(fdo, CALL_MODE_NO_NOTIFY, tmpPreset); // may change presetToApply by calling applyPreset()
}
if (!errorFlag && tmpPreset < 255 && changePreset) currentPreset = tmpPreset;
+1 -1
View File
@@ -212,7 +212,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char ld[4] = "LD"; ld[2] = offset+s; ld[3] = 0; //driver type (RMT=0, I2S=1)
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
if (!request->hasArg(lp)) {
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s);
break;
}
for (int i = 0; i < 5; i++) {
+32 -25
View File
@@ -21,10 +21,10 @@
#include "../network/Network.h"
#include <string.h>
// E1.17 ACN Packet Identifier
// E1.17 ACN Packet Identifier "ASC-E1.17"
const byte ESPAsyncE131::ACN_ID[12] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
// Art-Net Packet Identifier
// Art-Net Packet Identifier "Art-Net"
const byte ESPAsyncE131::ART_ID[8] = { 0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00 };
// Constructor
@@ -99,36 +99,43 @@ bool ESPAsyncE131::initMulticast(uint16_t port, uint16_t universe, uint8_t n) {
void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
bool error = false;
uint8_t protocol = P_E131;
uint8_t protocol = P_ARTNET;
const size_t pktLen = _packet.length();
e131_packet_t *sbuff = reinterpret_cast<e131_packet_t *>(_packet.data());
//E1.31 packet identifier ("ACS-E1.17")
if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
protocol = P_ARTNET;
if (protocol == P_ARTNET) {
if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id)))
error = true; //not "Art-Net"
if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX && sbuff->art_opcode != ARTNET_OPCODE_OPPOLL)
error = true; //not a DMX or poll packet
} else { //E1.31 error handling
if (htonl(sbuff->root_vector) != ESPAsyncE131::VECTOR_ROOT)
error = true;
if (htonl(sbuff->frame_vector) != ESPAsyncE131::VECTOR_FRAME)
error = true;
if (sbuff->dmp_vector != ESPAsyncE131::VECTOR_DMP)
error = true;
if (sbuff->property_values[0] != 0)
error = true;
}
// E1.31 packet identifier (ACN_ID = "ASC-E1.17"), need at least 16 bytes to safely read acn_id (offset 4, length 12).
if (pktLen >= 16) {
if (!memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
protocol = P_E131;
}
if (protocol == P_ARTNET) {
if (memcmp(sbuff->art_id, ESPAsyncE131::ART_ID, sizeof(sbuff->art_id)))
error = true; //not ART_ID = "Art-Net"
if (sbuff->art_opcode != ARTNET_OPCODE_OPDMX && sbuff->art_opcode != ARTNET_OPCODE_OPPOLL)
error = true; //not a DMX or poll packet
} else { //E1.31 error handling
if (pktLen < 126) { // need up to property_values[0] at offset 125
error = true;
} else {
if (htonl(sbuff->root_vector) != ESPAsyncE131::VECTOR_ROOT)
error = true;
if (htonl(sbuff->frame_vector) != ESPAsyncE131::VECTOR_FRAME)
error = true;
if (sbuff->dmp_vector != ESPAsyncE131::VECTOR_DMP)
error = true;
if (sbuff->property_values[0] != 0)
error = true;
}
}
if (error && _packet.localPort() == DDP_DEFAULT_PORT) { //DDP packet
error = false;
protocol = P_DDP;
}
if (!error) {
_callback(sbuff, _packet.remoteIP(), protocol);
_callback(sbuff, _packet.remoteIP(), protocol, pktLen);
}
}
+13 -8
View File
@@ -55,19 +55,24 @@ typedef struct ip_addr ip4_addr_t;
#define DDP_FLAGS_VER 0xc0 // version mask
#define DDP_FLAGS_VER1 0x40 // version=1
#define DDP_FLAGS_PUSH 0x01
#define DDP_FLAGS_QUERY 0x02
#define DDP_FLAGS_REPLY 0x04
#define DDP_FLAGS_STORAGE 0x08
#define DDP_FLAGS_QUERY 0x02 // unsupported - used by XLights for auto-discovery
#define DDP_FLAGS_REPLY 0x04 // unsupported - response packet from another display
#define DDP_FLAGS_STORAGE 0x08 // unsupported - show data from a storage unit instead of from packet data field. Data field defines storage unit (by name, number, URL or whatever mechanism wanted).
#define DDP_FLAGS_TIME 0x10
#define DDP_CHANNELS_PER_PACKET 1440 // 480 leds
#define DDP_TYPE_RGB24 0x0B // 00 001 011 (RGB , 8 bits per channel, 3 channels)
#define DDP_TYPE_RGBW32 0x1B // 00 011 011 (RGBW, 8 bits per channel, 4 channels)
#define DDP_TYPE_LEGACY 0x01 // 00 000 001 legacy RGB 8bit definition
#define DDP_TYPE_UNDEF 0x00 // type and bit depth undefined
#define DDP_ID_DISPLAY 1
#define DDP_ID_CONFIG 250
#define DDP_ID_STATUS 251
// DDP Source or Destination ID (header byte 3)
#define DDP_ID_DISPLAY 1 // default output device
#define DDP_ID_CONTROL 246 // JSON control (not implemented)
#define DDP_ID_CONFIG 250 // JSON config (not implemented)
#define DDP_ID_STATUS 251 // JSON status (not implemented)
#define DDP_ID_ALL 255 // all devices
#define ARTNET_OPCODE_OPDMX 0x5000
#define ARTNET_OPCODE_OPPOLL 0x2000
@@ -212,7 +217,7 @@ typedef union {
} ArtPollReply;
// new packet callback
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol);
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP, byte protocol, size_t packetLen);
class ESPAsyncE131 {
private:
@@ -267,4 +272,4 @@ class E131Priority {
}
};
#endif // ESPASYNCE131_H_
#endif // ESPASYNCE131_H_
+1
View File
@@ -804,6 +804,7 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const
// write the header
/*0*/ddpUdp.write(flags);
// TODO: sequence number should be 1-15 as 0 means "unused", it has no bad consequences other than out of sequence packet may be accepted
/*1*/ddpUdp.write(sequenceNumber++ & 0x0F); // sequence may be unnecessary unless we are sending twice (as requested in Sync settings)
/*2*/ddpUdp.write(isRGBW ? DDP_TYPE_RGBW32 : DDP_TYPE_RGB24);
/*3*/ddpUdp.write(DDP_ID_DISPLAY);
-29
View File
@@ -1,29 +0,0 @@
#include "wled.h"
/*
* This v1 usermod file allows you to add own functionality to WLED more easily
* See: https://github.com/wled-dev/WLED/wiki/Add-own-functionality
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
* If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE)
*
* Consider the v2 usermod API if you need a more advanced feature set!
*/
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup()
{
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected()
{
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop()
{
}
+31 -5
View File
@@ -317,10 +317,36 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
} else return 0;
}
if (src == JSON_palette_names && mode > 255-customPalettes.size()) {
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
dest[maxLen] = '\0';
return strlen(dest);
if (src == JSON_palette_names) {
if (mode > WLED_CUSTOM_PALETTE_ID_BASE) {
// usermod palette (IDs 201-255)
uint8_t umIdx = WLED_USERMOD_PALETTE_ID_BASE - mode;
if (umIdx >= usermodPalettes.size()) {
dest[0] = '\0'; // empty string if requested index is out of bounds
return 0;
}
const UsermodPalette &ump = usermodPalettes[umIdx];
char base[33];
strncpy_P(base, ump.name, sizeof(base) - 1);
base[sizeof(base) - 1] = '\0';
if (ump.palName) {
// usermod supplied a specific display name — prefix with the usermod name (e.g. "AudioReactive: Hue")
char palName[33];
strncpy_P(palName, ump.palName, sizeof(palName) - 1);
palName[sizeof(palName) - 1] = '\0';
snprintf(dest, maxLen + 1, "%s: %s", base, palName);
} else {
// fallback: "UMName index" (e.g. "AudioReactive 1")
snprintf(dest, maxLen + 1, "%s %u", base, (unsigned)ump.palIndex);
}
return strlen(dest);
}
if (mode >= FIXED_PALETTE_COUNT && mode <= WLED_CUSTOM_PALETTE_ID_BASE) {
// user custom palette (IDs FIXED_PALETTE_COUNT up to WLED_CUSTOM_PALETTE_ID_BASE=200)
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode);
dest[maxLen] = '\0';
return strlen(dest);
}
}
unsigned qComma = 0;
@@ -894,7 +920,7 @@ void *allocate_buffer(size_t size, uint32_t type) {
buffer = p_malloc(size); // prefer PSRAM
}
else if (type & BFRALLOC_ENFORCE_PSRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // use PSRAM only, otherwise return nullptr
buffer = p_malloc(size); // use PSRAM if available, fall back to DRAM if not (safeguard for boards without PSRAM #5629)
buffer = validateFreeHeap(buffer);
#endif
if (buffer && (type & BFRALLOC_CLEAR))
+15 -4
View File
@@ -78,7 +78,6 @@ void WLED::loop()
#ifdef WLED_DEBUG
unsigned long usermodMillis = millis();
#endif
userLoop();
UsermodManager::loop();
#ifdef WLED_DEBUG
usermodMillis = millis() - usermodMillis;
@@ -369,8 +368,10 @@ void WLED::setup()
#endif
#ifdef ARDUINO_ARCH_ESP32
pinMode(hardwareRX, INPUT_PULLDOWN); delay(1); // suppress noise in case RX pin is floating (at low noise energy) - see issue #3128
gpio_pulldown_en((gpio_num_t)hardwareRX); delay(1); // suppress noise in case RX pin is floating (at low noise energy) - see issue #3128
// note: can not use pinMode(): it routes GPIO through the GPIO matrix and detaches UART0 RX
#endif
#ifdef WLED_BOOTUPDELAY
delay(WLED_BOOTUPDELAY); // delay to let voltage stabilize, helps with boot issues on some setups
#endif
@@ -495,12 +496,17 @@ void WLED::setup()
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTLN(F("Usermods setup"));
userSetup();
UsermodManager::setup();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
if (needsCfgSave) serializeConfigToFS(); // usermods required new parameters; need to wait for strip to be initialised #4752
if (bootPreset > 0) {
handlePresets(); // handle boot preset
handlePlaylist(); // handle playlist if preset queued one
handlePresets(); // handle presets again to give a chance for anything queued by the boot preset or playlist
}
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0 && !configBackupExists())
showWelcomePage = true;
@@ -649,6 +655,7 @@ void WLED::initAP(bool resetAP)
WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0));
WiFi.softAP(apSSID, apPass, apChannel, apHide);
#ifdef ARDUINO_ARCH_ESP32
DEBUG_PRINT(F("access point maxTxPower set to ")); DEBUG_PRINTLN(txPower);
WiFi.setTxPower(wifi_power_t(txPower));
#endif
@@ -689,6 +696,7 @@ void WLED::initConnection()
}
#endif
DEBUG_PRINTLN(F("WiFi disconnect."));
WiFi.disconnect(true); // close old connections
delay(5); // wait for hardware to be ready
#ifdef ESP8266
@@ -701,6 +709,7 @@ void WLED::initConnection()
#ifdef ARDUINO_ARCH_ESP32
// Reset mode to NULL to force a full STA mode transition, so that WiFi.mode(WIFI_STA) below actually applies the hostname (and TX power, etc.).
// This is required on reconnects when mode is already WIFI_STA.
DEBUG_PRINTLN(F("WiFi mode_null: driver teardown / re-init."));
WiFi.mode(WIFI_MODE_NULL);
apActive = false; // the AP is physically torn down by WIFI_MODE_NULL
delay(5); // give the WiFi stack time to complete the mode transition
@@ -784,9 +793,12 @@ void WLED::initConnection()
#endif // WLED_ENABLE_WPA_ENTERPRISE
#ifdef ARDUINO_ARCH_ESP32
DEBUG_PRINT(F("WiFi maxTxPower set to ")); DEBUG_PRINT(txPower);
DEBUG_PRINT(F("; WiFi sleep ")); DEBUG_PRINTLN(noWifiSleep ? F("disabled."):F("enabled."));
WiFi.setTxPower(wifi_power_t(txPower));
WiFi.setSleep(!noWifiSleep);
#else // ESP8266 accepts a hostname set after WiFi interface initialization
DEBUG_PRINT(F("WiFi sleep ")); DEBUG_PRINTLN(noWifiSleep ? F("disabled."):F("enabled."));
wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T);
WiFi.hostname(hostname);
#endif
@@ -966,7 +978,6 @@ void WLED::handleConnection()
if (improvActive > 1) sendImprovIPRPCResult(ImprovRPCType::Command_Wifi);
}
initInterfaces();
userConnected();
UsermodManager::connected();
lastMqttReconnectAttempt = 0; // force immediate update
+6 -5
View File
@@ -7,7 +7,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2602141
#define VERSION 2605011
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -469,9 +469,9 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f
WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start
#endif
#ifdef WLED_ENABLE_DMX_INPUT
WLED_GLOBAL int dmxInputTransmitPin _INIT(0);
WLED_GLOBAL int dmxInputReceivePin _INIT(0);
WLED_GLOBAL int dmxInputEnablePin _INIT(0);
WLED_GLOBAL int dmxInputTransmitPin _INIT(-1);
WLED_GLOBAL int dmxInputReceivePin _INIT(-1);
WLED_GLOBAL int dmxInputEnablePin _INIT(-1);
WLED_GLOBAL int dmxInputPort _INIT(2);
WLED_GLOBAL DMXInput dmxInput;
#endif
@@ -599,7 +599,8 @@ WLED_GLOBAL bool wasConnected _INIT(false);
// color
WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same
WLED_GLOBAL std::vector<CRGBPalette16> customPalettes; // custom palettes
WLED_GLOBAL std::vector<CRGBPalette16> customPalettes; // custom palettes (file-based, IDs grow downwards starting at 200)
WLED_GLOBAL std::vector<UsermodPalette> usermodPalettes; // usermod-registered palettes (IDs 255, 254, 253...)
WLED_GLOBAL uint8_t paletteBlend _INIT(0); // determines blending and wrapping of palette: 0: blend, wrap if moving (SEGMENT.speed>0); 1: blend, always wrap; 2: blend, never wrap; 3: don't blend or wrap
// transitions
+3 -1
View File
@@ -506,7 +506,9 @@ void initServer()
server.on(_update, HTTP_POST, [](AsyncWebServerRequest *request){
if (request->_tempObject) {
auto ota_result = getOTAResult(request);
if (ota_result.first) {
if (ota_result.first == OTAResultStatus::TryAgain) {
request->deferResponse();
} else if (ota_result.first == OTAResultStatus::Ready) {
if (ota_result.second.length() > 0) {
serveMessage(request, 500, F("Update failed!"), ota_result.second, 254);
} else {
+3 -9
View File
@@ -87,19 +87,13 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if (!data || len < offset+1) return; // catch invalid / single-byte payload
switch (data[0]) {
case BINARY_PROTOCOL_E131:
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_E131);
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_E131, len - offset);
break;
case BINARY_PROTOCOL_ARTNET:
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_ARTNET);
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_ARTNET, len - offset);
break;
case BINARY_PROTOCOL_DDP:
if (len < 10 + offset) return; // DDP header is 10 bytes (+1 protocol byte)
size_t ddpDataLen = (data[8+offset] << 8) | data[9+offset]; // data length in bytes from DDP header
uint8_t flags = data[0+offset];
if ((flags & DDP_FLAGS_TIME) ) ddpDataLen += 4; // timecode flag adds 4 bytes to data length
if (len < (10 + offset + ddpDataLen)) return; // not enough data, prevent out of bounds read
// could be a valid DDP packet, forward to handler
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_DDP);
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_DDP, len - offset);
}
}
} else {
+12
View File
@@ -185,6 +185,18 @@ static void appendGPIOinfo(Print& settingsScript)
#else
settingsScript.print(F("d.touch=[];"));
#endif
// add info about ADC-capable GPIO (for analog button pin filtering)
settingsScript.print(F("d.adc=["));
firstPin = true;
for (unsigned i = 0; i < WLED_NUM_PINS; i++) {
if (PinManager::isAnalogPin(i)) {
if (!firstPin) settingsScript.print(',');
settingsScript.print(i);
firstPin = false;
}
}
settingsScript.print(F("];"));
}
//get values for settings form in javascript