From 6655e2664e81e59a5ffbedf3a4121ab0f7c61d95 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Thu, 29 Aug 2024 13:18:48 +0200 Subject: [PATCH 01/50] rebase to bus-config --- wled00/FX_fcn.cpp | 39 +++++++++++++++++++++++++++------------ wled00/bus_manager.cpp | 2 +- wled00/bus_manager.h | 9 +++------ wled00/const.h | 7 +++++++ 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a06d4f84..69a742c3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -44,8 +44,8 @@ */ //factory defaults LED setup -//#define PIXEL_COUNTS 30, 30, 30, 30 -//#define DATA_PINS 16, 1, 3, 4 +//#define PIXEL_COUNTS 30 +//#define DATA_PINS 2 (8266/C3) or 16 //#define DEFAULT_LED_TYPE TYPE_WS2812_RGB #ifndef PIXEL_COUNTS @@ -56,8 +56,8 @@ #define DATA_PINS LEDPIN #endif -#ifndef DEFAULT_LED_TYPE - #define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#ifndef LED_TYPES + #define LED_TYPES DEFAULT_LED_TYPE #endif #ifndef DEFAULT_LED_COLOR_ORDER @@ -1215,17 +1215,30 @@ void WS2812FX::finalizeInit(void) { //if busses failed to load, add default (fresh install, FS issue, ...) if (BusManager::getNumBusses() == 0) { DEBUG_PRINTLN(F("No busses, init default")); + constexpr unsigned defDataTypes[] = {LED_TYPES}; const unsigned defDataPins[] = {DATA_PINS}; const unsigned defCounts[] = {PIXEL_COUNTS}; - const unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); + const unsigned defNumTypes = ((sizeof defDataTypes) / (sizeof defDataTypes[0])); + constexpr unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); - // if number of pins is divisible by counts, use number of counts to determine number of buses, otherwise use pins - const unsigned defNumBusses = defNumPins > defNumCounts && defNumPins%defNumCounts == 0 ? defNumCounts : defNumPins; - const unsigned pinsPerBus = defNumPins / defNumBusses; + + static_assert(Bus::getNumberOfPins(defDataTypes[0]) <= defNumPins, + "The first LED type configured requires more pins than have been defined!"); + unsigned prevLen = 0; - for (unsigned i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { - uint8_t defPin[5]; // max 5 pins - for (unsigned j = 0; j < pinsPerBus; j++) defPin[j] = defDataPins[i*pinsPerBus + j]; + unsigned pinsIndex = 0; + for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { + uint8_t defPin[OUTPUT_MAX_PINS]; // max 5 pins + // if we have less types than requested outputs and they do not align, use last known type to set current type + unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; + unsigned busPins = Bus::getNumberOfPins(dataType); + // check if we have enough pins left to configure an output of this type + if (pinsIndex + busPins > defNumPins) { + DEBUG_PRINTLN(F("LED outputs misaligned with defined pins. Some pins will remain unused.")); + break; + } + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; + pinsIndex += busPins; // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc if (pinManager.isPinAllocated(defPin[0])) { @@ -1235,8 +1248,10 @@ void WS2812FX::finalizeInit(void) { unsigned start = prevLen; // if we have less counts than pins and they do not align, use last known count to set current count unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; + // analog always has length 1 + if (Bus::isPWM(dataType)) count = 1; prevLen += count; - BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); + BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); if (BusManager::add(defCfg) == -1) break; } } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index e5918ce9..77e60ba4 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -673,7 +673,7 @@ void BusNetwork::cleanup(void) { //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 5; + 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); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 1d1136fd..f6aa4b57 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -267,8 +267,8 @@ class BusPwm : public Bus { void cleanup(void) { deallocatePins(); } private: - uint8_t _pins[5]; - uint8_t _pwmdata[5]; + uint8_t _pins[OUTPUT_MAX_PINS]; + uint8_t _pwmdata[OUTPUT_MAX_PINS]; #ifdef ARDUINO_ARCH_ESP32 uint8_t _ledcStart; #endif @@ -346,10 +346,7 @@ struct BusConfig { { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - size_t nPins = 1; - if (Bus::isVirtual(type)) nPins = 4; //virtual network bus. 4 "pins" store IP address - else if (Bus::is2Pin(type)) nPins = 2; - else if (Bus::isPWM(type)) nPins = Bus::numPWMPins(type); + size_t nPins = Bus::getNumberOfPins(type); for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; } diff --git a/wled00/const.h b/wled00/const.h index bdc80aab..cb50a108 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -466,6 +466,9 @@ #define NTP_PACKET_SIZE 48 // size of NTP receive buffer #define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields +// Maximum number of pins per output. 5 for RGBCCT analog LEDs. +#define OUTPUT_MAX_PINS 5 + //maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses #ifndef MAX_LEDS #ifdef ESP8266 @@ -577,6 +580,10 @@ #endif #endif +#ifndef DEFAULT_LED_TYPE + #define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#endif + #ifndef DEFAULT_LED_COUNT #define DEFAULT_LED_COUNT 30 #endif From fcc344ba995b6c39d8004d440057046fb5ecefa6 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Sun, 1 Sep 2024 21:31:19 +0200 Subject: [PATCH 02/50] add read/only pin logic and check --- wled00/FX_fcn.cpp | 26 +++++++++++++++----------- wled00/const.h | 13 +++++++++++++ wled00/pin_manager.cpp | 23 +++++++++++++++++++++++ wled00/pin_manager.h | 3 +++ wled00/xml.cpp | 20 +++++++++----------- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 69a742c3..331ceb07 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1216,11 +1216,11 @@ void WS2812FX::finalizeInit(void) { if (BusManager::getNumBusses() == 0) { DEBUG_PRINTLN(F("No busses, init default")); constexpr unsigned defDataTypes[] = {LED_TYPES}; - const unsigned defDataPins[] = {DATA_PINS}; - const unsigned defCounts[] = {PIXEL_COUNTS}; - const unsigned defNumTypes = ((sizeof defDataTypes) / (sizeof defDataTypes[0])); + constexpr unsigned defDataPins[] = {DATA_PINS}; + constexpr unsigned defCounts[] = {PIXEL_COUNTS}; + constexpr unsigned defNumTypes = ((sizeof defDataTypes) / (sizeof defDataTypes[0])); constexpr unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); - const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); + constexpr unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); static_assert(Bus::getNumberOfPins(defDataTypes[0]) <= defNumPins, "The first LED type configured requires more pins than have been defined!"); @@ -1237,14 +1237,18 @@ void WS2812FX::finalizeInit(void) { DEBUG_PRINTLN(F("LED outputs misaligned with defined pins. Some pins will remain unused.")); break; } - for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; - pinsIndex += busPins; - // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware - // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc - if (pinManager.isPinAllocated(defPin[0])) { - defPin[0] = 1; // start with GPIO1 and work upwards - while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { + defPin[j] = defDataPins[pinsIndex + j]; + + // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. + if (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) { + defPin[j] = 1; // start with GPIO1 and work upwards + while (pinManager.isPinAllocated(defPin[j]) && pinManager.isReadOnlyPin(defPin[j]) && defPin[j] < WLED_NUM_PINS) defPin[j]++; + } } + pinsIndex += busPins; + unsigned start = prevLen; // if we have less counts than pins and they do not align, use last known count to set current count unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; diff --git a/wled00/const.h b/wled00/const.h index cb50a108..ffefb3ca 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -572,6 +572,19 @@ #endif #endif +// List of read only pins. Cannot be used for LED outputs. +#if defined(CONFIG_IDF_TARGET_ESP32S2) + #define READ_ONLY_PINS 46 +#elif defined(CONFIG_IDF_TARGET_ESP32S3) +// none for S3 +#elif defined(CONFIG_IDF_TARGET_ESP32C3) +// none for C3 +#elif defined(ESP32) + #define READ_ONLY_PINS 34,35,36,37,38,39 +#else +// none for ESP8266 +#endif + #ifdef WLED_ENABLE_DMX #if (LEDPIN == 2) #undef LEDPIN diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 0a0f3915..507ce81c 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -267,6 +267,29 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const return false; } +unsigned *PinManagerClass::getReadOnlyPins() +{ + #ifdef READ_ONLY_PINS + static unsigned readOnlyPins[] = {READ_ONLY_PINS}; + #elif + static unsigned readOnlyPins[] = 255; + #endif + return readOnlyPins; +} + +bool PinManagerClass::isReadOnlyPin(byte gpio) +{ + const unsigned* pins = PinManagerClass::getReadOnlyPins(); + const unsigned numPins = sizeof(pins) / sizeof(pins[0]); + + for (unsigned i = 0; i < numPins; i++) { + if (pins[i] == gpio) { + return true; + } + } + return false; +} + PinOwner PinManagerClass::getPinOwner(byte gpio) const { if (!isPinOk(gpio, false)) return PinOwner::None; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 986964a7..b441b89a 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -112,6 +112,9 @@ class PinManagerClass { // will return false for reserved pins bool isPinOk(byte gpio, bool output = true) const; + static unsigned* getReadOnlyPins(); + static bool isReadOnlyPin(byte gpio); + PinOwner getPinOwner(byte gpio) const; #ifdef ARDUINO_ARCH_ESP32 diff --git a/wled00/xml.cpp b/wled00/xml.cpp index c06b0276..8f8dd9e7 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -191,17 +191,15 @@ void appendGPIOinfo() { // add info for read-only GPIO oappend(SET_F("d.ro_gpio=[")); - #if defined(CONFIG_IDF_TARGET_ESP32S2) - oappendi(46); - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - // none for S3 - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - // none for C3 - #elif defined(ESP32) - oappend(SET_F("34,35,36,37,38,39")); - #else - // none for ESP8266 - #endif + const unsigned* readOnlyPins = pinManager.getReadOnlyPins(); + const unsigned numReadOnlyPins = ((sizeof readOnlyPins) / (sizeof readOnlyPins[0])); + for (unsigned i = 0; i < numReadOnlyPins; i++) { + // Ignore 255 + if (readOnlyPins[i] <= WLED_NUM_PINS) { + oappendi(readOnlyPins[i]); + if (i != numReadOnlyPins) oappend(SET_F(",")); + } + } oappend(SET_F("];")); // add info about max. # of pins From c9423454531999027d6b7e50444a51d036cd5794 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Sun, 1 Sep 2024 21:35:09 +0200 Subject: [PATCH 03/50] bug fix --- wled00/pin_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 507ce81c..3ba48ead 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -271,7 +271,7 @@ unsigned *PinManagerClass::getReadOnlyPins() { #ifdef READ_ONLY_PINS static unsigned readOnlyPins[] = {READ_ONLY_PINS}; - #elif + #else static unsigned readOnlyPins[] = 255; #endif return readOnlyPins; From 9bb979f2e8cab910ad0a2ec8a7d3026b1f93413c Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Sun, 1 Sep 2024 21:46:30 +0200 Subject: [PATCH 04/50] bug fix --- wled00/pin_manager.cpp | 4 ++-- wled00/xml.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 3ba48ead..527e4470 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -272,7 +272,7 @@ unsigned *PinManagerClass::getReadOnlyPins() #ifdef READ_ONLY_PINS static unsigned readOnlyPins[] = {READ_ONLY_PINS}; #else - static unsigned readOnlyPins[] = 255; + static unsigned readOnlyPins[] = {255}; #endif return readOnlyPins; } @@ -280,7 +280,7 @@ unsigned *PinManagerClass::getReadOnlyPins() bool PinManagerClass::isReadOnlyPin(byte gpio) { const unsigned* pins = PinManagerClass::getReadOnlyPins(); - const unsigned numPins = sizeof(pins) / sizeof(pins[0]); + const unsigned numPins = (sizeof *pins) / (sizeof pins[0]); for (unsigned i = 0; i < numPins; i++) { if (pins[i] == gpio) { diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 8f8dd9e7..dfb3d67d 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -192,7 +192,7 @@ void appendGPIOinfo() { // add info for read-only GPIO oappend(SET_F("d.ro_gpio=[")); const unsigned* readOnlyPins = pinManager.getReadOnlyPins(); - const unsigned numReadOnlyPins = ((sizeof readOnlyPins) / (sizeof readOnlyPins[0])); + const unsigned numReadOnlyPins = (sizeof *readOnlyPins) / (sizeof readOnlyPins[0]); for (unsigned i = 0; i < numReadOnlyPins; i++) { // Ignore 255 if (readOnlyPins[i] <= WLED_NUM_PINS) { From d79d5dbadd99925d792f907464e8279808438c0a Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Tue, 3 Sep 2024 17:29:38 +0200 Subject: [PATCH 05/50] remove getReadOnlyPins() function --- wled00/pin_manager.cpp | 23 +++++++---------------- wled00/pin_manager.h | 3 +-- wled00/xml.cpp | 16 ++++++++-------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 527e4470..2bd82115 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -267,26 +267,17 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const return false; } -unsigned *PinManagerClass::getReadOnlyPins() -{ - #ifdef READ_ONLY_PINS - static unsigned readOnlyPins[] = {READ_ONLY_PINS}; - #else - static unsigned readOnlyPins[] = {255}; - #endif - return readOnlyPins; -} - bool PinManagerClass::isReadOnlyPin(byte gpio) { - const unsigned* pins = PinManagerClass::getReadOnlyPins(); - const unsigned numPins = (sizeof *pins) / (sizeof pins[0]); + #ifdef READ_ONLY_PINS + const unsigned pins[] = {READ_ONLY_PINS}; + const unsigned numPins = ((sizeof pins) / (sizeof pins[0])); - for (unsigned i = 0; i < numPins; i++) { - if (pins[i] == gpio) { - return true; - } + if (gpio <= WLED_NUM_PINS) { + for (unsigned i = 0; i < numPins; i++) if (gpio == pins[i]) return true; } + #endif + return false; } diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index b441b89a..a8ddf5f7 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -111,8 +111,7 @@ class PinManagerClass { bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None) const; // will return false for reserved pins bool isPinOk(byte gpio, bool output = true) const; - - static unsigned* getReadOnlyPins(); + static bool isReadOnlyPin(byte gpio); PinOwner getPinOwner(byte gpio) const; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dfb3d67d..fce453cf 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -191,14 +191,14 @@ void appendGPIOinfo() { // add info for read-only GPIO oappend(SET_F("d.ro_gpio=[")); - const unsigned* readOnlyPins = pinManager.getReadOnlyPins(); - const unsigned numReadOnlyPins = (sizeof *readOnlyPins) / (sizeof readOnlyPins[0]); - for (unsigned i = 0; i < numReadOnlyPins; i++) { - // Ignore 255 - if (readOnlyPins[i] <= WLED_NUM_PINS) { - oappendi(readOnlyPins[i]); - if (i != numReadOnlyPins) oappend(SET_F(",")); - } + bool firstPin = true; + for (unsigned i = 0; i < WLED_NUM_PINS; i++) { + if (pinManager.isReadOnlyPin(i)) { + // No comma before the first pin + if (!firstPin) oappend(SET_F(",")); + oappendi(i); + firstPin = false; + } } oappend(SET_F("];")); From c1c707c77ad8f97260557e953d1c7537700f5755 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:38:03 +0200 Subject: [PATCH 06/50] 8266 compatibility builds for older chips (another attempt t o solve #3690 and #3685) some users have reported that releases after 0.14.0 are not working reliably. So we add a few "compat" for 8266 that try to reproduce the buildenv of 0.14.0 as much as possible. * platform and platform_packages from 0.14.0 * not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 * due to smaller IRAM, we had to move some functions back from IRAM to normal flash (may cause slowdown) --- platformio.ini | 56 ++++++++++++++++++++++++++++++++++++++++++++- wled00/FX_2Dfcn.cpp | 6 ++--- wled00/FX_fcn.cpp | 16 ++++++------- wled00/const.h | 8 +++++++ 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/platformio.ini b/platformio.ini index 5ebc43f5..9888ea10 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # CI/release binaries -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover src_dir = ./wled00 data_dir = ./wled00/data @@ -205,6 +205,38 @@ lib_deps = ESP8266PWM ${env.lib_deps} +;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 +build_flags_compat = + -DESP8266 + -DFP_IN_IROM + ;;-Wno-deprecated-declarations + -Wno-misleading-indentation + ;;-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + -DVTABLES_IN_FLASH + -DMIMETYPE_MINIMAL + -DWLED_SAVE_IRAM ;; needed to prevent linker error + +;; this platform version was used for WLED 0.14.0 +platform_compat = espressif8266@4.2.0 +platform_packages_compat = + platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 + platformio/tool-esptool #@ ~1.413.0 + platformio/tool-esptoolpy #@ ~1.30000.0 + +;; experimental - for using older NeoPixelBus 2.7.9 +lib_deps_compat = + ESPAsyncTCP @ 1.2.2 + ESPAsyncUDP + ESP8266PWM + fastled/FastLED @ 3.6.0 + IRremoteESP8266 @ 2.8.2 + makuna/NeoPixelBus @ 2.7.9 + https://github.com/blazoncek/QuickESPNow.git#optional-debug + https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + + [esp32] #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 @@ -315,6 +347,14 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME= lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder +[env:nodemcuv2_compat] +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 +;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 + [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L @@ -329,6 +369,13 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} +[env:esp8266_2m_compat] +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 + [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L @@ -344,6 +391,13 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME= ; -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} +[env:esp01_1m_full_compat] +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 + [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 44919f92..efae0e5e 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -161,14 +161,14 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) +uint16_t IRAM_ATTR_YN Segment::XY(uint16_t x, uint16_t y) { unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; } -void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit @@ -264,7 +264,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) #endif // returns RGBW values of pixel -uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) const { +uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index bc5c8b05..7a6fa40b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -146,7 +146,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept { } // allocates effect data buffer on heap and initialises (erases) it -bool IRAM_ATTR Segment::allocateData(size_t len) { +bool IRAM_ATTR_YN Segment::allocateData(size_t len) { if (len == 0) return false; // nothing to do if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation @@ -170,7 +170,7 @@ bool IRAM_ATTR Segment::allocateData(size_t len) { return true; } -void IRAM_ATTR Segment::deallocateData() { +void IRAM_ATTR_YN Segment::deallocateData() { if (!data) { _dataLen = 0; return; } //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer @@ -202,7 +202,7 @@ void Segment::resetIfRequired() { reset = false; } -CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { +CRGBPalette16 IRAM_ATTR_YN &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip //default palette. Differs depending on effect @@ -417,7 +417,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const { return (useCct ? cct : (on ? opacity : 0)); } -uint8_t IRAM_ATTR Segment::currentMode() const { +uint8_t IRAM_ATTR_YN Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); if (modeBlending && prog < 0xFFFFU) return _t->_modeT; @@ -425,7 +425,7 @@ uint8_t IRAM_ATTR Segment::currentMode() const { return mode; } -uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) const { +uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const { if (slot >= NUM_COLORS) slot = 0; #ifndef WLED_DISABLE_MODE_BLEND return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; @@ -618,7 +618,7 @@ uint16_t IRAM_ATTR Segment::virtualHeight() const { return vHeight; } -uint16_t IRAM_ATTR Segment::nrOfVStrips() const { +uint16_t IRAM_ATTR_YN Segment::nrOfVStrips() const { unsigned vLen = 1; #ifndef WLED_DISABLE_2D if (is2D()) { @@ -701,7 +701,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } -void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D @@ -895,7 +895,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) } #endif -uint32_t IRAM_ATTR Segment::getPixelColor(int i) const +uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const { if (!isActive()) return 0; // not active #ifndef WLED_DISABLE_2D diff --git a/wled00/const.h b/wled00/const.h index d5cd049d..af6d4a70 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -628,4 +628,12 @@ #define HW_PIN_MISOSPI MISO #endif +// IRAM_ATTR for 8266 with 32Kb IRAM causes error: section `.text1' will not fit in region `iram1_0_seg' +// this hack removes the IRAM flag for some 1D/2D functions - somewhat slower, but it solves problems with some older 8266 chips +#ifdef WLED_SAVE_IRAM + #define IRAM_ATTR_YN +#else + #define IRAM_ATTR_YN IRAM_ATTR +#endif + #endif From 8e4f8fcbc8eb069306a7eca3c7750061734da8fc Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Wed, 4 Sep 2024 13:27:04 +0200 Subject: [PATCH 07/50] fix bug in reassignment logic --- wled00/FX_fcn.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 331ceb07..a737fb54 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1237,6 +1237,7 @@ void WS2812FX::finalizeInit(void) { DEBUG_PRINTLN(F("LED outputs misaligned with defined pins. Some pins will remain unused.")); break; } + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { defPin[j] = defDataPins[pinsIndex + j]; @@ -1244,7 +1245,7 @@ void WS2812FX::finalizeInit(void) { // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. if (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) { defPin[j] = 1; // start with GPIO1 and work upwards - while (pinManager.isPinAllocated(defPin[j]) && pinManager.isReadOnlyPin(defPin[j]) && defPin[j] < WLED_NUM_PINS) defPin[j]++; + while ((pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) && defPin[j] < WLED_NUM_PINS) defPin[j]++; } } pinsIndex += busPins; From 329173e1452846a419feb426bcbc041e9163cc93 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Wed, 4 Sep 2024 13:28:43 +0200 Subject: [PATCH 08/50] added complete validation logic for pins and types --- wled00/FX_fcn.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a737fb54..74d61256 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -69,6 +69,18 @@ #error "Max segments must be at least max number of busses!" #endif +static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) { + return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0; +} + +static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) { + // Pins provided < pins required -> always invalid + // Pins provided = pins required -> always valid + // Pins provided > pins required -> last type will repeat until we run out of pins, make sure excess pins modulo last type pins == 0 + return (sumPinsRequired(types, numTypes) > numPins) ? false : + (numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0; +} + /////////////////////////////////////////////////////////////////////////////// // Segment class implementation @@ -1222,9 +1234,9 @@ void WS2812FX::finalizeInit(void) { constexpr unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); constexpr unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); - static_assert(Bus::getNumberOfPins(defDataTypes[0]) <= defNumPins, - "The first LED type configured requires more pins than have been defined!"); - + static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), + "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); + unsigned prevLen = 0; unsigned pinsIndex = 0; for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { @@ -1232,7 +1244,9 @@ void WS2812FX::finalizeInit(void) { // if we have less types than requested outputs and they do not align, use last known type to set current type unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; unsigned busPins = Bus::getNumberOfPins(dataType); + // check if we have enough pins left to configure an output of this type + // should never happen due to static assert above if (pinsIndex + busPins > defNumPins) { DEBUG_PRINTLN(F("LED outputs misaligned with defined pins. Some pins will remain unused.")); break; @@ -1245,6 +1259,7 @@ void WS2812FX::finalizeInit(void) { // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. if (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) { defPin[j] = 1; // start with GPIO1 and work upwards + // @FIX pins are allocated after the loop, so if we reassign to a pin that's already in the array for this output 2 fields will have the same pin while ((pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) && defPin[j] < WLED_NUM_PINS) defPin[j]++; } } From b9080f9479fc18bc1b3ba28ba55ec546ea256e87 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 4 Sep 2024 21:17:21 +0200 Subject: [PATCH 09/50] Mirroring bugfix. --- wled00/FX_2Dfcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index efae0e5e..df2dc8d0 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -211,7 +211,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); + strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol); } } } From 0642b17ab0135bebc15890d8a14b303744311598 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 6 Sep 2024 17:54:36 +0200 Subject: [PATCH 10/50] Bugfix --- usermods/ST7789_display/ST7789_display.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 281fba25..59f6d927 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -23,6 +23,9 @@ #ifndef TFT_RST #error Please define TFT_RST #endif + #ifndef TFT_CS + #error Please define TFT_CS + #endif #ifndef LOAD_GLCD #error Please define LOAD_GLCD #endif @@ -377,7 +380,7 @@ class St7789DisplayUsermod : public Usermod { oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); - oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI BL');")); + oappend(SET_F("addInfo('ST7789:pin[]',3,'','SPI BL');")); } /* From 81cec6a25ae6447d3ec8b7169d522ea7992cfb5e Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:36:24 +0200 Subject: [PATCH 11/50] Update usermod_seven_segment_reloaded.h - prevent array bounds violation (solves #4121) I'm not the maintainer of this usermod, but its obvious that the code might overrun array bounds, so fixing this. --- .../usermod_seven_segment_reloaded.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 5c2fac0d..572d4ceb 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -236,6 +236,8 @@ private: } void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) { + if ((lednr < 0) || (lednr >= umSSDRLength)) return; // prevent array bounds violation + if ((number < 0) || (countSegments < 0) || (lastSeenLedNr <0)) return; // prevent array out of range access if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) { From 340a9f8b762195b10f64ff42af9228e5140bb6b3 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 6 Sep 2024 20:09:25 +0200 Subject: [PATCH 12/50] better bugfix for seven-seg-reloaded * only reject invalid ranges when array access will be actually performed * fixed another stupid pointer arithmetic error Hint: I AM NOT THE MAINTAINER of this usermod. I'm just fixing an obvious coding error without knowing what the usermod really does. --- .../usermod_seven_segment_reloaded.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 572d4ceb..111df296 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -165,7 +165,7 @@ private: void _showElements(String *map, int timevar, bool isColon, bool removeZero ) { - if (!(*map).equals("") && !(*map) == NULL) { + if ((map != nullptr) && (*map != nullptr) && !(*map).equals("")) { int length = String(timevar).length(); bool addZero = false; if (length == 1) { @@ -237,12 +237,12 @@ private: void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) { if ((lednr < 0) || (lednr >= umSSDRLength)) return; // prevent array bounds violation - if ((number < 0) || (countSegments < 0) || (lastSeenLedNr <0)) return; // prevent array out of range access + if (!(colon && umSSDRColonblink) && ((number < 0) || (countSegments < 0))) return; if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) { if (range) { - for(int i = lastSeenLedNr; i <= lednr; i++) { + for(int i = max(0, lastSeenLedNr); i <= lednr; i++) { umSSDRMask[i] = true; } } else { From b4315152e2636244beb78d3b862e2b9ac851f0aa Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 8 Sep 2024 11:07:53 +0200 Subject: [PATCH 13/50] Add numPins() --- wled00/data/settings_leds.htm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 58bd8420..f9bfd3dc 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -30,6 +30,7 @@ function hasW(t) { return !!(gT(t).c & 0x02); } // has white channel function hasCCT(t) { return !!(gT(t).c & 0x04); } // is white CCT enabled function is16b(t) { return !!(gT(t).c & 0x10); } // is digital 16 bit type + function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript function loadJS(FILE_URL, async = true) { let scE = d.createElement("script"); @@ -83,7 +84,7 @@ let t = parseInt(d.Sf["LT"+n].value, 10); // LED type SELECT // ignore IP address if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { - if (t>=80) return; + if (isNet(t)) return; } //check for pin conflicts if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4") @@ -237,7 +238,7 @@ p0d = "Data "+p0d; break; case 'A': // PWM analog - if (gT(t).t.length > 1) p0d = "GPIOs:"; + if (numPins(t) > 1) p0d = "GPIOs:"; off = "Dithering"; break; case 'N': // network @@ -412,7 +413,7 @@ let t = s.value; if (isDig(t) && !isD2P(t)) digitalB++; if (isD2P(t)) twopinB++; - if (isPWM(t)) analogB += t-40; // type defines PWM pins + if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel if (isVir(t)) virtB++; }); From eb06e575c2ff00aa663ca9d1ee0c5d5ce9ad2bf6 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Tue, 10 Sep 2024 01:03:19 +0200 Subject: [PATCH 14/50] add check for pin reassigned conflict with pin defined for current bus --- wled00/FX_fcn.cpp | 19 +++++++++++++++---- wled00/pin_manager.cpp | 7 +++++++ wled00/pin_manager.h | 1 + 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 74d61256..6f218c93 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -76,7 +76,7 @@ static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) { // Pins provided < pins required -> always invalid // Pins provided = pins required -> always valid - // Pins provided > pins required -> last type will repeat until we run out of pins, make sure excess pins modulo last type pins == 0 + // Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated return (sumPinsRequired(types, numTypes) > numPins) ? false : (numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0; } @@ -1240,7 +1240,7 @@ void WS2812FX::finalizeInit(void) { unsigned prevLen = 0; unsigned pinsIndex = 0; for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { - uint8_t defPin[OUTPUT_MAX_PINS]; // max 5 pins + 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 unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; unsigned busPins = Bus::getNumberOfPins(dataType); @@ -1259,8 +1259,19 @@ void WS2812FX::finalizeInit(void) { // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. if (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) { defPin[j] = 1; // start with GPIO1 and work upwards - // @FIX pins are allocated after the loop, so if we reassign to a pin that's already in the array for this output 2 fields will have the same pin - while ((pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) && defPin[j] < WLED_NUM_PINS) defPin[j]++; + while ( + ( + pinManager.isPinAllocated(defPin[j]) || + pinManager.isReadOnlyPin(defPin[j]) || + // Check if pin is defined for current bus + pinManager.isPinDefined(defPin[j], defDataPins, pinsIndex + j, pinsIndex + busPins) + ) + && + defPin[j] < WLED_NUM_PINS + ) + { + defPin[j]++; + } } } pinsIndex += busPins; diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 2bd82115..814d81c1 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -281,6 +281,13 @@ bool PinManagerClass::isReadOnlyPin(byte gpio) return false; } +bool PinManagerClass::isPinDefined(byte gpio, const unsigned *pins, unsigned start, unsigned end) { + for (unsigned i = start; i < end; i++) { + if (pins[i] == gpio) return true; + } + return false; +} + PinOwner PinManagerClass::getPinOwner(byte gpio) const { if (!isPinOk(gpio, false)) return PinOwner::None; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index a8ddf5f7..1263dc4f 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -113,6 +113,7 @@ class PinManagerClass { bool isPinOk(byte gpio, bool output = true) const; static bool isReadOnlyPin(byte gpio); + static bool isPinDefined(byte gpio, const unsigned* pins, unsigned start = 0, unsigned end = WLED_NUM_PINS); PinOwner getPinOwner(byte gpio) const; From 20444ee7d5f6cc255783e66adcb6a3a41806e2b7 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 10 Sep 2024 15:20:34 +0200 Subject: [PATCH 15/50] Debug optimisations --- wled00/button.cpp | 16 +++--- wled00/cfg.cpp | 6 +- wled00/e131.cpp | 8 +-- wled00/json.cpp | 7 +-- wled00/mqtt.cpp | 3 +- wled00/ntp.cpp | 7 +-- wled00/presets.cpp | 11 ++-- wled00/remote.cpp | 3 +- wled00/set.cpp | 10 ++-- wled00/src/dependencies/toki/Toki.h | 4 +- wled00/udp.cpp | 20 +++---- wled00/wled.cpp | 87 +++++++++++++---------------- wled00/wled_server.cpp | 5 +- wled00/ws.cpp | 8 +-- wled00/xml.cpp | 3 +- 15 files changed, 84 insertions(+), 114 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 8b366e05..b5a4e943 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -125,7 +125,7 @@ void handleSwitch(uint8_t b) { // isButtonPressed() handles inverted/noninverted logic if (buttonPressedBefore[b] != isButtonPressed(b)) { - DEBUG_PRINT(F("Switch: State changed ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: State changed %u\n"), b); buttonPressedTime[b] = millis(); buttonPressedBefore[b] = !buttonPressedBefore[b]; } @@ -133,15 +133,15 @@ void handleSwitch(uint8_t b) if (buttonLongPressed[b] == buttonPressedBefore[b]) return; if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) - DEBUG_PRINT(F("Switch: Activating ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: Activating %u\n"), b); if (!buttonPressedBefore[b]) { // on -> off - DEBUG_PRINT(F("Switch: On -> Off ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: On -> Off (%u)\n"), b); if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET); else { //turn on if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} } } else { // off -> on - DEBUG_PRINT(F("Switch: Off -> On ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Switch: Off -> On (%u)\n"), b); if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET); else { //turn off if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);} @@ -173,7 +173,7 @@ void handleAnalog(uint8_t b) static float filteredReading[WLED_MAX_BUTTONS] = {0.0f}; unsigned rawReading; // raw value from analogRead, scaled to 12bit - DEBUG_PRINT(F("Analog: Reading button ")); DEBUG_PRINTLN(b); + DEBUG_PRINTF_P(PSTR("Analog: Reading button %u\n"), b); #ifdef ESP8266 rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit @@ -193,8 +193,8 @@ void handleAnalog(uint8_t b) // remove noise & reduce frequency of UI updates if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading - DEBUG_PRINT(F("Analog: Raw = ")); DEBUG_PRINT(rawReading); - DEBUG_PRINT(F(" Filtered = ")); DEBUG_PRINTLN(aRead); + DEBUG_PRINTF_P(PSTR("Analog: Raw = %u\n"), rawReading); + DEBUG_PRINTF_P(PSTR(" Filtered = %u\n"), aRead); // Unomment the next lines if you still see flickering related to potentiometer // This waits until strip finishes updating (why: strip was not updating at the start of handleButton() but may have started during analogRead()?) @@ -207,7 +207,7 @@ void handleAnalog(uint8_t b) // if no macro for "short press" and "long press" is defined use brightness control if (!macroButton[b] && !macroLongPress[b]) { - DEBUG_PRINT(F("Analog: Action = ")); DEBUG_PRINTLN(macroDoublePress[b]); + DEBUG_PRINTF_P(PSTR("Analog: Action = %u\n"), macroDoublePress[b]); // if "double press" macro defines which option to change if (macroDoublePress[b] >= 250) { // global brightness diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 89076efa..a6c3ab74 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -277,9 +277,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if ((buttonType[s] == BTN_TYPE_ANALOG) || (buttonType[s] == BTN_TYPE_ANALOG_INVERTED)) { if (digitalPinToAnalogChannel(btnPin[s]) < 0) { // not an ADC analog pin - DEBUG_PRINT(F("PIN ALLOC error: GPIO")); DEBUG_PRINT(btnPin[s]); - DEBUG_PRINT(F("for analog button #")); DEBUG_PRINT(s); - DEBUG_PRINTLN(F(" is not an analog pin!")); + DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[s], s); btnPin[s] = -1; pinManager.deallocatePin(pin,PinOwner::Button); } else { @@ -290,7 +288,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { { if (digitalPinToTouchChannel(btnPin[s]) < 0) { // not a touch pin - DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[s], s); + DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s); btnPin[s] = -1; pinManager.deallocatePin(pin,PinOwner::Button); } diff --git a/wled00/e131.cpp b/wled00/e131.cpp index e28750db..7c074759 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -108,13 +108,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ if (e131SkipOutOfSequence) if (seq < e131LastSequenceNumber[previousUniverses] && seq > 20 && e131LastSequenceNumber[previousUniverses] < 250){ - DEBUG_PRINT(F("skipping E1.31 frame (last seq=")); - DEBUG_PRINT(e131LastSequenceNumber[previousUniverses]); - DEBUG_PRINT(F(", current seq=")); - DEBUG_PRINT(seq); - DEBUG_PRINT(F(", universe=")); - DEBUG_PRINT(uni); - DEBUG_PRINTLN(")"); + DEBUG_PRINTF_P(PSTR("skipping E1.31 frame (last seq=%d, current seq=%d, universe=%d)\n"), e131LastSequenceNumber[previousUniverses], seq, uni); return; } e131LastSequenceNumber[previousUniverses] = seq; diff --git a/wled00/json.cpp b/wled00/json.cpp index 01acc7ba..596bd780 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1144,11 +1144,8 @@ void serveJson(AsyncWebServerRequest* request) DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for request: %d\n"), lDoc.memoryUsage(), subJson); - #ifdef WLED_DEBUG - size_t len = - #endif - response->setLength(); - DEBUG_PRINT(F("JSON content length: ")); DEBUG_PRINTLN(len); + [[maybe_unused]] size_t len = response->setLength(); + DEBUG_PRINTF_P(PSTR("JSON content length: %u\n"), len); request->send(response); } diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 775a4fe5..833e6eb7 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -55,8 +55,7 @@ static void onMqttConnect(bool sessionPresent) static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { static char *payloadStr; - DEBUG_PRINT(F("MQTT msg: ")); - DEBUG_PRINTLN(topic); + DEBUG_PRINTF_P(PSTR("MQTT msg: %s\n"), topic); // paranoia check to avoid npe if no payload if (payload==nullptr) { diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 056110a5..7b7dac96 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -246,8 +246,7 @@ bool checkNTPResponse() } uint32_t ntpPacketReceivedTime = millis(); - DEBUG_PRINT(F("NTP recv, l=")); - DEBUG_PRINTLN(cb); + DEBUG_PRINTF_P(PSTR("NTP recv, l=%d\n"), cb); byte pbuf[NTP_PACKET_SIZE]; ntpUdp.read(pbuf, NTP_PACKET_SIZE); // read the packet into the buffer if (!isValidNtpResponse(pbuf)) return false; // verify we have a valid response to client @@ -493,7 +492,7 @@ void calculateSunriseAndSunset() { do { time_t theDay = localTime - retryCount * 86400; // one day back = 86400 seconds minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, false); - DEBUG_PRINT(F("* sunrise (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); + DEBUG_PRINTF_P(PSTR("* sunrise (minutes from UTC) = %d\n"), minUTC); retryCount ++; } while ((abs(minUTC) > SUNSET_MAX) && (retryCount <= 3)); @@ -512,7 +511,7 @@ void calculateSunriseAndSunset() { do { time_t theDay = localTime - retryCount * 86400; // one day back = 86400 seconds minUTC = getSunriseUTC(year(theDay), month(theDay), day(theDay), latitude, longitude, true); - DEBUG_PRINT(F("* sunset (minutes from UTC) = ")); DEBUG_PRINTLN(minUTC); + DEBUG_PRINTF_P(PSTR("* sunset (minutes from UTC) = %d\n"), minUTC); retryCount ++; } while ((abs(minUTC) > SUNSET_MAX) && (retryCount <= 3)); diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 2916d337..20edfd91 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -117,8 +117,7 @@ void initPresetsFile() bool applyPresetFromPlaylist(byte index) { - DEBUG_PRINT(F("Request to apply preset: ")); - DEBUG_PRINTLN(index); + DEBUG_PRINTF_P(PSTR("Request to apply preset: %d\n"), index); presetToApply = index; callModeToApply = CALL_MODE_DIRECT_CHANGE; return true; @@ -127,8 +126,7 @@ bool applyPresetFromPlaylist(byte index) bool applyPreset(byte index, byte callMode) { unloadPlaylist(); // applying a preset unloads the playlist (#3827) - DEBUG_PRINT(F("Request to apply preset: ")); - DEBUG_PRINTLN(index); + DEBUG_PRINTF_P(PSTR("Request to apply preset: %u\n"), index); presetToApply = index; callModeToApply = callMode; return true; @@ -163,8 +161,7 @@ void handlePresets() presetToApply = 0; //clear request for preset callModeToApply = 0; - DEBUG_PRINT(F("Applying preset: ")); - DEBUG_PRINTLN(tmpPreset); + DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); #ifdef ARDUINO_ARCH_ESP32 if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { @@ -222,7 +219,7 @@ void savePreset(byte index, const char* pname, JsonObject sObj) else sprintf_P(saveName, PSTR("Preset %d"), index); } - DEBUG_PRINT(F("Saving preset (")); DEBUG_PRINT(index); DEBUG_PRINT(F(") ")); DEBUG_PRINTLN(saveName); + DEBUG_PRINTF_P(PSTR("Saving preset (%d) %s\n"), index, saveName); presetToSave = index; playlistSave = false; diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 9d8b94df..9bc5430c 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -186,8 +186,7 @@ void handleRemote(uint8_t *incomingData, size_t len) { } if (len != sizeof(message_structure_t)) { - DEBUG_PRINT(F("Unknown incoming ESP Now message received of length ")); - DEBUG_PRINTLN(len); + DEBUG_PRINTF_P(PSTR("Unknown incoming ESP Now message received of length %u\n"), len); return; } diff --git a/wled00/set.cpp b/wled00/set.cpp index 2811d708..7814e55d 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -157,8 +157,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA if (!request->hasArg(lp)) { - DEBUG_PRINT(F("No data for ")); - DEBUG_PRINTLN(s); + DEBUG_PRINTF_P(PSTR("No data for %d\n"), s); break; } for (int i = 0; i < 5; i++) { @@ -729,7 +728,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) else subObj[name].add(value.toInt()); // we may have an int j++; } - DEBUG_PRINT(F("[")); DEBUG_PRINT(j); DEBUG_PRINT(F("] = ")); DEBUG_PRINTLN(value); + DEBUG_PRINTF_P(PSTR("[%d] = %s\n"), j, value.c_str()); } else { // we are using a hidden field with the same name as our parameter (!before the actual parameter!) // to describe the type of parameter (text,float,int), for boolean parameters the first field contains "off" @@ -748,7 +747,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } else if (type == "int") subObj[name] = value.toInt(); else subObj[name] = value; // text fields } - DEBUG_PRINT(F(" = ")); DEBUG_PRINTLN(value); + DEBUG_PRINTF_P(PSTR(" = %s\n"), value.c_str()); } } usermods.readFromConfig(um); // force change of usermod parameters @@ -809,8 +808,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) if (!(req.indexOf("win") >= 0)) return false; int pos = 0; - DEBUG_PRINT(F("API req: ")); - DEBUG_PRINTLN(req); + DEBUG_PRINTF_P(PSTR("API req: %s\n"), req.c_str()); //segment select (sets main segment) pos = req.indexOf(F("SM=")); diff --git a/wled00/src/dependencies/toki/Toki.h b/wled00/src/dependencies/toki/Toki.h index e7f12a45..0e849d3c 100644 --- a/wled00/src/dependencies/toki/Toki.h +++ b/wled00/src/dependencies/toki/Toki.h @@ -155,7 +155,7 @@ class Toki { return (tick == TickT::active); } - void printTime(const Time& t) { - Serial.printf_P(PSTR("%u,%03u\n"),t.sec,t.ms); + void printTime(const Time& t, Print &dest = Serial) { + dest.printf_P(PSTR("%u,%03u\n"),t.sec,t.ms); } }; \ No newline at end of file diff --git a/wled00/udp.cpp b/wled00/udp.cpp index c2221e2c..d030b665 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -213,7 +213,7 @@ void parseNotifyPacket(uint8_t *udpIn) { //compatibilityVersionByte: byte version = udpIn[11]; - DEBUG_PRINT(F("UDP packet version: ")); DEBUG_PRINTLN(version); + DEBUG_PRINTF_P(PSTR("UDP packet version: %d\n"), (int)version); // if we are not part of any sync group ignore message if (version < 9) { @@ -256,7 +256,7 @@ void parseNotifyPacket(uint8_t *udpIn) { if (applyEffects && currentPlaylist >= 0) unloadPlaylist(); if (version > 10 && (receiveSegmentOptions || receiveSegmentBounds)) { unsigned numSrcSegs = udpIn[39]; - DEBUG_PRINT(F("UDP segments: ")); DEBUG_PRINTLN(numSrcSegs); + DEBUG_PRINTF_P(PSTR("UDP segments: %d\n"), numSrcSegs); // are we syncing bounds and slave has more active segments than master? if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) { DEBUG_PRINTLN(F("Removing excessive segments.")); @@ -270,13 +270,13 @@ void parseNotifyPacket(uint8_t *udpIn) { for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) { unsigned ofs = 41 + i*udpIn[40]; //start of segment offset byte unsigned id = udpIn[0 +ofs]; - DEBUG_PRINT(F("UDP segment received: ")); DEBUG_PRINTLN(id); + DEBUG_PRINTF_P(PSTR("UDP segment received: %u\n"), id); if (id > strip.getSegmentsNum()) break; else if (id == strip.getSegmentsNum()) { if (receiveSegmentBounds && id < strip.getMaxSegments()) strip.appendSegment(); else break; } - DEBUG_PRINT(F("UDP segment check: ")); DEBUG_PRINTLN(id); + DEBUG_PRINTF_P(PSTR("UDP segment check: %u\n"), id); Segment& selseg = strip.getSegment(id); // if we are not syncing bounds skip unselected segments if (selseg.isActive() && !(selseg.isSelected() || receiveSegmentBounds)) continue; @@ -290,7 +290,7 @@ void parseNotifyPacket(uint8_t *udpIn) { id += inactiveSegs; // adjust id } } - DEBUG_PRINT(F("UDP segment processing: ")); DEBUG_PRINTLN(id); + DEBUG_PRINTF_P(PSTR("UDP segment processing: %u\n"), id); uint16_t start = (udpIn[1+ofs] << 8 | udpIn[2+ofs]); uint16_t stop = (udpIn[3+ofs] << 8 | udpIn[4+ofs]); @@ -307,14 +307,14 @@ void parseNotifyPacket(uint8_t *udpIn) { selseg.options = (selseg.options & 0x0071U) | (udpIn[9 +ofs] & 0x0E); // ignore selected, freeze, reset & transitional selseg.setOpacity(udpIn[10+ofs]); if (applyEffects) { - DEBUG_PRINT(F("Apply effect: ")); DEBUG_PRINTLN(id); + DEBUG_PRINTF_P(PSTR("Apply effect: %u\n"), id); selseg.setMode(udpIn[11+ofs]); selseg.speed = udpIn[12+ofs]; selseg.intensity = udpIn[13+ofs]; selseg.palette = udpIn[14+ofs]; } if (receiveNotificationColor || !someSel) { - DEBUG_PRINT(F("Apply color: ")); DEBUG_PRINTLN(id); + DEBUG_PRINTF_P(PSTR("Apply color: %u\n"), id); selseg.setColor(0, RGBW32(udpIn[15+ofs],udpIn[16+ofs],udpIn[17+ofs],udpIn[18+ofs])); selseg.setColor(1, RGBW32(udpIn[19+ofs],udpIn[20+ofs],udpIn[21+ofs],udpIn[22+ofs])); selseg.setColor(2, RGBW32(udpIn[23+ofs],udpIn[24+ofs],udpIn[25+ofs],udpIn[26+ofs])); @@ -324,10 +324,10 @@ void parseNotifyPacket(uint8_t *udpIn) { // when applying synced options ignore selected as it may be used as indicator of which segments to sync // freeze, reset should never be synced // LSB to MSB: select, reverse, on, mirror, freeze, reset, reverse_y, mirror_y, transpose, map1d2d (3), ssim (2), set (2) - DEBUG_PRINT(F("Apply options: ")); DEBUG_PRINTLN(id); + DEBUG_PRINTF_P(PSTR("Apply options: %u\n"), id); selseg.options = (selseg.options & 0b0000000000110001U) | (udpIn[28+ofs]<<8) | (udpIn[9 +ofs] & 0b11001110U); // ignore selected, freeze, reset if (applyEffects) { - DEBUG_PRINT(F("Apply sliders: ")); DEBUG_PRINTLN(id); + DEBUG_PRINTF_P(PSTR("Apply sliders: %u\n"), id); selseg.custom1 = udpIn[29+ofs]; selseg.custom2 = udpIn[30+ofs]; selseg.custom3 = udpIn[31+ofs] & 0x1F; @@ -561,7 +561,7 @@ void handleNotifications() //wled notifier, ignore if realtime packets active if (udpIn[0] == 0 && !realtimeMode && receiveGroups) { - DEBUG_PRINT(F("UDP notification from: ")); DEBUG_PRINTLN(notifierUdp.remoteIP()); + DEBUG_PRINTF_P(PSTR("UDP notification from: %d.%d.%d.%d\n"), notifierUdp.remoteIP()[0], notifierUdp.remoteIP()[1], notifierUdp.remoteIP()[2], notifierUdp.remoteIP()[3]); parseNotifyPacket(udpIn); return; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index a6143eee..840c06bf 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -159,7 +159,7 @@ void WLED::loop() if (millis() - heapTime > 15000) { uint32_t heap = ESP.getFreeHeap(); if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) { - DEBUG_PRINT(F("Heap too low! ")); DEBUG_PRINTLN(heap); + DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap); forceReconnect = true; strip.resetSegments(); // remove all but one segments from memory } else if (heap < MIN_HEAP_SIZE) { @@ -264,9 +264,9 @@ void WLED::loop() if (loopMillis > maxLoopMillis) maxLoopMillis = loopMillis; if (millis() - debugTime > 29999) { DEBUG_PRINTLN(F("---DEBUG INFO---")); - DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis()); - DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime()); - DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("Runtime: %u\n"), millis()); + DEBUG_PRINTF_P(PSTR("Unix time: %u,%03u\n"), toki.getTime().sec, toki.getTime().ms); + DEBUG_PRINTF_P(PSTR("Free heap: %u\n"), ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) if (psramFound()) { DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024); @@ -276,21 +276,21 @@ void WLED::loop() #endif DEBUG_PRINTF_P(PSTR("Wifi state: %d\n"), WiFi.status()); #ifndef WLED_DISABLE_ESPNOW - DEBUG_PRINT(F("ESP-NOW state: ")); DEBUG_PRINTLN(statusESPNow); + DEBUG_PRINTF_P(PSTR("ESP-NOW state: %u\n"), statusESPNow); #endif if (WiFi.status() != lastWifiState) { wifiStateChangedTime = millis(); } lastWifiState = WiFi.status(); - DEBUG_PRINT(F("State time: ")); DEBUG_PRINTLN(wifiStateChangedTime); - DEBUG_PRINT(F("NTP last sync: ")); DEBUG_PRINTLN(ntpLastSyncTime); - DEBUG_PRINT(F("Client IP: ")); DEBUG_PRINTLN(Network.localIP()); + DEBUG_PRINTF_P(PSTR("State time: %u\n"), wifiStateChangedTime); + DEBUG_PRINTF_P(PSTR("NTP last sync: %u\n"), ntpLastSyncTime); + DEBUG_PRINTF_P(PSTR("Client IP: %u.%u.%u.%u\n"), Network.localIP()[0], Network.localIP()[1], Network.localIP()[2], Network.localIP()[3]); if (loops > 0) { // avoid division by zero - DEBUG_PRINT(F("Loops/sec: ")); DEBUG_PRINTLN(loops / 30); - DEBUG_PRINT(F("Loop time[ms]: ")); DEBUG_PRINT(avgLoopMillis/loops); DEBUG_PRINT("/");DEBUG_PRINTLN(maxLoopMillis); - DEBUG_PRINT(F("UM time[ms]: ")); DEBUG_PRINT(avgUsermodMillis/loops); DEBUG_PRINT("/");DEBUG_PRINTLN(maxUsermodMillis); - DEBUG_PRINT(F("Strip time[ms]: ")); DEBUG_PRINT(avgStripMillis/loops); DEBUG_PRINT("/"); DEBUG_PRINTLN(maxStripMillis); + DEBUG_PRINTF_P(PSTR("Loops/sec: %u\n"), loops / 30); + DEBUG_PRINTF_P(PSTR("Loop time[ms]: %u/%u\n"), avgLoopMillis/loops, maxLoopMillis); + DEBUG_PRINTF_P(PSTR("UM time[ms]: %u/%u\n"), avgUsermodMillis/loops, maxUsermodMillis); + DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%u\n"), avgStripMillis/loops, maxStripMillis); } strip.printSize(); loops = 0; @@ -358,45 +358,38 @@ void WLED::setup() Serial.setDebugOutput(false); // switch off kernel messages when using USBCDC #endif DEBUG_PRINTLN(); - DEBUG_PRINT(F("---WLED ")); - DEBUG_PRINT(versionString); - DEBUG_PRINT(F(" ")); - DEBUG_PRINT(VERSION); - DEBUG_PRINTLN(F(" INIT---")); + DEBUG_PRINTF_P(PSTR("---WLED %s %u INIT---\n"), versionString, VERSION); + DEBUG_PRINTLN(); #ifdef ARDUINO_ARCH_ESP32 - DEBUG_PRINT(F("esp32 ")); - DEBUG_PRINTLN(ESP.getSdkVersion()); + DEBUG_PRINTF_P(PSTR("esp32 %s\n"), ESP.getSdkVersion()); #if defined(ESP_ARDUINO_VERSION) - //DEBUG_PRINTF_P(PSTR("arduino-esp32 0x%06x\n"), ESP_ARDUINO_VERSION); - DEBUG_PRINTF_P(PSTR("arduino-esp32 v%d.%d.%d\n"), int(ESP_ARDUINO_VERSION_MAJOR), int(ESP_ARDUINO_VERSION_MINOR), int(ESP_ARDUINO_VERSION_PATCH)); // availeable since v2.0.0 + DEBUG_PRINTF_P(PSTR("arduino-esp32 v%d.%d.%d\n"), int(ESP_ARDUINO_VERSION_MAJOR), int(ESP_ARDUINO_VERSION_MINOR), int(ESP_ARDUINO_VERSION_PATCH)); // available since v2.0.0 #else DEBUG_PRINTLN(F("arduino-esp32 v1.0.x\n")); // we can't say in more detail. #endif - DEBUG_PRINT(F("CPU: ")); DEBUG_PRINT(ESP.getChipModel()); - DEBUG_PRINT(F(" rev.")); DEBUG_PRINT(ESP.getChipRevision()); - DEBUG_PRINT(F(", ")); DEBUG_PRINT(ESP.getChipCores()); DEBUG_PRINT(F(" core(s)")); - DEBUG_PRINT(F(", ")); DEBUG_PRINT(ESP.getCpuFreqMHz()); DEBUG_PRINTLN(F("MHz.")); - DEBUG_PRINT(F("FLASH: ")); DEBUG_PRINT((ESP.getFlashChipSize()/1024)/1024); - DEBUG_PRINT(F("MB, Mode ")); DEBUG_PRINT(ESP.getFlashChipMode()); + DEBUG_PRINTF_P(PSTR("CPU: "), ESP.getChipModel()); + DEBUG_PRINTF_P(PSTR(" rev."), ESP.getChipRevision()); + DEBUG_PRINTF_P(PSTR(", %d core(s)"), ESP.getChipCores()); + DEBUG_PRINTF_P(PSTR(", %d MHz.\n"), ESP.getCpuFreqMHz()); + DEBUG_PRINTF_P(PSTR("FLASH: %dMB, Mode %d "), (ESP.getFlashChipSize()/1024)/1024, ESP.getFlashChipMode()); #ifdef WLED_DEBUG switch (ESP.getFlashChipMode()) { // missing: Octal modes - case FM_QIO: DEBUG_PRINT(F(" (QIO)")); break; - case FM_QOUT: DEBUG_PRINT(F(" (QOUT)"));break; - case FM_DIO: DEBUG_PRINT(F(" (DIO)")); break; - case FM_DOUT: DEBUG_PRINT(F(" (DOUT)"));break; + case FM_QIO: DEBUG_PRINT(F("(QIO)")); break; + case FM_QOUT: DEBUG_PRINT(F("(QOUT)"));break; + case FM_DIO: DEBUG_PRINT(F("(DIO)")); break; + case FM_DOUT: DEBUG_PRINT(F("(DOUT)"));break; default: break; } #endif - DEBUG_PRINT(F(", speed ")); DEBUG_PRINT(ESP.getFlashChipSpeed()/1000000);DEBUG_PRINTLN(F("MHz.")); + DEBUG_PRINTF_P(PSTR(", speed %u MHz.\n"), ESP.getFlashChipSpeed()/1000000); #else - DEBUG_PRINT(F("esp8266 @ ")); DEBUG_PRINT(ESP.getCpuFreqMHz()); DEBUG_PRINT(F("MHz.\nCore: ")); - DEBUG_PRINTLN(ESP.getCoreVersion()); - DEBUG_PRINT(F("FLASH: ")); DEBUG_PRINT((ESP.getFlashChipSize()/1024)/1024); DEBUG_PRINTLN(F(" MB")); + DEBUG_PRINTF_P(PSTR("esp8266 @ %u MHz.\nCore: %s\n"), ESP.getCpuFreqMHz(), ESP.getCoreVersion()); + DEBUG_PRINTF_P(PSTR("FLASH: %u MB\n"), (ESP.getFlashChipSize()/1024)/1024); #endif - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) // BOARD_HAS_PSRAM also means that a compiler flag "-mfix-esp32-psram-cache-issue" was used and so PSRAM is safe to use on rev.1 ESP32 @@ -405,7 +398,7 @@ void WLED::setup() if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM.")); #endif pDoc = new PSRAMDynamicJsonDocument((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); - DEBUG_PRINT(F("JSON buffer allocated: ")); DEBUG_PRINTLN((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); + DEBUG_PRINTF_P(PSTR("JSON buffer allocated: %u\n"), (psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE); // if the above fails requestJsonBufferLock() will always return false preventing crashes if (psramFound()) { DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024); @@ -427,7 +420,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Registering usermods ...")); registerUsermods(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); bool fsinit = false; DEBUGFS_PRINTLN(F("Mount FS")); @@ -457,7 +450,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Reading config")); deserializeConfigFromFS(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #if defined(STATUSLED) && STATUSLED>=0 if (!pinManager.isPinAllocated(STATUSLED)) { @@ -469,12 +462,12 @@ void WLED::setup() DEBUG_PRINTLN(F("Initializing strip")); beginStrip(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); DEBUG_PRINTLN(F("Usermods setup")); userSetup(); usermods.setup(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) showWelcomePage = true; @@ -537,13 +530,13 @@ void WLED::setup() // HTTP server page init DEBUG_PRINTLN(F("initServer")); initServer(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #ifndef WLED_DISABLE_INFRARED // init IR DEBUG_PRINTLN(F("initIR")); initIR(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #endif // Seed FastLED random functions with an esp random value, which already works properly at this point. @@ -654,11 +647,11 @@ bool WLED::initEthernet() return false; } if (ethernetType >= WLED_NUM_ETH_TYPES) { - DEBUG_PRINT(F("initE: Ignoring attempt for invalid ethernetType ")); DEBUG_PRINTLN(ethernetType); + DEBUG_PRINTF_P(PSTR("initE: Ignoring attempt for invalid ethernetType (%d)\n"), ethernetType); return false; } - DEBUG_PRINT(F("initE: Attempting ETH config: ")); DEBUG_PRINTLN(ethernetType); + DEBUG_PRINTF_P(PSTR("initE: Attempting ETH config: %d\n"), ethernetType); // Ethernet initialization should only succeed once -- else reboot required ethernet_settings es = ethernetBoards[ethernetType]; @@ -689,9 +682,7 @@ bool WLED::initEthernet() pinsToAllocate[9].pin = 17; pinsToAllocate[9].isOutput = true; } else { - DEBUG_PRINT(F("initE: Failing due to invalid eth_clk_mode (")); - DEBUG_PRINT(es.eth_clk_mode); - DEBUG_PRINTLN(")"); + DEBUG_PRINTF_P(PSTR("initE: Failing due to invalid eth_clk_mode (%d)\n"), es.eth_clk_mode); return false; } diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index d184e987..291f6f5f 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -163,8 +163,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, } request->_tempFile = WLED_FS.open(finalname, "w"); - DEBUG_PRINT(F("Uploading ")); - DEBUG_PRINTLN(finalname); + DEBUG_PRINTF_P(PSTR("Uploading %s\n"), finalname.c_str()); if (finalname.equals(FPSTR(getPresetsFileName()))) presetsModifiedTime = toki.second(); } if (len) { @@ -466,7 +465,7 @@ void initServer() //called when the url is not defined here, ajax-in; get-settings server.onNotFound([](AsyncWebServerRequest *request){ - DEBUG_PRINT(F("Not-Found HTTP call: ")); DEBUG_PRINTLN(request->url()); + DEBUG_PRINTF_P(PSTR("Not-Found HTTP call: %s\n"), request->url().c_str()); if (captivePortal(request)) return; //make API CORS compatible diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 3dec548f..45640b68 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -125,7 +125,7 @@ void sendDataWs(AsyncWebSocketClient * client) // the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS size_t heap1 = ESP.getFreeHeap(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #ifdef ESP8266 if (len>heap1) { DEBUG_PRINTLN(F("Out of memory (WS)!")); @@ -135,7 +135,7 @@ void sendDataWs(AsyncWebSocketClient * client) AsyncWebSocketBuffer buffer(len); #ifdef ESP8266 size_t heap2 = ESP.getFreeHeap(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #else size_t heap2 = 0; // ESP32 variants do not have the same issue and will work without checking heap allocation #endif @@ -150,11 +150,11 @@ void sendDataWs(AsyncWebSocketClient * client) DEBUG_PRINT(F("Sending WS data ")); if (client) { - client->text(std::move(buffer)); DEBUG_PRINTLN(F("to a single client.")); + client->text(std::move(buffer)); } else { - ws.textAll(std::move(buffer)); DEBUG_PRINTLN(F("to multiple clients.")); + ws.textAll(std::move(buffer)); } releaseJSONBufferLock(); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 25c0c629..6c3ee83d 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -224,8 +224,7 @@ void appendGPIOinfo() { void getSettingsJS(byte subPage, char* dest) { //0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec - DEBUG_PRINT(F("settings resp")); - DEBUG_PRINTLN(subPage); + DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage); obuf = dest; olen = 0; From fd00e5eaae9a587d3cf5950465f786a5f8ed60e0 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 10 Sep 2024 15:23:27 +0200 Subject: [PATCH 16/50] LED settings text update --- wled00/data/settings_leds.htm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index b7d2d18a..06bcf3e6 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -805,12 +805,13 @@ Swap: mA
Use per-output limiter:

Advanced

- Palette blending: + Palette wrapping:
(in AP mode or no WiFi

Receive

-Brightness, Color, and Effects
+Brightness, Color, Effects, and Palette
Segment options, bounds

Send

Enable Sync on start:
diff --git a/wled00/set.cpp b/wled00/set.cpp index 7814e55d..fa0732fa 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -380,6 +380,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) receiveNotificationBrightness = request->hasArg(F("RB")); receiveNotificationColor = request->hasArg(F("RC")); receiveNotificationEffects = request->hasArg(F("RX")); + receiveNotificationPalette = request->hasArg(F("RP")); receiveSegmentOptions = request->hasArg(F("SO")); receiveSegmentBounds = request->hasArg(F("SG")); sendNotifications = request->hasArg(F("SS")); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index d030b665..8cf733df 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -221,7 +221,7 @@ void parseNotifyPacket(uint8_t *udpIn) { if (!(receiveGroups & 0x01)) return; } else if (!(receiveGroups & udpIn[36])) return; - bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); + bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects || receiveNotificationPalette); // set transition time before making any segment changes if (version > 3) { @@ -311,6 +311,9 @@ void parseNotifyPacket(uint8_t *udpIn) { selseg.setMode(udpIn[11+ofs]); selseg.speed = udpIn[12+ofs]; selseg.intensity = udpIn[13+ofs]; + } + if (receiveNotificationPalette || !someSel) { + DEBUG_PRINTF_P(PSTR("Apply palette: %u\n"), id); selseg.palette = udpIn[14+ofs]; } if (receiveNotificationColor || !someSel) { @@ -352,14 +355,16 @@ void parseNotifyPacket(uint8_t *udpIn) { } // simple effect sync, applies to all selected segments - if (applyEffects && (version < 11 || !receiveSegmentOptions)) { + if ((applyEffects || receiveNotificationPalette) && (version < 11 || !receiveSegmentOptions)) { for (size_t i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; - seg.setMode(udpIn[8]); - seg.speed = udpIn[9]; - if (version > 2) seg.intensity = udpIn[16]; - if (version > 4) seg.setPalette(udpIn[19]); + if (applyEffects) { + seg.setMode(udpIn[8]); + seg.speed = udpIn[9]; + if (version > 2) seg.intensity = udpIn[16]; + } + if (version > 4 && receiveNotificationPalette) seg.setPalette(udpIn[19]); } stateChanged = true; } diff --git a/wled00/wled.h b/wled00/wled.h index 99c5f422..e5cd7fd4 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -676,6 +676,7 @@ WLED_GLOBAL send_notification_t notifyG _INIT(0b00001111); #define receiveNotificationBrightness receiveN.Brightness #define receiveNotificationColor receiveN.Color #define receiveNotificationEffects receiveN.Effects +#define receiveNotificationPalette receiveN.Palette #define receiveSegmentOptions receiveN.SegmentOptions #define receiveSegmentBounds receiveN.SegmentBounds #define receiveDirect receiveN.Direct @@ -687,6 +688,7 @@ WLED_GLOBAL send_notification_t notifyG _INIT(0b00001111); WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup +WLED_GLOBAL bool receiveNotificationPalette _INIT(true); // apply palette WLED_GLOBAL bool receiveSegmentOptions _INIT(false); // apply segment options WLED_GLOBAL bool receiveSegmentBounds _INIT(false); // apply segment bounds (start, stop, offset) WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP/Hyperion realtime diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 6c3ee83d..e151f412 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -506,6 +506,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("RB"),receiveNotificationBrightness); sappend('c',SET_F("RC"),receiveNotificationColor); sappend('c',SET_F("RX"),receiveNotificationEffects); + sappend('c',SET_F("RP"),receiveNotificationPalette); sappend('c',SET_F("SO"),receiveSegmentOptions); sappend('c',SET_F("SG"),receiveSegmentBounds); sappend('c',SET_F("SS"),sendNotifications); From 8ae09c3d0299737786a77e96d56dbb277ff9112f Mon Sep 17 00:00:00 2001 From: Felddy Date: Tue, 10 Sep 2024 18:16:00 +0000 Subject: [PATCH 19/50] Fix comment alignment --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index e5cd7fd4..9db6928a 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -688,7 +688,7 @@ WLED_GLOBAL send_notification_t notifyG _INIT(0b00001111); WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup -WLED_GLOBAL bool receiveNotificationPalette _INIT(true); // apply palette +WLED_GLOBAL bool receiveNotificationPalette _INIT(true); // apply palette WLED_GLOBAL bool receiveSegmentOptions _INIT(false); // apply segment options WLED_GLOBAL bool receiveSegmentBounds _INIT(false); // apply segment bounds (start, stop, offset) WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP/Hyperion realtime From cce5e8f81100272f89a56bd4ef5f2f6c28f2d19e Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:32:40 +0200 Subject: [PATCH 20/50] Update CONTRIBUTING.md - adding a hint to avoid force-pushing --- CONTRIBUTING.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd50b133..c6f90f99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,20 @@ A good description helps us to review and understand your proposed changes. For Please make all PRs against the `0_15` branch. +### Updating your code +While the PR is open - and under review by maintainers - you may be asked to modify your PR source code. +You can simply update your own branch, and push changes in response to reviewer recommendations. +Github will pick up the changes so your PR stays up-to-date. + +> [!CAUTION] +> Do not use "force-push" while your PR is open! +> It has many subtle and unexpected consequences on our github reposistory. +> For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. + + +You kan find a collection of very usefull tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR + + ### Code style When in doubt, it is easiest to replicate the code style you find in the files you want to edit :) @@ -37,6 +51,11 @@ if (a == b) { } ``` +```cpp +if (a == b) doStuff(a); +``` + +Acceptable - however the first variant is usually easier to read: ```cpp if (a == b) { @@ -44,9 +63,6 @@ if (a == b) } ``` -```cpp -if (a == b) doStuff(a); -``` There should always be a space between a keyword and its condition and between the condition and brace. Within the condition, no space should be between the parenthesis and variables. From 5bc5ef0ae8173992d4e92f5682f7c45519498b70 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:36:18 +0200 Subject: [PATCH 21/50] Update CONTRIBUTING.md - typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6f90f99..06c221fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ Github will pick up the changes so your PR stays up-to-date. > For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. -You kan find a collection of very usefull tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR +You can find a collection of very useful tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR ### Code style From 5869627b32366e4784435f84f6d4cbf10e0041c6 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Wed, 11 Sep 2024 01:27:19 +0200 Subject: [PATCH 22/50] - More optimization on bus configuration logic. - Renamed LEDPIN to DEFAULT_LED_PIN. - Removed ability to override DEFAULT_LED_PIN, DEFAULT_LED_TYPE and DEFAULT_LED_COUNT. Use DATA_PINS, LED_TYPES and PIXEL_COUNTS instead. --- wled00/FX_fcn.cpp | 39 +++++++++++++++++---------------------- wled00/const.h | 22 +++++++--------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 6f218c93..435998cd 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -53,7 +53,7 @@ #endif #ifndef DATA_PINS - #define DATA_PINS LEDPIN + #define DATA_PINS DEFAULT_LED_PIN #endif #ifndef LED_TYPES @@ -1245,32 +1245,27 @@ void WS2812FX::finalizeInit(void) { unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; unsigned busPins = Bus::getNumberOfPins(dataType); - // check if we have enough pins left to configure an output of this type - // should never happen due to static assert above - if (pinsIndex + busPins > defNumPins) { - DEBUG_PRINTLN(F("LED outputs misaligned with defined pins. Some pins will remain unused.")); - break; - } + // if we need more pins than available all outputs have been configured + if (pinsIndex + busPins > defNumPins) break; for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { defPin[j] = defDataPins[pinsIndex + j]; - // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + bool validPin = true; + // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. - if (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j])) { - defPin[j] = 1; // start with GPIO1 and work upwards - while ( - ( - pinManager.isPinAllocated(defPin[j]) || - pinManager.isReadOnlyPin(defPin[j]) || - // Check if pin is defined for current bus - pinManager.isPinDefined(defPin[j], defDataPins, pinsIndex + j, pinsIndex + busPins) - ) - && - defPin[j] < WLED_NUM_PINS - ) - { - defPin[j]++; + // Pin should not be already allocated, read/only or defined for current bus + while (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j]) || + pinManager.isPinDefined(defPin[j], defDataPins, pinsIndex + j + 1, pinsIndex + busPins)) { + if (validPin) { + defPin[j] = 1; // start with GPIO1 and work upwards + validPin = false; + } + if (defPin[j] < WLED_NUM_PINS) { + defPin[j]++; + } else { + DEBUG_PRINTLN(F("No available pins left! Can't configure output.")); + return; } } } diff --git a/wled00/const.h b/wled00/const.h index ffefb3ca..1bfca075 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -563,15 +563,6 @@ #define WLED_MAX_NODES 150 #endif -//this is merely a default now and can be changed at runtime -#ifndef LEDPIN -#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) //|| (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(ARDUINO_ESP32_PICO) - #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board -#else - #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) -#endif -#endif - // List of read only pins. Cannot be used for LED outputs. #if defined(CONFIG_IDF_TARGET_ESP32S2) #define READ_ONLY_PINS 46 @@ -593,13 +584,14 @@ #endif #endif -#ifndef DEFAULT_LED_TYPE - #define DEFAULT_LED_TYPE TYPE_WS2812_RGB -#endif - -#ifndef DEFAULT_LED_COUNT - #define DEFAULT_LED_COUNT 30 +// Defaults pins, type and counts to configure LED output +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) + #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board +#else + #define DEFAULT_LED_PIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) #endif +#define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#define DEFAULT_LED_COUNT 30 #define INTERFACE_UPDATE_COOLDOWN 1000 // time in ms to wait between websockets, alexa, and MQTT updates From dbe78dff939cf831bbe7ac24c908ec3b632ba039 Mon Sep 17 00:00:00 2001 From: Matt Coleman Date: Sat, 7 Sep 2024 01:13:59 -0400 Subject: [PATCH 23/50] Temperature usermod: fix changelog formatting This change ensures that the dates are displayed on their own lines. Without the blank lines, many Markdown renderers will append the dates to the previous bullet point. --- usermods/Temperature/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md index b41e3e11..8edc7f92 100644 --- a/usermods/Temperature/readme.md +++ b/usermods/Temperature/readme.md @@ -56,8 +56,10 @@ lib_deps = * Do not report erroneous low temperatures to MQTT * Disable plugin if temperature sensor not detected * Report the number of seconds until the first read in the info screen instead of sensor error + 2021-04 * Adaptation for runtime configuration. + 2023-05 * Rewrite to conform to newer recommendations. * Recommended @blazoncek fork of OneWire for ESP32 to avoid Sensor error \ No newline at end of file From 750a429bd4aa60f396461efba8e7db4f129a1932 Mon Sep 17 00:00:00 2001 From: Matt Coleman Date: Sat, 7 Sep 2024 02:27:54 -0400 Subject: [PATCH 24/50] Temperature usermod: update OneWire to 2.3.8 OneWire 2.3.8 includes stickbreaker's and garyd9's ESP32 fixes: blazoncek's fork is no longer needed --- usermods/Temperature/platformio_override.ini | 4 +--- usermods/Temperature/readme.md | 12 +++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/usermods/Temperature/platformio_override.ini b/usermods/Temperature/platformio_override.ini index 0e354da9..cc86367f 100644 --- a/usermods/Temperature/platformio_override.ini +++ b/usermods/Temperature/platformio_override.ini @@ -7,6 +7,4 @@ extends = env:d1_mini build_flags = ${common.build_flags_esp8266} -D USERMOD_DALLASTEMPERATURE lib_deps = ${env.lib_deps} - paulstoffregen/OneWire@~2.3.7 -# you may want to use following with ESP32 -; https://github.com/blazoncek/OneWire.git # fixes Sensor error on ESP32 \ No newline at end of file + paulstoffregen/OneWire@~2.3.8 \ No newline at end of file diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md index 8edc7f92..3d65be7e 100644 --- a/usermods/Temperature/readme.md +++ b/usermods/Temperature/readme.md @@ -43,10 +43,8 @@ default_envs = d1_mini ... lib_deps = ... - #For Dallas sensor uncomment following line - OneWire@~2.3.7 - # ... or you may want to use following with ESP32 -; https://github.com/blazoncek/OneWire.git # fixes Sensor error on ESP32... + #For Dallas sensor uncomment following + paulstoffregen/OneWire @ ~2.3.8 ``` ## Change Log @@ -62,4 +60,8 @@ lib_deps = 2023-05 * Rewrite to conform to newer recommendations. -* Recommended @blazoncek fork of OneWire for ESP32 to avoid Sensor error \ No newline at end of file +* Recommended @blazoncek fork of OneWire for ESP32 to avoid Sensor error + +2024-09 +* Update OneWire to version 2.3.8, which includes stickbreaker's and garyd9's ESP32 fixes: + blazoncek's fork is no longer needed \ No newline at end of file From 5df1a223c283769db9a696cc665d9b37dc6fb1bd Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 11 Sep 2024 16:45:39 +0200 Subject: [PATCH 25/50] Pre-allocate COM vector --- wled00/bus_manager.h | 1 + wled00/cfg.cpp | 1 + wled00/set.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 3411f4e6..c3ebcad6 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -35,6 +35,7 @@ struct ColorOrderMap { bool add(uint16_t start, uint16_t len, uint8_t colorOrder); inline uint8_t count() const { return _mappings.size(); } + inline void reserve(size_t num) { _mappings.reserve(num); } void reset() { _mappings.clear(); diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 5f0c8593..bcb6f7aa 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -244,6 +244,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // read color order map configuration JsonArray hw_com = hw[F("com")]; if (!hw_com.isNull()) { + BusManager::getColorOrderMap().reserve(std::min(hw_com.size(), (size_t)WLED_MAX_COLOR_ORDER_MAPPINGS)); for (JsonObject entry : hw_com) { uint16_t start = entry["start"] | 0; uint16_t len = entry["len"] | 0; diff --git a/wled00/set.cpp b/wled00/set.cpp index 7be27ed2..a0e762e6 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 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 eae5a74a11c3d3770d910bb85c475a253573899d Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 11 Sep 2024 17:14:59 +0200 Subject: [PATCH 26/50] color_fade bugfix impure void remove optimisations: hot --- wled00/FX.h | 164 +++++++++++++++++++++---------------------- wled00/FX_2Dfcn.cpp | 2 +- wled00/FX_fcn.cpp | 18 ++--- wled00/colors.cpp | 19 ++--- wled00/fcn_declare.h | 14 ++-- 5 files changed, 110 insertions(+), 107 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index b1b1fbcb..a9de2a18 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -517,26 +517,26 @@ typedef struct Segment { #endif inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } - inline bool isSelected(void) const { return selected; } - inline bool isInTransition(void) const { return _t != nullptr; } - inline bool isActive(void) const { return stop > start; } - inline bool is2D(void) const { return (width()>1 && height()>1); } - inline bool hasRGB(void) const { return _isRGB; } - inline bool hasWhite(void) const { return _hasW; } - inline bool isCCT(void) const { return _isCCT; } - inline uint16_t width(void) const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) - inline uint16_t height(void) const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) - inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels - inline uint16_t groupLength(void) const { return grouping + spacing; } - inline uint8_t getLightCapabilities(void) const { return _capabilities; } + inline bool isSelected() const { return selected; } + inline bool isInTransition() const { return _t != nullptr; } + inline bool isActive() const { return stop > start; } + inline bool is2D() const { return (width()>1 && height()>1); } + inline bool hasRGB() const { return _isRGB; } + inline bool hasWhite() const { return _hasW; } + inline bool isCCT() const { return _isCCT; } + inline uint16_t width() const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) + inline uint16_t height() const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) + 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; } - static uint16_t getUsedSegmentData(void) { return _usedSegmentData; } - static void addUsedSegmentData(int len) { _usedSegmentData += len; } + inline static uint16_t getUsedSegmentData() { return _usedSegmentData; } + inline static void addUsedSegmentData(int len) { _usedSegmentData += len; } #ifndef WLED_DISABLE_MODE_BLEND - static void modeBlend(bool blend) { _modeBlend = blend; } + inline static void modeBlend(bool blend) { _modeBlend = blend; } #endif static void handleRandomPalette(); - inline static const CRGBPalette16 &getCurrentPalette(void) { return Segment::_currentPalette; } + 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); bool setColor(uint8_t slot, uint32_t c); //returns true if changed @@ -546,39 +546,39 @@ typedef struct Segment { void setMode(uint8_t fx, bool loadDefaults = false); void setPalette(uint8_t pal); uint8_t differs(Segment& b) const; - void refreshLightCapabilities(void); + void refreshLightCapabilities(); // runtime data functions - inline uint16_t dataSize(void) const { return _dataLen; } + inline uint16_t dataSize() const { return _dataLen; } bool allocateData(size_t len); // allocates effect data buffer in heap and clears it - void deallocateData(void); // deallocates (frees) effect data buffer from heap - void resetIfRequired(void); // sets all SEGENV variables to 0 and clears data buffer + void deallocateData(); // deallocates (frees) effect data buffer from heap + void resetIfRequired(); // sets all SEGENV variables to 0 and clears data buffer /** * Flags that before the next effect is calculated, * the internal segment state should be reset. * Call resetIfRequired before calling the next effect function. * Safe to call from interrupts and network requests. */ - inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true) + inline void markForReset() { reset = true; } // setOption(SEG_OPTION_RESET, true) // transition functions void startTransition(uint16_t dur); // transition has to start before actual segment values change - void stopTransition(void); // ends transition mode by destroying transition structure (does nothing if not in transition) - inline void handleTransition(void) { if (progress() == 0xFFFFU) stopTransition(); } + void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition) + inline void handleTransition() { if (progress() == 0xFFFFU) stopTransition(); } #ifndef WLED_DISABLE_MODE_BLEND void swapSegenv(tmpsegd_t &tmpSegD); // copies segment data into specifed buffer, if buffer is not a transition buffer, segment data is overwritten from transition buffer void restoreSegenv(tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer #endif - uint16_t progress(void) const; // transition progression between 0-65535 - uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) - uint8_t currentMode(void) const; // currently active effect/mode (while in transition) - uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) - CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); - void setCurrentPalette(void); + [[gnu::hot]] uint16_t progress() const; // transition progression between 0-65535 + [[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) + uint8_t currentMode() const; // currently active effect/mode (while in transition) + [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) + [[gnu::hot]] CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); + void setCurrentPalette(); // 1D strip - uint16_t virtualLength(void) const; - void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color + [[gnu::hot]] uint16_t virtualLength() const; + [[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } @@ -587,7 +587,7 @@ typedef struct Segment { inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif - uint32_t getPixelColor(int i) const; + [[gnu::hot]] uint32_t getPixelColor(int i) const; // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); void fill(uint32_t c); @@ -599,8 +599,8 @@ typedef struct Segment { inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } - uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; - uint32_t color_wheel(uint8_t pos) const; + [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; + [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns @@ -613,12 +613,12 @@ typedef struct Segment { } // 2D matrix - uint16_t virtualWidth(void) const; // segment width in virtual pixels (accounts for groupping and spacing) - uint16_t virtualHeight(void) const; // segment height in virtual pixels (accounts for groupping and spacing) - uint16_t nrOfVStrips(void) const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + [[gnu::hot]] uint16_t virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) + [[gnu::hot]] uint16_t virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) + [[gnu::hot]] uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D - uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment - void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color + [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment + [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } @@ -628,7 +628,7 @@ typedef struct Segment { inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } #endif - uint32_t getPixelColorXY(int x, int y) const; + [[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const; // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } @@ -697,8 +697,8 @@ typedef struct Segment { // main "strip" class class WS2812FX { // 96 bytes - typedef uint16_t (*mode_ptr)(void); // pointer to mode function - typedef void (*show_callback)(void); // pre show callback + typedef uint16_t (*mode_ptr)(); // pointer to mode function + typedef void (*show_callback)(); // pre show callback typedef struct ModeData { uint8_t _id; // mode (effect) id mode_ptr _fcn; // mode (effect) function @@ -764,29 +764,29 @@ class WS2812FX { // 96 bytes customPalettes.clear(); } - static WS2812FX* getInstance(void) { return instance; } + static WS2812FX* getInstance() { return instance; } void #ifdef WLED_DEBUG printSize(), // prints memory usage for strip components #endif finalizeInit(), // initialises strip components - service(void), // executes effect functions when due and calls strip.show() + service(), // executes effect functions when due and calls strip.show() setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API) setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API) setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K) setBrightness(uint8_t b, bool direct = false), // sets strip brightness setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay - purgeSegments(void), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) + purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(uint8_t n), resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs fixInvalidSegments(), // fixes incorrect segment configuration setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c - show(void), // initiates LED output + show(), // initiates LED output setTargetFps(uint8_t fps), - setupEffectData(void); // add default effects to the list; defined in FX.cpp + setupEffectData(); // add default effects to the list; defined in FX.cpp inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } @@ -794,74 +794,74 @@ class WS2812FX { // 96 bytes inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) - inline void trigger(void) { _triggered = true; } // Forces the next frame to be computed on all active segments. + inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments. inline void setShowCallback(show_callback cb) { _callback = cb; } inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms) inline void appendSegment(const Segment &seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(seg); } - inline void suspend(void) { _suspend = true; } // will suspend (and canacel) strip.service() execution - inline void resume(void) { _suspend = false; } // will resume strip.service() execution + inline void suspend() { _suspend = true; } // will suspend (and canacel) strip.service() execution + inline void resume() { _suspend = false; } // will resume strip.service() execution bool paletteFade, - checkSegmentAlignment(void), - hasRGBWBus(void) const, - hasCCTBus(void) const, - isUpdating(void) const, // return true if the strip is being sent pixel updates + checkSegmentAlignment(), + hasRGBWBus() const, + hasCCTBus() const, + isUpdating() const, // return true if the strip is being sent pixel updates deserializeMap(uint8_t n=0); - inline bool isServicing(void) const { return _isServicing; } // returns true if strip.service() is executing - inline bool hasWhiteChannel(void) const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel - inline bool isOffRefreshRequired(void) const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) - inline bool isSuspended(void) const { return _suspend; } // returns true if strip.service() execution is suspended - inline bool needsUpdate(void) const { return _triggered; } // returns true if strip received a trigger() request + inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing + inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel + inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) + inline bool isSuspended() const { return _suspend; } // returns true if strip.service() execution is suspended + inline bool needsUpdate() const { return _triggered; } // returns true if strip received a trigger() request uint8_t paletteBlend, cctBlending, - getActiveSegmentsNum(void) const, - getFirstSelectedSegId(void) const, - getLastActiveSegmentId(void) const, + getActiveSegmentsNum() const, + getFirstSelectedSegId() const, + getLastActiveSegmentId() const, getActiveSegsLightCapabilities(bool selectedOnly = false) const, addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; - inline uint8_t getBrightness(void) const { return _brightness; } // returns current strip brightness - inline uint8_t getMaxSegments(void) const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) - inline uint8_t getSegmentsNum(void) const { return _segments.size(); } // returns currently present segments - inline uint8_t getCurrSegmentId(void) const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) - inline uint8_t getMainSegmentId(void) const { return _mainSegment; } // returns main segment index - inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } - inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval - inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects + inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness + inline uint8_t getMaxSegments() const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments + inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) + inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index + inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } + inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval + inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects uint16_t - getLengthPhysical(void) const, - getLengthTotal(void) const, // will include virtual/nonexistent pixels in matrix + getLengthPhysical() const, + getLengthTotal() const, // will include virtual/nonexistent pixels in matrix getFps() const, getMappedPixelIndex(uint16_t index) const; - inline uint16_t getFrameTime(void) const { return _frametime; } // returns amount of time a frame should take (in ms) - inline uint16_t getMinShowDelay(void) const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) - inline uint16_t getLength(void) const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) - inline uint16_t getTransition(void) const { return _transitionDur; } // returns currently set transition time (in ms) + 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 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) uint32_t now, timebase, getPixelColor(uint16_t) const; - inline uint32_t getLastShow(void) const { return _lastShow; } // returns millis() timestamp of last strip.show() call + inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition const char * getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } const char ** - getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data + getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data Segment& getSegment(uint8_t id); - inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" - inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; } // returns reference to main segment - inline Segment* getSegments(void) { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) + inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" + inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment + inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) // 2D support (panels) bool @@ -908,7 +908,7 @@ class WS2812FX { // 96 bytes // end 2D support - void loadCustomPalettes(void); // loads custom palettes from JSON + void loadCustomPalettes(); // loads custom palettes from JSON std::vector customPalettes; // TODO: move custom palettes out of WS2812FX class struct { diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index df2dc8d0..26ec1d60 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -161,7 +161,7 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR_YN Segment::XY(uint16_t x, uint16_t y) +uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) { unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7a6fa40b..d3521c90 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1197,7 +1197,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ /////////////////////////////////////////////////////////////////////////////// //do not call this method from system context (network callback) -void WS2812FX::finalizeInit(void) { +void WS2812FX::finalizeInit() { //reset segment runtimes for (segment &seg : _segments) { seg.markForReset(); @@ -1366,7 +1366,7 @@ uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) const { return BusManager::getPixelColor(i); } -void WS2812FX::show(void) { +void WS2812FX::show() { // avoid race condition, capture _callback value show_callback callback = _callback; if (callback) callback(); @@ -1463,7 +1463,7 @@ uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const { return totalLC; } -uint8_t WS2812FX::getFirstSelectedSegId(void) const { +uint8_t WS2812FX::getFirstSelectedSegId() const { size_t i = 0; for (const segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) return i; @@ -1481,14 +1481,14 @@ void WS2812FX::setMainSegmentId(uint8_t n) { return; } -uint8_t WS2812FX::getLastActiveSegmentId(void) const { +uint8_t WS2812FX::getLastActiveSegmentId() const { for (size_t i = _segments.size() -1; i > 0; i--) { if (_segments[i].isActive()) return i; } return 0; } -uint8_t WS2812FX::getActiveSegmentsNum(void) const { +uint8_t WS2812FX::getActiveSegmentsNum() const { uint8_t c = 0; for (size_t i = 0; i < _segments.size(); i++) { if (_segments[i].isActive()) c++; @@ -1496,13 +1496,13 @@ uint8_t WS2812FX::getActiveSegmentsNum(void) const { return c; } -uint16_t WS2812FX::getLengthTotal(void) const { +uint16_t WS2812FX::getLengthTotal() const { unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D if (isMatrix && _length > len) len = _length; // for 2D with trailing strip return len; } -uint16_t WS2812FX::getLengthPhysical(void) const { +uint16_t WS2812FX::getLengthPhysical() const { unsigned len = 0; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); @@ -1515,7 +1515,7 @@ uint16_t WS2812FX::getLengthPhysical(void) const { //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. //returns if there is an RGBW bus (supports RGB and White, not only white) //not influenced by auto-white mode, also true if white slider does not affect output white channel -bool WS2812FX::hasRGBWBus(void) const { +bool WS2812FX::hasRGBWBus() const { for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); if (bus == nullptr || bus->getLength()==0) break; @@ -1524,7 +1524,7 @@ bool WS2812FX::hasRGBWBus(void) const { return false; } -bool WS2812FX::hasCCTBus(void) const { +bool WS2812FX::hasCCTBus() const { if (cctFromRgb && !correctWB) return false; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ebea7ea0..ac1dee00 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -37,6 +37,8 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) */ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) { + if (c1 == BLACK) return c2; + if (c2 == BLACK) return c1; if (fast) { uint8_t r = R(c1); uint8_t g = G(c1); @@ -68,17 +70,18 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { + if (c1 == BLACK || amount + video == 0) return BLACK; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t r = R(c1); uint32_t g = G(c1); uint32_t b = B(c1); uint32_t w = W(c1); - uint32_t scale = amount + !video; // 32bit for faster calculation + uint32_t scale = amount; // 32bit for faster calculation if (video) { - scaledcolor = (((r * scale) >> 8) << 16) + ((r && scale) ? 1 : 0); - scaledcolor |= (((g * scale) >> 8) << 8) + ((g && scale) ? 1 : 0); - scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); - scaledcolor |= (((w * scale) >> 8) << 24) + ((w && scale) ? 1 : 0); + scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16; + scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8; + scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); + scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24; } else { scaledcolor = ((r * scale) >> 8) << 16; scaledcolor |= ((g * scale) >> 8) << 8; @@ -195,7 +198,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) RGBpalettecolors[3]); } -CRGBPalette16 generateRandomPalette(void) //generate fully random palette +CRGBPalette16 generateRandomPalette() //generate fully random palette { return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)), CHSV(random8(), random8(160, 255), random8(128, 255)), @@ -476,14 +479,14 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) } } -uint8_t NeoGammaWLEDMethod::Correct(uint8_t value) +uint8_t IRAM_ATTR NeoGammaWLEDMethod::Correct(uint8_t value) { if (!gammaCorrectCol) return value; return gammaT[value]; } // used for color gamma correction -uint32_t NeoGammaWLEDMethod::Correct32(uint32_t color) +uint32_t IRAM_ATTR NeoGammaWLEDMethod::Correct32(uint32_t color) { if (!gammaCorrectCol) return color; uint8_t w = W(color); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d95b8ef8..f8399b1a 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -69,20 +69,20 @@ typedef struct WiFiConfig { // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) class NeoGammaWLEDMethod { public: - static uint8_t Correct(uint8_t value); // apply Gamma to single channel - static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB) - static void calcGammaTable(float gamma); // re-calculates & fills gamma table + [[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel + [[gnu::hot]] static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB) + static void calcGammaTable(float gamma); // re-calculates & fills gamma table static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB) private: static uint8_t gammaT[]; }; #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) -uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); -uint32_t color_add(uint32_t,uint32_t, bool fast=false); -uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); +[[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); +[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false); +[[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); -CRGBPalette16 generateRandomPalette(void); +CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb void colorKtoRGB(uint16_t kelvin, byte* rgb); From 23400d0449841a5d45f39e24b657cfa227d47502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Thu, 12 Sep 2024 08:30:46 +0200 Subject: [PATCH 27/50] Conditional IRAM for gamma --- wled00/colors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ac1dee00..478a0a27 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -479,14 +479,14 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) } } -uint8_t IRAM_ATTR NeoGammaWLEDMethod::Correct(uint8_t value) +uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) { if (!gammaCorrectCol) return value; return gammaT[value]; } // used for color gamma correction -uint32_t IRAM_ATTR NeoGammaWLEDMethod::Correct32(uint32_t color) +uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color) { if (!gammaCorrectCol) return color; uint8_t w = W(color); From 6169c458bfb327a2a700481be52e6b71584df0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Thu, 12 Sep 2024 08:48:31 +0200 Subject: [PATCH 28/50] Remove void --- wled00/bus_manager.cpp | 36 +++++----- wled00/bus_manager.h | 158 ++++++++++++++++++++--------------------- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index c6c54885..72329717 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -173,7 +173,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(void) { +uint8_t BusDigital::estimateCurrentAndLimitBri() { bool useWackyWS2815PowerModel = false; byte actualMilliampsPerLed = _milliAmpsPerLed; @@ -225,7 +225,7 @@ uint8_t BusDigital::estimateCurrentAndLimitBri(void) { return newBri; } -void BusDigital::show(void) { +void BusDigital::show() { _milliAmpsTotal = 0; if (!_valid) return; @@ -286,7 +286,7 @@ void BusDigital::show(void) { if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri); } -bool BusDigital::canShow(void) const { +bool BusDigital::canShow() const { if (!_valid) return true; return PolyBus::canShow(_busPtr, _iType); } @@ -410,12 +410,12 @@ std::vector BusDigital::getLEDTypes() { }; } -void BusDigital::reinit(void) { +void BusDigital::reinit() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins); } -void BusDigital::cleanup(void) { +void BusDigital::cleanup() { DEBUG_PRINTLN(F("Digital Cleanup.")); PolyBus::cleanup(_busPtr, _iType); _iType = I_NONE; @@ -637,7 +637,7 @@ std::vector BusPwm::getLEDTypes() { }; } -void BusPwm::deallocatePins(void) { +void BusPwm::deallocatePins() { unsigned numPins = getPins(); for (unsigned i = 0; i < numPins; i++) { pinManager.deallocatePin(_pins[i], PinOwner::BusPwm); @@ -689,7 +689,7 @@ uint32_t BusOnOff::getPixelColor(uint16_t pix) const { return RGBW32(_data[0], _data[0], _data[0], _data[0]); } -void BusOnOff::show(void) { +void BusOnOff::show() { if (!_valid) return; digitalWrite(_pin, _reversed ? !(bool)_data[0] : (bool)_data[0]); } @@ -751,7 +751,7 @@ uint32_t BusNetwork::getPixelColor(uint16_t pix) const { return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0)); } -void BusNetwork::show(void) { +void BusNetwork::show() { if (!_valid || !canShow()) return; _broadcastLock = true; realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, hasWhite()); @@ -778,7 +778,7 @@ std::vector BusNetwork::getLEDTypes() { }; } -void BusNetwork::cleanup(void) { +void BusNetwork::cleanup() { _type = I_NONE; _valid = false; freeData(); @@ -839,7 +839,7 @@ static String LEDTypesToJson(const std::vector& types) { } // credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 -String BusManager::getLEDTypesJSONString(void) { +String BusManager::getLEDTypesJSONString() { String json = "["; json += LEDTypesToJson(BusDigital::getLEDTypes()); json += LEDTypesToJson(BusOnOff::getLEDTypes()); @@ -850,13 +850,13 @@ String BusManager::getLEDTypesJSONString(void) { return json; } -void BusManager::useParallelOutput(void) { +void BusManager::useParallelOutput() { _parallelOutputs = 8; // hardcoded since we use NPB I2S x8 methods PolyBus::setParallelI2S1Output(); } //do not call this method from system context (network callback) -void BusManager::removeAll(void) { +void BusManager::removeAll() { DEBUG_PRINTLN(F("Removing all.")); //prevents crashes due to deleting busses while in use. while (!canAllShow()) yield(); @@ -870,7 +870,7 @@ void BusManager::removeAll(void) { // #2478 // If enabled, RMT idle level is set to HIGH when off // to prevent leakage current when using an N-channel MOSFET to toggle LED power -void BusManager::esp32RMTInvertIdle(void) { +void BusManager::esp32RMTInvertIdle() { bool idle_out; unsigned rmt = 0; for (unsigned u = 0; u < numBusses(); u++) { @@ -901,7 +901,7 @@ void BusManager::esp32RMTInvertIdle(void) { } #endif -void BusManager::on(void) { +void BusManager::on() { #ifdef ESP8266 //Fix for turning off onboard LED breaking bus if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { @@ -922,7 +922,7 @@ void BusManager::on(void) { #endif } -void BusManager::off(void) { +void BusManager::off() { #ifdef ESP8266 // turn off built-in LED if strip is turned off // this will break digital bus so will need to be re-initialised on On @@ -937,7 +937,7 @@ void BusManager::off(void) { #endif } -void BusManager::show(void) { +void BusManager::show() { _milliAmpsUsed = 0; for (unsigned i = 0; i < numBusses; i++) { busses[i]->show(); @@ -984,7 +984,7 @@ uint32_t BusManager::getPixelColor(uint16_t pix) { return 0; } -bool BusManager::canAllShow(void) { +bool BusManager::canAllShow() { for (unsigned i = 0; i < numBusses; i++) { if (!busses[i]->canShow()) return false; } @@ -997,7 +997,7 @@ Bus* BusManager::getBus(uint8_t busNr) { } //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) -uint16_t BusManager::getTotalLength(void) { +uint16_t BusManager::getTotalLength() { unsigned len = 0; for (unsigned i=0; igetLength(); return len; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index c3ebcad6..8205bd66 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -79,46 +79,46 @@ class Bus { virtual ~Bus() {} //throw the bus under the bus - virtual void show(void) = 0; - virtual bool canShow(void) const { return true; } - virtual void setStatusPixel(uint32_t c) {} + 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(void) const { return isOk() ? _len : 0; } - virtual uint8_t getColorOrder(void) const { return COL_ORDER_RGB; } - virtual uint8_t skippedLeds(void) const { return 0; } - virtual uint16_t getFrequency(void) const { return 0U; } - virtual uint16_t getLEDCurrent(void) const { return 0; } - virtual uint16_t getUsedCurrent(void) const { return 0; } - virtual uint16_t getMaxCurrent(void) const { return 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(void) const { return _hasRgb; } - inline bool hasWhite(void) const { return _hasWhite; } - inline bool hasCCT(void) const { return _hasCCT; } - inline bool isDigital(void) const { return isDigital(_type); } - inline bool is2Pin(void) const { return is2Pin(_type); } - inline bool isOnOff(void) const { return isOnOff(_type); } - inline bool isPWM(void) const { return isPWM(_type); } - inline bool isVirtual(void) const { return isVirtual(_type); } - inline bool is16bit(void) const { return is16bit(_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(void) const { return _autoWhiteMode; } - inline uint8_t getNumberOfChannels(void) const { return hasWhite() + 3*hasRGB() + hasCCT(); } - inline uint16_t getStart(void) const { return _start; } - inline uint8_t getType(void) const { return _type; } - inline bool isOk(void) const { return _valid; } - inline bool isReversed(void) const { return _reversed; } - inline bool isOffRefreshRequired(void) const { return _needsRefresh; } - inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } + inline bool hasRGB() const { return _hasRgb; } + inline bool hasWhite() const { return _hasWhite; } + inline bool hasCCT() const { return _hasCCT; } + inline bool isDigital() const { return isDigital(_type); } + inline bool is2Pin() const { return is2Pin(_type); } + inline bool isOnOff() const { return isOnOff(_type); } + inline bool isPWM() const { return isPWM(_type); } + inline bool isVirtual() const { return isVirtual(_type); } + inline bool is16bit() const { return is16bit(_type); } + inline 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(void) { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes - static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK - static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes + static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static 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); } @@ -144,11 +144,11 @@ class Bus { static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; } static constexpr int numPWMPins(uint8_t type) { return (type - 40); } - static inline int16_t getCCT(void) { return _cct; } + static inline int16_t getCCT() { return _cct; } static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } - static inline uint8_t getGlobalAWMode(void) { return _gAWM; } + static inline uint8_t getGlobalAWMode() { return _gAWM; } static inline void setCCT(int16_t cct) { _cct = cct; } - static inline uint8_t getCCTBlend(void) { return _cctBlend; } + static inline uint8_t getCCTBlend() { return _cctBlend; } 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 @@ -197,24 +197,24 @@ class BusDigital : public Bus { BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com); ~BusDigital() { cleanup(); } - void show(void) override; - bool canShow(void) const override; + void show() override; + bool canShow() const override; void setBrightness(uint8_t b) override; void setStatusPixel(uint32_t c) override; void setPixelColor(uint16_t pix, uint32_t c) override; void setColorOrder(uint8_t colorOrder) override; uint32_t getPixelColor(uint16_t pix) const override; - uint8_t getColorOrder(void) const override { return _colorOrder; } + uint8_t getColorOrder() const override { return _colorOrder; } uint8_t getPins(uint8_t* pinArray = nullptr) const override; - uint8_t skippedLeds(void) const override { return _skip; } - uint16_t getFrequency(void) const override { return _frequencykHz; } - uint16_t getLEDCurrent(void) const override { return _milliAmpsPerLed; } - uint16_t getUsedCurrent(void) const override { return _milliAmpsTotal; } - uint16_t getMaxCurrent(void) const override { return _milliAmpsMax; } - void reinit(void); - void cleanup(void); + uint8_t 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; } + void reinit(); + void cleanup(); - static std::vector getLEDTypes(void); + static std::vector getLEDTypes(); private: uint8_t _skip; @@ -240,7 +240,7 @@ class BusDigital : public Bus { return c; } - uint8_t estimateCurrentAndLimitBri(void); + uint8_t estimateCurrentAndLimitBri(); }; @@ -252,11 +252,11 @@ class BusPwm : public Bus { 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; - uint16_t getFrequency(void) const override { return _frequency; } - void show(void) override; - void cleanup(void) { deallocatePins(); } + uint16_t getFrequency() const override { return _frequency; } + void show() override; + void cleanup() { deallocatePins(); } - static std::vector getLEDTypes(void); + static std::vector getLEDTypes(); private: uint8_t _pins[5]; @@ -267,7 +267,7 @@ class BusPwm : public Bus { uint8_t _depth; uint16_t _frequency; - void deallocatePins(void); + void deallocatePins(); }; @@ -279,10 +279,10 @@ class BusOnOff : public Bus { 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 show(void) override; - void cleanup(void) { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } + void show() override; + void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } - static std::vector getLEDTypes(void); + static std::vector getLEDTypes(); private: uint8_t _pin; @@ -295,14 +295,14 @@ class BusNetwork : public Bus { BusNetwork(BusConfig &bc); ~BusNetwork() { cleanup(); } - bool canShow(void) const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out + 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; - void show(void) override; - void cleanup(void); + void show() override; + void cleanup(); - static std::vector getLEDTypes(void); + static std::vector getLEDTypes(); private: IPAddress _client; @@ -367,20 +367,20 @@ 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(void) { return _milliAmpsUsed; } - static uint16_t ablMilliampsMax(void) { return _milliAmpsMax; } + static uint16_t currentMilliamps() { return _milliAmpsUsed; } + static uint16_t ablMilliampsMax() { return _milliAmpsMax; } static int add(BusConfig &bc); - static void useParallelOutput(void); // workaround for inaccessible PolyBus + static void useParallelOutput(); // workaround for inaccessible PolyBus //do not call this method from system context (network callback) - static void removeAll(void); + static void removeAll(); - static void on(void); - static void off(void); + static void on(); + static void off(); - static void show(void); - static bool canAllShow(void); + static void show(); + static bool canAllShow(); static void setStatusPixel(uint32_t c); static void setPixelColor(uint16_t pix, uint32_t c); static void setBrightness(uint8_t b); @@ -389,16 +389,16 @@ class BusManager { 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 inline int16_t getSegmentCCT(void) { return Bus::getCCT(); } + 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(void); - static inline uint8_t getNumBusses(void) { return numBusses; } - static String getLEDTypesJSONString(void); + static uint16_t getTotalLength(); + static inline uint8_t getNumBusses() { return numBusses; } + static String getLEDTypesJSONString(); - static inline ColorOrderMap& getColorOrderMap(void) { return colorOrderMap; } + static inline ColorOrderMap& getColorOrderMap() { return colorOrderMap; } private: static uint8_t numBusses; @@ -409,9 +409,9 @@ class BusManager { static uint8_t _parallelOutputs; #ifdef ESP32_DATA_IDLE_HIGH - static void esp32RMTInvertIdle(void) ; + static void esp32RMTInvertIdle() ; #endif - static uint8_t getNumVirtualBusses(void) { + static uint8_t getNumVirtualBusses() { int j = 0; for (int i=0; iisVirtual()) j++; return j; From e59b38a5d23d0c885c2ea49d4cfb4f761218aca7 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 12 Sep 2024 15:04:10 +0200 Subject: [PATCH 29/50] Fix unsigned long in debug printf --- wled00/wled.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 840c06bf..ade01dfb 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -264,7 +264,7 @@ void WLED::loop() if (loopMillis > maxLoopMillis) maxLoopMillis = loopMillis; if (millis() - debugTime > 29999) { DEBUG_PRINTLN(F("---DEBUG INFO---")); - DEBUG_PRINTF_P(PSTR("Runtime: %u\n"), millis()); + DEBUG_PRINTF_P(PSTR("Runtime: %lu\n"), millis()); DEBUG_PRINTF_P(PSTR("Unix time: %u,%03u\n"), toki.getTime().sec, toki.getTime().ms); DEBUG_PRINTF_P(PSTR("Free heap: %u\n"), ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) @@ -283,14 +283,14 @@ void WLED::loop() wifiStateChangedTime = millis(); } lastWifiState = WiFi.status(); - DEBUG_PRINTF_P(PSTR("State time: %u\n"), wifiStateChangedTime); - DEBUG_PRINTF_P(PSTR("NTP last sync: %u\n"), ntpLastSyncTime); + DEBUG_PRINTF_P(PSTR("State time: %lu\n"), wifiStateChangedTime); + DEBUG_PRINTF_P(PSTR("NTP last sync: %lu\n"), ntpLastSyncTime); DEBUG_PRINTF_P(PSTR("Client IP: %u.%u.%u.%u\n"), Network.localIP()[0], Network.localIP()[1], Network.localIP()[2], Network.localIP()[3]); if (loops > 0) { // avoid division by zero - DEBUG_PRINTF_P(PSTR("Loops/sec: %u\n"), loops / 30); - DEBUG_PRINTF_P(PSTR("Loop time[ms]: %u/%u\n"), avgLoopMillis/loops, maxLoopMillis); - DEBUG_PRINTF_P(PSTR("UM time[ms]: %u/%u\n"), avgUsermodMillis/loops, maxUsermodMillis); - DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%u\n"), avgStripMillis/loops, maxStripMillis); + DEBUG_PRINTF_P(PSTR("Loops/sec: %u\n"), loops / 30); + DEBUG_PRINTF_P(PSTR("Loop time[ms]: %u/%lu\n"), avgLoopMillis/loops, maxLoopMillis); + DEBUG_PRINTF_P(PSTR("UM time[ms]: %u/%lu\n"), avgUsermodMillis/loops, maxUsermodMillis); + DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%lu\n"), avgStripMillis/loops, maxStripMillis); } strip.printSize(); loops = 0; @@ -368,11 +368,8 @@ void WLED::setup() DEBUG_PRINTLN(F("arduino-esp32 v1.0.x\n")); // we can't say in more detail. #endif - DEBUG_PRINTF_P(PSTR("CPU: "), ESP.getChipModel()); - DEBUG_PRINTF_P(PSTR(" rev."), ESP.getChipRevision()); - DEBUG_PRINTF_P(PSTR(", %d core(s)"), ESP.getChipCores()); - DEBUG_PRINTF_P(PSTR(", %d MHz.\n"), ESP.getCpuFreqMHz()); - DEBUG_PRINTF_P(PSTR("FLASH: %dMB, Mode %d "), (ESP.getFlashChipSize()/1024)/1024, ESP.getFlashChipMode()); + DEBUG_PRINTF_P(PSTR("CPU: %s rev.%d, %d core(s), %d MHz.\n"), ESP.getChipModel(), (int)ESP.getChipRevision(), ESP.getChipCores(), ESP.getCpuFreqMHz()); + DEBUG_PRINTF_P(PSTR("FLASH: %d MB, Mode %d "), (ESP.getFlashChipSize()/1024)/1024, (int)ESP.getFlashChipMode()); #ifdef WLED_DEBUG switch (ESP.getFlashChipMode()) { // missing: Octal modes From 71fcd6482d03d3e6c3fb1c270d478b6240313579 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 12 Sep 2024 15:07:49 +0200 Subject: [PATCH 30/50] Fix CRLF in boblight --- usermods/boblight/boblight.h | 918 +++++++++++++++++------------------ 1 file changed, 459 insertions(+), 459 deletions(-) diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index 32208a4f..916f7da9 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -1,459 +1,459 @@ -#pragma once - -#include "wled.h" - -/* - * Usermod that implements BobLight "ambilight" protocol - * - * See the accompanying README.md file for more info. - */ - -#ifndef BOB_PORT - #define BOB_PORT 19333 // Default boblightd port -#endif - -class BobLightUsermod : public Usermod { - typedef struct _LIGHT { - char lightname[5]; - float hscan[2]; - float vscan[2]; - } light_t; - - private: - unsigned long lastTime = 0; - bool enabled = false; - bool initDone = false; - - light_t *lights = nullptr; - uint16_t numLights = 0; // 16 + 9 + 16 + 9 - uint16_t top, bottom, left, right; // will be filled in readFromConfig() - uint16_t pct; - - WiFiClient bobClient; - WiFiServer *bob; - uint16_t bobPort = BOB_PORT; - - static const char _name[]; - static const char _enabled[]; - - /* - # boblight - # Copyright (C) Bob 2009 - # - # makeboblight.sh created by Adam Boeglin - # - # boblight is free software: you can redistribute it and/or modify it - # under the terms of the GNU General Public License as published by the - # Free Software Foundation, either version 3 of the License, or - # (at your option) any later version. - # - # boblight is distributed in the hope that it will be useful, but - # WITHOUT ANY WARRANTY; without even the implied warranty of - # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - # See the GNU General Public License for more details. - # - # You should have received a copy of the GNU General Public License along - # with this program. If not, see . - */ - - // fills the lights[] array with position & depth of scan for each LED - void fillBobLights(int bottom, int left, int top, int right, float pct_scan) { - - int lightcount = 0; - int total = top+left+right+bottom; - int bcount; - - if (total > strip.getLengthTotal()) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); - return; - } - - // start left part of bottom strip (clockwise direction, 1st half) - if (bottom > 0) { - bcount = 1; - float brange = 100.0/bottom; - float bcurrent = 50.0; - if (bottom < top) { - int diff = top - bottom; - brange = 100.0/top; - bcurrent -= (diff/2)*brange; - } - while (bcount <= bottom/2) { - float btop = bcurrent - brange; - String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = btop; - lights[lightcount].hscan[1] = bcurrent; - lights[lightcount].vscan[0] = 100 - pct_scan; - lights[lightcount].vscan[1] = 100; - lightcount+=1; - bcurrent = btop; - bcount+=1; - } - } - - // left side - if (left > 0) { - int lcount = 1; - float lrange = 100.0/left; - float lcurrent = 100.0; - while (lcount <= left) { - float ltop = lcurrent - lrange; - String name = "l"+String(lcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = 0; - lights[lightcount].hscan[1] = pct_scan; - lights[lightcount].vscan[0] = ltop; - lights[lightcount].vscan[1] = lcurrent; - lightcount+=1; - lcurrent = ltop; - lcount+=1; - } - } - - // top side - if (top > 0) { - int tcount = 1; - float trange = 100.0/top; - float tcurrent = 0; - while (tcount <= top) { - float ttop = tcurrent + trange; - String name = "t"+String(tcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = tcurrent; - lights[lightcount].hscan[1] = ttop; - lights[lightcount].vscan[0] = 0; - lights[lightcount].vscan[1] = pct_scan; - lightcount+=1; - tcurrent = ttop; - tcount+=1; - } - } - - // right side - if (right > 0) { - int rcount = 1; - float rrange = 100.0/right; - float rcurrent = 0; - while (rcount <= right) { - float rtop = rcurrent + rrange; - String name = "r"+String(rcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = 100-pct_scan; - lights[lightcount].hscan[1] = 100; - lights[lightcount].vscan[0] = rcurrent; - lights[lightcount].vscan[1] = rtop; - lightcount+=1; - rcurrent = rtop; - rcount+=1; - } - } - - // right side of bottom strip (2nd half) - if (bottom > 0) { - float brange = 100.0/bottom; - float bcurrent = 100; - if (bottom < top) { - brange = 100.0/top; - } - while (bcount <= bottom) { - float btop = bcurrent - brange; - String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = btop; - lights[lightcount].hscan[1] = bcurrent; - lights[lightcount].vscan[0] = 100 - pct_scan; - lights[lightcount].vscan[1] = 100; - lightcount+=1; - bcurrent = btop; - bcount+=1; - } - } - - numLights = lightcount; - - #if WLED_DEBUG - DEBUG_PRINTLN(F("Fill light data: ")); - DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); - for (int i=0; i strip.getLengthTotal() ) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); - DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); - totalLights = strip.getLengthTotal(); - top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); - left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); - } - lights = new light_t[totalLights]; - if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights - else enable(false); - initDone = true; - } - - void connected() override { - // we can only start server when WiFi is connected - if (!bob) bob = new WiFiServer(bobPort, 1); - bob->begin(); - bob->setNoDelay(true); - } - - void loop() override { - if (!enabled || strip.isUpdating()) return; - if (millis() - lastTime > 10) { - lastTime = millis(); - pollBob(); - } - } - - void enable(bool en) { enabled = en; } - -#ifndef WLED_DISABLE_MQTT - /** - * handling of MQTT message - * topic only contains stripped topic (part after /wled/MAC) - * topic should look like: /swipe with amessage of [up|down] - */ - bool onMqttMessage(char* topic, char* payload) override { - //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { - // String action = payload; - // if (action == "on") { - // enable(true); - // return true; - // } else if (action == "off") { - // enable(false); - // return true; - // } - //} - return false; - } - - /** - * subscribe to MQTT topic for controlling usermod - */ - void onMqttConnect(bool sessionPresent) override { - //char subuf[64]; - //if (mqttDeviceTopic[0] != 0) { - // strcpy(subuf, mqttDeviceTopic); - // strcat_P(subuf, PSTR("/subtopic")); - // mqtt->subscribe(subuf, 0); - //} - } -#endif - - void addToJsonInfo(JsonObject& root) override - { - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray(FPSTR(_name)); - String uiDomString = F(""); - infoArr.add(uiDomString); - } - - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void addToJsonState(JsonObject& root) override - { - } - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void readFromJsonState(JsonObject& root) override { - if (!initDone) return; // prevent crash on boot applyPreset() - bool en = enabled; - JsonObject um = root[FPSTR(_name)]; - if (!um.isNull()) { - if (um[FPSTR(_enabled)].is()) { - en = um[FPSTR(_enabled)].as(); - } else { - String str = um[FPSTR(_enabled)]; // checkbox -> off or on - en = (bool)(str!="off"); // off is guaranteed to be present - } - if (en != enabled && lights) { - enable(en); - if (!enabled && bob && bob->hasClient()) { - if (bobClient) bobClient.stop(); - bobClient = bob->available(); - BobClear(); - exitRealtime(); - } - } - } - } - - void appendConfigData() override { - //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field - } - - void addToConfig(JsonObject& root) override { - JsonObject umData = root.createNestedObject(FPSTR(_name)); - umData[FPSTR(_enabled)] = enabled; - umData[ "port" ] = bobPort; - umData[F("top")] = top; - umData[F("bottom")] = bottom; - umData[F("left")] = left; - umData[F("right")] = right; - umData[F("pct")] = pct; - } - - bool readFromConfig(JsonObject& root) override { - JsonObject umData = root[FPSTR(_name)]; - bool configComplete = !umData.isNull(); - - bool en = enabled; - configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); - enable(en); - - configComplete &= getJsonValue(umData[ "port" ], bobPort); - configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); - configComplete &= getJsonValue(umData[F("top")], top, 16); - configComplete &= getJsonValue(umData[F("left")], left, 9); - configComplete &= getJsonValue(umData[F("right")], right, 9); - configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%] - pct = MIN(50,MAX(1,pct)); - - uint16_t totalLights = bottom + left + top + right; - if (initDone && numLights != totalLights) { - if (lights) delete[] lights; - setup(); - } - return configComplete; - } - - /* - * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. - * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. - * Commonly used for custom clocks (Cronixie, 7 segment) - */ - void handleOverlayDraw() override { - //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black - } - - uint16_t getId() override { return USERMOD_ID_BOBLIGHT; } - -}; - -// strings to reduce flash memory usage (used more than twice) -const char BobLightUsermod::_name[] PROGMEM = "BobLight"; -const char BobLightUsermod::_enabled[] PROGMEM = "enabled"; - -// main boblight handling (definition here prevents inlining) -void BobLightUsermod::pollBob() { - - //check if there are any new clients - if (bob && bob->hasClient()) { - //find free/disconnected spot - if (!bobClient || !bobClient.connected()) { - if (bobClient) bobClient.stop(); - bobClient = bob->available(); - DEBUG_PRINTLN(F("Boblight: Client connected.")); - } - //no free/disconnected spot so reject - WiFiClient bobClientTmp = bob->available(); - bobClientTmp.stop(); - BobClear(); - exitRealtime(); - } - - //check clients for data - if (bobClient && bobClient.connected()) { - realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected - - //get data from the client - while (bobClient.available()) { - String input = bobClient.readStringUntil('\n'); - // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial - if (input.startsWith(F("hello"))) { - DEBUG_PRINTLN(F("hello")); - bobClient.print(F("hello\n")); - } else if (input.startsWith(F("ping"))) { - DEBUG_PRINTLN(F("ping 1")); - bobClient.print(F("ping 1\n")); - } else if (input.startsWith(F("get version"))) { - DEBUG_PRINTLN(F("version 5")); - bobClient.print(F("version 5\n")); - } else if (input.startsWith(F("get lights"))) { - char tmp[64]; - String answer = ""; - sprintf_P(tmp, PSTR("lights %d\n"), numLights); - DEBUG_PRINT(tmp); - answer.concat(tmp); - for (int i=0; i ... - input.remove(0,10); - String tmp = input.substring(0,input.indexOf(' ')); - - int light_id = -1; - for (uint16_t i=0; iavailable(); - BobClear(); - } - } - } -} +#pragma once + +#include "wled.h" + +/* + * Usermod that implements BobLight "ambilight" protocol + * + * See the accompanying README.md file for more info. + */ + +#ifndef BOB_PORT + #define BOB_PORT 19333 // Default boblightd port +#endif + +class BobLightUsermod : public Usermod { + typedef struct _LIGHT { + char lightname[5]; + float hscan[2]; + float vscan[2]; + } light_t; + + private: + unsigned long lastTime = 0; + bool enabled = false; + bool initDone = false; + + light_t *lights = nullptr; + uint16_t numLights = 0; // 16 + 9 + 16 + 9 + uint16_t top, bottom, left, right; // will be filled in readFromConfig() + uint16_t pct; + + WiFiClient bobClient; + WiFiServer *bob; + uint16_t bobPort = BOB_PORT; + + static const char _name[]; + static const char _enabled[]; + + /* + # boblight + # Copyright (C) Bob 2009 + # + # makeboblight.sh created by Adam Boeglin + # + # boblight is free software: you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by the + # Free Software Foundation, either version 3 of the License, or + # (at your option) any later version. + # + # boblight is distributed in the hope that it will be useful, but + # WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + # See the GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License along + # with this program. If not, see . + */ + + // fills the lights[] array with position & depth of scan for each LED + void fillBobLights(int bottom, int left, int top, int right, float pct_scan) { + + int lightcount = 0; + int total = top+left+right+bottom; + int bcount; + + if (total > strip.getLengthTotal()) { + DEBUG_PRINTLN(F("BobLight: Too many lights.")); + return; + } + + // start left part of bottom strip (clockwise direction, 1st half) + if (bottom > 0) { + bcount = 1; + float brange = 100.0/bottom; + float bcurrent = 50.0; + if (bottom < top) { + int diff = top - bottom; + brange = 100.0/top; + bcurrent -= (diff/2)*brange; + } + while (bcount <= bottom/2) { + float btop = bcurrent - brange; + String name = "b"+String(bcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = btop; + lights[lightcount].hscan[1] = bcurrent; + lights[lightcount].vscan[0] = 100 - pct_scan; + lights[lightcount].vscan[1] = 100; + lightcount+=1; + bcurrent = btop; + bcount+=1; + } + } + + // left side + if (left > 0) { + int lcount = 1; + float lrange = 100.0/left; + float lcurrent = 100.0; + while (lcount <= left) { + float ltop = lcurrent - lrange; + String name = "l"+String(lcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = 0; + lights[lightcount].hscan[1] = pct_scan; + lights[lightcount].vscan[0] = ltop; + lights[lightcount].vscan[1] = lcurrent; + lightcount+=1; + lcurrent = ltop; + lcount+=1; + } + } + + // top side + if (top > 0) { + int tcount = 1; + float trange = 100.0/top; + float tcurrent = 0; + while (tcount <= top) { + float ttop = tcurrent + trange; + String name = "t"+String(tcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = tcurrent; + lights[lightcount].hscan[1] = ttop; + lights[lightcount].vscan[0] = 0; + lights[lightcount].vscan[1] = pct_scan; + lightcount+=1; + tcurrent = ttop; + tcount+=1; + } + } + + // right side + if (right > 0) { + int rcount = 1; + float rrange = 100.0/right; + float rcurrent = 0; + while (rcount <= right) { + float rtop = rcurrent + rrange; + String name = "r"+String(rcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = 100-pct_scan; + lights[lightcount].hscan[1] = 100; + lights[lightcount].vscan[0] = rcurrent; + lights[lightcount].vscan[1] = rtop; + lightcount+=1; + rcurrent = rtop; + rcount+=1; + } + } + + // right side of bottom strip (2nd half) + if (bottom > 0) { + float brange = 100.0/bottom; + float bcurrent = 100; + if (bottom < top) { + brange = 100.0/top; + } + while (bcount <= bottom) { + float btop = bcurrent - brange; + String name = "b"+String(bcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = btop; + lights[lightcount].hscan[1] = bcurrent; + lights[lightcount].vscan[0] = 100 - pct_scan; + lights[lightcount].vscan[1] = 100; + lightcount+=1; + bcurrent = btop; + bcount+=1; + } + } + + numLights = lightcount; + + #if WLED_DEBUG + DEBUG_PRINTLN(F("Fill light data: ")); + DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); + for (int i=0; i strip.getLengthTotal() ) { + DEBUG_PRINTLN(F("BobLight: Too many lights.")); + DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); + totalLights = strip.getLengthTotal(); + top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); + left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); + } + lights = new light_t[totalLights]; + if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights + else enable(false); + initDone = true; + } + + void connected() override { + // we can only start server when WiFi is connected + if (!bob) bob = new WiFiServer(bobPort, 1); + bob->begin(); + bob->setNoDelay(true); + } + + void loop() override { + if (!enabled || strip.isUpdating()) return; + if (millis() - lastTime > 10) { + lastTime = millis(); + pollBob(); + } + } + + void enable(bool en) { enabled = en; } + +#ifndef WLED_DISABLE_MQTT + /** + * handling of MQTT message + * topic only contains stripped topic (part after /wled/MAC) + * topic should look like: /swipe with amessage of [up|down] + */ + bool onMqttMessage(char* topic, char* payload) override { + //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { + // String action = payload; + // if (action == "on") { + // enable(true); + // return true; + // } else if (action == "off") { + // enable(false); + // return true; + // } + //} + return false; + } + + /** + * subscribe to MQTT topic for controlling usermod + */ + void onMqttConnect(bool sessionPresent) override { + //char subuf[64]; + //if (mqttDeviceTopic[0] != 0) { + // strcpy(subuf, mqttDeviceTopic); + // strcat_P(subuf, PSTR("/subtopic")); + // mqtt->subscribe(subuf, 0); + //} + } +#endif + + void addToJsonInfo(JsonObject& root) override + { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + String uiDomString = F(""); + infoArr.add(uiDomString); + } + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void addToJsonState(JsonObject& root) override + { + } + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject& root) override { + if (!initDone) return; // prevent crash on boot applyPreset() + bool en = enabled; + JsonObject um = root[FPSTR(_name)]; + if (!um.isNull()) { + if (um[FPSTR(_enabled)].is()) { + en = um[FPSTR(_enabled)].as(); + } else { + String str = um[FPSTR(_enabled)]; // checkbox -> off or on + en = (bool)(str!="off"); // off is guaranteed to be present + } + if (en != enabled && lights) { + enable(en); + if (!enabled && bob && bob->hasClient()) { + if (bobClient) bobClient.stop(); + bobClient = bob->available(); + BobClear(); + exitRealtime(); + } + } + } + } + + void appendConfigData() override { + //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); + //oappend(SET_F("addOption(dd,'1st value',0);")); + //oappend(SET_F("addOption(dd,'2nd value',1);")); + oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field + } + + void addToConfig(JsonObject& root) override { + JsonObject umData = root.createNestedObject(FPSTR(_name)); + umData[FPSTR(_enabled)] = enabled; + umData[ "port" ] = bobPort; + umData[F("top")] = top; + umData[F("bottom")] = bottom; + umData[F("left")] = left; + umData[F("right")] = right; + umData[F("pct")] = pct; + } + + bool readFromConfig(JsonObject& root) override { + JsonObject umData = root[FPSTR(_name)]; + bool configComplete = !umData.isNull(); + + bool en = enabled; + configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); + enable(en); + + configComplete &= getJsonValue(umData[ "port" ], bobPort); + configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); + configComplete &= getJsonValue(umData[F("top")], top, 16); + configComplete &= getJsonValue(umData[F("left")], left, 9); + configComplete &= getJsonValue(umData[F("right")], right, 9); + configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%] + pct = MIN(50,MAX(1,pct)); + + uint16_t totalLights = bottom + left + top + right; + if (initDone && numLights != totalLights) { + if (lights) delete[] lights; + setup(); + } + return configComplete; + } + + /* + * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. + * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. + * Commonly used for custom clocks (Cronixie, 7 segment) + */ + void handleOverlayDraw() override { + //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black + } + + uint16_t getId() override { return USERMOD_ID_BOBLIGHT; } + +}; + +// strings to reduce flash memory usage (used more than twice) +const char BobLightUsermod::_name[] PROGMEM = "BobLight"; +const char BobLightUsermod::_enabled[] PROGMEM = "enabled"; + +// main boblight handling (definition here prevents inlining) +void BobLightUsermod::pollBob() { + + //check if there are any new clients + if (bob && bob->hasClient()) { + //find free/disconnected spot + if (!bobClient || !bobClient.connected()) { + if (bobClient) bobClient.stop(); + bobClient = bob->available(); + DEBUG_PRINTLN(F("Boblight: Client connected.")); + } + //no free/disconnected spot so reject + WiFiClient bobClientTmp = bob->available(); + bobClientTmp.stop(); + BobClear(); + exitRealtime(); + } + + //check clients for data + if (bobClient && bobClient.connected()) { + realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected + + //get data from the client + while (bobClient.available()) { + String input = bobClient.readStringUntil('\n'); + // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial + if (input.startsWith(F("hello"))) { + DEBUG_PRINTLN(F("hello")); + bobClient.print(F("hello\n")); + } else if (input.startsWith(F("ping"))) { + DEBUG_PRINTLN(F("ping 1")); + bobClient.print(F("ping 1\n")); + } else if (input.startsWith(F("get version"))) { + DEBUG_PRINTLN(F("version 5")); + bobClient.print(F("version 5\n")); + } else if (input.startsWith(F("get lights"))) { + char tmp[64]; + String answer = ""; + sprintf_P(tmp, PSTR("lights %d\n"), numLights); + DEBUG_PRINT(tmp); + answer.concat(tmp); + for (int i=0; i ... + input.remove(0,10); + String tmp = input.substring(0,input.indexOf(' ')); + + int light_id = -1; + for (uint16_t i=0; iavailable(); + BobClear(); + } + } + } +} From a8c5bf257395a8985c5e80bf90f0b3464db00e76 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 12 Sep 2024 15:22:37 +0200 Subject: [PATCH 31/50] Ease IRAM --- wled00/FX.h | 4 ++-- wled00/FX_fcn.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index a9de2a18..3c28274d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -573,7 +573,7 @@ typedef struct Segment { [[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) uint8_t currentMode() const; // currently active effect/mode (while in transition) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) - [[gnu::hot]] CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); + CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); void setCurrentPalette(); // 1D strip @@ -615,7 +615,7 @@ typedef struct Segment { // 2D matrix [[gnu::hot]] uint16_t virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) [[gnu::hot]] uint16_t virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) - [[gnu::hot]] uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index d3521c90..29126527 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -202,7 +202,7 @@ void Segment::resetIfRequired() { reset = false; } -CRGBPalette16 IRAM_ATTR_YN &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { +CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip //default palette. Differs depending on effect @@ -417,7 +417,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const { return (useCct ? cct : (on ? opacity : 0)); } -uint8_t IRAM_ATTR_YN Segment::currentMode() const { +uint8_t Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); if (modeBlending && prog < 0xFFFFU) return _t->_modeT; From 49d51c5af8234cbd6431c71221bf42110257243b Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 12 Sep 2024 15:26:26 +0200 Subject: [PATCH 32/50] Hot --- wled00/bus_manager.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 8205bd66..bf4657d8 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -47,7 +47,7 @@ struct ColorOrderMap { return &(_mappings[n]); } - uint8_t getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const; + [[gnu::hot]] uint8_t getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const; private: std::vector _mappings; @@ -201,9 +201,9 @@ class BusDigital : public Bus { bool canShow() const override; void setBrightness(uint8_t b) override; void setStatusPixel(uint32_t c) override; - void setPixelColor(uint16_t pix, uint32_t c) override; + [[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override; void setColorOrder(uint8_t colorOrder) override; - uint32_t getPixelColor(uint16_t pix) const override; + [[gnu::hot]] uint32_t getPixelColor(uint16_t 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; } @@ -382,7 +382,7 @@ class BusManager { static void show(); static bool canAllShow(); static void setStatusPixel(uint32_t c); - static void setPixelColor(uint16_t pix, uint32_t c); + [[gnu::hot]] static void setPixelColor(uint16_t 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() From daf0bcfac364846928a2277c1f16475c8d32ac42 Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Thu, 12 Sep 2024 18:12:38 +0200 Subject: [PATCH 33/50] bug fix on pin already defined check --- wled00/FX_fcn.cpp | 9 +++++---- wled00/pin_manager.cpp | 8 +++++--- wled00/pin_manager.h | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 435998cd..6b01d9a3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1248,15 +1248,16 @@ void WS2812FX::finalizeInit(void) { // if we need more pins than available all outputs have been configured if (pinsIndex + busPins > defNumPins) break; - for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { - defPin[j] = defDataPins[pinsIndex + j]; + // Assign all pins first so we can check for conflicts on this bus + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { bool validPin = true; // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. // Pin should not be already allocated, read/only or defined for current bus - while (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j]) || - pinManager.isPinDefined(defPin[j], defDataPins, pinsIndex + j + 1, pinsIndex + busPins)) { + while (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j]) || pinManager.isPinDefined(defPin[j], defPin, j)) { + DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); if (validPin) { defPin[j] = 1; // start with GPIO1 and work upwards validPin = false; diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 9398ec94..815bddee 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -281,9 +281,11 @@ bool PinManagerClass::isReadOnlyPin(byte gpio) return false; } -bool PinManagerClass::isPinDefined(byte gpio, const unsigned *pins, unsigned start, unsigned end) { - for (unsigned i = start; i < end; i++) { - if (pins[i] == gpio) return true; +// Given an array of pins, check if a given pin is defined except at given index +bool PinManagerClass::isPinDefined(const byte gpio, const uint8_t *pins, const unsigned index) { + unsigned numPins = ((sizeof pins) / (sizeof pins[0])); + for (unsigned i = 0; i < numPins; i++) { + if ((pins[i] == gpio) && (i != index)) return true; } return false; } diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 1263dc4f..6c572613 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -113,7 +113,7 @@ class PinManagerClass { bool isPinOk(byte gpio, bool output = true) const; static bool isReadOnlyPin(byte gpio); - static bool isPinDefined(byte gpio, const unsigned* pins, unsigned start = 0, unsigned end = WLED_NUM_PINS); + static bool isPinDefined(const byte gpio, const uint8_t* pins, const unsigned index = 255); PinOwner getPinOwner(byte gpio) const; From fa82e759bd0e2e29683f1b33ed7eff26311586db Mon Sep 17 00:00:00 2001 From: PaoloTK Date: Thu, 12 Sep 2024 18:27:51 +0200 Subject: [PATCH 34/50] bug fix --- wled00/FX_fcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 6b01d9a3..d652435b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1257,8 +1257,8 @@ void WS2812FX::finalizeInit(void) { // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. // Pin should not be already allocated, read/only or defined for current bus while (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j]) || pinManager.isPinDefined(defPin[j], defPin, j)) { - DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); if (validPin) { + DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); defPin[j] = 1; // start with GPIO1 and work upwards validPin = false; } From e3d9417b84dfac3e64b8634b78e3cd29ed3027de Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 14 Sep 2024 11:39:56 +0200 Subject: [PATCH 35/50] Minor fixes - rely on HAL for RO pins and max pins - remove isPinDefined() and do clash check inline - JS fix to use HAL max pins --- wled00/FX_fcn.cpp | 16 +++++++++++++--- wled00/const.h | 13 ------------- wled00/data/settings_leds.htm | 2 +- wled00/data/settings_um.htm | 12 ++++++------ wled00/pin_manager.cpp | 21 +++------------------ wled00/pin_manager.h | 1 - wled00/xml.cpp | 12 +----------- 7 files changed, 24 insertions(+), 53 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 447ceaab..09b2ee51 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1256,18 +1256,28 @@ void WS2812FX::finalizeInit() { // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. // Pin should not be already allocated, read/only or defined for current bus - while (pinManager.isPinAllocated(defPin[j]) || pinManager.isReadOnlyPin(defPin[j]) || pinManager.isPinDefined(defPin[j], defPin, j)) { + while (pinManager.isPinAllocated(defPin[j]) || !pinManager.isPinOk(defPin[j],true)) { if (validPin) { DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); defPin[j] = 1; // start with GPIO1 and work upwards validPin = false; - } - if (defPin[j] < WLED_NUM_PINS) { + } else if (defPin[j] < WLED_NUM_PINS) { defPin[j]++; } else { DEBUG_PRINTLN(F("No available pins left! Can't configure output.")); return; } + // is the newly assigned pin already defined? try next in line until there are no clashes + bool clash; + do { + clash = false; + for (const auto pin : defDataPins) { + if (pin == defPin[j]) { + defPin[j]++; + if (defPin[j] < WLED_NUM_PINS) clash = true; + } + } + } while (clash); } } pinsIndex += busPins; diff --git a/wled00/const.h b/wled00/const.h index a0ebeebb..47ec02c6 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -567,19 +567,6 @@ #define WLED_MAX_NODES 150 #endif -// List of read only pins. Cannot be used for LED outputs. -#if defined(CONFIG_IDF_TARGET_ESP32S2) - #define READ_ONLY_PINS 46 -#elif defined(CONFIG_IDF_TARGET_ESP32S3) -// none for S3 -#elif defined(CONFIG_IDF_TARGET_ESP32C3) -// none for C3 -#elif defined(ESP32) - #define READ_ONLY_PINS 34,35,36,37,38,39 -#else -// none for ESP8266 -#endif - #ifdef WLED_ENABLE_DMX #if (LEDPIN == 2) #undef LEDPIN diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index b3188f0d..206d4a8c 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -671,7 +671,7 @@ Swap: Emulate Alexa device:
Alexa invocation name:
-Also emulate devices to call the first presets

+Also emulate devices to call the first presets


MQTT and Hue sync all connect to external hosts!
@@ -245,6 +245,10 @@ Hue Bridge IP:
Hue status: Disabled in this build

Serial

+
+ This firmware build does support Serial interface.
+
+
Baud rate:
Keep at 115200 to use Improv. Some boards may not support high rates. +

diff --git a/wled00/ir.cpp b/wled00/ir.cpp index 9e197436..e4541cd9 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -714,9 +714,8 @@ void handleIR() if (strip.isUpdating() && timeDiff < 240) return; // be nice, but not too nice irCheckedTime = currentTime; if (irrecv->decode(&results)) { - if (results.value != 0) { // only print results if anything is received ( != 0 ) - if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) // Serial TX pin (GPIO 1 on ESP32 and ESP8266) - Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value); + if (results.value != 0 && serialCanTX) { // only print results if anything is received ( != 0 ) + Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value); } decodeIR(results.value); irrecv->resume(); diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 47cba2ec..be2a4f97 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -1,6 +1,18 @@ #include "pin_manager.h" #include "wled.h" +#ifdef ARDUINO_ARCH_ESP32 + #ifdef bitRead + // Arduino variants assume 32 bit values + #undef bitRead + #undef bitSet + #undef bitClear + #define bitRead(var,bit) (((unsigned long long)(var)>>(bit))&0x1ULL) + #define bitSet(var,bit) ((var)|=(1ULL<<(bit))) + #define bitClear(var,bit) ((var)&=(~(1ULL<<(bit)))) + #endif +#endif + #ifdef WLED_DEBUG static void DebugPrintOwnerTag(PinOwner tag) { diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index a8ddf5f7..a64900c8 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -87,7 +87,11 @@ class PinManagerClass { PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; public: - PinManagerClass() : pinAlloc(0), i2cAllocCount(0), spiAllocCount(0) {} + PinManagerClass() : pinAlloc(0ULL), i2cAllocCount(0), spiAllocCount(0) { + #ifdef ARDUINO_ARCH_ESP32 + ledcAlloc = 0; + #endif + } // De-allocates a single pin bool deallocatePin(byte gpio, PinOwner tag); // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c46fc8eb..bc1cc7b7 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -54,17 +54,19 @@ void WLED::loop() #endif handleTime(); -#ifndef WLED_DISABLE_INFRARED + #ifndef WLED_DISABLE_INFRARED handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too -#endif + #endif handleConnection(); + #ifdef WLED_ENABLE_ADALIGHT handleSerial(); + #endif handleImprovWifiScan(); handleNotifications(); handleTransitions(); -#ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX handleDMX(); -#endif + #endif #ifdef WLED_DEBUG unsigned long usermodMillis = millis(); @@ -476,10 +478,14 @@ void WLED::setup() WiFi.mode(WIFI_STA); // enable scanning findWiFi(true); // start scanning for available WiFi-s + // all GPIOs are allocated at this point + serialCanRX = !pinManager.isPinAllocated(hardwareRX); // Serial RX pin (GPIO 3 on ESP32 and ESP8266) + serialCanTX = !pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut; // Serial TX pin (GPIO 1 on ESP32 and ESP8266) + #ifdef WLED_ENABLE_ADALIGHT //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused //Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused - if (!pinManager.isPinAllocated(hardwareRX) && !pinManager.isPinAllocated(hardwareTX)) { + if (serialCanRX && serialCanTX) { Serial.println(F("Ada")); } #endif @@ -491,10 +497,6 @@ void WLED::setup() if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6); #endif -#ifdef WLED_ENABLE_ADALIGHT - if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); -#endif - #ifndef WLED_DISABLE_OTA if (aOtaEnabled) { ArduinoOTA.onStart([]() { @@ -521,7 +523,7 @@ void WLED::setup() #endif #ifdef WLED_ENABLE_ADALIGHT - if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); + if (serialCanRX && Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); #endif // HTTP server page init diff --git a/wled00/wled.h b/wled00/wled.h index 23c34590..33dea8b0 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2409140 +#define VERSION 2409170 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -510,6 +510,8 @@ WLED_GLOBAL bool hueApplyColor _INIT(true); #endif WLED_GLOBAL uint16_t serialBaud _INIT(1152); // serial baud rate, multiply by 100 +WLED_GLOBAL bool serialCanRX _INIT(false); +WLED_GLOBAL bool serialCanTX _INIT(false); #ifndef WLED_DISABLE_ESPNOW WLED_GLOBAL bool enableESPNow _INIT(false); // global on/off for ESP-NOW diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index 3ca7c7f2..ad9bb141 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -28,7 +28,7 @@ void updateBaudRate(uint32_t rate){ if (rate100 == currentBaud || rate100 < 96) return; currentBaud = rate100; - if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut){ + if (serialCanTX){ Serial.print(F("Baud is now ")); Serial.println(rate); } @@ -38,7 +38,7 @@ void updateBaudRate(uint32_t rate){ // RGB LED data return as JSON array. Slow, but easy to use on the other end. void sendJSON(){ - if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) { + if (serialCanTX) { unsigned used = strip.getLengthTotal(); Serial.write('['); for (unsigned i=0; ias()); - //only send response if TX pin is unused for other purposes - if (verboseResponse && (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut)) { - pDoc->clear(); - JsonObject state = pDoc->createNestedObject("state"); - serializeState(state); - JsonObject info = pDoc->createNestedObject("info"); - serializeInfo(info); + if (!error) { + verboseResponse = deserializeState(pDoc->as()); + //only send response if TX pin is unused for other purposes + if (verboseResponse && serialCanTX) { + pDoc->clear(); + JsonObject state = pDoc->createNestedObject("state"); + serializeState(state); + JsonObject info = pDoc->createNestedObject("info"); + serializeInfo(info); - serializeJson(*pDoc, Serial); - Serial.println(); + serializeJson(*pDoc, Serial); + Serial.println(); + } } releaseJSONBufferLock(); } @@ -199,11 +186,10 @@ void handleSerial() // All other received bytes will disable Continuous Serial Streaming if (continuousSendLED && next != 'O'){ continuousSendLED = false; - } + } Serial.read(); //discard the byte } - #endif // If Continuous Serial Streaming is enabled, send new LED data as bytes if (continuousSendLED && (lastUpdate != strip.getLastShow())){ diff --git a/wled00/xml.cpp b/wled00/xml.cpp index ef23c090..71d66d00 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -571,6 +571,9 @@ void getSettingsJS(byte subPage, char* dest) oappend(SET_F("toggle('Hue');")); // hide Hue Sync settings #endif sappend('v',SET_F("BD"),serialBaud); + #ifndef WLED_ENABLE_ADALIGHT + oappend(SET_F("toggle('Serial);")); + #endif } if (subPage == SUBPAGE_TIME) From ceed494cf7936dce6e72c916f91a99227f74fbf3 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 17 Sep 2024 16:26:11 +0200 Subject: [PATCH 45/50] Introduce common.js in settings pages --- tools/cdata.js | 9 ++- wled00/data/common.js | 118 +++++++++++++++++++++++++++++ wled00/data/cpal/cpal.htm | 14 ++-- wled00/data/pxmagic/pxmagic.htm | 20 +++-- wled00/data/settings.htm | 45 +---------- wled00/data/settings_2D.htm | 82 ++------------------ wled00/data/settings_dmx.htm | 68 ++++------------- wled00/data/settings_leds.htm | 113 +++++++-------------------- wled00/data/settings_sec.htm | 79 ++----------------- wled00/data/settings_sync.htm | 50 ++---------- wled00/data/settings_time.htm | 65 +++------------- wled00/data/settings_ui.htm | 83 ++------------------ wled00/data/settings_um.htm | 130 ++++++++++---------------------- wled00/data/settings_wifi.htm | 52 +------------ wled00/wled_server.cpp | 9 +++ 15 files changed, 275 insertions(+), 662 deletions(-) create mode 100644 wled00/data/common.js diff --git a/tools/cdata.js b/tools/cdata.js index 12dda1cb..d65573a8 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -116,7 +116,8 @@ async function minify(str, type = "plain") { } else if (type == "css-minify") { return new CleanCSS({}).minify(str).styles; } else if (type == "js-minify") { - return await minifyHtml('', options).replace(/<[\/]*script>/g, ''); + let js = await minifyHtml('', options); + return js.replace(/<[\/]*script>/g, ''); } else if (type == "html-minify") { return await minifyHtml(str, options); } @@ -252,6 +253,12 @@ writeChunks( str .replace("%%", "%") }, + { + file: "common.js", + name: "JS_common", + method: "gzip", + filter: "js-minify", + }, { file: "settings.htm", name: "PAGE_settings", diff --git a/wled00/data/common.js b/wled00/data/common.js new file mode 100644 index 00000000..9378ef07 --- /dev/null +++ b/wled00/data/common.js @@ -0,0 +1,118 @@ +var d=document; +var loc = false, locip, locproto = "http:"; + +function H(pg="") { window.open("https://kno.wled.ge/"+pg); } +function GH() { window.open("https://github.com/Aircoookie/WLED"); } +function gId(c) { return d.getElementById(c); } // getElementById +function cE(e) { return d.createElement(e); } // createElement +function gEBCN(c) { return d.getElementsByClassName(c); } // getElementsByClassName +function gN(s) { return d.getElementsByName(s)[0]; } // getElementsByName +function isE(o) { return Object.keys(o).length === 0; } // isEmpty +function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); } // isObject +function isN(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // isNumber +// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer +function isF(n) { return n === +n && n !== (n|0); } // isFloat +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", ()=>{ + // save title + element.setAttribute("data-title", element.getAttribute("title")); + const tooltip = d.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = element.getAttribute("title"); + + // prevent default title popup + element.removeAttribute("title"); + + let { top, left, width } = element.getBoundingClientRect(); + + d.body.appendChild(tooltip); + + const { offsetHeight, offsetWidth } = tooltip; + + const offset = element.classList.contains("sliderwrap") ? 4 : 10; + top -= offsetHeight + offset; + left += (width - offsetWidth) / 2; + + tooltip.style.top = top + "px"; + tooltip.style.left = left + "px"; + tooltip.classList.add("visible"); + }); + + element.addEventListener("mouseout", ()=>{ + d.querySelectorAll('.tooltip').forEach((tooltip)=>{ + tooltip.classList.remove("visible"); + d.body.removeChild(tooltip); + }); + // restore title + element.setAttribute("title", element.getAttribute("data-title")); + }); + }); +}; +// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript +function loadJS(FILE_URL, async = true, preGetV = undefined, postGetV = undefined) { + let scE = d.createElement("script"); + scE.setAttribute("src", FILE_URL); + scE.setAttribute("type", "text/javascript"); + scE.setAttribute("async", async); + d.body.appendChild(scE); + // success event + scE.addEventListener("load", () => { + //console.log("File loaded"); + if (preGetV) preGetV(); + GetV(); + if (postGetV) postGetV(); + }); + // error event + scE.addEventListener("error", (ev) => { + console.log("Error on loading file", ev); + alert("Loading of configuration script failed.\nIncomplete page data!"); + }); +} +function getLoc() { + let l = window.location; + if (l.protocol == "file:") { + loc = true; + locip = localStorage.getItem('locIp'); + if (!locip) { + locip = prompt("File Mode. Please enter WLED IP!"); + localStorage.setItem('locIp', locip); + } + } else { + // detect reverse proxy + let path = l.pathname; + let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/"); + if (paths.length > 1) paths.pop(); // remove subpage (or "settings") + if (paths.length > 0 && paths[paths.length-1]=="settings") paths.pop(); // remove "settings" + if (paths.length > 1) { + locproto = l.protocol; + loc = true; + locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths.join('/'); + } + } +} +function getURL(path) { return (loc ? locproto + "//" + locip : "") + path; } +function B() { window.open(getURL("/settings"),"_self"); } +var timeout; +function showToast(text, error = false) { + var x = gId("toast"); + if (!x) return; + x.innerHTML = text; + x.className = error ? "error":"show"; + clearTimeout(timeout); + x.style.animation = 'none'; + timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); +} +function uploadFile(fileObj, name) { + var req = new XMLHttpRequest(); + req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); + req.addEventListener('error', function(e){showToast(e.stack,true);}); + req.open("POST", "/upload"); + var formData = new FormData(); + formData.append("data", fileObj.files[0], name); + req.send(formData); + fileObj.value = ''; + return false; +} diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index a4b91359..b58c0987 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -608,8 +608,8 @@ } function generatePaletteDivs() { - const palettesDiv = d.getElementById("palettes"); - const staticPalettesDiv = d.getElementById("staticPalettes"); + const palettesDiv = gId("palettes"); + const staticPalettesDiv = gId("staticPalettes"); const paletteDivs = Array.from(palettesDiv.children).filter((child) => { return child.id.match(/^palette\d$/); // match only elements with id starting with "palette" followed by a single digit }); @@ -620,25 +620,25 @@ for (let i = 0; i < paletteArray.length; i++) { const palette = paletteArray[i]; - const paletteDiv = d.createElement("div"); + const paletteDiv = cE("div"); paletteDiv.id = `palette${i}`; paletteDiv.classList.add("palette"); const thisKey = Object.keys(palette)[0]; paletteDiv.dataset.colarray = JSON.stringify(palette[thisKey]); - const gradientDiv = d.createElement("div"); + const gradientDiv = cE("div"); gradientDiv.id = `paletteGradient${i}` - const buttonsDiv = d.createElement("div"); + const buttonsDiv = cE("div"); buttonsDiv.id = `buttonsDiv${i}`; buttonsDiv.classList.add("buttonsDiv") - const sendSpan = d.createElement("span"); + const sendSpan = cE("span"); sendSpan.id = `sendSpan${i}`; sendSpan.onclick = function() {initiateUpload(i)}; sendSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send? sendSpan.innerHTML = svgSave; sendSpan.classList.add("sendSpan") - const editSpan = d.createElement("span"); + const editSpan = cE("span"); editSpan.id = `editSpan${i}`; editSpan.onclick = function() {loadForEdit(i)}; editSpan.setAttribute('title', `Copy slot ${i} palette to editor`); diff --git a/wled00/data/pxmagic/pxmagic.htm b/wled00/data/pxmagic/pxmagic.htm index d59f924c..8ec11f45 100644 --- a/wled00/data/pxmagic/pxmagic.htm +++ b/wled00/data/pxmagic/pxmagic.htm @@ -882,10 +882,8 @@ hostnameLabel(); })(); - function gId(id) { - return d.getElementById(id); - } - + function gId(e) {return d.getElementById(e);} + function cE(e) {return d.createElement(e);} function hostnameLabel() { const link = gId("wledEdit"); link.href = WLED_URL + "/edit"; @@ -1675,7 +1673,7 @@ } function createCanvas(width, height) { - const canvas = d.createElement("canvas"); + const canvas = cE("canvas"); canvas.width = width; canvas.height = height; @@ -1719,7 +1717,7 @@ const blob = new Blob([text], { type: mimeType }); const url = URL.createObjectURL(blob); - const anchorElement = d.createElement("a"); + const anchorElement = cE("a"); anchorElement.href = url; anchorElement.download = `${filename}.${fileExtension}`; @@ -1790,7 +1788,7 @@ hideElement = "preview" ) { const hide = gId(hideElement); - const toast = d.createElement("div"); + const toast = cE("div"); const wait = 100; toast.style.animation = "fadeIn"; @@ -1799,14 +1797,14 @@ toast.classList.add("toast", type); - const body = d.createElement("span"); + const body = cE("span"); body.classList.add("toast-body"); body.textContent = message; toast.appendChild(body); - const progress = d.createElement("div"); + const progress = cE("div"); progress.classList.add("toast-progress"); progress.style.animation = "progress"; @@ -1831,7 +1829,7 @@ function carousel(id, images, delay = 3000) { let index = 0; - const carousel = d.createElement("div"); + const carousel = cE("div"); carousel.classList.add("carousel"); images.forEach((canvas, i) => { @@ -1959,7 +1957,7 @@ let errorElement = parent.querySelector(".error-message"); if (!errorElement) { - errorElement = d.createElement("div"); + errorElement = cE("div"); errorElement.classList.add("error-message"); parent.appendChild(errorElement); } diff --git a/wled00/data/settings.htm b/wled00/data/settings.htm index 52b64006..82c77821 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -4,53 +4,12 @@ WLED Settings +
-
+

Imma firin ma lazer (if it has DMX support)

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 206d4a8c..54ba9d8b 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -4,20 +4,12 @@ LED Settings +
-
+

LED & Hardware setup

@@ -861,7 +800,7 @@ Swap:  ✕
Apply IR change to main segment only:
- + IR info

Relay GPIO:  ✕
diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index ff8231cc..ce9bd8aa 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -4,55 +4,9 @@ Misc Settings +
-
+


diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index e2fbd5eb..c2f0ffbf 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -4,75 +4,55 @@ Usermod Settings +
-
+

WiFi setup

diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 291f6f5f..9d4e4c85 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -18,6 +18,7 @@ static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security setti static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!"; static const char s_notimplemented[] PROGMEM = "Not implemented"; static const char s_accessdenied[] PROGMEM = "Access Denied"; +static const char _common_js[] PROGMEM = "/common.js"; //Is this an IP? static bool isIp(String str) { @@ -237,6 +238,10 @@ void initServer() handleStaticContent(request, "", 200, FPSTR(CONTENT_TYPE_HTML), PAGE_liveview, PAGE_liveview_length); }); + server.on(_common_js, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); + }); + //settings page server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request); @@ -511,6 +516,10 @@ void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t erro void serveSettingsJS(AsyncWebServerRequest* request) { + if (request->url().indexOf(FPSTR(_common_js)) > 0) { + handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); + return; + } char buf[SETTINGS_STACK_BUF_SIZE+37]; buf[0] = 0; byte subPage = request->arg(F("p")).toInt(); From 88fb8605681a6417e27b7a09a40ae846c8f4d909 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 17 Sep 2024 16:34:38 +0200 Subject: [PATCH 46/50] SAVE_RAM bugfix introduced by #4137 --- wled00/wled.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/wled00/wled.h b/wled00/wled.h index 33dea8b0..31a61285 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -641,17 +641,19 @@ typedef class Receive { bool SegmentOptions : 1; bool SegmentBounds : 1; bool Direct : 1; - uint8_t reserved : 2; + bool Palette : 1; + uint8_t reserved : 1; }; }; Receive(int i) { Options = i; } - Receive(bool b, bool c, bool e, bool sO, bool sB) { - Brightness = b; - Color = c; - Effects = e; - SegmentOptions = sO; - SegmentBounds = sB; - }; + Receive(bool b, bool c, bool e, bool sO, bool sB, bool p) + : Brightness(b) + , Color(c) + , Effects(e) + , SegmentOptions(sO) + , SegmentBounds(sB) + , Palette(p) + {}; } __attribute__ ((aligned(1), packed)) receive_notification_t; typedef class Send { public: @@ -673,7 +675,7 @@ typedef class Send { Hue = h; } } __attribute__ ((aligned(1), packed)) send_notification_t; -WLED_GLOBAL receive_notification_t receiveN _INIT(0b00100111); +WLED_GLOBAL receive_notification_t receiveN _INIT(0b01100111); WLED_GLOBAL send_notification_t notifyG _INIT(0b00001111); #define receiveNotificationBrightness receiveN.Brightness #define receiveNotificationColor receiveN.Color From 72455ccde1f35c390e86a1038d2c2fd63d5e86fb Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 17 Sep 2024 19:47:24 +0200 Subject: [PATCH 47/50] Missing "not" --- wled00/data/settings_sync.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index bf5ce397..34b9fc6c 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -206,7 +206,7 @@ Hue status: Disabled in this build

Serial

- This firmware build does support Serial interface.
+ This firmware build does not support Serial interface.
Baud rate: From b50e6e0d90ce47adcc4e7ecb865b671c2c9dec1f Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 19 Sep 2024 21:44:11 +0200 Subject: [PATCH 48/50] Static PinManager & UsermodManager - saves a few bytes of flash --- .../Animated_Staircase/Animated_Staircase.h | 10 +- usermods/Animated_Staircase/README.md | 2 +- usermods/Battery/usermod_v2_Battery.h | 4 +- usermods/EXAMPLE_v2/usermod_v2_example.h | 2 +- .../Fix_unreachable_netservices_v2/readme.md | 8 +- .../usermod_LDR_Dusk_Dawn_v2.h | 6 +- usermods/PIR_sensor_switch/readme.md | 2 +- .../usermod_PIR_sensor_switch.h | 4 +- usermods/PWM_fan/usermod_PWM_fan.h | 16 +- usermods/SN_Photoresistor/usermods_list.cpp | 2 +- usermods/ST7789_display/ST7789_display.h | 6 +- usermods/Temperature/usermod_temperature.h | 10 +- usermods/audioreactive/audio_reactive.old.h | 2071 +++++++++++++++++ usermods/audioreactive/audio_source.h | 20 +- usermods/mpu6050_imu/readme.md | 2 +- usermods/mpu6050_imu/usermod_gyro_surge.h | 2 +- usermods/mpu6050_imu/usermod_mpu6050_imu.h | 4 +- usermods/mqtt_switch_v2/README.md | 2 +- usermods/multi_relay/readme.md | 8 +- usermods/multi_relay/usermod_multi_relay.h | 4 +- usermods/pixels_dice_tray/pixels_dice_tray.h | 6 +- usermods/pwm_outputs/usermod_pwm_outputs.h | 10 +- usermods/quinled-an-penta/quinled-an-penta.h | 18 +- .../rgb-rotary-encoder/rgb-rotary-encoder.h | 10 +- usermods/sd_card/usermod_sd_card.h | 10 +- .../usermod_seven_segment_reloaded.h | 2 +- .../usermod_v2_auto_save.h | 2 +- .../usermod_v2_four_line_display_ALT.h | 10 +- .../usermod_v2_rotary_encoder_ui_ALT.h | 14 +- wled00/FX.cpp | 8 +- wled00/FX_fcn.cpp | 8 +- wled00/bus_manager.cpp | 26 +- wled00/bus_manager.h | 2 +- wled00/button.cpp | 2 +- wled00/cfg.cpp | 28 +- wled00/fcn_declare.h | 44 +- wled00/json.cpp | 6 +- wled00/led.cpp | 2 +- wled00/mqtt.cpp | 6 +- wled00/overlay.cpp | 2 +- wled00/pin_manager.cpp | 109 +- wled00/pin_manager.h | 87 +- wled00/set.cpp | 34 +- wled00/udp.cpp | 2 +- wled00/um_manager.cpp | 3 + wled00/usermods_list.cpp | 112 +- wled00/wled.cpp | 22 +- wled00/wled_server.cpp | 4 +- wled00/xml.cpp | 12 +- 49 files changed, 2401 insertions(+), 385 deletions(-) create mode 100644 usermods/audioreactive/audio_reactive.old.h diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 8953756d..d1ec9bb7 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -332,7 +332,7 @@ class Animated_Staircase : public Usermod { }; // NOTE: this *WILL* return TRUE if all the pins are set to -1. // this is *BY DESIGN*. - if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) { + if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_AnimatedStaircase)) { topPIRorTriggerPin = -1; topEchoPin = -1; bottomPIRorTriggerPin = -1; @@ -513,10 +513,10 @@ class Animated_Staircase : public Usermod { (oldBottomAPin != bottomPIRorTriggerPin) || (oldBottomBPin != bottomEchoPin)) { changed = true; - pinManager.deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase); - pinManager.deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldTopAPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldTopBPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldBottomAPin, PinOwner::UM_AnimatedStaircase); + PinManager::deallocatePin(oldBottomBPin, PinOwner::UM_AnimatedStaircase); } if (changed) setup(); } diff --git a/usermods/Animated_Staircase/README.md b/usermods/Animated_Staircase/README.md index 320b744a..2ad66b5a 100644 --- a/usermods/Animated_Staircase/README.md +++ b/usermods/Animated_Staircase/README.md @@ -18,7 +18,7 @@ Before compiling, you have to make the following modifications: Edit `usermods_list.cpp`: 1. Open `wled00/usermods_list.cpp` 2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file -3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. +3. add `UsermodManager::add(new Animated_Staircase());` to the end of the `void registerUsermods()` function. You can configure usermod using the Usermods settings page. Please enter GPIO pins for PIR or ultrasonic sensors (trigger and echo). diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 136d3a71..e91de850 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -200,7 +200,7 @@ class UsermodBattery : public Usermod bool success = false; DEBUG_PRINTLN(F("Allocating battery pin...")); if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0) - if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { + if (PinManager::allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; } @@ -561,7 +561,7 @@ class UsermodBattery : public Usermod if (newBatteryPin != batteryPin) { // deallocate pin - pinManager.deallocatePin(batteryPin, PinOwner::UM_Battery); + PinManager::deallocatePin(batteryPin, PinOwner::UM_Battery); batteryPin = newBatteryPin; // initialise setup(); diff --git a/usermods/EXAMPLE_v2/usermod_v2_example.h b/usermods/EXAMPLE_v2/usermod_v2_example.h index 32374fde..3d562b58 100644 --- a/usermods/EXAMPLE_v2/usermod_v2_example.h +++ b/usermods/EXAMPLE_v2/usermod_v2_example.h @@ -71,7 +71,7 @@ class MyExampleUsermod : public Usermod { // #endif // in setup() // #ifdef USERMOD_EXAMPLE - // UM = (MyExampleUsermod*) usermods.lookup(USERMOD_ID_EXAMPLE); + // UM = (MyExampleUsermod*) UsermodManager::lookup(USERMOD_ID_EXAMPLE); // #endif // somewhere in loop() or other member method // #ifdef USERMOD_EXAMPLE diff --git a/usermods/Fix_unreachable_netservices_v2/readme.md b/usermods/Fix_unreachable_netservices_v2/readme.md index 006eaf9f..07d64bc6 100644 --- a/usermods/Fix_unreachable_netservices_v2/readme.md +++ b/usermods/Fix_unreachable_netservices_v2/readme.md @@ -59,10 +59,10 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); - //usermods.add(new UsermodRenameMe()); - usermods.add(new FixUnreachableNetServices()); + //UsermodManager::add(new MyExampleUsermod()); + //UsermodManager::add(new UsermodTemperature()); + //UsermodManager::add(new UsermodRenameMe()); + UsermodManager::add(new FixUnreachableNetServices()); } ``` diff --git a/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h index 393fc223..03f4c078 100644 --- a/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h +++ b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h @@ -30,7 +30,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { void setup() { // register ldrPin if ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)) { - if(!pinManager.allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod + if(!PinManager::allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod else pinMode(ldrPin, INPUT); // alloc success -> configure pin for input } else ldrEnabled = false; // invalid pin -> disable usermod initDone = true; @@ -110,7 +110,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { if (initDone && (ldrPin != oldLdrPin)) { // pin changed - un-register previous pin, register new pin - if (oldLdrPin >= 0) pinManager.deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN); + if (oldLdrPin >= 0) PinManager::deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN); setup(); // setup new pin } return configComplete; @@ -139,7 +139,7 @@ class LDR_Dusk_Dawn_v2 : public Usermod { //LDR_Off_Count.add(ldrOffCount); //bool pinValid = ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)); - //if (pinManager.getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false; + //if (PinManager::getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false; //JsonArray LDR_valid = user.createNestedArray(F("LDR pin")); //LDR_valid.add(ldrPin); //LDR_valid.add(pinValid ? F(" OK"): F(" invalid")); diff --git a/usermods/PIR_sensor_switch/readme.md b/usermods/PIR_sensor_switch/readme.md index 4dfdb07b..fac5419f 100644 --- a/usermods/PIR_sensor_switch/readme.md +++ b/usermods/PIR_sensor_switch/readme.md @@ -52,7 +52,7 @@ class MyUsermod : public Usermod { void togglePIRSensor() { #ifdef USERMOD_PIR_SENSOR_SWITCH - PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) usermods.lookup(USERMOD_ID_PIRSWITCH); + PIRsensorSwitch *PIRsensor = (PIRsensorSwitch::*) UsermodManager::lookup(USERMOD_ID_PIRSWITCH); if (PIRsensor != nullptr) { PIRsensor->EnablePIRsensor(!PIRsensor->PIRsensorEnabled()); } diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 7a67dd74..29070cf8 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -375,7 +375,7 @@ void PIRsensorSwitch::setup() sensorPinState[i] = LOW; if (PIRsensorPin[i] < 0) continue; // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { + if (PinManager::allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) { // PIR Sensor mode INPUT_PULLDOWN #ifdef ESP8266 pinMode(PIRsensorPin[i], PIRsensorPin[i]==16 ? INPUT_PULLDOWN_16 : INPUT_PULLUP); // ESP8266 has INPUT_PULLDOWN on GPIO16 only @@ -564,7 +564,7 @@ bool PIRsensorSwitch::readFromConfig(JsonObject &root) DEBUG_PRINTLN(F(" config loaded.")); } else { for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) - if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR); + if (oldPin[i] >= 0) PinManager::deallocatePin(oldPin[i], PinOwner::UM_PIR); setup(); DEBUG_PRINTLN(F(" config (re)loaded.")); } diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index 1b78cfd4..c3ef24fe 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -75,7 +75,7 @@ class PWMFanUsermod : public Usermod { static const char _lock[]; void initTacho(void) { - if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){ + if (tachoPin < 0 || !PinManager::allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){ tachoPin = -1; return; } @@ -88,7 +88,7 @@ class PWMFanUsermod : public Usermod { void deinitTacho(void) { if (tachoPin < 0) return; detachInterrupt(digitalPinToInterrupt(tachoPin)); - pinManager.deallocatePin(tachoPin, PinOwner::UM_Unspecified); + PinManager::deallocatePin(tachoPin, PinOwner::UM_Unspecified); tachoPin = -1; } @@ -111,7 +111,7 @@ class PWMFanUsermod : public Usermod { // https://randomnerdtutorials.com/esp32-pwm-arduino-ide/ void initPWMfan(void) { - if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { + if (pwmPin < 0 || !PinManager::allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { enabled = false; pwmPin = -1; return; @@ -121,7 +121,7 @@ class PWMFanUsermod : public Usermod { analogWriteRange(255); analogWriteFreq(WLED_PWM_FREQ); #else - pwmChannel = pinManager.allocateLedc(1); + pwmChannel = PinManager::allocateLedc(1); if (pwmChannel == 255) { //no more free LEDC channels deinitPWMfan(); return; } @@ -136,9 +136,9 @@ class PWMFanUsermod : public Usermod { void deinitPWMfan(void) { if (pwmPin < 0) return; - pinManager.deallocatePin(pwmPin, PinOwner::UM_Unspecified); + PinManager::deallocatePin(pwmPin, PinOwner::UM_Unspecified); #ifdef ARDUINO_ARCH_ESP32 - pinManager.deallocateLedc(pwmChannel, 1); + PinManager::deallocateLedc(pwmChannel, 1); #endif pwmPin = -1; } @@ -191,9 +191,9 @@ class PWMFanUsermod : public Usermod { void setup() override { #ifdef USERMOD_DALLASTEMPERATURE // This Usermod requires Temperature usermod - tempUM = (UsermodTemperature*) usermods.lookup(USERMOD_ID_TEMPERATURE); + tempUM = (UsermodTemperature*) UsermodManager::lookup(USERMOD_ID_TEMPERATURE); #elif defined(USERMOD_SHT) - tempUM = (ShtUsermod*) usermods.lookup(USERMOD_ID_SHT); + tempUM = (ShtUsermod*) UsermodManager::lookup(USERMOD_ID_SHT); #endif initTacho(); initPWMfan(); diff --git a/usermods/SN_Photoresistor/usermods_list.cpp b/usermods/SN_Photoresistor/usermods_list.cpp index 649e1973..a2c6ca16 100644 --- a/usermods/SN_Photoresistor/usermods_list.cpp +++ b/usermods/SN_Photoresistor/usermods_list.cpp @@ -9,6 +9,6 @@ void registerUsermods() { #ifdef USERMOD_SN_PHOTORESISTOR - usermods.add(new Usermod_SN_Photoresistor()); + UsermodManager::add(new Usermod_SN_Photoresistor()); #endif } \ No newline at end of file diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 59f6d927..0dbada38 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -138,10 +138,10 @@ class St7789DisplayUsermod : public Usermod { void setup() override { PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } }; - if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } + if (!PinManager::allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } PinManagerPinType displayPins[] = { { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; - if (!pinManager.allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { - pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + if (!PinManager::allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { + PinManager::deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); enabled = false; return; } diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index d7a9d82a..ad755eae 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -73,7 +73,7 @@ class UsermodTemperature : public Usermod { void publishHomeAssistantAutodiscovery(); #endif - static UsermodTemperature* _instance; // to overcome nonstatic getTemperatureC() method and avoid usermods.lookup(USERMOD_ID_TEMPERATURE); + static UsermodTemperature* _instance; // to overcome nonstatic getTemperatureC() method and avoid UsermodManager::lookup(USERMOD_ID_TEMPERATURE); public: @@ -223,14 +223,14 @@ void UsermodTemperature::setup() { // config says we are enabled DEBUG_PRINTLN(F("Allocating temperature pin...")); // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (temperaturePin >= 0 && pinManager.allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { + if (temperaturePin >= 0 && PinManager::allocatePin(temperaturePin, true, PinOwner::UM_Temperature)) { oneWire = new OneWire(temperaturePin); if (oneWire->reset()) { while (!findSensor() && retries--) { delay(25); // try to find sensor } } - if (parasite && pinManager.allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { + if (parasite && PinManager::allocatePin(parasitePin, true, PinOwner::UM_Temperature)) { pinMode(parasitePin, OUTPUT); digitalWrite(parasitePin, LOW); // deactivate power (close MOSFET) } else { @@ -423,9 +423,9 @@ bool UsermodTemperature::readFromConfig(JsonObject &root) { DEBUG_PRINTLN(F("Re-init temperature.")); // deallocate pin and release memory delete oneWire; - pinManager.deallocatePin(temperaturePin, PinOwner::UM_Temperature); + PinManager::deallocatePin(temperaturePin, PinOwner::UM_Temperature); temperaturePin = newTemperaturePin; - pinManager.deallocatePin(parasitePin, PinOwner::UM_Temperature); + PinManager::deallocatePin(parasitePin, PinOwner::UM_Temperature); // initialise setup(); } diff --git a/usermods/audioreactive/audio_reactive.old.h b/usermods/audioreactive/audio_reactive.old.h new file mode 100644 index 00000000..4f2e04c0 --- /dev/null +++ b/usermods/audioreactive/audio_reactive.old.h @@ -0,0 +1,2071 @@ +#pragma once + +#include "wled.h" + +#ifdef ARDUINO_ARCH_ESP32 + +#include +#include + +#ifdef WLED_ENABLE_DMX + #error This audio reactive usermod is not compatible with DMX Out. +#endif + +#endif + +#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) +#include +#endif + +/* + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * This is an audioreactive v2 usermod. + * .... + */ + +#if !defined(FFTTASK_PRIORITY) +#define FFTTASK_PRIORITY 1 // standard: looptask prio +//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp +//#define FFTTASK_PRIORITY 4 // above asyc_tcp +#endif + +// Comment/Uncomment to toggle usb serial debugging +// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) +// #define FFT_SAMPLING_LOG // FFT result debugging +// #define SR_DEBUG // generic SR DEBUG messages + +#ifdef SR_DEBUG + #define DEBUGSR_PRINT(x) DEBUGOUT.print(x) + #define DEBUGSR_PRINTLN(x) DEBUGOUT.println(x) + #define DEBUGSR_PRINTF(x...) DEBUGOUT.printf(x) +#else + #define DEBUGSR_PRINT(x) + #define DEBUGSR_PRINTLN(x) + #define DEBUGSR_PRINTF(x...) +#endif + +#if defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG) + #define PLOT_PRINT(x) DEBUGOUT.print(x) + #define PLOT_PRINTLN(x) DEBUGOUT.println(x) + #define PLOT_PRINTF(x...) DEBUGOUT.printf(x) +#else + #define PLOT_PRINT(x) + #define PLOT_PRINTLN(x) + #define PLOT_PRINTF(x...) +#endif + +#define MAX_PALETTES 3 + +static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. +static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) +static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group + +#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! + +// audioreactive variables +#ifdef ARDUINO_ARCH_ESP32 +static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point +static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier +static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) +static float sampleAgc = 0.0f; // Smoothed AGC sample +static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) +#endif +//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 WS2812FX::getMinShowDelay() +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 + +// TODO: probably best not used by receive nodes +//static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 + +// user settable parameters for limitSoundDynamics() +#ifdef UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF +static bool limiterOn = false; // bool: enable / disable dynamics limiter +#else +static bool limiterOn = true; +#endif +static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec +static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec + +// peak detection +#ifdef ARDUINO_ARCH_ESP32 +static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode +#endif +static void autoResetPeak(void); // peak auto-reset function +static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) + +#ifdef ARDUINO_ARCH_ESP32 + +// use audio source class (ESP32 specific) +#include "audio_source.h" +constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) +constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples) + +// globals +static uint8_t inputLevel = 128; // UI slider value +#ifndef SR_SQUELCH + uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) +#else + uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value) +#endif +#ifndef SR_GAIN + uint8_t sampleGain = 60; // sample gain (config value) +#else + uint8_t sampleGain = SR_GAIN; // sample gain (config value) +#endif +// user settable options for FFTResult scaling +static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root + +// +// AGC presets +// Note: in C++, "const" implies "static" - no need to explicitly declare everything as "static const" +// +#define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy +const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax +const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone +const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone +const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level +const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% +const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) +const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% +const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec +const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs +const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter +const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter +const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) +// AGC presets end + +static AudioSource *audioSource = nullptr; +static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT. + +//////////////////// +// Begin FFT Code // +//////////////////// + +// some prototypes, to ensure consistent interfaces +static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float +static float fftAddAvg(int from, int to); // average of several FFT result bins +static void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results +static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) +static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels + +static TaskHandle_t FFT_Task = nullptr; + +// Table of multiplication factors so that we can even out the frequency response. +static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; + +// globals and FFT Output variables shared with animations +#if defined(WLED_DEBUG) || defined(SR_DEBUG) +static uint64_t fftTime = 0; +static uint64_t sampleTime = 0; +#endif + +// FFT Task variables (filtering and post-processing) +static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. +static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) +#ifdef SR_DEBUG +static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working. +#endif + +// audio source parameters and constant +#ifdef ARDUINO_ARCH_ESP32C3 +constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +#else +constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms +//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms +//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms +#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling +//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling +//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling +#endif + +// FFT Constants +constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 +constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. +// the following are observed values, supported by a bit of "educated guessing" +//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels +#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels +#define LOG_256 5.54517744f // log(256) + +// These are the input and output vectors. Input vectors receive computed results from FFT. +static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins +static float vImag[samplesFFT] = {0.0f}; // imaginary parts + +// Create FFT object +// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 2.0.1 +// these options actually cause slow-downs on all esp32 processors, don't use them. +// #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 +// #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 +// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() +#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 +#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 + +#include + +/* Create FFT object with weighing factor storage */ +static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); + +// Helper functions + +// float version of map() +static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +// compute average of several FFT result bins +static float fftAddAvg(int from, int to) { + float result = 0.0f; + for (int i = from; i <= to; i++) { + result += vReal[i]; + } + return result / float(to - from + 1); +} + +// +// FFT main task +// +void FFTcode(void * parameter) +{ + DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); + + // see https://www.freertos.org/vtaskdelayuntil.html + const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; + + TickType_t xLastWakeTime = xTaskGetTickCount(); + for(;;) { + delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. + // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. + + // Don't run FFT computing code if we're in Receive mode or in realtime mode + if (disableSoundProcessing || (audioSyncEnabled & 0x02)) { + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers + continue; + } + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + uint64_t start = esp_timer_get_time(); + bool haveDoneFFT = false; // indicates if second measurement (FFT time) is valid +#endif + + // get a fresh batch of samples from I2S + if (audioSource) audioSource->getSamples(vReal, samplesFFT); + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + if (start < esp_timer_get_time()) { // filter out overflows + uint64_t sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding + sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth + } + start = esp_timer_get_time(); // start measuring FFT time +#endif + + xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay + + // band pass filter - can reduce noise floor by a factor of 50 + // downside: frequencies below 100Hz will be ignored + if (useBandPassFilter) runMicFilter(samplesFFT, vReal); + + // find highest sample in the batch + float maxSample = 0.0f; // max sample from FFT batch + for (int i=0; i < samplesFFT; i++) { + // set imaginary parts to 0 + vImag[i] = 0; + // pick our our current mic sample - we take the max value from all samples that go into FFT + if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts + if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); + } + // release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function + // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. + micDataReal = maxSample; + +#ifdef SR_DEBUG + if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization +#else + if (sampleAvg > 0.25f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed. +#endif + + // run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2) + FFT.dcRemoval(); // remove DC offset + FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy + //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection + FFT.compute( FFTDirection::Forward ); // Compute FFT + FFT.complexToMagnitude(); // Compute magnitudes + vReal[0] = 0.0f; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. + + FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant + FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + haveDoneFFT = true; +#endif + + } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. + memset(vReal, 0, sizeof(vReal)); + FFT_MajorPeak = 1.0f; + FFT_Magnitude = 0.001f; + } + + for (int i = 0; i < samplesFFT; i++) { + float t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way + vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max. + } // for() + + // mapping of FFT result bins to frequency channels + if (fabsf(sampleAvg) > 0.5f) { // noise gate open +#if 0 + /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. + * + * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. + * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. + * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins. + * End frequency = Start frequency * multiplier ^ 16 + * Multiplier = (End frequency/ Start frequency) ^ 1/16 + * Multiplier = 1.320367784 + */ // Range + fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 + fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 + fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 + fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 + fftCalc[ 4] = fftAddAvg(9,12); // 180 - 260 + fftCalc[ 5] = fftAddAvg(12,16); // 240 - 340 + fftCalc[ 6] = fftAddAvg(16,21); // 320 - 440 + fftCalc[ 7] = fftAddAvg(21,29); // 420 - 600 + fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 + fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 + fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 + fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 + fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 + fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 + fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 + fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate +#else + /* new mapping, optimized for 22050 Hz by softhack007 */ + // bins frequency range + if (useBandPassFilter) { + // skip frequencies below 100hz + fftCalc[ 0] = 0.8f * fftAddAvg(3,4); + fftCalc[ 1] = 0.9f * fftAddAvg(4,5); + fftCalc[ 2] = fftAddAvg(5,6); + fftCalc[ 3] = fftAddAvg(6,7); + // don't use the last bins from 206 to 255. + fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping + } else { + fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass + fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass + fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass + fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange + // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) + fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping + } + fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange + fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange + fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange + fftCalc[ 7] = fftAddAvg(19,26); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = fftAddAvg(26,33); // 7 1120 - 1421 midrange + fftCalc[ 9] = fftAddAvg(33,44); // 9 1421 - 1895 midrange + fftCalc[10] = fftAddAvg(44,56); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = fftAddAvg(56,70); // 14 2412 - 3015 high mid + fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid + fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid + fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping +#endif + } else { // noise gate closed - just decay old values + for (int i=0; i < NUM_GEQ_CHANNELS; i++) { + fftCalc[i] *= 0.85f; // decay to zero + if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; + } + } + + // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) + postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS); + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + if (haveDoneFFT && (start < esp_timer_get_time())) { // filter out overflows + uint64_t fftTimeInMillis = ((esp_timer_get_time() - start) +5ULL) / 10ULL; // "+5" to ensure proper rounding + fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth + } +#endif + // run peak detection + autoResetPeak(); + detectSamplePeak(); + + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC + #endif + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers + + } // for(;;)ever +} // FFTcode() task end + + +/////////////////////////// +// Pre / Postprocessing // +/////////////////////////// + +static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // pre-filtering of raw samples (band-pass) +{ + // low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency + //constexpr float alpha = 0.04f; // 150Hz + //constexpr float alpha = 0.03f; // 110Hz + constexpr float alpha = 0.0225f; // 80hz + //constexpr float alpha = 0.01693f;// 60hz + // high frequency cutoff parameter + //constexpr float beta1 = 0.75f; // 11Khz + //constexpr float beta1 = 0.82f; // 15Khz + //constexpr float beta1 = 0.8285f; // 18Khz + constexpr float beta1 = 0.85f; // 20Khz + + constexpr float beta2 = (1.0f - beta1) / 2.0f; + static float last_vals[2] = { 0.0f }; // FIR high freq cutoff filter + static float lowfilt = 0.0f; // IIR low frequency cutoff filter + + for (int i=0; i < numSamples; i++) { + // FIR lowpass, to remove high frequency noise + float highFilteredSample; + if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes + else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array + last_vals[1] = last_vals[0]; + last_vals[0] = sampleBuffer[i]; + sampleBuffer[i] = highFilteredSample; + // IIR highpass, to remove low frequency noise + lowfilt += alpha * (sampleBuffer[i] - lowfilt); + sampleBuffer[i] = sampleBuffer[i] - lowfilt; + } +} + +static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // post-processing and post-amp of GEQ channels +{ + for (int i=0; i < numberOfChannels; i++) { + + if (noiseGateOpen) { // noise gate open + // Adjustment for frequency curves. + fftCalc[i] *= fftResultPink[i]; + if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function + // Manual linear adjustment of gain using sampleGain adjustment for different input types. + fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment + if(fftCalc[i] < 0) fftCalc[i] = 0.0f; + } + + // smooth results - rise fast, fall slower + if (fftCalc[i] > fftAvg[i]) fftAvg[i] = fftCalc[i]*0.75f + 0.25f*fftAvg[i]; // rise fast; will need approx 2 cycles (50ms) for converging against fftCalc[i] + else { // fall slow + if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero + else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero + else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero + else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero + } + // constrain internal vars - just to be sure + fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); + fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); + + float currentResult; + if(limiterOn == true) + currentResult = fftAvg[i]; + else + currentResult = fftCalc[i]; + + switch (FFTScalingMode) { + case 1: + // Logarithmic scaling + currentResult *= 0.42f; // 42 is the answer ;-) + currentResult -= 8.0f; // this skips the lowest row, giving some room for peaks + if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function + else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined + currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0.0f, LOG_256, 0.0f, 255.0f); // map [log(1) ... log(255)] to [0 ... 255] + break; + case 2: + // Linear scaling + currentResult *= 0.30f; // needs a bit more damping, get stay below 255 + currentResult -= 4.0f; // giving a bit more room for peaks (WLEDMM uses -2) + if (currentResult < 1.0f) currentResult = 0.0f; + currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies + break; + case 3: + // square root scaling + currentResult *= 0.38f; + currentResult -= 6.0f; + if (currentResult > 1.0f) currentResult = sqrtf(currentResult); + else currentResult = 0.0f; // special handling, because sqrt(0) = undefined + currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0.0f, 16.0f, 0.0f, 255.0f); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] + break; + + case 0: + default: + // no scaling - leave freq bins as-is + currentResult -= 4; // just a bit more room for peaks (WLEDMM uses -2) + break; + } + + // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. + if (soundAgc > 0) { // apply extra "GEQ Gain" if set by user + float post_gain = (float)inputLevel/128.0f; + if (post_gain < 1.0f) post_gain = ((post_gain -1.0f) * 0.8f) +1.0f; + currentResult *= post_gain; + } + fftResult[i] = constrain((int)currentResult, 0, 255); + } +} +//////////////////// +// Peak detection // +//////////////////// + +// peak detection is called from FFT task when vReal[] contains valid FFT results +static void detectSamplePeak(void) { + bool havePeak = false; + // softhack007: this code continuously triggers while amplitude in the selected bin is above a certain threshold. So it does not detect peaks - it detects high activity in a frequency bin. + // Poor man's beat detection by seeing if sample > Average + some value. + // This goes through ALL of the 255 bins - but ignores stupid settings + // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. + if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { + havePeak = true; + } + + if (havePeak) { + samplePeak = true; + timeOfPeak = millis(); + udpSamplePeak = true; + } +} + +#endif + +static void autoResetPeak(void) { + uint16_t MinShowDelay = MAX(50, WS2812FX::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. + samplePeak = false; + if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData + } +} + + +//////////////////// +// usermod class // +//////////////////// + +//class name. Use something descriptive and leave the ": public Usermod" part :) +class AudioReactive : public Usermod { + + private: +#ifdef ARDUINO_ARCH_ESP32 + + #ifndef AUDIOPIN + int8_t audioPin = -1; + #else + int8_t audioPin = AUDIOPIN; + #endif + #ifndef SR_DMTYPE // I2S mic type + uint8_t dmType = 1; // 0=none/disabled/analog; 1=generic I2S + #define SR_DMTYPE 1 // default type = I2S + #else + uint8_t dmType = SR_DMTYPE; + #endif + #ifndef I2S_SDPIN // aka DOUT + int8_t i2ssdPin = 32; + #else + int8_t i2ssdPin = I2S_SDPIN; + #endif + #ifndef I2S_WSPIN // aka LRCL + int8_t i2swsPin = 15; + #else + int8_t i2swsPin = I2S_WSPIN; + #endif + #ifndef I2S_CKPIN // aka BCLK + int8_t i2sckPin = 14; /*PDM: set to I2S_PIN_NO_CHANGE*/ + #else + int8_t i2sckPin = I2S_CKPIN; + #endif + #ifndef MCLK_PIN + int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ + #else + int8_t mclkPin = MCLK_PIN; + #endif +#endif + + // new "V2" audiosync struct - 44 Bytes + struct __attribute__ ((packed)) audioSyncPacket { // "packed" ensures that there are no additional gaps + char header[6]; // 06 Bytes offset 0 + uint8_t reserved1[2]; // 02 Bytes, offset 6 - gap required by the compiler - not used yet + float sampleRaw; // 04 Bytes offset 8 - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting + float sampleSmth; // 04 Bytes offset 12 - either "sampleAvg" or "sampleAgc" depending on soundAgc setting + uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude + uint8_t reserved2; // 01 Bytes offset 17 - for future extensions - not used yet + uint8_t fftResult[16]; // 16 Bytes offset 18 + uint16_t reserved3; // 02 Bytes, offset 34 - gap required by the compiler - not used yet + float FFT_Magnitude; // 04 Bytes offset 36 + float FFT_MajorPeak; // 04 Bytes offset 40 + }; + + // old "V1" audiosync struct - 83 Bytes payload, 88 bytes total (with padding added by compiler) - for backwards compatibility + struct audioSyncPacket_v1 { + char header[6]; // 06 Bytes + uint8_t myVals[32]; // 32 Bytes + int sampleAgc; // 04 Bytes + int sampleRaw; // 04 Bytes + float sampleAvg; // 04 Bytes + bool samplePeak; // 01 Bytes + uint8_t fftResult[16]; // 16 Bytes + double FFT_Magnitude; // 08 Bytes + double FFT_MajorPeak; // 08 Bytes + }; + + constexpr static unsigned UDPSOUND_MAX_PACKET = MAX(sizeof(audioSyncPacket), sizeof(audioSyncPacket_v1)); + + // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) + #ifdef UM_AUDIOREACTIVE_ENABLE + bool enabled = true; + #else + bool enabled = false; + #endif + + bool initDone = false; + bool addPalettes = false; + int8_t palettes = 0; + + // variables for UDP sound sync + WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!) + unsigned long lastTime = 0; // last time of running UDP Microphone Sync + const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED + uint16_t audioSyncPort= 11988;// default port for UDP sound sync + + bool updateIsRunning = false; // true during OTA. + +#ifdef ARDUINO_ARCH_ESP32 + // used for AGC + int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) + float control_integrated = 0.0f; // persistent across calls to agcAvg(); "integrator control" = accumulated error + // variables used by getSample() and agcAvg() + int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed + float sampleMax = 0.0f; // Max sample over a few seconds. Needed for AGC controller. + float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller + float expAdjF = 0.0f; // Used for exponential filter. + float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. + int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) + int16_t rawSampleAgc = 0; // not smoothed AGC sample +#endif + + // variables used in effects + float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample + int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc + float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc + + // used to feed "Info" Page + unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket + int receivedFormat = 0; // last received UDP sound sync format - 0=none, 1=v1 (0.13.x), 2=v2 (0.14.x) + float maxSample5sec = 0.0f; // max sample (after AGC) in last 5 seconds + unsigned long sampleMaxTimer = 0; // last time maxSample5sec was reset + #define CYCLE_SAMPLEMAX 3500 // time window for merasuring + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _config[]; + static const char _dynamics[]; + static const char _frequency[]; + static const char _inputLvl[]; +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + static const char _analogmic[]; +#endif + static const char _digitalmic[]; + static const char _addPalettes[]; + static const char UDP_SYNC_HEADER[]; + static const char UDP_SYNC_HEADER_v1[]; + + // private methods + void removeAudioPalettes(void); + void createAudioPalettes(void); + CRGB getCRGBForBand(int x, int pal); + void fillAudioPalettes(void); + + //////////////////// + // Debug support // + //////////////////// + void logAudio() + { + if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable + #ifdef MIC_LOGGER + // Debugging functions for audio input and sound processing. Comment out the values you want to see + PLOT_PRINT("micReal:"); PLOT_PRINT(micDataReal); PLOT_PRINT("\t"); + PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth); PLOT_PRINT("\t"); + //PLOT_PRINT("volumeRaw:"); PLOT_PRINT(volumeRaw); PLOT_PRINT("\t"); + PLOT_PRINT("DC_Level:"); PLOT_PRINT(micLev); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleAgc:"); PLOT_PRINT(sampleAgc); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleAvg:"); PLOT_PRINT(sampleAvg); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleReal:"); PLOT_PRINT(sampleReal); PLOT_PRINT("\t"); + #ifdef ARDUINO_ARCH_ESP32 + //PLOT_PRINT("micIn:"); PLOT_PRINT(micIn); PLOT_PRINT("\t"); + //PLOT_PRINT("sample:"); PLOT_PRINT(sample); PLOT_PRINT("\t"); + //PLOT_PRINT("sampleMax:"); PLOT_PRINT(sampleMax); PLOT_PRINT("\t"); + //PLOT_PRINT("samplePeak:"); PLOT_PRINT((samplePeak!=0) ? 128:0); PLOT_PRINT("\t"); + //PLOT_PRINT("multAgc:"); PLOT_PRINT(multAgc, 4); PLOT_PRINT("\t"); + #endif + PLOT_PRINTLN(); + #endif + + #ifdef FFT_SAMPLING_LOG + #if 0 + for(int i=0; i maxVal) maxVal = fftResult[i]; + if(fftResult[i] < minVal) minVal = fftResult[i]; + } + for(int i = 0; i < NUM_GEQ_CHANNELS; i++) { + PLOT_PRINT(i); PLOT_PRINT(":"); + PLOT_PRINTF("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1)); + } + if(printMaxVal) { + PLOT_PRINTF("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0)); + } + if(printMinVal) { + PLOT_PRINTF("%04d:minVal ", minVal); // printed with value first, then label, so negative values can be seen in Serial Monitor but don't throw off y axis in Serial Plotter + } + if(mapValuesToPlotterSpace) + PLOT_PRINTF("max:%04d ", (printMaxVal ? 17 : 16)*256); // print line above the maximum value we expect to see on the plotter to avoid autoscaling y axis + else { + PLOT_PRINTF("max:%04d ", 256); + } + PLOT_PRINTLN(); + #endif // FFT_SAMPLING_LOG + } // logAudio() + + +#ifdef ARDUINO_ARCH_ESP32 + ////////////////////// + // Audio Processing // + ////////////////////// + + /* + * A "PI controller" multiplier to automatically adjust sound sensitivity. + * + * A few tricks are implemented so that sampleAgc does't only utilize 0% and 100%: + * 0. don't amplify anything below squelch (but keep previous gain) + * 1. gain input = maximum signal observed in the last 5-10 seconds + * 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal + * 3. the amplification depends on signal level: + * a) normal zone - very slow adjustment + * b) emergency zone (<10% or >90%) - very fast adjustment + */ + void agcAvg(unsigned long the_time) + { + const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function + + float lastMultAgc = multAgc; // last multiplier used + float multAgcTemp = multAgc; // new multiplier + float tmpAgc = sampleReal * multAgc; // what-if amplified signal + + float control_error; // "control error" input for PI control + + if (last_soundAgc != soundAgc) control_integrated = 0.0f; // new preset - reset integrator + + // For PI controller, we need to have a constant "frequency" + // so let's make sure that the control loop is not running at insane speed + static unsigned long last_time = 0; + unsigned long time_now = millis(); + if ((the_time > 0) && (the_time < time_now)) time_now = the_time; // allow caller to override my clock + + if (time_now - last_time > 2) { + last_time = time_now; + + if ((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { + // MIC signal is "squelched" - deliver silence + tmpAgc = 0; + // we need to "spin down" the intgrated error buffer + if (fabs(control_integrated) < 0.01f) control_integrated = 0.0f; + else control_integrated *= 0.91f; + } else { + // compute new setpoint + if (tmpAgc <= agcTarget0Up[AGC_preset]) + multAgcTemp = agcTarget0[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = first setpoint + else + multAgcTemp = agcTarget1[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = second setpoint + } + // limit amplification + if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; + if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; + + // compute error terms + control_error = multAgcTemp - lastMultAgc; + + if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping + && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) + control_integrated += control_error * 0.002f * 0.25f; // 2ms = integration time; 0.25 for damping + else + control_integrated *= 0.9f; // spin down that beasty integrator + + // apply PI Control + tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain + if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower energy zone + multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; + multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; + } else { // "normal zone" + multAgcTemp = lastMultAgc + agcFollowSlow[AGC_preset] * agcControlKp[AGC_preset] * control_error; + multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; + } + + // limit amplification again - PI controller sometimes "overshoots" + //multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32 + if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; + if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; + } + + // NOW finally amplify the signal + tmpAgc = sampleReal * multAgcTemp; // apply gain to signal + if (fabsf(sampleReal) < 2.0f) tmpAgc = 0.0f; // apply squelch threshold + //tmpAgc = constrain(tmpAgc, 0, 255); + if (tmpAgc > 255) tmpAgc = 255.0f; // limit to 8bit + if (tmpAgc < 1) tmpAgc = 0.0f; // just to be sure + + // update global vars ONCE - multAgc, sampleAGC, rawSampleAgc + multAgc = multAgcTemp; + rawSampleAgc = 0.8f * tmpAgc + 0.2f * (float)rawSampleAgc; + // update smoothed AGC sample + if (fabsf(tmpAgc) < 1.0f) + sampleAgc = 0.5f * tmpAgc + 0.5f * sampleAgc; // fast path to zero + else + sampleAgc += agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path + + sampleAgc = fabsf(sampleAgc); // // make sure we have a positive value + last_soundAgc = soundAgc; + } // agcAvg() + + // post-processing and filtering of MIC sample (micDataReal) from FFTcode() + void getSample() + { + float sampleAdj; // Gain adjusted sample value + float tmpSample; // An interim sample variable used for calculations. + const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release. + const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function + + #ifdef WLED_DISABLE_SOUND + micIn = inoise8(millis(), millis()); // Simulated analog read + micDataReal = micIn; + #else + #ifdef ARDUINO_ARCH_ESP32 + micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4; + #else + // this is the minimal code for reading analog mic input on 8266. + // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. + static unsigned long lastAnalogTime = 0; + static float lastAnalogValue = 0.0f; + if (millis() - lastAnalogTime > 20) { + micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. + lastAnalogTime = millis(); + lastAnalogValue = micDataReal; + yield(); + } else micDataReal = lastAnalogValue; + micIn = int(micDataReal); + #endif + #endif + + micLev += (micDataReal-micLev) / 12288.0f; + if (micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align micLev to lowest input signal + + micIn -= micLev; // Let's center it to 0 now + // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. + float micInNoDC = fabsf(micDataReal - micLev); + expAdjF = (weighting * micInNoDC + (1.0f-weighting) * expAdjF); + expAdjF = fabsf(expAdjF); // Now (!) take the absolute value + + expAdjF = (expAdjF <= soundSquelch) ? 0.0f : expAdjF; // simple noise gate + if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0.0f; // do something meaningfull when "squelch = 0" + + tmpSample = expAdjF; + micIn = abs(micIn); // And get the absolute value of each sample + + sampleAdj = tmpSample * sampleGain * inputLevel / 5120.0f /* /40 /128 */ + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment + sampleReal = tmpSample; + + sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? + sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! + + // keep "peak" sample, but decay value if current sample is below peak + if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { + sampleMax += 0.5f * (sampleReal - sampleMax); // new peak - with some filtering + // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume + if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { + samplePeak = true; + timeOfPeak = millis(); + udpSamplePeak = true; + } + } else { + if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0)) + sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly + else + sampleMax *= agcSampleDecay[AGC_preset]; // signal to zero --> 5-8sec + } + if (sampleMax < 0.5f) sampleMax = 0.0f; + + sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. + sampleAvg = fabsf(sampleAvg); // make sure we have a positive value + } // getSample() + +#endif + + /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). + * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) + */ + // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) + void limitSampleDynamics(void) { + const float bigChange = 196.0f; // just a representative number - a large, expected sample value + static unsigned long last_time = 0; + static float last_volumeSmth = 0.0f; + + if (limiterOn == false) return; + + long delta_time = millis() - last_time; + delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up + float deltaSample = volumeSmth - last_volumeSmth; + + if (attackTime > 0) { // user has defined attack time > 0 + float maxAttack = bigChange * float(delta_time) / float(attackTime); + if (deltaSample > maxAttack) deltaSample = maxAttack; + } + if (decayTime > 0) { // user has defined decay time > 0 + float maxDecay = - bigChange * float(delta_time) / float(decayTime); + if (deltaSample < maxDecay) deltaSample = maxDecay; + } + + volumeSmth = last_volumeSmth + deltaSample; + + last_volumeSmth = volumeSmth; + last_time = millis(); + } + + + ////////////////////// + // UDP Sound Sync // + ////////////////////// + + // try to establish UDP sound sync connection + void connectUDPSoundSync(void) { + // This function tries to establish a UDP sync connection if needed + // necessary as we also want to transmit in "AP Mode", but the standard "connected()" callback only reacts on STA connection + static unsigned long last_connection_attempt = 0; + + if ((audioSyncPort <= 0) || ((audioSyncEnabled & 0x03) == 0)) return; // Sound Sync not enabled + if (udpSyncConnected) return; // already connected + if (!(apActive || interfacesInited)) return; // neither AP nor other connections availeable + if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds + if (updateIsRunning) return; + + // if we arrive here, we need a UDP connection but don't have one + last_connection_attempt = millis(); + connected(); // try to start UDP + } + +#ifdef ARDUINO_ARCH_ESP32 + void transmitAudioData() + { + //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); + + audioSyncPacket transmitData; + memset(reinterpret_cast(&transmitData), 0, sizeof(transmitData)); // make sure that the packet - including "invisible" padding bytes added by the compiler - is fully initialized + + strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); + // transmit samples that were not modified by limitSampleDynamics() + transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg; + transmitData.samplePeak = udpSamplePeak ? 1:0; + udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it + + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { + transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); + } + + transmitData.FFT_Magnitude = my_magnitude; + transmitData.FFT_MajorPeak = FFT_MajorPeak; + +#ifndef WLED_DISABLE_ESPNOW + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { + EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; + //DEBUGSR_PRINTLN(F("ESP-NOW Sending audio packet.")); + size_t packetSize = sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) + sizeof(transmitData); + memcpy(buffer.data, &transmitData, sizeof(transmitData)); + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize); + } +#endif + + if (udpSyncConnected && fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error + fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); + fftUdp.endPacket(); + } + return; + } // transmitAudioData() + +#endif + + static inline bool isValidUdpSyncVersion(const char *header) { + return strncmp_P(header, UDP_SYNC_HEADER, 6) == 0; + } + static inline bool isValidUdpSyncVersion_v1(const char *header) { + return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0; + } + + void decodeAudioData(int packetSize, uint8_t *fftBuff) { + audioSyncPacket receivedPacket; + memset(&receivedPacket, 0, sizeof(receivedPacket)); // start clean + memcpy(&receivedPacket, fftBuff, min((unsigned)packetSize, (unsigned)sizeof(receivedPacket))); // don't violate alignment - thanks @willmmiles# + + // update samples for effects + volumeSmth = fmaxf(receivedPacket.sampleSmth, 0.0f); + volumeRaw = fmaxf(receivedPacket.sampleRaw, 0.0f); +#ifdef ARDUINO_ARCH_ESP32 + // update internal samples + sampleRaw = volumeRaw; + sampleAvg = volumeSmth; + rawSampleAgc = volumeRaw; + sampleAgc = volumeSmth; + multAgc = 1.0f; +#endif + // Only change samplePeak IF it's currently false. + // If it's true already, then the animation still needs to respond. + autoResetPeak(); + if (!samplePeak) { + samplePeak = receivedPacket.samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); + } + //These values are only computed by ESP32 + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket.fftResult[i]; + my_magnitude = fmaxf(receivedPacket.FFT_Magnitude, 0.0f); + FFT_Magnitude = my_magnitude; + FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + } + + void decodeAudioData_v1(int packetSize, uint8_t *fftBuff) { + audioSyncPacket_v1 *receivedPacket = reinterpret_cast(fftBuff); + // update samples for effects + volumeSmth = fmaxf(receivedPacket->sampleAgc, 0.0f); + volumeRaw = volumeSmth; // V1 format does not have "raw" AGC sample +#ifdef ARDUINO_ARCH_ESP32 + // update internal samples + sampleRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); + sampleAvg = fmaxf(receivedPacket->sampleAvg, 0.0f);; + sampleAgc = volumeSmth; + rawSampleAgc = volumeRaw; + multAgc = 1.0f; +#endif + // Only change samplePeak IF it's currently false. + // If it's true already, then the animation still needs to respond. + autoResetPeak(); + if (!samplePeak) { + samplePeak = receivedPacket->samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); + } + //These values are only available on the ESP32 + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; + my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); + FFT_Magnitude = my_magnitude; + FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + } + + bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. + { + if (!udpSyncConnected) return false; + bool haveFreshData = false; + + size_t packetSize = fftUdp.parsePacket(); +#ifdef ARDUINO_ARCH_ESP32 + if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET))) fftUdp.flush(); // discard invalid packets (too small or too big) - only works on esp32 +#endif + if ((packetSize > 5) && (packetSize <= UDPSOUND_MAX_PACKET)) { + //DEBUGSR_PRINTLN("Received UDP Sync Packet"); + uint8_t fftBuff[UDPSOUND_MAX_PACKET+1] = { 0 }; // fixed-size buffer for receiving (stack), to avoid heap fragmentation caused by variable sized arrays + fftUdp.read(fftBuff, packetSize); + + // VERIFY THAT THIS IS A COMPATIBLE PACKET + if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { + decodeAudioData(packetSize, fftBuff); + //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2"); + haveFreshData = true; + receivedFormat = 2; + } else { + if (packetSize == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftBuff))) { + decodeAudioData_v1(packetSize, fftBuff); + //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v1"); + haveFreshData = true; + receivedFormat = 1; + } else receivedFormat = 0; // unknown format + } + } + return haveFreshData; + } + + + ////////////////////// + // usermod functions// + ////////////////////// + + public: + //Functions called by WLED or other usermods + + /* + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + * It is called *AFTER* readFromConfig() + */ + void setup() override + { + disableSoundProcessing = true; // just to be sure + if (!initDone) { + // usermod exchangeable data + // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers + um_data = new um_data_t; + um_data->u_size = 8; + um_data->u_type = new um_types_t[um_data->u_size]; + um_data->u_data = new void*[um_data->u_size]; + um_data->u_data[0] = &volumeSmth; //*used (New) + um_data->u_type[0] = UMT_FLOAT; + um_data->u_data[1] = &volumeRaw; // used (New) + um_data->u_type[1] = UMT_UINT16; + um_data->u_data[2] = fftResult; //*used (Blurz, DJ Light, Noisemove, GEQ_base, 2D Funky Plank, Akemi) + um_data->u_type[2] = UMT_BYTE_ARR; + um_data->u_data[3] = &samplePeak; //*used (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[3] = UMT_BYTE; + um_data->u_data[4] = &FFT_MajorPeak; //*used (Ripplepeak, Freqmap, Freqmatrix, Freqpixels, Freqwave, Gravfreq, Rocktaves, Waterfall) + um_data->u_type[4] = UMT_FLOAT; + um_data->u_data[5] = &my_magnitude; // used (New) + um_data->u_type[5] = UMT_FLOAT; + um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[6] = UMT_BYTE; + um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[7] = UMT_BYTE; + } + + +#ifdef ARDUINO_ARCH_ESP32 + + // Reset I2S peripheral for good measure + i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed + #if !defined(CONFIG_IDF_TARGET_ESP32C3) + delay(100); + periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3 + #endif + delay(100); // Give that poor microphone some time to setup. + + useBandPassFilter = false; + + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone + #endif + + switch (dmType) { + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + // stub cases for not-yet-supported I2S modes on other ESP32 chips + case 0: //ADC analog + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + case 5: //PDM Microphone + #endif + #endif + case 1: + DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); + break; + case 2: + DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); + audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + case 3: + DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); + break; + case 4: + DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + case 5: + DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/4.0f); + useBandPassFilter = true; // this reduces the noise floor on SPM1423 from 5% Vpp (~380) down to 0.05% Vpp (~5) + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); + break; + #endif + case 6: + DEBUGSR_PRINTLN(F("AR: ES8388 Source")); + audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + // ADC over I2S is only possible on "classic" ESP32 + case 0: + default: + DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); + audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog + if (audioSource) audioSource->initialize(audioPin); + break; + #endif + } + delay(250); // give microphone enough time to initialise + + if (!audioSource) enabled = false; // audio failed to initialise +#endif + if (enabled) onUpdateBegin(false); // create FFT task, and initialize network + if (enabled) disableSoundProcessing = false; // all good - enable audio processing +#ifdef ARDUINO_ARCH_ESP32 + if (FFT_Task == nullptr) enabled = false; // FFT task creation failed + if ((!audioSource) || (!audioSource->isInitialized())) { + // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync + #ifdef WLED_DEBUG + DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); + #else + DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); + #endif + disableSoundProcessing = true; + } +#endif + if (enabled) connectUDPSoundSync(); + if (enabled && addPalettes) createAudioPalettes(); + initDone = true; + } + + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() override + { + if (udpSyncConnected) { // clean-up: if open, close old UDP sync connection + udpSyncConnected = false; + fftUdp.stop(); + } + + if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { + #ifdef ARDUINO_ARCH_ESP32 + udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); + #else + udpSyncConnected = fftUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 0, 0, 1), audioSyncPort); + #endif + } + } + + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + * + * Tips: + * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. + * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. + * + * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. + * Instead, use a timer check as shown here. + */ + void loop() override + { + static unsigned long lastUMRun = millis(); + + if (!enabled) { + disableSoundProcessing = true; // keep processing suspended (FFT task) + lastUMRun = millis(); // update time keeping + return; + } + // We cannot wait indefinitely before processing audio data + if (WS2812FX::isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice + + // suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET) + if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed + &&( (realtimeMode == REALTIME_MODE_GENERIC) + ||(realtimeMode == REALTIME_MODE_E131) + ||(realtimeMode == REALTIME_MODE_UDP) + ||(realtimeMode == REALTIME_MODE_ADALIGHT) + ||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed + { + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) + if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" + DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); + DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); + } + #endif + disableSoundProcessing = true; + } else { + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) + if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" + DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); + DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); + } + #endif + if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping + disableSoundProcessing = false; + } + + if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode + if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode +#ifdef ARDUINO_ARCH_ESP32 + if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source + + + // Only run the sampling code IF we're not in Receive mode or realtime mode + if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { + if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation) + + unsigned long t_now = millis(); // remember current time + int userloopDelay = int(t_now - lastUMRun); + if (lastUMRun == 0) userloopDelay=0; // startup - don't have valid data from last run. + + #ifdef WLED_DEBUG + // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. + // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS + //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { + // DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); + //} + #endif + + // run filters, and repeat in case of loop delays (hick-up compensation) + if (userloopDelay <2) userloopDelay = 0; // minor glitch, no problem + if (userloopDelay >200) userloopDelay = 200; // limit number of filter re-runs + do { + getSample(); // run microphone sampling filters + agcAvg(t_now - userloopDelay); // Calculated the PI adjusted value as sampleAvg + userloopDelay -= 2; // advance "simulated time" by 2ms + } while (userloopDelay > 0); + lastUMRun = t_now; // update time keeping + + // update samples for effects (raw, smooth) + volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; + volumeRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + // update FFTMagnitude, taking into account AGC amplification + my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects + if (soundAgc) my_magnitude *= multAgc; + if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute + + limitSampleDynamics(); + } // if (!disableSoundProcessing) +#endif + + autoResetPeak(); // auto-reset sample peak after strip minShowDelay + if (!udpSyncConnected) udpSamplePeak = false; // reset UDP samplePeak while UDP is unconnected + + connectUDPSoundSync(); // ensure we have a connection - if needed + + // UDP Microphone Sync - receive mode + if ((audioSyncEnabled & 0x02) && udpSyncConnected) { + // Only run the audio listener code if we're in Receive mode + static float syncVolumeSmth = 0; + bool have_new_sample = false; + if (millis() - lastTime > delayMs) { + have_new_sample = receiveAudioData(); + if (have_new_sample) last_UDPTime = millis(); +#ifdef ARDUINO_ARCH_ESP32 + else fftUdp.flush(); // Flush udp input buffers if we haven't read it - avoids hickups in receive mode. Does not work on 8266. +#endif + lastTime = millis(); + } + if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample + else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter + limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups + } + + #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) + static unsigned long lastMicLoggerTime = 0; + if (millis()-lastMicLoggerTime > 20) { + lastMicLoggerTime = millis(); + logAudio(); + } + #endif + + // Info Page: keep max sample from last 5 seconds +#ifdef ARDUINO_ARCH_ESP32 + if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { + sampleMaxTimer = millis(); + maxSample5sec = (0.15f * maxSample5sec) + 0.85f *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing + if (sampleAvg < 1) maxSample5sec = 0; // noise gate + } else { + if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume + } +#else // similar functionality for 8266 receive only - use VolumeSmth instead of raw sample data + if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { + sampleMaxTimer = millis(); + maxSample5sec = (0.15 * maxSample5sec) + 0.85 * volumeSmth; // reset, and start with some smoothing + if (volumeSmth < 1.0f) maxSample5sec = 0; // noise gate + if (maxSample5sec < 0.0f) maxSample5sec = 0; // avoid negative values + } else { + if (volumeSmth >= 1.0f) maxSample5sec = fmaxf(maxSample5sec, volumeRaw); // follow maximum volume + } +#endif + +#ifdef ARDUINO_ARCH_ESP32 + //UDP Microphone Sync - transmit mode + if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { + // Only run the transmit code IF we're in Transmit mode + transmitAudioData(); + lastTime = millis(); + } +#endif + + fillAudioPalettes(); + } + + + bool getUMData(um_data_t **data) override + { + if (!data || !enabled) return false; // no pointer provided by caller or not enabled -> exit + *data = um_data; + return true; + } + +#ifdef ARDUINO_ARCH_ESP32 + void onUpdateBegin(bool init) override + { +#ifdef WLED_DEBUG + fftTime = sampleTime = 0; +#endif + // gracefully suspend FFT task (if running) + disableSoundProcessing = true; + + // reset sound data + micDataReal = 0.0f; + volumeRaw = 0; volumeSmth = 0.0f; + sampleAgc = 0.0f; sampleAvg = 0.0f; + sampleRaw = 0; rawSampleAgc = 0.0f; + my_magnitude = 0.0f; FFT_Magnitude = 0.0f; FFT_MajorPeak = 1.0f; + multAgc = 1.0f; + // reset FFT data + memset(fftCalc, 0, sizeof(fftCalc)); + memset(fftAvg, 0, sizeof(fftAvg)); + memset(fftResult, 0, sizeof(fftResult)); + for(int i=(init?0:1); i don't process audio + updateIsRunning = init; + } +#endif + +#ifdef ARDUINO_ARCH_ESP32 + /** + * handleButton() can be used to override default button behaviour. Returning true + * will prevent button working in a default way. + */ + bool handleButton(uint8_t b) override { + yield(); + // crude way of determining if audio input is analog + // better would be for AudioSource to implement getType() + if (enabled + && dmType == 0 && audioPin>=0 + && (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) + ) { + return true; + } + return false; + } + +#endif + //////////////////////////// + // Settings and Info Page // + //////////////////////////// + + /* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ + void addToJsonInfo(JsonObject& root) override + { +#ifdef ARDUINO_ARCH_ESP32 + char myStringBuffer[16]; // buffer for snprintf() - not used yet on 8266 +#endif + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + + String uiDomString = F(""); + infoArr.add(uiDomString); + + if (enabled) { +#ifdef ARDUINO_ARCH_ESP32 + // Input Level Slider + if (disableSoundProcessing == false) { // only show slider when audio processing is running + if (soundAgc > 0) { + infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies + } else { + infoArr = user.createNestedArray(F("Audio Input Level")); + } + uiDomString = F("
"); // + infoArr.add(uiDomString); + } +#endif + // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG + + // current Audio input + infoArr = user.createNestedArray(F("Audio Source")); + if (audioSyncEnabled & 0x02) { + // UDP sound sync - receive mode + infoArr.add(F("UDP sound sync")); + if (udpSyncConnected) { + if (millis() - last_UDPTime < 2500) + infoArr.add(F(" - receiving")); + else + infoArr.add(F(" - idle")); + } else { + infoArr.add(F(" - no connection")); + } +#ifndef ARDUINO_ARCH_ESP32 // substitute for 8266 + } else { + infoArr.add(F("sound sync Off")); + } +#else // ESP32 only + } else { + // Analog or I2S digital input + if (audioSource && (audioSource->isInitialized())) { + // audio source successfully configured + if (audioSource->getType() == AudioSource::Type_I2SAdc) { + infoArr.add(F("ADC analog")); + } else { + infoArr.add(F("I2S digital")); + } + // input level or "silence" + if (maxSample5sec > 1.0f) { + float my_usage = 100.0f * (maxSample5sec / 255.0f); + snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); + infoArr.add(myStringBuffer); + } else { + infoArr.add(F(" - quiet")); + } + } else { + // error during audio source setup + infoArr.add(F("not initialized")); + infoArr.add(F(" - check pin settings")); + } + } + + // Sound processing (FFT and input filters) + infoArr = user.createNestedArray(F("Sound Processing")); + if (audioSource && (disableSoundProcessing == false)) { + infoArr.add(F("running")); + } else { + infoArr.add(F("suspended")); + } + + // AGC or manual Gain + if ((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("Manual Gain")); + float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets + infoArr.add(roundf(myGain*100.0f) / 100.0f); + infoArr.add("x"); + } + if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("AGC Gain")); + infoArr.add(roundf(multAgc*100.0f) / 100.0f); + infoArr.add("x"); + } +#endif + // UDP Sound Sync status + infoArr = user.createNestedArray(F("UDP Sound Sync")); + if (audioSyncEnabled) { + if (audioSyncEnabled & 0x01) { + infoArr.add(F("send mode")); + if ((udpSyncConnected) && (millis() - lastTime < 2500)) infoArr.add(F(" v2")); + } else if (audioSyncEnabled & 0x02) { + infoArr.add(F("receive mode")); + } + } else + infoArr.add("off"); + if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)"); + if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < 2500)) { + if (receivedFormat == 1) infoArr.add(F(" v1")); + if (receivedFormat == 2) infoArr.add(F(" v2")); + } + + #if defined(WLED_DEBUG) || defined(SR_DEBUG) + #ifdef ARDUINO_ARCH_ESP32 + infoArr = user.createNestedArray(F("Sampling time")); + infoArr.add(float(sampleTime)/100.0f); + infoArr.add(" ms"); + + infoArr = user.createNestedArray(F("FFT time")); + infoArr.add(float(fftTime)/100.0f); + if ((fftTime/100) >= FFT_MIN_CYCLE) // FFT time over budget -> I2S buffer will overflow + infoArr.add("! ms"); + else if ((fftTime/80 + sampleTime/80) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability + infoArr.add(" ms!"); + else + infoArr.add(" ms"); + + DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); + DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime)/100.0f); + #endif + #endif + } + } + + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void addToJsonState(JsonObject& root) override + { + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject usermod = root[FPSTR(_name)]; + if (usermod.isNull()) { + usermod = root.createNestedObject(FPSTR(_name)); + } + usermod["on"] = enabled; + } + + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject& root) override + { + if (!initDone) return; // prevent crash on boot applyPreset() + bool prevEnabled = enabled; + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) { + if (usermod[FPSTR(_enabled)].is()) { + enabled = usermod[FPSTR(_enabled)].as(); + if (prevEnabled != enabled) onUpdateBegin(!enabled); + if (addPalettes) { + // add/remove custom/audioreactive palettes + if (prevEnabled && !enabled) removeAudioPalettes(); + if (!prevEnabled && enabled) createAudioPalettes(); + } + } +#ifdef ARDUINO_ARCH_ESP32 + if (usermod[FPSTR(_inputLvl)].is()) { + inputLevel = min(255,max(0,usermod[FPSTR(_inputLvl)].as())); + } +#endif + } + if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { + // handle removal of custom palettes from JSON call so we don't break things + removeAudioPalettes(); + } + } + + void onStateChange(uint8_t callMode) override { + if (initDone && enabled && addPalettes && palettes==0 && WS2812FX::customPalettes.size()<10) { + // if palettes were removed during JSON call re-add them + createAudioPalettes(); + } + } + + /* + * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will make your settings editable through the Usermod Settings page automatically. + * + * Usermod Settings Overview: + * - Numeric values are treated as floats in the browser. + * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float + * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and + * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. + * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. + * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a + * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. + * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type + * used in the Usermod when reading the value from ArduinoJson. + * - Pin values can be treated differently from an integer value by using the key name "pin" + * - "pin" can contain a single or array of integer values + * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins + * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) + * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used + * + * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings + * + * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. + * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. + * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) override + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_addPalettes)] = addPalettes; + +#ifdef ARDUINO_ARCH_ESP32 + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); + amic["pin"] = audioPin; + #endif + + JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); + dmic["type"] = dmType; + JsonArray pinArray = dmic.createNestedArray("pin"); + pinArray.add(i2ssdPin); + pinArray.add(i2swsPin); + pinArray.add(i2sckPin); + pinArray.add(mclkPin); + + JsonObject cfg = top.createNestedObject(FPSTR(_config)); + cfg[F("squelch")] = soundSquelch; + cfg[F("gain")] = sampleGain; + cfg[F("AGC")] = soundAgc; + + JsonObject freqScale = top.createNestedObject(FPSTR(_frequency)); + freqScale[F("scale")] = FFTScalingMode; +#endif + + JsonObject dynLim = top.createNestedObject(FPSTR(_dynamics)); + dynLim[F("limiter")] = limiterOn; + dynLim[F("rise")] = attackTime; + dynLim[F("fall")] = decayTime; + + JsonObject sync = top.createNestedObject("sync"); + sync["port"] = audioSyncPort; + sync["mode"] = audioSyncEnabled; + } + + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + * + * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) + * + * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present + * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them + * + * This function is guaranteed to be called on boot, but could also be called every time settings are updated + */ + bool readFromConfig(JsonObject& root) override + { + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + bool oldEnabled = enabled; + bool oldAddPalettes = addPalettes; + + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes); + +#ifdef ARDUINO_ARCH_ESP32 + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); + #else + audioPin = -1; // MCU does not support analog mic + #endif + + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType); + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM + #endif + #endif + + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); + + configComplete &= getJsonValue(top[FPSTR(_config)][F("squelch")], soundSquelch); + configComplete &= getJsonValue(top[FPSTR(_config)][F("gain")], sampleGain); + configComplete &= getJsonValue(top[FPSTR(_config)][F("AGC")], soundAgc); + + configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode); + + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("limiter")], limiterOn); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("rise")], attackTime); + configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("fall")], decayTime); +#endif + configComplete &= getJsonValue(top["sync"]["port"], audioSyncPort); + configComplete &= getJsonValue(top["sync"]["mode"], audioSyncEnabled); + + if (initDone) { + // add/remove custom/audioreactive palettes + if ((oldAddPalettes && !addPalettes) || (oldAddPalettes && !enabled)) removeAudioPalettes(); + if ((addPalettes && !oldAddPalettes && enabled) || (addPalettes && !oldEnabled && enabled)) createAudioPalettes(); + } // else setup() will create palettes + return configComplete; + } + + + void appendConfigData() override + { +#ifdef ARDUINO_ARCH_ESP32 + oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + oappend(SET_F("addOption(dd,'Generic Analog',0);")); + #endif + oappend(SET_F("addOption(dd,'Generic I2S',1);")); + oappend(SET_F("addOption(dd,'ES7243',2);")); + oappend(SET_F("addOption(dd,'SPH0654',3);")); + oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); + #endif + oappend(SET_F("addOption(dd,'ES8388',6);")); + + oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'Normal',1);")); + oappend(SET_F("addOption(dd,'Vivid',2);")); + oappend(SET_F("addOption(dd,'Lazy',3);")); + + oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'On',1);")); + oappend(SET_F("addInfo('AudioReactive:dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('AudioReactive:dynamics:rise',1,'ms (♪ effects only)');")); + oappend(SET_F("addInfo('AudioReactive:dynamics:fall',1,'ms (♪ effects only)');")); + + oappend(SET_F("dd=addDropdown('AudioReactive','frequency:scale');")); + oappend(SET_F("addOption(dd,'None',0);")); + oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); + oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); + oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); +#endif + + oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); + oappend(SET_F("addOption(dd,'Off',0);")); +#ifdef ARDUINO_ARCH_ESP32 + oappend(SET_F("addOption(dd,'Send',1);")); +#endif + oappend(SET_F("addOption(dd,'Receive',2);")); +#ifdef ARDUINO_ARCH_ESP32 + oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'only use -1, 0, 1 or 3','I2S MCLK');")); + #else + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); + #endif +#endif + } + + + /* + * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. + * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. + * Commonly used for custom clocks (Cronixie, 7 segment) + */ + //void handleOverlayDraw() override + //{ + //WS2812FX::setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black + //} + + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() override + { + return USERMOD_ID_AUDIOREACTIVE; + } +}; + +void AudioReactive::removeAudioPalettes(void) { + DEBUG_PRINTLN(F("Removing audio palettes.")); + while (palettes>0) { + WS2812FX::customPalettes.pop_back(); + DEBUG_PRINTLN(palettes); + palettes--; + } + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); +} + +void AudioReactive::createAudioPalettes(void) { + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); + if (palettes) return; + DEBUG_PRINTLN(F("Adding audio palettes.")); + for (int i=0; i= palettes) lastCustPalette -= palettes; + for (int pal=0; pal= 0) { - irqBound = pinManager.allocatePin(config.interruptPin, false, PinOwner::UM_IMU); + irqBound = PinManager::allocatePin(config.interruptPin, false, PinOwner::UM_IMU); if (!irqBound) { DEBUG_PRINTLN(F("MPU6050: IRQ pin already in use.")); return; } pinMode(config.interruptPin, INPUT); }; @@ -408,7 +408,7 @@ class MPU6050Driver : public Usermod { // Previously loaded and config changed if (irqBound && ((old_cfg.interruptPin != config.interruptPin) || !config.enabled)) { detachInterrupt(old_cfg.interruptPin); - pinManager.deallocatePin(old_cfg.interruptPin, PinOwner::UM_IMU); + PinManager::deallocatePin(old_cfg.interruptPin, PinOwner::UM_IMU); irqBound = false; } diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md index 4cb7ef0e..382f72d0 100644 --- a/usermods/mqtt_switch_v2/README.md +++ b/usermods/mqtt_switch_v2/README.md @@ -19,7 +19,7 @@ Example `usermods_list.cpp`: void registerUsermods() { - usermods.add(new UsermodMqttSwitch()); + UsermodManager::add(new UsermodMqttSwitch()); } ``` diff --git a/usermods/multi_relay/readme.md b/usermods/multi_relay/readme.md index 24dd394b..eaa069ae 100644 --- a/usermods/multi_relay/readme.md +++ b/usermods/multi_relay/readme.md @@ -41,7 +41,7 @@ When a relay is switched, a message is published: ## Usermod installation -1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `usermods.add(new MultiRelay());` at the bottom of `usermods_list.cpp`. +1. Register the usermod by adding `#include "../usermods/multi_relay/usermod_multi_relay.h"` at the top and `UsermodManager::add(new MultiRelay());` at the bottom of `usermods_list.cpp`. or 2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY` in your platformio.ini @@ -90,9 +90,9 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); - usermods.add(new MultiRelay()); + //UsermodManager::add(new MyExampleUsermod()); + //UsermodManager::add(new UsermodTemperature()); + UsermodManager::add(new MultiRelay()); } ``` diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index efb3c8ae..33a6cf85 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -516,7 +516,7 @@ void MultiRelay::setup() { if (!_relay[i].external) _relay[i].state = !offMode; state |= (uint8_t)(_relay[i].invert ? !_relay[i].state : _relay[i].state) << pin; } else if (_relay[i].pin<100 && _relay[i].pin>=0) { - if (pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { + if (PinManager::allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { if (!_relay[i].external) _relay[i].state = !offMode; switchRelay(i, _relay[i].state); _relay[i].active = false; @@ -817,7 +817,7 @@ bool MultiRelay::readFromConfig(JsonObject &root) { // deallocate all pins 1st for (int i=0; i=0 && oldPin[i]<100) { - pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); + PinManager::deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); } // allocate new pins setup(); diff --git a/usermods/pixels_dice_tray/pixels_dice_tray.h b/usermods/pixels_dice_tray/pixels_dice_tray.h index 238af314..a1e45ba3 100644 --- a/usermods/pixels_dice_tray/pixels_dice_tray.h +++ b/usermods/pixels_dice_tray/pixels_dice_tray.h @@ -112,15 +112,15 @@ class PixelsDiceTrayUsermod : public Usermod { SetSPIPinsFromMacros(); PinManagerPinType spiPins[] = { {spi_mosi, true}, {spi_miso, false}, {spi_sclk, true}}; - if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { + if (!PinManager::allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; } else { PinManagerPinType displayPins[] = { {TFT_CS, true}, {TFT_DC, true}, {TFT_RST, true}, {TFT_BL, true}}; - if (!pinManager.allocateMultiplePins( + if (!PinManager::allocateMultiplePins( displayPins, sizeof(displayPins) / sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { - pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + PinManager::deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); enabled = false; } } diff --git a/usermods/pwm_outputs/usermod_pwm_outputs.h b/usermods/pwm_outputs/usermod_pwm_outputs.h index 1880308c..09232f04 100644 --- a/usermods/pwm_outputs/usermod_pwm_outputs.h +++ b/usermods/pwm_outputs/usermod_pwm_outputs.h @@ -29,13 +29,13 @@ class PwmOutput { return; DEBUG_PRINTF("pwm_output[%d]: setup to freq %d\n", pin_, freq_); - if (!pinManager.allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS)) + if (!PinManager::allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS)) return; - channel_ = pinManager.allocateLedc(1); + channel_ = PinManager::allocateLedc(1); if (channel_ == 255) { DEBUG_PRINTF("pwm_output[%d]: failed to quire ledc\n", pin_); - pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); + PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); return; } @@ -49,9 +49,9 @@ class PwmOutput { DEBUG_PRINTF("pwm_output[%d]: close\n", pin_); if (!enabled_) return; - pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); + PinManager::deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS); if (channel_ != 255) - pinManager.deallocateLedc(channel_, 1); + PinManager::deallocateLedc(channel_, 1); channel_ = 255; duty_ = 0.0f; enabled_ = false; diff --git a/usermods/quinled-an-penta/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h index 10b78433..e4467203 100644 --- a/usermods/quinled-an-penta/quinled-an-penta.h +++ b/usermods/quinled-an-penta/quinled-an-penta.h @@ -129,7 +129,7 @@ class QuinLEDAnPentaUsermod : public Usermod void initOledDisplay() { PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } }; - if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { + if (!PinManager::allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name); oledEnabled = oledInitDone = false; return; @@ -164,11 +164,11 @@ class QuinLEDAnPentaUsermod : public Usermod oledDisplay->clear(); } - pinManager.deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta); delete oledDisplay; @@ -184,7 +184,7 @@ class QuinLEDAnPentaUsermod : public Usermod void initSht30TempHumiditySensor() { PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { + if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name); shtEnabled = shtInitDone = false; return; @@ -212,8 +212,8 @@ class QuinLEDAnPentaUsermod : public Usermod sht30TempHumidSensor->reset(); } - pinManager.deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta); - pinManager.deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta); + PinManager::deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta); delete sht30TempHumidSensor; diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h index e57641bf..00fc2272 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h @@ -40,7 +40,7 @@ class RgbRotaryEncoderUsermod : public Usermod void initRotaryEncoder() { PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } }; - if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) { + if (!PinManager::allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) { eaIo = -1; ebIo = -1; cleanup(); @@ -108,11 +108,11 @@ class RgbRotaryEncoderUsermod : public Usermod { // Only deallocate pins if we allocated them ;) if (eaIo != -1) { - pinManager.deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(eaIo, PinOwner::UM_RGBRotaryEncoder); eaIo = -1; } if (ebIo != -1) { - pinManager.deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(ebIo, PinOwner::UM_RGBRotaryEncoder); ebIo = -1; } @@ -303,8 +303,8 @@ class RgbRotaryEncoderUsermod : public Usermod } if (eaIo != oldEaIo || ebIo != oldEbIo || stepsPerClick != oldStepsPerClick || incrementPerClick != oldIncrementPerClick) { - pinManager.deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder); - pinManager.deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(oldEaIo, PinOwner::UM_RGBRotaryEncoder); + PinManager::deallocatePin(oldEbIo, PinOwner::UM_RGBRotaryEncoder); delete rotaryEncoder; initRotaryEncoder(); diff --git a/usermods/sd_card/usermod_sd_card.h b/usermods/sd_card/usermod_sd_card.h index 5dac7915..da1999d9 100644 --- a/usermods/sd_card/usermod_sd_card.h +++ b/usermods/sd_card/usermod_sd_card.h @@ -45,7 +45,7 @@ class UsermodSdCard : public Usermod { { configPinPico, true } }; - if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { + if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { DEBUG_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name); sdInitDone = false; return; @@ -75,10 +75,10 @@ class UsermodSdCard : public Usermod { SD_ADAPTER.end(); DEBUG_PRINTF("[%s] deallocate pins!\n", _name); - pinManager.deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinPoci, PinOwner::UM_SdCard); - pinManager.deallocatePin(configPinPico, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinPoci, PinOwner::UM_SdCard); + PinManager::deallocatePin(configPinPico, PinOwner::UM_SdCard); sdInitDone = false; } diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 111df296..1436f8fc 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -385,7 +385,7 @@ public: _setAllFalse(); #ifdef USERMOD_SN_PHOTORESISTOR - ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR); + ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); #endif DEBUG_PRINTLN(F("Setup done")); } diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 52ff3cc1..a257413b 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -103,7 +103,7 @@ class AutoSaveUsermod : public Usermod { #ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod has enhanced functionality if // FourLineDisplayUsermod is available. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + display = (FourLineDisplayUsermod*) UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP); #endif initDone = true; if (enabled && applyAutoSaveOnBoot) applyPreset(autoSavePreset); diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 008647fa..dfab7e6f 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -543,7 +543,7 @@ void FourLineDisplayUsermod::setup() { type = NONE; } else { PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; - if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type = NONE; } + if (!PinManager::allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type = NONE; } } } else { if (i2c_scl<0 || i2c_sda<0) { type=NONE; } @@ -569,7 +569,7 @@ void FourLineDisplayUsermod::setup() { if (nullptr == u8x8) { DEBUG_PRINTLN(F("Display init failed.")); if (isSPI) { - pinManager.deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); + PinManager::deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); } type = NONE; return; @@ -1307,7 +1307,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type == SSD1309_SPI64); bool newSPI = (newType == SSD1306_SPI || newType == SSD1306_SPI64 || newType == SSD1309_SPI64); if (isSPI) { - if (pinsChanged || !newSPI) pinManager.deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); + if (pinsChanged || !newSPI) PinManager::deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); if (!newSPI) { // was SPI but is no longer SPI if (i2c_scl<0 || i2c_sda<0) { newType=NONE; } @@ -1315,7 +1315,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { // still SPI but pins changed PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } - else if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + else if (!PinManager::allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } } } else if (newSPI) { // was I2C but is now SPI @@ -1324,7 +1324,7 @@ bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { } else { PinManagerPinType pins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; if (ioPin[0]<0 || ioPin[1]<0 || ioPin[1]<0) { newType=NONE; } - else if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + else if (!PinManager::allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } } } else { // just I2C type changed diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 5756fbb6..55715b7c 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -489,7 +489,7 @@ void RotaryEncoderUIUsermod::setup() enabled = false; return; } else { - if (pinIRQ >= 0 && pinManager.allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { + if (pinIRQ >= 0 && PinManager::allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { pinMode(pinIRQ, INPUT_PULLUP); attachInterrupt(pinIRQ, i2cReadingISR, FALLING); // RISING, FALLING, CHANGE, ONLOW, ONHIGH DEBUG_PRINTLN(F("Interrupt attached.")); @@ -502,7 +502,7 @@ void RotaryEncoderUIUsermod::setup() } } else { PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; - if (pinA<0 || pinB<0 || !pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { + if (pinA<0 || pinB<0 || !PinManager::allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { pinA = pinB = pinC = -1; enabled = false; return; @@ -525,7 +525,7 @@ void RotaryEncoderUIUsermod::setup() #ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod uses FourLineDisplayUsermod for the best experience. // But it's optional. But you want it. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + display = (FourLineDisplayUsermod*) UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP); if (display != nullptr) { display->setMarkLine(1, 0); } @@ -1138,14 +1138,14 @@ bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { if (oldPcf8574) { if (pinIRQ >= 0) { detachInterrupt(pinIRQ); - pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); DEBUG_PRINTLN(F("Deallocated old IRQ pin.")); } pinIRQ = newIRQpin<100 ? newIRQpin : -1; // ignore PCF8574 pins } else { - pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); - pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); + PinManager::deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); DEBUG_PRINTLN(F("Deallocated old pins.")); } pinA = newDTpin; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0084b09e..ad843f0f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -75,7 +75,7 @@ int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { static um_data_t* getAudioData() { um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } @@ -6298,7 +6298,7 @@ static const char _data_FX_MODE_2DPLASMAROTOZOOM[] PROGMEM = "Rotozoomer@!,Scale uint8_t *fftResult = nullptr; float *fftBin = nullptr; um_data_t *um_data; - if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { volumeSmth = *(float*) um_data->u_data[0]; volumeRaw = *(float*) um_data->u_data[1]; fftResult = (uint8_t*) um_data->u_data[2]; @@ -6911,7 +6911,7 @@ uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline. uint8_t *myVals = reinterpret_cast(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } float volumeSmth = *(float*) um_data->u_data[0]; @@ -7494,7 +7494,7 @@ uint16_t mode_2DAkemi(void) { const float normalFactor = 0.4f; um_data_t *um_data; - if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + if (!UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 0c4ec657..0f197e80 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -183,11 +183,7 @@ void IRAM_ATTR_YN Segment::deallocateData() { if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer free(data); } else { - DEBUG_PRINT(F("---- Released data ")); - DEBUG_PRINTF_P(PSTR("(%p): "), this); - DEBUG_PRINT(F("inconsistent UsedSegmentData ")); - DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData()); - DEBUG_PRINTLN(F(", cowardly refusing to free nothing.")); + DEBUG_PRINTF_P(PSTR("---- Released data (%p): inconsistent UsedSegmentData (%d/%d), cowardly refusing to free nothing.\n"), this, _dataLen, Segment::getUsedSegmentData()); } data = nullptr; Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); @@ -1251,7 +1247,7 @@ void WS2812FX::finalizeInit() { // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. // Pin should not be already allocated, read/only or defined for current bus - while (pinManager.isPinAllocated(defPin[j]) || !pinManager.isPinOk(defPin[j],true)) { + while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) { if (validPin) { DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); defPin[j] = 1; // start with GPIO1 and work upwards diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index b20095d4..3766975f 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -130,11 +130,11 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) , _colorOrderMap(com) { if (!isDigital(bc.type) || !bc.count) return; - if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; + if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; _frequencykHz = 0U; _pins[0] = bc.pins[0]; if (is2Pin(bc.type)) { - if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { + if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { cleanup(); return; } @@ -422,8 +422,8 @@ void BusDigital::cleanup() { _valid = false; _busPtr = nullptr; if (_data != nullptr) freeData(); - pinManager.deallocatePin(_pins[1], PinOwner::BusDigital); - pinManager.deallocatePin(_pins[0], PinOwner::BusDigital); + PinManager::deallocatePin(_pins[1], PinOwner::BusDigital); + PinManager::deallocatePin(_pins[0], PinOwner::BusDigital); } @@ -464,16 +464,16 @@ BusPwm::BusPwm(BusConfig &bc) managed_pin_type pins[numPins]; for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; - if (!pinManager.allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; + if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; #ifdef ESP8266 analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); #else // for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer - _ledcStart = pinManager.allocateLedc(numPins); + _ledcStart = PinManager::allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels - pinManager.deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); + PinManager::deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); return; } // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) @@ -640,8 +640,8 @@ std::vector BusPwm::getLEDTypes() { void BusPwm::deallocatePins() { unsigned numPins = getPins(); for (unsigned i = 0; i < numPins; i++) { - pinManager.deallocatePin(_pins[i], PinOwner::BusPwm); - if (!pinManager.isPinOk(_pins[i])) continue; + PinManager::deallocatePin(_pins[i], PinOwner::BusPwm); + if (!PinManager::isPinOk(_pins[i])) continue; #ifdef ESP8266 digitalWrite(_pins[i], LOW); //turn off PWM interrupt #else @@ -649,7 +649,7 @@ void BusPwm::deallocatePins() { #endif } #ifdef ARDUINO_ARCH_ESP32 - pinManager.deallocateLedc(_ledcStart, numPins); + PinManager::deallocateLedc(_ledcStart, numPins); #endif } @@ -661,7 +661,7 @@ BusOnOff::BusOnOff(BusConfig &bc) if (!Bus::isOnOff(bc.type)) return; uint8_t currentPin = bc.pins[0]; - if (!pinManager.allocatePin(currentPin, true, PinOwner::BusOnOff)) { + if (!PinManager::allocatePin(currentPin, true, PinOwner::BusOnOff)) { return; } _pin = currentPin; //store only after allocatePin() succeeds @@ -904,7 +904,7 @@ void BusManager::esp32RMTInvertIdle() { void BusManager::on() { #ifdef ESP8266 //Fix for turning off onboard LED breaking bus - if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { + if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (unsigned i = 0; i < numBusses; i++) { uint8_t pins[2] = {255,255}; if (busses[i]->isDigital() && busses[i]->getPins(pins)) { @@ -926,7 +926,7 @@ void BusManager::off() { #ifdef ESP8266 // 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) { + if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (unsigned i = 0; i < numBusses; i++) if (busses[i]->isOffRefreshRequired()) return; pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 24f10f0a..40fe61f4 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -280,7 +280,7 @@ class BusOnOff : public Bus { uint32_t getPixelColor(uint16_t pix) const override; uint8_t getPins(uint8_t* pinArray) const override; void show() override; - void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } + void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } static std::vector getLEDTypes(); diff --git a/wled00/button.cpp b/wled00/button.cpp index b5a4e943..f02ed3d6 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -267,7 +267,7 @@ void handleButton() if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue; #endif - if (usermods.handleButton(b)) continue; // did usermod handle buttons + if (UsermodManager::handleButton(b)) continue; // did usermod handle buttons if (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index f99aa8cd..3f6cfbac 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -261,12 +261,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonArray hw_btn_ins = btn_obj["ins"]; if (!hw_btn_ins.isNull()) { // deallocate existing button pins - for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button + for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) PinManager::deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button unsigned s = 0; for (JsonObject btn : hw_btn_ins) { CJSON(buttonType[s], btn["type"]); int8_t pin = btn["pin"][0] | -1; - if (pin > -1 && pinManager.allocatePin(pin, false, PinOwner::Button)) { + if (pin > -1 && PinManager::allocatePin(pin, false, PinOwner::Button)) { btnPin[s] = pin; #ifdef ARDUINO_ARCH_ESP32 // ESP32 only: check that analog button pin is a valid ADC gpio @@ -275,7 +275,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // not an ADC analog pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[s], s); btnPin[s] = -1; - pinManager.deallocatePin(pin,PinOwner::Button); + PinManager::deallocatePin(pin,PinOwner::Button); } else { analogReadResolution(12); // see #4040 } @@ -286,7 +286,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // not a touch pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s); btnPin[s] = -1; - pinManager.deallocatePin(pin,PinOwner::Button); + PinManager::deallocatePin(pin,PinOwner::Button); } //if touch pin, enable the touch interrupt on ESP32 S2 & S3 #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so @@ -331,7 +331,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (fromFS) { // relies upon only being called once with fromFS == true, which is currently true. for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) { - if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !pinManager.allocatePin(btnPin[s], false, PinOwner::Button)) { + if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) { btnPin[s] = -1; buttonType[s] = BTN_TYPE_NONE; } @@ -358,8 +358,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { #ifndef WLED_DISABLE_INFRARED int hw_ir_pin = hw["ir"]["pin"] | -2; // 4 if (hw_ir_pin > -2) { - pinManager.deallocatePin(irPin, PinOwner::IR); - if (pinManager.allocatePin(hw_ir_pin, false, PinOwner::IR)) { + PinManager::deallocatePin(irPin, PinOwner::IR); + if (PinManager::allocatePin(hw_ir_pin, false, PinOwner::IR)) { irPin = hw_ir_pin; } else { irPin = -1; @@ -374,8 +374,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { rlyOpenDrain = relay[F("odrain")] | rlyOpenDrain; int hw_relay_pin = relay["pin"] | -2; if (hw_relay_pin > -2) { - pinManager.deallocatePin(rlyPin, PinOwner::Relay); - if (pinManager.allocatePin(hw_relay_pin,true, PinOwner::Relay)) { + PinManager::deallocatePin(rlyPin, PinOwner::Relay); + if (PinManager::allocatePin(hw_relay_pin,true, PinOwner::Relay)) { rlyPin = hw_relay_pin; pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); } else { @@ -394,7 +394,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(i2c_sda, hw_if_i2c[0]); CJSON(i2c_scl, hw_if_i2c[1]); PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; - if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { + if (i2c_scl >= 0 && i2c_sda >= 0 && PinManager::allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { #ifdef ESP32 if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initialised (Wire.begin() called prior) else Wire.begin(); @@ -410,7 +410,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(spi_sclk, hw_if_spi[1]); CJSON(spi_miso, hw_if_spi[2]); PinManagerPinType spi[3] = { { spi_mosi, true }, { spi_miso, true }, { spi_sclk, true } }; - if (spi_mosi >= 0 && spi_sclk >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { + if (spi_mosi >= 0 && spi_sclk >= 0 && PinManager::allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { #ifdef ESP32 SPI.begin(spi_sclk, spi_miso, spi_mosi); // SPI global uses VSPI on ESP32 and FSPI on C3, S3 #else @@ -664,7 +664,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { DEBUG_PRINTLN(F("Starting usermod config.")); JsonObject usermods_settings = doc["um"]; if (!usermods_settings.isNull()) { - needsSave = !usermods.readFromConfig(usermods_settings); + needsSave = !UsermodManager::readFromConfig(usermods_settings); } if (fromFS) return needsSave; @@ -700,7 +700,7 @@ void deserializeConfigFromFS() { // save default values to /cfg.json // call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving JsonObject empty = JsonObject(); - usermods.readFromConfig(empty); + UsermodManager::readFromConfig(empty); serializeConfig(); // init Ethernet (in case default type is set at compile time) #ifdef WLED_USE_ETHERNET @@ -1121,7 +1121,7 @@ void serializeConfig() { #endif JsonObject usermods_settings = root.createNestedObject("um"); - usermods.addToConfig(usermods_settings); + UsermodManager::addToConfig(usermods_settings); File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); if (f) serializeJson(root, f); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a95064a2..8903d1f2 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -318,34 +318,34 @@ class Usermod { class UsermodManager { private: - Usermod* ums[WLED_MAX_USERMODS]; - byte numMods = 0; + static Usermod* ums[WLED_MAX_USERMODS]; + static byte numMods; public: - void loop(); - void handleOverlayDraw(); - bool handleButton(uint8_t b); - bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods - void setup(); - void connected(); - void appendConfigData(); - void addToJsonState(JsonObject& obj); - void addToJsonInfo(JsonObject& obj); - void readFromJsonState(JsonObject& obj); - void addToConfig(JsonObject& obj); - bool readFromConfig(JsonObject& obj); + static void loop(); + static void handleOverlayDraw(); + static bool handleButton(uint8_t b); + static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods + static void setup(); + static void connected(); + static void appendConfigData(); + static void addToJsonState(JsonObject& obj); + static void addToJsonInfo(JsonObject& obj); + static void readFromJsonState(JsonObject& obj); + static void addToConfig(JsonObject& obj); + static bool readFromConfig(JsonObject& obj); #ifndef WLED_DISABLE_MQTT - void onMqttConnect(bool sessionPresent); - bool onMqttMessage(char* topic, char* payload); + static void onMqttConnect(bool sessionPresent); + static bool onMqttMessage(char* topic, char* payload); #endif #ifndef WLED_DISABLE_ESPNOW - bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); + static bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); #endif - void onUpdateBegin(bool); - void onStateChange(uint8_t); - bool add(Usermod* um); - Usermod* lookup(uint16_t mod_id); - byte getModCount() {return numMods;}; + static void onUpdateBegin(bool); + static void onStateChange(uint8_t); + static bool add(Usermod* um); + static Usermod* lookup(uint16_t mod_id); + static inline byte getModCount() {return numMods;}; }; //usermods_list.cpp diff --git a/wled00/json.cpp b/wled00/json.cpp index 596bd780..0df7294c 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -436,7 +436,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } strip.resume(); - usermods.readFromJsonState(root); + UsermodManager::readFromJsonState(root); loadLedmap = root[F("ledmap")] | loadLedmap; @@ -592,7 +592,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme root[F("pl")] = currentPlaylist; root[F("ledmap")] = currentLedmap; - usermods.addToJsonState(root); + UsermodManager::addToJsonState(root); JsonObject nl = root.createNestedObject("nl"); nl["on"] = nightlightActive; @@ -784,7 +784,7 @@ void serializeInfo(JsonObject root) getTimeString(time); root[F("time")] = time; - usermods.addToJsonInfo(root); + UsermodManager::addToJsonInfo(root); uint16_t os = 0; #ifdef WLED_DEBUG diff --git a/wled00/led.cpp b/wled00/led.cpp index ba6ed255..9de0495b 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -131,7 +131,7 @@ void stateUpdated(byte callMode) { if (bri == nightlightTargetBri && callMode != CALL_MODE_NO_NOTIFY && nightlightMode != NL_MODE_SUN) nightlightActive = false; // notify usermods of state change - usermods.onStateChange(callMode); + UsermodManager::onStateChange(callMode); if (fadeTransition) { if (strip.getTransition() == 0) { diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index 833e6eb7..6c523c3e 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -45,7 +45,7 @@ static void onMqttConnect(bool sessionPresent) mqtt->subscribe(subuf, 0); } - usermods.onMqttConnect(sessionPresent); + UsermodManager::onMqttConnect(sessionPresent); DEBUG_PRINTLN(F("MQTT ready")); publishMqtt(); @@ -89,7 +89,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp topic += topicPrefixLen; } else { // Non-Wled Topic used here. Probably a usermod subscribed to this topic. - usermods.onMqttMessage(topic, payloadStr); + UsermodManager::onMqttMessage(topic, payloadStr); delete[] payloadStr; payloadStr = nullptr; return; @@ -115,7 +115,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp } } else if (strlen(topic) != 0) { // non standard topic, check with usermods - usermods.onMqttMessage(topic, payloadStr); + UsermodManager::onMqttMessage(topic, payloadStr); } else { // topmost topic (just wled/MAC) parseMQTTBriPayload(payloadStr); diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 239cff52..fcd0a40c 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -88,7 +88,7 @@ void _overlayAnalogCountdown() } void handleOverlayDraw() { - usermods.handleOverlayDraw(); + UsermodManager::handleOverlayDraw(); if (analogClockSolidBlack) { const Segment* segments = strip.getSegments(); for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index be2a4f97..793b5440 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -13,34 +13,16 @@ #endif #endif -#ifdef WLED_DEBUG -static void DebugPrintOwnerTag(PinOwner tag) -{ - uint32_t q = static_cast(tag); - if (q) { - DEBUG_PRINTF_P(PSTR("0x%02x (%d)"), q, q); - } else { - DEBUG_PRINT(F("(no owner)")); - } -} -#endif /// Actual allocation/deallocation routines -bool PinManagerClass::deallocatePin(byte gpio, PinOwner tag) +bool PinManager::deallocatePin(byte gpio, PinOwner tag) { if (gpio == 0xFF) return true; // explicitly allow clients to free -1 as a no-op if (!isPinOk(gpio, false)) return false; // but return false for any other invalid pin // if a non-zero ownerTag, only allow de-allocation if the owner's tag is provided if ((ownerTag[gpio] != PinOwner::None) && (ownerTag[gpio] != tag)) { - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN DEALLOC: IO ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" allocated by ")); - DebugPrintOwnerTag(ownerTag[gpio]); - DEBUG_PRINT(F(", but attempted de-allocation by ")); - DebugPrintOwnerTag(tag); - #endif + DEBUG_PRINTF_P(PSTR("PIN DEALLOC: FAIL GPIO %d allocated by 0x%02X, but attempted de-allocation by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio]), static_cast(tag)); return false; } @@ -50,7 +32,7 @@ bool PinManagerClass::deallocatePin(byte gpio, PinOwner tag) } // support function for deallocating multiple pins -bool PinManagerClass::deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag) +bool PinManager::deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag) { bool shouldFail = false; DEBUG_PRINTLN(F("MULTIPIN DEALLOC")); @@ -66,14 +48,7 @@ bool PinManagerClass::deallocateMultiplePins(const uint8_t *pinArray, byte array // if the current pin is allocated by selected owner it is possible to release it continue; } - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN DEALLOC: IO ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" allocated by ")); - DebugPrintOwnerTag(ownerTag[gpio]); - DEBUG_PRINT(F(", but attempted de-allocation by ")); - DebugPrintOwnerTag(tag); - #endif + DEBUG_PRINTF_P(PSTR("PIN DEALLOC: FAIL GPIO %d allocated by 0x%02X, but attempted de-allocation by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio]), static_cast(tag)); shouldFail = true; } if (shouldFail) { @@ -97,14 +72,14 @@ bool PinManagerClass::deallocateMultiplePins(const uint8_t *pinArray, byte array return true; } -bool PinManagerClass::deallocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag) +bool PinManager::deallocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag) { uint8_t pins[arrayElementCount]; for (int i=0; i(ownerTag[gpio])); shouldFail = true; } } @@ -158,64 +122,45 @@ bool PinManagerClass::allocateMultiplePins(const managed_pin_type * mptArray, by bitWrite(pinAlloc, gpio, true); ownerTag[gpio] = tag; - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN ALLOC: Pin ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" allocated by ")); - DebugPrintOwnerTag(tag); - DEBUG_PRINTLN(F("")); - #endif + DEBUG_PRINTF_P(PSTR("PIN ALLOC: Pin %d allocated by 0x%02X.\n"), gpio, static_cast(tag)); } + DEBUG_PRINTF_P(PSTR("PIN ALLOC: 0x%014llX.\n"), (unsigned long long)pinAlloc); return true; } -bool PinManagerClass::allocatePin(byte gpio, bool output, PinOwner tag) +bool PinManager::allocatePin(byte gpio, bool output, PinOwner tag) { // HW I2C & SPI pins have to be allocated using allocateMultiplePins variant since there is always SCL/SDA pair if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI) { #ifdef WLED_DEBUG if (gpio < 255) { // 255 (-1) is the "not defined GPIO" if (!isPinOk(gpio, output)) { - DEBUG_PRINT(F("PIN ALLOC: FAIL for owner ")); - DebugPrintOwnerTag(tag); - DEBUG_PRINT(F(": GPIO ")); DEBUG_PRINT(gpio); + DEBUG_PRINTF_P(PSTR("PIN ALLOC: FAIL for owner 0x%02X: GPIO %d "), static_cast(tag), gpio); if (output) DEBUG_PRINTLN(F(" cannot be used for i/o on this MCU.")); else DEBUG_PRINTLN(F(" cannot be used as input on this MCU.")); } else { - DEBUG_PRINT(F("PIN ALLOC: FAIL: GPIO ")); DEBUG_PRINT(gpio); - DEBUG_PRINTLN(F(" - HW I2C & SPI pins have to be allocated using allocateMultiplePins()")); + DEBUG_PRINTF_P(PSTR("PIN ALLOC: FAIL GPIO %d - HW I2C & SPI pins have to be allocated using allocateMultiplePins.\n"), gpio); } } #endif return false; } if (isPinAllocated(gpio)) { - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN ALLOC: Pin ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" already allocated by ")); - DebugPrintOwnerTag(ownerTag[gpio]); - DEBUG_PRINTLN(F("")); - #endif + DEBUG_PRINTF_P(PSTR("PIN ALLOC: FAIL Pin %d already allocated by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio])); return false; } bitWrite(pinAlloc, gpio, true); ownerTag[gpio] = tag; - #ifdef WLED_DEBUG - DEBUG_PRINT(F("PIN ALLOC: Pin ")); - DEBUG_PRINT(gpio); - DEBUG_PRINT(F(" successfully allocated by ")); - DebugPrintOwnerTag(tag); - DEBUG_PRINTLN(F("")); - #endif + DEBUG_PRINTF_P(PSTR("PIN ALLOC: Pin %d successfully allocated by 0x%02X.\n"), gpio, static_cast(ownerTag[gpio])); + DEBUG_PRINTF_P(PSTR("PIN ALLOC: 0x%014llX.\n"), (unsigned long long)pinAlloc); return true; } // if tag is set to PinOwner::None, checks for ANY owner of the pin. // if tag is set to any other value, checks if that tag is the current owner of the pin. -bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) const +bool PinManager::isPinAllocated(byte gpio, PinOwner tag) { if (!isPinOk(gpio, false)) return true; if ((tag != PinOwner::None) && (ownerTag[gpio] != tag)) return false; @@ -239,7 +184,7 @@ bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) const */ // Check if supplied GPIO is ok to use -bool PinManagerClass::isPinOk(byte gpio, bool output) const +bool PinManager::isPinOk(byte gpio, bool output) { if (gpio >= WLED_NUM_PINS) return false; // catch error case, to avoid array out-of-bounds access #ifdef ARDUINO_ARCH_ESP32 @@ -279,7 +224,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const return false; } -bool PinManagerClass::isReadOnlyPin(byte gpio) +bool PinManager::isReadOnlyPin(byte gpio) { #ifdef ARDUINO_ARCH_ESP32 if (gpio < WLED_NUM_PINS) return (digitalPinIsValid(gpio) && !digitalPinCanOutput(gpio)); @@ -287,14 +232,14 @@ bool PinManagerClass::isReadOnlyPin(byte gpio) return false; } -PinOwner PinManagerClass::getPinOwner(byte gpio) const +PinOwner PinManager::getPinOwner(byte gpio) { if (!isPinOk(gpio, false)) return PinOwner::None; return ownerTag[gpio]; } #ifdef ARDUINO_ARCH_ESP32 -byte PinManagerClass::allocateLedc(byte channels) +byte PinManager::allocateLedc(byte channels) { if (channels > WLED_MAX_ANALOG_CHANNELS || channels == 0) return 255; unsigned ca = 0; @@ -321,7 +266,7 @@ byte PinManagerClass::allocateLedc(byte channels) return 255; //not enough consecutive free LEDC channels } -void PinManagerClass::deallocateLedc(byte pos, byte channels) +void PinManager::deallocateLedc(byte pos, byte channels) { for (unsigned j = pos; j < pos + channels && j < WLED_MAX_ANALOG_CHANNELS; j++) { bitWrite(ledcAlloc, j, false); @@ -329,4 +274,12 @@ void PinManagerClass::deallocateLedc(byte pos, byte channels) } #endif -PinManagerClass pinManager = PinManagerClass(); +#ifdef ESP8266 +uint32_t PinManager::pinAlloc = 0UL; +#else +uint64_t PinManager::pinAlloc = 0ULL; +uint16_t PinManager::ledcAlloc = 0; +#endif +uint8_t PinManager::i2cAllocCount = 0; +uint8_t PinManager::spiAllocCount = 0; +PinOwner PinManager::ownerTag[WLED_NUM_PINS] = { PinOwner::None }; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index a64900c8..73a4a365 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -70,61 +70,54 @@ enum struct PinOwner : uint8_t { }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); -class PinManagerClass { +class PinManager { private: - struct { #ifdef ESP8266 - #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) - uint32_t pinAlloc : 24; // 24bit, 1 bit per pin, we use first 17bits + #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) + static uint32_t pinAlloc; // 1 bit per pin, we use first 17bits #else - #define WLED_NUM_PINS (GPIO_PIN_COUNT) - uint64_t pinAlloc : 56; // 56 bits, 1 bit per pin, we use 50 bits on ESP32-S3 - uint16_t ledcAlloc : 16; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) + #define WLED_NUM_PINS (GPIO_PIN_COUNT) + static uint64_t pinAlloc; // 1 bit per pin, we use 50 bits on ESP32-S3 + static uint16_t ledcAlloc; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) #endif - uint8_t i2cAllocCount : 4; // allow multiple allocation of I2C bus pins but keep track of allocations - uint8_t spiAllocCount : 4; // allow multiple allocation of SPI bus pins but keep track of allocations - } __attribute__ ((packed)); - PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; + static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations + static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations + static PinOwner ownerTag[WLED_NUM_PINS]; public: - PinManagerClass() : pinAlloc(0ULL), i2cAllocCount(0), spiAllocCount(0) { + // De-allocates a single pin + static bool deallocatePin(byte gpio, PinOwner tag); + // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) + static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); + static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); + // Allocates a single pin, with an owner tag. + // De-allocation requires the same owner tag (or override) + static bool allocatePin(byte gpio, bool output, PinOwner tag); + // Allocates all the pins, or allocates none of the pins, with owner tag. + // Provided to simplify error condition handling in clients + // using more than one pin, such as I2C, SPI, rotary encoders, + // ethernet, etc.. + static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); + + [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] + static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } + [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] + static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } + + // will return true for reserved pins + static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); + // will return false for reserved pins + static bool isPinOk(byte gpio, bool output = true); + + static bool isReadOnlyPin(byte gpio); + + static PinOwner getPinOwner(byte gpio); + #ifdef ARDUINO_ARCH_ESP32 - ledcAlloc = 0; + static byte allocateLedc(byte channels); + static void deallocateLedc(byte pos, byte channels); #endif - } - // De-allocates a single pin - bool deallocatePin(byte gpio, PinOwner tag); - // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) - bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); - bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); - // Allocates a single pin, with an owner tag. - // De-allocation requires the same owner tag (or override) - bool allocatePin(byte gpio, bool output, PinOwner tag); - // Allocates all the pins, or allocates none of the pins, with owner tag. - // Provided to simplify error condition handling in clients - // using more than one pin, such as I2C, SPI, rotary encoders, - // ethernet, etc.. - bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); - - [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] - inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } - [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] - inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } - - // will return true for reserved pins - bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None) const; - // will return false for reserved pins - bool isPinOk(byte gpio, bool output = true) const; - - static bool isReadOnlyPin(byte gpio); - - PinOwner getPinOwner(byte gpio) const; - - #ifdef ARDUINO_ARCH_ESP32 - byte allocateLedc(byte channels); - void deallocateLedc(byte pos, byte channels); - #endif }; -extern PinManagerClass pinManager; +//extern PinManager pinManager; #endif diff --git a/wled00/set.cpp b/wled00/set.cpp index 812bcc52..96eb3ed1 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -104,18 +104,18 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) { int t = 0; - if (rlyPin>=0 && pinManager.isPinAllocated(rlyPin, PinOwner::Relay)) { - pinManager.deallocatePin(rlyPin, PinOwner::Relay); + if (rlyPin>=0 && PinManager::isPinAllocated(rlyPin, PinOwner::Relay)) { + PinManager::deallocatePin(rlyPin, PinOwner::Relay); } #ifndef WLED_DISABLE_INFRARED - if (irPin>=0 && pinManager.isPinAllocated(irPin, PinOwner::IR)) { + if (irPin>=0 && PinManager::isPinAllocated(irPin, PinOwner::IR)) { deInitIR(); - pinManager.deallocatePin(irPin, PinOwner::IR); + PinManager::deallocatePin(irPin, PinOwner::IR); } #endif for (unsigned s=0; s=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) { - pinManager.deallocatePin(btnPin[s], PinOwner::Button); + if (btnPin[s]>=0 && PinManager::isPinAllocated(btnPin[s], PinOwner::Button)) { + PinManager::deallocatePin(btnPin[s], PinOwner::Button); #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt if (digitalPinToTouchChannel(btnPin[s]) >= 0) // if touch capable pin touchDetachInterrupt(btnPin[s]); // if not assigned previously, this will do nothing @@ -233,7 +233,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // update other pins #ifndef WLED_DISABLE_INFRARED int hw_ir_pin = request->arg(F("IR")).toInt(); - if (pinManager.allocatePin(hw_ir_pin,false, PinOwner::IR)) { + if (PinManager::allocatePin(hw_ir_pin,false, PinOwner::IR)) { irPin = hw_ir_pin; } else { irPin = -1; @@ -244,7 +244,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) irApplyToAllSelected = !request->hasArg(F("MSO")); int hw_rly_pin = request->arg(F("RL")).toInt(); - if (pinManager.allocatePin(hw_rly_pin,true, PinOwner::Relay)) { + if (PinManager::allocatePin(hw_rly_pin,true, PinOwner::Relay)) { rlyPin = hw_rly_pin; } else { rlyPin = -1; @@ -259,7 +259,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10) char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10) int hw_btn_pin = request->arg(bt).toInt(); - if (hw_btn_pin >= 0 && pinManager.allocatePin(hw_btn_pin,false,PinOwner::Button)) { + if (hw_btn_pin >= 0 && PinManager::allocatePin(hw_btn_pin,false,PinOwner::Button)) { btnPin[i] = hw_btn_pin; buttonType[i] = request->arg(be).toInt(); #ifdef ARDUINO_ARCH_ESP32 @@ -270,7 +270,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // not an ADC analog pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[i], i); btnPin[i] = -1; - pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); + PinManager::deallocatePin(hw_btn_pin,PinOwner::Button); } else { analogReadResolution(12); // see #4040 } @@ -282,7 +282,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) // not a touch pin DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[i], i); btnPin[i] = -1; - pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); + PinManager::deallocatePin(hw_btn_pin,PinOwner::Button); } #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so else @@ -631,10 +631,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (i2c_sda != hw_sda_pin || i2c_scl != hw_scl_pin) { // only if pins changed uint8_t old_i2c[2] = { static_cast(i2c_scl), static_cast(i2c_sda) }; - pinManager.deallocateMultiplePins(old_i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins + PinManager::deallocateMultiplePins(old_i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins PinManagerPinType i2c[2] = { { hw_sda_pin, true }, { hw_scl_pin, true } }; - if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { + if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && PinManager::allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { i2c_sda = hw_sda_pin; i2c_scl = hw_scl_pin; // no bus re-initialisation as usermods do not get any notification @@ -658,9 +658,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (spi_mosi != hw_mosi_pin || spi_miso != hw_miso_pin || spi_sclk != hw_sclk_pin) { // only if pins changed uint8_t old_spi[3] = { static_cast(spi_mosi), static_cast(spi_miso), static_cast(spi_sclk) }; - pinManager.deallocateMultiplePins(old_spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins + PinManager::deallocateMultiplePins(old_spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins PinManagerPinType spi[3] = { { hw_mosi_pin, true }, { hw_miso_pin, true }, { hw_sclk_pin, true } }; - if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { + if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && PinManager::allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { spi_mosi = hw_mosi_pin; spi_miso = hw_miso_pin; spi_sclk = hw_sclk_pin; @@ -750,8 +750,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) DEBUG_PRINTF_P(PSTR(" = %s\n"), value.c_str()); } } - usermods.readFromConfig(um); // force change of usermod parameters - DEBUG_PRINTLN(F("Done re-init usermods.")); + UsermodManager::readFromConfig(um); // force change of usermod parameters + DEBUG_PRINTLN(F("Done re-init UsermodManager::")); releaseJSONBufferLock(); } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 8cf733df..09e1440e 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -976,7 +976,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs #ifndef WLED_DISABLE_ESPNOW // usermods hook can override processing - if (usermods.onEspNowMessage(address, data, len)) return; + if (UsermodManager::onEspNowMessage(address, data, len)) return; #endif // handle WiZ Mote data diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 2db29c3c..d4ed8135 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -68,3 +68,6 @@ bool UsermodManager::add(Usermod* um) ums[numMods++] = um; return true; } + +Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr}; +byte UsermodManager::numMods = 0; diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 25d9ee9a..36bd122a 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -249,225 +249,225 @@ void registerUsermods() * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); + //UsermodManager::add(new MyExampleUsermod()); #ifdef USERMOD_BATTERY - usermods.add(new UsermodBattery()); + UsermodManager::add(new UsermodBattery()); #endif #ifdef USERMOD_DALLASTEMPERATURE - usermods.add(new UsermodTemperature()); + UsermodManager::add(new UsermodTemperature()); #endif #ifdef USERMOD_SN_PHOTORESISTOR - usermods.add(new Usermod_SN_Photoresistor()); + UsermodManager::add(new Usermod_SN_Photoresistor()); #endif #ifdef USERMOD_PWM_FAN - usermods.add(new PWMFanUsermod()); + UsermodManager::add(new PWMFanUsermod()); #endif #ifdef USERMOD_BUZZER - usermods.add(new BuzzerUsermod()); + UsermodManager::add(new BuzzerUsermod()); #endif #ifdef USERMOD_BH1750 - usermods.add(new Usermod_BH1750()); + UsermodManager::add(new Usermod_BH1750()); #endif #ifdef USERMOD_BME280 - usermods.add(new UsermodBME280()); + UsermodManager::add(new UsermodBME280()); #endif #ifdef USERMOD_BME68X - usermods.add(new UsermodBME68X()); + UsermodManager::add(new UsermodBME68X()); #endif #ifdef USERMOD_SENSORSTOMQTT - usermods.add(new UserMod_SensorsToMQTT()); + UsermodManager::add(new UserMod_SensorsToMQTT()); #endif #ifdef USERMOD_PIRSWITCH - usermods.add(new PIRsensorSwitch()); + UsermodManager::add(new PIRsensorSwitch()); #endif #ifdef USERMOD_FOUR_LINE_DISPLAY - usermods.add(new FourLineDisplayUsermod()); + UsermodManager::add(new FourLineDisplayUsermod()); #endif #ifdef USERMOD_ROTARY_ENCODER_UI - usermods.add(new RotaryEncoderUIUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY + UsermodManager::add(new RotaryEncoderUIUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY #endif #ifdef USERMOD_AUTO_SAVE - usermods.add(new AutoSaveUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY + UsermodManager::add(new AutoSaveUsermod()); // can use USERMOD_FOUR_LINE_DISPLAY #endif #ifdef USERMOD_DHT - usermods.add(new UsermodDHT()); + UsermodManager::add(new UsermodDHT()); #endif #ifdef USERMOD_VL53L0X_GESTURES - usermods.add(new UsermodVL53L0XGestures()); + UsermodManager::add(new UsermodVL53L0XGestures()); #endif #ifdef USERMOD_ANIMATED_STAIRCASE - usermods.add(new Animated_Staircase()); + UsermodManager::add(new Animated_Staircase()); #endif #ifdef USERMOD_MULTI_RELAY - usermods.add(new MultiRelay()); + UsermodManager::add(new MultiRelay()); #endif #ifdef USERMOD_RTC - usermods.add(new RTCUsermod()); + UsermodManager::add(new RTCUsermod()); #endif #ifdef USERMOD_ELEKSTUBE_IPS - usermods.add(new ElekstubeIPSUsermod()); + UsermodManager::add(new ElekstubeIPSUsermod()); #endif #ifdef USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR - usermods.add(new RotaryEncoderBrightnessColor()); + UsermodManager::add(new RotaryEncoderBrightnessColor()); #endif #ifdef RGB_ROTARY_ENCODER - usermods.add(new RgbRotaryEncoderUsermod()); + UsermodManager::add(new RgbRotaryEncoderUsermod()); #endif #ifdef USERMOD_ST7789_DISPLAY - usermods.add(new St7789DisplayUsermod()); + UsermodManager::add(new St7789DisplayUsermod()); #endif #ifdef USERMOD_PIXELS_DICE_TRAY - usermods.add(new PixelsDiceTrayUsermod()); + UsermodManager::add(new PixelsDiceTrayUsermod()); #endif #ifdef USERMOD_SEVEN_SEGMENT - usermods.add(new SevenSegmentDisplay()); + UsermodManager::add(new SevenSegmentDisplay()); #endif #ifdef USERMOD_SSDR - usermods.add(new UsermodSSDR()); + UsermodManager::add(new UsermodSSDR()); #endif #ifdef USERMOD_CRONIXIE - usermods.add(new UsermodCronixie()); + UsermodManager::add(new UsermodCronixie()); #endif #ifdef QUINLED_AN_PENTA - usermods.add(new QuinLEDAnPentaUsermod()); + UsermodManager::add(new QuinLEDAnPentaUsermod()); #endif #ifdef USERMOD_WIZLIGHTS - usermods.add(new WizLightsUsermod()); + UsermodManager::add(new WizLightsUsermod()); #endif #ifdef USERMOD_WIREGUARD - usermods.add(new WireguardUsermod()); + UsermodManager::add(new WireguardUsermod()); #endif #ifdef USERMOD_WORDCLOCK - usermods.add(new WordClockUsermod()); + UsermodManager::add(new WordClockUsermod()); #endif #ifdef USERMOD_MY9291 - usermods.add(new MY9291Usermod()); + UsermodManager::add(new MY9291Usermod()); #endif #ifdef USERMOD_SI7021_MQTT_HA - usermods.add(new Si7021_MQTT_HA()); + UsermodManager::add(new Si7021_MQTT_HA()); #endif #ifdef USERMOD_SMARTNEST - usermods.add(new Smartnest()); + UsermodManager::add(new Smartnest()); #endif #ifdef USERMOD_AUDIOREACTIVE - usermods.add(new AudioReactive()); + UsermodManager::add(new AudioReactive()); #endif #ifdef USERMOD_ANALOG_CLOCK - usermods.add(new AnalogClockUsermod()); + UsermodManager::add(new AnalogClockUsermod()); #endif #ifdef USERMOD_PING_PONG_CLOCK - usermods.add(new PingPongClockUsermod()); + UsermodManager::add(new PingPongClockUsermod()); #endif #ifdef USERMOD_ADS1115 - usermods.add(new ADS1115Usermod()); + UsermodManager::add(new ADS1115Usermod()); #endif #ifdef USERMOD_KLIPPER_PERCENTAGE - usermods.add(new klipper_percentage()); + UsermodManager::add(new klipper_percentage()); #endif #ifdef USERMOD_BOBLIGHT - usermods.add(new BobLightUsermod()); + UsermodManager::add(new BobLightUsermod()); #endif #ifdef SD_ADAPTER - usermods.add(new UsermodSdCard()); + UsermodManager::add(new UsermodSdCard()); #endif #ifdef USERMOD_PWM_OUTPUTS - usermods.add(new PwmOutputsUsermod()); + UsermodManager::add(new PwmOutputsUsermod()); #endif #ifdef USERMOD_SHT - usermods.add(new ShtUsermod()); + UsermodManager::add(new ShtUsermod()); #endif #ifdef USERMOD_ANIMARTRIX - usermods.add(new AnimartrixUsermod("Animartrix", false)); + UsermodManager::add(new AnimartrixUsermod("Animartrix", false)); #endif #ifdef USERMOD_INTERNAL_TEMPERATURE - usermods.add(new InternalTemperatureUsermod()); + UsermodManager::add(new InternalTemperatureUsermod()); #endif #ifdef USERMOD_HTTP_PULL_LIGHT_CONTROL - usermods.add(new HttpPullLightControl()); + UsermodManager::add(new HttpPullLightControl()); #endif #ifdef USERMOD_MPU6050_IMU - static MPU6050Driver mpu6050; usermods.add(&mpu6050); + static MPU6050Driver mpu6050; UsermodManager::add(&mpu6050); #endif #ifdef USERMOD_GYRO_SURGE - static GyroSurge gyro_surge; usermods.add(&gyro_surge); + static GyroSurge gyro_surge; UsermodManager::add(&gyro_surge); #endif #ifdef USERMOD_LDR_DUSK_DAWN - usermods.add(new LDR_Dusk_Dawn_v2()); + UsermodManager::add(new LDR_Dusk_Dawn_v2()); #endif #ifdef USERMOD_STAIRCASE_WIPE - usermods.add(new StairwayWipeUsermod()); + UsermodManager::add(new StairwayWipeUsermod()); #endif #ifdef USERMOD_MAX17048 - usermods.add(new Usermod_MAX17048()); + UsermodManager::add(new Usermod_MAX17048()); #endif #ifdef USERMOD_TETRISAI - usermods.add(new TetrisAIUsermod()); + UsermodManager::add(new TetrisAIUsermod()); #endif #ifdef USERMOD_AHT10 - usermods.add(new UsermodAHT10()); + UsermodManager::add(new UsermodAHT10()); #endif #ifdef USERMOD_INA226 - usermods.add(new UsermodINA226()); + UsermodManager::add(new UsermodINA226()); #endif #ifdef USERMOD_LD2410 - usermods.add(new LD2410Usermod()); + UsermodManager::add(new LD2410Usermod()); #endif #ifdef USERMOD_POV_DISPLAY - usermods.add(new PovDisplayUsermod()); + UsermodManager::add(new PovDisplayUsermod()); #endif } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index bc1cc7b7..39e0d250 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -72,7 +72,7 @@ void WLED::loop() unsigned long usermodMillis = millis(); #endif userLoop(); - usermods.loop(); + UsermodManager::loop(); #ifdef WLED_DEBUG usermodMillis = millis() - usermodMillis; avgUsermodMillis += usermodMillis; @@ -410,10 +410,10 @@ void WLED::setup() #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output + PinManager::allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output #endif #ifdef WLED_ENABLE_DMX //reserve GPIO2 as hardcoded DMX pin - pinManager.allocatePin(2, true, PinOwner::DMX); + PinManager::allocatePin(2, true, PinOwner::DMX); #endif DEBUG_PRINTLN(F("Registering usermods ...")); @@ -452,7 +452,7 @@ void WLED::setup() DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); #if defined(STATUSLED) && STATUSLED>=0 - if (!pinManager.isPinAllocated(STATUSLED)) { + if (!PinManager::isPinAllocated(STATUSLED)) { // NOTE: Special case: The status LED should *NOT* be allocated. // See comments in handleStatusLed(). pinMode(STATUSLED, OUTPUT); @@ -465,7 +465,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Usermods setup")); userSetup(); - usermods.setup(); + UsermodManager::setup(); DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap()); if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) @@ -479,8 +479,8 @@ void WLED::setup() findWiFi(true); // start scanning for available WiFi-s // all GPIOs are allocated at this point - serialCanRX = !pinManager.isPinAllocated(hardwareRX); // Serial RX pin (GPIO 3 on ESP32 and ESP8266) - serialCanTX = !pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut; // Serial TX pin (GPIO 1 on ESP32 and ESP8266) + serialCanRX = !PinManager::isPinAllocated(hardwareRX); // Serial RX pin (GPIO 3 on ESP32 and ESP8266) + serialCanTX = !PinManager::isPinAllocated(hardwareTX) || PinManager::getPinOwner(hardwareTX) == PinOwner::DebugOut; // Serial TX pin (GPIO 1 on ESP32 and ESP8266) #ifdef WLED_ENABLE_ADALIGHT //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused @@ -685,7 +685,7 @@ bool WLED::initEthernet() return false; } - if (!pinManager.allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) { + if (!PinManager::allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) { DEBUG_PRINTLN(F("initE: Failed to allocate ethernet pins")); return false; } @@ -719,7 +719,7 @@ bool WLED::initEthernet() DEBUG_PRINTLN(F("initC: ETH.begin() failed")); // de-allocate the allocated pins for (managed_pin_type mpt : pinsToAllocate) { - pinManager.deallocatePin(mpt.pin, PinOwner::Ethernet); + PinManager::deallocatePin(mpt.pin, PinOwner::Ethernet); } return false; } @@ -1010,7 +1010,7 @@ void WLED::handleConnection() } initInterfaces(); userConnected(); - usermods.connected(); + UsermodManager::connected(); lastMqttReconnectAttempt = 0; // force immediate update // shut down AP @@ -1033,7 +1033,7 @@ void WLED::handleStatusLED() uint32_t c = 0; #if STATUSLED>=0 - if (pinManager.isPinAllocated(STATUSLED)) { + if (PinManager::isPinAllocated(STATUSLED)) { return; //lower priority if something else uses the same pin } #endif diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 9d4e4c85..7d6fecd8 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -396,7 +396,7 @@ void initServer() #if WLED_WATCHDOG_TIMEOUT > 0 WLED::instance().disableWatchdog(); #endif - usermods.onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init) + 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 @@ -412,7 +412,7 @@ void initServer() } else { DEBUG_PRINTLN(F("Update Failed")); strip.resume(); - usermods.onUpdateBegin(false); // notify usermods that update has failed (some may require task init) + UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init) #if WLED_WATCHDOG_TIMEOUT > 0 WLED::instance().enableWatchdog(); #endif diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 71d66d00..a9195a30 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -135,7 +135,7 @@ void appendGPIOinfo() { if (requestJSONBufferLock(6)) { // if we can't allocate JSON buffer ignore usermod pins JsonObject mods = pDoc->createNestedObject(F("um")); - usermods.addToConfig(mods); + UsermodManager::addToConfig(mods); if (!mods.isNull()) fillUMPins(mods); releaseJSONBufferLock(); } @@ -144,7 +144,7 @@ void appendGPIOinfo() { // add reserved (unusable) pins oappend(SET_F("d.rsvd=[")); for (unsigned i = 0; i < WLED_NUM_PINS; i++) { - if (!pinManager.isPinOk(i, false)) { // include readonly pins + if (!PinManager::isPinOk(i, false)) { // include readonly pins oappendi(i); oappend(","); } } @@ -181,7 +181,7 @@ void appendGPIOinfo() { oappend(SET_F("d.ro_gpio=[")); bool firstPin = true; for (unsigned i = 0; i < WLED_NUM_PINS; i++) { - if (pinManager.isReadOnlyPin(i)) { + if (PinManager::isReadOnlyPin(i)) { // No comma before the first pin if (!firstPin) oappend(SET_F(",")); oappendi(i); @@ -370,7 +370,7 @@ void getSettingsJS(byte subPage, char* dest) int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) sappend('v',lp,pins[i]); + if (PinManager::isPinOk(pins[i]) || bus->isVirtual()) sappend('v',lp,pins[i]); } sappend('v',lc,bus->getLength()); sappend('v',lt,bus->getType()); @@ -694,7 +694,7 @@ void getSettingsJS(byte subPage, char* dest) { appendGPIOinfo(); oappend(SET_F("numM=")); - oappendi(usermods.getModCount()); + oappendi(UsermodManager::getModCount()); oappend(";"); sappend('v',SET_F("SDA"),i2c_sda); sappend('v',SET_F("SCL"),i2c_scl); @@ -706,7 +706,7 @@ void getSettingsJS(byte subPage, char* dest) oappend(SET_F("addInfo('MOSI','")); oappendi(HW_PIN_DATASPI); oappend(SET_F("');")); oappend(SET_F("addInfo('MISO','")); oappendi(HW_PIN_MISOSPI); oappend(SET_F("');")); oappend(SET_F("addInfo('SCLK','")); oappendi(HW_PIN_CLOCKSPI); oappend(SET_F("');")); - usermods.appendConfigData(); + UsermodManager::appendConfigData(); } if (subPage == SUBPAGE_UPDATE) // update From 9cb3531e2d3cd12dbdfc0669dfdbd1f5a58e75b6 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 21 Sep 2024 22:24:36 +0200 Subject: [PATCH 49/50] Remove erroneous file Fix constant dependancy --- usermods/audioreactive/audio_reactive.old.h | 2071 ------------------- wled00/FX_fcn.cpp | 3 +- 2 files changed, 1 insertion(+), 2073 deletions(-) delete mode 100644 usermods/audioreactive/audio_reactive.old.h diff --git a/usermods/audioreactive/audio_reactive.old.h b/usermods/audioreactive/audio_reactive.old.h deleted file mode 100644 index 4f2e04c0..00000000 --- a/usermods/audioreactive/audio_reactive.old.h +++ /dev/null @@ -1,2071 +0,0 @@ -#pragma once - -#include "wled.h" - -#ifdef ARDUINO_ARCH_ESP32 - -#include -#include - -#ifdef WLED_ENABLE_DMX - #error This audio reactive usermod is not compatible with DMX Out. -#endif - -#endif - -#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) -#include -#endif - -/* - * Usermods allow you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * - * This is an audioreactive v2 usermod. - * .... - */ - -#if !defined(FFTTASK_PRIORITY) -#define FFTTASK_PRIORITY 1 // standard: looptask prio -//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp -//#define FFTTASK_PRIORITY 4 // above asyc_tcp -#endif - -// Comment/Uncomment to toggle usb serial debugging -// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) -// #define FFT_SAMPLING_LOG // FFT result debugging -// #define SR_DEBUG // generic SR DEBUG messages - -#ifdef SR_DEBUG - #define DEBUGSR_PRINT(x) DEBUGOUT.print(x) - #define DEBUGSR_PRINTLN(x) DEBUGOUT.println(x) - #define DEBUGSR_PRINTF(x...) DEBUGOUT.printf(x) -#else - #define DEBUGSR_PRINT(x) - #define DEBUGSR_PRINTLN(x) - #define DEBUGSR_PRINTF(x...) -#endif - -#if defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG) - #define PLOT_PRINT(x) DEBUGOUT.print(x) - #define PLOT_PRINTLN(x) DEBUGOUT.println(x) - #define PLOT_PRINTF(x...) DEBUGOUT.printf(x) -#else - #define PLOT_PRINT(x) - #define PLOT_PRINTLN(x) - #define PLOT_PRINTF(x...) -#endif - -#define MAX_PALETTES 3 - -static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. -static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) -static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group - -#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! - -// audioreactive variables -#ifdef ARDUINO_ARCH_ESP32 -static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point -static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier -static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) -static float sampleAgc = 0.0f; // Smoothed AGC sample -static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) -#endif -//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 WS2812FX::getMinShowDelay() -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 - -// TODO: probably best not used by receive nodes -//static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 - -// user settable parameters for limitSoundDynamics() -#ifdef UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF -static bool limiterOn = false; // bool: enable / disable dynamics limiter -#else -static bool limiterOn = true; -#endif -static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec -static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec - -// peak detection -#ifdef ARDUINO_ARCH_ESP32 -static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode -#endif -static void autoResetPeak(void); // peak auto-reset function -static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) -static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) - -#ifdef ARDUINO_ARCH_ESP32 - -// use audio source class (ESP32 specific) -#include "audio_source.h" -constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) -constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples) - -// globals -static uint8_t inputLevel = 128; // UI slider value -#ifndef SR_SQUELCH - uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) -#else - uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value) -#endif -#ifndef SR_GAIN - uint8_t sampleGain = 60; // sample gain (config value) -#else - uint8_t sampleGain = SR_GAIN; // sample gain (config value) -#endif -// user settable options for FFTResult scaling -static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root - -// -// AGC presets -// Note: in C++, "const" implies "static" - no need to explicitly declare everything as "static const" -// -#define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy -const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax -const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone -const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone -const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level -const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% -const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) -const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% -const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec -const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs -const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter -const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter -const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) -// AGC presets end - -static AudioSource *audioSource = nullptr; -static bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT. - -//////////////////// -// Begin FFT Code // -//////////////////// - -// some prototypes, to ensure consistent interfaces -static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float -static float fftAddAvg(int from, int to); // average of several FFT result bins -static void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results -static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) -static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels - -static TaskHandle_t FFT_Task = nullptr; - -// Table of multiplication factors so that we can even out the frequency response. -static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; - -// globals and FFT Output variables shared with animations -#if defined(WLED_DEBUG) || defined(SR_DEBUG) -static uint64_t fftTime = 0; -static uint64_t sampleTime = 0; -#endif - -// FFT Task variables (filtering and post-processing) -static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. -static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) -#ifdef SR_DEBUG -static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working. -#endif - -// audio source parameters and constant -#ifdef ARDUINO_ARCH_ESP32C3 -constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms -#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling -#else -constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms -//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms -//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms -//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms -#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling -//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling -//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling -//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling -#endif - -// FFT Constants -constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 -constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. -// the following are observed values, supported by a bit of "educated guessing" -//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels -#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels -#define LOG_256 5.54517744f // log(256) - -// These are the input and output vectors. Input vectors receive computed results from FFT. -static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins -static float vImag[samplesFFT] = {0.0f}; // imaginary parts - -// Create FFT object -// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 2.0.1 -// these options actually cause slow-downs on all esp32 processors, don't use them. -// #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 -// #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 -// Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() -#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 -#define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 - -#include - -/* Create FFT object with weighing factor storage */ -static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); - -// Helper functions - -// float version of map() -static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} - -// compute average of several FFT result bins -static float fftAddAvg(int from, int to) { - float result = 0.0f; - for (int i = from; i <= to; i++) { - result += vReal[i]; - } - return result / float(to - from + 1); -} - -// -// FFT main task -// -void FFTcode(void * parameter) -{ - DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); - - // see https://www.freertos.org/vtaskdelayuntil.html - const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; - - TickType_t xLastWakeTime = xTaskGetTickCount(); - for(;;) { - delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. - // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. - - // Don't run FFT computing code if we're in Receive mode or in realtime mode - if (disableSoundProcessing || (audioSyncEnabled & 0x02)) { - vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers - continue; - } - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - uint64_t start = esp_timer_get_time(); - bool haveDoneFFT = false; // indicates if second measurement (FFT time) is valid -#endif - - // get a fresh batch of samples from I2S - if (audioSource) audioSource->getSamples(vReal, samplesFFT); - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - if (start < esp_timer_get_time()) { // filter out overflows - uint64_t sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding - sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth - } - start = esp_timer_get_time(); // start measuring FFT time -#endif - - xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay - - // band pass filter - can reduce noise floor by a factor of 50 - // downside: frequencies below 100Hz will be ignored - if (useBandPassFilter) runMicFilter(samplesFFT, vReal); - - // find highest sample in the batch - float maxSample = 0.0f; // max sample from FFT batch - for (int i=0; i < samplesFFT; i++) { - // set imaginary parts to 0 - vImag[i] = 0; - // pick our our current mic sample - we take the max value from all samples that go into FFT - if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts - if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); - } - // release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function - // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. - micDataReal = maxSample; - -#ifdef SR_DEBUG - if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization -#else - if (sampleAvg > 0.25f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed. -#endif - - // run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2) - FFT.dcRemoval(); // remove DC offset - FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy - //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection - FFT.compute( FFTDirection::Forward ); // Compute FFT - FFT.complexToMagnitude(); // Compute magnitudes - vReal[0] = 0.0f; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. - - FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant - FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - haveDoneFFT = true; -#endif - - } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. - memset(vReal, 0, sizeof(vReal)); - FFT_MajorPeak = 1.0f; - FFT_Magnitude = 0.001f; - } - - for (int i = 0; i < samplesFFT; i++) { - float t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way - vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max. - } // for() - - // mapping of FFT result bins to frequency channels - if (fabsf(sampleAvg) > 0.5f) { // noise gate open -#if 0 - /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. - * - * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. - * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. - * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins. - * End frequency = Start frequency * multiplier ^ 16 - * Multiplier = (End frequency/ Start frequency) ^ 1/16 - * Multiplier = 1.320367784 - */ // Range - fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 - fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 - fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 - fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 - fftCalc[ 4] = fftAddAvg(9,12); // 180 - 260 - fftCalc[ 5] = fftAddAvg(12,16); // 240 - 340 - fftCalc[ 6] = fftAddAvg(16,21); // 320 - 440 - fftCalc[ 7] = fftAddAvg(21,29); // 420 - 600 - fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 - fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 - fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 - fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 - fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 - fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 - fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 - fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate -#else - /* new mapping, optimized for 22050 Hz by softhack007 */ - // bins frequency range - if (useBandPassFilter) { - // skip frequencies below 100hz - fftCalc[ 0] = 0.8f * fftAddAvg(3,4); - fftCalc[ 1] = 0.9f * fftAddAvg(4,5); - fftCalc[ 2] = fftAddAvg(5,6); - fftCalc[ 3] = fftAddAvg(6,7); - // don't use the last bins from 206 to 255. - fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping - } else { - fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass - fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass - fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass - fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange - // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) - fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping - } - fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange - fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange - fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange - fftCalc[ 7] = fftAddAvg(19,26); // 7 818 - 1120 midrange -- 1Khz should always be the center ! - fftCalc[ 8] = fftAddAvg(26,33); // 7 1120 - 1421 midrange - fftCalc[ 9] = fftAddAvg(33,44); // 9 1421 - 1895 midrange - fftCalc[10] = fftAddAvg(44,56); // 12 1895 - 2412 midrange + high mid - fftCalc[11] = fftAddAvg(56,70); // 14 2412 - 3015 high mid - fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid - fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid - fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping -#endif - } else { // noise gate closed - just decay old values - for (int i=0; i < NUM_GEQ_CHANNELS; i++) { - fftCalc[i] *= 0.85f; // decay to zero - if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; - } - } - - // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) - postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS); - -#if defined(WLED_DEBUG) || defined(SR_DEBUG) - if (haveDoneFFT && (start < esp_timer_get_time())) { // filter out overflows - uint64_t fftTimeInMillis = ((esp_timer_get_time() - start) +5ULL) / 10ULL; // "+5" to ensure proper rounding - fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth - } -#endif - // run peak detection - autoResetPeak(); - detectSamplePeak(); - - #if !defined(I2S_GRAB_ADC1_COMPLETELY) - if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC - #endif - vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers - - } // for(;;)ever -} // FFTcode() task end - - -/////////////////////////// -// Pre / Postprocessing // -/////////////////////////// - -static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // pre-filtering of raw samples (band-pass) -{ - // low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency - //constexpr float alpha = 0.04f; // 150Hz - //constexpr float alpha = 0.03f; // 110Hz - constexpr float alpha = 0.0225f; // 80hz - //constexpr float alpha = 0.01693f;// 60hz - // high frequency cutoff parameter - //constexpr float beta1 = 0.75f; // 11Khz - //constexpr float beta1 = 0.82f; // 15Khz - //constexpr float beta1 = 0.8285f; // 18Khz - constexpr float beta1 = 0.85f; // 20Khz - - constexpr float beta2 = (1.0f - beta1) / 2.0f; - static float last_vals[2] = { 0.0f }; // FIR high freq cutoff filter - static float lowfilt = 0.0f; // IIR low frequency cutoff filter - - for (int i=0; i < numSamples; i++) { - // FIR lowpass, to remove high frequency noise - float highFilteredSample; - if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes - else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array - last_vals[1] = last_vals[0]; - last_vals[0] = sampleBuffer[i]; - sampleBuffer[i] = highFilteredSample; - // IIR highpass, to remove low frequency noise - lowfilt += alpha * (sampleBuffer[i] - lowfilt); - sampleBuffer[i] = sampleBuffer[i] - lowfilt; - } -} - -static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // post-processing and post-amp of GEQ channels -{ - for (int i=0; i < numberOfChannels; i++) { - - if (noiseGateOpen) { // noise gate open - // Adjustment for frequency curves. - fftCalc[i] *= fftResultPink[i]; - if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function - // Manual linear adjustment of gain using sampleGain adjustment for different input types. - fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment - if(fftCalc[i] < 0) fftCalc[i] = 0.0f; - } - - // smooth results - rise fast, fall slower - if (fftCalc[i] > fftAvg[i]) fftAvg[i] = fftCalc[i]*0.75f + 0.25f*fftAvg[i]; // rise fast; will need approx 2 cycles (50ms) for converging against fftCalc[i] - else { // fall slow - if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero - else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero - else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero - else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero - } - // constrain internal vars - just to be sure - fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); - fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); - - float currentResult; - if(limiterOn == true) - currentResult = fftAvg[i]; - else - currentResult = fftCalc[i]; - - switch (FFTScalingMode) { - case 1: - // Logarithmic scaling - currentResult *= 0.42f; // 42 is the answer ;-) - currentResult -= 8.0f; // this skips the lowest row, giving some room for peaks - if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function - else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined - currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0.0f, LOG_256, 0.0f, 255.0f); // map [log(1) ... log(255)] to [0 ... 255] - break; - case 2: - // Linear scaling - currentResult *= 0.30f; // needs a bit more damping, get stay below 255 - currentResult -= 4.0f; // giving a bit more room for peaks (WLEDMM uses -2) - if (currentResult < 1.0f) currentResult = 0.0f; - currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies - break; - case 3: - // square root scaling - currentResult *= 0.38f; - currentResult -= 6.0f; - if (currentResult > 1.0f) currentResult = sqrtf(currentResult); - else currentResult = 0.0f; // special handling, because sqrt(0) = undefined - currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0.0f, 16.0f, 0.0f, 255.0f); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] - break; - - case 0: - default: - // no scaling - leave freq bins as-is - currentResult -= 4; // just a bit more room for peaks (WLEDMM uses -2) - break; - } - - // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. - if (soundAgc > 0) { // apply extra "GEQ Gain" if set by user - float post_gain = (float)inputLevel/128.0f; - if (post_gain < 1.0f) post_gain = ((post_gain -1.0f) * 0.8f) +1.0f; - currentResult *= post_gain; - } - fftResult[i] = constrain((int)currentResult, 0, 255); - } -} -//////////////////// -// Peak detection // -//////////////////// - -// peak detection is called from FFT task when vReal[] contains valid FFT results -static void detectSamplePeak(void) { - bool havePeak = false; - // softhack007: this code continuously triggers while amplitude in the selected bin is above a certain threshold. So it does not detect peaks - it detects high activity in a frequency bin. - // Poor man's beat detection by seeing if sample > Average + some value. - // This goes through ALL of the 255 bins - but ignores stupid settings - // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. - if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { - havePeak = true; - } - - if (havePeak) { - samplePeak = true; - timeOfPeak = millis(); - udpSamplePeak = true; - } -} - -#endif - -static void autoResetPeak(void) { - uint16_t MinShowDelay = MAX(50, WS2812FX::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. - samplePeak = false; - if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData - } -} - - -//////////////////// -// usermod class // -//////////////////// - -//class name. Use something descriptive and leave the ": public Usermod" part :) -class AudioReactive : public Usermod { - - private: -#ifdef ARDUINO_ARCH_ESP32 - - #ifndef AUDIOPIN - int8_t audioPin = -1; - #else - int8_t audioPin = AUDIOPIN; - #endif - #ifndef SR_DMTYPE // I2S mic type - uint8_t dmType = 1; // 0=none/disabled/analog; 1=generic I2S - #define SR_DMTYPE 1 // default type = I2S - #else - uint8_t dmType = SR_DMTYPE; - #endif - #ifndef I2S_SDPIN // aka DOUT - int8_t i2ssdPin = 32; - #else - int8_t i2ssdPin = I2S_SDPIN; - #endif - #ifndef I2S_WSPIN // aka LRCL - int8_t i2swsPin = 15; - #else - int8_t i2swsPin = I2S_WSPIN; - #endif - #ifndef I2S_CKPIN // aka BCLK - int8_t i2sckPin = 14; /*PDM: set to I2S_PIN_NO_CHANGE*/ - #else - int8_t i2sckPin = I2S_CKPIN; - #endif - #ifndef MCLK_PIN - int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ - #else - int8_t mclkPin = MCLK_PIN; - #endif -#endif - - // new "V2" audiosync struct - 44 Bytes - struct __attribute__ ((packed)) audioSyncPacket { // "packed" ensures that there are no additional gaps - char header[6]; // 06 Bytes offset 0 - uint8_t reserved1[2]; // 02 Bytes, offset 6 - gap required by the compiler - not used yet - float sampleRaw; // 04 Bytes offset 8 - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting - float sampleSmth; // 04 Bytes offset 12 - either "sampleAvg" or "sampleAgc" depending on soundAgc setting - uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude - uint8_t reserved2; // 01 Bytes offset 17 - for future extensions - not used yet - uint8_t fftResult[16]; // 16 Bytes offset 18 - uint16_t reserved3; // 02 Bytes, offset 34 - gap required by the compiler - not used yet - float FFT_Magnitude; // 04 Bytes offset 36 - float FFT_MajorPeak; // 04 Bytes offset 40 - }; - - // old "V1" audiosync struct - 83 Bytes payload, 88 bytes total (with padding added by compiler) - for backwards compatibility - struct audioSyncPacket_v1 { - char header[6]; // 06 Bytes - uint8_t myVals[32]; // 32 Bytes - int sampleAgc; // 04 Bytes - int sampleRaw; // 04 Bytes - float sampleAvg; // 04 Bytes - bool samplePeak; // 01 Bytes - uint8_t fftResult[16]; // 16 Bytes - double FFT_Magnitude; // 08 Bytes - double FFT_MajorPeak; // 08 Bytes - }; - - constexpr static unsigned UDPSOUND_MAX_PACKET = MAX(sizeof(audioSyncPacket), sizeof(audioSyncPacket_v1)); - - // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) - #ifdef UM_AUDIOREACTIVE_ENABLE - bool enabled = true; - #else - bool enabled = false; - #endif - - bool initDone = false; - bool addPalettes = false; - int8_t palettes = 0; - - // variables for UDP sound sync - WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!) - unsigned long lastTime = 0; // last time of running UDP Microphone Sync - const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED - uint16_t audioSyncPort= 11988;// default port for UDP sound sync - - bool updateIsRunning = false; // true during OTA. - -#ifdef ARDUINO_ARCH_ESP32 - // used for AGC - int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) - float control_integrated = 0.0f; // persistent across calls to agcAvg(); "integrator control" = accumulated error - // variables used by getSample() and agcAvg() - int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed - float sampleMax = 0.0f; // Max sample over a few seconds. Needed for AGC controller. - float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller - float expAdjF = 0.0f; // Used for exponential filter. - float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. - int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) - int16_t rawSampleAgc = 0; // not smoothed AGC sample -#endif - - // variables used in effects - float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample - int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc - float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc - - // used to feed "Info" Page - unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket - int receivedFormat = 0; // last received UDP sound sync format - 0=none, 1=v1 (0.13.x), 2=v2 (0.14.x) - float maxSample5sec = 0.0f; // max sample (after AGC) in last 5 seconds - unsigned long sampleMaxTimer = 0; // last time maxSample5sec was reset - #define CYCLE_SAMPLEMAX 3500 // time window for merasuring - - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; - static const char _config[]; - static const char _dynamics[]; - static const char _frequency[]; - static const char _inputLvl[]; -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - static const char _analogmic[]; -#endif - static const char _digitalmic[]; - static const char _addPalettes[]; - static const char UDP_SYNC_HEADER[]; - static const char UDP_SYNC_HEADER_v1[]; - - // private methods - void removeAudioPalettes(void); - void createAudioPalettes(void); - CRGB getCRGBForBand(int x, int pal); - void fillAudioPalettes(void); - - //////////////////// - // Debug support // - //////////////////// - void logAudio() - { - if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable - #ifdef MIC_LOGGER - // Debugging functions for audio input and sound processing. Comment out the values you want to see - PLOT_PRINT("micReal:"); PLOT_PRINT(micDataReal); PLOT_PRINT("\t"); - PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth); PLOT_PRINT("\t"); - //PLOT_PRINT("volumeRaw:"); PLOT_PRINT(volumeRaw); PLOT_PRINT("\t"); - PLOT_PRINT("DC_Level:"); PLOT_PRINT(micLev); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleAgc:"); PLOT_PRINT(sampleAgc); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleAvg:"); PLOT_PRINT(sampleAvg); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleReal:"); PLOT_PRINT(sampleReal); PLOT_PRINT("\t"); - #ifdef ARDUINO_ARCH_ESP32 - //PLOT_PRINT("micIn:"); PLOT_PRINT(micIn); PLOT_PRINT("\t"); - //PLOT_PRINT("sample:"); PLOT_PRINT(sample); PLOT_PRINT("\t"); - //PLOT_PRINT("sampleMax:"); PLOT_PRINT(sampleMax); PLOT_PRINT("\t"); - //PLOT_PRINT("samplePeak:"); PLOT_PRINT((samplePeak!=0) ? 128:0); PLOT_PRINT("\t"); - //PLOT_PRINT("multAgc:"); PLOT_PRINT(multAgc, 4); PLOT_PRINT("\t"); - #endif - PLOT_PRINTLN(); - #endif - - #ifdef FFT_SAMPLING_LOG - #if 0 - for(int i=0; i maxVal) maxVal = fftResult[i]; - if(fftResult[i] < minVal) minVal = fftResult[i]; - } - for(int i = 0; i < NUM_GEQ_CHANNELS; i++) { - PLOT_PRINT(i); PLOT_PRINT(":"); - PLOT_PRINTF("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1)); - } - if(printMaxVal) { - PLOT_PRINTF("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0)); - } - if(printMinVal) { - PLOT_PRINTF("%04d:minVal ", minVal); // printed with value first, then label, so negative values can be seen in Serial Monitor but don't throw off y axis in Serial Plotter - } - if(mapValuesToPlotterSpace) - PLOT_PRINTF("max:%04d ", (printMaxVal ? 17 : 16)*256); // print line above the maximum value we expect to see on the plotter to avoid autoscaling y axis - else { - PLOT_PRINTF("max:%04d ", 256); - } - PLOT_PRINTLN(); - #endif // FFT_SAMPLING_LOG - } // logAudio() - - -#ifdef ARDUINO_ARCH_ESP32 - ////////////////////// - // Audio Processing // - ////////////////////// - - /* - * A "PI controller" multiplier to automatically adjust sound sensitivity. - * - * A few tricks are implemented so that sampleAgc does't only utilize 0% and 100%: - * 0. don't amplify anything below squelch (but keep previous gain) - * 1. gain input = maximum signal observed in the last 5-10 seconds - * 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal - * 3. the amplification depends on signal level: - * a) normal zone - very slow adjustment - * b) emergency zone (<10% or >90%) - very fast adjustment - */ - void agcAvg(unsigned long the_time) - { - const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function - - float lastMultAgc = multAgc; // last multiplier used - float multAgcTemp = multAgc; // new multiplier - float tmpAgc = sampleReal * multAgc; // what-if amplified signal - - float control_error; // "control error" input for PI control - - if (last_soundAgc != soundAgc) control_integrated = 0.0f; // new preset - reset integrator - - // For PI controller, we need to have a constant "frequency" - // so let's make sure that the control loop is not running at insane speed - static unsigned long last_time = 0; - unsigned long time_now = millis(); - if ((the_time > 0) && (the_time < time_now)) time_now = the_time; // allow caller to override my clock - - if (time_now - last_time > 2) { - last_time = time_now; - - if ((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { - // MIC signal is "squelched" - deliver silence - tmpAgc = 0; - // we need to "spin down" the intgrated error buffer - if (fabs(control_integrated) < 0.01f) control_integrated = 0.0f; - else control_integrated *= 0.91f; - } else { - // compute new setpoint - if (tmpAgc <= agcTarget0Up[AGC_preset]) - multAgcTemp = agcTarget0[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = first setpoint - else - multAgcTemp = agcTarget1[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = second setpoint - } - // limit amplification - if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; - if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; - - // compute error terms - control_error = multAgcTemp - lastMultAgc; - - if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping - && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) - control_integrated += control_error * 0.002f * 0.25f; // 2ms = integration time; 0.25 for damping - else - control_integrated *= 0.9f; // spin down that beasty integrator - - // apply PI Control - tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain - if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower energy zone - multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; - multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; - } else { // "normal zone" - multAgcTemp = lastMultAgc + agcFollowSlow[AGC_preset] * agcControlKp[AGC_preset] * control_error; - multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; - } - - // limit amplification again - PI controller sometimes "overshoots" - //multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32 - if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; - if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; - } - - // NOW finally amplify the signal - tmpAgc = sampleReal * multAgcTemp; // apply gain to signal - if (fabsf(sampleReal) < 2.0f) tmpAgc = 0.0f; // apply squelch threshold - //tmpAgc = constrain(tmpAgc, 0, 255); - if (tmpAgc > 255) tmpAgc = 255.0f; // limit to 8bit - if (tmpAgc < 1) tmpAgc = 0.0f; // just to be sure - - // update global vars ONCE - multAgc, sampleAGC, rawSampleAgc - multAgc = multAgcTemp; - rawSampleAgc = 0.8f * tmpAgc + 0.2f * (float)rawSampleAgc; - // update smoothed AGC sample - if (fabsf(tmpAgc) < 1.0f) - sampleAgc = 0.5f * tmpAgc + 0.5f * sampleAgc; // fast path to zero - else - sampleAgc += agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path - - sampleAgc = fabsf(sampleAgc); // // make sure we have a positive value - last_soundAgc = soundAgc; - } // agcAvg() - - // post-processing and filtering of MIC sample (micDataReal) from FFTcode() - void getSample() - { - float sampleAdj; // Gain adjusted sample value - float tmpSample; // An interim sample variable used for calculations. - const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release. - const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function - - #ifdef WLED_DISABLE_SOUND - micIn = inoise8(millis(), millis()); // Simulated analog read - micDataReal = micIn; - #else - #ifdef ARDUINO_ARCH_ESP32 - micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4; - #else - // this is the minimal code for reading analog mic input on 8266. - // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. - static unsigned long lastAnalogTime = 0; - static float lastAnalogValue = 0.0f; - if (millis() - lastAnalogTime > 20) { - micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. - lastAnalogTime = millis(); - lastAnalogValue = micDataReal; - yield(); - } else micDataReal = lastAnalogValue; - micIn = int(micDataReal); - #endif - #endif - - micLev += (micDataReal-micLev) / 12288.0f; - if (micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align micLev to lowest input signal - - micIn -= micLev; // Let's center it to 0 now - // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. - float micInNoDC = fabsf(micDataReal - micLev); - expAdjF = (weighting * micInNoDC + (1.0f-weighting) * expAdjF); - expAdjF = fabsf(expAdjF); // Now (!) take the absolute value - - expAdjF = (expAdjF <= soundSquelch) ? 0.0f : expAdjF; // simple noise gate - if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0.0f; // do something meaningfull when "squelch = 0" - - tmpSample = expAdjF; - micIn = abs(micIn); // And get the absolute value of each sample - - sampleAdj = tmpSample * sampleGain * inputLevel / 5120.0f /* /40 /128 */ + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment - sampleReal = tmpSample; - - sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? - sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! - - // keep "peak" sample, but decay value if current sample is below peak - if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { - sampleMax += 0.5f * (sampleReal - sampleMax); // new peak - with some filtering - // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume - if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { - samplePeak = true; - timeOfPeak = millis(); - udpSamplePeak = true; - } - } else { - if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0)) - sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly - else - sampleMax *= agcSampleDecay[AGC_preset]; // signal to zero --> 5-8sec - } - if (sampleMax < 0.5f) sampleMax = 0.0f; - - sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. - sampleAvg = fabsf(sampleAvg); // make sure we have a positive value - } // getSample() - -#endif - - /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). - * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) - */ - // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) - void limitSampleDynamics(void) { - const float bigChange = 196.0f; // just a representative number - a large, expected sample value - static unsigned long last_time = 0; - static float last_volumeSmth = 0.0f; - - if (limiterOn == false) return; - - long delta_time = millis() - last_time; - delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up - float deltaSample = volumeSmth - last_volumeSmth; - - if (attackTime > 0) { // user has defined attack time > 0 - float maxAttack = bigChange * float(delta_time) / float(attackTime); - if (deltaSample > maxAttack) deltaSample = maxAttack; - } - if (decayTime > 0) { // user has defined decay time > 0 - float maxDecay = - bigChange * float(delta_time) / float(decayTime); - if (deltaSample < maxDecay) deltaSample = maxDecay; - } - - volumeSmth = last_volumeSmth + deltaSample; - - last_volumeSmth = volumeSmth; - last_time = millis(); - } - - - ////////////////////// - // UDP Sound Sync // - ////////////////////// - - // try to establish UDP sound sync connection - void connectUDPSoundSync(void) { - // This function tries to establish a UDP sync connection if needed - // necessary as we also want to transmit in "AP Mode", but the standard "connected()" callback only reacts on STA connection - static unsigned long last_connection_attempt = 0; - - if ((audioSyncPort <= 0) || ((audioSyncEnabled & 0x03) == 0)) return; // Sound Sync not enabled - if (udpSyncConnected) return; // already connected - if (!(apActive || interfacesInited)) return; // neither AP nor other connections availeable - if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds - if (updateIsRunning) return; - - // if we arrive here, we need a UDP connection but don't have one - last_connection_attempt = millis(); - connected(); // try to start UDP - } - -#ifdef ARDUINO_ARCH_ESP32 - void transmitAudioData() - { - //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); - - audioSyncPacket transmitData; - memset(reinterpret_cast(&transmitData), 0, sizeof(transmitData)); // make sure that the packet - including "invisible" padding bytes added by the compiler - is fully initialized - - strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); - // transmit samples that were not modified by limitSampleDynamics() - transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; - transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg; - transmitData.samplePeak = udpSamplePeak ? 1:0; - udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it - - for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { - transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); - } - - transmitData.FFT_Magnitude = my_magnitude; - transmitData.FFT_MajorPeak = FFT_MajorPeak; - -#ifndef WLED_DISABLE_ESPNOW - if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { - EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; - //DEBUGSR_PRINTLN(F("ESP-NOW Sending audio packet.")); - size_t packetSize = sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) + sizeof(transmitData); - memcpy(buffer.data, &transmitData, sizeof(transmitData)); - quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize); - } -#endif - - if (udpSyncConnected && fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error - fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); - fftUdp.endPacket(); - } - return; - } // transmitAudioData() - -#endif - - static inline bool isValidUdpSyncVersion(const char *header) { - return strncmp_P(header, UDP_SYNC_HEADER, 6) == 0; - } - static inline bool isValidUdpSyncVersion_v1(const char *header) { - return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0; - } - - void decodeAudioData(int packetSize, uint8_t *fftBuff) { - audioSyncPacket receivedPacket; - memset(&receivedPacket, 0, sizeof(receivedPacket)); // start clean - memcpy(&receivedPacket, fftBuff, min((unsigned)packetSize, (unsigned)sizeof(receivedPacket))); // don't violate alignment - thanks @willmmiles# - - // update samples for effects - volumeSmth = fmaxf(receivedPacket.sampleSmth, 0.0f); - volumeRaw = fmaxf(receivedPacket.sampleRaw, 0.0f); -#ifdef ARDUINO_ARCH_ESP32 - // update internal samples - sampleRaw = volumeRaw; - sampleAvg = volumeSmth; - rawSampleAgc = volumeRaw; - sampleAgc = volumeSmth; - multAgc = 1.0f; -#endif - // Only change samplePeak IF it's currently false. - // If it's true already, then the animation still needs to respond. - autoResetPeak(); - if (!samplePeak) { - samplePeak = receivedPacket.samplePeak > 0; - if (samplePeak) timeOfPeak = millis(); - } - //These values are only computed by ESP32 - for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket.fftResult[i]; - my_magnitude = fmaxf(receivedPacket.FFT_Magnitude, 0.0f); - FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - } - - void decodeAudioData_v1(int packetSize, uint8_t *fftBuff) { - audioSyncPacket_v1 *receivedPacket = reinterpret_cast(fftBuff); - // update samples for effects - volumeSmth = fmaxf(receivedPacket->sampleAgc, 0.0f); - volumeRaw = volumeSmth; // V1 format does not have "raw" AGC sample -#ifdef ARDUINO_ARCH_ESP32 - // update internal samples - sampleRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); - sampleAvg = fmaxf(receivedPacket->sampleAvg, 0.0f);; - sampleAgc = volumeSmth; - rawSampleAgc = volumeRaw; - multAgc = 1.0f; -#endif - // Only change samplePeak IF it's currently false. - // If it's true already, then the animation still needs to respond. - autoResetPeak(); - if (!samplePeak) { - samplePeak = receivedPacket->samplePeak > 0; - if (samplePeak) timeOfPeak = millis(); - } - //These values are only available on the ESP32 - for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; - my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); - FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - } - - bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. - { - if (!udpSyncConnected) return false; - bool haveFreshData = false; - - size_t packetSize = fftUdp.parsePacket(); -#ifdef ARDUINO_ARCH_ESP32 - if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET))) fftUdp.flush(); // discard invalid packets (too small or too big) - only works on esp32 -#endif - if ((packetSize > 5) && (packetSize <= UDPSOUND_MAX_PACKET)) { - //DEBUGSR_PRINTLN("Received UDP Sync Packet"); - uint8_t fftBuff[UDPSOUND_MAX_PACKET+1] = { 0 }; // fixed-size buffer for receiving (stack), to avoid heap fragmentation caused by variable sized arrays - fftUdp.read(fftBuff, packetSize); - - // VERIFY THAT THIS IS A COMPATIBLE PACKET - if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { - decodeAudioData(packetSize, fftBuff); - //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2"); - haveFreshData = true; - receivedFormat = 2; - } else { - if (packetSize == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftBuff))) { - decodeAudioData_v1(packetSize, fftBuff); - //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v1"); - haveFreshData = true; - receivedFormat = 1; - } else receivedFormat = 0; // unknown format - } - } - return haveFreshData; - } - - - ////////////////////// - // usermod functions// - ////////////////////// - - public: - //Functions called by WLED or other usermods - - /* - * setup() is called once at boot. WiFi is not yet connected at this point. - * You can use it to initialize variables, sensors or similar. - * It is called *AFTER* readFromConfig() - */ - void setup() override - { - disableSoundProcessing = true; // just to be sure - if (!initDone) { - // usermod exchangeable data - // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers - um_data = new um_data_t; - um_data->u_size = 8; - um_data->u_type = new um_types_t[um_data->u_size]; - um_data->u_data = new void*[um_data->u_size]; - um_data->u_data[0] = &volumeSmth; //*used (New) - um_data->u_type[0] = UMT_FLOAT; - um_data->u_data[1] = &volumeRaw; // used (New) - um_data->u_type[1] = UMT_UINT16; - um_data->u_data[2] = fftResult; //*used (Blurz, DJ Light, Noisemove, GEQ_base, 2D Funky Plank, Akemi) - um_data->u_type[2] = UMT_BYTE_ARR; - um_data->u_data[3] = &samplePeak; //*used (Puddlepeak, Ripplepeak, Waterfall) - um_data->u_type[3] = UMT_BYTE; - um_data->u_data[4] = &FFT_MajorPeak; //*used (Ripplepeak, Freqmap, Freqmatrix, Freqpixels, Freqwave, Gravfreq, Rocktaves, Waterfall) - um_data->u_type[4] = UMT_FLOAT; - um_data->u_data[5] = &my_magnitude; // used (New) - um_data->u_type[5] = UMT_FLOAT; - um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) - um_data->u_type[6] = UMT_BYTE; - um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) - um_data->u_type[7] = UMT_BYTE; - } - - -#ifdef ARDUINO_ARCH_ESP32 - - // Reset I2S peripheral for good measure - i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed - #if !defined(CONFIG_IDF_TARGET_ESP32C3) - delay(100); - periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3 - #endif - delay(100); // Give that poor microphone some time to setup. - - useBandPassFilter = false; - - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if ((i2sckPin == I2S_PIN_NO_CHANGE) && (i2ssdPin >= 0) && (i2swsPin >= 0) && ((dmType == 1) || (dmType == 4)) ) dmType = 5; // dummy user support: SCK == -1 --means--> PDM microphone - #endif - - switch (dmType) { - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) - // stub cases for not-yet-supported I2S modes on other ESP32 chips - case 0: //ADC analog - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) - case 5: //PDM Microphone - #endif - #endif - case 1: - DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); - break; - case 2: - DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); - audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); - break; - case 3: - DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); - break; - case 4: - DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); - break; - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - case 5: - DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/4.0f); - useBandPassFilter = true; // this reduces the noise floor on SPM1423 from 5% Vpp (~380) down to 0.05% Vpp (~5) - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); - break; - #endif - case 6: - DEBUGSR_PRINTLN(F("AR: ES8388 Source")); - audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); - break; - - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - // ADC over I2S is only possible on "classic" ESP32 - case 0: - default: - DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); - audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); - delay(100); - useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog - if (audioSource) audioSource->initialize(audioPin); - break; - #endif - } - delay(250); // give microphone enough time to initialise - - if (!audioSource) enabled = false; // audio failed to initialise -#endif - if (enabled) onUpdateBegin(false); // create FFT task, and initialize network - if (enabled) disableSoundProcessing = false; // all good - enable audio processing -#ifdef ARDUINO_ARCH_ESP32 - if (FFT_Task == nullptr) enabled = false; // FFT task creation failed - if ((!audioSource) || (!audioSource->isInitialized())) { - // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync - #ifdef WLED_DEBUG - DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #else - DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #endif - disableSoundProcessing = true; - } -#endif - if (enabled) connectUDPSoundSync(); - if (enabled && addPalettes) createAudioPalettes(); - initDone = true; - } - - - /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ - void connected() override - { - if (udpSyncConnected) { // clean-up: if open, close old UDP sync connection - udpSyncConnected = false; - fftUdp.stop(); - } - - if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { - #ifdef ARDUINO_ARCH_ESP32 - udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); - #else - udpSyncConnected = fftUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 0, 0, 1), audioSyncPort); - #endif - } - } - - - /* - * loop() is called continuously. Here you can check for events, read sensors, etc. - * - * Tips: - * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. - * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. - * - * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. - * Instead, use a timer check as shown here. - */ - void loop() override - { - static unsigned long lastUMRun = millis(); - - if (!enabled) { - disableSoundProcessing = true; // keep processing suspended (FFT task) - lastUMRun = millis(); // update time keeping - return; - } - // We cannot wait indefinitely before processing audio data - if (WS2812FX::isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice - - // suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET) - if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed - &&( (realtimeMode == REALTIME_MODE_GENERIC) - ||(realtimeMode == REALTIME_MODE_E131) - ||(realtimeMode == REALTIME_MODE_UDP) - ||(realtimeMode == REALTIME_MODE_ADALIGHT) - ||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed - { - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) - if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" - DEBUG_PRINTLN(F("[AR userLoop] realtime mode active - audio processing suspended.")); - DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); - } - #endif - disableSoundProcessing = true; - } else { - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG) - if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" - DEBUG_PRINTLN(F("[AR userLoop] realtime mode ended - audio processing resumed.")); - DEBUG_PRINTF_P(PSTR(" RealtimeMode = %d; RealtimeOverride = %d\n"), int(realtimeMode), int(realtimeOverride)); - } - #endif - if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping - disableSoundProcessing = false; - } - - if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode - if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode -#ifdef ARDUINO_ARCH_ESP32 - if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source - - - // Only run the sampling code IF we're not in Receive mode or realtime mode - if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { - if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation) - - unsigned long t_now = millis(); // remember current time - int userloopDelay = int(t_now - lastUMRun); - if (lastUMRun == 0) userloopDelay=0; // startup - don't have valid data from last run. - - #ifdef WLED_DEBUG - // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. - // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS - //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { - // DEBUG_PRINTF_P(PSTR("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n"), userloopDelay); - //} - #endif - - // run filters, and repeat in case of loop delays (hick-up compensation) - if (userloopDelay <2) userloopDelay = 0; // minor glitch, no problem - if (userloopDelay >200) userloopDelay = 200; // limit number of filter re-runs - do { - getSample(); // run microphone sampling filters - agcAvg(t_now - userloopDelay); // Calculated the PI adjusted value as sampleAvg - userloopDelay -= 2; // advance "simulated time" by 2ms - } while (userloopDelay > 0); - lastUMRun = t_now; // update time keeping - - // update samples for effects (raw, smooth) - volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; - volumeRaw = (soundAgc) ? rawSampleAgc: sampleRaw; - // update FFTMagnitude, taking into account AGC amplification - my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects - if (soundAgc) my_magnitude *= multAgc; - if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute - - limitSampleDynamics(); - } // if (!disableSoundProcessing) -#endif - - autoResetPeak(); // auto-reset sample peak after strip minShowDelay - if (!udpSyncConnected) udpSamplePeak = false; // reset UDP samplePeak while UDP is unconnected - - connectUDPSoundSync(); // ensure we have a connection - if needed - - // UDP Microphone Sync - receive mode - if ((audioSyncEnabled & 0x02) && udpSyncConnected) { - // Only run the audio listener code if we're in Receive mode - static float syncVolumeSmth = 0; - bool have_new_sample = false; - if (millis() - lastTime > delayMs) { - have_new_sample = receiveAudioData(); - if (have_new_sample) last_UDPTime = millis(); -#ifdef ARDUINO_ARCH_ESP32 - else fftUdp.flush(); // Flush udp input buffers if we haven't read it - avoids hickups in receive mode. Does not work on 8266. -#endif - lastTime = millis(); - } - if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample - else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter - limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups - } - - #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) - static unsigned long lastMicLoggerTime = 0; - if (millis()-lastMicLoggerTime > 20) { - lastMicLoggerTime = millis(); - logAudio(); - } - #endif - - // Info Page: keep max sample from last 5 seconds -#ifdef ARDUINO_ARCH_ESP32 - if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { - sampleMaxTimer = millis(); - maxSample5sec = (0.15f * maxSample5sec) + 0.85f *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing - if (sampleAvg < 1) maxSample5sec = 0; // noise gate - } else { - if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume - } -#else // similar functionality for 8266 receive only - use VolumeSmth instead of raw sample data - if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { - sampleMaxTimer = millis(); - maxSample5sec = (0.15 * maxSample5sec) + 0.85 * volumeSmth; // reset, and start with some smoothing - if (volumeSmth < 1.0f) maxSample5sec = 0; // noise gate - if (maxSample5sec < 0.0f) maxSample5sec = 0; // avoid negative values - } else { - if (volumeSmth >= 1.0f) maxSample5sec = fmaxf(maxSample5sec, volumeRaw); // follow maximum volume - } -#endif - -#ifdef ARDUINO_ARCH_ESP32 - //UDP Microphone Sync - transmit mode - if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { - // Only run the transmit code IF we're in Transmit mode - transmitAudioData(); - lastTime = millis(); - } -#endif - - fillAudioPalettes(); - } - - - bool getUMData(um_data_t **data) override - { - if (!data || !enabled) return false; // no pointer provided by caller or not enabled -> exit - *data = um_data; - return true; - } - -#ifdef ARDUINO_ARCH_ESP32 - void onUpdateBegin(bool init) override - { -#ifdef WLED_DEBUG - fftTime = sampleTime = 0; -#endif - // gracefully suspend FFT task (if running) - disableSoundProcessing = true; - - // reset sound data - micDataReal = 0.0f; - volumeRaw = 0; volumeSmth = 0.0f; - sampleAgc = 0.0f; sampleAvg = 0.0f; - sampleRaw = 0; rawSampleAgc = 0.0f; - my_magnitude = 0.0f; FFT_Magnitude = 0.0f; FFT_MajorPeak = 1.0f; - multAgc = 1.0f; - // reset FFT data - memset(fftCalc, 0, sizeof(fftCalc)); - memset(fftAvg, 0, sizeof(fftAvg)); - memset(fftResult, 0, sizeof(fftResult)); - for(int i=(init?0:1); i don't process audio - updateIsRunning = init; - } -#endif - -#ifdef ARDUINO_ARCH_ESP32 - /** - * handleButton() can be used to override default button behaviour. Returning true - * will prevent button working in a default way. - */ - bool handleButton(uint8_t b) override { - yield(); - // crude way of determining if audio input is analog - // better would be for AudioSource to implement getType() - if (enabled - && dmType == 0 && audioPin>=0 - && (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) - ) { - return true; - } - return false; - } - -#endif - //////////////////////////// - // Settings and Info Page // - //////////////////////////// - - /* - * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. - * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. - * Below it is shown how this could be used for e.g. a light sensor - */ - void addToJsonInfo(JsonObject& root) override - { -#ifdef ARDUINO_ARCH_ESP32 - char myStringBuffer[16]; // buffer for snprintf() - not used yet on 8266 -#endif - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray(FPSTR(_name)); - - String uiDomString = F(""); - infoArr.add(uiDomString); - - if (enabled) { -#ifdef ARDUINO_ARCH_ESP32 - // Input Level Slider - if (disableSoundProcessing == false) { // only show slider when audio processing is running - if (soundAgc > 0) { - infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies - } else { - infoArr = user.createNestedArray(F("Audio Input Level")); - } - uiDomString = F("
"); // - infoArr.add(uiDomString); - } -#endif - // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG - - // current Audio input - infoArr = user.createNestedArray(F("Audio Source")); - if (audioSyncEnabled & 0x02) { - // UDP sound sync - receive mode - infoArr.add(F("UDP sound sync")); - if (udpSyncConnected) { - if (millis() - last_UDPTime < 2500) - infoArr.add(F(" - receiving")); - else - infoArr.add(F(" - idle")); - } else { - infoArr.add(F(" - no connection")); - } -#ifndef ARDUINO_ARCH_ESP32 // substitute for 8266 - } else { - infoArr.add(F("sound sync Off")); - } -#else // ESP32 only - } else { - // Analog or I2S digital input - if (audioSource && (audioSource->isInitialized())) { - // audio source successfully configured - if (audioSource->getType() == AudioSource::Type_I2SAdc) { - infoArr.add(F("ADC analog")); - } else { - infoArr.add(F("I2S digital")); - } - // input level or "silence" - if (maxSample5sec > 1.0f) { - float my_usage = 100.0f * (maxSample5sec / 255.0f); - snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); - infoArr.add(myStringBuffer); - } else { - infoArr.add(F(" - quiet")); - } - } else { - // error during audio source setup - infoArr.add(F("not initialized")); - infoArr.add(F(" - check pin settings")); - } - } - - // Sound processing (FFT and input filters) - infoArr = user.createNestedArray(F("Sound Processing")); - if (audioSource && (disableSoundProcessing == false)) { - infoArr.add(F("running")); - } else { - infoArr.add(F("suspended")); - } - - // AGC or manual Gain - if ((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { - infoArr = user.createNestedArray(F("Manual Gain")); - float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets - infoArr.add(roundf(myGain*100.0f) / 100.0f); - infoArr.add("x"); - } - if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { - infoArr = user.createNestedArray(F("AGC Gain")); - infoArr.add(roundf(multAgc*100.0f) / 100.0f); - infoArr.add("x"); - } -#endif - // UDP Sound Sync status - infoArr = user.createNestedArray(F("UDP Sound Sync")); - if (audioSyncEnabled) { - if (audioSyncEnabled & 0x01) { - infoArr.add(F("send mode")); - if ((udpSyncConnected) && (millis() - lastTime < 2500)) infoArr.add(F(" v2")); - } else if (audioSyncEnabled & 0x02) { - infoArr.add(F("receive mode")); - } - } else - infoArr.add("off"); - if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)"); - if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < 2500)) { - if (receivedFormat == 1) infoArr.add(F(" v1")); - if (receivedFormat == 2) infoArr.add(F(" v2")); - } - - #if defined(WLED_DEBUG) || defined(SR_DEBUG) - #ifdef ARDUINO_ARCH_ESP32 - infoArr = user.createNestedArray(F("Sampling time")); - infoArr.add(float(sampleTime)/100.0f); - infoArr.add(" ms"); - - infoArr = user.createNestedArray(F("FFT time")); - infoArr.add(float(fftTime)/100.0f); - if ((fftTime/100) >= FFT_MIN_CYCLE) // FFT time over budget -> I2S buffer will overflow - infoArr.add("! ms"); - else if ((fftTime/80 + sampleTime/80) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability - infoArr.add(" ms!"); - else - infoArr.add(" ms"); - - DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); - DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime)/100.0f); - #endif - #endif - } - } - - - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void addToJsonState(JsonObject& root) override - { - if (!initDone) return; // prevent crash on boot applyPreset() - JsonObject usermod = root[FPSTR(_name)]; - if (usermod.isNull()) { - usermod = root.createNestedObject(FPSTR(_name)); - } - usermod["on"] = enabled; - } - - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void readFromJsonState(JsonObject& root) override - { - if (!initDone) return; // prevent crash on boot applyPreset() - bool prevEnabled = enabled; - JsonObject usermod = root[FPSTR(_name)]; - if (!usermod.isNull()) { - if (usermod[FPSTR(_enabled)].is()) { - enabled = usermod[FPSTR(_enabled)].as(); - if (prevEnabled != enabled) onUpdateBegin(!enabled); - if (addPalettes) { - // add/remove custom/audioreactive palettes - if (prevEnabled && !enabled) removeAudioPalettes(); - if (!prevEnabled && enabled) createAudioPalettes(); - } - } -#ifdef ARDUINO_ARCH_ESP32 - if (usermod[FPSTR(_inputLvl)].is()) { - inputLevel = min(255,max(0,usermod[FPSTR(_inputLvl)].as())); - } -#endif - } - if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { - // handle removal of custom palettes from JSON call so we don't break things - removeAudioPalettes(); - } - } - - void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 && WS2812FX::customPalettes.size()<10) { - // if palettes were removed during JSON call re-add them - createAudioPalettes(); - } - } - - /* - * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. - * It will be called by WLED when settings are actually saved (for example, LED settings are saved) - * If you want to force saving the current state, use serializeConfig() in your loop(). - * - * CAUTION: serializeConfig() will initiate a filesystem write operation. - * It might cause the LEDs to stutter and will cause flash wear if called too often. - * Use it sparingly and always in the loop, never in network callbacks! - * - * addToConfig() will make your settings editable through the Usermod Settings page automatically. - * - * Usermod Settings Overview: - * - Numeric values are treated as floats in the browser. - * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float - * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and - * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. - * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. - * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a - * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. - * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type - * used in the Usermod when reading the value from ArduinoJson. - * - Pin values can be treated differently from an integer value by using the key name "pin" - * - "pin" can contain a single or array of integer values - * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins - * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) - * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used - * - * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings - * - * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. - * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. - * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED - * - * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! - */ - void addToConfig(JsonObject& root) override - { - JsonObject top = root.createNestedObject(FPSTR(_name)); - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_addPalettes)] = addPalettes; - -#ifdef ARDUINO_ARCH_ESP32 - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); - amic["pin"] = audioPin; - #endif - - JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); - dmic["type"] = dmType; - JsonArray pinArray = dmic.createNestedArray("pin"); - pinArray.add(i2ssdPin); - pinArray.add(i2swsPin); - pinArray.add(i2sckPin); - pinArray.add(mclkPin); - - JsonObject cfg = top.createNestedObject(FPSTR(_config)); - cfg[F("squelch")] = soundSquelch; - cfg[F("gain")] = sampleGain; - cfg[F("AGC")] = soundAgc; - - JsonObject freqScale = top.createNestedObject(FPSTR(_frequency)); - freqScale[F("scale")] = FFTScalingMode; -#endif - - JsonObject dynLim = top.createNestedObject(FPSTR(_dynamics)); - dynLim[F("limiter")] = limiterOn; - dynLim[F("rise")] = attackTime; - dynLim[F("fall")] = decayTime; - - JsonObject sync = top.createNestedObject("sync"); - sync["port"] = audioSyncPort; - sync["mode"] = audioSyncEnabled; - } - - - /* - * readFromConfig() can be used to read back the custom settings you added with addToConfig(). - * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) - * - * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), - * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. - * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) - * - * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) - * - * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present - * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them - * - * This function is guaranteed to be called on boot, but could also be called every time settings are updated - */ - bool readFromConfig(JsonObject& root) override - { - JsonObject top = root[FPSTR(_name)]; - bool configComplete = !top.isNull(); - bool oldEnabled = enabled; - bool oldAddPalettes = addPalettes; - - configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); - configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes); - -#ifdef ARDUINO_ARCH_ESP32 - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); - #else - audioPin = -1; // MCU does not support analog mic - #endif - - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType); - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) - if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) - if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM - #endif - #endif - - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); - - configComplete &= getJsonValue(top[FPSTR(_config)][F("squelch")], soundSquelch); - configComplete &= getJsonValue(top[FPSTR(_config)][F("gain")], sampleGain); - configComplete &= getJsonValue(top[FPSTR(_config)][F("AGC")], soundAgc); - - configComplete &= getJsonValue(top[FPSTR(_frequency)][F("scale")], FFTScalingMode); - - configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("limiter")], limiterOn); - configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("rise")], attackTime); - configComplete &= getJsonValue(top[FPSTR(_dynamics)][F("fall")], decayTime); -#endif - configComplete &= getJsonValue(top["sync"]["port"], audioSyncPort); - configComplete &= getJsonValue(top["sync"]["mode"], audioSyncEnabled); - - if (initDone) { - // add/remove custom/audioreactive palettes - if ((oldAddPalettes && !addPalettes) || (oldAddPalettes && !enabled)) removeAudioPalettes(); - if ((addPalettes && !oldAddPalettes && enabled) || (addPalettes && !oldEnabled && enabled)) createAudioPalettes(); - } // else setup() will create palettes - return configComplete; - } - - - void appendConfigData() override - { -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addOption(dd,'Generic Analog',0);")); - #endif - oappend(SET_F("addOption(dd,'Generic I2S',1);")); - oappend(SET_F("addOption(dd,'ES7243',2);")); - oappend(SET_F("addOption(dd,'SPH0654',3);")); - oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);")); - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) - oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); - #endif - oappend(SET_F("addOption(dd,'ES8388',6);")); - - oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'Normal',1);")); - oappend(SET_F("addOption(dd,'Vivid',2);")); - oappend(SET_F("addOption(dd,'Lazy',3);")); - - oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); - oappend(SET_F("addOption(dd,'Off',0);")); - oappend(SET_F("addOption(dd,'On',1);")); - oappend(SET_F("addInfo('AudioReactive:dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:dynamics:rise',1,'ms (♪ effects only)');")); - oappend(SET_F("addInfo('AudioReactive:dynamics:fall',1,'ms (♪ effects only)');")); - - oappend(SET_F("dd=addDropdown('AudioReactive','frequency:scale');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); - oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); - oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); -#endif - - oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); - oappend(SET_F("addOption(dd,'Off',0);")); -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addOption(dd,'Send',1);")); -#endif - oappend(SET_F("addOption(dd,'Receive',2);")); -#ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'only use -1, 0, 1 or 3','I2S MCLK');")); - #else - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); - #endif -#endif - } - - - /* - * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. - * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. - * Commonly used for custom clocks (Cronixie, 7 segment) - */ - //void handleOverlayDraw() override - //{ - //WS2812FX::setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black - //} - - - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() override - { - return USERMOD_ID_AUDIOREACTIVE; - } -}; - -void AudioReactive::removeAudioPalettes(void) { - DEBUG_PRINTLN(F("Removing audio palettes.")); - while (palettes>0) { - WS2812FX::customPalettes.pop_back(); - DEBUG_PRINTLN(palettes); - palettes--; - } - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); -} - -void AudioReactive::createAudioPalettes(void) { - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(WS2812FX::customPalettes.size()); - if (palettes) return; - DEBUG_PRINTLN(F("Adding audio palettes.")); - for (int i=0; i= palettes) lastCustPalette -= palettes; - for (int pal=0; palgetStart() >= segStopIdx) continue; if (bus->getStart() + bus->getLength() <= segStartIdx) continue; - //uint8_t type = bus->getType(); if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) @@ -1563,7 +1562,7 @@ uint16_t WS2812FX::getLengthPhysical() const { unsigned len = 0; for (size_t b = 0; b < BusManager::getNumBusses(); b++) { Bus *bus = BusManager::getBus(b); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + if (bus->isVirtual()) continue; //exclude non-physical network busses len += bus->getLength(); } return len; From bd7cd32f911d85963ec5aba22bdf3420a8770866 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 22 Sep 2024 13:56:14 +0200 Subject: [PATCH 50/50] Add mandatory refresh capability to remove type dependency. --- wled00/bus_manager.cpp | 2 +- wled00/bus_manager.h | 2 ++ wled00/data/settings_leds.htm | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 3766975f..5b948b9c 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -830,7 +830,7 @@ static String LEDTypesToJson(const std::vector& types) { String json; for (const auto &type : types) { // capabilities follows similar pattern as JSON API - int capabilities = Bus::hasRGB(type.id) | Bus::hasWhite(type.id)<<1 | Bus::hasCCT(type.id)<<2 | Bus::is16bit(type.id)<<4; + int capabilities = Bus::hasRGB(type.id) | Bus::hasWhite(type.id)<<1 | Bus::hasCCT(type.id)<<2 | Bus::is16bit(type.id)<<4 | Bus::mustRefresh(type.id)<<5; char str[256]; sprintf_P(str, PSTR("{i:%d,c:%d,t:\"%s\",n:\"%s\"},"), type.id, capabilities, type.type, type.name); json += str; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 40fe61f4..e96b9de7 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -104,6 +104,7 @@ class Bus { 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; } @@ -142,6 +143,7 @@ class Bus { static constexpr bool isPWM(uint8_t type) { return (type >= TYPE_ANALOG_MIN && type <= TYPE_ANALOG_MAX); } static constexpr bool isVirtual(uint8_t type) { return (type >= TYPE_VIRTUAL_MIN && type <= TYPE_VIRTUAL_MAX); } static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; } + static constexpr bool mustRefresh(uint8_t type) { return type == TYPE_TM1814; } static constexpr int numPWMPins(uint8_t type) { return (type - 40); } static inline int16_t getCCT() { return _cct; } diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 54ba9d8b..dd0e8ee8 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -22,6 +22,7 @@ function hasW(t) { return !!(gT(t).c & 0x02); } // has white channel function hasCCT(t) { return !!(gT(t).c & 0x04); } // is white CCT enabled 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 S() { getLoc(); @@ -255,7 +256,7 @@ d.Sf["LA"+n].min = (isVir(t) || isAna(t)) ? 0 : 1; d.Sf["MA"+n].min = (isVir(t) || isAna(t)) ? 0 : 250; } - gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814 + gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory gRGBW |= hasW(t); // RGBW checkbox gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown @@ -457,9 +458,9 @@ mA/LED: