Compare commits

...

141 Commits

Author SHA1 Message Date
Will Tatam
90b1912fea Merge pull request #5034 from willmmiles/0_15_x-fix-4929
0.15 - Fix bin file output names
2025-11-07 09:52:58 +00:00
Will Miles
9a32fea1a5 Always use package.json for WLED_VERSION
Ensures consistency between UI and metadata; fixes release bin names.
2025-10-26 17:53:27 -04:00
Will Tatam
ab3cf4aa4d Merge pull request #4999 from willmmiles/0_15_x-fix-4929
0.15 version of OTA validation
2025-10-25 19:00:19 +01:00
Will Miles
14ffde4fed Fix bad backport merge on update.htm 2025-10-25 13:50:27 -04:00
Will Miles
114f85f95e set_metadata: Apply code fixes from @coderabbit 2025-10-25 13:46:54 -04:00
Will Miles
d37cae8ad9 Fix set_metadata script 2025-10-25 13:46:54 -04:00
Will Miles
7b872bfff6 Fix metadata includes 2025-10-25 13:46:54 -04:00
Will Miles
b552744a07 Process metadata only in metadata.cpp
Improves cache utilization as fewer things are passed via CFLAGS to
all files.  In the event that no metadata is available, let the cpp
file handle warning about default usage.
2025-10-11 10:51:36 -04:00
copilot-swe-agent[bot]
3029a4e735 Implement OTA release compatibility checking system
Implement a comprehensive solution for validating a firmware before an
OTA updated is committed.  WLED metadata such as version and release
is moved to a data structure located at near the start of the firmware
binary, where it can be identified and validated.

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2025-10-11 10:51:20 -04:00
Damian Schneider
c756a12880 fix buffer size calculation for ESP8266 (0.15 edition) (#4951)
* fix buffer size calculation for ESP8266

also adds proper global buffer calculation and RMT/I2S channel calculation
thx to @blazoncek
2025-10-10 20:40:14 +02:00
Will Miles
4cee97561f Merge pull request #4980 from willmmiles/0_15_x_rmthi
(0.15) RMT High-priority Interrupt driver backport
2025-10-05 11:20:42 -04:00
Will Miles
3e80c2ce40 RmtHI: Add missing includes
While these were both fortunately included in Arduino.h, as
@coderabbitai suggests, it's best practice to be explicit for anything
one uses directly.
2025-10-04 17:12:12 -04:00
Will Miles
32fccb6a34 Undo RMTHi change to platformio.ini
PlatformIO's Library Dependency Finder will take care of it based on
the #include.
2025-10-04 17:12:12 -04:00
Will Miles
139f4d949f RmtHI: Fix incorrect method typedefs 2025-10-04 17:11:56 -04:00
Will Miles
b762704d37 RmtHI: Remove incorrect default selection block 2025-10-04 17:11:56 -04:00
Will Miles
37612e56eb Import RmtHI driver
Pull the RMT High-priority Interrupt driver in to a vendored local
library, pending inclusion in upstream NeoPixelBus.

Driver is enabled only for XTensa chips; there's some unresolved
issue with nested interrupts on RISCV.
2025-10-04 17:11:44 -04:00
Damian Schneider
a50f12d000 Merge pull request #4918 from wled/tricolor-fix-015
Bugfix for FX: Tri Fade
2025-09-28 19:53:16 +02:00
Damian Schneider
0516dfed78 bugfix: do not reset segments if unchanged #4969
lines were swapped, causing segment reset on every preset call.
2025-09-28 17:06:31 +02:00
Damian Schneider
5a52f4a4cd do not mark for reset if boundsUnchanged. 2025-09-28 16:58:42 +02:00
Blaž Kristan
fc35f0aab2 Bugfix for FX: Tri Fade
- incorrectly calculated counter and progress
2025-09-08 11:40:57 +02:00
Damian Schneider
68a853455d Merge pull request #4914 from DedeHai/percentFX_UI_fix_015
fix ancient UI bug that hides the speed slider in percent FX 0.15 edition
2025-09-07 20:34:36 +02:00
Damian Schneider
c24f67c479 fix ancient bug in percent FX 2025-09-05 19:55:21 +02:00
netmindz
d1763024bf Merge pull request #4845 from Arcitec/0_15_x-fix-sync
(0.15.2 backport) Fix broken Sync button after 0.15 refactor
2025-08-31 12:30:37 +01:00
netmindz
b155f80dc7 Merge pull request #4849 from Arcitec/0_15_x-improve-version-info
(0.15.2 backport): Make version information consistent across update interfaces
2025-08-20 07:02:01 +01:00
Arcitec
2df536c0a4 0.15.x: Make version information consistent across update interfaces
The duplication of logic and the formatting differences between the "OTA Updates" and "Security & Updates" pages made it very difficult to find the exact version details.

With this change, both update-pages now share the same consistent and detailed formatting, making it easy for users to identify which exact version and binary of WLED they've installed.

The version format has also been improved to make it much easier to understand.
2025-08-19 18:27:02 +02:00
Arcitec
dba31cb433 Fix broken Sync button after 0.15 refactor
In the past, the "notify direct" flag controlled all network syncing, propagating all color changes to other devices on the network. Pressing the UI Sync button only toggled this flag, so "notify direct" was set to false by default.

In version 0.15, a separate "master" sync flag was introduced, and the UI Sync button now only activates this master flag. However, the rest of the flag defaults weren't configured to sync anything at all. As a result, users pressing Sync saw *no* syncing at all, leading to multiple bug reports.

Defaults are now user-friendly: Enabling Sync on a WLED device syncs all of *its* color changes, whether made via the UI, API or remote button, providing a consistent experience which matches the intended behavior from past WLED versions.

Philips Hue sync is now also disabled by default, making the stock defaults focused on WLED devices. Users with other RGB ecosystems can manually enable the Hue or Alexa syncing in the settings.
2025-08-17 18:15:12 +02:00
netmindz
7a52144e98 Merge pull request #4834 from willmmiles/0_15_x_wsonly
0.15 - updated AsyncWebServer and AsyncTCP
2025-08-13 06:41:28 +01:00
Will Miles
9c82add757 Downtune AsyncTCP stack size
We downtuned the stack usage of AsyncTCP, and at some point in the
history of our fork, this got folded in to the default.  Re-apply the
stack size we've been using and recover that RAM.
2025-08-10 09:05:24 -04:00
Will Miles
4e1ca9be49 Update to AsyncTCP 3.4.7
Bugfix on 3.4.6
2025-08-10 09:05:15 -04:00
Will Miles
a9f52a132b Update AsyncWebServer and AsyncTCP
This should fix (or at least improve) some of the crash cases under
excessive web server load.
2025-08-10 09:04:31 -04:00
Damian Schneider
0ecae7e831 Bugfix for brightness factor upon save: fixes #4824 (#4827) 2025-08-09 10:20:01 +02:00
Will Tatam
a24d4bc7e9 Revert "Revert NeoPixelBus back to 2.8.0 due to flicker issues"
Not possible in isolation as code depends on newer version

This reverts commit b6d9aad6b4.
2025-08-02 17:00:33 +01:00
Will Tatam
26080b23b1 0.15.2-beta1 2025-08-02 16:58:22 +01:00
Will Tatam
b6d9aad6b4 Revert NeoPixelBus back to 2.8.0 due to flicker issues 2025-08-02 16:55:47 +01:00
Will Tatam
4f3992b4dc 0.15.1 2025-07-30 23:43:29 +01:00
Will Tatam
78252c6885 Add esp32dev_v4 env 2025-07-27 14:29:16 +01:00
Will Tatam
9dc7e81ba7 Add DMX Input support to builds 2025-07-27 14:25:58 +01:00
Will Tatam
ab2c975c36 Add esp32dev_V4 to default build 2025-07-27 13:59:31 +01:00
Will Tatam
ec51804f33 0.15.1-rc2 2025-07-27 11:39:14 +01:00
Will Tatam
83ae6d07a7 Filter release notes for releaseBranch: 0_15_x 2025-07-27 11:36:30 +01:00
Will Tatam
4b42f6bbe2 update to 0.15.1-rc1 2025-07-26 19:54:09 +01:00
Blaž Kristan
b27541e3e4 Set parallel I2S on by default 2025-07-26 20:47:15 +02:00
Will Miles
6fa2f4893d Merge pull request #4766 from Arcitec/backport-i2s-bus-selection
(0.15.x backport) bus_wrapper: Use parallel I2S first when enabled
2025-07-21 19:25:28 -04:00
Blaž Kristan
39f3c99cc1 Merge pull request #4763 from wled/fix-parallel-i2s-selection
Prevent parallel I2S use if different LED types are used.
2025-07-16 10:18:24 +02:00
Will Miles
e428e80d94 bus_wrapper: Update comments to reflect RMT usage 2025-07-11 01:05:15 +02:00
Will Miles
cfa8b735f4 bus_wrapper: Use parallel I2S first when enabled 2025-07-11 01:05:04 +02:00
Blaž Kristan
1808fa776b Prevent parallel I2S use if different LED types are used.
- helps with #4315
- add-on to #4762
- increases parallel I2S LED limit to 600 per bus
2025-07-09 20:58:13 +02:00
Blaž Kristan
232dc044e7 Fix color conversion bug for parallel I2S output
- fixes wled#4719
2025-07-05 22:29:38 +02:00
Blaž Kristan
a25fc6e098 Cherry pick fix 2025-07-01 11:36:12 +02:00
Blaž Kristan
00ab1daadb Fix for #4752 2025-07-01 10:33:11 +02:00
Blaž Kristan
2f31ff047d Fix missing "adPal" ID 2025-05-29 14:15:37 +02:00
Damian Schneider
5ec39f7fd3 bugfix in enumerating buttons and busses (#4657)
char value was changed from "55" to 'A' which is 65.
need to deduct 10 so the result is 'A' if index counter is 10.
2025-04-26 16:11:12 +02:00
netmindz
ecfe6e625d Merge pull request #4595 from wled/cherry-pick-mulifix
Cherry pick mulifix
2025-04-15 18:08:25 +00:00
Damian Schneider
2e4f3f8729 Merge pull request #4624 from DedeHai/0_15_x_randompalette_timing_fix
fix timer overflow bug in handleRandomPalette()
2025-04-13 09:27:14 +02:00
Damian Schneider
0597102f7f fix timer overflow bug in handleRandomPalette() 2025-03-29 10:25:25 +01:00
Blaž Kristan
6e7fffefec Merge pull request #4596 from Dschogo/patch-1
Fix wipe effect smoothness
2025-03-15 14:37:46 +01:00
Dschogo
a353a64568 Fix wipe effect smoothness
With update to 15.x many vars got changed to unsigned.
Wipe relies on truncation (of an uint16_t) for modulo operation, and was therefore broken.

Using directly an uint16_t like my proposal *should* be overall faster than using an unsigned and then doing a modulo. (Not an expert)
2025-03-15 13:38:34 +01:00
Blaž Kristan
5cac18f844 Update map1D2D 2025-03-13 16:44:20 +01:00
Blaž Kristan
3830d49bf8 Additional fix 2025-03-13 16:35:54 +01:00
Blaž Kristan
22eee967c2 Cherry pick fixes 2025-03-13 16:27:20 +01:00
Blaž Kristan
8654c2e4da Bugfix for #4590 2025-03-11 11:40:29 +01:00
Blaž Kristan
9bddfb1158 Cherry-pick fix 2025-03-11 10:58:49 +01:00
Blaž Kristan
7455ea7dde Avoid shadowing global col[] 2025-03-11 10:55:25 +01:00
Blaž Kristan
741bdf08ec Clarify use of index counter 2025-03-11 10:52:00 +01:00
Blaž Kristan
bbc9b9c173 Use constant CALL_MODE_INIT 2025-03-11 10:50:39 +01:00
Blaž Kristan
a38d6075c7 Bugfix in settings 2025-03-11 10:49:42 +01:00
Blaž Kristan
762679177c Replace magic with cosntant 2025-03-11 10:48:40 +01:00
Blaž Kristan
b008a6476b W Hex entry bugfix & optiisation 2025-03-11 10:47:59 +01:00
Blaž Kristan
47a9e4aa51 Shifting bugfix & size tuning in fade_out 2025-03-11 10:47:18 +01:00
Blaž Kristan
249c124176 Clear spaced segment
- also wait for strip before updating segment
2025-03-11 10:39:27 +01:00
Blaž Kristan
e16c4b8681 UI info 2025-03-11 10:29:46 +01:00
netmindz
7b521c7c40 Merge pull request #4589 from PaoloTK/add_last_output_warning
Add warning about unconfigurable outputs
2025-03-11 08:13:42 +00:00
PaoloTK
4c01893bd8 fix indentation 2025-03-10 22:53:38 +01:00
PaoloTK
ddec6fbb11 swap tags 2025-03-08 16:14:53 +01:00
PaoloTK
d9629039a6 add warning about unconfigurable outputs 2025-03-08 12:01:36 +01:00
netmindz
0a28acf6e7 Generate changelog 2025-02-22 18:23:59 +00:00
Will Tatam
6572efbf9f Update version to 0.15.1.beta2 2025-02-22 18:02:31 +00:00
Blaž Kristan
dbe76479a2 Merge pull request #4484 from blazoncek/parallel-I2S
WWA strip support & parallel I2S for S2/S3 (bumping outputs from 5/4 to 12)
2025-02-22 18:44:10 +01:00
netmindz
dc3d463925 Merge pull request #4428 from blazoncek/waterfall-fix
FX: Waterfall, Matripix & Dissolve fix
2025-02-22 17:49:10 +01:00
netmindz
f593d404cb Merge pull request #4244 from MoonModules/framerate_ac015
Improved framerate control code - strip.show(), strip.service()
2025-02-22 11:21:27 +00:00
netmindz
b75a2de485 Merge pull request #4356 from blazoncek/json-cycle
Proper fix for #3605 & #4346
2025-02-22 11:21:09 +00:00
Blaž Kristan
642a9e3652 Idle current bugfix (#4402) 2025-02-22 11:20:34 +00:00
Frank
85d3f6f11c Merge pull request #4398 from Aircoookie/4395-platformio_override
update platformio override example file (solves #4395)
2025-02-22 11:19:48 +00:00
Frank
f490908278 Merge pull request #4439 from dosipod/Upstream_PRs
Update readme.md for rgb-rotary-encoder usermod
2025-02-22 11:17:29 +00:00
Frank
1fc3cc83bd Merge pull request #4450 from adafruit/main
Add correct pin availability for ESP32 Mini modules
2025-02-22 11:16:06 +00:00
Will Miles
e96fd8ae58 Merge pull request #4511 from mlichvar/main
fix reproduction in game of life
2025-02-22 11:06:54 +00:00
netmindz
c46e328b59 Merge pull request #4556 from spiro-c/npm-check
Fix for: Build should stop if npm fails #4513
2025-02-22 11:02:32 +00:00
Damian Schneider
fa2f831044 fix for incorrect hardware timing 2025-02-16 10:36:03 +00:00
Damian Schneider
1bf13ea525 BUGFIX in oscillate FX (#4494)
effect was changed from int to uint but it relied on negative numbers. fixed by checking overflow and a cast.
2025-02-16 10:35:40 +00:00
maxi4329
edc6022441 Fix for #4153 (#4253)
* fix for #4153

* only load touch/mouse events for touch/mouse devices

* undid formating changes

* undid more formating changes

* undid all formating changes

* use pointerover and pointerout eventlisteners
2025-02-15 15:16:43 +01:00
Damian Schneider
2ac4d03160 Fixes first pixel not being set in Stream FX (#4542)
* Fixes first pixel not being set
* added fix to Stream 2 as well
2025-02-10 20:34:24 +01:00
netmindz
cc4a4c4ae1 Merge pull request #4428 from blazoncek/waterfall-fix
FX: Waterfall, Matripix & Dissolve fix
2025-01-16 15:55:33 +00:00
Will Tatam
4e11ecda4b Set version to 0.15.1.beta1 2025-01-16 13:18:50 +00:00
netmindz
473700e4c0 Merge pull request #4018 from Brandon502/main
Added Cube Mapping Tool
2025-01-16 13:03:47 +00:00
netmindz
0d5a0fb830 Merge pull request #4386 from DedeHai/ESPNow_glitchfix
Fix for ESPNow remote causing output glitches
2025-01-16 13:01:40 +00:00
Damian Schneider
012143bd7b BUGFIX in oscillate FX
effect was changed from int to uint but it relied on negative numbers. fixed by checking overflow and a cast.
2025-01-14 18:46:47 +01:00
Damian Schneider
700a7076fd added a delay after switching relay (#4474)
- helps to stabilize power on the LEDs before sending data
2025-01-12 15:20:27 +01:00
Damian Schneider
5fc2175dd4 Playlist output glitchfix update: found it also happens on S3 (#4462)
* Fix output glitches when playlist changes preset update: glitches also happen on S3
2025-01-12 15:20:27 +01:00
Damian Schneider
c9b95e22d3 Fix output glitches when playlist changes preset (#4442)
same issue as with https://github.com/Aircoookie/WLED/pull/4386
waiting on bus to finish updating before file access fixes the glitches.
this issue is only present on S2 and C3, not on ESP8266 or dual-core ESPs, the fix is only applied for these two.
2025-01-12 15:20:27 +01:00
Damian Schneider
a265318037 fixed CIE brightness calculation for PWM outputs 2025-01-12 15:20:27 +01:00
Damian Schneider
866a4c8ab6 fix for repeating glitch
glitch appeared every 65s due to missing uint16_t overflow.
2025-01-12 15:20:26 +01:00
TripleWhy
9dc1022010 palette effect overflow fix 2025-01-10 17:41:12 +00:00
Will Tatam
faadb67eb0 update changelog for 2412100 2024-12-10 20:20:28 +00:00
Will Tatam
a111a2e7a1 update version to 0.15.0 2024-12-10 20:13:12 +00:00
netmindz
32864d8986 update version to 0.15.0 2024-12-10 18:54:39 +00:00
netmindz
d7bebc2659 Merge pull request #4360 from euphi/patch-1
Usermod BME280: Fix "Unit of Measurement" for temperature
2024-12-10 08:07:24 +00:00
Blaž Kristan
af410ae2d0 WiFi reconnect bugfix
- additional debug info
2024-12-09 23:06:21 +00:00
Will Tatam
1891cc816f Merge branch '0_15_0' of https://github.com/Aircoookie/WLED into 0_15_0 2024-12-09 09:33:33 +00:00
Ian Hubbertz
9a4073e606 Fix "Unit of Measurement" for BME280 temperature
The Unit of Measurement ("tempScale") of the MQTT message is set for each published measurement - but not for the homeassistant discovery.

Thus, openhab (and mabye other systems?) don't recognize the value as "Number" - so it uses String instead. This is somehow annoying when trying to configure the sensor channel to be linked to an existing item in OpenHAB.
(Items that are created automatically or with "Add point to model" can be configured in a way that the String is transformed to Number or Number:Temperature, but existing Items cannot be linked).

When a "Unit of Measurement" is set in HomeAssistant discovery, the HA binding of OpenHAB notices that the MQTT String is a number and automatically converts it.
2024-12-07 21:33:46 +01:00
Damian Schneider
b78229d1e2 Fix update for #4193 (twinkle fox & cat)
- previous fix worked but there was still an overflow after some time passed. there were still missing roll-overs apparently: reverting these two variables back to 16bit/8bit should fix it for good.
2024-12-05 06:49:17 +01:00
Damian Schneider
71b242874f Fix for #4193 (twinkle fox & cat) 2024-12-04 20:39:19 +01:00
Will Tatam
9328e6faca Merge branch '0_15_0' of https://github.com/Aircoookie/WLED into 0_15_0 2024-11-30 12:07:46 +00:00
netmindz
bbacc2daae Merge pull request #4287 from netmindz/v0.15.0-rcX
0.15.0-rc1
2024-11-30 11:42:46 +00:00
Will Tatam
3e22f9cabb Merge branch '0_15' into 0_15_0 2024-11-30 11:39:13 +00:00
Will Miles
685ad83d4b PolyBus: Clarify use of clock_kHz
While not used by most bus types, it's not an optional parameter.
2024-11-27 19:36:16 +00:00
Will Miles
62ddb18a1a BusDigital::begin: Pass clock rate argument
Fixes bug introduced by #4312.
2024-11-27 19:36:01 +00:00
Blaž Kristan
6a12378475 Fix for #4321 2024-11-27 19:33:29 +00:00
Blaž Kristan
d26b3108da Fix for #4300 2024-11-27 11:04:52 +00:00
Will Tatam
c89e4576b4 Dedicated release workflow 2024-11-27 10:42:38 +00:00
Will Tatam
bc79f44a26 workflow refactor to used shared build file 2024-11-27 10:42:29 +00:00
Will Tatam
7ece14ff3f rename workflow ready for workflow refactor 2024-11-27 10:42:19 +00:00
Will Tatam
0b3643132b Update to 2411250 2024-11-25 23:47:41 +00:00
Will Tatam
a5693fbf8d Merge branch '0_15_0' into v0.15.0-rcX 2024-11-25 23:45:34 +00:00
maxi4329
5c5b70f52b version changed to 20 2024-11-25 23:34:27 +00:00
maxi4329
ae97e388a6 indened formating 2024-11-25 23:34:20 +00:00
maxi4329
37cddcaacc specified required nodejs ver 2024-11-25 23:34:13 +00:00
Will Miles
a1b332fc78 handleSet: Fix incorrect response generation
Don't generate a response if there's no HTTP request.

Fixes #4269
2024-11-25 23:33:53 +00:00
Frank
86d7c24513 rename delay -> frameDelay
Avoiding name collisions with the 'delay' function.
2024-11-25 23:32:27 +00:00
Damian Schneider
b28add3b8b Added define for bitshift, removed dithering
dithering is not really needed, the FPS_MULTIPLIER is a much better option.
2024-11-25 23:31:17 +00:00
Damian Schneider
5fd3a513a4 bugfix
bitshift was still set from testing, forgot to update
2024-11-25 23:31:08 +00:00
Damian Schneider
b98a8a10b0 improved FPS calc resolution, added averaging & multiplier compileflags
Fixed point calculation for improved accuracy, dithering in debug builds only.
Averaging and optional multiplier can be set as compile flags, example for speed testing with long averaging and a 10x multiplier:

-D FPS_CALC_AVG=200
-D FPS_MULTIPLIER=10

The calculation resolution is limited (9.7bit fixed point) so values larger than 200 can hit resolution limit and get stuck before reaching the final value.

If WLED_DEBUG is defined, dithering is added to the returned value so sub-frame accuracy is possible in post-processingwithout enabling the multiplier.
2024-11-25 23:30:59 +00:00
netmindz
2bee2793ef Merge pull request #4309 from netmindz/release-name-fix
Fix release name macro expansion
2024-11-25 23:12:04 +00:00
netmindz
2f6fa66f4d Merge pull request #4309 from netmindz/release-name-fix
Fix release name macro expansion
2024-11-25 23:04:22 +00:00
Will Tatam
5d38acd787 Update CHANGELOG 2024-11-24 21:04:50 +00:00
Will Tatam
e607fcb5c5 Merge branch '0_15_0' into v0.15.0-rcX 2024-11-24 21:04:08 +00:00
Frank
8a18555ae4 Merge pull request #4243 from MoonModules/AC_0_15_S3-WROOM2
Add support for ESP32-S3 WROOM-2 (solves #4099)
2024-11-24 20:49:49 +00:00
netmindz
beb709dc8f Merge pull request #4312 from willmmiles/mixed-led-crash
Defer calling begin() on buses
2024-11-24 20:46:45 +00:00
Will Tatam
7a58c69a80 Stip \" from WLED_RELEASE_NAME 2024-11-23 16:50:34 +00:00
Will Tatam
1082c85789 Fix WLED_RELEASE_NAME=ESP32-S3_WROOM-2 2024-11-23 16:37:54 +00:00
Will Tatam
568d2edd96 Remove TOSTRING for releaseString and add quotes to WLED_RELEASE_NAME 2024-11-23 16:37:03 +00:00
Will Tatam
d2d56ebbd2 0.15.0-rc1 2024-11-15 20:27:30 +00:00
58 changed files with 3431 additions and 1226 deletions

View File

@@ -18,9 +18,17 @@ jobs:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: "✏️ Generate release changelog"
id: changelog
uses: janheinrichmerker/action-github-changelog-generator@v2.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
sinceTag: v0.15.0
releaseBranch: 0_15_x
- name: Create draft release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
draft: True
files: |
*.bin

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ wled-update.sh
/build_output/
/node_modules/
/logs/
/wled00/extLibs
/wled00/LittleFS

View File

@@ -1,5 +1,23 @@
## WLED changelog
#### Build 2412100
- WLED 0.15.0 release
- Usermod BME280: Fix "Unit of Measurement" for temperature
- WiFi reconnect bugfix (@blazoncek)
#### Build 2411250
- WLED 0.15.0-rc1 release
- Add support for esp32S3_wroom2 (#4243 by @softhack007)
- Fix mixed LED SK6812 and ws2812b booloop (#4301 by @willmmiles)
- Improved FPS calculation (by DedeHai)
- Fix crashes when using HTTP API within MQTT (#4269 by @willmmiles)
- Fix array overflow in exploding_fireworks (#4120 by @willmmiles)
- Fix MQTT topic buffer length (#4293 by @WouterGritter)
- Fix SparkFunDMX fix for possible array bounds violation in DMX.write (by @softhack007)
- Allow TV Simulator on single LED segments (by @softhack007)
- Fix WLED_RELEASE_NAME (by @netmindz)
#### Build 2410270
- WLED 0.15.0-b7 release
- Re-license the WLED project from MIT to EUPL (#4194 by @Aircoookie)

View File

@@ -0,0 +1,469 @@
/*-------------------------------------------------------------------------
NeoPixel driver for ESP32 RMTs using High-priority Interrupt
(NB. This cannot be mixed with the non-HI driver.)
Written by Will M. Miles.
I invest time and resources providing this open source code,
please support me by donating (see https://github.com/Makuna/NeoPixelBus)
-------------------------------------------------------------------------
This file is part of the Makuna/NeoPixelBus library.
NeoPixelBus is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
NeoPixelBus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with NeoPixel. If not, see
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#pragma once
#if defined(ARDUINO_ARCH_ESP32)
// Use the NeoEspRmtSpeed types from the driver-based implementation
#include <NeoPixelBus.h>
namespace NeoEsp32RmtHiMethodDriver {
// Install the driver for a specific channel, specifying timing properties
esp_err_t Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t resetDuration);
// Remove the driver on a specific channel
esp_err_t Uninstall(rmt_channel_t channel);
// Write a buffer of data to a specific channel.
// Buffer reference is held until write completes.
esp_err_t Write(rmt_channel_t channel, const uint8_t *src, size_t src_size);
// Wait until transaction is complete.
esp_err_t WaitForTxDone(rmt_channel_t channel, TickType_t wait_time);
};
template<typename T_SPEED, typename T_CHANNEL> class NeoEsp32RmtHIMethodBase
{
public:
typedef NeoNoSettings SettingsObject;
NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
_sizeData(pixelCount * elementSize + settingsSize),
_pin(pin)
{
construct();
}
NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) :
_sizeData(pixelCount* elementSize + settingsSize),
_pin(pin),
_channel(channel)
{
construct();
}
~NeoEsp32RmtHIMethodBase()
{
// wait until the last send finishes before destructing everything
// arbitrary time out of 10 seconds
ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS));
ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Uninstall(_channel.RmtChannelNumber));
gpio_matrix_out(_pin, SIG_GPIO_OUT_IDX, false, false);
pinMode(_pin, INPUT);
free(_dataEditing);
free(_dataSending);
}
bool IsReadyToUpdate() const
{
return (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 0)));
}
void Initialize()
{
rmt_config_t config = {};
config.rmt_mode = RMT_MODE_TX;
config.channel = _channel.RmtChannelNumber;
config.gpio_num = static_cast<gpio_num_t>(_pin);
config.mem_block_num = 1;
config.tx_config.loop_en = false;
config.tx_config.idle_output_en = true;
config.tx_config.idle_level = T_SPEED::IdleLevel;
config.tx_config.carrier_en = false;
config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
config.clk_div = T_SPEED::RmtClockDivider;
ESP_ERROR_CHECK(rmt_config(&config)); // Uses ESP library
ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Install(_channel.RmtChannelNumber, T_SPEED::RmtBit0, T_SPEED::RmtBit1, T_SPEED::RmtDurationReset));
}
void Update(bool maintainBufferConsistency)
{
// wait for not actively sending data
// this will time out at 10 seconds, an arbitrarily long period of time
// and do nothing if this happens
if (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS)))
{
// now start the RMT transmit with the editing buffer before we swap
ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::Write(_channel.RmtChannelNumber, _dataEditing, _sizeData));
if (maintainBufferConsistency)
{
// copy editing to sending,
// this maintains the contract that "colors present before will
// be the same after", otherwise GetPixelColor will be inconsistent
memcpy(_dataSending, _dataEditing, _sizeData);
}
// swap so the user can modify without affecting the async operation
std::swap(_dataSending, _dataEditing);
}
}
bool AlwaysUpdate()
{
// this method requires update to be called only if changes to buffer
return false;
}
bool SwapBuffers()
{
std::swap(_dataSending, _dataEditing);
return true;
}
uint8_t* getData() const
{
return _dataEditing;
};
size_t getDataSize() const
{
return _sizeData;
}
void applySettings([[maybe_unused]] const SettingsObject& settings)
{
}
private:
const size_t _sizeData; // Size of '_data*' buffers
const uint8_t _pin; // output pin number
const T_CHANNEL _channel; // holds instance for multi channel support
// Holds data stream which include LED color values and other settings as needed
uint8_t* _dataEditing; // exposed for get and set
uint8_t* _dataSending; // used for async send using RMT
void construct()
{
_dataEditing = static_cast<uint8_t*>(malloc(_sizeData));
// data cleared later in Begin()
_dataSending = static_cast<uint8_t*>(malloc(_sizeData));
// no need to initialize it, it gets overwritten on every send
}
};
// normal
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsMethod;
typedef NeoEsp32RmtHINWs2805Method NeoEsp32RmtHINWs2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsMethod;
typedef NeoEsp32RmtHI0Ws2805Method NeoEsp32RmtHI0Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsMethod;
typedef NeoEsp32RmtHI1Ws2805Method NeoEsp32RmtHI1Ws2814Method;
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsMethod;
typedef NeoEsp32RmtHI2Ws2805Method NeoEsp32RmtHI2Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsMethod;
typedef NeoEsp32RmtHI3Ws2805Method NeoEsp32RmtHI3Ws2814Method;
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsMethod;
typedef NeoEsp32RmtHI4Ws2805Method NeoEsp32RmtHI4Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsMethod;
typedef NeoEsp32RmtHI5Ws2805Method NeoEsp32RmtHI5Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsMethod;
typedef NeoEsp32RmtHI6Ws2805Method NeoEsp32RmtHI6Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsMethod;
typedef NeoEsp32RmtHI7Ws2805Method NeoEsp32RmtHI7Ws2814Method;
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
// inverted
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsInvertedMethod;
typedef NeoEsp32RmtHINWs2805InvertedMethod NeoEsp32RmtHINWs2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsInvertedMethod;
typedef NeoEsp32RmtHI0Ws2805InvertedMethod NeoEsp32RmtHI0Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsInvertedMethod;
typedef NeoEsp32RmtHI1Ws2805InvertedMethod NeoEsp32RmtHI1Ws2814InvertedMethod;
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsInvertedMethod;
typedef NeoEsp32RmtHI2Ws2805InvertedMethod NeoEsp32RmtHI2Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsInvertedMethod;
typedef NeoEsp32RmtHI3Ws2805InvertedMethod NeoEsp32RmtHI3Ws2814InvertedMethod;
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsInvertedMethod;
typedef NeoEsp32RmtHI4Ws2805InvertedMethod NeoEsp32RmtHI4Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsInvertedMethod;
typedef NeoEsp32RmtHI5Ws2805InvertedMethod NeoEsp32RmtHI5Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsInvertedMethod;
typedef NeoEsp32RmtHI6Ws2805InvertedMethod NeoEsp32RmtHI6Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsInvertedMethod;
typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod;
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
#endif

View File

@@ -0,0 +1,12 @@
{
"name": "NeoESP32RmtHI",
"build": { "libArchive": false },
"platforms": ["espressif32"],
"dependencies": [
{
"owner": "makuna",
"name": "NeoPixelBus",
"version": "^2.8.3"
}
]
}

View File

@@ -0,0 +1,263 @@
/* RMT ISR shim
* Bridges from a high-level interrupt to the C++ code.
*
* This code is largely derived from Espressif's 'hli_vector.S' Bluetooth ISR.
*
*/
#if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI)
#include <freertos/xtensa_context.h>
#include "sdkconfig.h"
#include "soc/soc.h"
/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */
#ifndef CONFIG_BTDM_CTRL_HLI
/*
Select interrupt based on system check level
- Base ESP32: could be 4 or 5, depends on platform config
- S2: 5
- S3: 5
*/
#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5
/* Use level 4 */
#define RFI_X 4
#define xt_highintx xt_highint4
#else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
/* Use level 5 */
#define RFI_X 5
#define xt_highintx xt_highint5
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
// Register map, based on interrupt level
#define EPC_X (EPC + RFI_X)
#define EXCSAVE_X (EXCSAVE + RFI_X)
// The sp mnemonic is used all over in ESP's assembly, though I'm not sure where it's expected to be defined?
#define sp a1
/* Interrupt stack size, for C code. */
#define RMT_INTR_STACK_SIZE 512
/* Save area for the CPU state:
* - 64 words for the general purpose registers
* - 7 words for some of the special registers:
* - WINDOWBASE, WINDOWSTART only WINDOWSTART is truly needed
* - SAR, LBEG, LEND, LCOUNT since the C code might use these
* - EPC1 since the C code might cause window overflow exceptions
* This is not laid out as standard exception frame structure
* for simplicity of the save/restore code.
*/
#define REG_FILE_SIZE (64 * 4)
#define SPECREG_OFFSET REG_FILE_SIZE
#define SPECREG_SIZE (7 * 4)
#define REG_SAVE_AREA_SIZE (SPECREG_OFFSET + SPECREG_SIZE)
.data
_rmt_intr_stack:
.space RMT_INTR_STACK_SIZE
_rmt_save_ctx:
.space REG_SAVE_AREA_SIZE
.section .iram1,"ax"
.global xt_highintx
.type xt_highintx,@function
.align 4
xt_highintx:
movi a0, _rmt_save_ctx
/* save 4 lower registers */
s32i a1, a0, 4
s32i a2, a0, 8
s32i a3, a0, 12
rsr a2, EXCSAVE_X /* holds the value of a0 */
s32i a2, a0, 0
/* Save special registers */
addi a0, a0, SPECREG_OFFSET
rsr a2, WINDOWBASE
s32i a2, a0, 0
rsr a2, WINDOWSTART
s32i a2, a0, 4
rsr a2, SAR
s32i a2, a0, 8
#if XCHAL_HAVE_LOOPS
rsr a2, LBEG
s32i a2, a0, 12
rsr a2, LEND
s32i a2, a0, 16
rsr a2, LCOUNT
s32i a2, a0, 20
#endif
rsr a2, EPC1
s32i a2, a0, 24
/* disable exception mode, window overflow */
movi a0, PS_INTLEVEL(RFI_X+1) | PS_EXCM
wsr a0, PS
rsync
/* Save the remaining physical registers.
* 4 registers are already saved, which leaves 60 registers to save.
* (FIXME: consider the case when the CPU is configured with physical 32 registers)
* These 60 registers are saved in 5 iterations, 12 registers at a time.
*/
movi a1, 5
movi a3, _rmt_save_ctx + 4 * 4
/* This is repeated 5 times, each time the window is shifted by 12 registers.
* We come here with a1 = downcounter, a3 = save pointer, a2 and a0 unused.
*/
1:
s32i a4, a3, 0
s32i a5, a3, 4
s32i a6, a3, 8
s32i a7, a3, 12
s32i a8, a3, 16
s32i a9, a3, 20
s32i a10, a3, 24
s32i a11, a3, 28
s32i a12, a3, 32
s32i a13, a3, 36
s32i a14, a3, 40
s32i a15, a3, 44
/* We are about to rotate the window, so that a12-a15 will become the new a0-a3.
* Copy a0-a3 to a12-15 to still have access to these values.
* At the same time we can decrement the counter and adjust the save area pointer
*/
/* a0 is constant (_rmt_save_ctx), no need to copy */
addi a13, a1, -1 /* copy and decrement the downcounter */
/* a2 is scratch so no need to copy */
addi a15, a3, 48 /* copy and adjust the save area pointer */
beqz a13, 2f /* have saved all registers ? */
rotw 3 /* rotate the window and go back */
j 1b
/* the loop is complete */
2:
rotw 4 /* this brings us back to the original window */
/* a0 still points to _rmt_save_ctx */
/* Can clear WINDOWSTART now, all registers are saved */
rsr a2, WINDOWBASE
/* WINDOWSTART = (1 << WINDOWBASE) */
movi a3, 1
ssl a2
sll a3, a3
wsr a3, WINDOWSTART
_highint_stack_switch:
movi a0, 0
movi sp, _rmt_intr_stack + RMT_INTR_STACK_SIZE - 16
s32e a0, sp, -12 /* For GDB: set null SP */
s32e a0, sp, -16 /* For GDB: set null PC */
movi a0, _highint_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */
/* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */
movi a6, PS_INTLEVEL(RFI_X) | PS_UM | PS_WOE
wsr a6, PS
rsync
/* Call C handler */
mov a6, sp
call4 NeoEsp32RmtMethodIsr
l32e sp, sp, -12 /* switch back to the original stack */
/* Done with C handler; re-enable exception mode, disabling window overflow */
movi a2, PS_INTLEVEL(RFI_X+1) | PS_EXCM /* TOCHECK */
wsr a2, PS
rsync
/* Restore the special registers.
* WINDOWSTART will be restored near the end.
*/
movi a0, _rmt_save_ctx + SPECREG_OFFSET
l32i a2, a0, 8
wsr a2, SAR
#if XCHAL_HAVE_LOOPS
l32i a2, a0, 12
wsr a2, LBEG
l32i a2, a0, 16
wsr a2, LEND
l32i a2, a0, 20
wsr a2, LCOUNT
#endif
l32i a2, a0, 24
wsr a2, EPC1
/* Restoring the physical registers.
* This is the reverse to the saving process above.
*/
/* Rotate back to the final window, then start loading 12 registers at a time,
* in 5 iterations.
* Again, a1 is the downcounter and a3 is the save area pointer.
* After each rotation, a1 and a3 are copied from a13 and a15.
* To simplify the loop, we put the initial values into a13 and a15.
*/
rotw -4
movi a15, _rmt_save_ctx + 64 * 4 /* point to the end of the save area */
movi a13, 5
1:
/* Copy a1 and a3 from their previous location,
* at the same time decrementing and adjusting the save area pointer.
*/
addi a1, a13, -1
addi a3, a15, -48
/* Load 12 registers */
l32i a4, a3, 0
l32i a5, a3, 4
l32i a6, a3, 8
l32i a7, a3, 12
l32i a8, a3, 16
l32i a9, a3, 20
l32i a10, a3, 24
l32i a11, a3, 28 /* ensure PS and EPC written */
l32i a12, a3, 32
l32i a13, a3, 36
l32i a14, a3, 40
l32i a15, a3, 44
/* Done with the loop? */
beqz a1, 2f
/* If no, rotate the window and repeat */
rotw -3
j 1b
2:
/* Done with the loop. Only 4 registers (a0-a3 in the original window) remain
* to be restored. Also need to restore WINDOWSTART, since all the general
* registers are now in place.
*/
movi a0, _rmt_save_ctx
l32i a2, a0, SPECREG_OFFSET + 4
wsr a2, WINDOWSTART
l32i a1, a0, 4
l32i a2, a0, 8
l32i a3, a0, 12
rsr a0, EXCSAVE_X /* holds the value of a0 before the interrupt handler */
/* Return from the interrupt, restoring PS from EPS_X */
rfi RFI_X
/* The linker has no reason to link in this file; all symbols it exports are already defined
(weakly!) in the default int handler. Define a symbol here so we can use it to have the
linker inspect this anyway. */
.global ld_include_hli_vectors_rmt
ld_include_hli_vectors_rmt:
#endif // CONFIG_BTDM_CTRL_HLI
#endif // XTensa

View File

@@ -0,0 +1,507 @@
/*-------------------------------------------------------------------------
NeoPixel library helper functions for Esp32.
A BIG thanks to Andreas Merkle for the investigation and implementation of
a workaround to the GCC bug that drops method attributes from template methods
Written by Michael C. Miller.
I invest time and resources providing this open source code,
please support me by donating (see https://github.com/Makuna/NeoPixelBus)
-------------------------------------------------------------------------
This file is part of the Makuna/NeoPixelBus library.
NeoPixelBus is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
NeoPixelBus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with NeoPixel. If not, see
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#include <Arduino.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <algorithm>
#include "esp_idf_version.h"
#include "NeoEsp32RmtHIMethod.h"
#include "soc/soc.h"
#include "soc/rmt_reg.h"
#ifdef __riscv
#include "riscv/interrupt.h"
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
#include "hal/rmt_ll.h"
#else
/* Shims for older ESP-IDF v3; we can safely assume original ESP32 */
#include "soc/rmt_struct.h"
// Selected RMT API functions borrowed from ESP-IDF v4.4.8
// components/hal/esp32/include/hal/rmt_ll.h
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
__attribute__((always_inline))
static inline void rmt_ll_tx_reset_pointer(rmt_dev_t *dev, uint32_t channel)
{
dev->conf_ch[channel].conf1.mem_rd_rst = 1;
dev->conf_ch[channel].conf1.mem_rd_rst = 0;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_start(rmt_dev_t *dev, uint32_t channel)
{
dev->conf_ch[channel].conf1.tx_start = 1;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_stop(rmt_dev_t *dev, uint32_t channel)
{
RMTMEM.chan[channel].data32[0].val = 0;
dev->conf_ch[channel].conf1.tx_start = 0;
dev->conf_ch[channel].conf1.mem_rd_rst = 1;
dev->conf_ch[channel].conf1.mem_rd_rst = 0;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_enable_pingpong(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->apb_conf.mem_tx_wrap_en = enable;
}
__attribute__((always_inline))
static inline void rmt_ll_tx_enable_loop(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->conf_ch[channel].conf1.tx_conti_mode = enable;
}
__attribute__((always_inline))
static inline uint32_t rmt_ll_tx_get_channel_status(rmt_dev_t *dev, uint32_t channel)
{
return dev->status_ch[channel];
}
__attribute__((always_inline))
static inline void rmt_ll_tx_set_limit(rmt_dev_t *dev, uint32_t channel, uint32_t limit)
{
dev->tx_lim_ch[channel].limit = limit;
}
__attribute__((always_inline))
static inline void rmt_ll_enable_interrupt(rmt_dev_t *dev, uint32_t mask, bool enable)
{
if (enable) {
dev->int_ena.val |= mask;
} else {
dev->int_ena.val &= ~mask;
}
}
__attribute__((always_inline))
static inline void rmt_ll_enable_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->int_ena.val &= ~(1 << (channel * 3));
dev->int_ena.val |= (enable << (channel * 3));
}
__attribute__((always_inline))
static inline void rmt_ll_enable_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->int_ena.val &= ~(1 << (channel * 3 + 2));
dev->int_ena.val |= (enable << (channel * 3 + 2));
}
__attribute__((always_inline))
static inline void rmt_ll_enable_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
{
dev->int_ena.val &= ~(1 << (channel + 24));
dev->int_ena.val |= (enable << (channel + 24));
}
__attribute__((always_inline))
static inline void rmt_ll_clear_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel)
{
dev->int_clr.val = (1 << (channel * 3));
}
__attribute__((always_inline))
static inline void rmt_ll_clear_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel)
{
dev->int_clr.val = (1 << (channel * 3 + 2));
}
__attribute__((always_inline))
static inline void rmt_ll_clear_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel)
{
dev->int_clr.val = (1 << (channel + 24));
}
__attribute__((always_inline))
static inline uint32_t rmt_ll_get_tx_thres_interrupt_status(rmt_dev_t *dev)
{
uint32_t status = dev->int_st.val;
return (status & 0xFF000000) >> 24;
}
#endif
// *********************************
// Select method for binding interrupt
//
// - If the Bluetooth driver has registered a high-level interrupt, piggyback on that API
// - If we're on a modern core, allocate the interrupt with the API (old cores are bugged)
// - Otherwise use the low-level hardware API to manually bind the interrupt
#if defined(CONFIG_BTDM_CTRL_HLI)
// Espressif's bluetooth driver offers a helpful sharing layer; bring in the interrupt management calls
#include "hal/interrupt_controller_hal.h"
extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask);
#else /* !CONFIG_BTDM_CTRL_HLI*/
// Declare the our high-priority ISR handler
extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
#include "soc/periph_defs.h"
#endif
// Select level flag
#if defined(__riscv)
// RISCV chips don't block interrupts while scheduling; all we need to do is be higher than the WiFi ISR
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL3
#elif defined(CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5)
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL4
#else
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL5
#endif
// ESP-IDF v3 cannot enable high priority interrupts through the API at all;
// and ESP-IDF v4 on XTensa cannot enable Level 5 due to incorrect interrupt descriptor tables
#if !defined(__XTENSA__) || (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) || ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) && CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5))
#define NEOESP32_RMT_CAN_USE_INTR_ALLOC
// XTensa cores require the assembly bridge
#ifdef __XTENSA__
#define HI_IRQ_HANDLER nullptr
#define HI_IRQ_HANDLER_ARG ld_include_hli_vectors_rmt
#else
#define HI_IRQ_HANDLER NeoEsp32RmtMethodIsr
#define HI_IRQ_HANDLER_ARG nullptr
#endif
#else
/* !CONFIG_BTDM_CTRL_HLI && !NEOESP32_RMT_CAN_USE_INTR_ALLOC */
// This is the index of the LV5 interrupt vector - see interrupt descriptor table in idf components/hal/esp32/interrupt_descriptor_table.c
#define ESP32_LV5_IRQ_INDEX 26
#endif /* NEOESP32_RMT_CAN_USE_INTR_ALLOC */
#endif /* CONFIG_BTDM_CTRL_HLI */
// RMT driver implementation
struct NeoEsp32RmtHIChannelState {
uint32_t rmtBit0, rmtBit1;
uint32_t resetDuration;
const byte* txDataStart; // data array
const byte* txDataEnd; // one past end
const byte* txDataCurrent; // current location
size_t rmtOffset;
};
// Global variables
#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
static intr_handle_t isrHandle = nullptr;
#endif
static NeoEsp32RmtHIChannelState** driverState = nullptr;
constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2;
// Fill the RMT buffer memory
// This is implemented using many arguments instead of passing the structure object to ensure we do only one lookup
// All the arguments are passed in registers, so they don't need to be looked up again
static void IRAM_ATTR RmtFillBuffer(uint8_t channel, const byte** src_ptr, const byte* end, uint32_t bit0, uint32_t bit1, size_t* offset_ptr, size_t reserve) {
// We assume that (rmtToWrite % 8) == 0
size_t rmtToWrite = rmtBatchSize - reserve;
rmt_item32_t* dest =(rmt_item32_t*) &RMTMEM.chan[channel].data32[*offset_ptr + reserve]; // write directly in to RMT memory
const byte* psrc = *src_ptr;
*offset_ptr ^= rmtBatchSize;
if (psrc != end) {
while (rmtToWrite > 0) {
uint8_t data = *psrc;
for (uint8_t bit = 0; bit < 8; bit++)
{
dest->val = (data & 0x80) ? bit1 : bit0;
dest++;
data <<= 1;
}
rmtToWrite -= 8;
psrc++;
if (psrc == end) {
break;
}
}
*src_ptr = psrc;
}
if (rmtToWrite > 0) {
// Add end event
rmt_item32_t bit0_val = {{.val = bit0 }};
*dest = rmt_item32_t {{{ .duration0 = 0, .level0 = bit0_val.level1, .duration1 = 0, .level1 = bit0_val.level1 }}};
}
}
static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& state) {
// Reset context state
state.rmtOffset = 0;
// Fill the first part of the buffer with a reset event
// FUTURE: we could do timing analysis with the last interrupt on this channel
// Use 8 words to stay aligned with the buffer fill logic
rmt_item32_t bit0_val = {{.val = state.rmtBit0 }};
rmt_item32_t fill = {{{ .duration0 = 100, .level0 = bit0_val.level1, .duration1 = 100, .level1 = bit0_val.level1 }}};
rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0];
for (auto i = 0; i < 7; ++i) dest[i] = fill;
fill.duration1 = state.resetDuration > 1400 ? (state.resetDuration - 1400) : 100;
dest[7] = fill;
// Fill the remaining buffer with real data
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 8);
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
// Start operation
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
rmt_ll_tx_reset_pointer(&RMT, channel);
rmt_ll_tx_start(&RMT, channel);
}
extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) {
// Tx threshold interrupt
uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
while (status) {
uint8_t channel = __builtin_ffs(status) - 1;
if (driverState[channel]) {
// Normal case
NeoEsp32RmtHIChannelState& state = *driverState[channel];
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
} else {
// Danger - another driver got invoked?
rmt_ll_tx_stop(&RMT, channel);
}
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
}
};
// Wrapper around the register analysis defines
// For all currently supported chips, this is constant for all channels; but this is not true of *all* ESP32
static inline bool _RmtStatusIsTransmitting(rmt_channel_t channel, uint32_t status) {
uint32_t v;
switch(channel) {
#ifdef RMT_STATE_CH0
case 0: v = (status >> RMT_STATE_CH0_S) & RMT_STATE_CH0_V; break;
#endif
#ifdef RMT_STATE_CH1
case 1: v = (status >> RMT_STATE_CH1_S) & RMT_STATE_CH1_V; break;
#endif
#ifdef RMT_STATE_CH2
case 2: v = (status >> RMT_STATE_CH2_S) & RMT_STATE_CH2_V; break;
#endif
#ifdef RMT_STATE_CH3
case 3: v = (status >> RMT_STATE_CH3_S) & RMT_STATE_CH3_V; break;
#endif
#ifdef RMT_STATE_CH4
case 4: v = (status >> RMT_STATE_CH4_S) & RMT_STATE_CH4_V; break;
#endif
#ifdef RMT_STATE_CH5
case 5: v = (status >> RMT_STATE_CH5_S) & RMT_STATE_CH5_V; break;
#endif
#ifdef RMT_STATE_CH6
case 6: v = (status >> RMT_STATE_CH6_S) & RMT_STATE_CH6_V; break;
#endif
#ifdef RMT_STATE_CH7
case 7: v = (status >> RMT_STATE_CH7_S) & RMT_STATE_CH7_V; break;
#endif
default: v = 0;
}
return v != 0;
}
esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t reset) {
// Validate channel number
if (channel >= RMT_CHANNEL_MAX) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = ESP_OK;
if (!driverState) {
// First time init
driverState = reinterpret_cast<NeoEsp32RmtHIChannelState**>(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtHIChannelState*), MALLOC_CAP_INTERNAL));
if (!driverState) return ESP_ERR_NO_MEM;
// Ensure all interrupts are cleared before binding
RMT.int_ena.val = 0;
RMT.int_clr.val = 0xFFFFFFFF;
// Bind interrupt handler
#if defined(CONFIG_BTDM_CTRL_HLI)
// Bluetooth driver has taken the empty high-priority interrupt. Fortunately, it allows us to
// hook up another handler.
err = hli_intr_register(NeoEsp32RmtMethodIsr, nullptr, (uintptr_t) &RMT.int_st, 0xFF000000);
// 25 is the magic number of the bluetooth ISR on ESP32 - see soc/soc.h.
intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25);
intr_cntrl_ll_enable_interrupts(1<<25);
#elif defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
// Use the platform code to allocate the interrupt
// If we need the additional assembly bridge, we pass it as the "arg" to the IDF so it gets linked in
err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, HI_IRQ_HANDLER, (void*) HI_IRQ_HANDLER_ARG, &isrHandle);
//err = ESP_ERR_NOT_FINISHED;
#else
// Broken IDF API does not allow us to reserve the interrupt; do it manually
static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt;
intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, ESP32_LV5_IRQ_INDEX);
ESP_INTR_ENABLE(ESP32_LV5_IRQ_INDEX);
#endif
if (err != ESP_OK) {
heap_caps_free(driverState);
driverState = nullptr;
return err;
}
}
if (driverState[channel] != nullptr) {
return ESP_ERR_INVALID_STATE; // already in use
}
NeoEsp32RmtHIChannelState* state = reinterpret_cast<NeoEsp32RmtHIChannelState*>(heap_caps_calloc(1, sizeof(NeoEsp32RmtHIChannelState), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
if (state == nullptr) {
return ESP_ERR_NO_MEM;
}
// Store timing information
state->rmtBit0 = rmtBit0;
state->rmtBit1 = rmtBit1;
state->resetDuration = reset;
// Initialize hardware
rmt_ll_tx_stop(&RMT, channel);
rmt_ll_tx_reset_pointer(&RMT, channel);
rmt_ll_enable_tx_err_interrupt(&RMT, channel, false);
rmt_ll_enable_tx_end_interrupt(&RMT, channel, false);
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
rmt_ll_clear_tx_err_interrupt(&RMT, channel);
rmt_ll_clear_tx_end_interrupt(&RMT, channel);
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
rmt_ll_tx_enable_loop(&RMT, channel, false);
rmt_ll_tx_enable_pingpong(&RMT, channel, true);
rmt_ll_tx_set_limit(&RMT, channel, rmtBatchSize);
driverState[channel] = state;
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true);
return err;
}
esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) {
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
NeoEsp32RmtHIChannelState* state = driverState[channel];
WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
// Done or not, we're out of here
rmt_ll_tx_stop(&RMT, channel);
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
driverState[channel] = nullptr;
heap_caps_free(state);
#if !defined(CONFIG_BTDM_CTRL_HLI) /* Cannot unbind from bluetooth ISR */
// Turn off the driver ISR and release global state if none are left
for (uint8_t channelIndex = 0; channelIndex < RMT_CHANNEL_MAX; ++channelIndex) {
if (driverState[channelIndex]) return ESP_OK; // done
}
#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
esp_intr_free(isrHandle);
#else
ESP_INTR_DISABLE(ESP32_LV5_IRQ_INDEX);
#endif
heap_caps_free(driverState);
driverState = nullptr;
#endif /* !defined(CONFIG_BTDM_CTRL_HLI) */
return ESP_OK;
}
esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t *src, size_t src_size) {
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
NeoEsp32RmtHIChannelState& state = *driverState[channel];
esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
if (result == ESP_OK) {
state.txDataStart = src;
state.txDataCurrent = src;
state.txDataEnd = src + src_size;
RmtStartWrite(channel, state);
}
return result;
}
esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickType_t wait_time) {
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
NeoEsp32RmtHIChannelState& state = *driverState[channel];
// yield-wait until wait_time
esp_err_t rv = ESP_OK;
uint32_t status;
while(1) {
status = rmt_ll_tx_get_channel_status(&RMT, channel);
if (!_RmtStatusIsTransmitting(channel, status)) break;
if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; };
TickType_t sleep = std::min(wait_time, (TickType_t) 5);
vTaskDelay(sleep);
wait_time -= sleep;
};
return rv;
}
#endif

9
package-lock.json generated
View File

@@ -1,18 +1,21 @@
{
"name": "wled",
"version": "0.15.0-b7",
"version": "0.15.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wled",
"version": "0.15.0-b7",
"version": "0.15.1",
"license": "ISC",
"dependencies": {
"clean-css": "^5.3.3",
"html-minifier-terser": "^7.2.0",
"inliner": "^1.13.1",
"nodemon": "^3.0.2"
"nodemon": "^3.1.7"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {

View File

@@ -1,6 +1,6 @@
{
"name": "wled",
"version": "0.15.0-b7",
"version": "0.15.2-beta1",
"description": "Tools for WLED project",
"main": "tools/cdata.js",
"directories": {

View File

@@ -1,3 +1,21 @@
Import('env')
Import("env")
import shutil
env.Execute("npm run build")
node_ex = shutil.which("node")
# Check if Node.js is installed and present in PATH if it failed, abort the build
if node_ex is None:
print('\x1b[0;31;43m' + 'Node.js is not installed or missing from PATH html css js will not be processed check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
exitCode = env.Execute("null")
exit(exitCode)
else:
# Install the necessary node packages for the pre-build asset bundling script
print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m')
env.Execute("npm install")
# Call the bundling script
exitCode = env.Execute("npm run build")
# If it failed, abort the build
if (exitCode):
print('\x1b[0;31;43m' + 'npm run build fails check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
exit(exitCode)

View File

@@ -2,6 +2,7 @@ Import('env')
import os
import shutil
import gzip
import json
OUTPUT_DIR = "build_output{}".format(os.path.sep)
#OUTPUT_DIR = os.path.join("build_output")
@@ -22,7 +23,8 @@ def create_release(source):
release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
if release_name_def:
release_name = release_name_def.replace("\\\"", "")
version = _get_cpp_define_value(env, "WLED_VERSION")
with open("package.json", "r") as package:
version = json.load(package)["version"]
release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin")
release_gz_file = release_file + ".gz"
print(f"Copying {source} to {release_file}")

116
pio-scripts/set_metadata.py Normal file
View File

@@ -0,0 +1,116 @@
Import('env')
import subprocess
import json
import re
def get_github_repo():
"""Extract GitHub repository name from git remote URL.
Uses the remote that the current branch tracks, falling back to 'origin'.
This handles cases where repositories have multiple remotes or where the
main remote is not named 'origin'.
Returns:
str: Repository name in 'owner/repo' format for GitHub repos,
'unknown' for non-GitHub repos, missing git CLI, or any errors.
"""
try:
remote_name = 'origin' # Default fallback
# Try to get the remote for the current branch
try:
# Get current branch name
branch_result = subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
capture_output=True, text=True, check=True)
current_branch = branch_result.stdout.strip()
# Get the remote for the current branch
remote_result = subprocess.run(['git', 'config', f'branch.{current_branch}.remote'],
capture_output=True, text=True, check=True)
tracked_remote = remote_result.stdout.strip()
# Use the tracked remote if we found one
if tracked_remote:
remote_name = tracked_remote
except subprocess.CalledProcessError:
# If branch config lookup fails, continue with 'origin' as fallback
pass
# Get the remote URL for the determined remote
result = subprocess.run(['git', 'remote', 'get-url', remote_name],
capture_output=True, text=True, check=True)
remote_url = result.stdout.strip()
# Check if it's a GitHub URL
if 'github.com' not in remote_url.lower():
return None
# Parse GitHub URL patterns:
# https://github.com/owner/repo.git
# git@github.com:owner/repo.git
# https://github.com/owner/repo
# Remove .git suffix if present
if remote_url.endswith('.git'):
remote_url = remote_url[:-4]
# Handle HTTPS URLs
https_match = re.search(r'github\.com/([^/]+/[^/]+)', remote_url, re.IGNORECASE)
if https_match:
return https_match.group(1)
# Handle SSH URLs
ssh_match = re.search(r'github\.com:([^/]+/[^/]+)', remote_url, re.IGNORECASE)
if ssh_match:
return ssh_match.group(1)
return None
except FileNotFoundError:
# Git CLI is not installed or not in PATH
return None
except subprocess.CalledProcessError:
# Git command failed (e.g., not a git repo, no remote, etc.)
return None
except Exception:
# Any other unexpected error
return None
# WLED version is managed by package.json; this is picked up in several places
# - It's integrated in to the UI code
# - Here, for wled_metadata.cpp
# - The output_bins script
# We always take it from package.json to ensure consistency
with open("package.json", "r") as package:
WLED_VERSION = json.load(package)["version"]
def has_def(cppdefs, name):
""" Returns true if a given name is set in a CPPDEFINES collection """
for f in cppdefs:
if isinstance(f, tuple):
f = f[0]
if f == name:
return True
return False
def add_wled_metadata_flags(env, node):
cdefs = env["CPPDEFINES"].copy()
if not has_def(cdefs, "WLED_REPO"):
repo = get_github_repo()
if repo:
cdefs.append(("WLED_REPO", f"\\\"{repo}\\\""))
cdefs.append(("WLED_VERSION", WLED_VERSION))
# This transforms the node in to a Builder; it cannot be modified again
return env.Object(
node,
CPPDEFINES=cdefs
)
env.AddBuildMiddleware(
add_wled_metadata_flags,
"*/wled_metadata.cpp"
)

View File

@@ -1,8 +0,0 @@
Import('env')
import json
PACKAGE_FILE = "package.json"
with open(PACKAGE_FILE, "r") as package:
version = json.load(package)["version"]
env.Append(BUILD_FLAGS=[f"-DWLED_VERSION={version}"])

View File

@@ -10,7 +10,7 @@
# ------------------------------------------------------------------------------
# CI/release binaries
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover
src_dir = ./wled00
data_dir = ./wled00/data
@@ -110,7 +110,7 @@ ldscript_4m1m = eagle.flash.4m1m.ld
[scripts_defaults]
extra_scripts =
pre:pio-scripts/set_version.py
pre:pio-scripts/set_metadata.py
post:pio-scripts/output_bins.py
post:pio-scripts/strip-floats.py
pre:pio-scripts/user_config_copy.py
@@ -138,9 +138,8 @@ lib_compat_mode = strict
lib_deps =
fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.8.0
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1
makuna/NeoPixelBus @ 2.8.3
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
# for I2C interface
;Wire
# ESP-NOW library
@@ -176,6 +175,7 @@ lib_deps =
extra_scripts = ${scripts_defaults.extra_scripts}
[esp8266]
build_unflags = ${common.build_unflags}
build_flags =
-DESP8266
-DFP_IN_IROM
@@ -242,10 +242,12 @@ lib_deps_compat =
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
platform = espressif32@3.5.0
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
build_unflags = ${common.build_unflags}
build_flags = -g
-DARDUINO_ARCH_ESP32
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
-D LOROL_LITTLEFS
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
@@ -257,12 +259,13 @@ large_partitions = tools/WLED_ESP32_8MB.csv
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
lib_deps =
https://github.com/lorol/LITTLEFS.git
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
esp32async/AsyncTCP @ 3.4.7
${env.lib_deps}
# additional build flags for audioreactive
AR_build_flags = -D USERMOD_AUDIOREACTIVE
-D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster)
AR_lib_deps = kosme/arduinoFFT @ 2.0.1
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32_idf_V4]
;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
@@ -272,67 +275,81 @@ AR_lib_deps = kosme/arduinoFFT @ 2.0.1
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_unflags = ${common.build_unflags}
build_flags = -g
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
-DARDUINO_ARCH_ESP32 -DESP32
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
-D WLED_ENABLE_DMX_INPUT
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
esp32async/AsyncTCP @ 3.4.7
https://github.com/someweisguy/esp_dmx.git#47db25d
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32s2]
;; generic definitions for all ESP32-S2 boards
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_unflags = ${common.build_unflags}
build_flags = -g
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32S2
-DCONFIG_IDF_TARGET_ESP32S2=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0
-DCO
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_CDC_ON_BOOT
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
esp32async/AsyncTCP @ 3.4.7
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32c3]
;; generic definitions for all ESP32-C3 boards
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_unflags = ${common.build_unflags}
build_flags = -g
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32C3
-DCONFIG_IDF_TARGET_ESP32C3=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
-DCO
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_CDC_ON_BOOT
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
esp32async/AsyncTCP @ 3.4.7
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
[esp32s3]
;; generic definitions for all ESP32-S3 boards
platform = espressif32@ ~6.3.2
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
build_unflags = ${common.build_unflags}
build_flags = -g
-DESP32
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32S3
-DCONFIG_IDF_TARGET_ESP32S3=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0
-DCO
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
esp32async/AsyncTCP @ 3.4.7
${env.lib_deps}
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
# ------------------------------------------------------------------------------
@@ -421,6 +438,18 @@ lib_deps = ${esp32.lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
[env:esp32dev_V4]
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags}
lib_deps = ${esp32_idf_V4.lib_deps}
${esp32.AR_lib_deps}
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
[env:esp32dev_8M]
board = esp32dev
platform = ${esp32_idf_V4.platform}
@@ -619,6 +648,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
-D DATA_PINS=16
-D HW_PIN_SCL=35
-D HW_PIN_SDA=33

View File

@@ -5,7 +5,7 @@
# Please visit documentation: https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = WLED_tasmota_1M # define as many as you need
default_envs = WLED_generic8266_1M, esp32dev_V4_dio80 # put the name(s) of your own build environment here. You can define as many as you need
#----------
# SAMPLE
@@ -28,8 +28,8 @@ lib_deps = ${esp8266.lib_deps}
; robtillaart/SHT85@~0.3.3
; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
; ${esp32.AR_lib_deps} ;; used for USERMOD_AUDIOREACTIVE
; bitbank2/PNGdec@^1.0.1 ;; used for POV display uncomment following
; ${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags}
@@ -141,7 +141,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering)
;
; Use Audioreactive usermod and configure I2S microphone
; -D USERMOD_AUDIOREACTIVE
; ${esp32.AR_build_flags} ;; default flags required to properly configure ArduinoFFT
; ;; don't forget to add ArduinoFFT to your libs_deps: ${esp32.AR_lib_deps}
; -D AUDIOPIN=-1
; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM
; -D I2S_SDPIN=36
@@ -157,17 +158,22 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; -D USERMOD_POV_DISPLAY
; Use built-in or custom LED as a status indicator (assumes LED is connected to GPIO16)
; -D STATUSLED=16
;
;
; set the name of the module - make sure there is a quote-backslash-quote before the name and a backslash-quote-quote after the name
; -D SERVERNAME="\"WLED\""
;
;
; set the number of LEDs
; -D DEFAULT_LED_COUNT=30
; -D PIXEL_COUNTS=30
; or this for multiple outputs
; -D PIXEL_COUNTS=30,30
;
; set the default LED type
; -D DEFAULT_LED_TYPE=22 # see const.h (TYPE_xxxx)
; -D LED_TYPES=22 # see const.h (TYPE_xxxx)
; or this for multiple outputs
; -D LED_TYPES=TYPE_SK6812_RGBW,TYPE_WS2812_RGB
;
; set default color order of your led strip
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB
;
; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs
; -D ABL_MILLIAMPS_DEFAULT=850
@@ -176,9 +182,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
; enable IR by setting remote type
; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
;
; set default color order of your led strip
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB
;
; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues)
; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1
;
@@ -236,14 +239,13 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=1 -D WLE
lib_deps = ${esp8266.lib_deps}
[env:esp32dev_qio80]
extends = env:esp32dev # we want to extend the existing esp32dev environment (and define only updated options)
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags} ;; optional - includes USERMOD_AUDIOREACTIVE
lib_deps = ${esp32.lib_deps}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
@@ -251,26 +253,25 @@ board_build.flash_mode = qio
;; experimental ESP32 env using ESP-IDF V4.4.x
;; Warning: this build environment is not stable!!
;; please erase your device before installing.
extends = esp32_idf_V4 # based on newer "esp-idf V4" platform environment
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags} ;; includes USERMOD_AUDIOREACTIVE
lib_deps = ${esp32_idf_V4.lib_deps}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32_idf_V4.default_partitions}
board_build.partitions = ${esp32.default_partitions} ;; if you get errors about "out of program space", change this to ${esp32.extended_partitions} or even ${esp32.big_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = dio
[env:esp32s2_saola]
extends = esp32s2
board = esp32-s2-saola-1
platform = ${esp32s2.platform}
platform_packages = ${esp32s2.platform_packages}
framework = arduino
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
board_build.flash_mode = qio
upload_speed = 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32s2.build_flags}
;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work
-DARDUINO_USB_CDC_ON_BOOT=1
@@ -307,7 +308,7 @@ platform = ${common.platform_wled_default}
platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_USE_SHOJO_PCB
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.
lib_deps = ${esp8266.lib_deps}
[env:d1_mini_debug]
@@ -362,35 +363,48 @@ board_upload.flash_size = 2MB
board_upload.maximum_size = 2097152
[env:wemos_shield_esp32]
extends = esp32 ;; use default esp32 platform
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 460800
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags}
-D WLED_RELEASE_NAME=\"ESP32_wemos_shield\"
-D DATA_PINS=16
-D RLYPIN=19
-D BTNPIN=17
-D IRPIN=18
-D UWLED_USE_MY_CONFIG
-UWLED_USE_MY_CONFIG
-D USERMOD_DALLASTEMPERATURE
-D USERMOD_FOUR_LINE_DISPLAY
-D TEMPERATURE_PIN=23
-D USERMOD_AUDIOREACTIVE
${esp32.AR_build_flags} ;; includes USERMOD_AUDIOREACTIVE
lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5
olikraus/U8g2 @ ^2.28.8
https://github.com/blazoncek/arduinoFFT.git
OneWire@~2.3.5 ;; needed for USERMOD_DALLASTEMPERATURE
olikraus/U8g2 @ ^2.28.8 ;; needed for USERMOD_FOUR_LINE_DISPLAY
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
board_build.partitions = ${esp32.default_partitions}
[env:m5atom]
board = esp32dev
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39
[env:esp32_pico-D4]
extends = esp32 ;; use default esp32 platform
board = pico32 ;; pico32-D4 is different from the standard esp32dev
;; hardware details from https://github.com/srg74/WLED-ESP32-pico
build_flags = ${common.build_flags} ${esp32.build_flags}
-D WLED_RELEASE_NAME=\"pico32-D4\" -D SERVERNAME='"WLED-pico32"'
-D WLED_DISABLE_ADALIGHT ;; no serial-to-USB chip on this board - better to disable serial protocols
-D DATA_PINS=2,18 ;; LED pins
-D RLYPIN=19 -D BTNPIN=0 -D IRPIN=-1 ;; no default pin for IR
${esp32.AR_build_flags} ;; include USERMOD_AUDIOREACTIVE
-D UM_AUDIOREACTIVE_ENABLE ;; enable AR by default
;; Audioreactive settings for on-board microphone (ICS-43432)
-D SR_DMTYPE=1 -D I2S_SDPIN=25 -D I2S_WSPIN=15 -D I2S_CKPIN=14
-D SR_SQUELCH=5 -D SR_GAIN=30
lib_deps = ${esp32.lib_deps}
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
${esp32.AR_lib_deps} ;; needed for USERMOD_AUDIOREACTIVE
board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
[env:m5atom]
extends = env:esp32dev # we want to extend the existing esp32dev environment (and define only updated options)
build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39
[env:sp501e]
board = esp_wroom_02
@@ -413,7 +427,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0
-D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0
lib_deps = ${esp8266.lib_deps}
[env:Athom_15w_RGBCW] ;15w bulb
@@ -423,7 +437,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT
-D LED_TYPES=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT
lib_deps = ${esp8266.lib_deps}
[env:Athom_3Pin_Controller] ;small controller with only data
@@ -489,9 +503,8 @@ lib_deps = ${esp8266.lib_deps}
# EleksTube-IPS
# ------------------------------------------------------------------------------
[env:elekstube_ips]
extends = esp32 ;; use default esp32 platform
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 921600
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
-D USERMOD_RTC
@@ -499,7 +512,7 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU
-D DATA_PINS=12
-D RLYPIN=27
-D BTNPIN=34
-D DEFAULT_LED_COUNT=6
-D PIXEL_COUNTS=6
# Display config
-D ST7789_DRIVER
-D TFT_WIDTH=135
@@ -515,5 +528,4 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOU
monitor_filters = esp32_exception_decoder
lib_deps =
${esp32.lib_deps}
TFT_eSPI @ ^2.3.70
board_build.partitions = ${esp32.default_partitions}
TFT_eSPI @ 2.5.33 ;; this is the last version that compiles with the WLED default framework - newer versions require platform = espressif32 @ ^6.3.2

BIN
tools/AutoCubeMap.xlsx Normal file

Binary file not shown.

View File

@@ -370,12 +370,6 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
name: "PAGE_update",
method: "gzip",
filter: "html-minify",
mangle: (str) =>
str
.replace(
/function GetV().*\<\/script\>/gms,
"</script><script src=\"/settings/s.js?p=9\"></script>"
)
},
{
file: "welcome.htm",

View File

@@ -444,6 +444,7 @@ public:
configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false);
configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true);
configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false);
tempScale = UseCelsius ? "°C" : "°F";
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {

View File

@@ -75,7 +75,7 @@ static uint8_t soundAgc = 0; // Automagic gain control: 0 - n
//static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency
static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getFrameTime()
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData
static unsigned long timeOfPeak = 0; // time of last sample peak detection.
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects
@@ -536,8 +536,8 @@ static void detectSamplePeak(void) {
#endif
static void autoResetPeak(void) {
uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC
if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed.
uint16_t peakDelay = max(uint16_t(50), strip.getFrameTime());
if (millis() - timeOfPeak > peakDelay) { // Auto-reset of samplePeak after at least one complete frame has passed.
samplePeak = false;
if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData
}

View File

@@ -9,7 +9,7 @@ The actual / original code that controls the LED modes is from Adam Zeloof. I ta
It was quite a bit more work than I hoped, but I got there eventually :)
## Requirements
* "ESP Rotary" by Lennart Hennigs, v1.5.0 or higher: https://github.com/LennartHennigs/ESPRotary
* "ESP Rotary" by Lennart Hennigs, v2.1.1 or higher: https://github.com/LennartHennigs/ESPRotary
## Usermod installation
Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D RGB_ROTARY_ENCODER`.
@@ -20,7 +20,7 @@ ESP32:
extends = env:esp32dev
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D RGB_ROTARY_ENCODER
lib_deps = ${esp32.lib_deps}
lennarthennigs/ESP Rotary@^1.5.0
lennarthennigs/ESP Rotary@^2.1.1
```
ESP8266 / D1 Mini:
@@ -29,7 +29,7 @@ ESP8266 / D1 Mini:
extends = env:d1_mini
build_flags = ${common.build_flags_esp8266} -D RGB_ROTARY_ENCODER
lib_deps = ${esp8266.lib_deps}
lennarthennigs/ESP Rotary@^1.5.0
lennarthennigs/ESP Rotary@^2.1.1
```
## How to connect the board to your ESP

View File

@@ -95,9 +95,9 @@ public:
}
else
{
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
fastled_col.red = colPri[0];
fastled_col.green = colPri[1];
fastled_col.blue = colPri[2];
prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h + fadeAmount;
if (new_val > 255)
@@ -106,9 +106,9 @@ public:
new_val += 255; // roll-over if smaller than 0
prim_hsv.h = (byte)new_val;
hsv2rgb_rainbow(prim_hsv, fastled_col);
col[0] = fastled_col.red;
col[1] = fastled_col.green;
col[2] = fastled_col.blue;
colPri[0] = fastled_col.red;
colPri[1] = fastled_col.green;
colPri[2] = fastled_col.blue;
}
}
else if (Enc_B == LOW)
@@ -120,9 +120,9 @@ public:
}
else
{
fastled_col.red = col[0];
fastled_col.green = col[1];
fastled_col.blue = col[2];
fastled_col.red = colPri[0];
fastled_col.green = colPri[1];
fastled_col.blue = colPri[2];
prim_hsv = rgb2hsv_approximate(fastled_col);
new_val = (int16_t)prim_hsv.h - fadeAmount;
if (new_val > 255)
@@ -131,9 +131,9 @@ public:
new_val += 255; // roll-over if smaller than 0
prim_hsv.h = (byte)new_val;
hsv2rgb_rainbow(prim_hsv, fastled_col);
col[0] = fastled_col.red;
col[1] = fastled_col.green;
col[2] = fastled_col.blue;
colPri[0] = fastled_col.red;
colPri[1] = fastled_col.green;
colPri[2] = fastled_col.blue;
}
}
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)

View File

@@ -518,7 +518,7 @@ void RotaryEncoderUIUsermod::setup()
loopTime = millis();
currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5;
currentCCT = (approximateKelvinFromRGB(RGBW32(colPri[0], colPri[1], colPri[2], colPri[3])) - 1900) >> 5;
if (!initDone) sortModesAndPalettes();
@@ -920,17 +920,17 @@ void RotaryEncoderUIUsermod::changeHue(bool increase){
display->updateRedrawTime();
#endif
currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0);
colorHStoRGB(currentHue1*256, currentSat1, col);
colorHStoRGB(currentHue1*256, currentSat1, colPri);
stateChanged = true;
if (applyToAll) {
for (unsigned i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
@@ -950,16 +950,16 @@ void RotaryEncoderUIUsermod::changeSat(bool increase){
display->updateRedrawTime();
#endif
currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0);
colorHStoRGB(currentHue1*256, currentSat1, col);
colorHStoRGB(currentHue1*256, currentSat1, colPri);
if (applyToAll) {
for (unsigned i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
seg.colors[0] = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY

View File

@@ -183,8 +183,7 @@ uint16_t color_wipe(bool rev, bool useRandomColors) {
}
unsigned ledIndex = (prog * SEGLEN) >> 15;
unsigned rem = 0;
rem = (prog * SEGLEN) * 2; //mod 0xFFFF
uint16_t rem = (prog * SEGLEN) * 2; //mod 0xFFFF by truncating
rem /= (SEGMENT.intensity +1);
if (rem > 255) rem = 255;
@@ -600,11 +599,12 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0";
* Dissolve function
*/
uint16_t dissolve(uint32_t color) {
unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED
unsigned dataSize = sizeof(uint32_t) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
if (SEGENV.call == 0) {
memset(SEGMENT.data, 0xFF, dataSize); // start by fading pixels up
for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = SEGCOLOR(1);
SEGENV.aux0 = 1;
}
@@ -612,33 +612,26 @@ uint16_t dissolve(uint32_t color) {
if (random8() <= SEGMENT.intensity) {
for (size_t times = 0; times < 10; times++) { //attempt to spawn a new pixel 10 times
unsigned i = random16(SEGLEN);
unsigned index = i >> 3;
unsigned bitNum = i & 0x07;
bool fadeUp = bitRead(SEGENV.data[index], bitNum);
if (SEGENV.aux0) { //dissolve to primary/palette
if (fadeUp) {
if (color == SEGCOLOR(0)) {
SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
} else {
SEGMENT.setPixelColor(i, color);
}
bitWrite(SEGENV.data[index], bitNum, false);
if (pixels[i] == SEGCOLOR(1)) {
pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color;
break; //only spawn 1 new pixel per frame per 50 LEDs
}
} else { //dissolve to secondary
if (!fadeUp) {
SEGMENT.setPixelColor(i, SEGCOLOR(1)); break;
bitWrite(SEGENV.data[index], bitNum, true);
if (pixels[i] != SEGCOLOR(1)) {
pixels[i] = SEGCOLOR(1);
break;
}
}
}
}
}
// fix for #4401
for (unsigned i = 0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, pixels[i]);
if (SEGENV.step > (255 - SEGMENT.speed) + 15U) {
SEGENV.aux0 = !SEGENV.aux0;
SEGENV.step = 0;
memset(SEGMENT.data, (SEGENV.aux0 ? 0xFF : 0), dataSize); // switch fading
} else {
SEGENV.step++;
}
@@ -1097,7 +1090,7 @@ uint16_t mode_running_random(void) {
unsigned z = it % zoneSize;
bool nzone = (!z && it != SEGENV.aux1);
for (unsigned i=SEGLEN-1; i > 0; i--) {
for (int i=SEGLEN-1; i >= 0; i--) {
if (nzone || z >= zoneSize) {
unsigned lastrand = PRNG16 >> 8;
int16_t diff = 0;
@@ -1441,7 +1434,7 @@ uint16_t mode_fairy() {
if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1));
for (unsigned f = firstFlasher; f < firstFlasher + flashersInZone; f++) {
unsigned stateTime = now16 - flashers[f].stateStart;
unsigned stateTime = uint16_t(now16 - flashers[f].stateStart);
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 10) {
flashers[f].stateOn = !flashers[f].stateOn;
@@ -1500,7 +1493,7 @@ uint16_t mode_fairytwinkle() {
unsigned maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1);
for (int f = 0; f < SEGLEN; f++) {
unsigned stateTime = now16 - flashers[f].stateStart;
uint16_t stateTime = now16 - flashers[f].stateStart;
//random on/off time reached, switch state
if (stateTime > flashers[f].stateDur * 100) {
flashers[f].stateOn = !flashers[f].stateOn;
@@ -1653,8 +1646,8 @@ static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!;1,2,3;!";
* Modified by Aircoookie
*/
uint16_t mode_tricolor_fade(void) {
unsigned counter = strip.now * ((SEGMENT.speed >> 3) +1);
uint16_t prog = (counter * 768) >> 16;
uint16_t counter = strip.now * ((SEGMENT.speed >> 3) +1);
uint32_t prog = (counter * 768) >> 16;
uint32_t color1 = 0, color2 = 0;
unsigned stage = 0;
@@ -1745,7 +1738,7 @@ uint16_t mode_random_chase(void) {
uint32_t color = SEGENV.step;
random16_set_seed(SEGENV.aux0);
for (unsigned i = SEGLEN -1; i > 0; i--) {
for (int i = SEGLEN -1; i >= 0; i--) {
uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8();
uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8();
uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8();
@@ -1798,7 +1791,7 @@ uint16_t mode_oscillate(void) {
// if the counter has increased, move the oscillator by the random step
if (it != SEGENV.step) oscillators[i].pos += oscillators[i].dir * oscillators[i].speed;
oscillators[i].size = SEGLEN/(3+SEGMENT.intensity/8);
if((oscillators[i].dir == -1) && (oscillators[i].pos <= 0)) {
if((oscillators[i].dir == -1) && (oscillators[i].pos > SEGLEN << 1)) { // use integer overflow
oscillators[i].pos = 0;
oscillators[i].dir = 1;
// make bigger steps for faster speeds
@@ -1814,8 +1807,8 @@ uint16_t mode_oscillate(void) {
for (unsigned i = 0; i < SEGLEN; i++) {
uint32_t color = BLACK;
for (unsigned j = 0; j < numOscillators; j++) {
if(i >= (unsigned)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) {
color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), 128);
if((int)i >= (int)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) {
color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), uint8_t(128));
}
}
SEGMENT.setPixelColor(i, color);
@@ -2003,7 +1996,7 @@ uint16_t mode_palette() {
const mathType sourceX = xtSinTheta + ytCosTheta + centerX;
// The computation was scaled just right so that the result should always be in range [0, maxXOut], but enforce this anyway
// to account for imprecision. Then scale it so that the range is [0, 255], which we can use with the palette.
int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * 255) / (sInt16Scale * maxXOut);
int colorIndex = (std::min(std::max(sourceX, mathType(0)), maxXOut * sInt16Scale) * wideMathType(255)) / (sInt16Scale * maxXOut);
// inputSize determines by how much we want to scale the palette:
// values < 128 display a fraction of a palette,
// values > 128 display multiple palettes.
@@ -2565,11 +2558,11 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
{
// Overall twinkle speed (changed)
unsigned ticks = ms / SEGENV.aux0;
unsigned fastcycle8 = ticks;
unsigned slowcycle16 = (ticks >> 8) + salt;
unsigned fastcycle8 = uint8_t(ticks);
uint16_t slowcycle16 = (ticks >> 8) + salt;
slowcycle16 += sin8_t(slowcycle16);
slowcycle16 = (slowcycle16 * 2053) + 1384;
unsigned slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
// Overall twinkle density.
// 0 (NONE lit) to 8 (ALL lit at once).
@@ -3920,7 +3913,7 @@ uint16_t mode_percent(void) {
return FRAMETIME;
}
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One color;!,!;!";
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@!,% of fill,,,,One color;!,!;!";
/*
@@ -5170,7 +5163,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
neighbors++;
bool colorFound = false;
int k;
for (k=0; k<9 && colorsCount[i].count != 0; k++)
for (k=0; k<9 && colorsCount[k].count != 0; k++)
if (colorsCount[k].color == prevLeds[xy]) {
colorsCount[k].count++;
colorFound = true;
@@ -6639,14 +6632,16 @@ static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!;
// * MATRIPIX //
//////////////////////
uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline.
if (SEGLEN == 1) return mode_static();
// even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment
// effect can work on single pixels, we just lose the shifting effect
unsigned dataSize = sizeof(uint32_t) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
um_data_t *um_data = getAudioData();
int volumeRaw = *(int16_t*)um_data->u_data[1];
if (SEGENV.call == 0) {
SEGMENT.fill(BLACK);
for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer
}
uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16;
@@ -6654,8 +6649,14 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline.
SEGENV.aux0 = secondHand;
int pixBri = volumeRaw * SEGMENT.intensity / 64;
for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left
SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri));
unsigned k = SEGLEN-1;
// loop will not execute if SEGLEN equals 1
for (unsigned i = 0; i < k; i++) {
pixels[i] = pixels[i+1]; // shift left
SEGMENT.setPixelColor(i, pixels[i]);
}
pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri);
SEGMENT.setPixelColor(k, pixels[k]);
}
return FRAMETIME;
@@ -7283,8 +7284,11 @@ static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12=
// Combines peak detection with FFT_MajorPeak and FFT_Magnitude.
uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline
// effect can work on single pixels, we just lose the shifting effect
um_data_t *um_data = getAudioData();
unsigned dataSize = sizeof(uint32_t) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
um_data_t *um_data = getAudioData();
uint8_t samplePeak = *(uint8_t*)um_data->u_data[3];
float FFT_MajorPeak = *(float*) um_data->u_data[4];
uint8_t *maxVol = (uint8_t*)um_data->u_data[6];
@@ -7294,7 +7298,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin
if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception)
if (SEGENV.call == 0) {
SEGMENT.fill(BLACK);
for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer
SEGENV.aux0 = 255;
SEGMENT.custom1 = *binNum;
SEGMENT.custom2 = *maxVol * 2;
@@ -7311,13 +7315,18 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin
uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly.
if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow
unsigned k = SEGLEN-1;
if (samplePeak) {
SEGMENT.setPixelColor(SEGLEN-1, CHSV(92,92,92));
pixels[k] = (uint32_t)CRGB(CHSV(92,92,92));
} else {
SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude));
pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (uint8_t)my_magnitude);
}
SEGMENT.setPixelColor(k, pixels[k]);
// loop will not execute if SEGLEN equals 1
for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left
for (unsigned i = 0; i < k; i++) {
pixels[i] = pixels[i+1]; // shift left
SEGMENT.setPixelColor(i, pixels[i]);
}
}
return FRAMETIME;

View File

@@ -1,3 +1,4 @@
#pragma once
/*
WS2812FX.h - Library for WS2812 LED effects.
Harm Aldick - 2016
@@ -8,12 +9,15 @@
Adapted from code originally licensed under the MIT license
Modified for WLED
Segment class/struct (c) 2022 Blaz Kristan (@blazoncek)
*/
#ifndef WS2812FX_h
#define WS2812FX_h
#include <vector>
#include "wled.h"
#include "const.h"
@@ -46,6 +50,14 @@
#define WLED_FPS 42
#define FRAMETIME_FIXED (1000/WLED_FPS)
#define FRAMETIME strip.getFrameTime()
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive
#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define MIN_FRAME_DELAY 3 // S2/C3 are slower than normal esp32, and only have one core
#else
#define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY
#endif
#define FPS_UNLIMITED 0
// FPS calculation (can be defined as compile flag for debugging)
#ifndef FPS_CALC_AVG
@@ -59,26 +71,21 @@
/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
#ifdef ESP8266
#define MAX_NUM_SEGMENTS 16
#define MAX_NUM_SEGMENTS 16
/* How much data bytes all segments combined may allocate */
#define MAX_SEGMENT_DATA 5120
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_NUM_SEGMENTS 20
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*512) // 10k by default (S2 is short on free RAM)
#else
#ifndef MAX_NUM_SEGMENTS
#define MAX_NUM_SEGMENTS 32
#endif
#if defined(ARDUINO_ARCH_ESP32S2)
#define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default (S2 is short on free RAM)
#else
#define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1280 // 40k by default
#endif
#define MAX_NUM_SEGMENTS 32 // warning: going beyond 32 may consume too much RAM for stable operation
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default
#endif
/* How much data bytes each segment should max allocate to leave enough space for other segments,
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments())
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT strip._segments[strip.getCurrSegmentId()]
#define SEGENV strip._segments[strip.getCurrSegmentId()]
@@ -524,6 +531,9 @@ typedef struct Segment {
inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels
inline uint16_t groupLength() const { return grouping + spacing; }
inline uint8_t getLightCapabilities() const { return _capabilities; }
inline void deactivate() { setGeometry(0,0); }
inline Segment &clearName() { if (name) free(name); name = nullptr; return *this; }
inline Segment &setName(const String &name) { return setName(name.c_str()); }
inline static uint16_t getUsedSegmentData() { return _usedSegmentData; }
inline static void addUsedSegmentData(int len) { _usedSegmentData += len; }
@@ -533,14 +543,15 @@ typedef struct Segment {
static void handleRandomPalette();
inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; }
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1);
void setGeometry(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, uint8_t m12 = 0);
Segment &setColor(uint8_t slot, uint32_t c);
Segment &setCCT(uint16_t k);
Segment &setOpacity(uint8_t o);
Segment &setOption(uint8_t n, bool val);
Segment &setMode(uint8_t fx, bool loadDefaults = false);
Segment &setPalette(uint8_t pal);
uint8_t differs(Segment& b) const;
Segment &setName(const char* name);
uint8_t differs(const Segment& b) const;
void refreshLightCapabilities();
// runtime data functions
@@ -748,6 +759,7 @@ class WS2812FX { // 96 bytes
customMappingTable(nullptr),
customMappingSize(0),
_lastShow(0),
_lastServiceShow(0),
_segment_index(0),
_mainSegment(0)
{
@@ -846,7 +858,7 @@ class WS2812FX { // 96 bytes
getMappedPixelIndex(uint16_t index) const;
inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms)
inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getMinShowDelay() const { return MIN_FRAME_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant)
inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H)
inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms)
@@ -958,6 +970,7 @@ class WS2812FX { // 96 bytes
uint16_t customMappingSize;
unsigned long _lastShow;
unsigned long _lastServiceShow;
uint8_t _segment_index;
uint8_t _mainSegment;

View File

@@ -438,37 +438,44 @@ void Segment::setCurrentPalette() {
// relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() {
uint16_t time_ms = millis();
uint16_t time_s = millis()/1000U;
// is it time to generate a new palette?
if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = (uint16_t)(millis() / 1000U);
_lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately
if ((uint16_t)(time_s - _lastPaletteChange) > randomPaletteChangeTime) {
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = time_s;
_lastPaletteBlend = time_ms - 512; // starts blending immediately
}
// if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
if (strip.paletteFade) {
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
if ((uint16_t)(time_ms - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = (uint16_t)millis();
}
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
}
// segId is given when called from network callback, changes are queued if that segment is currently in its effect function
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) {
void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t m12) {
// return if neither bounds nor grouping have changed
bool boundsUnchanged = (start == i1 && stop == i2);
#ifndef WLED_DISABLE_2D
if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D
#endif
m12 = constrain(m12, 0, 7);
if (stop && (spc > 0 || m12 != map1D2D)) fill(BLACK);
if (m12 != map1D2D) map1D2D = m12;
/*
if (boundsUnchanged
&& (!grp || (grouping == grp && spacing == spc))
&& (ofs == UINT16_MAX || ofs == offset)) return;
&& (m12 == map1D2D)
) return;
*/
stateChanged = true; // send UDP/WS broadcast
if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing)
if (grp) { // prevent assignment of 0
grouping = grp;
spacing = spc;
@@ -478,12 +485,9 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
}
if (ofs < UINT16_MAX) offset = ofs;
DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1);
DEBUG_PRINT(','); DEBUG_PRINT(i2);
DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y);
DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y);
markForReset();
DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y);
if (boundsUnchanged) return;
markForReset();
// apply change immediately
if (i2 <= i1) { //disable segment
@@ -601,6 +605,20 @@ Segment &Segment::setPalette(uint8_t pal) {
return *this;
}
Segment &Segment::setName(const char *newName) {
if (newName) {
const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN);
if (newLen) {
if (name) name = static_cast<char*>(realloc(name, newLen+1));
else name = static_cast<char*>(malloc(newLen+1));
if (name) strlcpy(name, newName, newLen+1);
name[newLen] = 0;
return *this;
}
}
return clearName();
}
// 2D matrix
unsigned IRAM_ATTR Segment::virtualWidth() const {
unsigned groupLen = groupLength();
@@ -951,7 +969,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
return strip.getPixelColor(i);
}
uint8_t Segment::differs(Segment& b) const {
uint8_t Segment::differs(const Segment& b) const {
uint8_t d = 0;
if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
@@ -1047,36 +1065,25 @@ void Segment::fade_out(uint8_t rate) {
const int cols = is2D() ? virtualWidth() : virtualLength();
const int rows = virtualHeight(); // will be 1 for 1D
rate = (255-rate) >> 1;
float mappedRate = 1.0f / (float(rate) + 1.1f);
uint32_t color = colors[1]; // SEGCOLOR(1); // target color
int w2 = W(color);
int r2 = R(color);
int g2 = G(color);
int b2 = B(color);
rate = (256-rate) >> 1;
const int mappedRate = 256 / (rate + 1);
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x);
uint32_t color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x);
if (color == colors[1]) continue; // already at target color
int w1 = W(color);
int r1 = R(color);
int g1 = G(color);
int b1 = B(color);
int wdelta = (w2 - w1) * mappedRate;
int rdelta = (r2 - r1) * mappedRate;
int gdelta = (g2 - g1) * mappedRate;
int bdelta = (b2 - b1) * mappedRate;
// if fade isn't complete, make sure delta is at least 1 (fixes rounding issues)
wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1;
rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1;
gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1;
bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1;
if (is2D()) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta);
else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta);
for (int i = 0; i < 32; i += 8) {
uint8_t c2 = (colors[1]>>i); // get background channel
uint8_t c1 = (color>>i); // get foreground channel
// we can't use bitshift since we are using int
int delta = (c2 - c1) * mappedRate / 256;
// if fade isn't complete, make sure delta is at least 1 (fixes rounding issues)
if (delta == 0) delta += (c2 == c1) ? 0 : (c2 > c1) ? 1 : -1;
// stuff new value back into color
color &= ~(0xFF<<i);
color |= ((c1 + delta) & 0xFF) << i;
}
if (is2D()) setPixelColorXY(x, y, color);
else setPixelColor(x, color);
}
}
@@ -1192,6 +1199,40 @@ void WS2812FX::finalizeInit() {
_hasWhiteChannel = _isOffRefreshRequired = false;
unsigned digitalCount = 0;
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
unsigned maxLedsOnBus = 0;
unsigned busType = 0;
for (const auto &bus : busConfigs) {
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
digitalCount++;
if (busType == 0) busType = bus.type; // remember first bus type
if (busType != bus.type) {
DEBUG_PRINTF_P(PSTR("Mixed digital bus types detected! Forcing single I2S output.\n"));
useParallelI2S = false; // mixed bus types, no parallel I2S
}
if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
}
}
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount);
// we may remove 600 LEDs per bus limit when NeoPixelBus is updated beyond 2.8.3
if (digitalCount > 1 && maxLedsOnBus <= 600 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses
else useParallelI2S = false; // enforce single I2S
digitalCount = 0;
#endif
// create buses/outputs
unsigned mem = 0;
for (const auto &bus : busConfigs) {
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
if (mem <= MAX_LED_MEMORY) {
if (BusManager::add(bus) == -1) break;
} else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
}
busConfigs.clear();
busConfigs.shrink_to_fit();
//if busses failed to load, add default (fresh install, FS issue, ...)
if (BusManager::getNumBusses() == 0) {
DEBUG_PRINTLN(F("No busses, init default"));
@@ -1207,6 +1248,7 @@ void WS2812FX::finalizeInit() {
unsigned prevLen = 0;
unsigned pinsIndex = 0;
digitalCount = 0;
for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
uint8_t defPin[OUTPUT_MAX_PINS];
// if we have less types than requested outputs and they do not align, use last known type to set current type
@@ -1271,9 +1313,11 @@ void WS2812FX::finalizeInit() {
if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1;
prevLen += count;
BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer);
mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0);
if (BusManager::add(defCfg) == -1) break;
}
}
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage());
_length = 0;
for (int i=0; i<BusManager::getNumBusses(); i++) {
@@ -1290,6 +1334,7 @@ void WS2812FX::finalizeInit() {
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
bus->begin();
}
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
Segment::maxWidth = _length;
Segment::maxHeight = 1;
@@ -1304,14 +1349,21 @@ void WS2812FX::finalizeInit() {
void WS2812FX::service() {
unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days
now = nowUp + timebase;
if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return;
if (_suspend) return;
unsigned long elapsed = nowUp - _lastServiceShow;
if (elapsed <= MIN_FRAME_DELAY) return; // keep wifi alive - no matter if triggered or unlimited
if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime
if (elapsed < _frametime) return; // too early for service
}
bool doShow = false;
_isServicing = true;
_segment_index = 0;
for (segment &seg : _segments) {
if (_suspend) return; // immediately stop processing segments if suspend requested during service()
if (_suspend) break; // immediately stop processing segments if suspend requested during service()
// process transition (mode changes in the middle of transition)
seg.handleTransition();
@@ -1321,7 +1373,7 @@ void WS2812FX::service() {
if (!seg.isActive()) continue;
// last condition ensures all solid segments are updated at the same time
if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
if (nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
{
doShow = true;
unsigned frameDelay = FRAMETIME;
@@ -1371,15 +1423,16 @@ void WS2812FX::service() {
_triggered = false;
#ifdef WLED_DEBUG
if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
if (doShow) {
yield();
Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette
show();
_lastServiceShow = nowUp; // update timestamp, for precise FPS control
if (!_suspend) show();
}
#ifdef WLED_DEBUG
if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
}
@@ -1399,13 +1452,13 @@ void WS2812FX::show() {
// avoid race condition, capture _callback value
show_callback callback = _callback;
if (callback) callback();
unsigned long showNow = millis();
// some buses send asynchronously and this method will return before
// all of the data has been sent.
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
BusManager::show();
unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
if (diff > 0) { // skip calculation if no time has passed
@@ -1433,8 +1486,9 @@ uint16_t WS2812FX::getFps() const {
}
void WS2812FX::setTargetFps(uint8_t fps) {
if (fps > 0 && fps <= 120) _targetFps = fps;
_frametime = 1000 / _targetFps;
if (fps <= 250) _targetFps = fps;
if (_targetFps > 0) _frametime = 1000 / _targetFps;
else _frametime = MIN_FRAME_DELAY; // unlimited mode
}
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
@@ -1482,7 +1536,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
BusManager::setBrightness(b);
if (!direct) {
unsigned long t = millis();
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_FRAME_DELAY) trigger(); //apply brightness change immediately if no refresh soon
}
}
@@ -1592,7 +1646,7 @@ void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t group
segId = getSegmentsNum()-1; // segments are added at the end of list
}
suspend();
_segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY);
_segments[segId].setGeometry(i1, i2, grouping, spacing, offset, startY, stopY);
resume();
if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector
}

View File

@@ -18,10 +18,12 @@
#endif
#include "const.h"
#include "pin_manager.h"
#include "bus_wrapper.h"
#include "bus_manager.h"
#include "bus_wrapper.h"
#include <bits/unique_ptr.h>
extern bool cctICused;
extern bool useParallelI2S;
//colors.cpp
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
@@ -29,28 +31,6 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
//udp.cpp
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false);
// enable additional debug output
#if defined(WLED_DEBUG_HOST)
#include "net_debug.h"
#define DEBUGOUT NetDebug
#else
#define DEBUGOUT Serial
#endif
#ifdef WLED_DEBUG
#ifndef ESP8266
#include <rom/rtc.h>
#endif
#define DEBUG_PRINT(x) DEBUGOUT.print(x)
#define DEBUG_PRINTLN(x) DEBUGOUT.println(x)
#define DEBUG_PRINTF(x...) DEBUGOUT.printf(x)
#define DEBUG_PRINTF_P(x...) DEBUGOUT.printf_P(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_PRINTF(x...)
#define DEBUG_PRINTF_P(x...)
#endif
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
@@ -63,6 +43,7 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte
bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) {
if (count() >= WLED_MAX_COLOR_ORDER_MAPPINGS || len == 0 || (colorOrder & 0x0F) > COL_ORDER_MAX) return false; // upper nibble contains W swap information
_mappings.push_back({start,len,colorOrder});
DEBUGBUS_PRINTF_P(PSTR("Bus: Add COM (%d,%d,%d)\n"), (int)start, (int)len, (int)colorOrder);
return true;
}
@@ -116,12 +97,16 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) const {
}
uint8_t *Bus::allocateData(size_t size) {
if (_data) free(_data); // should not happen, but for safety
freeData(); // should not happen, but for safety
return _data = (uint8_t *)(size>0 ? calloc(size, sizeof(uint8_t)) : nullptr);
}
void Bus::freeData() {
if (_data) free(_data);
_data = nullptr;
}
BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
BusDigital::BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
, _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder)
@@ -129,42 +114,43 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
, _milliAmpsMax(bc.milliAmpsMax)
, _colorOrderMap(com)
{
if (!isDigital(bc.type) || !bc.count) return;
if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return;
DEBUGBUS_PRINTLN(F("Bus: Creating digital bus."));
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; }
if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; }
_frequencykHz = 0U;
_pins[0] = bc.pins[0];
if (is2Pin(bc.type)) {
if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) {
cleanup();
DEBUGBUS_PRINTLN(F("Pin 1 allocated!"));
return;
}
_pins[1] = bc.pins[1];
_frequencykHz = bc.frequency ? bc.frequency : 2000U; // 2MHz clock if undefined
}
_iType = PolyBus::getI(bc.type, _pins, nr);
if (_iType == I_NONE) return;
if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; }
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type);
if (bc.doubleBuffer && !allocateData(bc.count * Bus::getNumberOfChannels(bc.type))) return;
if (bc.doubleBuffer && !allocateData(bc.count * Bus::getNumberOfChannels(bc.type))) { DEBUGBUS_PRINTLN(F("Buffer allocation failed!")); return; }
//_buffering = bc.doubleBuffer;
uint16_t lenToCreate = bc.count;
if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
_busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr);
_valid = (_busPtr != nullptr);
DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax);
DEBUGBUS_PRINTF_P(PSTR("Bus: %successfully inited #%u (len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u] mA=%d/%d)\n"),
_valid?"S":"Uns",
(int)nr,
(int)bc.count,
(int)bc.type,
(int)_hasRgb, (int)_hasWhite, (int)_hasCCT,
(unsigned)_pins[0], is2Pin(bc.type)?(unsigned)_pins[1]:255U,
(unsigned)_iType,
(int)_milliAmpsPerLed, (int)_milliAmpsMax
);
}
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
//DISCLAIMER
//The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold.
@@ -173,7 +159,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
//I am NOT to be held liable for burned down garages or houses!
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
uint8_t BusDigital::estimateCurrentAndLimitBri() {
uint8_t BusDigital::estimateCurrentAndLimitBri() const {
bool useWackyWS2815PowerModel = false;
byte actualMilliampsPerLed = _milliAmpsPerLed;
@@ -186,7 +172,7 @@ uint8_t BusDigital::estimateCurrentAndLimitBri() {
actualMilliampsPerLed = 12; // from testing an actual strip
}
size_t powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power
unsigned powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power
if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget
powerBudget -= getLength();
} else {
@@ -211,26 +197,25 @@ uint8_t BusDigital::estimateCurrentAndLimitBri() {
}
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
busPowerSum = (busPowerSum * actualMilliampsPerLed) / 765;
_milliAmpsTotal = busPowerSum * _bri / 255;
BusDigital::_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * _bri) / (765*255);
uint8_t newBri = _bri;
if (busPowerSum * _bri / 255 > powerBudget) { //scale brightness down to stay in current limit
float scale = (float)(powerBudget * 255) / (float)(busPowerSum * _bri);
if (scale >= 1.0f) return _bri;
_milliAmpsTotal = ceilf((float)_milliAmpsTotal * scale);
uint8_t scaleB = min((int)(scale * 255), 255);
newBri = unsigned(_bri * scaleB) / 256 + 1;
if (BusDigital::_milliAmpsTotal > powerBudget) {
//scale brightness down to stay in current limit
unsigned scaleB = powerBudget * 255 / BusDigital::_milliAmpsTotal;
newBri = (_bri * scaleB) / 256 + 1;
BusDigital::_milliAmpsTotal = powerBudget;
//_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * newBri) / (765*255);
}
return newBri;
}
void BusDigital::show() {
_milliAmpsTotal = 0;
BusDigital::_milliAmpsTotal = 0;
if (!_valid) return;
uint8_t cctWW = 0, cctCW = 0;
unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal
unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere())
if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits
if (_data) {
@@ -256,6 +241,7 @@ void BusDigital::show() {
// TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer
Bus::_cct = _data[offset+channels-1];
Bus::calculateCCT(c, cctWW, cctCW);
if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); // may need swapping
}
unsigned pix = i;
if (_reversed) pix = _len - pix -1;
@@ -306,9 +292,8 @@ void BusDigital::setStatusPixel(uint32_t c) {
}
}
void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) {
void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid) return;
uint8_t cctWW = 0, cctCW = 0;
if (hasWhite()) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
if (_data) {
@@ -336,13 +321,19 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) {
case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
}
}
if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW);
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW);
uint16_t wwcw = 0;
if (hasCCT()) {
uint8_t cctWW = 0, cctCW = 0;
Bus::calculateCCT(c, cctWW, cctCW);
wwcw = (cctCW<<8) | cctWW;
if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); // may need swapping
}
PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
}
}
// returns original color if global buffering is enabled, else returns lossly restored color from bus
uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const {
uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
if (_data) {
size_t offset = pix * getNumberOfChannels();
@@ -368,16 +359,24 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const {
case 2: c = RGBW32(b, b, b, b); break;
}
}
if (_type == TYPE_WS2812_WWA) {
uint8_t w = R(c) | G(c);
c = RGBW32(w, w, 0, w);
}
return c;
}
}
uint8_t BusDigital::getPins(uint8_t* pinArray) const {
unsigned BusDigital::getPins(uint8_t* pinArray) const {
unsigned numPins = is2Pin(_type) + 1;
if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i];
return numPins;
}
unsigned BusDigital::getBusSize() const {
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) + (_data ? _len * getNumberOfChannels() : 0) : 0);
}
void BusDigital::setColorOrder(uint8_t colorOrder) {
// upper nibble contains W swap information
if ((colorOrder & 0x0F) > 5) return;
@@ -400,8 +399,8 @@ std::vector<LEDType> BusDigital::getLEDTypes() {
{TYPE_WS2805, "D", PSTR("WS2805 RGBCW")},
{TYPE_SM16825, "D", PSTR("SM16825 RGBCW")},
{TYPE_WS2812_1CH_X3, "D", PSTR("WS2811 White")},
//{TYPE_WS2812_2CH_X3, "D", PSTR("WS2811 CCT")}, // not implemented
//{TYPE_WS2812_WWA, "D", PSTR("WS2811 WWA")}, // not implemented
//{TYPE_WS2812_2CH_X3, "D", PSTR("WS281x CCT")}, // not implemented
{TYPE_WS2812_WWA, "D", PSTR("WS281x WWA")}, // amber ignored
{TYPE_WS2801, "2P", PSTR("WS2801")},
{TYPE_APA102, "2P", PSTR("APA102")},
{TYPE_LPD8806, "2P", PSTR("LPD8806")},
@@ -416,12 +415,13 @@ void BusDigital::begin() {
}
void BusDigital::cleanup() {
DEBUG_PRINTLN(F("Digital Cleanup."));
DEBUGBUS_PRINTLN(F("Digital Cleanup."));
PolyBus::cleanup(_busPtr, _iType);
_iType = I_NONE;
_valid = false;
_busPtr = nullptr;
if (_data != nullptr) freeData();
freeData();
//PinManager::deallocateMultiplePins(_pins, 2, PinOwner::BusDigital);
PinManager::deallocatePin(_pins[1], PinOwner::BusDigital);
PinManager::deallocatePin(_pins[0], PinOwner::BusDigital);
}
@@ -452,7 +452,7 @@ void BusDigital::cleanup() {
#endif
#endif
BusPwm::BusPwm(BusConfig &bc)
BusPwm::BusPwm(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering
{
if (!isPWM(bc.type)) return;
@@ -496,12 +496,12 @@ BusPwm::BusPwm(BusConfig &bc)
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type);
_data = _pwmdata; // avoid malloc() and use stack
_data = _pwmdata; // avoid malloc() and use already allocated memory
_valid = true;
DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]);
DEBUGBUS_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]);
}
void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
void BusPwm::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel
if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) {
@@ -538,7 +538,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) {
}
//does no index check
uint32_t BusPwm::getPixelColor(uint16_t pix) const {
uint32_t BusPwm::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
// TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented)
switch (_type) {
@@ -567,19 +567,15 @@ void BusPwm::show() {
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
[[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
// use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness
// the formula is based on 12 bit resolution as there is no need for greater precision
// use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
// see: https://en.wikipedia.org/wiki/Lightness
unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response
if (pwmBri < 2040) {
// linear response for values [0-20]
pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding
} else {
// cubic response for values [21-255]
pwmBri += 4080;
float temp = (float)pwmBri / 29580.0f;
temp = temp * temp * temp * (float)maxBri;
pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri]
unsigned pwmBri = _bri;
if (pwmBri < 21) { // linear response for values [0-20]
pwmBri = (pwmBri * maxBri + 2300 / 2) / 2300 ; // adding '0.5' before division for correct rounding, 2300 gives a good match to CIE curve
} else { // cubic response for values [21-255]
float temp = float(pwmBri + 41) / float(255 + 41); // 41 is to match offset & slope to linear part
temp = temp * temp * temp * (float)maxBri;
pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] C
}
[[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri)
@@ -618,7 +614,7 @@ void BusPwm::show() {
}
}
uint8_t BusPwm::getPins(uint8_t* pinArray) const {
unsigned BusPwm::getPins(uint8_t* pinArray) const {
if (!_valid) return 0;
unsigned numPins = numPWMPins(_type);
if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i];
@@ -654,7 +650,7 @@ void BusPwm::deallocatePins() {
}
BusOnOff::BusOnOff(BusConfig &bc)
BusOnOff::BusOnOff(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed)
, _onoffdata(0)
{
@@ -671,10 +667,10 @@ BusOnOff::BusOnOff(BusConfig &bc)
_hasCCT = false;
_data = &_onoffdata; // avoid malloc() and use stack
_valid = true;
DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin);
DEBUGBUS_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin);
}
void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) {
void BusOnOff::setPixelColor(unsigned pix, uint32_t c) {
if (pix != 0 || !_valid) return; //only react to first pixel
c = autoWhiteCalc(c);
uint8_t r = R(c);
@@ -684,7 +680,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) {
_data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0;
}
uint32_t BusOnOff::getPixelColor(uint16_t pix) const {
uint32_t BusOnOff::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
return RGBW32(_data[0], _data[0], _data[0], _data[0]);
}
@@ -694,7 +690,7 @@ void BusOnOff::show() {
digitalWrite(_pin, _reversed ? !(bool)_data[0] : (bool)_data[0]);
}
uint8_t BusOnOff::getPins(uint8_t* pinArray) const {
unsigned BusOnOff::getPins(uint8_t* pinArray) const {
if (!_valid) return 0;
if (pinArray) pinArray[0] = _pin;
return 1;
@@ -707,7 +703,7 @@ std::vector<LEDType> BusOnOff::getLEDTypes() {
};
}
BusNetwork::BusNetwork(BusConfig &bc)
BusNetwork::BusNetwork(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count)
, _broadcastLock(false)
{
@@ -731,10 +727,10 @@ BusNetwork::BusNetwork(BusConfig &bc)
_UDPchannels = _hasWhite + 3;
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
_valid = (allocateData(_len * _UDPchannels) != nullptr);
DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
DEBUGBUS_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]);
}
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
void BusNetwork::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid || pix >= _len) return;
if (_hasWhite) c = autoWhiteCalc(c);
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
@@ -745,7 +741,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
if (_hasWhite) _data[offset+3] = W(c);
}
uint32_t BusNetwork::getPixelColor(uint16_t pix) const {
uint32_t BusNetwork::getPixelColor(unsigned pix) const {
if (!_valid || pix >= _len) return 0;
unsigned offset = pix * _UDPchannels;
return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0));
@@ -758,7 +754,7 @@ void BusNetwork::show() {
_broadcastLock = false;
}
uint8_t BusNetwork::getPins(uint8_t* pinArray) const {
unsigned BusNetwork::getPins(uint8_t* pinArray) const {
if (pinArray) for (unsigned i = 0; i < 4; i++) pinArray[i] = _client[i];
return 4;
}
@@ -779,6 +775,7 @@ std::vector<LEDType> BusNetwork::getLEDTypes() {
}
void BusNetwork::cleanup() {
DEBUGBUS_PRINTLN(F("Virtual Cleanup."));
_type = I_NONE;
_valid = false;
freeData();
@@ -786,43 +783,66 @@ void BusNetwork::cleanup() {
//utility to get the approx. memory usage of a given BusConfig
uint32_t BusManager::memUsage(BusConfig &bc) {
if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS;
unsigned len = bc.count + bc.skipAmount;
unsigned channels = Bus::getNumberOfChannels(bc.type);
unsigned multiplier = 1;
if (Bus::isDigital(bc.type)) { // digital types
if (Bus::is16bit(bc.type)) len *= 2; // 16-bit LEDs
#ifdef ESP8266
if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem
multiplier = 5;
}
#else //ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times)
multiplier = PolyBus::isParallelI2S1Output() ? 24 : 2;
#endif
}
return (len * multiplier + bc.doubleBuffer * (bc.count + bc.skipAmount)) * channels;
}
uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned minBuses) {
//ESP32 RMT uses double buffer, parallel I2S uses 8x buffer (3 times)
unsigned multiplier = PolyBus::isParallelI2S1Output() ? 3 : 2;
return (maxChannels * maxCount * minBuses * multiplier);
}
int BusManager::add(BusConfig &bc) {
if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1;
if (Bus::isVirtual(bc.type)) {
busses[numBusses] = new BusNetwork(bc);
} else if (Bus::isDigital(bc.type)) {
busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap);
} else if (Bus::isOnOff(bc.type)) {
busses[numBusses] = new BusOnOff(bc);
unsigned BusConfig::memUsage(unsigned nr) const {
if (Bus::isVirtual(type)) {
return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type));
} else if (Bus::isDigital(type)) {
return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) + doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type);
} else if (Bus::isOnOff(type)) {
return sizeof(BusOnOff);
} else {
busses[numBusses] = new BusPwm(bc);
return sizeof(BusPwm);
}
return numBusses++;
}
unsigned BusManager::memUsage() {
// when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers
// front buffers are always allocated per bus
unsigned size = 0;
unsigned maxI2S = 0;
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
unsigned digitalCount = 0;
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
#define MAX_RMT 4
#else
#define MAX_RMT 8
#endif
#endif
for (const auto &bus : busses) {
unsigned busSize = bus->getBusSize();
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
if (bus->isDigital() && !bus->is2Pin()) digitalCount++;
if (PolyBus::isParallelI2S1Output() && digitalCount > MAX_RMT) {
unsigned i2sCommonSize = 3 * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1);
if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize;
busSize -= i2sCommonSize;
}
#endif
size += busSize;
}
return size + maxI2S;
}
int BusManager::add(const BusConfig &bc) {
DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (%d - %d >= %d)\n"), getNumBusses(), getNumVirtualBusses(), WLED_MAX_BUSSES);
if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1;
unsigned numDigital = 0;
for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++;
if (Bus::isVirtual(bc.type)) {
//busses.push_back(std::make_unique<BusNetwork>(bc)); // when C++ >11
busses.push_back(new BusNetwork(bc));
} else if (Bus::isDigital(bc.type)) {
//busses.push_back(std::make_unique<BusDigital>(bc, numDigital, colorOrderMap));
busses.push_back(new BusDigital(bc, numDigital, colorOrderMap));
} else if (Bus::isOnOff(bc.type)) {
//busses.push_back(std::make_unique<BusOnOff>(bc));
busses.push_back(new BusOnOff(bc));
} else {
//busses.push_back(std::make_unique<BusPwm>(bc));
busses.push_back(new BusPwm(bc));
}
return busses.size();
}
// credit @willmmiles
@@ -851,18 +871,21 @@ String BusManager::getLEDTypesJSONString() {
}
void BusManager::useParallelOutput() {
_parallelOutputs = 8; // hardcoded since we use NPB I2S x8 methods
DEBUGBUS_PRINTLN(F("Bus: Enabling parallel I2S."));
PolyBus::setParallelI2S1Output();
}
bool BusManager::hasParallelOutput() {
return PolyBus::isParallelI2S1Output();
}
//do not call this method from system context (network callback)
void BusManager::removeAll() {
DEBUG_PRINTLN(F("Removing all."));
DEBUGBUS_PRINTLN(F("Removing all."));
//prevents crashes due to deleting busses while in use.
while (!canAllShow()) yield();
for (unsigned i = 0; i < numBusses; i++) delete busses[i];
numBusses = 0;
_parallelOutputs = 1;
for (auto &bus : busses) delete bus; // needed when not using std::unique_ptr C++ >11
busses.clear();
PolyBus::setParallelI2S1Output(false);
}
@@ -873,7 +896,9 @@ void BusManager::removeAll() {
void BusManager::esp32RMTInvertIdle() {
bool idle_out;
unsigned rmt = 0;
for (unsigned u = 0; u < numBusses(); u++) {
unsigned u = 0;
for (auto &bus : busses) {
if (bus->getLength()==0 || !bus->isDigital() || bus->is2Pin()) continue;
#if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, only has 1 I2S but NPB does not support it ATM
if (u > 1) return;
rmt = u;
@@ -884,11 +909,11 @@ void BusManager::esp32RMTInvertIdle() {
if (u > 3) return;
rmt = u;
#else
if (u < _parallelOutputs) continue;
if (u >= _parallelOutputs + 8) return; // only 8 RMT channels
rmt = u - _parallelOutputs;
unsigned numI2S = !PolyBus::isParallelI2S1Output(); // if using parallel I2S, RMT is used 1st
if (numI2S > u) continue;
if (u > 7 + numI2S) return;
rmt = u - numI2S;
#endif
if (busses[u]->getLength()==0 || !busses[u]->isDigital() || busses[u]->is2Pin()) continue;
//assumes that bus number to rmt channel mapping stays 1:1
rmt_channel_t ch = static_cast<rmt_channel_t>(rmt);
rmt_idle_level_t lvl;
@@ -897,6 +922,7 @@ void BusManager::esp32RMTInvertIdle() {
else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH;
else continue;
rmt_set_idle_level(ch, idle_out, lvl);
u++
}
}
#endif
@@ -905,12 +931,12 @@ void BusManager::on() {
#ifdef ESP8266
//Fix for turning off onboard LED breaking bus
if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) {
for (unsigned i = 0; i < numBusses; i++) {
for (auto &bus : busses) {
uint8_t pins[2] = {255,255};
if (busses[i]->isDigital() && busses[i]->getPins(pins)) {
if (bus->isDigital() && bus->getPins(pins)) {
if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) {
BusDigital *bus = static_cast<BusDigital*>(busses[i]);
bus->begin();
BusDigital *b = static_cast<BusDigital*>(bus);
b->begin();
break;
}
}
@@ -927,7 +953,7 @@ void BusManager::off() {
// turn off built-in LED if strip is turned off
// this will break digital bus so will need to be re-initialised on On
if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) {
for (unsigned i = 0; i < numBusses; i++) if (busses[i]->isOffRefreshRequired()) return;
for (const auto &bus : busses) if (bus->isOffRefreshRequired()) return;
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
}
@@ -939,31 +965,26 @@ void BusManager::off() {
void BusManager::show() {
_milliAmpsUsed = 0;
for (unsigned i = 0; i < numBusses; i++) {
busses[i]->show();
_milliAmpsUsed += busses[i]->getUsedCurrent();
for (auto &bus : busses) {
bus->show();
_milliAmpsUsed += bus->getUsedCurrent();
}
if (_milliAmpsUsed) _milliAmpsUsed += MA_FOR_ESP;
}
void BusManager::setStatusPixel(uint32_t c) {
for (unsigned i = 0; i < numBusses; i++) {
busses[i]->setStatusPixel(c);
}
for (auto &bus : busses) bus->setStatusPixel(c);
}
void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) {
for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart();
if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue;
busses[i]->setPixelColor(pix - bstart, c);
void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
for (auto &bus : busses) {
unsigned bstart = bus->getStart();
if (pix < bstart || pix >= bstart + bus->getLength()) continue;
bus->setPixelColor(pix - bstart, c);
}
}
void BusManager::setBrightness(uint8_t b) {
for (unsigned i = 0; i < numBusses; i++) {
busses[i]->setBrightness(b);
}
for (auto &bus : busses) bus->setBrightness(b);
}
void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
@@ -975,35 +996,33 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) {
Bus::setCCT(cct);
}
uint32_t BusManager::getPixelColor(uint16_t pix) {
for (unsigned i = 0; i < numBusses; i++) {
unsigned bstart = busses[i]->getStart();
if (!busses[i]->containsPixel(pix)) continue;
return busses[i]->getPixelColor(pix - bstart);
uint32_t BusManager::getPixelColor(unsigned pix) {
for (auto &bus : busses) {
unsigned bstart = bus->getStart();
if (!bus->containsPixel(pix)) continue;
return bus->getPixelColor(pix - bstart);
}
return 0;
}
bool BusManager::canAllShow() {
for (unsigned i = 0; i < numBusses; i++) {
if (!busses[i]->canShow()) return false;
}
for (const auto &bus : busses) if (!bus->canShow()) return false;
return true;
}
Bus* BusManager::getBus(uint8_t busNr) {
if (busNr >= numBusses) return nullptr;
if (busNr >= busses.size()) return nullptr;
return busses[busNr];
}
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
uint16_t BusManager::getTotalLength() {
unsigned len = 0;
for (unsigned i=0; i<numBusses; i++) len += busses[i]->getLength();
for (const auto &bus : busses) len += bus->getLength();
return len;
}
bool PolyBus::useParallelI2S = false;
bool PolyBus::_useParallelI2S = false;
// Bus static member definition
int16_t Bus::_cct = -1;
@@ -1012,9 +1031,8 @@ uint8_t Bus::_gAWM = 255;
uint16_t BusDigital::_milliAmpsTotal = 0;
uint8_t BusManager::numBusses = 0;
Bus* BusManager::busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES];
//std::vector<std::unique_ptr<Bus>> BusManager::busses;
std::vector<Bus*> BusManager::busses;
ColorOrderMap BusManager::colorOrderMap = {};
uint16_t BusManager::_milliAmpsUsed = 0;
uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT;
uint8_t BusManager::_parallelOutputs = 1;

View File

@@ -1,3 +1,4 @@
#pragma once
#ifndef BusManager_h
#define BusManager_h
@@ -7,6 +8,30 @@
#include "const.h"
#include <vector>
#include <memory>
// enable additional debug output
#if defined(WLED_DEBUG_HOST)
#include "net_debug.h"
#define DEBUGOUT NetDebug
#else
#define DEBUGOUT Serial
#endif
#ifdef WLED_DEBUG_BUS
#ifndef ESP8266
#include <rom/rtc.h>
#endif
#define DEBUGBUS_PRINT(x) DEBUGOUT.print(x)
#define DEBUGBUS_PRINTLN(x) DEBUGOUT.println(x)
#define DEBUGBUS_PRINTF(x...) DEBUGOUT.printf(x)
#define DEBUGBUS_PRINTF_P(x...) DEBUGOUT.printf_P(x)
#else
#define DEBUGBUS_PRINT(x)
#define DEBUGBUS_PRINTLN(x)
#define DEBUGBUS_PRINTF(x...)
#define DEBUGBUS_PRINTF_P(x...)
#endif
//colors.cpp
uint16_t approximateKelvinFromRGB(uint32_t rgb);
@@ -77,50 +102,51 @@ class Bus {
_autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY;
};
virtual ~Bus() {} //throw the bus under the bus
virtual ~Bus() {} //throw the bus under the bus (derived class needs to freeData())
virtual void begin() {};
virtual void begin() {};
virtual void show() = 0;
virtual bool canShow() const { return true; }
virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(uint16_t pix, uint32_t c) = 0;
virtual void setBrightness(uint8_t b) { _bri = b; };
virtual void setColorOrder(uint8_t co) {}
virtual uint32_t getPixelColor(uint16_t pix) const { return 0; }
virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; }
virtual uint16_t getLength() const { return isOk() ? _len : 0; }
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() const { return 0; }
virtual uint16_t getFrequency() const { return 0U; }
virtual uint16_t getLEDCurrent() const { return 0; }
virtual uint16_t getUsedCurrent() const { return 0; }
virtual uint16_t getMaxCurrent() const { return 0; }
virtual bool canShow() const { return true; }
virtual void setStatusPixel(uint32_t c) {}
virtual void setPixelColor(unsigned pix, uint32_t c) = 0;
virtual void setBrightness(uint8_t b) { _bri = b; };
virtual void setColorOrder(uint8_t co) {}
virtual uint32_t getPixelColor(unsigned pix) const { return 0; }
virtual unsigned getPins(uint8_t* pinArray = nullptr) const { return 0; }
virtual uint16_t getLength() const { return isOk() ? _len : 0; }
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
virtual unsigned skippedLeds() const { return 0; }
virtual uint16_t getFrequency() const { return 0U; }
virtual uint16_t getLEDCurrent() const { return 0; }
virtual uint16_t getUsedCurrent() const { return 0; }
virtual uint16_t getMaxCurrent() const { return 0; }
virtual unsigned getBusSize() const { return sizeof(Bus); }
inline bool hasRGB() const { return _hasRgb; }
inline bool hasWhite() const { return _hasWhite; }
inline bool hasCCT() const { return _hasCCT; }
inline bool isDigital() const { return isDigital(_type); }
inline bool is2Pin() const { return is2Pin(_type); }
inline bool isOnOff() const { return isOnOff(_type); }
inline bool isPWM() const { return isPWM(_type); }
inline bool isVirtual() const { return isVirtual(_type); }
inline bool is16bit() const { return is16bit(_type); }
inline bool mustRefresh() const { return mustRefresh(_type); }
inline void setReversed(bool reversed) { _reversed = reversed; }
inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; }
inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint16_t getStart() const { return _start; }
inline uint8_t getType() const { return _type; }
inline bool isOk() const { return _valid; }
inline bool isReversed() const { return _reversed; }
inline bool isOffRefreshRequired() const { return _needsRefresh; }
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
inline bool hasRGB() const { return _hasRgb; }
inline bool hasWhite() const { return _hasWhite; }
inline bool hasCCT() const { return _hasCCT; }
inline bool isDigital() const { return isDigital(_type); }
inline bool is2Pin() const { return is2Pin(_type); }
inline bool isOnOff() const { return isOnOff(_type); }
inline bool isPWM() const { return isPWM(_type); }
inline bool isVirtual() const { return isVirtual(_type); }
inline bool is16bit() const { return is16bit(_type); }
inline bool mustRefresh() const { return mustRefresh(_type); }
inline void setReversed(bool reversed) { _reversed = reversed; }
inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; }
inline unsigned getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint16_t getStart() const { return _start; }
inline uint8_t getType() const { return _type; }
inline bool isOk() const { return _valid; }
inline bool isReversed() const { return _reversed; }
inline bool isOffRefreshRequired() const { return _needsRefresh; }
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 uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); }
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
static constexpr unsigned getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
static constexpr unsigned 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);
}
@@ -152,7 +178,7 @@ class Bus {
static inline uint8_t getGlobalAWMode() { return _gAWM; }
static inline void setCCT(int16_t cct) { _cct = cct; }
static inline uint8_t getCCTBlend() { return _cctBlend; }
static inline void setCCTBlend(uint8_t b) {
static inline void setCCTBlend(uint8_t b) {
_cctBlend = (std::min((int)b,100) * 127) / 100;
//compile-time limiter for hardware that can't power both white channels at max
#ifdef WLED_MAX_CCT_BLEND
@@ -191,29 +217,30 @@ class Bus {
uint32_t autoWhiteCalc(uint32_t c) const;
uint8_t *allocateData(size_t size = 1);
void freeData() { if (_data != nullptr) free(_data); _data = nullptr; }
void freeData();
};
class BusDigital : public Bus {
public:
BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
~BusDigital() { cleanup(); }
void show() override;
bool canShow() const override;
void setBrightness(uint8_t b) override;
void setStatusPixel(uint32_t c) override;
[[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override;
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
void setColorOrder(uint8_t colorOrder) override;
[[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
uint8_t getColorOrder() const override { return _colorOrder; }
uint8_t getPins(uint8_t* pinArray = nullptr) const override;
uint8_t skippedLeds() const override { return _skip; }
unsigned getPins(uint8_t* pinArray = nullptr) const override;
unsigned skippedLeds() const override { return _skip; }
uint16_t getFrequency() const override { return _frequencykHz; }
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
unsigned getBusSize() const override;
void begin() override;
void cleanup();
@@ -243,21 +270,22 @@ class BusDigital : public Bus {
return c;
}
uint8_t estimateCurrentAndLimitBri();
uint8_t estimateCurrentAndLimitBri() const;
};
class BusPwm : public Bus {
public:
BusPwm(BusConfig &bc);
BusPwm(const BusConfig &bc);
~BusPwm() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override; //does no index check
uint8_t getPins(uint8_t* pinArray = nullptr) const override;
void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override; //does no index check
unsigned getPins(uint8_t* pinArray = nullptr) const override;
uint16_t getFrequency() const override { return _frequency; }
unsigned getBusSize() const override { return sizeof(BusPwm); }
void show() override;
void cleanup() { deallocatePins(); }
inline void cleanup() { deallocatePins(); _data = nullptr; }
static std::vector<LEDType> getLEDTypes();
@@ -276,14 +304,15 @@ class BusPwm : public Bus {
class BusOnOff : public Bus {
public:
BusOnOff(BusConfig &bc);
BusOnOff(const BusConfig &bc);
~BusOnOff() { cleanup(); }
void setPixelColor(uint16_t pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override;
uint8_t getPins(uint8_t* pinArray) const override;
void setPixelColor(unsigned pix, uint32_t c) override;
uint32_t getPixelColor(unsigned pix) const override;
unsigned getPins(uint8_t* pinArray) const override;
unsigned getBusSize() const override { return sizeof(BusOnOff); }
void show() override;
void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); }
inline void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); _data = nullptr; }
static std::vector<LEDType> getLEDTypes();
@@ -295,13 +324,14 @@ class BusOnOff : public Bus {
class BusNetwork : public Bus {
public:
BusNetwork(BusConfig &bc);
BusNetwork(const BusConfig &bc);
~BusNetwork() { cleanup(); }
bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out
void setPixelColor(uint16_t pix, uint32_t c) override;
uint32_t getPixelColor(uint16_t pix) const override;
uint8_t getPins(uint8_t* pinArray = nullptr) const override;
[[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override;
[[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override;
unsigned getPins(uint8_t* pinArray = nullptr) const override;
unsigned getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); }
void show() override;
void cleanup();
@@ -347,6 +377,16 @@ struct BusConfig {
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
size_t nPins = Bus::getNumberOfPins(type);
for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i];
DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"),
(int)start, (int)(start+len),
(int)type,
(int)colorOrder,
(int)reversed,
(int)skipAmount,
(int)autoWhite,
(int)frequency,
(int)milliAmpsPerLed, (int)milliAmpsMax
);
}
//validates start and length and extends total if needed
@@ -360,21 +400,32 @@ struct BusConfig {
if (start + count > total) total = start + count;
return true;
}
unsigned memUsage(unsigned nr = 0) const;
};
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
class BusManager {
public:
BusManager() {};
//utility to get the approx. memory usage of a given BusConfig
static uint32_t memUsage(BusConfig &bc);
static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1);
static uint16_t currentMilliamps() { return _milliAmpsUsed; }
static unsigned memUsage();
static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; }
static uint16_t ablMilliampsMax() { return _milliAmpsMax; }
static int add(BusConfig &bc);
static int add(const BusConfig &bc);
static void useParallelOutput(); // workaround for inaccessible PolyBus
static bool hasParallelOutput(); // workaround for inaccessible PolyBus
//do not call this method from system context (network callback)
static void removeAll();
@@ -385,38 +436,37 @@ class BusManager {
static void show();
static bool canAllShow();
static void setStatusPixel(uint32_t c);
[[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c);
[[gnu::hot]] static void setPixelColor(unsigned pix, uint32_t c);
static void setBrightness(uint8_t b);
// for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K
// WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT()
static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false);
static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;}
static uint32_t getPixelColor(uint16_t pix);
static uint32_t getPixelColor(unsigned pix);
static inline int16_t getSegmentCCT() { return Bus::getCCT(); }
static Bus* getBus(uint8_t busNr);
//semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit())
static uint16_t getTotalLength();
static inline uint8_t getNumBusses() { return numBusses; }
static inline uint8_t getNumBusses() { return busses.size(); }
static String getLEDTypesJSONString();
static inline ColorOrderMap& getColorOrderMap() { return colorOrderMap; }
private:
static uint8_t numBusses;
static Bus* busses[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES];
//static std::vector<std::unique_ptr<Bus>> busses; // we'd need C++ >11
static std::vector<Bus*> busses;
static ColorOrderMap colorOrderMap;
static uint16_t _milliAmpsUsed;
static uint16_t _milliAmpsMax;
static uint8_t _parallelOutputs;
#ifdef ESP32_DATA_IDLE_HIGH
static void esp32RMTInvertIdle() ;
#endif
static uint8_t getNumVirtualBusses() {
int j = 0;
for (int i=0; i<numBusses; i++) if (busses[i]->isVirtual()) j++;
for (const auto &bus : busses) j += bus->isVirtual();
return j;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,7 @@ void longPressAction(uint8_t b)
{
if (!macroLongPress[b]) {
switch (b) {
case 0: setRandomColor(col); colorUpdated(CALL_MODE_BUTTON); break;
case 0: setRandomColor(colPri); colorUpdated(CALL_MODE_BUTTON); break;
case 1:
if(buttonBriDirection) {
if (bri == 255) break; // avoid unnecessary updates to brightness
@@ -230,7 +230,7 @@ void handleAnalog(uint8_t b)
effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
} else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation
colorHStoRGB(aRead*256,255,col);
colorHStoRGB(aRead*256,255,colPri);
} else {
// otherwise use "double press" for segment selection
Segment& seg = strip.getSegment(macroDoublePress[b]);
@@ -375,6 +375,7 @@ void handleIO()
if (rlyPin>=0) {
pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
digitalWrite(rlyPin, rlyMde);
delay(50); // wait for relay to switch and power to stabilize
}
offMode = false;
}

View File

@@ -118,6 +118,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
Bus::setCCTBlend(strip.cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
CJSON(useGlobalLedBuffer, hw_led[F("ld")]);
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
CJSON(useParallelI2S, hw_led[F("prl")]);
#endif
#ifndef WLED_DISABLE_2D
// 2D Matrix Settings
@@ -162,34 +165,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
int s = 0; // bus iterator
if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback
unsigned mem = 0;
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
bool useParallel = false;
#if defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP32S2) && !defined(ARDUINO_ARCH_ESP32S3) && !defined(ARDUINO_ARCH_ESP32C3)
unsigned digitalCount = 0;
unsigned maxLedsOnBus = 0;
unsigned maxChannels = 0;
for (JsonObject elm : ins) {
unsigned type = elm["type"] | TYPE_WS2812_RGB;
unsigned len = elm["len"] | DEFAULT_LED_COUNT;
if (!Bus::isDigital(type)) continue;
if (!Bus::is2Pin(type)) {
digitalCount++;
unsigned channels = Bus::getNumberOfChannels(type);
if (len > maxLedsOnBus) maxLedsOnBus = len;
if (channels > maxChannels) maxChannels = channels;
}
}
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount);
// we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0
if (maxLedsOnBus <= 300 && digitalCount > 5) {
DEBUG_PRINTLN(F("Switching to parallel I2S."));
useParallel = true;
BusManager::useParallelOutput();
mem = BusManager::memUsage(maxChannels, maxLedsOnBus, 8); // use alternate memory calculation
}
#endif
for (JsonObject elm : ins) {
if (s >= WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES) break;
@@ -220,24 +195,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
maMax = 0;
}
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
if (fromFS) {
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax);
if (useParallel && s < 8) {
// if for some unexplained reason the above pre-calculation was wrong, update
unsigned memT = BusManager::memUsage(bc); // includes x8 memory allocation for parallel I2S
if (memT > mem) mem = memT; // if we have unequal LED count use the largest
} else
mem += BusManager::memUsage(bc); // includes global buffer
if (mem <= MAX_LED_MEMORY) if (BusManager::add(bc) == -1) break; // finalization will be done in WLED::beginStrip()
} else {
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax);
doInitBusses = true; // finalization done in beginStrip()
}
busConfigs.push_back(std::move(BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax)));
doInitBusses = true; // finalization done in beginStrip()
s++;
}
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB\n"), mem);
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
}
if (hw_led["rev"]) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus
@@ -677,16 +639,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
static const char s_cfg_json[] PROGMEM = "/cfg.json";
void deserializeConfigFromFS() {
bool success = deserializeConfigSec();
bool deserializeConfigFromFS() {
[[maybe_unused]] bool success = deserializeConfigSec();
#ifdef WLED_ADD_EEPROM_SUPPORT
if (!success) { //if file does not exist, try reading from EEPROM
deEEPSettings();
return;
return true;
}
#endif
if (!requestJSONBufferLock(1)) return;
if (!requestJSONBufferLock(1)) return false;
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
@@ -696,17 +658,11 @@ void deserializeConfigFromFS() {
#ifdef WLED_ADD_EEPROM_SUPPORT
deEEPSettings();
#endif
// save default values to /cfg.json
// call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving
JsonObject empty = JsonObject();
UsermodManager::readFromConfig(empty);
serializeConfig();
// init Ethernet (in case default type is set at compile time)
#ifdef WLED_USE_ETHERNET
WLED::instance().initEthernet();
#endif
return;
return true; // config does not exist (we will need to save it once strip is initialised)
}
// NOTE: This routine deserializes *and* applies the configuration
@@ -715,7 +671,7 @@ void deserializeConfigFromFS() {
bool needsSave = deserializeConfig(root, true);
releaseJSONBufferLock();
if (needsSave) serializeConfig(); // usermods required new parameters
return needsSave;
}
void serializeConfig() {
@@ -824,6 +780,9 @@ void serializeConfig() {
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override
hw_led[F("ld")] = useGlobalLedBuffer;
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
hw_led[F("prl")] = BusManager::hasParallelOutput();
#endif
#ifndef WLED_DISABLE_2D
// 2D Matrix Settings
@@ -848,8 +807,19 @@ void serializeConfig() {
JsonArray hw_led_ins = hw_led.createNestedArray("ins");
for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s);
Bus *bus = BusManager::getBus(s);
if (!bus || bus->getLength()==0) break;
DEBUG_PRINTF_P(PSTR(" (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"),
(int)bus->getStart(), (int)(bus->getStart()+bus->getLength()),
(int)(bus->getType() & 0x7F),
(int)bus->getColorOrder(),
(int)bus->isReversed(),
(int)bus->skippedLeds(),
(int)bus->getAutoWhiteMode(),
(int)bus->getFrequency(),
(int)bus->getLEDCurrent(), (int)bus->getMaxCurrent()
);
JsonObject ins = hw_led_ins.createNestedObject();
ins["start"] = bus->getStart();
ins["len"] = bus->getLength();

View File

@@ -37,7 +37,7 @@
#endif
#ifndef WLED_MAX_USERMODS
#ifdef ESP8266
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32S2)
#define WLED_MAX_USERMODS 4
#else
#define WLED_MAX_USERMODS 6
@@ -49,31 +49,31 @@
#define WLED_MAX_DIGITAL_CHANNELS 3
#define WLED_MAX_ANALOG_CHANNELS 5
#define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB
#define WLED_MIN_VIRTUAL_BUSSES 2
#define WLED_MIN_VIRTUAL_BUSSES 3
#else
#define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX)
#if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM
#define WLED_MAX_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white
#define WLED_MAX_DIGITAL_CHANNELS 2
//#define WLED_MAX_ANALOG_CHANNELS 6
#define WLED_MIN_VIRTUAL_BUSSES 3
#define WLED_MIN_VIRTUAL_BUSSES 4
#elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB
// the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though)
#define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 5
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 3
#elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM
#define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 4
#define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 4
#elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1
#define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD
//#define WLED_MAX_ANALOG_CHANNELS 8
#define WLED_MIN_VIRTUAL_BUSSES 6
#else
// the last digital bus (I2S0) will prevent Audioreactive usermod from functioning
#define WLED_MAX_BUSSES 20 // will allow 17 digital & 3 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 17
#define WLED_MAX_BUSSES 19 // will allow 16 digital & 3 analog RGB
#define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT
//#define WLED_MAX_ANALOG_CHANNELS 16
#define WLED_MIN_VIRTUAL_BUSSES 4
#define WLED_MIN_VIRTUAL_BUSSES 6
#endif
#endif
#else
@@ -115,7 +115,7 @@
#endif
#endif
#ifdef ESP8266
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32S2)
#define WLED_MAX_COLOR_ORDER_MAPPINGS 5
#else
#define WLED_MAX_COLOR_ORDER_MAPPINGS 10
@@ -125,7 +125,7 @@
#undef WLED_MAX_LEDMAPS
#endif
#ifndef WLED_MAX_LEDMAPS
#ifdef ESP8266
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32S2)
#define WLED_MAX_LEDMAPS 10
#else
#define WLED_MAX_LEDMAPS 16
@@ -473,6 +473,8 @@
#ifndef MAX_LEDS
#ifdef ESP8266
#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_LEDS 2048 //due to memory constraints
#else
#define MAX_LEDS 8192
#endif
@@ -482,7 +484,9 @@
#ifdef ESP8266
#define MAX_LED_MEMORY 4000
#else
#if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
#if defined(ARDUINO_ARCH_ESP32S2)
#define MAX_LED_MEMORY 16000
#elif defined(ARDUINO_ARCH_ESP32C3)
#define MAX_LED_MEMORY 32000
#else
#define MAX_LED_MEMORY 64000

View File

@@ -16,7 +16,7 @@ function isI(n) { return n === +n && n === (n|0); } // isInteger
function toggle(el) { gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide"); }
function tooltip(cont=null) {
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
element.addEventListener("mouseover", ()=>{
element.addEventListener("pointerover", ()=>{
// save title
element.setAttribute("data-title", element.getAttribute("title"));
const tooltip = d.createElement("span");
@@ -41,7 +41,7 @@ function tooltip(cont=null) {
tooltip.classList.add("visible");
});
element.addEventListener("mouseout", ()=>{
element.addEventListener("pointerout", ()=>{
d.querySelectorAll('.tooltip').forEach((tooltip)=>{
tooltip.classList.remove("visible");
d.body.removeChild(tooltip);

View File

@@ -128,7 +128,7 @@
<div style="padding: 8px 0;" id="btns">
<button class="btn btn-xs" title="File editor" type="button" id="edit" onclick="window.location.href=getURL('/edit')"><i class="icons btn-icon">&#xe2c6;</i></button>
<button class="btn btn-xs" title="Pixel Magic Tool" type="button" id="pxmb" onclick="window.location.href=getURL('/pxmagic.htm')"><i class="icons btn-icon">&#xe410;</i></button>
<button class="btn btn-xs" title="Add custom palette" type="button" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon">&#xe18a;</i></button>
<button class="btn btn-xs" title="Add custom palette" type="button" id="adPal" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon">&#xe18a;</i></button>
<button class="btn btn-xs" title="Remove last custom palette" type="button" id="rmPal" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon">&#xe037;</i></button>
</div>
<p class="labels hd" id="pall"><i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette</p>

View File

@@ -773,8 +773,8 @@ function populateSegments(s)
}
let segp = `<div id="segp${i}" class="sbs">`+
`<i class="icons slider-icon pwr ${inst.on ? "act":""}" id="seg${i}pwr" onclick="setSegPwr(${i})">&#xe08f;</i>`+
`<div class="sliderwrap il">`+
`<i class="icons slider-icon pwr ${inst.on ? "act":""}" id="seg${i}pwr" title="Power" onclick="setSegPwr(${i})">&#xe08f;</i>`+
`<div class="sliderwrap il" title="Opacity/Brightness">`+
`<input id="seg${i}bri" class="noslide" onchange="setSegBri(${i})" oninput="updateTrail(this)" max="255" min="1" type="range" value="${inst.bri}" />`+
`<div class="sliderdisplay"></div>`+
`</div>`+
@@ -810,7 +810,7 @@ function populateSegments(s)
cn += `<div class="seg lstI ${i==s.mainseg && !simplifiedUI ? 'selected' : ''} ${exp ? "expanded":""}" id="seg${i}" data-set="${inst.set}">`+
`<label class="check schkl ${smpl}">`+
`<input type="checkbox" id="seg${i}sel" onchange="selSeg(${i})" ${inst.sel ? "checked":""}>`+
`<span class="checkmark"></span>`+
`<span class="checkmark" title="Select"></span>`+
`</label>`+
`<div class="segname ${smpl}" onclick="selSegEx(${i})">`+
`<i class="icons e-icon frz" id="seg${i}frz" title="(un)Freeze" onclick="event.preventDefault();tglFreeze(${i});">&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};</i>`+
@@ -1659,13 +1659,17 @@ function setEffectParameters(idx)
paOnOff[0] = paOnOff[0].substring(0,dPos);
}
if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0];
gId("adPal").classList.remove("hide");
if (lastinfo.cpalcount>0) gId("rmPal").classList.remove("hide");
} else {
// disable palette list
text += ' not used';
palw.style.display = "none";
gId("adPal").classList.add("hide");
gId("rmPal").classList.add("hide");
// Close palette dialog if not available
if (gId("palw").lastElementChild.tagName == "DIALOG") {
gId("palw").lastElementChild.close();
if (palw.lastElementChild.tagName == "DIALOG") {
palw.lastElementChild.close();
}
}
pall.innerHTML = icon + text;
@@ -1879,7 +1883,7 @@ function makeSeg()
function resetUtil(off=false)
{
gId('segutil').innerHTML = `<div class="seg btn btn-s${off?' off':''}" style="padding:0;margin-bottom:12px;">`
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark"></span></label>'
+ '<label class="check schkl"><input type="checkbox" id="selall" onchange="selSegAll(this)"><span class="checkmark" title="Select all"></span></label>'
+ `<div class="segname" ${off?'':'onclick="makeSeg()"'}><i class="icons btn-icon">&#xe18a;</i>Add segment</div>`
+ '<div class="pop hide" onclick="event.stopPropagation();">'
+ `<i class="icons g-icon" title="Select group" onclick="this.nextElementSibling.classList.toggle('hide');">&#xE34B;</i>`
@@ -2649,28 +2653,28 @@ function fromRgb()
var g = gId('sliderG').value;
var b = gId('sliderB').value;
setPicker(`rgb(${r},${g},${b})`);
let cd = gId('csl').children; // color slots
cd[csel].dataset.r = r;
cd[csel].dataset.g = g;
cd[csel].dataset.b = b;
setCSL(cd[csel]);
let cd = gId('csl').children[csel]; // color slots
cd.dataset.r = r;
cd.dataset.g = g;
cd.dataset.b = b;
setCSL(cd);
}
function fromW()
{
let w = gId('sliderW');
let cd = gId('csl').children; // color slots
cd[csel].dataset.w = w.value;
setCSL(cd[csel]);
let cd = gId('csl').children[csel]; // color slots
cd.dataset.w = w.value;
setCSL(cd);
updateTrail(w);
}
// sr 0: from RGB sliders, 1: from picker, 2: from hex
function setColor(sr)
{
var cd = gId('csl').children; // color slots
let cdd = cd[csel].dataset;
let w = 0, r,g,b;
var cd = gId('csl').children[csel]; // color slots
let cdd = cd.dataset;
let w = parseInt(cdd.w), r = parseInt(cdd.r), g = parseInt(cdd.g), b = parseInt(cdd.b);
if (sr == 1 && isRgbBlack(cdd)) cpick.color.setChannel('hsv', 'v', 100);
if (sr != 2 && hasWhite) w = parseInt(gId('sliderW').value);
var col = cpick.color.rgb;
@@ -2678,7 +2682,7 @@ function setColor(sr)
cdd.g = g = hasRGB ? col.g : w;
cdd.b = b = hasRGB ? col.b : w;
cdd.w = w;
setCSL(cd[csel]);
setCSL(cd);
var obj = {"seg": {"col": [[],[],[]]}};
obj.seg.col[csel] = [r, g, b, w];
requestJson(obj);
@@ -3114,10 +3118,9 @@ function mergeDeep(target, ...sources)
return mergeDeep(target, ...sources);
}
function tooltip(cont=null)
{
function tooltip(cont=null) {
d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{
element.addEventListener("mouseover", ()=>{
element.addEventListener("pointerover", ()=>{
// save title
element.setAttribute("data-title", element.getAttribute("title"));
const tooltip = d.createElement("span");
@@ -3142,7 +3145,7 @@ function tooltip(cont=null)
tooltip.classList.add("visible");
});
element.addEventListener("mouseout", ()=>{
element.addEventListener("pointerout", ()=>{
d.querySelectorAll('.tooltip').forEach((tooltip)=>{
tooltip.classList.remove("visible");
d.body.removeChild(tooltip);

View File

@@ -6,8 +6,7 @@
<title>LED Settings</title>
<script src="common.js" async type="text/javascript"></script>
<script>
var laprev=55,maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var oMaxB=1;
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;}
// these functions correspond to C macros found in const.h
@@ -24,6 +23,7 @@
function is16b(t) { return !!(gT(t).c & 0x10); } // is digital 16 bit type
function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory
function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins
function chrID(x) { return String.fromCharCode((x<10?48:55)+x); }
function S() {
getLoc();
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
@@ -42,15 +42,20 @@
if (loc) d.Sf.action = getURL('/settings/leds');
}
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S)
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S)
maxA = a; // maxA - max analog channels
maxV = v; // maxV - min virtual buses
maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxV = v; // maxV - min virtual buses: 4 - ESP32/S3, 3 - S2/C3, 2 - ESP8266
maxPB = p; // maxPB - max LEDs per bus
maxM = m; // maxM - max LED memory
maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
maxCO = o; // maxCO - max Color Order mappings
}
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
function isC3() { return maxA == 6 && maxD == 2; } // NOTE: see const.h
function isS2() { return maxA == 8 && maxD == 12 && maxV == 4; } // NOTE: see const.h
function isS3() { return maxA == 8 && maxD == 12 && maxV == 6; } // NOTE: see const.h
function pinsOK() {
var ok = true;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
@@ -138,7 +143,7 @@
gId("ppldis").style.display = ppl ? 'inline' : 'none';
// set PPL minimum value and clear actual PPL limit if ABL is disabled
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,x)=>{
var n = String.fromCharCode((x<10?48:55)+x);
var n = chrID(x);
gId("PSU"+n).style.display = ppl ? "inline" : "none";
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
const c = parseInt(d.Sf["LC"+n].value); //get LED count
@@ -169,7 +174,7 @@
// select appropriate LED current
d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{
sel.value = 0; // set custom
var n = String.fromCharCode((x<10?48:55)+x);
var n = chrID(x);
if (en)
switch (parseInt(d.Sf["LA"+n].value)) {
case 0: break; // disable ABL
@@ -193,6 +198,7 @@
let dbl = 0;
let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
let mul = 1;
if (d.Sf.LD.checked) dbl = len * ch; // global buffer
if (isDig(t)) {
if (is16b(t)) len *= 2; // 16 bit LEDs
if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem
@@ -201,7 +207,6 @@
if (maxM >= 10000) { //ESP32 RMT uses double buffer?
mul = 2;
}
if (d.Sf.LD.checked) dbl = len * ch; // double buffering
}
return len * ch * mul + dbl;
}
@@ -212,7 +217,6 @@
let busMA = 0;
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
const abl = d.Sf.ABL.checked;
maxB = oMaxB; // TODO make sure we start with all possible buses
let setPinConfig = (n,t) => {
let p0d = "GPIO:";
let p1d = "";
@@ -250,6 +254,7 @@
}
// enable/disable LED fields
let dC = 0; // count of digital buses (for parallel I2S)
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
LTs.forEach((s,i)=>{
if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options)
@@ -257,6 +262,7 @@
var n = s.name.substring(2);
var t = parseInt(s.value);
memu += getMem(t, n); // calc memory
dC += (isDig(t) && !isD2P(t));
setPinConfig(n,t);
gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings
if (change) { // did we change LED type?
@@ -269,7 +275,7 @@
gRGBW |= hasW(t); // RGBW checkbox
gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
@@ -287,16 +293,20 @@
d.Sf.CR.checked = false;
}
// update start indexes, max values, calculate current, etc
let sameType = 0;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
nList.forEach((LC,i)=>{
let nm = LC.name.substring(0,2); // field name
let n = LC.name.substring(2); // bus number
let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
if (isDig(t)) {
if (sameType == 0) sameType = t; // first bus type
else if (sameType != t) sameType = -1; // different bus type
}
// do we have a led count field
if (nm=="LC") {
let c = parseInt(LC.value,10); //get LED count
if (c > 300 && i < 8) maxB = oMaxB - Math.max(maxD-7,0); //TODO: hard limit for buses when using ESP32 parallel I2S
if (!customStarts || !startsDirty[n]) gId("ls"+n).value=sLC; //update start value
if (!customStarts || !startsDirty[n]) gId("ls"+n).value = sLC; //update start value
gId("ls"+n).disabled = !customStarts; //enable/disable field editing
if (c) {
let s = parseInt(gId("ls"+n).value); //start value
@@ -350,6 +360,13 @@
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
}
});
if (is32() || isS2() || isS3()) {
if (maxLC > 600 || dC < 2 || sameType <= 0) {
d.Sf["PR"].checked = false;
gId("prl").classList.add("hide");
} else
gId("prl").classList.remove("hide");
} else d.Sf["PR"].checked = false;
// distribute ABL current if not using PPL
enPPL(sDI);
@@ -379,10 +396,15 @@
gId('psu').innerHTML = s;
gId('psu2').innerHTML = s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
// show/hide FPS warning messages
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';
}
function lastEnd(i) {
if (i-- < 1) return 0;
var s = String.fromCharCode((i<10?48:55)+i);
var s = chrID(i);
v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value);
var t = parseInt(d.getElementsByName("LT"+s)[0].value);
if (isPWM(t)) v = 1; //PWM busses
@@ -405,7 +427,7 @@
});
if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return;
var s = String.fromCharCode((i<10?48:55)+i);
var s = chrID(i);
if (n==1) {
// npm run build has trouble minimizing spaces inside string
@@ -461,14 +483,17 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
if (type.t != undefined && type.t != "") {
opt.setAttribute('data-type', type.t);
}
sel.appendChild(opt);
sel.appendChild(opt);
}
}
});
enLA(d.Sf["LAsel"+s],s); // update LED mA
// disable inappropriate LED types
let sel = d.getElementsByName("LT"+s)[0]
if (i >= maxB || digitalB >= maxD) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P()
let sel = d.getElementsByName("LT"+s)[0];
// 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses)
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
}
@@ -488,7 +513,7 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
function addCOM(start=0,len=1,co=0) {
var i = gEBCN("com_entry").length;
if (i >= maxCO) return;
var s = String.fromCharCode((i<10?48:55)+i);
var s = chrID(i);
var b = `<div class="com_entry">
<hr class="sml">
${i+1}: Start: <input type="number" name="XS${s}" id="xs${s}" class="l starts" min="0" max="65535" value="${start}" oninput="UI();" required="">&nbsp;
@@ -542,7 +567,7 @@ Swap: <select id="xw${s}" name="XW${s}">
function addBtn(i,p,t) {
var c = gId("btns").innerHTML;
var s = String.fromCharCode((i<10?48:55)+i);
var s = chrID(i);
c += `Button ${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="BE${s}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
@@ -566,8 +591,10 @@ Swap: <select id="xw${s}" name="XW${s}">
function checkSi() { //on load, checks whether there are custom start fields
var cs = false;
for (var i=1; i < gEBCN("iST").length; i++) {
var v = parseInt(gId("ls"+(i-1)).value) + parseInt(gN("LC"+(i-1)).value);
if (v != parseInt(gId("ls"+i).value)) {cs = true; startsDirty[i] = true;}
var s = chrID(i);
var p = chrID(i-1); // cover edge case 'A' previous char being '9'
var v = parseInt(gId("ls"+p).value) + parseInt(gN("LC"+p).value);
if (v != parseInt(gId("ls"+s).value)) {cs = true; startsDirty[i] = true;}
}
if (gId("ls0") && parseInt(gId("ls0").value) != 0) {cs = true; startsDirty[0] = true;}
gId("si").checked = cs;
@@ -596,10 +623,10 @@ Swap: <select id="xw${s}" name="XW${s}">
function receivedText(e) {
let lines = e.target.result;
var c = JSON.parse(lines);
var c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
for (var i=0; i<10; i++) addLEDs(-1);
for (var i=0; i<oMaxB+maxV; i++) addLEDs(-1);
var l = c.hw.led;
l.ins.forEach((v,i,a)=>{
addLEDs(1);
@@ -771,8 +798,11 @@ Swap: <select id="xw${s}" name="XW${s}">
</div>
</div>
<h3>Hardware setup</h3>
<div id="mLC">LED outputs:</div>
<hr class="sml">
<div id="mLC">
LED outputs:<br>
<hr class="sml">
<i>Only last output can be changed. Remove to edit others.</i><br>
</div>
<button type="button" id="+" onclick="addLEDs(1,false)">+</button>
<button type="button" id="-" onclick="addLEDs(-1,false)">-</button><br>
LED memory usage: <span id="m0">0</span> / <span id="m1">?</span> B<br>
@@ -782,6 +812,7 @@ Swap: <select id="xw${s}" name="XW${s}">
Use less than <span id="wreason">800 LEDs per output</span> for the best experience!<br>
</div>
<hr class="sml">
<div id="prl" class="hide">Use parallel I2S: <input type="checkbox" name="PR"><br></div>
Make a segment for each output: <input type="checkbox" name="MS"><br>
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br>
Use global LED buffer: <input type="checkbox" name="LD" onchange="UI()"><br>
@@ -869,7 +900,10 @@ Swap: <select id="xw${s}" name="XW${s}">
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
</select><br>
Target refresh rate: <input type="number" class="s" min="1" max="120" name="FR" required> FPS
Target refresh rate: <input type="number" class="s" min="0" max="250" name="FR" oninput="UI()" required> FPS
<div id="fpsNone" class="warn" style="display: none;">&#9888; Unlimited FPS Mode is experimental &#9888;<br></div>
<div id="fpsHigh" class="warn" style="display: none;">&#9888; High FPS Mode is experimental.<br></div>
<div id="fpsWarn" class="warn" style="display: none;">Please <a class="lnk" href="sec#backup">backup</a> WLED configuration and presets first!<br></div>
<hr class="sml">
<div id="cfg">Config template: <input type="file" name="data2" accept=".json"><button type="button" class="sml" onclick="loadCfg(d.Sf.data2)">Apply</button><br></div>
<hr>

View File

@@ -57,7 +57,7 @@
<h3>Software Update</h3>
<button type="button" onclick="U()">Manual OTA Update</button><br>
Enable ArduinoOTA: <input type="checkbox" name="AO">
<hr>
<hr id="backup">
<h3>Backup & Restore</h3>
<div class="warn">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.<br>
@@ -73,7 +73,7 @@
A huge thank you to everyone who helped me create WLED!<br><br>
(c) 2016-2024 Christian Schwinne <br>
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">EUPL v1.2 license</a></i><br><br>
Server message: <span class="sip"> Response error! </span><hr>
Installed version: <span class="sip">WLED ##VERSION##</span><hr>
<div id="toast"></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>

View File

@@ -6,7 +6,26 @@
<script>
function B() { window.history.back(); }
function U() { document.getElementById("uf").style.display="none";document.getElementById("msg").style.display="block"; }
function GetV() {/*injected values here*/}
function GetV() {
// Fetch device info via JSON API instead of compiling it in
fetch('/json/info')
.then(response => response.json())
.then(data => {
document.querySelector('.installed-version').textContent = `${data.brand} ${data.ver} (${data.vid})`;
document.querySelector('.release-name').textContent = data.release;
// TODO - assemble update URL
// TODO - can this be done at build time?
if (data.arch == "esp8266") {
toggle('rev');
}
})
.catch(error => {
console.log('Could not fetch device info:', error);
// Fallback to compiled-in value if API call fails
document.querySelector('.installed-version').textContent = 'Unknown';
document.querySelector('.release-name').textContent = 'Unknown';
});
}
</script>
<style>
@import url("style.css");
@@ -16,11 +35,15 @@
<body onload="GetV()">
<h2>WLED Software Update</h2>
<form method='POST' action='./update' id='uf' enctype='multipart/form-data' onsubmit="U()">
Installed version: <span class="sip">##VERSION##</span><br>
Installed version: <span class="sip installed-version">Loading...</span><br>
Release: <span class="sip release-name">Loading...</span><br>
Download the latest binary:&nbsp;<a href="https://github.com/Aircoookie/WLED/releases" target="_blank"
style="vertical-align: text-bottom; display: inline-flex;">
<img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br>
<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!</button><br>
<button type="button" onclick="B()">Back</button>
</form>

View File

@@ -416,7 +416,7 @@ void prepareArtnetPollReply(ArtPollReply *reply) {
reply->reply_port = ARTNET_DEFAULT_PORT;
char * numberEnd = versionString;
char * numberEnd = (char*) versionString; // strtol promises not to try to edit this.
reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10);
numberEnd++;
reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10);

View File

@@ -1,3 +1,4 @@
#pragma once
#ifndef WLED_FCN_DECLARE_H
#define WLED_FCN_DECLARE_H
@@ -24,7 +25,7 @@ void IRAM_ATTR touchButtonISR();
//cfg.cpp
bool deserializeConfig(JsonObject doc, bool fromFS = false);
void deserializeConfigFromFS();
bool deserializeConfigFromFS();
bool deserializeConfigSec();
void serializeConfig();
void serializeConfigSec();
@@ -230,7 +231,8 @@ void deletePreset(byte index);
bool getPresetName(byte index, String& name);
//remote.cpp
void handleRemote(uint8_t *data, size_t len);
void handleWiZdata(uint8_t *incomingData, size_t len);
void handleRemote();
//set.cpp
bool isAsterisksOnly(const char* str, byte maxLen);
@@ -372,7 +374,7 @@ void userLoop();
//util.cpp
int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); // getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
bool getBoolVal(JsonVariant elem, bool dflt);
bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255);
size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val);

View File

@@ -195,9 +195,9 @@ void onHueData(void* arg, AsyncClient* client, void *data, size_t len)
{
switch(hueColormode)
{
case 1: if (hueX != hueXLast || hueY != hueYLast) colorXYtoRGB(hueX,hueY,col); hueXLast = hueX; hueYLast = hueY; break;
case 2: if (hueHue != hueHueLast || hueSat != hueSatLast) colorHStoRGB(hueHue,hueSat,col); hueHueLast = hueHue; hueSatLast = hueSat; break;
case 3: if (hueCt != hueCtLast) colorCTtoRGB(hueCt,col); hueCtLast = hueCt; break;
case 1: if (hueX != hueXLast || hueY != hueYLast) colorXYtoRGB(hueX,hueY,colPri); hueXLast = hueX; hueYLast = hueY; break;
case 2: if (hueHue != hueHueLast || hueSat != hueSatLast) colorHStoRGB(hueHue,hueSat,colPri); hueHueLast = hueHue; hueSatLast = hueSat; break;
case 3: if (hueCt != hueCtLast) colorCTtoRGB(hueCt,colPri); hueCtLast = hueCt; break;
}
}
hueReceived = true;

View File

@@ -91,19 +91,20 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
}
}
uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
uint16_t of = seg.offset;
uint8_t soundSim = elem["si"] | seg.soundSim;
uint8_t map1D2D = elem["m12"] | seg.map1D2D;
if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps
seg.map1D2D = constrain(map1D2D, 0, 7);
seg.soundSim = constrain(soundSim, 0, 3);
uint8_t set = elem[F("set")] | seg.set;
seg.set = constrain(set, 0, 3);
uint16_t grp = elem["grp"] | seg.grouping;
uint16_t spc = elem[F("spc")] | seg.spacing;
uint16_t of = seg.offset;
uint8_t soundSim = elem["si"] | seg.soundSim;
uint8_t map1D2D = elem["m12"] | seg.map1D2D;
uint8_t set = elem[F("set")] | seg.set;
bool selected = getBoolVal(elem["sel"], seg.selected);
bool reverse = getBoolVal(elem["rev"], seg.reverse);
bool mirror = getBoolVal(elem["mi"] , seg.mirror);
#ifndef WLED_DISABLE_2D
bool reverse_y = getBoolVal(elem["rY"] , seg.reverse_y);
bool mirror_y = getBoolVal(elem["mY"] , seg.mirror_y);
bool transpose = getBoolVal(elem[F("tp")], seg.transpose);
#endif
int len = 1;
if (stop > start) len = stop - start;
@@ -117,7 +118,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (stop > start && of > len -1) of = len -1;
// update segment (delete if necessary)
seg.setUp(start, stop, grp, spc, of, startY, stopY); // strip needs to be suspended for this to work without issues
seg.setGeometry(start, stop, grp, spc, of, startY, stopY, map1D2D); // strip needs to be suspended for this to work without issues
if (newSeg) seg.refreshLightCapabilities(); // fix for #3403
@@ -206,47 +207,30 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
}
#endif
//seg.map1D2D = constrain(map1D2D, 0, 7); // done in setGeometry()
seg.set = constrain(set, 0, 3);
seg.soundSim = constrain(soundSim, 0, 3);
seg.selected = selected;
seg.reverse = reverse;
seg.mirror = mirror;
#ifndef WLED_DISABLE_2D
bool reverse = seg.reverse;
bool mirror = seg.mirror;
#endif
seg.selected = getBoolVal(elem["sel"], seg.selected);
seg.reverse = getBoolVal(elem["rev"], seg.reverse);
seg.mirror = getBoolVal(elem["mi"] , seg.mirror);
#ifndef WLED_DISABLE_2D
bool reverse_y = seg.reverse_y;
bool mirror_y = seg.mirror_y;
seg.reverse_y = getBoolVal(elem["rY"] , seg.reverse_y);
seg.mirror_y = getBoolVal(elem["mY"] , seg.mirror_y);
seg.transpose = getBoolVal(elem[F("tp")], seg.transpose);
if (seg.is2D() && seg.map1D2D == M12_pArc && (reverse != seg.reverse || reverse_y != seg.reverse_y || mirror != seg.mirror || mirror_y != seg.mirror_y)) seg.fill(BLACK); // clear entire segment (in case of Arc 1D to 2D expansion)
seg.reverse_y = reverse_y;
seg.mirror_y = mirror_y;
seg.transpose = transpose;
#endif
byte fx = seg.mode;
byte last = strip.getModeCount();
// partial fix for #3605
if (!elem["fx"].isNull() && elem["fx"].is<const char*>()) {
const char *tmp = elem["fx"].as<const char *>();
if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form
}
// end fix
if (getVal(elem["fx"], &fx, 0, last)) { //load effect ('r' random, '~' inc/dec, 0-255 exact value, 5~10r pick random between 5 & 10)
if (getVal(elem["fx"], &fx, 0, strip.getModeCount())) {
if (!presetId && currentPlaylist>=0) unloadPlaylist();
if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]);
}
//getVal also supports inc/decrementing and random
getVal(elem["sx"], &seg.speed);
getVal(elem["ix"], &seg.intensity);
uint8_t pal = seg.palette;
last = strip.getPaletteCount();
if (!elem["pal"].isNull() && elem["pal"].is<const char*>()) {
const char *tmp = elem["pal"].as<const char *>();
if (strlen(tmp) > 3 && (strchr(tmp,'r') || strchr(tmp,'~') != strrchr(tmp,'~'))) last = 0; // we have "X~Y(r|[w]~[-])" form
}
if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments
if (getVal(elem["pal"], &pal, 0, last)) seg.setPalette(pal);
if (getVal(elem["pal"], &pal, 0, strip.getPaletteCount())) seg.setPalette(pal);
}
getVal(elem["c1"], &seg.custom1);
@@ -406,35 +390,38 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
int it = 0;
JsonVariant segVar = root["seg"];
if (!segVar.isNull()) strip.suspend();
if (segVar.is<JsonObject>())
{
int id = segVar["id"] | -1;
//if "seg" is not an array and ID not specified, apply to all selected/checked segments
if (id < 0) {
//apply all selected segments
//bool didSet = false;
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
Segment &sg = strip.getSegment(s);
if (sg.isActive() && sg.isSelected()) {
deserializeSegment(segVar, s, presetId);
//didSet = true;
if (!segVar.isNull()) {
// we may be called during strip.service() so we must not modify segments while effects are executing
strip.suspend();
const unsigned long waitUntil = millis() + strip.getFrameTime();
while (strip.isServicing() && millis() < waitUntil) delay(1); // wait until frame is over
#ifdef WLED_DEBUG
if (millis() >= waitUntil) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing."));
#endif
if (segVar.is<JsonObject>()) {
int id = segVar["id"] | -1;
//if "seg" is not an array and ID not specified, apply to all selected/checked segments
if (id < 0) {
//apply all selected segments
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
Segment &sg = strip.getSegment(s);
if (sg.isActive() && sg.isSelected()) {
deserializeSegment(segVar, s, presetId);
}
}
} else {
deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID
}
//TODO: not sure if it is good idea to change first active but unselected segment
//if (!didSet) deserializeSegment(segVar, strip.getMainSegmentId(), presetId);
} else {
deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID
size_t deleted = 0;
JsonArray segs = segVar.as<JsonArray>();
for (JsonObject elem : segs) {
if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++;
}
if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments
}
} else {
size_t deleted = 0;
JsonArray segs = segVar.as<JsonArray>();
for (JsonObject elem : segs) {
if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++;
}
if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments
strip.resume();
}
strip.resume();
UsermodManager::readFromJsonState(root);
@@ -467,7 +454,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset);
} else if (!root["ps"].isNull()) {
// we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call)
if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 0, 0) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) {
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)

View File

@@ -9,10 +9,10 @@ void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelect
void setValuesFromSegment(uint8_t s)
{
Segment& seg = strip.getSegment(s);
col[0] = R(seg.colors[0]);
col[1] = G(seg.colors[0]);
col[2] = B(seg.colors[0]);
col[3] = W(seg.colors[0]);
colPri[0] = R(seg.colors[0]);
colPri[1] = G(seg.colors[0]);
colPri[2] = B(seg.colors[0]);
colPri[3] = W(seg.colors[0]);
colSec[0] = R(seg.colors[1]);
colSec[1] = G(seg.colors[1]);
colSec[2] = B(seg.colors[1]);
@@ -39,7 +39,7 @@ void applyValuesToSelectedSegs()
if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);}
if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);}
uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]);
uint32_t col0 = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]);
uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]);
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);}
if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);}
@@ -73,8 +73,8 @@ byte scaledBri(byte in)
//applies global brightness
void applyBri() {
if (!realtimeMode || !arlsForceMaxBri)
{
if (!(realtimeMode && arlsForceMaxBri)) {
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
strip.setBrightness(scaledBri(briT));
}
}
@@ -85,6 +85,7 @@ void applyFinalBri() {
briOld = bri;
briT = bri;
applyBri();
strip.trigger();
}
@@ -146,7 +147,6 @@ void stateUpdated(byte callMode) {
transitionStartTime = millis();
} else {
applyFinalBri();
strip.trigger();
}
}
@@ -157,14 +157,14 @@ void updateInterfaces(uint8_t callMode)
sendDataWs();
lastInterfaceUpdate = millis();
interfaceUpdateCallMode = 0; //disable further updates
interfaceUpdateCallMode = CALL_MODE_INIT; //disable further updates
if (callMode == CALL_MODE_WS_SEND) return;
#ifndef WLED_DISABLE_ALEXA
if (espalexaDevice != nullptr && callMode != CALL_MODE_ALEXA) {
espalexaDevice->setValue(bri);
espalexaDevice->setColor(col[0], col[1], col[2]);
espalexaDevice->setColor(colPri[0], colPri[1], colPri[2]);
}
#endif
#ifndef WLED_DISABLE_MQTT
@@ -221,7 +221,7 @@ void handleNightlight()
nightlightDelayMs = (unsigned)(nightlightDelayMins*60000);
nightlightActiveOld = true;
briNlT = bri;
for (unsigned i=0; i<4; i++) colNlT[i] = col[i]; // remember starting color
for (unsigned i=0; i<4; i++) colNlT[i] = colPri[i]; // remember starting color
if (nightlightMode == NL_MODE_SUN)
{
//save current
@@ -246,7 +246,7 @@ void handleNightlight()
bri = briNlT + ((nightlightTargetBri - briNlT)*nper);
if (nightlightMode == NL_MODE_COLORFADE) // color fading only is enabled with "NF=2"
{
for (unsigned i=0; i<4; i++) col[i] = colNlT[i]+ ((colSec[i] - colNlT[i])*nper); // fading from actual color to secondary color
for (unsigned i=0; i<4; i++) colPri[i] = colNlT[i]+ ((colSec[i] - colNlT[i])*nper); // fading from actual color to secondary color
}
colorUpdated(CALL_MODE_NO_NOTIFY);
}

View File

@@ -99,7 +99,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
//Prefix is stripped from the topic at this point
if (strcmp_P(topic, PSTR("/col")) == 0) {
colorFromDecOrHexString(col, payloadStr);
colorFromDecOrHexString(colPri, payloadStr);
colorUpdated(CALL_MODE_DIRECT_CHANGE);
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
if (requestJSONBufferLock(15)) {
@@ -165,7 +165,7 @@ void publishMqtt()
strcat_P(subuf, PSTR("/g"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)
sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2]));
sprintf_P(s, PSTR("#%06X"), (colPri[3] << 24) | (colPri[0] << 16) | (colPri[1] << 8) | (colPri[2]));
strlcpy(subuf, mqttDeviceTopic, 33);
strcat_P(subuf, PSTR("/c"));
mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263)

View File

@@ -207,6 +207,7 @@ void WiFiEvent(WiFiEvent_t event)
break;
#endif
default:
DEBUG_PRINTF_P(PSTR("Network event: %d\n"), (int)event);
break;
}
}

252
wled00/ota_update.cpp Normal file
View File

@@ -0,0 +1,252 @@
#include "ota_update.h"
#include "wled.h"
#ifdef ESP32
#include <esp_ota_ops.h>
#endif
// Platform-specific metadata locations
#ifdef ESP32
constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata
#define UPDATE_ERROR errorString
#elif defined(ESP8266)
constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset
#define UPDATE_ERROR getErrorString
#endif
constexpr size_t METADATA_SEARCH_RANGE = 512; // bytes
/**
* 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 errorMessageLen Maximum length of error message buffer
* @return true if OTA should proceed, false if it should be blocked
*/
static bool validateOTA(const uint8_t* binaryData, size_t dataSize, char* errorMessage, size_t errorMessageLen) {
// Clear error message
if (errorMessage && errorMessageLen > 0) {
errorMessage[0] = '\0';
}
// Try to extract WLED structure directly from binary data
wled_metadata_t extractedDesc;
bool hasDesc = findWledMetadata(binaryData, dataSize, &extractedDesc);
if (hasDesc) {
return shouldAllowOTA(extractedDesc, errorMessage, errorMessageLen);
} else {
// No custom description - this could be a legacy binary
if (errorMessage && errorMessageLen > 0) {
strncpy_P(errorMessage, PSTR("This firmware file is missing compatibility metadata."), errorMessageLen - 1);
errorMessage[errorMessageLen - 1] = '\0';
}
return false;
}
}
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;
};
static void endOTA(AsyncWebServerRequest *request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
request->_tempObject = nullptr;
DEBUG_PRINTF_P(PSTR("EndOTA %x --> %x (%d)\n"), (uintptr_t)request,(uintptr_t) context, context ? context->uploadComplete : 0);
if (context) {
if (context->updateStarted) { // We initialized the update
// We use Update.end() because not all forms of Update() support an abort.
// If the upload is incomplete, Update.end(false) should error out.
if (Update.end(context->uploadComplete)) {
// Update successful!
doReboot = true;
context->needsRestart = false;
}
}
if (context->needsRestart) {
strip.resume();
UsermodManager::onUpdateBegin(false);
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
#endif
}
delete context;
}
};
static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
{
#ifdef ESP8266
Update.runAsync(true);
#endif
if (Update.isRunning()) {
request->send(503);
setOTAReplied(request);
return false;
}
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().disableWatchdog();
#endif
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
strip.suspend();
strip.resetSegments(); // free as much memory as you can
context->needsRestart = true;
DEBUG_PRINTF_P(PSTR("OTA Update Start, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context);
auto skipValidationParam = request->getParam("skipValidation", true);
if (skipValidationParam && (skipValidationParam->value() == "1")) {
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)) {
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;
}
// Create an OTA context object on an AsyncWebServerRequest
// Returns true if successful, false on failure.
bool initOTA(AsyncWebServerRequest *request) {
// Allocate update context
UpdateContext* context = new (std::nothrow) UpdateContext {};
if (context) {
request->_tempObject = context;
request->onDisconnect([=]() { endOTA(request); }); // ensures we restart on failure
};
DEBUG_PRINTF_P(PSTR("OTA Update init, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context);
return (context != nullptr);
}
void setOTAReplied(AsyncWebServerRequest *request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return;
context->replySent = true;
};
// Returns pointer to error message, or nullptr if OTA was successful.
std::pair<bool, String> getOTAResult(AsyncWebServerRequest* request) {
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return { true, F("OTA context unexpectedly missing") };
if (context->replySent) return { false, {} };
if (context->errorMessage.length()) return { true, context->errorMessage };
if (context->updateStarted) {
// Release the OTA context now.
endOTA(request);
if (Update.hasError()) {
return { true, Update.UPDATE_ERROR() };
} else {
return { true, {} };
}
}
// Should never happen
return { true, F("Internal software failure") };
}
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal)
{
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
if (!context) return;
//DEBUG_PRINTF_P(PSTR("HandleOTAData: %d %d %d\n"), index, len, isFinal);
if (context->replySent || (context->errorMessage.length())) return;
if (index == 0) {
if (!beginOTA(request, context)) return;
}
// Perform validation if we haven't done it yet and we have reached the metadata offset
if (!context->releaseCheckPassed && (index+len) > METADATA_OFFSET) {
// Current chunk contains the metadata offset
size_t availableDataAfterOffset = (index + len) - METADATA_OFFSET;
DEBUG_PRINTF_P(PSTR("OTA metadata check: %d in buffer, %d received, %d available\n"), context->releaseMetadataBuffer.size(), len, availableDataAfterOffset);
if (availableDataAfterOffset >= METADATA_SEARCH_RANGE) {
// We have enough data to validate, one way or another
const uint8_t* search_data = data;
size_t search_len = len;
// If we have saved data, use that instead
if (context->releaseMetadataBuffer.size()) {
// Add this data
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
search_data = context->releaseMetadataBuffer.data();
search_len = context->releaseMetadataBuffer.size();
}
// Do the checking
char errorMessage[128];
bool OTA_ok = validateOTA(search_data, search_len, errorMessage, sizeof(errorMessage));
// Release buffer if there was one
context->releaseMetadataBuffer = decltype(context->releaseMetadataBuffer){};
if (!OTA_ok) {
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
context->errorMessage = errorMessage;
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
return;
} else {
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
context->releaseCheckPassed = true;
}
} else {
// Store the data we just got for next pass
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
}
}
// Check if validation was still pending (shouldn't happen normally)
// 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) {
DEBUG_PRINTLN(F("OTA Update End"));
// Upload complete
context->uploadComplete = true;
}
}

52
wled00/ota_update.h Normal file
View File

@@ -0,0 +1,52 @@
// WLED OTA update interface
#include <Arduino.h>
#ifdef ESP8266
#include <Updater.h>
#else
#include <Update.h>
#endif
#pragma once
// Platform-specific metadata locations
#ifdef ESP32
#define BUILD_METADATA_SECTION ".rodata_custom_desc"
#elif defined(ESP8266)
#define BUILD_METADATA_SECTION ".ver_number"
#endif
class AsyncWebServerRequest;
/**
* Create an OTA context object on an AsyncWebServerRequest
* @param request Pointer to web request object
* @return true if allocation was successful, false if not
*/
bool initOTA(AsyncWebServerRequest *request);
/**
* Indicate to the OTA subsystem that a reply has already been generated
* @param request Pointer to web request object
*/
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.
*/
std::pair<bool, String> getOTAResult(AsyncWebServerRequest *request);
/**
* Process a block of OTA data. This is a passthrough of an ArUploadHandlerFunction.
* Requires that initOTA be called on the handler object before any work will be done.
* @param request Pointer to web request object
* @param index Offset in to uploaded file
* @param data New data bytes
* @param len Length of new data bytes
* @param isFinal Indicates that this is the last block
* @return bool indicating if a reply is necessary; string with error message if the update failed.
*/
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal);

View File

@@ -214,8 +214,20 @@ bool PinManager::isPinOk(byte gpio, bool output)
// JTAG: GPIO39-42 are usually used for inline debugging
// GPIO46 is input only and pulled down
#else
if (gpio > 5 && gpio < 12) return false; //SPI flash pins
if (strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0 && (gpio == 16 || gpio == 17)) return false; // PICO-D4: gpio16+17 are in use for onboard SPI FLASH
if ((strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0) || // this is the correct identifier, but....
(strncmp_P(PSTR("ESP32-PICO-D2"), ESP.getChipModel(), 13) == 0)) { // https://github.com/espressif/arduino-esp32/issues/10683
// this chip has 4 MB of internal Flash and different packaging, so available pins are different!
if (((gpio > 5) && (gpio < 9)) || (gpio == 11))
return false;
} else {
// for classic ESP32 (non-mini) modules, these are the SPI flash pins
if (gpio > 5 && gpio < 12) return false; //SPI flash pins
}
if (((strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0) ||
(strncmp_P(PSTR("ESP32-U4WDH"), ESP.getChipModel(), 11) == 0))
&& (gpio == 16 || gpio == 17)) return false; // PICO-D4/U4WDH: gpio16+17 are in use for onboard SPI FLASH
if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO)
#endif
if (output) return digitalPinCanOutput(gpio);

View File

@@ -164,6 +164,11 @@ void handlePresets()
DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset);
#if defined(ARDUINO_ARCH_ESP32S3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
unsigned long start = millis();
while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
#endif
#ifdef ARDUINO_ARCH_ESP32
if (tmpPreset==255 && tmpRAMbuffer!=nullptr) {
deserializeJson(*pDoc,tmpRAMbuffer);

View File

@@ -1,6 +1,8 @@
#include "wled.h"
#ifndef WLED_DISABLE_ESPNOW
#define ESPNOW_BUSWAIT_TIMEOUT 24 // one frame timeout to wait for bus to finish updating
#define NIGHT_MODE_DEACTIVATED -1
#define NIGHT_MODE_BRIGHTNESS 5
@@ -38,6 +40,7 @@ typedef struct WizMoteMessageStructure {
static uint32_t last_seq = UINT32_MAX;
static int brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED;
static int16_t ESPNowButton = -1; // set in callback if new button value is received
// Pulled from the IR Remote logic but reduced to 10 steps with a constant of 3
static const byte brightnessSteps[] = {
@@ -121,6 +124,9 @@ static bool remoteJson(int button)
sprintf_P(objKey, PSTR("\"%d\":"), button);
unsigned long start = millis();
while (strip.isUpdating() && millis()-start < ESPNOW_BUSWAIT_TIMEOUT) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
// attempt to read command from remote.json
readObjectFromFile(PSTR("/remote.json"), objKey, pDoc);
JsonObject fdo = pDoc->as<JsonObject>();
@@ -176,7 +182,7 @@ static bool remoteJson(int button)
}
// Callback function that will be executed when data is received
void handleRemote(uint8_t *incomingData, size_t len) {
void handleWiZdata(uint8_t *incomingData, size_t len) {
message_structure_t *incoming = reinterpret_cast<message_structure_t *>(incomingData);
if (strcmp(last_signal_src, linked_remote) != 0) {
@@ -202,8 +208,15 @@ void handleRemote(uint8_t *incomingData, size_t len) {
DEBUG_PRINT(F("] button: "));
DEBUG_PRINTLN(incoming->button);
if (!remoteJson(incoming->button))
switch (incoming->button) {
ESPNowButton = incoming->button; // save state, do not process in callback (can cause glitches)
last_seq = cur_seq;
}
// process ESPNow button data (acesses FS, should not be called while update to avoid glitches)
void handleRemote() {
if(ESPNowButton >= 0) {
if (!remoteJson(ESPNowButton))
switch (ESPNowButton) {
case WIZMOTE_BUTTON_ON : setOn(); break;
case WIZMOTE_BUTTON_OFF : setOff(); break;
case WIZMOTE_BUTTON_ONE : presetWithFallback(1, FX_MODE_STATIC, 0); break;
@@ -219,9 +232,10 @@ void handleRemote(uint8_t *incomingData, size_t len) {
case WIZ_SMART_BUTTON_BRIGHT_DOWN : brightnessDown(); break;
default: break;
}
last_seq = cur_seq;
}
ESPNowButton = -1;
}
#else
void handleRemote(uint8_t *incomingData, size_t len) {}
void handleRemote() {}
#endif

View File

@@ -134,15 +134,17 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.correctWB = request->hasArg(F("CCT"));
strip.cctFromRgb = request->hasArg(F("CR"));
cctICused = request->hasArg(F("IC"));
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
Bus::setCCTBlend(request->arg(F("CB")).toInt());
Bus::setGlobalAWMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt());
useGlobalLedBuffer = request->hasArg(F("LD"));
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
useParallelI2S = request->hasArg(F("PR"));
#endif
bool busesChanged = false;
for (int s = 0; s < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; s++) {
int offset = s < 10 ? 48 : 55;
int offset = s < 10 ? '0' : 'A' - 10;
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order
@@ -161,7 +163,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
break;
}
for (int i = 0; i < 5; i++) {
lp[1] = offset+i;
lp[1] = '0'+i;
if (!request->hasArg(lp)) break;
pins[i] = (request->arg(lp).length() > 0) ? request->arg(lp).toInt() : 255;
}
@@ -208,8 +210,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
type |= request->hasArg(rf) << 7; // off refresh override
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax);
busConfigs.push_back(std::move(BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax)));
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
@@ -217,7 +218,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// we will not bother with pre-allocating ColorOrderMappings vector
BusManager::getColorOrderMap().reset();
for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
int offset = s < 10 ? 48 : 55;
int offset = s < 10 ? '0' : 'A' - 10;
char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED
char xc[4] = "XC"; xc[2] = offset+s; xc[3] = 0; //strip length
char xo[4] = "XO"; xo[2] = offset+s; xo[3] = 0; //color order
@@ -256,7 +257,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
int offset = i < 10 ? 48 : 55;
int offset = i < 10 ? '0' : 'A' - 10;
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
int hw_btn_pin = request->arg(bt).toInt();
@@ -1184,7 +1185,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
}
// you can add more if you need
// global col[], effectCurrent, ... are updated in stateChanged()
// global colPri[], effectCurrent, ... are updated in stateChanged()
if (!apply) return true; // when called by JSON API, do not call colorUpdated() here
pos = req.indexOf(F("&NN")); //do not send UDP notifications this time

View File

@@ -300,7 +300,7 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (!receiveSegmentOptions) {
DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY);
selseg.setGeometry(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY);
strip.resume();
continue; // we do receive bounds, but not options
}
@@ -342,12 +342,12 @@ void parseNotifyPacket(uint8_t *udpIn) {
if (receiveSegmentBounds) {
DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY);
selseg.setGeometry(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY);
strip.resume();
} else {
DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]);
strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case"
selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY);
selseg.setGeometry(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY);
strip.resume();
}
}
@@ -979,7 +979,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
// handle WiZ Mote data
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {
handleRemote(data, len);
handleWiZdata(data, len);
return;
}

View File

@@ -52,7 +52,7 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
*val = atoi(str);
}
//getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
if (elem.is<int>()) {
if (elem < 0) return false; //ignore e.g. {"ps":-1}
@@ -60,8 +60,12 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
return true;
} else if (elem.is<const char*>()) {
const char* str = elem;
size_t len = strnlen(str, 12);
if (len == 0 || len > 10) return false;
size_t len = strnlen(str, 14);
if (len == 0 || len > 12) return false;
// fix for #3605 & #4346
// ignore vmin and vmax and use as specified in API
if (len > 3 && (strchr(str,'r') || strchr(str,'~') != strrchr(str,'~'))) vmax = vmin = 0; // we have "X~Y(r|~[w][-][Z])" form
// end fix
parseNumber(str, val, vmin, vmax);
return true;
}

View File

@@ -1,6 +1,7 @@
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
#include "wled.h"
#include "wled_ethernet.h"
#include "ota_update.h"
#include <Arduino.h>
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
@@ -84,6 +85,9 @@ void WLED::loop()
#ifndef WLED_DISABLE_INFRARED
handleIR();
#endif
#ifndef WLED_DISABLE_ESPNOW
handleRemote();
#endif
#ifndef WLED_DISABLE_ALEXA
handleAlexa();
#endif
@@ -161,9 +165,9 @@ void WLED::loop()
if (millis() - heapTime > 15000) {
uint32_t heap = ESP.getFreeHeap();
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
forceReconnect = true;
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
strip.resetSegments(); // remove all but one segments from memory
if (!Update.isRunning()) forceReconnect = true;
} else if (heap < MIN_HEAP_SIZE) {
DEBUG_PRINTLN(F("Heap low, purging segments."));
strip.purgeSegments();
@@ -179,49 +183,10 @@ void WLED::loop()
DEBUG_PRINTLN(F("Re-init busses."));
bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses)
BusManager::removeAll();
unsigned mem = 0;
// determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
bool useParallel = false;
#if defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP32S2) && !defined(ARDUINO_ARCH_ESP32S3) && !defined(ARDUINO_ARCH_ESP32C3)
unsigned digitalCount = 0;
unsigned maxLedsOnBus = 0;
unsigned maxChannels = 0;
for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
if (busConfigs[i] == nullptr) break;
if (!Bus::isDigital(busConfigs[i]->type)) continue;
if (!Bus::is2Pin(busConfigs[i]->type)) {
digitalCount++;
unsigned channels = Bus::getNumberOfChannels(busConfigs[i]->type);
if (busConfigs[i]->count > maxLedsOnBus) maxLedsOnBus = busConfigs[i]->count;
if (channels > maxChannels) maxChannels = channels;
}
}
DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount);
// we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0
if (maxLedsOnBus <= 300 && digitalCount > 5) {
DEBUG_PRINTF_P(PSTR("Switching to parallel I2S."));
useParallel = true;
BusManager::useParallelOutput();
mem = BusManager::memUsage(maxChannels, maxLedsOnBus, 8); // use alternate memory calculation (hse to be used *after* useParallelOutput())
}
#endif
// create buses/outputs
for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
if (busConfigs[i] == nullptr || (!useParallel && i > 10)) break;
if (useParallel && i < 8) {
// if for some unexplained reason the above pre-calculation was wrong, update
unsigned memT = BusManager::memUsage(*busConfigs[i]); // includes x8 memory allocation for parallel I2S
if (memT > mem) mem = memT; // if we have unequal LED count use the largest
} else
mem += BusManager::memUsage(*busConfigs[i]); // includes global buffer
if (mem <= MAX_LED_MEMORY) BusManager::add(*busConfigs[i]);
delete busConfigs[i];
busConfigs[i] = nullptr;
}
strip.finalizeInit(); // also loads default ledmap if present
BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005
strip.finalizeInit(); // will create buses and also load default ledmap if present
if (aligned) strip.makeAutoSegments();
else strip.fixInvalidSegments();
BusManager::setBrightness(scaledBri(bri)); // fix re-initialised bus' brightness #4005 and #4824
doSerializeConfig = true;
}
if (loadLedmap >= 0) {
@@ -455,7 +420,7 @@ void WLED::setup()
multiWiFi.push_back(WiFiConfig(CLIENT_SSID,CLIENT_PASS)); // initialise vector with default WiFi
DEBUG_PRINTLN(F("Reading config"));
deserializeConfigFromFS();
bool needsCfgSave = deserializeConfigFromFS();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
#if defined(STATUSLED) && STATUSLED>=0
@@ -475,13 +440,12 @@ void WLED::setup()
UsermodManager::setup();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
if (needsCfgSave) serializeConfig(); // usermods required new parameters; need to wait for strip to be initialised #4752
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0)
showWelcomePage = true;
WiFi.persistent(false);
#ifdef WLED_USE_ETHERNET
WiFi.onEvent(WiFiEvent);
#endif
WiFi.mode(WIFI_STA); // enable scanning
findWiFi(true); // start scanning for available WiFi-s
@@ -498,7 +462,7 @@ void WLED::setup()
#endif
// fill in unique mdns default
if (strcmp(cmDNS, "x") == 0) sprintf_P(cmDNS, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6);
if (strcmp(cmDNS, DEFAULT_MDNS_NAME) == 0) sprintf_P(cmDNS, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6);
#ifndef WLED_DISABLE_MQTT
if (mqttDeviceTopic[0] == 0) sprintf_P(mqttDeviceTopic, PSTR("wled/%*s"), 6, escapedMac.c_str() + 6);
if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6);
@@ -571,6 +535,7 @@ void WLED::beginStrip()
strip.makeAutoSegments();
strip.setBrightness(0);
strip.setShowCallback(handleOverlayDraw);
doInitBusses = false;
if (turnOnAtBoot) {
if (briS > 0) bri = briS;
@@ -578,11 +543,12 @@ void WLED::beginStrip()
} else {
// fix for #3196
if (bootPreset > 0) {
bool oldTransition = fadeTransition; // workaround if transitions are enabled
fadeTransition = false; // ignore transitions temporarily
strip.setColor(0, BLACK); // set all segments black
fadeTransition = oldTransition; // restore transitions
col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated()
// set all segments black (no transition)
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment &seg = strip.getSegment(i);
if (seg.isActive()) seg.colors[0] = BLACK;
}
colPri[0] = colPri[1] = colPri[2] = colPri[3] = 0; // needed for colorUpdated()
}
briLast = briS; bri = 0;
strip.fill(BLACK);
@@ -781,7 +747,7 @@ int8_t WLED::findWiFi(bool doScan) {
void WLED::initConnection()
{
DEBUG_PRINTLN(F("initConnection() called."));
DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000);
#ifdef WLED_ENABLE_WEBSOCKETS
ws.onEvent(wsEvent);
@@ -796,6 +762,7 @@ void WLED::initConnection()
#endif
WiFi.disconnect(true); // close old connections
delay(5); // wait for hardware to be ready
#ifdef ESP8266
WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N);
#endif
@@ -825,9 +792,7 @@ void WLED::initConnection()
if (WLED_WIFI_CONFIGURED) {
showWelcomePage = false;
DEBUG_PRINT(F("Connecting to "));
DEBUG_PRINT(multiWiFi[selectedWiFi].clientSSID);
DEBUG_PRINTLN(F("..."));
DEBUG_PRINTF_P(PSTR("Connecting to %s...\n"), multiWiFi[selectedWiFi].clientSSID);
// convert the "serverDescription" into a valid DNS hostname (alphanumeric)
char hostname[25];
@@ -926,7 +891,8 @@ void WLED::handleConnection()
{
static bool scanDone = true;
static byte stacO = 0;
unsigned long now = millis();
const unsigned long now = millis();
const unsigned long nowS = now/1000;
const bool wifiConfigured = WLED_WIFI_CONFIGURED;
// ignore connection handling if WiFi is configured and scan still running
@@ -935,7 +901,7 @@ void WLED::handleConnection()
return;
if (lastReconnectAttempt == 0 || forceReconnect) {
DEBUG_PRINTLN(F("Initial connect or forced reconnect."));
DEBUG_PRINTF_P(PSTR("Initial connect or forced reconnect (@ %lus).\n"), nowS);
selectedWiFi = findWiFi(); // find strongest WiFi
initConnection();
interfacesInited = false;
@@ -955,8 +921,7 @@ void WLED::handleConnection()
#endif
if (stac != stacO) {
stacO = stac;
DEBUG_PRINT(F("Connected AP clients: "));
DEBUG_PRINTLN(stac);
DEBUG_PRINTF_P(PSTR("Connected AP clients: %d\n"), (int)stac);
if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected
if (stac)
WiFi.disconnect(); // disable search so that AP can work
@@ -979,6 +944,7 @@ void WLED::handleConnection()
initConnection();
interfacesInited = false;
scanDone = true;
return;
}
//send improv failed 6 seconds after second init attempt (24 sec. after provisioning)
if (improvActive > 2 && now - lastReconnectAttempt > 6000) {
@@ -987,13 +953,13 @@ void WLED::handleConnection()
}
if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) {
if (improvActive == 2) improvActive = 3;
DEBUG_PRINTLN(F("Last reconnect too old."));
DEBUG_PRINTF_P(PSTR("Last reconnect (%lus) too old (@ %lus).\n"), lastReconnectAttempt/1000, nowS);
if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list
initConnection();
}
if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) {
if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) {
DEBUG_PRINTLN(F("Not connected AP."));
DEBUG_PRINTF_P(PSTR("Not connected AP (@ %lus).\n"), nowS);
initAP(); // start AP only within first 5min
}
}
@@ -1003,7 +969,7 @@ void WLED::handleConnection()
dnsServer.stop();
WiFi.softAPdisconnect(true);
apActive = false;
DEBUG_PRINTLN(F("Temporary AP disabled."));
DEBUG_PRINTF_P(PSTR("Temporary AP disabled (@ %lus).\n"), nowS);
}
}
} else if (!interfacesInited) { //newly connected

View File

@@ -3,12 +3,11 @@
/*
Main sketch, global variable declarations
@title WLED project sketch
@version 0.15.0-b7
@author Christian Schwinne
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2410270
#define VERSION 2508020
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@@ -188,6 +187,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#include "pin_manager.h"
#include "bus_manager.h"
#include "FX.h"
#include "wled_metadata.h"
#ifndef CLIENT_SSID
#define CLIENT_SSID DEFAULT_CLIENT_SSID
@@ -260,16 +260,6 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define STRINGIFY(X) #X
#define TOSTRING(X) STRINGIFY(X)
#ifndef WLED_VERSION
#define WLED_VERSION dev
#endif
#ifndef WLED_RELEASE_NAME
#define WLED_RELEASE_NAME "Custom"
#endif
// Global Variable definitions
WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION));
WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
#define WLED_CODENAME "Kōsen"
// AP and OTA default passwords (for maximum security change them!)
@@ -368,7 +358,7 @@ WLED_GLOBAL bool noWifiSleep _INIT(false);
WLED_GLOBAL bool force802_3g _INIT(false);
#endif // WLED_SAVE_RAM
#ifdef ARDUINO_ARCH_ESP32
#if defined(LOLIN_WIFI_FIX) && (defined(ARDUINO_ARCH_ESP32C3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32S3))
#if defined(LOLIN_WIFI_FIX) && (defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_8_5dBm);
#else
WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_19_5dBm);
@@ -395,6 +385,9 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load
WLED_GLOBAL bool useGlobalLedBuffer _INIT(false); // double buffering disabled on ESP8266
#else
WLED_GLOBAL bool useGlobalLedBuffer _INIT(true); // double buffering enabled on ESP32
#ifndef CONFIG_IDF_TARGET_ESP32C3
WLED_GLOBAL bool useParallelI2S _INIT(true); // parallel I2S for ESP32
#endif
#endif
#ifdef WLED_USE_IC_CCT
WLED_GLOBAL bool cctICused _INIT(true); // CCT IC used (Athom 15W bulbs)
@@ -405,7 +398,7 @@ WLED_GLOBAL bool gammaCorrectCol _INIT(true); // use gamma correction on col
WLED_GLOBAL bool gammaCorrectBri _INIT(false); // use gamma correction on brightness
WLED_GLOBAL float gammaCorrectVal _INIT(2.8f); // gamma correction value
WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color.
WLED_GLOBAL byte colPri[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. colPri[] should be updated if you want to change the color.
WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color
WLED_GLOBAL byte nightlightTargetBri _INIT(0); // brightness after nightlight is over
@@ -695,10 +688,10 @@ WLED_GLOBAL bool receiveNotificationPalette _INIT(true); // apply palet
WLED_GLOBAL bool receiveSegmentOptions _INIT(false); // apply segment options
WLED_GLOBAL bool receiveSegmentBounds _INIT(false); // apply segment bounds (start, stop, offset)
WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP/Hyperion realtime
WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API
WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote
WLED_GLOBAL bool notifyDirect _INIT(true); // send notification if change via UI or HTTP API
WLED_GLOBAL bool notifyButton _INIT(true); // send if updated by button or infrared remote
WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa
WLED_GLOBAL bool notifyHue _INIT(true); // send notification if Hue light changes
WLED_GLOBAL bool notifyHue _INIT(false); // send notification if Hue light changes
#endif
// effects
@@ -884,7 +877,7 @@ WLED_GLOBAL bool e131NewData _INIT(false);
// led fx library object
WLED_GLOBAL BusManager busses _INIT(BusManager());
WLED_GLOBAL WS2812FX strip _INIT(WS2812FX());
WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after
WLED_GLOBAL std::vector<BusConfig> busConfigs; //temporary, to remember values from network callback until after
WLED_GLOBAL bool doInitBusses _INIT(false);
WLED_GLOBAL int8_t loadLedmap _INIT(-1);
WLED_GLOBAL uint8_t currentLedmap _INIT(0);

157
wled00/wled_metadata.cpp Normal file
View File

@@ -0,0 +1,157 @@
#include "ota_update.h"
#include "wled.h"
#include "wled_metadata.h"
#ifndef WLED_VERSION
#warning WLED_VERSION was not set - using default value of 'dev'
#define WLED_VERSION dev
#endif
#ifndef WLED_RELEASE_NAME
#warning WLED_RELEASE_NAME was not set - using default value of 'Custom'
#define WLED_RELEASE_NAME "Custom"
#endif
#ifndef WLED_REPO
// No warning for this one: integrators are not always on GitHub
#define WLED_REPO "unknown"
#endif
constexpr uint32_t WLED_CUSTOM_DESC_MAGIC = 0x57535453; // "WSTS" (WLED System Tag Structure)
constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 1;
// Compile-time validation that release name doesn't exceed maximum length
static_assert(sizeof(WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN,
"WLED_RELEASE_NAME exceeds maximum length of WLED_RELEASE_NAME_MAX_LEN characters");
/**
* DJB2 hash function (C++11 compatible constexpr)
* Used for compile-time hash computation to validate structure contents
* Recursive for compile time: not usable at runtime due to stack depth
*
* Note that this only works on strings; there is no way to produce a compile-time
* hash of a struct in C++11 without explicitly listing all the struct members.
* So for now, we hash only the release name. This suffices for a "did you find
* valid structure" check.
*
*/
constexpr uint32_t djb2_hash_constexpr(const char* str, uint32_t hash = 5381) {
return (*str == '\0') ? hash : djb2_hash_constexpr(str + 1, ((hash << 5) + hash) + *str);
}
/**
* Runtime DJB2 hash function for validation
*/
inline uint32_t djb2_hash_runtime(const char* str) {
uint32_t hash = 5381;
while (*str) {
hash = ((hash << 5) + hash) + *str++;
}
return hash;
}
// ------------------------------------
// GLOBAL VARIABLES
// ------------------------------------
// Structure instantiation for this build
const wled_metadata_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUILD_DESCRIPTION = {
WLED_CUSTOM_DESC_MAGIC, // magic
WLED_CUSTOM_DESC_VERSION, // version
TOSTRING(WLED_VERSION),
WLED_RELEASE_NAME, // release_name
std::integral_constant<uint32_t, djb2_hash_constexpr(WLED_RELEASE_NAME)>::value, // hash - computed at compile time; integral_constant enforces this
};
static const char repoString_s[] PROGMEM = WLED_REPO;
const __FlashStringHelper* repoString = FPSTR(repoString_s);
static const char productString_s[] PROGMEM = WLED_PRODUCT_NAME;
const __FlashStringHelper* productString = FPSTR(productString_s);
static const char brandString_s [] PROGMEM = WLED_BRAND;
const __FlashStringHelper* brandString = FPSTR(brandString_s);
/**
* Extract WLED custom description structure from binary
* @param binaryData Pointer to binary file data
* @param dataSize Size of binary data in bytes
* @param extractedDesc Buffer to store extracted custom description structure
* @return true if structure was found and extracted, false otherwise
*/
bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_t* extractedDesc) {
if (!binaryData || !extractedDesc || dataSize < sizeof(wled_metadata_t)) {
return false;
}
for (size_t offset = 0; offset <= dataSize - sizeof(wled_metadata_t); offset++) {
const wled_metadata_t* custom_desc = (const wled_metadata_t*)(binaryData + offset);
// Check for magic number
if (custom_desc->magic == WLED_CUSTOM_DESC_MAGIC) {
// Found potential match, validate version
if (custom_desc->desc_version != WLED_CUSTOM_DESC_VERSION) {
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"),
offset, custom_desc->desc_version);
continue;
}
// Validate hash using runtime function
uint32_t expected_hash = djb2_hash_runtime(custom_desc->release_name);
if (custom_desc->hash != expected_hash) {
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
continue;
}
// Valid structure found - copy entire structure
memcpy(extractedDesc, custom_desc, sizeof(wled_metadata_t));
DEBUG_PRINTF_P(PSTR("Extracted WLED structure at offset %u: '%s'\n"),
offset, extractedDesc->release_name);
return true;
}
}
DEBUG_PRINTLN(F("No WLED custom description found in binary"));
return false;
}
/**
* 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 errorMessageLen Maximum length of error message buffer
* @return true if OTA should proceed, false if it should be blocked
*/
bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen) {
// Clear error message
if (errorMessage && errorMessageLen > 0) {
errorMessage[0] = '\0';
}
// Validate compatibility using extracted release name
// We make a stack copy so we can print it safely
char safeFirmwareRelease[WLED_RELEASE_NAME_MAX_LEN];
strncpy(safeFirmwareRelease, firmwareDescription.release_name, WLED_RELEASE_NAME_MAX_LEN - 1);
safeFirmwareRelease[WLED_RELEASE_NAME_MAX_LEN - 1] = '\0';
if (strlen(safeFirmwareRelease) == 0) {
return false;
}
if (strncmp_P(safeFirmwareRelease, releaseString, WLED_RELEASE_NAME_MAX_LEN) != 0) {
if (errorMessage && errorMessageLen > 0) {
snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware compatibility mismatch: current='%s', uploaded='%s'."),
releaseString, safeFirmwareRelease);
errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination
}
return false;
}
// TODO: additional checks go here
return true;
}

61
wled00/wled_metadata.h Normal file
View File

@@ -0,0 +1,61 @@
/*
WLED build metadata
Manages and exports information about the current WLED build.
*/
#pragma once
#include <cstdint>
#include <string.h>
#include <WString.h>
#define WLED_VERSION_MAX_LEN 48
#define WLED_RELEASE_NAME_MAX_LEN 48
/**
* WLED Custom Description Structure
* This structure is embedded in platform-specific sections at an approximately
* fixed offset in ESP32/ESP8266 binaries, where it can be found and validated
* by the OTA process.
*/
typedef struct {
uint32_t magic; // Magic number to identify WLED custom description
uint32_t desc_version; // Structure version for future compatibility
char wled_version[WLED_VERSION_MAX_LEN];
char release_name[WLED_RELEASE_NAME_MAX_LEN]; // Release name (null-terminated)
uint32_t hash; // Structure sanity check
} __attribute__((packed)) wled_metadata_t;
// Global build description
extern const wled_metadata_t WLED_BUILD_DESCRIPTION;
// Convenient metdata pointers
#define versionString (WLED_BUILD_DESCRIPTION.wled_version) // Build version, WLED_VERSION
#define releaseString (WLED_BUILD_DESCRIPTION.release_name) // Release name, WLED_RELEASE_NAME
extern const __FlashStringHelper* repoString; // Github repository (if available)
extern const __FlashStringHelper* productString; // Product, WLED_PRODUCT_NAME -- deprecated, use WLED_RELEASE_NAME
extern const __FlashStringHelper* brandString ; // Brand
// Metadata analysis functions
/**
* Extract WLED custom description structure from binary data
* @param binaryData Pointer to binary file data
* @param dataSize Size of binary data in bytes
* @param extractedDesc Buffer to store extracted custom description structure
* @return true if structure was found and extracted, false otherwise
*/
bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_t* extractedDesc);
/**
* Check if OTA should be allowed based on release compatibility
* @param firmwareDescription Pointer to firmware description
* @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
*/
bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen);

View File

@@ -1,5 +1,8 @@
#include "wled.h"
#ifndef WLED_DISABLE_OTA
#include "ota_update.h"
#endif
#include "html_ui.h"
#include "html_settings.h"
#include "html_other.h"
@@ -16,6 +19,7 @@ static const char s_redirecting[] PROGMEM = "Redirecting...";
static const char s_content_enc[] PROGMEM = "Content-Encoding";
static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security settings!";
static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!";
static const char s_rebooting [] PROGMEM = "Rebooting now...";
static const char s_notimplemented[] PROGMEM = "Not implemented";
static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char _common_js[] PROGMEM = "/common.js";
@@ -375,49 +379,40 @@ void initServer()
});
server.on(_update, HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveSettings(request, true); // handle PIN page POST request
return;
}
if (otaLock) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
return;
}
if (Update.hasError()) {
serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254);
} else {
serveMessage(request, 200, F("Update successful!"), F("Rebooting..."), 131);
doReboot = true;
}
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if (!correctPIN || otaLock) return;
if(!index){
DEBUG_PRINTLN(F("OTA Update Start"));
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().disableWatchdog();
#endif
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
lastEditTime = millis(); // make sure PIN does not lock during update
strip.suspend();
#ifdef ESP8266
strip.resetSegments(); // free as much memory as you can
Update.runAsync(true);
#endif
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
}
if(!Update.hasError()) Update.write(data, len);
if(final){
if(Update.end(true)){
DEBUG_PRINTLN(F("Update Success"));
} else {
DEBUG_PRINTLN(F("Update Failed"));
strip.resume();
UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
#endif
if (request->_tempObject) {
auto ota_result = getOTAResult(request);
if (ota_result.first) {
if (ota_result.second.length() > 0) {
serveMessage(request, 500, F("Update failed!"), ota_result.second, 254);
} else {
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
}
}
} else {
// No context structure - something's gone horribly wrong
serveMessage(request, 500, F("Update failed!"), F("Internal server fault"), 254);
}
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
if (index == 0) {
// Allocate the context structure
if (!initOTA(request)) {
return; // Error will be dealt with after upload in response handler, above
}
// Privilege checks
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
setOTAReplied(request);
return;
};
if (otaLock) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
setOTAReplied(request);
return;
}
}
handleOTAData(request, index, data, len, isFinal);
});
#else
server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){

View File

@@ -11,7 +11,7 @@ void XML_response(Print& dest)
dest.printf_P(PSTR("<?xml version=\"1.0\" ?><vs><ac>%d</ac>"), (nightlightActive && nightlightMode > NL_MODE_SET) ? briT : bri);
for (int i = 0; i < 3; i++)
{
dest.printf_P(PSTR("<cl>%d</cl>"), col[i]);
dest.printf_P(PSTR("<cl>%d</cl>"), colPri[i]);
}
for (int i = 0; i < 3; i++)
{
@@ -20,13 +20,14 @@ void XML_response(Print& dest)
dest.printf_P(PSTR("<ns>%d</ns><nr>%d</nr><nl>%d</nl><nf>%d</nf><nd>%d</nd><nt>%d</nt><fx>%d</fx><sx>%d</sx><ix>%d</ix><fp>%d</fp><wv>%d</wv><ws>%d</ws><ps>%d</ps><cy>%d</cy><ds>%s%s</ds><ss>%d</ss></vs>"),
notifyDirect, receiveGroups!=0, nightlightActive, nightlightMode > NL_MODE_SET, nightlightDelayMins,
nightlightTargetBri, effectCurrent, effectSpeed, effectIntensity, effectPalette,
strip.hasWhiteChannel() ? col[3] : -1, colSec[3], currentPreset, currentPlaylist >= 0,
strip.hasWhiteChannel() ? colPri[3] : -1, colSec[3], currentPreset, currentPlaylist >= 0,
serverDescription, realtimeMode ? PSTR(" (live)") : "",
strip.getFirstSelectedSegId()
);
}
static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) {
static void extractPin(Print& settingsScript, JsonObject &obj, const char *key)
{
if (obj[key].is<JsonArray>()) {
JsonArray pins = obj[key].as<JsonArray>();
for (JsonVariant pv : pins) {
@@ -37,6 +38,22 @@ static void extractPin(Print& settingsScript, JsonObject &obj, const char *key)
}
}
void fillWLEDVersion(char *buf, size_t len)
{
if (!buf || len == 0) return;
snprintf_P(buf,len,PSTR("WLED %s (%d)<br>\\\"%s\\\"<br>(Processor: %s)"),
versionString,
VERSION,
releaseString,
#if defined(ARDUINO_ARCH_ESP32)
ESP.getChipModel()
#else
"ESP8266"
#endif
);
}
// print used pins by scanning JsonObject (1 level deep)
static void fillUMPins(Print& settingsScript, JsonObject &mods)
{
@@ -72,7 +89,8 @@ static void fillUMPins(Print& settingsScript, JsonObject &mods)
}
}
void appendGPIOinfo(Print& settingsScript) {
void appendGPIOinfo(Print& settingsScript)
{
settingsScript.print(F("d.um_p=[-1")); // has to have 1 element
if (i2c_sda > -1 && i2c_scl > -1) {
settingsScript.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl);
@@ -289,12 +307,13 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps());
printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode());
printSetFormCheckbox(settingsScript,PSTR("LD"),useGlobalLedBuffer);
printSetFormCheckbox(settingsScript,PSTR("PR"),BusManager::hasParallelOutput()); // get it from bus manager not global variable
unsigned sumMa = 0;
for (int s = 0; s < BusManager::getNumBusses(); s++) {
Bus* bus = BusManager::getBus(s);
if (bus == nullptr) continue;
int offset = s < 10 ? 48 : 55;
const Bus* bus = BusManager::getBus(s);
if (!bus || !bus->isOk()) break; // should not happen but for safety
int offset = s < 10 ? '0' : 'A' - 10;
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order
@@ -312,7 +331,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
uint8_t pins[5];
int nPins = bus->getPins(pins);
for (int i = 0; i < nPins; i++) {
lp[1] = offset+i;
lp[1] = '0'+i;
if (PinManager::isPinOk(pins[i]) || bus->isVirtual()) printSetFormValue(settingsScript,lp,pins[i]);
}
printSetFormValue(settingsScript,lc,bus->getLength());
@@ -579,7 +598,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("OW"),wifiLock);
printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled);
char tmp_buf[128];
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION);
fillWLEDVersion(tmp_buf,sizeof(tmp_buf));
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription);
}
@@ -631,22 +650,6 @@ void getSettingsJS(byte subPage, Print& settingsScript)
UsermodManager::appendConfigData(settingsScript);
}
if (subPage == SUBPAGE_UPDATE) // update
{
char tmp_buf[128];
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s<br>%s<br>(%s build %d)"),
versionString,
releaseString,
#if defined(ARDUINO_ARCH_ESP32)
ESP.getChipModel(),
#else
"esp8266",
#endif
VERSION);
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
}
if (subPage == SUBPAGE_2D) // 2D matrices
{
printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix);