From d2d56ebbd28114cccd72eba497be66ea079b554c Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 15 Nov 2024 20:27:30 +0000 Subject: [PATCH 001/168] 0.15.0-rc1 --- CHANGELOG.md | 3 +++ package-lock.json | 6 +++--- package.json | 2 +- wled00/wled.h | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c570ac1f7e..c92fccbb96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## WLED changelog +#### Build 2411150 +- WLED 0.15.0-rc1 release + #### Build 2410270 - WLED 0.15.0-b7 release - Re-license the WLED project from MIT to EUPL (#4194 by @Aircoookie) diff --git a/package-lock.json b/package-lock.json index 0f73bff0c4..a10327b6c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "wled", - "version": "0.15.0-b7", + "version": "0.15.0-rc1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b7", + "version": "0.15.0-rc1", "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" } }, "node_modules/@jridgewell/gen-mapping": { diff --git a/package.json b/package.json index 9d095c82cc..0bf666fa55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b7", + "version": "0.15.0-rc1", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/wled.h b/wled00/wled.h index 2b3a77d24b..42f640721b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2410270 +#define VERSION 2411150 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 568d2edd9623e0b36926d561902f1c86a480adae Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 23 Nov 2024 16:31:07 +0000 Subject: [PATCH 002/168] Remove TOSTRING for releaseString and add quotes to WLED_RELEASE_NAME --- platformio.ini | 67 +++++++++++++++++++++++++++++++++++--------------- wled00/wled.h | 2 +- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/platformio.ini b/platformio.ini index 9628722aa4..a330318e32 100644 --- a/platformio.ini +++ b/platformio.ini @@ -345,7 +345,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_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder @@ -354,13 +354,13 @@ extends = env:nodemcuv2 ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D -D USERMOD_AUDIOREACTIVE [env:esp8266_2m] @@ -369,7 +369,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" lib_deps = ${esp8266.lib_deps} [env:esp8266_2m_compat] @@ -377,12 +377,12 @@ extends = env:esp8266_2m ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\" -D USERMOD_AUDIOREACTIVE [env:esp01_1m_full] @@ -391,7 +391,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} @@ -400,12 +400,12 @@ extends = env:esp01_1m_full ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA -D USERMOD_AUDIOREACTIVE ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM @@ -414,7 +414,7 @@ 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_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} ${esp32.AR_lib_deps} @@ -426,7 +426,7 @@ 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_8M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -442,7 +442,7 @@ 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_16M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -458,7 +458,7 @@ board_build.flash_mode = dio ;platform = ${esp32.platform} ;platform_packages = ${esp32.platform_packages} ;build_unflags = ${common.build_unflags} -;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET ; ${esp32.AR_build_flags} ;lib_deps = ${esp32.lib_deps} ; ${esp32.AR_lib_deps} @@ -473,7 +473,7 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 ; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} @@ -489,7 +489,7 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D DATA_PINS=25 ${esp32.AR_build_flags} @@ -503,7 +503,7 @@ platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = ${esp32.default_partitions} -build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\" -D WLED_WATCHDOG_TIMEOUT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB @@ -520,7 +520,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -543,7 +543,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -556,6 +556,33 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +[env:esp32S3_wroom2] +;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 +;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +board = esp32s3camlcd ;; this is the only standard board with "opi_opi" +board_build.arduino.memory_type = opi_opi +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -D LEDPIN=38 -D DATA_PINS=38 ;; buildin WS2812b LED + -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + -D WLED_DEBUG + ${esp32.AR_build_flags} + -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +monitor_filters = esp32_exception_decoder + [env:esp32s3_4M_qspi] ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM @@ -563,7 +590,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this @@ -584,7 +611,7 @@ board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = qio board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 +build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 diff --git a/wled00/wled.h b/wled00/wled.h index 42f640721b..771b1c0485 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -269,7 +269,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); -WLED_GLOBAL char releaseString[] _INIT(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[] +WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // somehow this will not work if using "const char releaseString[] #define WLED_CODENAME "Kōsen" // AP and OTA default passwords (for maximum security change them!) From 1082c85789aa4638171e468a2bc02cb1a9089687 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 23 Nov 2024 16:37:54 +0000 Subject: [PATCH 003/168] Fix WLED_RELEASE_NAME=ESP32-S3_WROOM-2 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index a330318e32..fe02213ff2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -565,7 +565,7 @@ board = esp32s3camlcd ;; this is the only standard board with "opi_opi" board_build.arduino.memory_type = opi_opi upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") From 7a58c69a8064f25f5b01a980fec51b1066f724c3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 23 Nov 2024 16:46:41 +0000 Subject: [PATCH 004/168] Stip \" from WLED_RELEASE_NAME --- pio-scripts/output_bins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 633654008d..bdd6fc941d 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -19,7 +19,7 @@ def _create_dirs(dirs=["map", "release", "firmware"]): os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME").replace("\\\"", "") if release_name: version = _get_cpp_define_value(env, "WLED_VERSION") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") From beb709dc8ff7437875ccd01cb2aad84a3d0499de Mon Sep 17 00:00:00 2001 From: netmindz Date: Sun, 24 Nov 2024 13:45:26 +0000 Subject: [PATCH 005/168] Merge pull request #4312 from willmmiles/mixed-led-crash Defer calling begin() on buses --- wled00/FX_fcn.cpp | 11 +++-------- wled00/bus_manager.cpp | 4 ++-- wled00/bus_manager.h | 3 ++- wled00/bus_wrapper.h | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 949b6a932b..7b4fb094b9 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1286,14 +1286,9 @@ void WS2812FX::finalizeInit() { _isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; - #ifdef ESP8266 - // why do we need to reinitialise GPIO3??? - //if (!bus->isDigital() || bus->is2Pin()) continue; - //uint8_t pins[5]; - //if (!bus->getPins(pins)) continue; - //BusDigital* bd = static_cast(bus); - //if (pins[0] == 3) bd->reinit(); - #endif + + // This must be done after all buses have been created, as some kinds (parallel I2S) interact + bus->begin(); } Segment::maxWidth = _length; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5b948b9c41..8281211c1d 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -410,7 +410,7 @@ std::vector BusDigital::getLEDTypes() { }; } -void BusDigital::reinit() { +void BusDigital::begin() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins); } @@ -910,7 +910,7 @@ void BusManager::on() { if (busses[i]->isDigital() && busses[i]->getPins(pins)) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { BusDigital *bus = static_cast(busses[i]); - bus->reinit(); + bus->begin(); break; } } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index e96b9de714..ecebc120e8 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -79,6 +79,7 @@ class Bus { virtual ~Bus() {} //throw the bus under the bus + virtual void begin() {}; virtual void show() = 0; virtual bool canShow() const { return true; } virtual void setStatusPixel(uint32_t c) {} @@ -213,7 +214,7 @@ class BusDigital : public Bus { uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } - void reinit(); + void begin() override; void cleanup(); static std::vector getLEDTypes(); diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 84c32f46bc..7e6e74d85d 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -597,7 +597,7 @@ class PolyBus { case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break; case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break; } - begin(busPtr, busType, pins, clock_kHz); + return busPtr; } From 8a18555ae4bea1b8fa32af6a96be6dbdc1e4774a Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:04:21 +0100 Subject: [PATCH 006/168] Merge pull request #4243 from MoonModules/AC_0_15_S3-WROOM2 Add support for ESP32-S3 WROOM-2 (solves #4099) --- platformio.ini | 27 +++++++++++++++++++++++++++ wled00/pin_manager.cpp | 7 ++++++- wled00/wled.cpp | 6 ++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9628722aa4..6d4aa1dc13 100644 --- a/platformio.ini +++ b/platformio.ini @@ -556,6 +556,33 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +[env:esp32S3_wroom2] +;; For ESP32-S3 WROOM-2, a.k.a. ESP32-S3 DevKitC-1 v1.1 +;; with >= 16MB FLASH and >= 8MB PSRAM (memory_type: opi_opi) +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +board = esp32s3camlcd ;; this is the only standard board with "opi_opi" +board_build.arduino.memory_type = opi_opi +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 + -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 + -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -DBOARD_HAS_PSRAM + -D LEDPIN=38 -D DATA_PINS=38 ;; buildin WS2812b LED + -D BTNPIN=0 -D RLYPIN=16 -D IRPIN=17 -D AUDIOPIN=-1 + -D WLED_DEBUG + ${esp32.AR_build_flags} + -D SR_DMTYPE=1 -D I2S_SDPIN=13 -D I2S_CKPIN=14 -D I2S_WSPIN=15 -D MCLK_PIN=4 ;; I2S mic +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} + +board_build.partitions = ${esp32.extreme_partitions} +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +monitor_filters = esp32_exception_decoder + [env:esp32s3_4M_qspi] ;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi) board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 793b5440c8..0d4c2ad5cb 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -201,7 +201,12 @@ bool PinManager::isPinOk(byte gpio, bool output) if (gpio > 18 && gpio < 21) return false; // 19 + 20 = USB-JTAG. Not recommended for other uses. #endif if (gpio > 21 && gpio < 33) return false; // 22 to 32: not connected + SPI FLASH - if (gpio > 32 && gpio < 38) return !psramFound(); // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM + #if CONFIG_ESPTOOLPY_FLASHMODE_OPI // 33-37: never available if using _octal_ Flash (opi_opi) + if (gpio > 32 && gpio < 38) return false; + #endif + #if CONFIG_SPIRAM_MODE_OCT // 33-37: not available if using _octal_ PSRAM (qio_opi), but free to use on _quad_ PSRAM (qio_qspi) + if (gpio > 32 && gpio < 38) return !psramFound(); + #endif // 38 to 48 are for general use. Be careful about straping pins GPIO45 and GPIO46 - these may be pull-up or pulled-down on your board. #elif defined(CONFIG_IDF_TARGET_ESP32S2) // strapping pins: 0, 45 & 46 diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 13d43218af..d6a39a399f 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -380,6 +380,12 @@ void WLED::setup() case FM_QOUT: DEBUG_PRINT(F("(QOUT)"));break; case FM_DIO: DEBUG_PRINT(F("(DIO)")); break; case FM_DOUT: DEBUG_PRINT(F("(DOUT)"));break; + #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_ESPTOOLPY_FLASHMODE_OPI + case FM_FAST_READ: DEBUG_PRINT(F("(OPI)")); break; + #else + case FM_FAST_READ: DEBUG_PRINT(F("(fast_read)")); break; + #endif + case FM_SLOW_READ: DEBUG_PRINT(F("(slow_read)")); break; default: break; } #endif From 5d38acd787536ea1d5a4c0ea904ce4fee75b7d34 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 24 Nov 2024 21:04:50 +0000 Subject: [PATCH 007/168] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c92fccbb96..b1db933736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Build 2411150 - WLED 0.15.0-rc1 release +- Add support for esp32S3_wroom2 (#4243 by @softhack007) +- Fix mixed LED SK6812 and ws2812b booloop (#4301 by @willmmiles) #### Build 2410270 - WLED 0.15.0-b7 release From 2f6fa66f4dc8579ee1ed9d86d41d7fa93933740f Mon Sep 17 00:00:00 2001 From: netmindz Date: Mon, 25 Nov 2024 23:00:56 +0000 Subject: [PATCH 008/168] Merge pull request #4309 from netmindz/release-name-fix Fix release name macro expansion --- pio-scripts/output_bins.py | 5 ++-- platformio.ini | 42 +++++++++++++++++----------------- platformio_override.sample.ini | 2 +- wled00/wled.h | 4 ++-- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 633654008d..4d1594d843 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -19,8 +19,9 @@ def _create_dirs(dirs=["map", "release", "firmware"]): os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") - if release_name: + 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") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") release_gz_file = release_file + ".gz" diff --git a/platformio.ini b/platformio.ini index 6d4aa1dc13..fe02213ff2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -345,7 +345,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_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder @@ -354,13 +354,13 @@ extends = env:nodemcuv2 ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D -D USERMOD_AUDIOREACTIVE [env:esp8266_2m] @@ -369,7 +369,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" lib_deps = ${esp8266.lib_deps} [env:esp8266_2m_compat] @@ -377,12 +377,12 @@ extends = env:esp8266_2m ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\" -D USERMOD_AUDIOREACTIVE [env:esp01_1m_full] @@ -391,7 +391,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} @@ -400,12 +400,12 @@ extends = env:esp01_1m_full ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA -D USERMOD_AUDIOREACTIVE ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM @@ -414,7 +414,7 @@ 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_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} ${esp32.AR_lib_deps} @@ -426,7 +426,7 @@ 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_8M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -442,7 +442,7 @@ 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_16M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -458,7 +458,7 @@ board_build.flash_mode = dio ;platform = ${esp32.platform} ;platform_packages = ${esp32.platform_packages} ;build_unflags = ${common.build_unflags} -;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET ; ${esp32.AR_build_flags} ;lib_deps = ${esp32.lib_deps} ; ${esp32.AR_lib_deps} @@ -473,7 +473,7 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 ; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} @@ -489,7 +489,7 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D DATA_PINS=25 ${esp32.AR_build_flags} @@ -503,7 +503,7 @@ platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = ${esp32.default_partitions} -build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\" -D WLED_WATCHDOG_TIMEOUT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB @@ -520,7 +520,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -543,7 +543,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -565,7 +565,7 @@ board = esp32s3camlcd ;; this is the only standard board with "opi_opi" board_build.arduino.memory_type = opi_opi upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -590,7 +590,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this @@ -611,7 +611,7 @@ board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = qio board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 +build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 8e5fdf0030..a4dea6677c 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -37,7 +37,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. ; ; Set a release name that may be used to distinguish required binary for flashing -; -D WLED_RELEASE_NAME=ESP32_MULTI_USREMODS +; -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\" ; ; disable specific features ; -D WLED_DISABLE_OTA diff --git a/wled00/wled.h b/wled00/wled.h index 2b3a77d24b..fcbc119784 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -264,12 +264,12 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #define WLED_VERSION dev #endif #ifndef WLED_RELEASE_NAME - #define WLED_RELEASE_NAME dev_release + #define WLED_RELEASE_NAME "Custom" #endif // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); -WLED_GLOBAL char releaseString[] _INIT(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[] +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!) From 2bee2793ef43fa7fb98cf50f5eb2170eb4e1da76 Mon Sep 17 00:00:00 2001 From: netmindz Date: Mon, 25 Nov 2024 23:00:56 +0000 Subject: [PATCH 009/168] Merge pull request #4309 from netmindz/release-name-fix Fix release name macro expansion --- tools/cdata.js | 1 + wled00/FX.cpp | 7 ++++--- wled00/src/dependencies/dmx/SparkFunDMX.cpp | 4 ++-- wled00/xml.cpp | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/cdata.js b/tools/cdata.js index d65573a8ea..c5d3c6aa52 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -101,6 +101,7 @@ function adoptVersionAndRepo(html) { async function minify(str, type = "plain") { const options = { collapseWhitespace: true, + conservativeCollapse: true, // preserve spaces in text collapseBooleanAttributes: true, collapseInlineTagWhitespace: true, minifyCSS: true, diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2f24f745a7..80be13c62e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3547,7 +3547,7 @@ uint16_t mode_exploding_fireworks(void) if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg - unsigned numSparks = min(2 + ((rows*cols) >> 1), maxSparks); + unsigned numSparks = min(5 + ((rows*cols) >> 1), maxSparks); unsigned dataSize = sizeof(spark) * numSparks; if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); @@ -3602,7 +3602,8 @@ uint16_t mode_exploding_fireworks(void) * Size is proportional to the height. */ unsigned nSparks = flare->pos + random8(4); - nSparks = constrain(nSparks, 4, numSparks); + nSparks = std::max(nSparks, 4U); // This is not a standard constrain; numSparks is not guaranteed to be at least 4 + nSparks = std::min(nSparks, numSparks); // initialize sparks if (SEGENV.aux0 == 2) { @@ -4630,7 +4631,7 @@ uint16_t mode_tv_simulator(void) { return FRAMETIME; } -static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;"; +static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;!;01"; /* diff --git a/wled00/src/dependencies/dmx/SparkFunDMX.cpp b/wled00/src/dependencies/dmx/SparkFunDMX.cpp index dbc9b15909..064b9ff620 100644 --- a/wled00/src/dependencies/dmx/SparkFunDMX.cpp +++ b/wled00/src/dependencies/dmx/SparkFunDMX.cpp @@ -34,8 +34,8 @@ static const int enablePin = -1; // disable the enable pin because it is not ne static const int rxPin = -1; // disable the receiving pin because it is not needed - softhack007: Pin=-1 means "use default" not "disable" static const int txPin = 2; // transmit DMX data over this pin (default is pin 2) -//DMX value array and size. Entry 0 will hold startbyte -static uint8_t dmxData[dmxMaxChannel] = { 0 }; +//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements +static uint8_t dmxData[dmxMaxChannel+1] = { 0 }; static int chanSize = 0; #if !defined(DMX_SEND_ONLY) static int currentChannel = 0; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dc26732712..0893e5d255 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -465,7 +465,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); - settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), + settingsScript.printf_P(PSTR("d.Sf.MD.maxLength=%d;d.Sf.MG.maxLength=%d;d.Sf.MS.maxLength=%d;"), MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings From b98a8a10b077f87728ea677a4ba016d40fc4daa0 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 4 Nov 2024 08:10:05 +0100 Subject: [PATCH 010/168] 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. --- wled00/FX.h | 10 +++++++++- wled00/FX_fcn.cpp | 16 +++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index ad39a7c06d..1579a5bcbd 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -47,6 +47,14 @@ #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() +// FPS calculation (can be defined as compile flag for debugging) +#ifndef FPS_CALC_AVG +#define FPS_CALC_AVG 7 // average FPS calculation over this many frames (moving average) +#endif +#ifndef FPS_MULTIPLIER +#define FPS_MULTIPLIER 1 // dev option: multiplier to get sub-frame FPS without floats +#endif + /* 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 @@ -729,7 +737,7 @@ class WS2812FX { // 96 bytes _transitionDur(750), _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(2), + _cumulativeFps(50<<6), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7b4fb094b9..0b0f16ea42 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1407,10 +1407,12 @@ void WS2812FX::show() { unsigned long showNow = millis(); size_t diff = showNow - _lastShow; - size_t fpsCurr = 200; - if (diff > 0) fpsCurr = 1000 / diff; - _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) - _lastShow = showNow; + + if (diff > 0) { // skip calculation if no time has passed + size_t fpsCurr = (1000<<7) / diff; // fixed point 9.7 bit + _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding + _lastShow = showNow; + } } /** @@ -1427,7 +1429,11 @@ bool WS2812FX::isUpdating() const { */ uint16_t WS2812FX::getFps() const { if (millis() - _lastShow > 2000) return 0; - return _cumulativeFps +1; + #ifdef WLED_DEBUG + return (FPS_MULTIPLIER * (_cumulativeFps + (random16() & 63))) >> 7; // + random("0.5") for dithering + #else + return (FPS_MULTIPLIER * _cumulativeFps) >> 7; // _cumulativeFps is stored in fixed point 9.7 bit + #endif } void WS2812FX::setTargetFps(uint8_t fps) { From 5fd3a513a4983bc6fda23d3692be7ce4ab0b7d93 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 4 Nov 2024 17:38:45 +0100 Subject: [PATCH 011/168] bugfix bitshift was still set from testing, forgot to update --- wled00/FX.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.h b/wled00/FX.h index 1579a5bcbd..e5b7a0e950 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -737,7 +737,7 @@ class WS2812FX { // 96 bytes _transitionDur(750), _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(50<<6), + _cumulativeFps(50<<7), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), From b28add3b8b6a1eecd972b06017fd51e4cce47e7b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 4 Nov 2024 19:33:42 +0100 Subject: [PATCH 012/168] Added define for bitshift, removed dithering dithering is not really needed, the FPS_MULTIPLIER is a much better option. --- wled00/FX.h | 3 ++- wled00/FX_fcn.cpp | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index e5b7a0e950..5451615464 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -54,6 +54,7 @@ #ifndef FPS_MULTIPLIER #define FPS_MULTIPLIER 1 // dev option: multiplier to get sub-frame FPS without floats #endif +#define FPS_CALC_SHIFT 7 // bit shift for fixed point math /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ @@ -737,7 +738,7 @@ class WS2812FX { // 96 bytes _transitionDur(750), _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(50<<7), + _cumulativeFps(50 << FPS_CALC_SHIFT), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 0b0f16ea42..a6e683c0a3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1409,7 +1409,7 @@ void WS2812FX::show() { size_t diff = showNow - _lastShow; if (diff > 0) { // skip calculation if no time has passed - size_t fpsCurr = (1000<<7) / diff; // fixed point 9.7 bit + size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding _lastShow = showNow; } @@ -1429,11 +1429,7 @@ bool WS2812FX::isUpdating() const { */ uint16_t WS2812FX::getFps() const { if (millis() - _lastShow > 2000) return 0; - #ifdef WLED_DEBUG - return (FPS_MULTIPLIER * (_cumulativeFps + (random16() & 63))) >> 7; // + random("0.5") for dithering - #else - return (FPS_MULTIPLIER * _cumulativeFps) >> 7; // _cumulativeFps is stored in fixed point 9.7 bit - #endif + return (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; // _cumulativeFps is stored in fixed point } void WS2812FX::setTargetFps(uint8_t fps) { From 86d7c245135dddbf2493b907b8a1802b3f66518b Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:50:30 +0100 Subject: [PATCH 013/168] rename delay -> frameDelay Avoiding name collisions with the 'delay' function. --- wled00/FX_fcn.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a6e683c0a3..7177ca89e8 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1324,7 +1324,7 @@ void WS2812FX::service() { if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; - unsigned delay = FRAMETIME; + unsigned frameDelay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) @@ -1344,7 +1344,7 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode + frameDelay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; @@ -1353,16 +1353,16 @@ void WS2812FX::service() { _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - delay = MIN(delay,d2); // use shortest delay + frameDelay = min(frameDelay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } #endif seg.call++; - if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } - seg.next_time = nowUp + delay; + seg.next_time = nowUp + frameDelay; } _segment_index++; } From a1b332fc7898c514f0b49ac32cc9ace00740612c Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 10 Nov 2024 18:04:07 -0500 Subject: [PATCH 014/168] handleSet: Fix incorrect response generation Don't generate a response if there's no HTTP request. Fixes #4269 --- wled00/set.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/set.cpp b/wled00/set.cpp index c446a2eff4..712e5f254a 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1191,7 +1191,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) // internal call, does not send XML response pos = req.indexOf(F("IN")); - if (pos < 1) { + if ((request != nullptr) && (pos < 1)) { auto response = request->beginResponseStream("text/xml"); XML_response(*response); request->send(response); From 37cddcaacc49e36cd8d23d37102f0d649aa996f9 Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 26 Oct 2024 20:33:38 +0200 Subject: [PATCH 015/168] specified required nodejs ver --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 9d095c82cc..a11a135539 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,8 @@ "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", "nodemon": "^3.1.7" + }, + "engines": { + "node": ">=20.0.0" } } From ae97e388a6b7211661f28c1683d34fbe2d4811a9 Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 26 Oct 2024 21:07:17 +0200 Subject: [PATCH 016/168] indened formating --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a11a135539..4b46f6032a 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,6 @@ "nodemon": "^3.1.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=21.0.0" } } From 5c5b70f52b7f0b31b87a7d70e751b86637395d39 Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sun, 27 Oct 2024 11:13:56 +0100 Subject: [PATCH 017/168] version changed to 20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b46f6032a..a11a135539 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,6 @@ "nodemon": "^3.1.7" }, "engines": { - "node": ">=21.0.0" + "node": ">=20.0.0" } } From 0b3643132b5a43bfb3629bdfdb53b8847873ffa5 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 25 Nov 2024 23:47:41 +0000 Subject: [PATCH 018/168] Update to 2411250 --- CHANGELOG.md | 10 +++++++++- wled00/wled.h | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1db933736..d3c3d22a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,17 @@ ## WLED changelog -#### Build 2411150 +#### 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 diff --git a/wled00/wled.h b/wled00/wled.h index f8709422bf..8ab66cc237 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0-b7 + @version 0.15.0-cr1 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2411150 +#define VERSION 2411250 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From 7ece14ff3f5f969dfcc3d642fa219b7694a97bcc Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:31:41 +0000 Subject: [PATCH 019/168] rename workflow ready for workflow refactor --- .github/workflows/{wled-ci.yml => build.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{wled-ci.yml => build.yml} (100%) diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/build.yml similarity index 100% rename from .github/workflows/wled-ci.yml rename to .github/workflows/build.yml From bc79f44a26ed8ab3b4eb7ed6ccf157eb00cab007 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:33:44 +0000 Subject: [PATCH 020/168] workflow refactor to used shared build file --- .github/workflows/build.yml | 8 +++++--- .github/workflows/wled-ci.yml | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/wled-ci.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1dcab26ab4..80fce38c3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,9 @@ -name: WLED CI - -on: [push, pull_request] +name: WLED Build +# Only included into other workflows +on: + workflow_call: + jobs: get_default_envs: diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml new file mode 100644 index 0000000000..3c862c1854 --- /dev/null +++ b/.github/workflows/wled-ci.yml @@ -0,0 +1,11 @@ +name: WLED CI + +on: + push: + branches: + - '*' + pull_request: + +jobs: + wled_build: + uses: ./.github/workflows/build.yml From c89e4576b4fd94a7419ecb468e7557f8d8743751 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:35:04 +0000 Subject: [PATCH 021/168] Dedicated release workflow --- .github/workflows/build.yml | 16 ---------------- .github/workflows/release.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80fce38c3a..e5fdfc5a3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,22 +64,6 @@ jobs: path: | build_output/release/*.bin build_output/release/*_ESP02*.bin.gz - release: - name: Create Release - runs-on: ubuntu-latest - needs: build - if: startsWith(github.ref, 'refs/tags/') - steps: - - uses: actions/download-artifact@v4 - with: - merge-multiple: true - - name: Create draft release - uses: softprops/action-gh-release@v1 - with: - draft: True - files: | - *.bin - *.bin.gz testCdata: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..27beec99c3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: WLED Release CI + +on: + push: + tags: + - '*' + +jobs: + + wled_build: + uses: ./.github/workflows/build.yml + + release: + name: Create Release + runs-on: ubuntu-latest + needs: wled_build + steps: + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + - name: Create draft release + uses: softprops/action-gh-release@v1 + with: + draft: True + files: | + *.bin + *.bin.gz + From d26b3108da65bcebf7c38e447aabf050596edd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 26 Nov 2024 19:35:15 +0100 Subject: [PATCH 022/168] Fix for #4300 --- wled00/set.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/set.cpp b/wled00/set.cpp index 712e5f254a..2a6fdd3fb0 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -215,6 +215,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed // 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; char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED From 6a1237847599707ccbafdd02f3c823bb054a95a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Wed, 27 Nov 2024 16:08:10 +0100 Subject: [PATCH 023/168] Fix for #4321 --- wled00/data/settings_leds.htm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be5becd10..baf80a5d79 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -42,15 +42,14 @@ if (loc) d.Sf.action = getURL('/settings/leds'); } function bLimits(b,v,p,m,l,o=5,d=2,a=6) { - // maxB - max buses (can be changed if using ESP32 parallel I2S) - // maxD - max digital channels (can be changed if using ESP32 parallel I2S) - // maxA - max analog channels - // maxV - min virtual buses - // maxPB - max LEDs per bus - // maxM - max LED memory - // maxL - max LEDs (will serve to determine ESP >1664 == ESP32) - // maxCO - max Color Order mappings - oMaxB = maxB = b; maxD = d, maxA = a, maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o; + 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 + 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 pinsOK() { var ok = true; From 62ddb18a1a2ba8a136971e8374df64925e28286d Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 27 Nov 2024 09:00:25 -0500 Subject: [PATCH 024/168] BusDigital::begin: Pass clock rate argument Fixes bug introduced by #4312. --- wled00/bus_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 8281211c1d..7154708002 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -412,7 +412,7 @@ std::vector BusDigital::getLEDTypes() { void BusDigital::begin() { if (!_valid) return; - PolyBus::begin(_busPtr, _iType, _pins); + PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz); } void BusDigital::cleanup() { From 685ad83d4b5f9fef2df8c73042b9fe00e5cf94a3 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 27 Nov 2024 09:05:10 -0500 Subject: [PATCH 025/168] PolyBus: Clarify use of clock_kHz While not used by most bus types, it's not an optional parameter. --- wled00/bus_manager.cpp | 2 +- wled00/bus_wrapper.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 7154708002..5b031bebbf 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -150,7 +150,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) //_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, _frequencykHz); + _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); } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 7e6e74d85d..d2a18c9d87 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -336,7 +336,7 @@ class PolyBus { // initialize SPI bus speed for DotStar methods template - static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { + static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz /* 0 == use default */) { T dotStar_strip = static_cast(busPtr); #ifdef ESP8266 dotStar_strip->Begin(); @@ -363,7 +363,7 @@ class PolyBus { tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly } - static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { + static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz /* only used by DotStar */) { switch (busType) { case I_NONE: break; #ifdef ESP8266 @@ -480,7 +480,7 @@ class PolyBus { } } - static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { + static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation From 71b242874f0223fcf777e1da9f79ee0f0761f515 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 4 Dec 2024 17:10:44 +0100 Subject: [PATCH 026/168] Fix for #4193 (twinkle fox & cat) --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 80be13c62e..19d19ad667 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2565,8 +2565,8 @@ 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); + unsigned slowcycle16 = uint16_t((ticks >> 8) + salt); slowcycle16 += sin8(slowcycle16); slowcycle16 = (slowcycle16 * 2053) + 1384; unsigned slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); From b78229d1e2ba27e6d7f2dfa905c621fb225041a4 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 5 Dec 2024 06:49:17 +0100 Subject: [PATCH 027/168] 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. --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 19d19ad667..0ea070c42e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2566,10 +2566,10 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) // Overall twinkle speed (changed) unsigned ticks = ms / SEGENV.aux0; unsigned fastcycle8 = uint8_t(ticks); - unsigned slowcycle16 = uint16_t((ticks >> 8) + salt); + uint16_t slowcycle16 = (ticks >> 8) + salt; slowcycle16 += sin8(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). From 9a4073e606551f162130259134f3e95d9b644d30 Mon Sep 17 00:00:00 2001 From: Ian Hubbertz Date: Sat, 7 Dec 2024 21:33:46 +0100 Subject: [PATCH 028/168] 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. --- usermods/BME280_v2/usermod_bme280.h | 1 + 1 file changed, 1 insertion(+) diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index 9168f42291..aa6d003f3d 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -444,6 +444,7 @@ class UsermodBME280 : public Usermod 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) { From af410ae2d07bf43c9ab8aa0743223cb2eb3c1d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sat, 7 Dec 2024 16:46:43 +0100 Subject: [PATCH 029/168] WiFi reconnect bugfix - additional debug info --- wled00/network.cpp | 1 + wled00/wled.cpp | 24 ++++++++++-------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/wled00/network.cpp b/wled00/network.cpp index 06860cf4de..3bd589f944 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -207,6 +207,7 @@ void WiFiEvent(WiFiEvent_t event) break; #endif default: + DEBUG_PRINTF_P(PSTR("Network event: %d\n"), (int)event); break; } } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index d6a39a399f..394da6783e 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -478,10 +478,7 @@ void WLED::setup() 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 @@ -781,7 +778,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); @@ -825,9 +822,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 +921,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 +931,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 +951,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 +974,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 +983,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 +999,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 From 32864d8986f73c09f7bb55f4ebd37738e0980317 Mon Sep 17 00:00:00 2001 From: netmindz Date: Tue, 10 Dec 2024 18:54:39 +0000 Subject: [PATCH 030/168] update version to 0.15.0 --- package.json | 2 +- wled00/wled.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5b7964084a..29fc0620b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-rc1", + "version": "0.15.0", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/wled.h b/wled00/wled.h index 8ab66cc237..d4dcd3fa64 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,12 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0-cr1 + @version 0.15.0 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2411250 +#define VERSION 2412100 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From a111a2e7a1b9638c03f121d3d1fcdaeb7feae9dd Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 10 Dec 2024 20:13:12 +0000 Subject: [PATCH 031/168] update version to 0.15.0 --- package-lock.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a10327b6c0..70344dec26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,21 @@ { "name": "wled", - "version": "0.15.0-rc1", + "version": "0.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-rc1", + "version": "0.15.0", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", "nodemon": "^3.1.7" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@jridgewell/gen-mapping": { From faadb67eb0a108ac947beb60624ccca66b2b1e70 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 10 Dec 2024 20:20:28 +0000 Subject: [PATCH 032/168] update changelog for 2412100 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c3d22a94..b04ce71f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## 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) From 9dc1022010c985090165833461ed3cf54fa66bd5 Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Fri, 27 Dec 2024 11:19:24 +0100 Subject: [PATCH 033/168] palette effect overflow fix --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 69ea17beab..f334ca2959 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2003,7 +2003,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. From 866a4c8ab6020532719bf18f78197ba26ce391cf Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Dec 2024 09:21:57 +0100 Subject: [PATCH 034/168] fix for repeating glitch glitch appeared every 65s due to missing uint16_t overflow. --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f334ca2959..0ab0f53ad4 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1441,7 +1441,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 +1500,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; From a26531803700a31bdcb0cb52338d295c4d024fa1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 23 Dec 2024 14:57:22 +0100 Subject: [PATCH 035/168] fixed CIE brightness calculation for PWM outputs --- wled00/bus_manager.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5b031bebbf..2f8207737d 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -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) From c9b95e22d389e19f2d57faa44e84df6b9b55c4cd Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 30 Dec 2024 12:58:38 +0100 Subject: [PATCH 036/168] 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. --- wled00/presets.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 04474113d1..fb09d48a4b 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -164,6 +164,11 @@ void handlePresets() DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); + #if 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); From 5fc2175dd4930f713eece93f1efa26e4f071da85 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 6 Jan 2025 22:24:28 +0100 Subject: [PATCH 037/168] Playlist output glitchfix update: found it also happens on S3 (#4462) * Fix output glitches when playlist changes preset update: glitches also happen on S3 --- wled00/presets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index fb09d48a4b..1abcb52b93 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -164,7 +164,7 @@ void handlePresets() DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); - #if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) + #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 From 700a7076fd09fab4fc52d70e7570fb708f623a8f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 9 Jan 2025 22:41:45 +0100 Subject: [PATCH 038/168] added a delay after switching relay (#4474) - helps to stabilize power on the LEDs before sending data --- wled00/button.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/button.cpp b/wled00/button.cpp index 4d6f954f60..a56b845f25 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -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; } From 012143bd7b44a6ffb14cdd269c1f7fcd6a99dd39 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 14 Jan 2025 18:45:01 +0100 Subject: [PATCH 039/168] BUGFIX in oscillate FX effect was changed from int to uint but it relied on negative numbers. fixed by checking overflow and a cast. --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0ab0f53ad4..de27c5fff7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1798,7 +1798,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,7 +1814,7 @@ 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) { + 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), 128); } } From 0d5a0fb83091e9aad9199d39b3a3cbc84f3019d1 Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 16 Jan 2025 13:01:00 +0000 Subject: [PATCH 040/168] Merge pull request #4386 from DedeHai/ESPNow_glitchfix Fix for ESPNow remote causing output glitches --- wled00/fcn_declare.h | 3 ++- wled00/remote.cpp | 24 +++++++++++++++++++----- wled00/udp.cpp | 2 +- wled00/wled.cpp | 3 +++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 7798fb9788..7d5a725333 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -230,7 +230,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); diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 9bc5430c01..c3325ab98b 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -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(); @@ -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(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 diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 60774d7010..5173842a68 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -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; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 394da6783e..b7f4ad7d6e 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -84,6 +84,9 @@ void WLED::loop() #ifndef WLED_DISABLE_INFRARED handleIR(); #endif + #ifndef WLED_DISABLE_ESPNOW + handleRemote(); + #endif #ifndef WLED_DISABLE_ALEXA handleAlexa(); #endif From 473700e4c08bee1bc698f3c748e5524f840313ed Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 16 Jan 2025 13:03:22 +0000 Subject: [PATCH 041/168] Merge pull request #4018 from Brandon502/main Added Cube Mapping Tool --- tools/AutoCubeMap.xlsx | Bin 0 -> 80009 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/AutoCubeMap.xlsx diff --git a/tools/AutoCubeMap.xlsx b/tools/AutoCubeMap.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b3f5cee2ad32ae887d6cc62d6667a05c61a8ac35 GIT binary patch literal 80009 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 literal 0 HcmV?d00001 From 4e11ecda4b74fd03e4863547703ee1c2d1943af8 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Thu, 16 Jan 2025 13:18:50 +0000 Subject: [PATCH 042/168] Set version to 0.15.1.beta1 --- package.json | 2 +- wled00/wled.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 29fc0620b5..34ecf32b0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0", + "version": "0.15.1.beta1", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/wled.h b/wled00/wled.h index d4dcd3fa64..db18f1b851 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,12 +3,11 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.15.0 @author Christian Schwinne */ // version code in format yymmddb (b = daily build) -#define VERSION 2412100 +#define VERSION 2501160 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From cc4a4c4ae1ce74b01fb13d5f486a4c4b0cf2ede2 Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 16 Jan 2025 15:52:21 +0000 Subject: [PATCH 043/168] Merge pull request #4428 from blazoncek/waterfall-fix FX: Waterfall, Matripix & Dissolve fix --- wled00/FX.cpp | 64 +++++++++++++++++++++++++++++--------------------- wled00/led.cpp | 6 ++--- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index de27c5fff7..6d0d360412 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -600,11 +600,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(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 +613,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++; } @@ -6639,14 +6633,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(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 +6650,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 +7285,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(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 +7299,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 +7316,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; diff --git a/wled00/led.cpp b/wled00/led.cpp index 9b97091e64..2ca64a5f38 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -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(); } } From 2ac4d03160762d9c093e677b277a65d969c646c9 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 8 Feb 2025 16:45:33 +0100 Subject: [PATCH 044/168] Fixes first pixel not being set in Stream FX (#4542) * Fixes first pixel not being set * added fix to Stream 2 as well --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 6d0d360412..060e0af5a7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1091,7 +1091,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; @@ -1739,7 +1739,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(); From edc6022441e13aaff965ad8dc968fb3b6a167f5f Mon Sep 17 00:00:00 2001 From: maxi4329 <84231420+maxi4329@users.noreply.github.com> Date: Sat, 15 Feb 2025 15:14:52 +0100 Subject: [PATCH 045/168] 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 --- wled00/data/common.js | 4 ++-- wled00/data/index.js | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index 9378ef07a8..eddaf6548d 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -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); diff --git a/wled00/data/index.js b/wled00/data/index.js index 1482c27b55..c9ee23ad5e 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -3114,10 +3114,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 +3141,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); From 1bf13ea5251842cb162d86f34d350b1e64a9ad11 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 15 Jan 2025 15:17:56 +0100 Subject: [PATCH 046/168] 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. --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 060e0af5a7..c28fdd5155 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1809,7 +1809,7 @@ uint16_t mode_oscillate(void) { uint32_t color = BLACK; for (unsigned j = 0; j < numOscillators; j++) { 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), 128); + color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), uint8_t(128)); } } SEGMENT.setPixelColor(i, color); From fa2f8310444cc33aa32f975fe870edaf5315c238 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 15 Feb 2025 10:34:44 +0100 Subject: [PATCH 047/168] fix for incorrect hardware timing --- wled00/wled.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index b7f4ad7d6e..3eeada5158 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -796,6 +796,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 From c46e328b596f320bc26d1f4bfbb4ce64aca693e1 Mon Sep 17 00:00:00 2001 From: netmindz Date: Sat, 15 Feb 2025 15:09:46 +0000 Subject: [PATCH 048/168] Merge pull request #4556 from spiro-c/npm-check Fix for: Build should stop if npm fails #4513 --- pio-scripts/build_ui.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pio-scripts/build_ui.py b/pio-scripts/build_ui.py index f3688a5d4a..047fac442c 100644 --- a/pio-scripts/build_ui.py +++ b/pio-scripts/build_ui.py @@ -1,3 +1,21 @@ -Import('env') +Import("env") +import shutil -env.Execute("npm run build") \ No newline at end of file +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) \ No newline at end of file From e96fd8ae58d17359278197477cdd7690bc749483 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 21 Jan 2025 19:56:51 -0500 Subject: [PATCH 049/168] Merge pull request #4511 from mlichvar/main fix reproduction in game of life --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c28fdd5155..ccc8e2d835 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5164,7 +5164,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; From 1fc3cc83bd87d9c516e528b2729d037862b7f510 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:33:31 +0100 Subject: [PATCH 050/168] Merge pull request #4450 from adafruit/main Add correct pin availability for ESP32 Mini modules --- wled00/pin_manager.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 0d4c2ad5cb..c37b193674 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -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); From f4909082789c0f88b7a7e612c3176672dbdc6326 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 4 Jan 2025 17:49:15 +0100 Subject: [PATCH 051/168] Merge pull request #4439 from dosipod/Upstream_PRs Update readme.md for rgb-rotary-encoder usermod --- usermods/rgb-rotary-encoder/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/rgb-rotary-encoder/readme.md b/usermods/rgb-rotary-encoder/readme.md index ba5aad4df7..6531791799 100644 --- a/usermods/rgb-rotary-encoder/readme.md +++ b/usermods/rgb-rotary-encoder/readme.md @@ -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 From 85d3f6f11c90e2e1ed823a66369d07cb4b6075d6 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:52:33 +0100 Subject: [PATCH 052/168] Merge pull request #4398 from Aircoookie/4395-platformio_override update platformio override example file (solves #4395) --- platformio.ini | 11 ++++ platformio_override.sample.ini | 96 +++++++++++++++++++--------------- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/platformio.ini b/platformio.ini index fe02213ff2..0870cde9d8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -176,6 +176,7 @@ lib_deps = extra_scripts = ${scripts_defaults.extra_scripts} [esp8266] +build_unflags = ${common.build_unflags} build_flags = -DESP8266 -DFP_IN_IROM @@ -242,6 +243,7 @@ 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 @@ -263,6 +265,7 @@ lib_deps = 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,6 +275,7 @@ 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 @@ -280,11 +284,13 @@ build_flags = -g lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${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 @@ -298,11 +304,13 @@ build_flags = -g lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${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 @@ -315,11 +323,13 @@ build_flags = -g lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${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 @@ -333,6 +343,7 @@ build_flags = -g lib_deps = https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 ${env.lib_deps} +board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs # ------------------------------------------------------------------------------ diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index dd3630d82c..cb5b43e7b8 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -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 From 642a9e36525f9e4ea5ea39033b7d40834c8c49ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 17 Dec 2024 18:59:53 +0100 Subject: [PATCH 053/168] Idle current bugfix (#4402) --- wled00/bus_manager.cpp | 11 ----------- wled00/bus_manager.h | 12 +++++++++++- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 2f8207737d..9a2a463d6c 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -155,16 +155,6 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) 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); } -//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. @@ -939,7 +929,6 @@ void BusManager::show() { busses[i]->show(); _milliAmpsUsed += busses[i]->getUsedCurrent(); } - if (_milliAmpsUsed) _milliAmpsUsed += MA_FOR_ESP; } void BusManager::setStatusPixel(uint32_t c) { diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index ecebc120e8..89307af2b6 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -363,6 +363,16 @@ struct BusConfig { }; +//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() {}; @@ -370,7 +380,7 @@ class 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 uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; } static uint16_t ablMilliampsMax() { return _milliAmpsMax; } static int add(BusConfig &bc); From b75a2de485739bbf0ae3eb2900108fff4d65a973 Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 12 Dec 2024 15:53:03 +0000 Subject: [PATCH 054/168] Merge pull request #4356 from blazoncek/json-cycle Proper fix for #3605 & #4346 --- wled00/fcn_declare.h | 2 +- wled00/json.cpp | 19 +++---------------- wled00/util.cpp | 10 +++++++--- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 7d5a725333..2e965a9af5 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -373,7 +373,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); diff --git a/wled00/json.cpp b/wled00/json.cpp index 288059653f..5f1bcceb9b 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -223,30 +223,17 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) #endif byte fx = seg.mode; - byte last = strip.getModeCount(); - // partial fix for #3605 - if (!elem["fx"].isNull() && elem["fx"].is()) { - const char *tmp = elem["fx"].as(); - 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 *tmp = elem["pal"].as(); - 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); @@ -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) diff --git a/wled00/util.cpp b/wled00/util.cpp index c43fdeabf0..41e3d6c235 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -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()) { 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* 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; } From f593d404cb0f82b9666c98b6589e086f98b1039c Mon Sep 17 00:00:00 2001 From: netmindz Date: Wed, 4 Dec 2024 07:48:19 +0000 Subject: [PATCH 055/168] Merge pull request #4244 from MoonModules/framerate_ac015 Improved framerate control code - strip.show(), strip.service() --- usermods/audioreactive/audio_reactive.h | 6 +++--- wled00/FX.h | 14 +++++++++++--- wled00/FX_fcn.cpp | 25 +++++++++++++++++-------- wled00/data/settings_leds.htm | 10 +++++++++- wled00/data/settings_sec.htm | 2 +- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index ad449fc83a..9c463e0a19 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -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 } diff --git a/wled00/FX.h b/wled00/FX.h index 5451615464..39373c0791 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -46,6 +46,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 @@ -77,8 +85,6 @@ 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()] @@ -748,6 +754,7 @@ class WS2812FX { // 96 bytes customMappingTable(nullptr), customMappingSize(0), _lastShow(0), + _lastServiceShow(0), _segment_index(0), _mainSegment(0) { @@ -846,7 +853,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 +965,7 @@ class WS2812FX { // 96 bytes uint16_t customMappingSize; unsigned long _lastShow; + unsigned long _lastServiceShow; uint8_t _segment_index; uint8_t _mainSegment; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7177ca89e8..220cc44816 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1304,7 +1304,14 @@ 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; @@ -1321,7 +1328,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 +1378,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 } #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 +1407,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 +1441,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 +1491,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 } } diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index baf80a5d79..62dfe99b61 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -379,6 +379,11 @@ 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; @@ -869,7 +874,10 @@

Advanced


- Target refresh rate: FPS + Target refresh rate: FPS + + +
Config template:

diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 7fe0fe62ee..34f8603d03 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -57,7 +57,7 @@

Security & Update setup

Software Update


Enable ArduinoOTA: -
+

Backup & Restore

⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
From dc3d46392515dd9d718cc8645d4e3ae77ecd1c1f Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 16 Jan 2025 15:52:21 +0000 Subject: [PATCH 056/168] Merge pull request #4428 from blazoncek/waterfall-fix FX: Waterfall, Matripix & Dissolve fix --- package-lock.json | 4 ++-- wled00/FX.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70344dec26..1f67845834 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0", + "version": "0.15.1.beta1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0", + "version": "0.15.1.beta1", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ccc8e2d835..cb829f7310 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -612,7 +612,7 @@ uint16_t dissolve(uint32_t color) { for (int j = 0; j <= SEGLEN / 15; j++) { 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 i = hw_random16(SEGLEN); if (SEGENV.aux0) { //dissolve to primary/palette if (pixels[i] == SEGCOLOR(1)) { pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color; From dbe76479a26b2688ddb4886f216f9ea0022971a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Thu, 13 Feb 2025 19:10:37 +0100 Subject: [PATCH 057/168] Merge pull request #4484 from blazoncek/parallel-I2S WWA strip support & parallel I2S for S2/S3 (bumping outputs from 5/4 to 12) --- .gitignore | 1 + platformio.ini | 2 +- wled00/FX.cpp | 2 +- wled00/FX.h | 27 +- wled00/FX_fcn.cpp | 52 ++- wled00/bus_manager.cpp | 307 +++++++------ wled00/bus_manager.h | 190 ++++---- wled00/bus_wrapper.h | 801 ++++++++++++++++++---------------- wled00/cfg.cpp | 64 +-- wled00/const.h | 36 +- wled00/data/settings_leds.htm | 26 +- wled00/fcn_declare.h | 1 + wled00/json.cpp | 2 +- wled00/set.cpp | 9 +- wled00/udp.cpp | 6 +- wled00/wled.cpp | 42 +- wled00/wled.h | 9 +- wled00/xml.cpp | 1 + 18 files changed, 863 insertions(+), 715 deletions(-) diff --git a/.gitignore b/.gitignore index 8f083e3f6a..51d321d922 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ wled-update.sh /build_output/ /node_modules/ +/logs/ /wled00/extLibs /wled00/LittleFS diff --git a/platformio.ini b/platformio.ini index 0870cde9d8..62c8ca5185 100644 --- a/platformio.ini +++ b/platformio.ini @@ -138,7 +138,7 @@ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 - makuna/NeoPixelBus @ 2.8.0 + makuna/NeoPixelBus @ 2.8.3 #https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 # for I2C interface diff --git a/wled00/FX.cpp b/wled00/FX.cpp index cb829f7310..ccc8e2d835 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -612,7 +612,7 @@ uint16_t dissolve(uint32_t color) { for (int j = 0; j <= SEGLEN / 15; j++) { if (random8() <= SEGMENT.intensity) { for (size_t times = 0; times < 10; times++) { //attempt to spawn a new pixel 10 times - unsigned i = hw_random16(SEGLEN); + unsigned i = random16(SEGLEN); if (SEGENV.aux0) { //dissolve to primary/palette if (pixels[i] == SEGCOLOR(1)) { pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color; diff --git a/wled00/FX.h b/wled00/FX.h index 39373c0791..e36cad3072 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -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 +#include "wled.h" #include "const.h" @@ -67,18 +71,15 @@ /* 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, @@ -530,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; } @@ -539,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); 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 diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 220cc44816..d0b13d7b8a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -456,7 +456,7 @@ void Segment::handleRandomPalette() { } // 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) { // return if neither bounds nor grouping have changed bool boundsUnchanged = (start == i1 && stop == i2); #ifndef WLED_DISABLE_2D @@ -601,6 +601,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(realloc(name, newLen+1)); + else name = static_cast(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 +965,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; @@ -1192,6 +1206,34 @@ 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; + for (const auto &bus : busConfigs) { + if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) { + digitalCount++; + 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 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0 + if (maxLedsOnBus <= 300 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses + else useParallelI2S = false; // enforce single I2S + #endif + + // create buses/outputs + unsigned mem = 0; + digitalCount = 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 +1249,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 +1314,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; ibegin(); } + DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap()); Segment::maxWidth = _length; Segment::maxHeight = 1; @@ -1601,7 +1647,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 } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 9a2a463d6c..0e72b28383 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -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 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 - #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,30 +114,41 @@ 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 + ); } //DISCLAIMER @@ -163,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; @@ -176,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 { @@ -201,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) { @@ -246,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; @@ -296,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) { @@ -326,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(); @@ -358,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; @@ -390,8 +399,8 @@ std::vector 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")}, @@ -406,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); } @@ -442,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; @@ -486,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)) { @@ -528,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) { @@ -604,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]; @@ -640,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) { @@ -657,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); @@ -670,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]); } @@ -680,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; @@ -693,7 +703,7 @@ std::vector BusOnOff::getLEDTypes() { }; } -BusNetwork::BusNetwork(BusConfig &bc) +BusNetwork::BusNetwork(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count) , _broadcastLock(false) { @@ -717,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 @@ -731,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)); @@ -744,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; } @@ -765,6 +775,7 @@ std::vector BusNetwork::getLEDTypes() { } void BusNetwork::cleanup() { + DEBUGBUS_PRINTLN(F("Virtual Cleanup.")); _type = I_NONE; _valid = false; freeData(); @@ -772,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 +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 { + return sizeof(BusPwm); } - 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); + +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(BusConfig &bc) { +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[numBusses] = new BusNetwork(bc); + //busses.push_back(std::make_unique(bc)); // when C++ >11 + busses.push_back(new BusNetwork(bc)); } else if (Bus::isDigital(bc.type)) { - busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); + //busses.push_back(std::make_unique(bc, numDigital, colorOrderMap)); + busses.push_back(new BusDigital(bc, numDigital, colorOrderMap)); } else if (Bus::isOnOff(bc.type)) { - busses[numBusses] = new BusOnOff(bc); + //busses.push_back(std::make_unique(bc)); + busses.push_back(new BusOnOff(bc)); } else { - busses[numBusses] = new BusPwm(bc); + //busses.push_back(std::make_unique(bc)); + busses.push_back(new BusPwm(bc)); } - return numBusses++; + return busses.size(); } // credit @willmmiles @@ -837,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); } @@ -859,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; @@ -870,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); rmt_idle_level_t lvl; @@ -883,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 @@ -891,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(busses[i]); - bus->begin(); + BusDigital *b = static_cast(bus); + b->begin(); break; } } @@ -913,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); } @@ -925,30 +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(); } } 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) { @@ -960,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; igetLength(); + 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; @@ -997,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> BusManager::busses; +std::vector BusManager::busses; ColorOrderMap BusManager::colorOrderMap = {}; uint16_t BusManager::_milliAmpsUsed = 0; uint16_t BusManager::_milliAmpsMax = ABL_MILLIAMPS_DEFAULT; -uint8_t BusManager::_parallelOutputs = 1; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 89307af2b6..7ea8276c51 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -1,3 +1,4 @@ +#pragma once #ifndef BusManager_h #define BusManager_h @@ -7,6 +8,30 @@ #include "const.h" #include +#include + +// 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 + #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; } - - 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; } - - static inline std::vector 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); } + 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 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 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 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 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,6 +400,8 @@ struct BusConfig { if (start + count > total) total = start + count; return true; } + + unsigned memUsage(unsigned nr = 0) const; }; @@ -377,14 +419,13 @@ 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 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(); @@ -395,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> busses; // we'd need C++ >11 + static std::vector 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; iisVirtual()) j++; + for (const auto &bus : busses) j += bus->isVirtual(); return j; } }; diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index d2a18c9d87..577aaeb826 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1,23 +1,9 @@ +#pragma once #ifndef BusWrapper_h #define BusWrapper_h +//#define NPB_CONF_4STEP_CADENCE #include "NeoPixelBusLg.h" -#include "bus_manager.h" - -// temporary - these defines should actually be set in platformio.ini -// C3: I2S0 and I2S1 methods not supported (has one I2S bus) -// S2: I2S1 methods not supported (has one I2S bus) -// S3: I2S0 and I2S1 methods not supported yet (has two I2S buses) -// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/Esp32_i2s.h#L4 -// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/NeoEsp32RmtMethod.h#L857 - -#if !defined(WLED_NO_I2S0_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) -#define WLED_NO_I2S0_PIXELBUS -#endif -#if !defined(WLED_NO_I2S1_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)) -#define WLED_NO_I2S1_PIXELBUS -#endif -// temporary end //Hardware SPI Pins #define P_8266_HS_MOSI 13 @@ -55,110 +41,98 @@ #define I_8266_DM_TM2_3 19 #define I_8266_BB_TM2_3 20 //UCS8903 (RGB) -#define I_8266_U0_UCS_3 49 -#define I_8266_U1_UCS_3 50 -#define I_8266_DM_UCS_3 51 -#define I_8266_BB_UCS_3 52 +#define I_8266_U0_UCS_3 21 +#define I_8266_U1_UCS_3 22 +#define I_8266_DM_UCS_3 23 +#define I_8266_BB_UCS_3 24 //UCS8904 (RGBW) -#define I_8266_U0_UCS_4 53 -#define I_8266_U1_UCS_4 54 -#define I_8266_DM_UCS_4 55 -#define I_8266_BB_UCS_4 56 +#define I_8266_U0_UCS_4 25 +#define I_8266_U1_UCS_4 26 +#define I_8266_DM_UCS_4 27 +#define I_8266_BB_UCS_4 28 //FW1906 GRBCW -#define I_8266_U0_FW6_5 66 -#define I_8266_U1_FW6_5 67 -#define I_8266_DM_FW6_5 68 -#define I_8266_BB_FW6_5 69 +#define I_8266_U0_FW6_5 29 +#define I_8266_U1_FW6_5 30 +#define I_8266_DM_FW6_5 31 +#define I_8266_BB_FW6_5 32 //ESP8266 APA106 -#define I_8266_U0_APA106_3 81 -#define I_8266_U1_APA106_3 82 -#define I_8266_DM_APA106_3 83 -#define I_8266_BB_APA106_3 84 +#define I_8266_U0_APA106_3 33 +#define I_8266_U1_APA106_3 34 +#define I_8266_DM_APA106_3 35 +#define I_8266_BB_APA106_3 36 //WS2805 (RGBCW) -#define I_8266_U0_2805_5 89 -#define I_8266_U1_2805_5 90 -#define I_8266_DM_2805_5 91 -#define I_8266_BB_2805_5 92 +#define I_8266_U0_2805_5 37 +#define I_8266_U1_2805_5 38 +#define I_8266_DM_2805_5 39 +#define I_8266_BB_2805_5 40 //TM1914 (RGB) -#define I_8266_U0_TM1914_3 99 -#define I_8266_U1_TM1914_3 100 -#define I_8266_DM_TM1914_3 101 -#define I_8266_BB_TM1914_3 102 +#define I_8266_U0_TM1914_3 41 +#define I_8266_U1_TM1914_3 42 +#define I_8266_DM_TM1914_3 43 +#define I_8266_BB_TM1914_3 44 //SM16825 (RGBCW) -#define I_8266_U0_SM16825_5 103 -#define I_8266_U1_SM16825_5 104 -#define I_8266_DM_SM16825_5 105 -#define I_8266_BB_SM16825_5 106 +#define I_8266_U0_SM16825_5 45 +#define I_8266_U1_SM16825_5 46 +#define I_8266_DM_SM16825_5 47 +#define I_8266_BB_SM16825_5 48 /*** ESP32 Neopixel methods ***/ //RGB -#define I_32_RN_NEO_3 21 -#define I_32_I0_NEO_3 22 -#define I_32_I1_NEO_3 23 +#define I_32_RN_NEO_3 1 +#define I_32_I2_NEO_3 2 //RGBW -#define I_32_RN_NEO_4 25 -#define I_32_I0_NEO_4 26 -#define I_32_I1_NEO_4 27 +#define I_32_RN_NEO_4 5 +#define I_32_I2_NEO_4 6 //400Kbps -#define I_32_RN_400_3 29 -#define I_32_I0_400_3 30 -#define I_32_I1_400_3 31 +#define I_32_RN_400_3 9 +#define I_32_I2_400_3 10 //TM1814 (RGBW) -#define I_32_RN_TM1_4 33 -#define I_32_I0_TM1_4 34 -#define I_32_I1_TM1_4 35 +#define I_32_RN_TM1_4 13 +#define I_32_I2_TM1_4 14 //TM1829 (RGB) -#define I_32_RN_TM2_3 36 -#define I_32_I0_TM2_3 37 -#define I_32_I1_TM2_3 38 +#define I_32_RN_TM2_3 17 +#define I_32_I2_TM2_3 18 //UCS8903 (RGB) -#define I_32_RN_UCS_3 57 -#define I_32_I0_UCS_3 58 -#define I_32_I1_UCS_3 59 +#define I_32_RN_UCS_3 21 +#define I_32_I2_UCS_3 22 //UCS8904 (RGBW) -#define I_32_RN_UCS_4 60 -#define I_32_I0_UCS_4 61 -#define I_32_I1_UCS_4 62 +#define I_32_RN_UCS_4 25 +#define I_32_I2_UCS_4 26 //FW1906 GRBCW -#define I_32_RN_FW6_5 63 -#define I_32_I0_FW6_5 64 -#define I_32_I1_FW6_5 65 +#define I_32_RN_FW6_5 29 +#define I_32_I2_FW6_5 30 //APA106 -#define I_32_RN_APA106_3 85 -#define I_32_I0_APA106_3 86 -#define I_32_I1_APA106_3 87 +#define I_32_RN_APA106_3 33 +#define I_32_I2_APA106_3 34 //WS2805 (RGBCW) -#define I_32_RN_2805_5 93 -#define I_32_I0_2805_5 94 -#define I_32_I1_2805_5 95 +#define I_32_RN_2805_5 37 +#define I_32_I2_2805_5 38 //TM1914 (RGB) -#define I_32_RN_TM1914_3 96 -#define I_32_I0_TM1914_3 97 -#define I_32_I1_TM1914_3 98 +#define I_32_RN_TM1914_3 41 +#define I_32_I2_TM1914_3 42 //SM16825 (RGBCW) -#define I_32_RN_SM16825_5 107 -#define I_32_I0_SM16825_5 108 -#define I_32_I1_SM16825_5 109 +#define I_32_RN_SM16825_5 45 +#define I_32_I2_SM16825_5 46 //APA102 -#define I_HS_DOT_3 39 //hardware SPI -#define I_SS_DOT_3 40 //soft SPI +#define I_HS_DOT_3 101 //hardware SPI +#define I_SS_DOT_3 102 //soft SPI //LPD8806 -#define I_HS_LPD_3 41 -#define I_SS_LPD_3 42 +#define I_HS_LPD_3 103 +#define I_SS_LPD_3 104 //WS2801 -#define I_HS_WS1_3 43 -#define I_SS_WS1_3 44 +#define I_HS_WS1_3 105 +#define I_SS_WS1_3 106 //P9813 -#define I_HS_P98_3 45 -#define I_SS_P98_3 46 +#define I_HS_P98_3 107 +#define I_SS_P98_3 108 //LPD6803 -#define I_HS_LPO_3 47 -#define I_SS_LPO_3 48 +#define I_HS_LPO_3 109 +#define I_SS_LPO_3 110 // In the following NeoGammaNullMethod can be replaced with NeoGammaWLEDMethod to perform Gamma correction implicitly @@ -230,66 +204,95 @@ /*** ESP32 Neopixel methods ***/ #ifdef ARDUINO_ARCH_ESP32 +// C3: I2S0 and I2S1 methods not supported (has one I2S bus) +// S2: I2S0 methods supported (single & parallel), I2S1 methods not supported (has one I2S bus) +// S3: I2S0 methods not supported, I2S1 supports LCD parallel methods (has two I2S buses) +// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/Esp32_i2s.h#L4 +// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/NeoEsp32RmtMethod.h#L857 +#if defined(CONFIG_IDF_TARGET_ESP32S3) + // S3 will always use LCD parallel output + typedef X8Ws2812xMethod X1Ws2812xMethod; + typedef X8Sk6812Method X1Sk6812Method; + typedef X8400KbpsMethod X1400KbpsMethod; + typedef X8800KbpsMethod X1800KbpsMethod; + typedef X8Tm1814Method X1Tm1814Method; + typedef X8Tm1829Method X1Tm1829Method; + typedef X8Apa106Method X1Apa106Method; + typedef X8Ws2805Method X1Ws2805Method; + typedef X8Tm1914Method X1Tm1914Method; +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + // S2 will use I2S0 + typedef NeoEsp32I2s0Ws2812xMethod X1Ws2812xMethod; + typedef NeoEsp32I2s0Sk6812Method X1Sk6812Method; + typedef NeoEsp32I2s0400KbpsMethod X1400KbpsMethod; + typedef NeoEsp32I2s0800KbpsMethod X1800KbpsMethod; + typedef NeoEsp32I2s0Tm1814Method X1Tm1814Method; + typedef NeoEsp32I2s0Tm1829Method X1Tm1829Method; + typedef NeoEsp32I2s0Apa106Method X1Apa106Method; + typedef NeoEsp32I2s0Ws2805Method X1Ws2805Method; + typedef NeoEsp32I2s0Tm1914Method X1Tm1914Method; +#elif !defined(CONFIG_IDF_TARGET_ESP32C3) + // regular ESP32 will use I2S1 + typedef NeoEsp32I2s1Ws2812xMethod X1Ws2812xMethod; + typedef NeoEsp32I2s1Sk6812Method X1Sk6812Method; + typedef NeoEsp32I2s1400KbpsMethod X1400KbpsMethod; + typedef NeoEsp32I2s1800KbpsMethod X1800KbpsMethod; + typedef NeoEsp32I2s1Tm1814Method X1Tm1814Method; + typedef NeoEsp32I2s1Tm1829Method X1Tm1829Method; + typedef NeoEsp32I2s1Apa106Method X1Apa106Method; + typedef NeoEsp32I2s1Ws2805Method X1Ws2805Method; + typedef NeoEsp32I2s1Tm1914Method X1Tm1914Method; +#endif + //RGB -#define B_32_RN_NEO_3 NeoPixelBusLg -#define B_32_I0_NEO_3 NeoPixelBusLg -#define B_32_I1_NEO_3 NeoPixelBusLg -#define B_32_I1_NEO_3P NeoPixelBusLg // parallel I2S +#define B_32_RN_NEO_3 NeoPixelBusLg // ESP32, S2, S3, C3 +//#define B_32_IN_NEO_3 NeoPixelBusLg // ESP32 (dynamic I2S selection) +#define B_32_I2_NEO_3 NeoPixelBusLg // ESP32, S2, S3 (automatic I2S selection, see typedef above) +#define B_32_IP_NEO_3 NeoPixelBusLg // parallel I2S (ESP32, S2, S3) //RGBW #define B_32_RN_NEO_4 NeoPixelBusLg -#define B_32_I0_NEO_4 NeoPixelBusLg -#define B_32_I1_NEO_4 NeoPixelBusLg -#define B_32_I1_NEO_4P NeoPixelBusLg // parallel I2S +#define B_32_I2_NEO_4 NeoPixelBusLg +#define B_32_IP_NEO_4 NeoPixelBusLg // parallel I2S //400Kbps #define B_32_RN_400_3 NeoPixelBusLg -#define B_32_I0_400_3 NeoPixelBusLg -#define B_32_I1_400_3 NeoPixelBusLg -#define B_32_I1_400_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_400_3 NeoPixelBusLg +#define B_32_IP_400_3 NeoPixelBusLg // parallel I2S //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBusLg -#define B_32_I0_TM1_4 NeoPixelBusLg -#define B_32_I1_TM1_4 NeoPixelBusLg -#define B_32_I1_TM1_4P NeoPixelBusLg // parallel I2S +#define B_32_I2_TM1_4 NeoPixelBusLg +#define B_32_IP_TM1_4 NeoPixelBusLg // parallel I2S //TM1829 (RGB) #define B_32_RN_TM2_3 NeoPixelBusLg -#define B_32_I0_TM2_3 NeoPixelBusLg -#define B_32_I1_TM2_3 NeoPixelBusLg -#define B_32_I1_TM2_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_TM2_3 NeoPixelBusLg +#define B_32_IP_TM2_3 NeoPixelBusLg // parallel I2S //UCS8903 #define B_32_RN_UCS_3 NeoPixelBusLg -#define B_32_I0_UCS_3 NeoPixelBusLg -#define B_32_I1_UCS_3 NeoPixelBusLg -#define B_32_I1_UCS_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_UCS_3 NeoPixelBusLg +#define B_32_IP_UCS_3 NeoPixelBusLg // parallel I2S //UCS8904 #define B_32_RN_UCS_4 NeoPixelBusLg -#define B_32_I0_UCS_4 NeoPixelBusLg -#define B_32_I1_UCS_4 NeoPixelBusLg -#define B_32_I1_UCS_4P NeoPixelBusLg// parallel I2S +#define B_32_I2_UCS_4 NeoPixelBusLg +#define B_32_IP_UCS_4 NeoPixelBusLg// parallel I2S //APA106 #define B_32_RN_APA106_3 NeoPixelBusLg -#define B_32_I0_APA106_3 NeoPixelBusLg -#define B_32_I1_APA106_3 NeoPixelBusLg -#define B_32_I1_APA106_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_APA106_3 NeoPixelBusLg +#define B_32_IP_APA106_3 NeoPixelBusLg // parallel I2S //FW1906 GRBCW #define B_32_RN_FW6_5 NeoPixelBusLg -#define B_32_I0_FW6_5 NeoPixelBusLg -#define B_32_I1_FW6_5 NeoPixelBusLg -#define B_32_I1_FW6_5P NeoPixelBusLg // parallel I2S +#define B_32_I2_FW6_5 NeoPixelBusLg +#define B_32_IP_FW6_5 NeoPixelBusLg // parallel I2S //WS2805 RGBWC #define B_32_RN_2805_5 NeoPixelBusLg -#define B_32_I0_2805_5 NeoPixelBusLg -#define B_32_I1_2805_5 NeoPixelBusLg -#define B_32_I1_2805_5P NeoPixelBusLg // parallel I2S +#define B_32_I2_2805_5 NeoPixelBusLg +#define B_32_IP_2805_5 NeoPixelBusLg // parallel I2S //TM1914 (RGB) #define B_32_RN_TM1914_3 NeoPixelBusLg -#define B_32_I0_TM1914_3 NeoPixelBusLg -#define B_32_I1_TM1914_3 NeoPixelBusLg -#define B_32_I1_TM1914_3P NeoPixelBusLg // parallel I2S +#define B_32_I2_TM1914_3 NeoPixelBusLg +#define B_32_IP_TM1914_3 NeoPixelBusLg // parallel I2S //Sm16825 (RGBWC) #define B_32_RN_SM16825_5 NeoPixelBusLg -#define B_32_I0_SM16825_5 NeoPixelBusLg -#define B_32_I1_SM16825_5 NeoPixelBusLg -#define B_32_I1_SM16825_5P NeoPixelBusLg // parallel I2S +#define B_32_I2_SM16825_5 NeoPixelBusLg +#define B_32_IP_SM16825_5 NeoPixelBusLg // parallel I2S #endif //APA102 @@ -328,11 +331,11 @@ //handles pointer type conversion for all possible bus types class PolyBus { private: - static bool useParallelI2S; + static bool _useParallelI2S; public: - static inline void setParallelI2S1Output(bool b = true) { useParallelI2S = b; } - static inline bool isParallelI2S1Output(void) { return useParallelI2S; } + static inline void setParallelI2S1Output(bool b = true) { _useParallelI2S = b; } + static inline bool isParallelI2S1Output(void) { return _useParallelI2S; } // initialize SPI bus speed for DotStar methods template @@ -436,34 +439,19 @@ class PolyBus { case I_32_RN_TM1914_3: beginTM1914(busPtr); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->Begin(); break; // I2S1 bus or parellel buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_TM1_4: if (useParallelI2S) beginTM1814(busPtr); else beginTM1814(busPtr); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - case I_32_I1_TM1914_3: if (useParallelI2S) beginTM1914(busPtr); else beginTM1914(busPtr); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; - case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_TM1_4: beginTM1814(busPtr); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->Begin(); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->Begin(); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->Begin(); break; - case I_32_I0_2805_5: (static_cast(busPtr))->Begin(); break; - case I_32_I0_TM1914_3: beginTM1914(busPtr); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->Begin(); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_TM1_4: if (_useParallelI2S) beginTM1814(busPtr); else beginTM1814(busPtr); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) beginTM1914(busPtr); else beginTM1914(busPtr); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->Begin(); else (static_cast(busPtr))->Begin(); break; #endif // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: beginDotStar(busPtr, pins[1], -1, pins[0], -1, clock_kHz); break; @@ -484,8 +472,8 @@ class PolyBus { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation - if (useParallelI2S && channel > 7) channel -= 8; // accommodate parallel I2S1 which is used 1st on classic ESP32 - else if (channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + if (!_useParallelI2S && channel > 0) channel--; // accommodate I2S1 which is used as 1st bus on classic ESP32 + // if user selected parallel I2S, RMT is used 1st (8 channels) followed by parallel I2S (8 channels) #endif void* busPtr = nullptr; switch (busType) { @@ -555,34 +543,19 @@ class PolyBus { case I_32_RN_TM1914_3: busPtr = new B_32_RN_TM1914_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_SM16825_5: busPtr = new B_32_RN_SM16825_5(len, pins[0], (NeoBusChannel)channel); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) busPtr = new B_32_I1_NEO_3P(len, pins[0]); else busPtr = new B_32_I1_NEO_3(len, pins[0]); break; - case I_32_I1_NEO_4: if (useParallelI2S) busPtr = new B_32_I1_NEO_4P(len, pins[0]); else busPtr = new B_32_I1_NEO_4(len, pins[0]); break; - case I_32_I1_400_3: if (useParallelI2S) busPtr = new B_32_I1_400_3P(len, pins[0]); else busPtr = new B_32_I1_400_3(len, pins[0]); break; - case I_32_I1_TM1_4: if (useParallelI2S) busPtr = new B_32_I1_TM1_4P(len, pins[0]); else busPtr = new B_32_I1_TM1_4(len, pins[0]); break; - case I_32_I1_TM2_3: if (useParallelI2S) busPtr = new B_32_I1_TM2_3P(len, pins[0]); else busPtr = new B_32_I1_TM2_3(len, pins[0]); break; - case I_32_I1_UCS_3: if (useParallelI2S) busPtr = new B_32_I1_UCS_3P(len, pins[0]); else busPtr = new B_32_I1_UCS_3(len, pins[0]); break; - case I_32_I1_UCS_4: if (useParallelI2S) busPtr = new B_32_I1_UCS_4P(len, pins[0]); else busPtr = new B_32_I1_UCS_4(len, pins[0]); break; - case I_32_I1_APA106_3: if (useParallelI2S) busPtr = new B_32_I1_APA106_3P(len, pins[0]); else busPtr = new B_32_I1_APA106_3(len, pins[0]); break; - case I_32_I1_FW6_5: if (useParallelI2S) busPtr = new B_32_I1_FW6_5P(len, pins[0]); else busPtr = new B_32_I1_FW6_5(len, pins[0]); break; - case I_32_I1_2805_5: if (useParallelI2S) busPtr = new B_32_I1_2805_5P(len, pins[0]); else busPtr = new B_32_I1_2805_5(len, pins[0]); break; - case I_32_I1_TM1914_3: if (useParallelI2S) busPtr = new B_32_I1_TM1914_3P(len, pins[0]); else busPtr = new B_32_I1_TM1914_3(len, pins[0]); break; - case I_32_I1_SM16825_5: if (useParallelI2S) busPtr = new B_32_I1_SM16825_5P(len, pins[0]); else busPtr = new B_32_I1_SM16825_5(len, pins[0]); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break; - case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; - case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; - case I_32_I0_TM1_4: busPtr = new B_32_I0_TM1_4(len, pins[0]); break; - case I_32_I0_TM2_3: busPtr = new B_32_I0_TM2_3(len, pins[0]); break; - case I_32_I0_UCS_3: busPtr = new B_32_I0_UCS_3(len, pins[0]); break; - case I_32_I0_UCS_4: busPtr = new B_32_I0_UCS_4(len, pins[0]); break; - case I_32_I0_APA106_3: busPtr = new B_32_I0_APA106_3(len, pins[0]); break; - case I_32_I0_FW6_5: busPtr = new B_32_I0_FW6_5(len, pins[0]); break; - case I_32_I0_2805_5: busPtr = new B_32_I0_2805_5(len, pins[0]); break; - case I_32_I0_TM1914_3: busPtr = new B_32_I0_TM1914_3(len, pins[0]); break; - case I_32_I0_SM16825_5: busPtr = new B_32_I0_SM16825_5(len, pins[0]); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) busPtr = new B_32_IP_NEO_3(len, pins[0]); else busPtr = new B_32_I2_NEO_3(len, pins[0]); break; + case I_32_I2_NEO_4: if (_useParallelI2S) busPtr = new B_32_IP_NEO_4(len, pins[0]); else busPtr = new B_32_I2_NEO_4(len, pins[0]); break; + case I_32_I2_400_3: if (_useParallelI2S) busPtr = new B_32_IP_400_3(len, pins[0]); else busPtr = new B_32_I2_400_3(len, pins[0]); break; + case I_32_I2_TM1_4: if (_useParallelI2S) busPtr = new B_32_IP_TM1_4(len, pins[0]); else busPtr = new B_32_I2_TM1_4(len, pins[0]); break; + case I_32_I2_TM2_3: if (_useParallelI2S) busPtr = new B_32_IP_TM2_3(len, pins[0]); else busPtr = new B_32_I2_TM2_3(len, pins[0]); break; + case I_32_I2_UCS_3: if (_useParallelI2S) busPtr = new B_32_IP_UCS_3(len, pins[0]); else busPtr = new B_32_I2_UCS_3(len, pins[0]); break; + case I_32_I2_UCS_4: if (_useParallelI2S) busPtr = new B_32_IP_UCS_4(len, pins[0]); else busPtr = new B_32_I2_UCS_4(len, pins[0]); break; + case I_32_I2_APA106_3: if (_useParallelI2S) busPtr = new B_32_IP_APA106_3(len, pins[0]); else busPtr = new B_32_I2_APA106_3(len, pins[0]); break; + case I_32_I2_FW6_5: if (_useParallelI2S) busPtr = new B_32_IP_FW6_5(len, pins[0]); else busPtr = new B_32_I2_FW6_5(len, pins[0]); break; + case I_32_I2_2805_5: if (_useParallelI2S) busPtr = new B_32_IP_2805_5(len, pins[0]); else busPtr = new B_32_I2_2805_5(len, pins[0]); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) busPtr = new B_32_IP_TM1914_3(len, pins[0]); else busPtr = new B_32_I2_TM1914_3(len, pins[0]); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) busPtr = new B_32_IP_SM16825_5(len, pins[0]); else busPtr = new B_32_I2_SM16825_5(len, pins[0]); break; #endif #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) @@ -669,34 +642,19 @@ class PolyBus { case I_32_RN_TM1914_3: (static_cast(busPtr))->Show(consistent); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->Show(consistent); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_400_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_2805_5: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_TM1914_3: (static_cast(busPtr))->Show(consistent); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->Show(consistent); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->Show(consistent); else (static_cast(busPtr))->Show(consistent); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(consistent); break; @@ -743,6 +701,7 @@ class PolyBus { case I_8266_U0_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_U1_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_UCS_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_UCS_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_U0_APA106_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_U1_APA106_3: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_APA106_3: return (static_cast(busPtr))->CanShow(); break; @@ -779,34 +738,19 @@ class PolyBus { case I_32_RN_TM1914_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_SM16825_5: return (static_cast(busPtr))->CanShow(); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_NEO_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_400_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM1_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM2_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_UCS_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_UCS_4: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_APA106_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_FW6_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_2805_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_TM1914_3: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - case I_32_I1_SM16825_5: if (useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM1_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM2_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_UCS_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_UCS_4: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_APA106_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_FW6_5: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_2805_5: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_TM1914_3: return (static_cast(busPtr))->CanShow(); break; - case I_32_I0_SM16825_5: return (static_cast(busPtr))->CanShow(); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_NEO_4: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_400_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_TM1_4: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_TM2_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_UCS_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_UCS_4: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_APA106_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_FW6_5: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_2805_5: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) return (static_cast(busPtr))->CanShow(); else return (static_cast(busPtr))->CanShow(); break; #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -823,7 +767,7 @@ class PolyBus { return true; } - static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { + [[gnu::hot]] static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { uint8_t r = c >> 16; uint8_t g = c >> 8; uint8_t b = c >> 0; @@ -916,34 +860,19 @@ class PolyBus { case I_32_RN_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I0_2805_5: (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; - case I_32_I0_TM1914_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgb48Color(RgbColor(col))); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbw64Color(col)); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); else (static_cast(busPtr))->SetPixelColor(pix, RgbwwColor(col.R, col.G, col.B, cctWW, cctCW)); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); else (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); else (static_cast(busPtr))->SetPixelColor(pix, Rgbww80Color(col.R*257, col.G*257, col.B*257, cctWW*257, cctCW*257)); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col)); break; @@ -1027,34 +956,19 @@ class PolyBus { case I_32_RN_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; case I_32_RN_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_NEO_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_400_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_TM1_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_TM2_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_UCS_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_UCS_4: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_APA106_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_FW6_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_2805_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_TM1914_3: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I1_SM16825_5: if (useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_400_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I0_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; @@ -1070,7 +984,7 @@ class PolyBus { } } - static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { + [[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { RgbwColor col(0,0,0,0); switch (busType) { case I_NONE: break; @@ -1139,34 +1053,19 @@ class PolyBus { case I_32_RN_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_SM16825_5: { Rgbww80Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_NEO_4: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_400_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_TM1_4: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_TM2_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_UCS_3: { Rgb48Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,0); } break; - case I_32_I1_UCS_4: { Rgbw64Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,c.W/257); } break; - case I_32_I1_APA106_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_FW6_5: { RgbwwColor c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I1_2805_5: { RgbwwColor c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I1_TM1914_3: col = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I1_SM16825_5: { Rgbww80Color c = (useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_UCS_3: { Rgb48Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,0); } break; - case I_32_I0_UCS_4: { Rgbw64Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,c.W/257); } break; - case I_32_I0_APA106_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_FW6_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I0_2805_5: { RgbwwColor c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W - case I_32_I0_TM1914_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - case I_32_I0_SM16825_5: { Rgbww80Color c = (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_NEO_4: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_400_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_TM1_4: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_TM2_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_UCS_3: { Rgb48Color c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,0); } break; + case I_32_I2_UCS_4: { Rgbw64Color c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,c.W/257); } break; + case I_32_I2_APA106_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_FW6_5: { RgbwwColor c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_I2_2805_5: { RgbwwColor c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R,c.G,c.B,max(c.WW,c.CW)); } break; // will not return original W + case I_32_I2_TM1914_3: col = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I2_SM16825_5: { Rgbww80Color c = (_useParallelI2S) ? (static_cast(busPtr))->GetPixelColor(pix) : (static_cast(busPtr))->GetPixelColor(pix); col = RGBW32(c.R/257,c.G/257,c.B/257,max(c.WW,c.CW)/257); } break; // will not return original W #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -1269,34 +1168,19 @@ class PolyBus { case I_32_RN_TM1914_3: delete (static_cast(busPtr)); break; case I_32_RN_SM16825_5: delete (static_cast(busPtr)); break; // I2S1 bus or paralell buses - #ifndef WLED_NO_I2S1_PIXELBUS - case I_32_I1_NEO_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_NEO_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_400_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_TM1_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_TM2_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_UCS_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_UCS_4: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_APA106_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_FW6_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_2805_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_TM1914_3: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - case I_32_I1_SM16825_5: if (useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; - #endif - // I2S0 bus - #ifndef WLED_NO_I2S0_PIXELBUS - case I_32_I0_NEO_3: delete (static_cast(busPtr)); break; - case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; - case I_32_I0_400_3: delete (static_cast(busPtr)); break; - case I_32_I0_TM1_4: delete (static_cast(busPtr)); break; - case I_32_I0_TM2_3: delete (static_cast(busPtr)); break; - case I_32_I0_UCS_3: delete (static_cast(busPtr)); break; - case I_32_I0_UCS_4: delete (static_cast(busPtr)); break; - case I_32_I0_APA106_3: delete (static_cast(busPtr)); break; - case I_32_I0_FW6_5: delete (static_cast(busPtr)); break; - case I_32_I0_2805_5: delete (static_cast(busPtr)); break; - case I_32_I0_TM1914_3: delete (static_cast(busPtr)); break; - case I_32_I0_SM16825_5: delete (static_cast(busPtr)); break; + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_NEO_4: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_400_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_TM1_4: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_TM2_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_UCS_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_UCS_4: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_APA106_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_FW6_5: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_2805_5: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_TM1914_3: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; + case I_32_I2_SM16825_5: if (_useParallelI2S) delete (static_cast(busPtr)); else delete (static_cast(busPtr)); break; #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; @@ -1312,8 +1196,178 @@ class PolyBus { } } + static unsigned getDataSize(void* busPtr, uint8_t busType) { + unsigned size = 0; + switch (busType) { + case I_NONE: break; + #ifdef ESP8266 + case I_8266_U0_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_NEO_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_NEO_4: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_400_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_TM1_4: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_TM2_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_UCS_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_UCS_4: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_APA106_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_FW6_5: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_2805_5: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U1_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_DM_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*5; break; + case I_8266_BB_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + // RMT buses (front + back + small system managed RMT) + case I_32_RN_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_32_RN_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + // I2S1 bus or paralell buses (front + DMA; DMA = front * cadence, aligned to 4 bytes) + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_NEO_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_400_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM2_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_UCS_4: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_APA106_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_FW6_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_2805_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_TM1914_3: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + case I_32_I2_SM16825_5: size = (_useParallelI2S) ? (static_cast(busPtr))->PixelsSize()*4 : (static_cast(busPtr))->PixelsSize()*4; break; + #endif + #endif + case I_HS_DOT_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_DOT_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_LPD_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_LPD_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_LPO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_LPO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_WS1_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_WS1_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_HS_P98_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_SS_P98_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + } + return size; + } + + static unsigned memUsage(unsigned count, unsigned busType) { + unsigned size = count*3; // let's assume 3 channels, we will add count or 2*count below for 4 channels or 5 channels + switch (busType) { + case I_NONE: size = 0; break; + #ifdef ESP8266 + // UART methods have front + back buffers + small UART + case I_8266_U0_NEO_4: size = (size + count)*2; break; // 4 channels + case I_8266_U1_NEO_4: size = (size + count)*2; break; // 4 channels + case I_8266_BB_NEO_4: size = (size + count)*2; break; // 4 channels + case I_8266_U0_TM1_4: size = (size + count)*2; break; // 4 channels + case I_8266_U1_TM1_4: size = (size + count)*2; break; // 4 channels + case I_8266_BB_TM1_4: size = (size + count)*2; break; // 4 channels + case I_8266_U0_UCS_3: size *= 4; break; // 16 bit + case I_8266_U1_UCS_3: size *= 4; break; // 16 bit + case I_8266_BB_UCS_3: size *= 4; break; // 16 bit + case I_8266_U0_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_U1_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_BB_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels + case I_8266_U0_FW6_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U1_FW6_5: size = (size + 2*count)*2; break; // 5channels + case I_8266_BB_FW6_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U0_2805_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U1_2805_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_BB_2805_5: size = (size + 2*count)*2; break; // 5 channels + case I_8266_U0_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels + case I_8266_U1_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels + case I_8266_BB_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels + // DMA methods have front + DMA buffer = ((1+(3+1)) * channels) + case I_8266_DM_NEO_3: size *= 5; break; + case I_8266_DM_NEO_4: size = (size + count)*5; break; + case I_8266_DM_400_3: size *= 5; break; + case I_8266_DM_TM1_4: size = (size + count)*5; break; + case I_8266_DM_TM2_3: size *= 5; break; + case I_8266_DM_UCS_3: size *= 2*5; break; + case I_8266_DM_UCS_4: size = (size + count)*2*5; break; + case I_8266_DM_APA106_3: size *= 5; break; + case I_8266_DM_FW6_5: size = (size + 2*count)*5; break; + case I_8266_DM_2805_5: size = (size + 2*count)*5; break; + case I_8266_DM_TM1914_3: size *= 5; break; + case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break; + #endif + #ifdef ARDUINO_ARCH_ESP32 + // RMT buses (1x front and 1x back buffer) + case I_32_RN_NEO_4: size = (size + count)*2; break; + case I_32_RN_TM1_4: size = (size + count)*2; break; + case I_32_RN_UCS_3: size *= 2*2; break; + case I_32_RN_UCS_4: size = (size + count)*2*2; break; + case I_32_RN_FW6_5: size = (size + 2*count)*2; break; + case I_32_RN_2805_5: size = (size + 2*count)*2; break; + case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; + // I2S1 bus or paralell buses (individual 1x front and 1 DMA (3x or 4x pixel count) or common back DMA buffers) + #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_I2_NEO_3: size *= 4; break; + case I_32_I2_NEO_4: size = (size + count)*4; break; + case I_32_I2_400_3: size *= 4; break; + case I_32_I2_TM1_4: size = (size + count)*4; break; + case I_32_I2_TM2_3: size *= 4; break; + case I_32_I2_UCS_3: size *= 2*4; break; + case I_32_I2_UCS_4: size = (size + count)*2*4; break; + case I_32_I2_APA106_3: size *= 4; break; + case I_32_I2_FW6_5: size = (size + 2*count)*4; break; + case I_32_I2_2805_5: size = (size + 2*count)*4; break; + case I_32_I2_TM1914_3: size *= 4; break; + case I_32_I2_SM16825_5: size = (size + 2*count)*2*4; break; + #endif + #endif + // everything else uses 2 buffers + default: size *= 2; break; + } + return size; + } + //gives back the internal type index (I_XX_XXX_X above) for the input - static uint8_t getI(uint8_t busType, uint8_t* pins, uint8_t num = 0) { + static uint8_t getI(uint8_t busType, const uint8_t* pins, uint8_t num = 0) { if (!Bus::isDigital(busType)) return I_NONE; if (Bus::is2Pin(busType)) { //SPI LED chips bool isHSPI = false; @@ -1372,26 +1426,33 @@ class PolyBus { uint8_t offset = 0; // 0 = RMT (num 1-8), 1 = I2S0 (used by Audioreactive), 2 = I2S1 #if defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32-S2 only has 4 RMT channels - if (num > 4) return I_NONE; - if (num > 3) offset = 1; // only one I2S (use last to allow Audioreactive) + if (_useParallelI2S) { + if (num > 11) return I_NONE; + if (num > 3) offset = 1; // use x8 parallel I2S0 channels (use last to allow Audioreactive) + } else { + if (num > 4) return I_NONE; + if (num > 3) offset = 1; // only one I2S0 (use last to allow Audioreactive) + } #elif defined(CONFIG_IDF_TARGET_ESP32C3) // On ESP32-C3 only the first 2 RMT channels are usable for transmitting if (num > 1) return I_NONE; //if (num > 1) offset = 1; // I2S not supported yet (only 1 I2S) #elif defined(CONFIG_IDF_TARGET_ESP32S3) // On ESP32-S3 only the first 4 RMT channels are usable for transmitting - if (num > 3) return I_NONE; - //if (num > 3) offset = num -4; // I2S not supported yet + if (_useParallelI2S) { + if (num > 11) return I_NONE; + if (num > 3) offset = 1; // use x8 parallel I2S LCD channels + } else { + if (num > 3) return I_NONE; // do not use single I2S (as it is not supported) + } #else - // standard ESP32 has 8 RMT and 2 I2S channels - if (useParallelI2S) { - if (num > 16) return I_NONE; - if (num < 8) offset = 2; // prefer 8 parallel I2S1 channels - if (num == 16) offset = 1; + // standard ESP32 has 8 RMT and x1/x8 I2S1 channels + if (_useParallelI2S) { + if (num > 15) return I_NONE; + if (num > 7) offset = 1; // 8 RMT followed by 8 I2S } else { if (num > 9) return I_NONE; - if (num > 8) offset = 1; - if (num == 0) offset = 2; // prefer I2S1 for 1st bus (less flickering but more RAM needed) + if (num == 0) offset = 1; // prefer I2S1 for 1st bus (less flickering but more RAM needed) } #endif switch (busType) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 3f6cfbacb6..478629c73e 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -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 @@ -824,6 +786,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 +813,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(); diff --git a/wled00/const.h b/wled00/const.h index 07873deca1..ff152beb62 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -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 diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 62dfe99b61..efa33367ea 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -42,10 +42,10 @@ 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 + oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S): 19 - 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) @@ -250,6 +250,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 +258,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? @@ -295,8 +297,7 @@ // 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 +351,17 @@ else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff"; } }); + const S2 = (oMaxB == 14) && (maxV == 4); + const S3 = (oMaxB == 14) && (maxV == 6); + if (oMaxB == 19 || S2 || S3) { // TODO: crude ESP32 & S2/S3 detection + if (maxLC > 300 || dC <= 2) { + d.Sf["PR"].checked = false; + gId("prl").classList.add("hide"); + } else + gId("prl").classList.remove("hide"); + maxD = (S2 || S3 ? 4 : 8) + (d.Sf["PR"].checked ? 8 : S2); // TODO: use bLimits() : 4/8RMT + (x1/x8 parallel) I2S1 + maxB = oMaxB - (d.Sf["PR"].checked ? 0 : 7 + S3); // S2 (maxV==3) does support single I2S + } // distribute ABL current if not using PPL enPPL(sDI); @@ -470,6 +482,7 @@ } } }); + 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() @@ -787,6 +800,7 @@

Hardware setup

Use less than 800 LEDs per output for the best experience!

+
Use parallel I2S:
Make a segment for each output:
Custom bus start indices:
Use global LED buffer:
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2e965a9af5..a4f19af458 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -1,3 +1,4 @@ +#pragma once #ifndef WLED_FCN_DECLARE_H #define WLED_FCN_DECLARE_H diff --git a/wled00/json.cpp b/wled00/json.cpp index 5f1bcceb9b..2f77f24ef7 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -117,7 +117,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); // strip needs to be suspended for this to work without issues if (newSeg) seg.refreshLightCapabilities(); // fix for #3403 diff --git a/wled00/set.cpp b/wled00/set.cpp index 2a6fdd3fb0..d7d927b71e 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -134,11 +134,13 @@ 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++) { @@ -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 diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 5173842a68..e38341ecc3 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -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(); } } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 3eeada5158..b24e0c5e9f 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -182,46 +182,7 @@ 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 + strip.finalizeInit(); // will create buses and also load default ledmap if present BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005 if (aligned) strip.makeAutoSegments(); else strip.fixInvalidSegments(); @@ -571,6 +532,7 @@ void WLED::beginStrip() strip.makeAutoSegments(); strip.setBrightness(0); strip.setShowCallback(handleOverlayDraw); + doInitBusses = false; if (turnOnAtBoot) { if (briS > 0) bri = briS; diff --git a/wled00/wled.h b/wled00/wled.h index db18f1b851..a00103481d 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -7,7 +7,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2501160 +#define VERSION 2502220 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -367,7 +367,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); @@ -394,6 +394,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(false); // parallel I2S for ESP32 + #endif #endif #ifdef WLED_USE_IC_CCT WLED_GLOBAL bool cctICused _INIT(true); // CCT IC used (Athom 15W bulbs) @@ -883,7 +886,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 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); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 2a19cdfabc..79ebf67852 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -289,6 +289,7 @@ 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++) { From 6572efbf9f8a977160b19f01fc48df24ccc8c434 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 22 Feb 2025 18:02:31 +0000 Subject: [PATCH 058/168] Update version to 0.15.1.beta2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34ecf32b0c..a1f3e3a064 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.1.beta1", + "version": "0.15.1.beta2", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { From 0a28acf6e7a979dd390169e2f2696c9eb20bd557 Mon Sep 17 00:00:00 2001 From: netmindz Date: Sat, 22 Feb 2025 12:00:16 +0000 Subject: [PATCH 059/168] Generate changelog --- .github/workflows/release.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27beec99c3..dfcb808de2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,16 @@ 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 - name: Create draft release uses: softprops/action-gh-release@v1 with: + body: ${{ steps.changelog.outputs.changelog }} draft: True files: | *.bin From d9629039a6f99855b9823717c09a407530ea8105 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Sat, 8 Mar 2025 12:01:36 +0100 Subject: [PATCH 060/168] add warning about unconfigurable outputs --- wled00/data/settings_leds.htm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index efa33367ea..1657cf7360 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -789,8 +789,9 @@

LED & Hardware setup

Hardware setup

-
LED outputs:
+
LED outputs:

+ Only last output can be changed. Remove to edit others.

LED memory usage: 0 / ? B
From ddec6fbb11d25e63383b8d2e39a842ff1716ef28 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Sat, 8 Mar 2025 16:14:53 +0100 Subject: [PATCH 061/168] swap tags --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 1657cf7360..7aca3de30b 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -791,7 +791,7 @@

LED & Hardware setup

Hardware setup

LED outputs:

- Only last output can be changed. Remove to edit others.
+ Only last output can be changed. Remove to edit others.

LED memory usage: 0 / ? B
From 4c01893bd84d598dc4ea70b1003f7f2b9e79fa09 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Mon, 10 Mar 2025 22:53:38 +0100 Subject: [PATCH 062/168] fix indentation --- wled00/data/settings_leds.htm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 7aca3de30b..5f9db08894 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -789,9 +789,11 @@

LED & Hardware setup

Hardware setup

-
LED outputs:
-
- Only last output can be changed. Remove to edit others.
+
+ LED outputs:
+
+ Only last output can be changed. Remove to edit others.
+

LED memory usage: 0 / ? B
From e16c4b868104a724faaec9245827f7a7b20db5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 29 Dec 2024 19:50:08 +0100 Subject: [PATCH 063/168] UI info --- wled00/data/index.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index c9ee23ad5e..388770cc6f 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -773,8 +773,8 @@ function populateSegments(s) } let segp = `
`+ - ``+ - `
`+ + ``+ + `
`+ ``+ `
`+ `
`+ @@ -810,7 +810,7 @@ function populateSegments(s) cn += `
`+ ``+ `
`+ `&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};`+ @@ -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 = `
` - + '' + + '' + `
Add segment
` + '
' + `` From 249c1241764d6e4e72999f7a566b20a4b8b92173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 31 Dec 2024 17:31:34 +0100 Subject: [PATCH 064/168] Clear spaced segment - also wait for strip before updating segment --- wled00/FX.h | 1 + wled00/FX_fcn.cpp | 67 ++++++++++++++++++++++---------- wled00/json.cpp | 97 ++++++++++++++++++++++++----------------------- 3 files changed, 97 insertions(+), 68 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index e36cad3072..747be80a72 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -596,6 +596,7 @@ typedef struct Segment { [[gnu::hot]] uint32_t getPixelColor(int i) const; // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); + void clear(); void fill(uint32_t c); void fade_out(uint8_t r); void fadeToBlackBy(uint8_t fadeBy); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index d0b13d7b8a..bd2787ad47 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -279,22 +279,27 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) { - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; - } + swapSegenv(_t->_segT); // copy runtime data to temporary + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; } } else { for (size_t i=0; i_segT._colorT[i] = colors[i]; } + DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), + (int)_t->_palTid, + (int)_t->_briT, + _t->_segT._colorT[0], + _t->_segT._colorT[1], + _t->_segT._colorT[2], + (int)_t->_modeT); #else for (size_t i=0; i_colorT[i] = colors[i]; #endif @@ -462,13 +467,16 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui #ifndef WLED_DISABLE_2D if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D #endif + + if (stop && (spc > 0 || m12 != map1D2D)) clear(); +/* 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,10 +486,7 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui } 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); + DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y); markForReset(); if (boundsUnchanged) return; @@ -1040,6 +1045,26 @@ void Segment::refreshLightCapabilities() { _capabilities = capabilities; } +/* + * Fills segment with black + */ +void Segment::clear() { + if (!isActive()) return; // not active + unsigned oldVW = _vWidth; + unsigned oldVH = _vHeight; + unsigned oldVL = _vLength; + unsigned oldSB = _segBri; + _vWidth = virtualWidth(); + _vHeight = virtualHeight(); + _vLength = virtualLength(); + _segBri = currentBri(); + fill(BLACK); + _vWidth = oldVW; + _vHeight = oldVH; + _vLength = oldVL; + _segBri = oldSB; +} + /* * Fills segment with color */ @@ -1364,7 +1389,7 @@ void WS2812FX::service() { _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(); @@ -1429,8 +1454,8 @@ void WS2812FX::service() { 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 ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); diff --git a/wled00/json.cpp b/wled00/json.cpp index 2f77f24ef7..bb2ab3dcaf 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -91,16 +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); + 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 uint8_t set = elem[F("set")] | seg.set; seg.set = constrain(set, 0, 3); @@ -206,20 +210,16 @@ 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; @@ -393,35 +393,38 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) int it = 0; JsonVariant segVar = root["seg"]; - if (!segVar.isNull()) strip.suspend(); - if (segVar.is()) - { - 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 start = millis(); + while (strip.isServicing() && millis() - start < strip.getFrameTime()) yield(); // wait until frame is over + #ifdef WLED_DEBUG + if (millis() - start > 0) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing.")); + #endif + if (segVar.is()) { + 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 - } - } else { - size_t deleted = 0; - JsonArray segs = segVar.as(); - for (JsonObject elem : segs) { - if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++; + size_t deleted = 0; + JsonArray segs = segVar.as(); + 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 } - if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments + strip.resume(); } - strip.resume(); UsermodManager::readFromJsonState(root); From 47a9e4aa51a8b6f0afa507a18deb405a32ae3080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 7 Jan 2025 20:39:44 +0100 Subject: [PATCH 065/168] Shifting bugfix & size tuning in fade_out --- wled00/FX_fcn.cpp | 88 +++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index bd2787ad47..0edc9333b8 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -279,30 +279,31 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - swapSegenv(_t->_segT); // copy runtime data to temporary - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; + if (modeBlending) { + swapSegenv(_t->_segT); // copy runtime data to temporary + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; + } + } else { + for (size_t i=0; i_segT._colorT[i] = colors[i]; } - } else { - for (size_t i=0; i_segT._colorT[i] = colors[i]; - } - DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), - (int)_t->_palTid, - (int)_t->_briT, - _t->_segT._colorT[0], - _t->_segT._colorT[1], - _t->_segT._colorT[2], - (int)_t->_modeT); -#else + DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), + (int)_t->_palTid, + (int)_t->_briT, + _t->_segT._colorT[0], + _t->_segT._colorT[1], + _t->_segT._colorT[2], + (int)_t->_modeT); + } else +# endif for (size_t i=0; i_colorT[i] = colors[i]; -#endif } void Segment::stopTransition() { @@ -1086,36 +1087,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< Date: Wed, 8 Jan 2025 21:20:07 +0100 Subject: [PATCH 066/168] W Hex entry bugfix & optiisation --- wled00/data/index.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index 388770cc6f..b008ba6027 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2653,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; @@ -2682,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); From 762679177c0d763c574753d0aeddbf0688ead925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 17 Jan 2025 16:05:26 +0100 Subject: [PATCH 067/168] Replace magic with cosntant --- wled00/wled.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index b24e0c5e9f..7b57b1daca 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -459,7 +459,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); From a38d6075c7de4d695ef23bdba7bbb9a959cc10e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 19 Jan 2025 21:30:17 +0100 Subject: [PATCH 068/168] Bugfix in settings --- wled00/data/settings_leds.htm | 21 ++++++++++++--------- wled00/set.cpp | 2 +- wled00/xml.cpp | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 5f9db08894..6c6811c2d1 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -24,6 +24,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, ()=>{ @@ -138,7 +139,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 +170,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 @@ -399,7 +400,7 @@ } 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 @@ -422,7 +423,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 @@ -506,7 +507,7 @@ 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 = `

${i+1}: Start:   @@ -560,7 +561,7 @@ 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: `; c += ` 
+ +

diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 7c074759e7..0520206949 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -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); diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp new file mode 100644 index 0000000000..9863fc66cc --- /dev/null +++ b/wled00/ota_update.cpp @@ -0,0 +1,252 @@ +#include "ota_update.h" +#include "wled.h" + +#ifdef ESP32 +#include +#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_custom_desc_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 releaseMetadataBuffer; +}; + + +static void endOTA(AsyncWebServerRequest *request) { + UpdateContext* context = reinterpret_cast(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(request->_tempObject); + if (!context) return; + context->replySent = true; +}; + +// Returns pointer to error message, or nullptr if OTA was successful. +std::pair getOTAResult(AsyncWebServerRequest* request) { + UpdateContext* context = reinterpret_cast(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(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; + } +} \ No newline at end of file diff --git a/wled00/ota_update.h b/wled00/ota_update.h new file mode 100644 index 0000000000..c8fd702643 --- /dev/null +++ b/wled00/ota_update.h @@ -0,0 +1,52 @@ +// WLED OTA update interface + +#include +#ifdef ESP8266 + #include +#else + #include +#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 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); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 0505095168..722182ee89 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -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 #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) @@ -164,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(); diff --git a/wled00/wled.h b/wled00/wled.h index 01e9eb850d..f29656e4c7 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -187,6 +187,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #include "pin_manager.h" #include "bus_manager.h" #include "FX.h" +#include "wled_metadata.h" #ifndef CLIENT_SSID #define CLIENT_SSID DEFAULT_CLIENT_SSID @@ -259,16 +260,6 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #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!) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp new file mode 100644 index 0000000000..2ff5c62d8f --- /dev/null +++ b/wled00/wled_metadata.cpp @@ -0,0 +1,144 @@ +#include "ota_update.h" +#include "wled.h" + +#define WLED_CUSTOM_DESC_MAGIC 0x57535453 // "WSTS" (WLED System Tag Structure) +#define WLED_CUSTOM_DESC_VERSION 1 +#define WLED_RELEASE_NAME_MAX_LEN 48 + +// 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_custom_desc_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::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_custom_desc_t* extractedDesc) { + if (!binaryData || !extractedDesc || dataSize < sizeof(wled_custom_desc_t)) { + return false; + } + + for (size_t offset = 0; offset <= dataSize - sizeof(wled_custom_desc_t); offset++) { + const wled_custom_desc_t* custom_desc = (const wled_custom_desc_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->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_custom_desc_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_custom_desc_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; +} diff --git a/wled00/wled_metadata.h b/wled00/wled_metadata.h new file mode 100644 index 0000000000..d82d75baf3 --- /dev/null +++ b/wled00/wled_metadata.h @@ -0,0 +1,71 @@ +/* + WLED build metadata + + Manages and exports information about the current WLED build. +*/ + + +#pragma once + +#include +#include +#include + +#ifndef WLED_VERSION + #define WLED_VERSION dev +#endif +#ifndef WLED_RELEASE_NAME + #define WLED_RELEASE_NAME "Custom" +#endif +#ifndef WLED_REPO + #define WLED_REPO "unknown" +#endif + +#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_custom_desc_t; + + +// Global build description +extern const wled_custom_desc_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_custom_desc_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_custom_desc_t& firmwareDescription, char* errorMessage, size_t errorMessageLen); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index e8cbb41ae5..1ae252c9e2 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -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); + 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 { - serveMessage(request, 200, F("Update successful!"), F("Rebooting..."), 131); - doReboot = true; + // 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 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 + },[](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){ diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 2e1e7fddf0..f95d7ff432 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -650,13 +650,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) UsermodManager::appendConfigData(settingsScript); } - if (subPage == SUBPAGE_UPDATE) // update - { - char tmp_buf[128]; - fillWLEDVersion(tmp_buf,sizeof(tmp_buf)); - printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf); - } - if (subPage == SUBPAGE_2D) // 2D matrices { printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix); From b552744a07308063ac8762072fb7f74f4d7b7e11 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 5 Oct 2025 23:36:16 -0400 Subject: [PATCH 127/168] 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. --- pio-scripts/set_metadata.py | 119 ++++++++++++++++++++++++++++++++++++ pio-scripts/set_version.py | 8 --- platformio.ini | 2 +- wled00/ota_update.cpp | 2 +- wled00/wled_metadata.cpp | 29 ++++++--- wled00/wled_metadata.h | 18 ++---- 6 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 pio-scripts/set_metadata.py delete mode 100644 pio-scripts/set_version.py diff --git a/pio-scripts/set_metadata.py b/pio-scripts/set_metadata.py new file mode 100644 index 0000000000..f89b424c54 --- /dev/null +++ b/pio-scripts/set_metadata.py @@ -0,0 +1,119 @@ +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 + +PACKAGE_FILE = "package.json" + +def get_version(): + with open(PACKAGE_FILE, "r") as package: + return json.load(package)["version"] + return None + + +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}\\\"")) + + if not has_def(cdefs, "WLED_VERSION"): + version = get_version() + if version: + cdefs.append(("WLED_VERSION", get_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" +) diff --git a/pio-scripts/set_version.py b/pio-scripts/set_version.py deleted file mode 100644 index 1d8e076ea8..0000000000 --- a/pio-scripts/set_version.py +++ /dev/null @@ -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}"]) diff --git a/platformio.ini b/platformio.ini index e757490242..8bb38fece3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp index 9863fc66cc..b678def1c9 100644 --- a/wled00/ota_update.cpp +++ b/wled00/ota_update.cpp @@ -32,7 +32,7 @@ static bool validateOTA(const uint8_t* binaryData, size_t dataSize, char* errorM } // Try to extract WLED structure directly from binary data - wled_custom_desc_t extractedDesc; + wled_metadata_t extractedDesc; bool hasDesc = findWledMetadata(binaryData, dataSize, &extractedDesc); if (hasDesc) { diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 2ff5c62d8f..13b54b9c09 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -1,6 +1,19 @@ #include "ota_update.h" #include "wled.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 + #define WLED_CUSTOM_DESC_MAGIC 0x57535453 // "WSTS" (WLED System Tag Structure) #define WLED_CUSTOM_DESC_VERSION 1 #define WLED_RELEASE_NAME_MAX_LEN 48 @@ -40,7 +53,7 @@ inline uint32_t djb2_hash_runtime(const char* str) { // GLOBAL VARIABLES // ------------------------------------ // Structure instantiation for this build -const wled_custom_desc_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUILD_DESCRIPTION = { +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), @@ -66,20 +79,20 @@ const __FlashStringHelper* brandString = FPSTR(brandString_s); * @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_custom_desc_t* extractedDesc) { - if (!binaryData || !extractedDesc || dataSize < sizeof(wled_custom_desc_t)) { +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_custom_desc_t); offset++) { - const wled_custom_desc_t* custom_desc = (const wled_custom_desc_t*)(binaryData + offset); + 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->version); + offset, custom_desc->desc_version); continue; } @@ -91,7 +104,7 @@ bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_custom_de } // Valid structure found - copy entire structure - memcpy(extractedDesc, custom_desc, sizeof(wled_custom_desc_t)); + memcpy(extractedDesc, custom_desc, sizeof(wled_metadata_t)); DEBUG_PRINTF_P(PSTR("Extracted WLED structure at offset %u: '%s'\n"), offset, extractedDesc->release_name); @@ -113,7 +126,7 @@ bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_custom_de * @return true if OTA should proceed, false if it should be blocked */ -bool shouldAllowOTA(const wled_custom_desc_t& firmwareDescription, char* errorMessage, size_t errorMessageLen) { +bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen) { // Clear error message if (errorMessage && errorMessageLen > 0) { errorMessage[0] = '\0'; diff --git a/wled00/wled_metadata.h b/wled00/wled_metadata.h index d82d75baf3..7ab4d09936 100644 --- a/wled00/wled_metadata.h +++ b/wled00/wled_metadata.h @@ -11,16 +11,6 @@ #include #include -#ifndef WLED_VERSION - #define WLED_VERSION dev -#endif -#ifndef WLED_RELEASE_NAME - #define WLED_RELEASE_NAME "Custom" -#endif -#ifndef WLED_REPO - #define WLED_REPO "unknown" -#endif - #define WLED_VERSION_MAX_LEN 48 #define WLED_RELEASE_NAME_MAX_LEN 48 @@ -36,11 +26,11 @@ typedef struct { 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_custom_desc_t; +} __attribute__((packed)) wled_metadata_t; // Global build description -extern const wled_custom_desc_t WLED_BUILD_DESCRIPTION; +extern const wled_metadata_t WLED_BUILD_DESCRIPTION; // Convenient metdata pointers #define versionString (WLED_BUILD_DESCRIPTION.wled_version) // Build version, WLED_VERSION @@ -59,7 +49,7 @@ extern const __FlashStringHelper* brandString ; // Brand * @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_custom_desc_t* extractedDesc); +bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_t* extractedDesc); /** * Check if OTA should be allowed based on release compatibility @@ -68,4 +58,4 @@ bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_custom_de * @param errorMessageLen Maximum length of error message buffer * @return true if OTA should proceed, false if it should be blocked */ -bool shouldAllowOTA(const wled_custom_desc_t& firmwareDescription, char* errorMessage, size_t errorMessageLen); +bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen); From 7e8bb20560856721d4150656096dd08c97094731 Mon Sep 17 00:00:00 2001 From: benjamw Date: Thu, 16 Oct 2025 22:30:51 -0600 Subject: [PATCH 128/168] Fix blank area issue with Twinkle --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d77bc7f29e..3fc5e85f7f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -580,7 +580,7 @@ uint16_t mode_twinkle(void) { SEGENV.step = it; } - unsigned PRNG16 = SEGENV.aux1; + uint16_t PRNG16 = SEGENV.aux1; for (unsigned i = 0; i < SEGENV.aux0; i++) { From 7b872bfff60e73b13c0b31d6dee05f7422d51f51 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 25 Oct 2025 09:57:18 -0400 Subject: [PATCH 129/168] Fix metadata includes --- wled00/wled_metadata.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 13b54b9c09..8398c1b01c 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -1,5 +1,6 @@ #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' @@ -14,9 +15,8 @@ #define WLED_REPO "unknown" #endif -#define WLED_CUSTOM_DESC_MAGIC 0x57535453 // "WSTS" (WLED System Tag Structure) -#define WLED_CUSTOM_DESC_VERSION 1 -#define WLED_RELEASE_NAME_MAX_LEN 48 +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, From d37cae8ad9f81799439177d02b56e0dd090b5c6a Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 25 Oct 2025 09:58:01 -0400 Subject: [PATCH 130/168] Fix set_metadata script --- pio-scripts/set_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pio-scripts/set_metadata.py b/pio-scripts/set_metadata.py index f89b424c54..5a1c1ae139 100644 --- a/pio-scripts/set_metadata.py +++ b/pio-scripts/set_metadata.py @@ -89,8 +89,8 @@ def has_def(cppdefs, name): for f in cppdefs: if isinstance(f, tuple): f = f[0] - if f == name: - return True + if f == name: + return True return False From 114f85f95eb4f358d13a2ecc20f4fc4cf95deb99 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 25 Oct 2025 13:43:10 -0400 Subject: [PATCH 131/168] set_metadata: Apply code fixes from @coderabbit --- pio-scripts/set_metadata.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pio-scripts/set_metadata.py b/pio-scripts/set_metadata.py index 5a1c1ae139..e023400528 100644 --- a/pio-scripts/set_metadata.py +++ b/pio-scripts/set_metadata.py @@ -79,9 +79,11 @@ def get_github_repo(): PACKAGE_FILE = "package.json" def get_version(): - with open(PACKAGE_FILE, "r") as package: - return json.load(package)["version"] - return None + try: + with open(PACKAGE_FILE, "r") as package: + return json.load(package)["version"] + except (FileNotFoundError, KeyError, json.JSONDecodeError): + return None def has_def(cppdefs, name): @@ -105,7 +107,7 @@ def add_wled_metadata_flags(env, node): if not has_def(cdefs, "WLED_VERSION"): version = get_version() if version: - cdefs.append(("WLED_VERSION", get_version())) + cdefs.append(("WLED_VERSION", version)) # This transforms the node in to a Builder; it cannot be modified again return env.Object( From 14ffde4fed909cd1722dc0752b3325b4aff1f690 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 25 Oct 2025 13:50:27 -0400 Subject: [PATCH 132/168] Fix bad backport merge on update.htm --- wled00/data/update.htm | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/data/update.htm b/wled00/data/update.htm index 76ed96de7b..267f2b63bc 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -40,6 +40,7 @@

WLED Software Update

Download the latest binary: 
+

From 9a32fea1a5311979f9ceca3f8ccef6dc4abba6ce Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 26 Oct 2025 17:53:27 -0400 Subject: [PATCH 133/168] Always use package.json for WLED_VERSION Ensures consistency between UI and metadata; fixes release bin names. --- pio-scripts/output_bins.py | 4 +++- pio-scripts/set_metadata.py | 21 ++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 4d1594d843..d369ed7927 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -2,6 +2,7 @@ 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}") diff --git a/pio-scripts/set_metadata.py b/pio-scripts/set_metadata.py index e023400528..7c8c223038 100644 --- a/pio-scripts/set_metadata.py +++ b/pio-scripts/set_metadata.py @@ -76,15 +76,13 @@ def get_github_repo(): # Any other unexpected error return None -PACKAGE_FILE = "package.json" - -def get_version(): - try: - with open(PACKAGE_FILE, "r") as package: - return json.load(package)["version"] - except (FileNotFoundError, KeyError, json.JSONDecodeError): - 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 """ @@ -104,10 +102,7 @@ def add_wled_metadata_flags(env, node): if repo: cdefs.append(("WLED_REPO", f"\\\"{repo}\\\"")) - if not has_def(cdefs, "WLED_VERSION"): - version = get_version() - if version: - cdefs.append(("WLED_VERSION", version)) + cdefs.append(("WLED_VERSION", WLED_VERSION)) # This transforms the node in to a Builder; it cannot be modified again return env.Object( From 7d0a33805822484b95a5b28b46f688fe75435ac3 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 8 Nov 2025 20:27:00 +0100 Subject: [PATCH 134/168] re-adding bootloop handling to ota_update --- wled00/ota_update.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wled00/ota_update.cpp b/wled00/ota_update.cpp index b678def1c9..2f3d6145e6 100644 --- a/wled00/ota_update.cpp +++ b/wled00/ota_update.cpp @@ -73,6 +73,9 @@ static void endOTA(AsyncWebServerRequest *request) { // If the upload is incomplete, Update.end(false) should error out. if (Update.end(context->uploadComplete)) { // Update successful! + #ifndef ESP8266 + bootloopCheckOTA(); // let the bootloop-checker know there was an OTA update + #endif doReboot = true; context->needsRestart = false; } @@ -109,6 +112,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context) strip.suspend(); strip.resetSegments(); // free as much memory as you can context->needsRestart = true; + backupConfig(); // backup current config in case the update ends badly DEBUG_PRINTF_P(PSTR("OTA Update Start, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context); From 0bc64b49b612e695ee6fb2ba2b3116c882e3c4c9 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 25 Oct 2025 09:57:03 -0400 Subject: [PATCH 135/168] Fix unaligned reads during metadata search --- wled00/wled_metadata.cpp | 67 ++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 8398c1b01c..19c83dda1c 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -80,40 +80,47 @@ const __FlashStringHelper* brandString = FPSTR(brandString_s); * @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; - } + 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); + for (size_t offset = 0; offset <= dataSize - sizeof(wled_metadata_t); offset++) { + if ((binaryData[offset]) == static_cast(WLED_CUSTOM_DESC_MAGIC)) { + // First byte matched; check next in an alignment-safe way + uint32_t data_magic; + memcpy(&data_magic, binaryData + offset, sizeof(data_magic)); + + // Check for magic number + if (data_magic == WLED_CUSTOM_DESC_MAGIC) { + wled_metadata_t candidate; + memcpy(&candidate, binaryData + offset, sizeof(candidate)); + + // Found potential match, validate version + if (candidate.desc_version != WLED_CUSTOM_DESC_VERSION) { + DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"), + offset, candidate.desc_version); + continue; + } - // 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; + // Validate hash using runtime function + uint32_t expected_hash = djb2_hash_runtime(candidate.release_name); + if (candidate.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 + *extractedDesc = candidate; + + 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; + } + + DEBUG_PRINTLN(F("No WLED custom description found in binary")); + return false; } From 4750ac501001e9c5bf6584df4c2a320cc981a101 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 8 Nov 2025 18:13:22 -0500 Subject: [PATCH 136/168] Add source version check to OTA update Add a field to the OTA metadata structure indicating the oldest base version it's safe to install this update /from/. This provides a clear path forward in case there are incompatibilities, eg. some case (bootloader compatibility) where 0.16.0 cannot be installed safely from 0.15.2, but a transitional 0.15.3 can arrange the groundwork. --- wled00/wled_metadata.cpp | 28 +++++++++++++++++++++++++--- wled00/wled_metadata.h | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 19c83dda1c..9ee19ade05 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -16,7 +16,7 @@ #endif constexpr uint32_t WLED_CUSTOM_DESC_MAGIC = 0x57535453; // "WSTS" (WLED System Tag Structure) -constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 1; +constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 2; // v1 - original PR; v2 - "safe to update from" version // Compile-time validation that release name doesn't exceed maximum length static_assert(sizeof(WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN, @@ -59,6 +59,11 @@ const wled_metadata_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUIL TOSTRING(WLED_VERSION), WLED_RELEASE_NAME, // release_name std::integral_constant::value, // hash - computed at compile time; integral_constant enforces this +#if defined(ESP32) && defined(CONFIG_IDF_TARGET_ESP32) + { 0, 15, 3 }, // Some older ESP32 might have bootloader issues; assume we'll have it sorted by 0.15.3 +#else + { 0, 15, 2 }, // All other platforms can update safely +#endif }; static const char repoString_s[] PROGMEM = WLED_REPO; @@ -96,7 +101,7 @@ bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_ memcpy(&candidate, binaryData + offset, sizeof(candidate)); // Found potential match, validate version - if (candidate.desc_version != WLED_CUSTOM_DESC_VERSION) { + if (candidate.desc_version > WLED_CUSTOM_DESC_VERSION) { DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"), offset, candidate.desc_version); continue; @@ -151,13 +156,30 @@ bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessa 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'."), + snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware release name mismatch: current='%s', uploaded='%s'."), releaseString, safeFirmwareRelease); errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination } return false; } + if (firmwareDescription.desc_version > 1) { + // Add safe version check + // Parse our version (x.y.z) and compare it to the "safe version" array + char* our_version = const_cast(versionString); // rip off const for legacy strtol compatibility + for(unsigned v_index = 0; v_index < 3; ++v_index) { + long our_v_parsed = strtol(our_version, &our_version, 10); + ++our_version; // skip the decimal point + if (firmwareDescription.safe_update_version[v_index] < our_v_parsed) { + snprintf_P(errorMessage, errorMessageLen, PSTR("Cannot update from this version: requires at least %d.%d.%d, current='%s'."), + firmwareDescription.safe_update_version[0], firmwareDescription.safe_update_version[1], firmwareDescription.safe_update_version[2], + versionString); + errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination + return false; + } + } + } + // TODO: additional checks go here return true; diff --git a/wled00/wled_metadata.h b/wled00/wled_metadata.h index 7ab4d09936..8c1dc0bb0f 100644 --- a/wled00/wled_metadata.h +++ b/wled00/wled_metadata.h @@ -26,6 +26,7 @@ typedef struct { 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 + uint8_t safe_update_version[3]; // Indicates version it's known to be safe to install this update from: major, minor, patch } __attribute__((packed)) wled_metadata_t; From 379f9c9ffa2fcc8dd632d0c99e7d122a5047ae88 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 8 Nov 2025 19:18:27 -0500 Subject: [PATCH 137/168] Implement correct update version check --- wled00/wled_metadata.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 9ee19ade05..05616a7294 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -166,17 +166,30 @@ bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessa if (firmwareDescription.desc_version > 1) { // Add safe version check // Parse our version (x.y.z) and compare it to the "safe version" array - char* our_version = const_cast(versionString); // rip off const for legacy strtol compatibility + const char* our_version = versionString; for(unsigned v_index = 0; v_index < 3; ++v_index) { - long our_v_parsed = strtol(our_version, &our_version, 10); - ++our_version; // skip the decimal point - if (firmwareDescription.safe_update_version[v_index] < our_v_parsed) { - snprintf_P(errorMessage, errorMessageLen, PSTR("Cannot update from this version: requires at least %d.%d.%d, current='%s'."), - firmwareDescription.safe_update_version[0], firmwareDescription.safe_update_version[1], firmwareDescription.safe_update_version[2], - versionString); - errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination + char* our_version_end = nullptr; + long our_v_parsed = strtol(our_version, &our_version_end, 10); + if (!our_version_end || (our_version_end == our_version)) { + // We were built with a malformed version string + // We blame the integrator and attempt the update anyways - nothing the user can do to fix this + break; + } + + if (firmwareDescription.safe_update_version[v_index] > our_v_parsed) { + if (errorMessage && errorMessageLen > 0) { + snprintf_P(errorMessage, errorMessageLen, PSTR("Cannot update from this version: requires at least %d.%d.%d, current='%s'."), + firmwareDescription.safe_update_version[0], firmwareDescription.safe_update_version[1], firmwareDescription.safe_update_version[2], + versionString); + errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination + } return false; + } else if (firmwareDescription.safe_update_version[v_index] < our_v_parsed) { + break; // no need to check the other components } + + if (*our_version_end == '.') ++our_version_end; + our_version = our_version_end; } } From 5a9bdad2ecd1c0282fd7ce54c7f6c71d92de3f7a Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 8 Nov 2025 19:33:13 -0500 Subject: [PATCH 138/168] Relax self update requirements Apply no restrictions in the 0.15 release line. --- wled00/wled_metadata.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/wled00/wled_metadata.cpp b/wled00/wled_metadata.cpp index 05616a7294..d1fd04bc49 100644 --- a/wled00/wled_metadata.cpp +++ b/wled00/wled_metadata.cpp @@ -59,11 +59,7 @@ const wled_metadata_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUIL TOSTRING(WLED_VERSION), WLED_RELEASE_NAME, // release_name std::integral_constant::value, // hash - computed at compile time; integral_constant enforces this -#if defined(ESP32) && defined(CONFIG_IDF_TARGET_ESP32) - { 0, 15, 3 }, // Some older ESP32 might have bootloader issues; assume we'll have it sorted by 0.15.3 -#else - { 0, 15, 2 }, // All other platforms can update safely -#endif + { 0, 0, 0 }, // All other platforms can update safely }; static const char repoString_s[] PROGMEM = WLED_REPO; From c8bda1abee6bf6869628c32f58333dadcc1272c1 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 9 Nov 2025 15:42:42 +0000 Subject: [PATCH 139/168] Bump version from 0.15.2-beta1 to 0.15.2-beta2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 56903bbf16..6e5472f454 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.2-beta1", + "version": "0.15.2-beta2", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { From e357c8aad35fcb48f763387c98388333603db221 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 13 Nov 2025 20:26:36 +0100 Subject: [PATCH 140/168] dynamic LED type selection, backport --- wled00/data/settings_leds.htm | 53 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 9c04a9cde3..6066062466 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -254,10 +254,10 @@ } // enable/disable LED fields + updateTypeDropdowns(); // restrict bus types in dropdowns to max allowed digital/analog buses 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) // is the field a LED type? var n = s.name.substring(2); var t = parseInt(s.value); @@ -414,17 +414,7 @@ { var o = gEBCN("iST"); var i = o.length; - let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); } - var f = gId("mLC"); - let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0; - f.querySelectorAll("select[name^=LT]").forEach((s)=>{ - let t = s.value; - if (isDig(t) && !isD2P(t)) digitalB++; - if (isD2P(t)) twopinB++; - if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel - if (isVir(t)) virtB++; - }); if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return; var s = chrID(i); @@ -434,7 +424,7 @@ var cn = `

${i+1}: -
+
mA/LED: