diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml index 59661ff8..d9933bd3 100644 --- a/.github/workflows/wled-ci.yml +++ b/.github/workflows/wled-ci.yml @@ -30,7 +30,7 @@ jobs: build: - name: Build Environments + name: Builds runs-on: ubuntu-latest needs: get_default_envs strategy: @@ -50,7 +50,7 @@ jobs: uses: actions/cache@v3 with: path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + key: ${{ runner.os }}-${{ matrix.environment}}-${{ hashFiles('platformio.ini') }} - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.gitignore b/.gitignore index 789de0a9..c85fae0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,24 @@ -.pio .cache +.clang-format +.direnv +.DS_Store +.gitignore +.idea +.pio .pioenvs .piolibdeps .vscode -/wled00/Release -/wled00/extLibs -/platformio_override.ini -/wled00/my_config.h -/build_output -.DS_Store -.gitignore -.clang-format -node_modules -.idea -.direnv -wled-update.sh + esp01-update.sh -/wled00/LittleFS +platformio_override.ini replace_fs.py -wled00/wled00.ino.cpp +wled-update.sh + +/build_output/ +/node_modules/ + +/wled00/extLibs +/wled00/LittleFS +/wled00/my_config.h +/wled00/Release +/wled00/wled00.ino.cpp diff --git a/package-lock.json b/package-lock.json index f02ad7c5..da832670 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.0-b27.31", + "version": "0.14.0-b27.32", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.0-b27.31", + "version": "0.14.0-b27.32", "license": "GPL-3.0-or-later", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index db669843..54e922ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0-b27.31", + "version": "0.14.0-b27.32", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 05b0cf86..1e511cf7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -92,7 +92,7 @@ default_envs = src_dir = ./wled00 data_dir = ./wled00/data -build_cache_dir = ~/.buildcache +build_cache_dir = ~/.platformio/buildcache extra_configs = platformio_override.ini @@ -133,12 +133,14 @@ platform_packages = platformio/framework-arduinoespressif8266 # ------------------------------------------------------------------------------ # FLAGS: DEBUG -# +# esp8266 : see https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level +# esp32 : see https://docs.platformio.org/en/latest/platforms/espressif32.html#debug-level # ------------------------------------------------------------------------------ -debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM +debug_flags = -D DEBUG=1 -D WLED_DEBUG + -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM ;; for esp8266 -DARDUINOJSON_DEBUG=1 ;; enables some debug asserts in arduinoJSON -#if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h" -#-DDEBUG_ESP_CORE is not working right now + # if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h" + # -DDEBUG_ESP_CORE is not working right now # ------------------------------------------------------------------------------ # FLAGS: ldscript (available ldscripts at https://github.com/esp8266/Arduino/tree/master/tools/sdk/ld) @@ -269,6 +271,9 @@ build_flags = -DVTABLES_IN_FLASH ; restrict to minimal mime-types -DMIMETYPE_MINIMAL + ; other special-purpose framework flags (see https://docs.platformio.org/en/latest/platforms/espressif8266.html) + ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" + ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown -D USERMOD_AUDIOREACTIVE lib_deps = @@ -456,6 +461,7 @@ lib_deps = ${esp8266.lib_deps} ; board_build.ldscript = ${common.ldscript_1m128k} ; build_unflags = ${common.build_unflags} ; build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA +; ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM ; lib_deps = ${esp8266.lib_deps} [env:esp07] @@ -686,6 +692,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME -DARDUINO_USB_DFU_ON_BOOT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_USE_PSRAM + ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 6792 bytes FLASH -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 -D LEDPIN=16 @@ -908,8 +915,8 @@ build_flags_S = ; -D WLED_USE_CIE_BRIGHTNESS_TABLE ;; experimental: use different color / brightness lookup table -D USERMOD_AUDIOREACTIVE -D UM_AUDIOREACTIVE_USE_NEW_FFT ; use latest (upstream) FFTLib, instead of older library modified by blazoncek. Slightly faster, more accurate, needs 2KB RAM extra - -D USERMOD_ARTIFX ; WLEDMM usermod - ; -D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. + ; -D USERMOD_ARTIFX ;; WLEDMM usermod - temporarily moved into "_M", due to problems in "_S" when compiling with -O2 + ; -D WLEDMM_FASTPATH ;; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions. ; -D WLED_DEBUG_HEAP ;; WLEDMM enable heap debugging ; -D WLED_DISABLE_LOXONE ; -D WLED_DISABLE_ALEXA @@ -919,12 +926,15 @@ build_flags_S = ; -D WLED_ENABLE_DMX lib_deps_S = - https://github.com/kosme/arduinoFFT#develop @ 1.9.2+sha.419d7b0 ;; used for USERMOD_AUDIOREACTIVE - using "known working" hash + ;; https://github.com/kosme/arduinoFFT#develop @ 1.9.2+sha.419d7b0 ;; used for USERMOD_AUDIOREACTIVE - using "known working" hash + https://github.com/softhack007/arduinoFFT.git#develop @ 1.9.2 ;; used for USERMOD_AUDIOREACTIVE - optimized version, 10% faster on -S2/-C3 + animartrix_build_flags = -D USERMOD_ANIMARTRIX ;; WLEDMM usermod: CC BY-NC 3.0 licensed effects by Stefan Petrick -animartrix_lib_deps = https://github.com/netmindz/animartrix.git#f070fefc42febe2de3a2ab5d6d39e78bbc539702 +animartrix_lib_deps = https://github.com/netmindz/animartrix.git#18bf17389e57c69f11bc8d04ebe1d215422c7fb7 build_flags_M = + -D USERMOD_ARTIFX ; WLEDMM usermod - temporarily moved into "_M", due to problems in "_S" when compiling with -O2 -D WLED_MAX_USERMODS=25 ; default only 4-6, also for _XL configs takes 25 pointers in memory ;; -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4 ewowi to softhack: move to build_flags_S? - We need a different solution -D WLED_USE_MY_CONFIG ; include custom my_config.h ewowi to softhack: redundant as also in build_flags_S? @@ -932,6 +942,7 @@ build_flags_M = -D USE_ALT_DISPLAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI -D USERMOD_FOUR_LINE_DISPLAY -D USERMOD_ROTARY_ENCODER_UI + -D USERMOD_AUTO_SAVE ${common_mm.animartrix_build_flags} ;WLEDMM: only setting WLED_DEBUG_HOST is enough, ip and port can be defined in sync settings as well -D WLED_DEBUG_HOST='"192.168.x.x"' ;; to send debug messages over network to host 192.168.x.y - FQDN is also possible @@ -950,7 +961,6 @@ lib_deps_V4_M = ${common_mm.animartrix_lib_deps} build_flags_XL = - -D USERMOD_AUTO_SAVE -D USERMOD_WEATHER ; WLEDMM usermod -D USERMOD_MPU6050_IMU ; gyro/accelero for USERMOD_GAMES (ONLY WORKS IF USERMOD_FOUR_LINE_DISPLAY NOT INCLUDED - I2C SHARING BUG) -D USERMOD_GAMES ; WLEDMM usermod @@ -1502,6 +1512,13 @@ build_flags = ${esp32_4MB_V4_M_base.build_flags} -D WLED_RELEASE_NAME=esp32_16MB_V4_M -D WLED_WATCHDOG_TIMEOUT=0 #-D WLED_DISABLE_BROWNOUT_DET -D ARDUINO_USB_CDC_ON_BOOT=0 ; needed for arduino-esp32 >=2.0.4; avoids errors on startup + ;; -D WLED_ENABLE_DMX ;; disabled because it does not work with ESP-IDF 4.4.x (buggy driver in SparkFunDMX) + -D WLED_ENABLE_DMX_INPUT ;; needs more testing + -D LEDPIN=16 -D RLYPIN=19 + -D AUDIOPIN=-1 -D TEMPERATURE_PIN=23 -D PIR_SENSOR_PIN=-1 -D PWM_PIN=-1 + -D ENCODER_DT_PIN=35 -D ENCODER_CLK_PIN=39 -D ENCODER_SW_PIN=5 ; WLEDMM spec by @SERG74: use 35 and 39 instead of 18 and 19 (conflicts) + -D FLD_PIN_SCL=-1 -D FLD_PIN_SDA=-1 ; use global! + -D HW_PIN_SCL=22 -D HW_PIN_SDA=21 board = esp32_16MB board_build.partitions = tools/WLED_ESP32_16MB.csv ;; WLED standard for 16MB flash: 2MB firmware, 12 MB filesystem ;board_build.partitions = tools/WLED_ESP32_16MB_9MB_FS.csv ;; WLED extended for 16MB flash: 3.2MB firmware, 9 MB filesystem @@ -1800,6 +1817,7 @@ monitor_filters = esp32_exception_decoder ; Flash: [==========] 97.9% (used 1411114 bytes from 1441792 bytes) !!! 98% ;; MM environment for generic ESP32-S2, with PSRAM, 4MB flash (300kB filesystem to have more program space) +;; PINs assignments optimized for use with serg74 "mini shield" [env:esp32s2_PSRAM_M] extends = esp32_4MB_V4_M_base platform = ${esp32.platformV4} ;; more stable on -S2 than 5.1.1 @@ -1811,41 +1829,41 @@ board_build.flash_mode = dio upload_speed = 256000 ;; 921600 build_unflags = ${common.build_unflags} -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_DFU_ON_BOOT=0 -D USERMOD_DALLASTEMPERATURE ;; disabled because it hangs during usermod setup on -S3 (autodetect broken?) -D WLED_ENABLE_DMX ;; disabled because it does not work with ESP-IDF 4.4.x (buggy driver in SparkFunDMX) -D WLED_ENABLE_DMX_INPUT ;; needs more testing build_flags = ${common.build_flags} ${esp32s2.build_flags} - ${Debug_Flags.build_flags} + ;; ${Debug_Flags.build_flags} -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 ${common_mm.build_flags_S} ${common_mm.build_flags_M} -Wno-misleading-indentation -Wno-format-truncation -D WLED_RELEASE_NAME=esp32s2_4MB_M - -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this - -DARDUINO_USB_CDC_ON_BOOT=0 - -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 - -D WLED_DISABLE_ADALIGHT ;; disables serial protocols, as the board only has CDC USB -DBOARD_HAS_PSRAM -D WLED_USE_PSRAM_JSON ;; -D WLED_USE_PSRAM - ; -D WLEDMM_WIFI_POWERON_HACK -DLOLIN_WIFI_FIX ;; use this _only_ if your device is not able to make a WiFI connection! + -DLOLIN_WIFI_FIX -DWLEDMM_WIFI_POWERON_HACK ;; seems to work much better with this + -DARDUINO_USB_CDC_ON_BOOT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=1 + -D WLED_DISABLE_ADALIGHT ;; disables serial protocols, as the board only has CDC USB -D WLED_DISABLE_INFRARED ;; save flash space -D WLED_DISABLE_ALEXA ;; save flash space -D WLED_DISABLE_HUESYNC ;; save flash space -D WLED_DISABLE_LOXONE ;; save flash space - -D LEDPIN=16 - -D BTNPIN=-1 -D RLYPIN=-1 -D IRPIN=-1 - -D HW_PIN_SCL=40 -D HW_PIN_SDA=41 - -D HW_PIN_MOSISPI=35 ;WLEDMM renamed from HW_PIN_DATASPI - -D HW_PIN_CLOCKSPI=36 - -D HW_PIN_MISOSPI=37 -D AUDIOPIN=-1 - -D SR_DMTYPE=1 -D I2S_SDPIN=9 -D I2S_WSPIN=8 -D I2S_CKPIN=17 -D MCLK_PIN=18 + -D BTNPIN=-1 -D IRPIN=-1 + -D LEDPIN=16 ;; second led pin = 18 + -D HW_PIN_SCL=35 -D HW_PIN_SDA=33 + -D RLYPIN=9 + ;; -D HW_PIN_MOSISPI=11 -D HW_PIN_CLOCKSPI=7 -D HW_PIN_MISOSPI=9 ;; 9 already in use for RELAY, 7 for IR + -D SR_DMTYPE=1 -D I2S_SDPIN=34 -D I2S_CKPIN=14 -D I2S_WSPIN=17 -D MCLK_PIN=-1 ;; reommended for mini shield + ;; -D FFTTASK_PRIORITY=2 ;; useful for testing FFT timing. reduces audio latency, but makes effects slower. + ;; -D STATUSLED=15 -D WLED_USE_MY_CONFIG lib_deps = ${esp32s2.lib_deps} ${common_mm.lib_deps_S} ${common_mm.lib_deps_V4_M} lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation OneWire ; not needed as we don't include USERMOD_DALLASTEMPERATURE monitor_filters = esp32_exception_decoder -; RAM: [=== ] 30.9% (used 101228 bytes from 327680 bytes) -; Flash: [======== ] 80.4% (used 1527278 bytes from 1900544 bytes) +; RAM: [=== ] 29.7% (used 97376 bytes from 327680 bytes) +; Flash: [======== ] 81.4% (used 1547834 bytes from 1900544 bytes) ;; MM environment for generic ESP32-C3 -> 4MB flash, no PSRAM @@ -2138,3 +2156,4 @@ build_flags = ${esp32_4MB_V4_S_base.build_flags} ;lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation ; RAM: [=== ] 25.4% (used 83144 bytes from 327680 bytes) ; Flash: [==========] 96.4% (used 1516029 bytes from 1572864 bytes) +; diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index 0cb4bf18..1903240f 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -118,8 +118,8 @@ private: JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device device[F("name")] = serverDescription; device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); - device[F("manufacturer")] = F("WLED"); - device[F("model")] = F("FOSS"); + device[F("manufacturer")] = F(WLED_BRAND); //WLEDMM + Moustachauve/Wled-Native + device[F("model")] = F(WLED_PRODUCT_NAME); //WLEDMM + Moustachauve/Wled-Native device[F("sw_version")] = versionString; String temp; diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index 3b9c80ec..6fe5dba9 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -163,8 +163,8 @@ private: JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device device[F("name")] = serverDescription; device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); - device[F("manufacturer")] = F("WLED"); - device[F("model")] = F("FOSS"); + device[F("manufacturer")] = F(WLED_BRAND); //WLEDMM + Moustachauve/Wled-Native + device[F("model")] = F(WLED_PRODUCT_NAME); //WLEDMM + Moustachauve/Wled-Native device[F("sw_version")] = versionString; String temp; diff --git a/usermods/Battery/usermod_v2_Battery.h b/usermods/Battery/usermod_v2_Battery.h index 3d519724..cd518b9a 100644 --- a/usermods/Battery/usermod_v2_Battery.h +++ b/usermods/Battery/usermod_v2_Battery.h @@ -60,9 +60,9 @@ class UsermodBattery : public Usermod bool initializing = true; // strings to reduce flash memory usage (used more than twice) - static const char _name[]; + // static const char _name[]; static const char _readInterval[]; - static const char _enabled[]; + // static const char _enabled[]; static const char _threshold[]; static const char _preset[]; static const char _duration[]; @@ -117,6 +117,7 @@ class UsermodBattery : public Usermod float readVoltage() { #ifdef ARDUINO_ARCH_ESP32 + if ((batteryPin <0) || !pinManager.isPinAnalog(batteryPin)) return(-1.0f); // WLEDMM avoid reading from invalid pin // use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value return (analogReadMilliVolts(batteryPin) / 1000.0f) * voltageMultiplier + calibration; #else @@ -127,6 +128,7 @@ class UsermodBattery : public Usermod public: //Functions called by WLED + UsermodBattery(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM: this shouldn't be necessary (passthrough of constructor), maybe because Usermod is an abstract class /* * setup() is called once at boot. WiFi is not yet connected at this point. @@ -134,10 +136,11 @@ class UsermodBattery : public Usermod */ void setup() { + if (!enabled) return; // WLEDMM #ifdef ARDUINO_ARCH_ESP32 bool success = false; DEBUG_PRINTLN(F("Allocating battery pin...")); - if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0) + if ((batteryPin >= 0) && (digitalPinToAnalogChannel(batteryPin) >= 0)) if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) { DEBUG_PRINTLN(F("Battery pin allocation succeeded.")); success = true; @@ -145,8 +148,9 @@ class UsermodBattery : public Usermod } if (!success) { - DEBUG_PRINTLN(F("Battery pin allocation failed.")); + USER_PRINTLN(F("Battery pin allocation failed.")); batteryPin = -1; // allocation failed + enabled = false; } else { pinMode(batteryPin, INPUT); } @@ -178,7 +182,13 @@ class UsermodBattery : public Usermod */ void loop() { - if(strip.isUpdating()) return; + // WLEDMM begin + if (!enabled) return; + if (batteryPin < 0) return; + unsigned long currentTime = millis(); // get the current elapsed time + if (strip.isUpdating() && (currentTime - lastTime < 30000)) return; // WLEDMM: be nice + lastTime = currentTime; + // WLEDMM end lowPowerIndicator(); @@ -252,6 +262,7 @@ class UsermodBattery : public Usermod JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); + if (!enabled) return; // WLEDMM if (batteryPin < 0) { JsonArray infoVoltage = user.createNestedArray(F("Battery voltage")); infoVoltage.add(F("n/a")); @@ -360,6 +371,8 @@ class UsermodBattery : public Usermod void addToConfig(JsonObject& root) { JsonObject battery = root.createNestedObject(FPSTR(_name)); // usermodname + battery[F("enabled")] = enabled; // WLEDMM + #ifdef ARDUINO_ARCH_ESP32 battery[F("pin")] = batteryPin; #endif @@ -373,11 +386,11 @@ class UsermodBattery : public Usermod battery[FPSTR(_readInterval)] = readingInterval; JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section - ao[FPSTR(_enabled)] = autoOffEnabled; + ao[F("auto-off-enabled")] = autoOffEnabled; ao[FPSTR(_threshold)] = autoOffThreshold; JsonObject lp = battery.createNestedObject(F("indicator")); // low power section - lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled; + lp[F("indicator-enabled")] = lowPowerIndicatorEnabled; lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset; lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold; lp[FPSTR(_duration)] = lowPowerIndicatorDuration; @@ -436,6 +449,10 @@ class UsermodBattery : public Usermod #endif JsonObject battery = root[FPSTR(_name)]; + + bool configComplete = !battery.isNull(); // WLEDMM + configComplete &= getJsonValue(battery[F("enabled")], enabled, true); // WLEDMM + if (battery.isNull()) { DEBUG_PRINT(FPSTR(_name)); @@ -455,11 +472,11 @@ class UsermodBattery : public Usermod setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval); JsonObject ao = battery[F("auto-off")]; - setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled); + setAutoOffEnabled(ao[F("auto-off-enabled")] | autoOffEnabled); // WLEDMM setAutoOffThreshold(ao[FPSTR(_threshold)] | autoOffThreshold); JsonObject lp = battery[F("indicator")]; - setLowPowerIndicatorEnabled(lp[FPSTR(_enabled)] | lowPowerIndicatorEnabled); + setLowPowerIndicatorEnabled(lp[F("indicator-enabled")] | lowPowerIndicatorEnabled); // WLEDMM setLowPowerIndicatorPreset(lp[FPSTR(_preset)] | lowPowerIndicatorPreset); // dropdown trickery (int)lp["preset"] setLowPowerIndicatorThreshold(lp[FPSTR(_threshold)] | lowPowerIndicatorThreshold); lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10; @@ -490,7 +507,7 @@ class UsermodBattery : public Usermod } #endif - return !battery[FPSTR(_readInterval)].isNull(); + return configComplete && (!battery[FPSTR(_readInterval)].isNull()); // WLEDMM } /* @@ -780,9 +797,9 @@ class UsermodBattery : public Usermod }; // strings to reduce flash memory usage (used more than twice) -const char UsermodBattery::_name[] PROGMEM = "Battery"; +// const char UsermodBattery::_name[] PROGMEM = "Battery"; const char UsermodBattery::_readInterval[] PROGMEM = "interval"; -const char UsermodBattery::_enabled[] PROGMEM = "enabled"; +//const char UsermodBattery::_enabled[] PROGMEM = "enabled"; const char UsermodBattery::_threshold[] PROGMEM = "threshold"; const char UsermodBattery::_preset[] PROGMEM = "preset"; const char UsermodBattery::_duration[] PROGMEM = "duration"; diff --git a/usermods/LDR_Dusk_Dawn_v2/README.md b/usermods/LDR_Dusk_Dawn_v2/README.md new file mode 100644 index 00000000..5e33518a --- /dev/null +++ b/usermods/LDR_Dusk_Dawn_v2/README.md @@ -0,0 +1,26 @@ +# LDR_Dusk_Dawn_v2 +This usermod will obtain readings from a Light Dependent Resistor (LDR) and will turn on/off specific presets based on those readings. This is useful for exterior lighting situations where you want the lights to only be on when it is dark out. + +# Installation +Add "-D USERMOD_LDR_DUSK_DAWN" to your platformio.ini [common] build_flags and build. + +Example: +``` +[common] +build_flags = + -D USERMOD_LDR_DUSK_DAWN # Enable LDR Dusk Dawn Usermod +``` + +# Usermod Settings +Setting | Description | Default +--- | --- | --- +Enabled | Enable/Disable the LDR functionality. | Disabled +LDR Pin | The analog capable pin your LDR is connected to. | 34 +Threshold Minutes | The number of minutes of consistent readings above/below the on/off threshold before the LED state will change. | 5 +Threshold | The analog read value threshold from the LDR. Readings lower than this number will count towards changing the LED state to off. You can see the current LDR reading by going into the info section when LDR functionality is enabled. | 1000 +On Preset | The WLED preset to be used for the LED on state. | 1 +Off Preset | The WLED preset to be used for the LED off state. | 2 + +## Author +[@jeffwdh](https://github.com/jeffwdh) +jeffwdh@tarball.ca 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 new file mode 100644 index 00000000..21f39090 --- /dev/null +++ b/usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h @@ -0,0 +1,126 @@ +#pragma once +#include "wled.h" + +class LDR_Dusk_Dawn_v2 : public Usermod { + private: + // Defaults + bool ldrEnabled = false; + int ldrPin = 34; //A2 on Adafruit Huzzah32 + int ldrThresholdMinutes = 5; // How many minutes of readings above/below threshold until it switches LED state + int ldrThreshold = 1000; // Readings higher than this number will turn off LED. + int ldrOnPreset = 1; // Default "On" Preset + int ldrOffPreset = 2; // Default "Off" Preset + + // Variables + bool ldrEnabledPreviously = false; // Was LDR enabled for the previous check? First check is always no. + int ldrOffCount; // Number of readings above the threshold + int ldrOnCount; // Number of readings below the threshold + int ldrReading; // Last LDR reading + int ldrLEDState; // Current LED on/off state + unsigned long lastMillis = 0; + static const char _name[]; + + public: + void setup() { + } + + void loop() { + // Only update every 10 seconds + if (millis() - lastMillis > 10000) { + if (ldrEnabled == true) { + // Default state is off + if (ldrEnabledPreviously == false) { + applyPreset(ldrOffPreset); + ldrEnabledPreviously = true; + ldrLEDState = 0; + } + + // Get LDR reading and increment counter by number of seconds since last read + ldrReading = analogRead(ldrPin); + if (ldrReading <= ldrThreshold) { + ldrOnCount = ldrOnCount + 10; + ldrOffCount = 0; + } else { + ldrOffCount = ldrOffCount + 10; + ldrOnCount = 0; + } + + if (ldrOnCount >= (ldrThresholdMinutes * 60)) { + ldrOnCount = 0; + // If LEDs were previously off, turn on + if (ldrLEDState == 0) { + applyPreset(ldrOnPreset); + ldrLEDState = 1; + } + } + + if (ldrOffCount >= (ldrThresholdMinutes * 60)) { + ldrOffCount = 0; + // If LEDs were previously on, turn off + if (ldrLEDState == 1) { + applyPreset(ldrOffPreset); + ldrLEDState = 0; + } + } + } else { + // LDR is disabled, reset variables to default + ldrReading = 0; + ldrOnCount = 0; + ldrOffCount = 0; + ldrLEDState = 0; + ldrEnabledPreviously = false; + } + lastMillis = millis(); + } + } + + void addToConfig(JsonObject& root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top["Enabled"] = ldrEnabled; + top["LDR Pin"] = ldrPin; + top["Threshold Minutes"] = ldrThresholdMinutes; + top["Threshold"] = ldrThreshold; + top["On Preset"] = ldrOnPreset; + top["Off Preset"] = ldrOffPreset; + } + + bool readFromConfig(JsonObject& root) { + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + configComplete &= getJsonValue(top["Enabled"], ldrEnabled); + configComplete &= getJsonValue(top["LDR Pin"], ldrPin); + configComplete &= getJsonValue(top["Threshold Minutes"], ldrThresholdMinutes); + configComplete &= getJsonValue(top["Threshold"], ldrThreshold); + configComplete &= getJsonValue(top["On Preset"], ldrOnPreset); + configComplete &= getJsonValue(top["Off Preset"], ldrOffPreset); + return configComplete; + } + + void addToJsonInfo(JsonObject& root) { + // If "u" object does not exist yet we need to create it + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray LDR_Enabled = user.createNestedArray("LDR dusk/dawn enabled"); + LDR_Enabled.add(ldrEnabled); + + JsonArray LDR_Reading = user.createNestedArray("LDR reading"); + LDR_Reading.add(ldrReading); + + JsonArray LDR_State = user.createNestedArray("LDR turned LEDs on"); + LDR_State.add(bool(ldrLEDState)); + + // Optional debug information: + //JsonArray LDR_On_Count = user.createNestedArray("LDR on count"); + //LDR_On_Count.add(ldrOnCount); + + //JsonArray LDR_Off_Count = user.createNestedArray("LDR off count"); + //LDR_Off_Count.add(ldrOffCount); + } + + uint16_t getId() { + return USERMOD_ID_LDR_DUSK_DAWN; + } +}; + +const char LDR_Dusk_Dawn_v2::_name[] PROGMEM = "LDR_Dusk_Dawn_v2"; diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 288edb32..3d69c208 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -189,8 +189,8 @@ private: JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device device[F("name")] = serverDescription; device[F("ids")] = String(F("wled-sensor-")) + mqttClientID; - device[F("mf")] = "WLED"; - device[F("mdl")] = F("FOSS"); + device[F("mf")] = F(WLED_BRAND); //WLEDMM + Moustachauve/Wled-Native + device[F("mdl")] = F(WLED_PRODUCT_NAME); //WLEDMM + Moustachauve/Wled-Native device[F("sw")] = versionString; sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid); diff --git a/usermods/RTC/usermod_rtc.h b/usermods/RTC/usermod_rtc.h index 08b92ef1..a68bf821 100644 --- a/usermods/RTC/usermod_rtc.h +++ b/usermods/RTC/usermod_rtc.h @@ -8,21 +8,25 @@ #define RTC_DELTA 2 // only modify RTC time if delta exceeds this number of seconds -//Connect DS1307 to standard I2C pins (ESP32: GPIO 21 (SDA)/GPIO 22 (SCL)) +//Connect DS1307 or DS3231 to standard I2C pins (ESP32: GPIO 21 (SDA)/GPIO 22 (SCL)) class RTCUsermod : public Usermod { private: unsigned long lastTime = 0; - bool disabled = false; + bool RTCfound = false; // WLEDMM to prevent errors after anabling the usermod (Wire.cpp:526] write(): NULL TX buffer pointer) public: + RTCUsermod(const char *name, bool enabled):Usermod(name, enabled) {} //WLEDMM: this shouldn't be necessary (passthrough of constructor), maybe because Usermod is an abstract class + void setup() { + RTCfound = false; // WLEDMM PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; - if (pins[1].pin < 0 || pins[0].pin < 0) { disabled=true; return; } //WLEDMM bugfix - ensure that "final" GPIO are valid and no "-1" sneaks trough + if (pins[1].pin < 0 || pins[0].pin < 0) { enabled=false; return; } //WLEDMM bugfix - ensure that "final" GPIO are valid and no "-1" sneaks trough + if (!enabled) { DEBUG_PRINTLN(F("RTC usermod not enabled.")); return; } // WLEDMM join hardware I2C if (!pinManager.joinWire()) { // WLEDMM - this allocates global I2C pins, then starts Wire - if not started previously - disabled = true; + enabled = false; return; } @@ -40,20 +44,31 @@ class RTCUsermod : public Usermod { updateLocalTime(); USER_PRINTLN(F("Localtime updated from RTC.")); } else { - if (!RTC.chipPresent()) disabled = true; //don't waste time if H/W error + if (!RTC.chipPresent()) { + enabled = false; //don't waste time if H/W error + USER_PRINTLN(F("RTC board not present.")); + } } + if (enabled) RTCfound = true; // WLEDMM } void loop() { - if (strip.isUpdating()) return; - if (!disabled && toki.isTick()) { + // WLEDMM begin + if (!enabled) return; + if (!RTCfound) return; // WLEDMM + unsigned long currentTime = millis(); // get the current elapsed time + if (strip.isUpdating() && (currentTime - lastTime < 10000)) return; // WLEDMM: be nice + // WLEDMM end + + if (enabled && toki.isTick()) { // WLEDMM time_t t = toki.second(); + lastTime = millis(); // WLEDMM if (abs(t - RTC.get())> RTC_DELTA) { // WLEDMM only consider time diffs > 2 seconds if ( (toki.getTimeSource() == TOKI_TS_NTP) ||( (toki.getTimeSource() != TOKI_TS_NONE) && (toki.getTimeSource() != TOKI_TS_RTC) && (toki.getTimeSource() != TOKI_TS_BAD) && (toki.getTimeSource() != TOKI_TS_UDP_SEC) && (toki.getTimeSource() != TOKI_TS_UDP))) - { // WLEMM update RTC if we have a reliable time source + { // WLEDMM update RTC if we have a reliable time source RTC.set(t); //set RTC to NTP/UI-provided value - WLEDMM allow up to 3 sec deviation USER_PRINTLN(F("RTC updated using localtime.")); } else { diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index c7fd194a..4726ee26 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -21,13 +21,13 @@ * .... */ -#define FFT_PREFER_EXACT_PEAKS // use different FFT wndowing -> results in "sharper" peaks and less "leaking" into other frequencies +#define FFT_PREFER_EXACT_PEAKS // use different FFT windowing -> results in "sharper" peaks and less "leaking" into other frequencies //#define SR_STATS #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 +//#define FFTTASK_PRIORITY 2 // above looptask, below async_tcp +//#define FFTTASK_PRIORITY 4 // above async_tcp #endif #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) @@ -109,7 +109,7 @@ static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() -static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData +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 @@ -123,7 +123,7 @@ static uint16_t decayTime = 300; // int: decay time in milliseconds // peak detection #ifdef ARDUINO_ARCH_ESP32 -static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[]) - no used for 8266 receive-only mode +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) @@ -150,7 +150,7 @@ static uint8_t inputLevel = 128; // UI slider value #endif // user settable options for FFTResult scaling -static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized sqare root +static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root #ifndef SR_FREQ_PROF static uint8_t pinkIndex = 0; // 0: default; 1: line-in; 2: IMNP441 #else @@ -220,7 +220,7 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // static TaskHandle_t FFT_Task = nullptr; // Table of multiplication factors so that we can even out the frequency response. -#define MAX_PINK 10 // 0 = standard, 1= line-in (pink moise only), 2..4 = IMNP441, 5..6 = ICS-43434, ,7=SPM1423, 8..9 = userdef, 10= flat (no pink noise adjustment) +#define MAX_PINK 10 // 0 = standard, 1= line-in (pink noise only), 2..4 = IMNP441, 5..6 = ICS-43434, ,7=SPM1423, 8..9 = userdef, 10= flat (no pink noise adjustment) static const float fftResultPink[MAX_PINK+1][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 }, // 0 default from SR WLED // { 1.30f, 1.32f, 1.40f, 1.46f, 1.52f, 1.57f, 1.68f, 1.80f, 1.89f, 2.00f, 2.11f, 2.21f, 2.30f, 2.39f, 3.09f, 4.34f }, // - Line-In Generic -> pink noise adjustment only @@ -244,7 +244,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = { /* how to make your own profile: * =============================== * preparation: make sure your microphone has direct line-of-sigh with the speaker, 1-2meter distance is best - * Prepare your HiFi equipment: disable all "Sound enhancements" - like Loudness, Equalizer, Bass Boost. Bass/Trebble controls set to middle. + * Prepare your HiFi equipment: disable all "Sound enhancements" - like Loudness, Equalizer, Bass Boost. Bass/Treble controls set to middle. * Your HiFi equipment should receive its audio input from Line-In, SPDIF, HDMI, or another "undistorted" connection (like CDROM). * Try not to use Bluetooth or MP3 when playing the "pink noise" audio. BT-audio and MP3 both perform "acoustic adjustments" that we don't want now. @@ -258,7 +258,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = { * Your own profile: * - Target for each LED bar is 50% to 75% of the max height --> 8(high) x 16(wide) panel means target = 5. 32 x 16 means target = 22. - * - From left to right - count the LEDs in each of the 16 frequency colums (that's why you need the photo). This is the barheight for each channel. + * - From left to right - count the LEDs in each of the 16 frequency columns (that's why you need the photo). This is the barheight for each channel. * - math time! Find the multiplier that will bring each bar to to target. * * in case of square root scale: multiplier = (target * target) / (barheight * barheight) * * in case of linear scale: multiplier = target / barheight @@ -360,7 +360,7 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } -// compute average of several FFT resut bins +// compute average of several FFT result bins // linear average static float fftAddAvgLin(int from, int to) { float result = 0.0f; @@ -430,7 +430,7 @@ void FFTcode(void * parameter) for(uint_fast16_t binInd = 0; binInd < samplesFFT; binInd++) { float binFreq = binInd * binWidth + binWidth/2.0f; if (binFreq > (SAMPLE_RATE * 0.42f)) - binFreq = (SAMPLE_RATE * 0.42f) - 0.25 * (binFreq - (SAMPLE_RATE * 0.42f)); // supress noise and aliasing + binFreq = (SAMPLE_RATE * 0.42f) - 0.25 * (binFreq - (SAMPLE_RATE * 0.42f)); // suppress noise and aliasing pinkFactors[binInd] = sqrtf(binFreq) / pinkcenter; } pinkFactors[0] *= 0.5; // suppress 0-42hz bin @@ -449,9 +449,9 @@ void FFTcode(void * parameter) } #if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) + // timing uint64_t start = esp_timer_get_time(); bool haveDoneFFT = false; // indicates if second measurement (FFT time) is valid - static uint64_t lastCycleStart = 0; static uint64_t lastLastTime = 0; if ((lastCycleStart > 0) && (lastCycleStart < start)) { // filter out overflows @@ -466,6 +466,14 @@ void FFTcode(void * parameter) if (audioSource) audioSource->getSamples(vReal, samplesFFT); #if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) + // debug info in case that stack usage changes + static unsigned int minStackFree = UINT32_MAX; + unsigned int stackFree = uxTaskGetStackHighWaterMark(NULL); + if (minStackFree > stackFree) { + minStackFree = stackFree; + DEBUGSR_PRINTF("|| %-9s min free stack %d\n", pcTaskGetTaskName(NULL), minStackFree); //WLEDMM + } + // timing 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.0; // smooth @@ -474,7 +482,7 @@ void FFTcode(void * parameter) #endif xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay - isFirstRun = !isFirstRun; // toggle throtte + isFirstRun = !isFirstRun; // toggle throttle #ifdef MIC_LOGGER float datMin = 0.0f; @@ -611,7 +619,7 @@ void FFTcode(void * parameter) * * 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 detetermine the bins. + * 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 @@ -665,7 +673,7 @@ void FFTcode(void * parameter) fftCalc[13] = fftAddAvg(86,103); // 18 3704 - 4479 high mid fftCalc[14] = fftAddAvg(104,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping } - else if (freqDist == 1) { //WLEDMM: Rightshft: note ewowi: frequencies in comments are not correct + else if (freqDist == 1) { //WLEDMM: Rightshift: note ewowi: frequencies in comments are not correct if (useInputFilter==1) { // skip frequencies below 100hz fftCalc[ 0] = 0.8f * fftAddAvg(1,1); @@ -709,12 +717,13 @@ void FFTcode(void * parameter) memcpy(fftCalc, lastFftCalc, sizeof(fftCalc)); // restore last "good" channels } - // post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling) + // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) if (pinkIndex > MAX_PINK) pinkIndex = MAX_PINK; //postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS); postProcessFFTResults((fabsf(volumeSmth)>0.25f)? true : false , NUM_GEQ_CHANNELS); // this function modifies fftCalc, fftAvg and fftResult #if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) + // timing static uint64_t lastLastFFT = 0; 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 @@ -766,7 +775,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p // 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]; // spcial handling for last sample in array + 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; @@ -957,7 +966,7 @@ class AudioReactive : public Usermod { float sampleRaw; // 04 Bytes - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting float sampleSmth; // 04 Bytes - either "sampleAvg" or "sampleAgc" depending on soundAgc setting uint8_t samplePeak; // 01 Bytes - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude - uint8_t reserved1; // 01 Bytes - for future extensions - not used yet + uint8_t frameCounter; // 01 Bytes - track duplicate/out of order packets uint8_t fftResult[16]; // 16 Bytes float FFT_Magnitude; // 04 Bytes float FFT_MajorPeak; // 04 Bytes @@ -1001,7 +1010,7 @@ class AudioReactive : public Usermod { // 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 - double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler. + double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controller. double micLev = 0.0; // 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. @@ -1039,7 +1048,7 @@ class AudioReactive : public Usermod { //////////////////// void logAudio() { - if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable + if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio available #ifdef MIC_LOGGER // Debugging functions for audio input and sound processing. Comment out the values you want to see PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth + 256.0f); PLOT_PRINT("\t"); // +256 to move above other lines @@ -1132,13 +1141,13 @@ class AudioReactive : public Usermod { * 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 zome (<10% or >90%) - very fast 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 muliplier used + float lastMultAgc = multAgc; // last multiplier used float multAgcTemp = multAgc; // new multiplier float tmpAgc = sampleReal * multAgc; // what-if amplified signal @@ -1178,13 +1187,13 @@ class AudioReactive : public Usermod { 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.002 * 0.25; // 2ms = intgration time; 0.25 for damping + control_integrated += control_error * 0.002 * 0.25; // 2ms = integration time; 0.25 for damping else - control_integrated *= 0.9; // spin down that beasty integrator + control_integrated *= 0.9; // spin down that integrator beast // 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 emergy zone + if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergency zone multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; } else { // "normal zone" @@ -1192,7 +1201,7 @@ class AudioReactive : public Usermod { multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; } - // limit amplification again - PI controler sometimes "overshoots" + // 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; @@ -1222,7 +1231,7 @@ class AudioReactive : public Usermod { void getSample() { float sampleAdj; // Gain adjusted sample value - float tmpSample; // An interim sample variable used for calculatioins. + float tmpSample; // An interim sample variable used for calculations. #ifdef WLEDMM_FASTPATH constexpr float weighting = 0.35f; // slightly reduced filter strength, to reduce audio latency constexpr float weighting2 = 0.25f; @@ -1398,7 +1407,7 @@ class AudioReactive : public Usermod { 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 + delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> silly lil hick-up float deltaSample = volumeSmth - last_volumeSmth; if (attackTime > 0) { // user has defined attack time > 0 @@ -1434,11 +1443,11 @@ class AudioReactive : public Usermod { receivedFormat = 0; DEBUGSR_PRINTLN(F("AR connectUDPSoundSync(): connection lost, UDP closed.")); } - return; // neither AP nor other connections availeable + return; // neither AP nor other connections available } if (udpSyncConnected) return; // already connected if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds - if (updateIsRunning) return; // don't reconect during OTA + if (updateIsRunning) return; // don't reconnect during OTA // if we arrive here, we need a UDP connection but don't have one last_connection_attempt = millis(); @@ -1448,16 +1457,19 @@ class AudioReactive : public Usermod { void transmitAudioData() { if (!udpSyncConnected) return; + static uint8_t frameCounter = 0; //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 - transmitData.reserved1 = 0; + transmitData.frameCounter = frameCounter; for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); @@ -1470,7 +1482,8 @@ class AudioReactive : public Usermod { fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); fftUdp.endPacket(); } - return; + + frameCounter++; } // transmitAudioData() #endif static bool isValidUdpSyncVersion(const char *header) { @@ -1482,6 +1495,16 @@ class AudioReactive : public Usermod { void decodeAudioData(int packetSize, uint8_t *fftBuff) { audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); + + static uint8_t lastFrameCounter = 0; + if(receivedPacket->frameCounter <= lastFrameCounter && receivedPacket->frameCounter != 0) { // TODO: might need extra checks here + DEBUGSR_PRINTF("Skipping audio frame out of order or duplicated - %u vs %u\n", lastFrameCounter, receivedPacket->frameCounter); + return; + } + else { + lastFrameCounter = receivedPacket->frameCounter; + } + // update samples for effects volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f); volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); @@ -1632,7 +1655,7 @@ class AudioReactive : public Usermod { um_data->u_type[10] = UMT_FLOAT; #else // ESP8266 - // See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explaination of these alternative sources of data + // See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explanation of these alternative sources of data um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) um_data->u_type[6] = UMT_BYTE; @@ -1678,7 +1701,13 @@ class AudioReactive : public Usermod { //useInputFilter = 0; // in case you need to disable low-cut software filtering audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); delay(100); - if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); + // WLEDMM align global pins + if ((sdaPin >= 0) && (i2c_sda < 0)) i2c_sda = sdaPin; // copy usermod prefs into globals (if globals not defined) + if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin; + if (i2c_sda >= 0) sdaPin = -1; // -1 = use global + if (i2c_scl >= 0) sclPin = -1; + + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; case 3: DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); @@ -1710,11 +1739,38 @@ class AudioReactive : public Usermod { break; #endif case 6: - DEBUGSR_PRINTLN(F("AR: ES8388 Source")); + #ifdef use_es8388_mic + DEBUGSR_PRINTLN(F("AR: ES8388 Source (Mic)")); + #else + DEBUGSR_PRINTLN(F("AR: ES8388 Source (Line-In)")); + #endif audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE, 1.0f); //useInputFilter = 0; // to disable low-cut software filtering and restore previous behaviour delay(100); - if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); + // WLEDMM align global pins + if ((sdaPin >= 0) && (i2c_sda < 0)) i2c_sda = sdaPin; // copy usermod prefs into globals (if globals not defined) + if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin; + if (i2c_sda >= 0) sdaPin = -1; // -1 = use global + if (i2c_scl >= 0) sclPin = -1; + + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + case 7: + #ifdef use_wm8978_mic + DEBUGSR_PRINTLN(F("AR: WM8978 Source (Mic)")); + #else + DEBUGSR_PRINTLN(F("AR: WM8978 Source (Line-In)")); + #endif + audioSource = new WM8978Source(SAMPLE_RATE, BLOCK_SIZE, 1.0f); + //useInputFilter = 0; // to disable low-cut software filtering and restore previous behaviour + delay(100); + // WLEDMM align global pins + if ((sdaPin >= 0) && (i2c_sda < 0)) i2c_sda = sdaPin; // copy usermod prefs into globals (if globals not defined) + if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin; + if (i2c_sda >= 0) sdaPin = -1; // -1 = use global + if (i2c_scl >= 0) sclPin = -1; + + 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) @@ -1733,7 +1789,7 @@ class AudioReactive : public Usermod { if (!audioSource) enabled = false; // audio failed to initialise #endif - if (enabled) onUpdateBegin(false); // create FFT task, and initailize network + if (enabled) onUpdateBegin(false); // create FFT task, and initialize network #ifdef ARDUINO_ARCH_ESP32 if (FFT_Task == nullptr) enabled = false; // FFT task creation failed @@ -1758,6 +1814,10 @@ class AudioReactive : public Usermod { DEBUGSR_PRINT(F("AR: init done, enabled = ")); DEBUGSR_PRINTLN(enabled ? F("true.") : F("false.")); USER_FLUSH(); + + #if defined(ARDUINO_ARCH_ESP32) && defined(SR_DEBUG) + DEBUGSR_PRINTF("|| %-9s min free stack %d\n", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); //WLEDMM + #endif } @@ -1788,6 +1848,10 @@ class AudioReactive : public Usermod { DEBUGSR_PRINTLN(udpSyncConnected ? F("AR connected(): UDP: connected to WIFI.") : F("AR connected(): UDP is disconnected (Wifi).")); } } + + #if defined(ARDUINO_ARCH_ESP32) && defined(SR_DEBUG) + DEBUGSR_PRINTF("|| %-9s min free stack %d\n", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); //WLEDMM + #endif } @@ -1844,6 +1908,15 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source + #ifdef SR_DEBUG + // debug info in case that task stack usage changes + static unsigned int minLoopStackFree = UINT32_MAX; + unsigned int stackFree = uxTaskGetStackHighWaterMark(NULL); + if (minLoopStackFree > stackFree) { + minLoopStackFree = stackFree; + DEBUGSR_PRINTF("|| %-9s min free stack %d\n", pcTaskGetTaskName(NULL), minLoopStackFree); //WLEDMM + } + #endif // Only run the sampling code IF we're not in Receive mode or realtime mode if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { @@ -1855,9 +1928,9 @@ class AudioReactive : public Usermod { #if defined(SR_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 + // softhack007 disabled temporarily - avoid serial console spam with MANY LEDs and low FPS //if ((userloopDelay > /*23*/ 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { - //DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); + //DEBUG_PRINTF("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n", userloopDelay); //} #endif @@ -2030,7 +2103,7 @@ class AudioReactive : public Usermod { xTaskCreateUniversal( FFTcode, // Function to implement the task "FFT", // Name of the task - 5000, // Stack size in words + 3592, // Stack size in words // 3592 leaves 800-1024 bytes of task stack free NULL, // Task input parameter FFTTASK_PRIORITY, // Priority of the task &FFT_Task // Task handle @@ -2040,6 +2113,10 @@ class AudioReactive : public Usermod { micDataReal = 0.0f; // just to be sure if (enabled) disableSoundProcessing = false; updateIsRunning = init; + + #if defined(ARDUINO_ARCH_ESP32) && defined(SR_DEBUG) + DEBUGSR_PRINTF("|| %-9s min free stack %d\n", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); //WLEDMM + #endif } #else // reduced function for 8266 @@ -2161,7 +2238,7 @@ class AudioReactive : public Usermod { } else { // Analog or I2S digital input if (audioSource && (audioSource->isInitialized())) { - // audio source sucessfully configured + // audio source successfully configured if (audioSource->getType() == AudioSource::Type_I2SAdc) { infoArr.add(F("ADC analog")); } else { @@ -2336,6 +2413,11 @@ class AudioReactive : public Usermod { JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); dmic[F("type")] = dmType; + // WLEDMM: align with globals I2C pins + if ((dmType == 2) || (dmType == 6)) { // only for ES7243 and ES8388 + if (i2c_sda >= 0) sdaPin = -1; // -1 = use global + if (i2c_scl >= 0) sclPin = -1; // -1 = use global + } JsonArray pinArray = dmic.createNestedArray("pin"); pinArray.add(i2ssdPin); pinArray.add(i2swsPin); @@ -2497,7 +2579,11 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addOption(dd,'ES8388 ☾',6);")); #endif - + #if SR_DMTYPE==7 + oappend(SET_F("addOption(dd,'WM8978 ☾ (⎌)',7);")); + #else + oappend(SET_F("addOption(dd,'WM8978 ☾',7);")); + #endif #ifdef SR_SQUELCH oappend(SET_F("addInfo('AudioReactive:config:squelch',1,'⎌ ")); oappendi(SR_SQUELCH); oappend("');"); // 0 is field type, 1 is actual field #endif diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 3c257d78..2009efbb 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -45,7 +45,7 @@ // benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches" // WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed; // for example if you want to read "analog buttons" -//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up +//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continuously sample analog ADC microphone. WARNING will cause analogRead() lock-up // data type requested from the I2S driver - currently we always use 32bit //#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible @@ -72,7 +72,7 @@ * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. */ -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4)) +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 6)) // fixed in IDF 4.4.5, however arduino-esp32 2.0.14 did an "I2S rollback" to 4.4.4 // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) @@ -126,7 +126,7 @@ class AudioSource { This function needs to take care of anything that needs to be done before samples can be obtained from the microphone. */ - virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) = 0; + virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) = 0; /* Deinitialize Release all resources and deactivate any functionality that is used @@ -163,7 +163,7 @@ class AudioSource { SRate_t _sampleRate; // Microphone sampling rate int _blockSize; // I2S block size bool _initialized; // Gets set to true if initialization is successful - bool _i2sMaster; // when false, ESP32 will be in I2S SLAVE mode (for devices that only operate in MASTER mode). Only workds in newer IDF >= 4.4.x + bool _i2sMaster; // when false, ESP32 will be in I2S SLAVE mode (for devices that only operate in MASTER mode). Only works in newer IDF >= 4.4.x float _sampleScale; // pre-scaling factor for I2S samples I2S_datatype newSampleBuffer[I2S_SAMPLES_MAX+4] = { 0 }; // global buffer for i2s_read }; @@ -205,7 +205,8 @@ class I2SSource : public AudioSource { }; } - virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE) { + DEBUGSR_PRINTLN("I2SSource:: initialize()."); if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) { if (!pinManager.allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) || !pinManager.allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206 @@ -417,26 +418,21 @@ class I2SSource : public AudioSource { }; /* ES7243 Microphone - This is an I2S microphone that requires ininitialization over + This is an I2S microphone that requires initialization over I2C before I2S data can be received */ class ES7243 : public I2SSource { private: // I2C initialization functions for ES7243 void _es7243I2cBegin() { - bool i2c_initialized = Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U); - if (i2c_initialized == false) { - ERRORSR_PRINTLN(F("AR: ES7243 failed to initialize I2C bus driver.")); - } + Wire.setClock(100000); } void _es7243I2cWrite(uint8_t reg, uint8_t val) { -#ifndef ES7243_ADDR - Wire.beginTransmission(0x13); - #define ES7243_ADDR 0x13 // default address -#else + #ifndef ES7243_ADDR + #define ES7243_ADDR 0x13 // default address + #endif Wire.beginTransmission(ES7243_ADDR); -#endif Wire.write((uint8_t)reg); Wire.write((uint8_t)val); uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK @@ -461,76 +457,48 @@ public: _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; }; - void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { - // check that pins are valid - if ((sdaPin < 0) || (sclPin < 0)) { - ERRORSR_PRINTF("\nAR: invalid ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN("ES7243:: initialize();"); - if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined" + // if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too + // ERRORSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); + // return; + // } + if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined" ERRORSR_PRINTF("\nAR: invalid ES7243 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); return; } - if (!pinManager.joinWire()) { // WLEDMM specific: start I2C with globally defined pins + if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); return; } - if ((i2sckPin < 0) || (mclkPin < 0)) { - ERRORSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); - return; - } - - // Reserve SDA and SCL pins of the I2C interface - PinManagerPinType es7243Pins[2] = { { sdaPin, true }, { sclPin, true } }; - if (!pinManager.allocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C)) { - pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C); - ERRORSR_PRINTF("\nAR: Failed to allocate ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } - - pin_ES7243_SDA = sdaPin; - pin_ES7243_SCL = sclPin; - // First route mclk, then configure ADC over I2C, then configure I2S _es7243InitAdc(); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); } void deinitialize() { - // Release SDA and SCL pins of the I2C interface - PinManagerPinType es7243Pins[2] = { { pin_ES7243_SDA, true }, { pin_ES7243_SCL, true } }; - pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C); I2SSource::deinitialize(); } - - private: - int8_t pin_ES7243_SDA; - int8_t pin_ES7243_SCL; }; -/* ES8388 Sound Modude - This is an I2S sound processing unit that requires ininitialization over +/* ES8388 Sound Module + This is an I2S sound processing unit that requires initialization over I2C before I2S data can be received. */ class ES8388Source : public I2SSource { private: // I2C initialization functions for ES8388 void _es8388I2cBegin() { - bool i2c_initialized = Wire.begin(pin_ES8388_SDA, pin_ES8388_SCL, 100000U); - if (i2c_initialized == false) { - ERRORSR_PRINTLN(F("AR: ES8388 failed to initialize I2C bus driver.")); - } + Wire.setClock(100000); } void _es8388I2cWrite(uint8_t reg, uint8_t val) { -#ifndef ES8388_ADDR - Wire.beginTransmission(0x10); - #define ES8388_ADDR 0x10 // default address -#else + #ifndef ES8388_ADDR + #define ES8388_ADDR 0x10 // default address + #endif Wire.beginTransmission(ES8388_ADDR); -#endif Wire.write((uint8_t)reg); Wire.write((uint8_t)val); uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK @@ -564,7 +532,7 @@ class ES8388Source : public I2SSource { // The mics *and* line-in are BOTH connected to LIN2/RIN2 on the AudioKit // so there's no way to completely eliminate the mics. It's also hella noisy. // Line-in works OK on the AudioKit, generally speaking, as the mics really need - // amplification to be noticable in a quiet room. If you're in a very loud room, + // amplification to be noticeable in a quiet room. If you're in a very loud room, // the mics on the AudioKit WILL pick up sound even in line-in mode. // TL;DR: Don't use the AudioKit for anything, use the LyraT. // @@ -617,58 +585,125 @@ class ES8388Source : public I2SSource { _config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; }; - void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN("ES8388Source:: initialize();"); + // if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too + // ERRORSR_PRINTF("\nAR: invalid I2S ES8388 pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); + // return; + // } // BUG: "use global I2C pins" are valid as -1, and -1 is seen as invalid here. // Workaround: Set I2C pins here, which will also set them globally. // Bug also exists in ES7243. - - // check that pins are valid - if ((sdaPin < 0) || (sclPin < 0)) { - ERRORSR_PRINTF("\nAR: invalid ES8388 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } - if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined" ERRORSR_PRINTF("\nAR: invalid ES8388 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); return; } - if (!pinManager.joinWire()) { // WLEDMM specific: start I2C with globally defined pins + if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); return; } - if ((i2sckPin < 0) || (mclkPin < 0)) { - ERRORSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); - return; - } - - // Reserve SDA and SCL pins of the I2C interface - PinManagerPinType es8388Pins[2] = { { sdaPin, true }, { sclPin, true } }; - if (!pinManager.allocateMultiplePins(es8388Pins, 2, PinOwner::HW_I2C)) { - pinManager.deallocateMultiplePins(es8388Pins, 2, PinOwner::HW_I2C); - ERRORSR_PRINTF("\nAR: Failed to allocate ES8388 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } - - pin_ES8388_SDA = sdaPin; - pin_ES8388_SCL = sclPin; - // First route mclk, then configure ADC over I2C, then configure I2S _es8388InitAdc(); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); } void deinitialize() { - // Release SDA and SCL pins of the I2C interface - PinManagerPinType es8388Pins[2] = { { pin_ES8388_SDA, true }, { pin_ES8388_SCL, true } }; - pinManager.deallocateMultiplePins(es8388Pins, 2, PinOwner::HW_I2C); I2SSource::deinitialize(); } +}; + +class WM8978Source : public I2SSource { private: - int8_t pin_ES8388_SDA; - int8_t pin_ES8388_SCL; + // I2C initialization functions for WM8978 + void _wm8978I2cBegin() { + Wire.setClock(400000); + } + + void _wm8978I2cWrite(uint8_t reg, uint16_t val) { + #ifndef WM8978_ADDR + #define WM8978_ADDR 0x1A + #endif + char buf[2]; + buf[0] = (reg << 1) | ((val >> 8) & 0X01); + buf[1] = val & 0XFF; + Wire.beginTransmission(WM8978_ADDR); + Wire.write((const uint8_t*)buf, 2); + uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK + if (i2cErr != 0) { + DEBUGSR_PRINTF("AR: WM8978 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, WM8978_ADDR, reg, val); + } + } + + void _wm8978InitAdc() { + // https://www.mouser.com/datasheet/2/76/WM8978_v4.5-1141768.pdf + // Sets ADC to around what AudioReactive expects, and loops line-in to line-out/headphone for monitoring. + // Registries are decimal, settings are 9-bit binary as that's how everything is listed in the docs + // ...which makes it easier to reference the docs. + // + _wm8978I2cBegin(); + + _wm8978I2cWrite( 0,0b000000000); // Reset all settings + _wm8978I2cWrite( 1,0b000001011); // Power Management 1 - power off most things + _wm8978I2cWrite( 2,0b110110011); // Power Management 2 - enable output and amp stages (amps may lift signal but it works better on the ADCs) + _wm8978I2cWrite( 3,0b000001100); // Power Management 3 - enable L&R output mixers + _wm8978I2cWrite( 4,0b001010000); // Audio Interface - standard I2S, 24-bit + _wm8978I2cWrite( 5,0b000000001); // Loopback Enable + _wm8978I2cWrite( 6,0b000000000); // Clock generation control - use external mclk + _wm8978I2cWrite( 7,0b000000100); // Sets sample rate to ~24kHz (only used for internal calculations, not I2S) + _wm8978I2cWrite(14,0b010001000); // 128x ADC oversampling - high pass filter disabled as it kills the bass response + _wm8978I2cWrite(43,0b000110000); // Mute signal paths we don't use + _wm8978I2cWrite(44,0b000000000); // Disconnect microphones + _wm8978I2cWrite(45,0b111000000); // Mute signal paths we don't use + _wm8978I2cWrite(46,0b111000000); // Mute signal paths we don't use + _wm8978I2cWrite(47,0b001000000); // 0dB gain on left line-in + _wm8978I2cWrite(48,0b001000000); // 0dB gain on right line-in + _wm8978I2cWrite(49,0b000000010); // Mixer thermal shutdown enable + _wm8978I2cWrite(50,0b000010110); // Output mixer enable only left bypass at 0dB gain + _wm8978I2cWrite(51,0b000010110); // Output mixer enable only right bypass at 0dB gain + _wm8978I2cWrite(52,0b110111001); // Left line-out enabled at 0dB gain + _wm8978I2cWrite(53,0b110111001); // Right line-out enabled at 0db gain + _wm8978I2cWrite(54,0b001000000); // Mute left speaker output + _wm8978I2cWrite(55,0b101000000); // Mute right speaker output + + } + + public: + WM8978Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) : + I2SSource(sampleRate, blockSize, sampleScale, i2sMaster) { + _config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; + }; + + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN("WM8978Source:: initialize();"); + + // if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too + // ERRORSR_PRINTF("\nAR: invalid I2S WM8978 pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); + // return; + // } + // BUG: "use global I2C pins" are valid as -1, and -1 is seen as invalid here. + // Workaround: Set I2C pins here, which will also set them globally. + // Bug also exists in ES7243. + if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined" + ERRORSR_PRINTF("\nAR: invalid WM8978 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins + ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + + // First route mclk, then configure ADC over I2C, then configure I2S + _wm8978InitAdc(); + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + } + + void deinitialize() { + I2SSource::deinitialize(); + } + }; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) @@ -678,7 +713,7 @@ class ES8388Source : public I2SSource { #endif #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) -// ADC over I2S is only availeable in "classic" ESP32 +// ADC over I2S is only available in "classic" ESP32 /* ADC over I2S Microphone This microphone is an ADC pin sampled via the I2S interval @@ -711,7 +746,8 @@ class I2SAdcSource : public I2SSource { /* identify Audiosource type - I2S-ADC*/ AudioSourceType getType(void) {return(Type_I2SAdc);} - void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + DEBUGSR_PRINTLN("I2SAdcSource:: initialize()."); _myADCchannel = 0x0F; if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { ERRORSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin); @@ -721,7 +757,7 @@ class I2SAdcSource : public I2SSource { // Determine Analog channel. Only Channels on ADC1 are supported int8_t channel = digitalPinToAnalogChannel(_audioPin); - if (channel > 9) { + if ((channel < 0) || (channel > 9)) { // channel == -1 means "not an ADC pin" USER_PRINTF("AR: Incompatible GPIO used for analog audio input: %d\n", _audioPin); return; } else { @@ -736,7 +772,7 @@ class I2SAdcSource : public I2SSource { return; } - adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution + // adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution - should not be needed, because i2s_set_adc_mode does that any way // Enable I2S mode of ADC err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel)); @@ -882,7 +918,8 @@ class SPH0654 : public I2SSource { I2SSource(sampleRate, blockSize, sampleScale, i2sMaster) {} - void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE) { + DEBUGSR_PRINTLN("SPH0654:: initialize();"); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // these registers are only existing in "classic" ESP32 @@ -893,4 +930,4 @@ class SPH0654 : public I2SSource { #endif } }; -#endif \ No newline at end of file +#endif 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 e2b65910..4acd6b15 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -90,7 +90,11 @@ class AutoSaveUsermod : public Usermod { #ifdef USERMOD_FOUR_LINE_DISPLAY if (display != nullptr) { display->wakeDisplay(); + #if defined(USE_ALT_DISPLAY) || defined(USE_ALT_DISPlAY) if (display->canDraw()) display->overlay("Settings", "Auto Saved", 1500); // WLEDMM bugfix + #else + display->overlay("Settings", "Auto Saved", 1500); + #endif } #endif } diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 96a2726d..84bd4a8c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -73,6 +73,12 @@ static int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { return 0; } +// float version of map() // WLEDMM moved here so its available for all effects +static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ + if (in_max == in_min) return (out_min); // WLEDMM avoid div/0 + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + // effect functions /* @@ -80,7 +86,7 @@ static int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { */ uint16_t mode_static(void) { SEGMENT.fill(SEGCOLOR(0)); - return 350; + return FRAMETIME_FIXED_SLOW; // WLEDMM to ensure smooth color changes from DMX (PR #73) } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; @@ -1850,10 +1856,10 @@ uint16_t mode_lightning(void) { } SEGENV.aux1--; - SEGENV.step = millis(); + SEGENV.step = strip.now; //return random8(4, 10); // each flash only lasts one frame/every 24ms... originally 4-10 milliseconds } else { - if (millis() - SEGENV.step > SEGENV.aux0) { + if (strip.now - SEGENV.step > SEGENV.aux0) { SEGENV.aux1--; if (SEGENV.aux1 < 2) SEGENV.aux1 = 0; @@ -1861,7 +1867,7 @@ uint16_t mode_lightning(void) { if (SEGENV.aux1 == 2) { SEGENV.aux0 = (random8(255 - SEGMENT.speed) * 100); // delay between strikes } - SEGENV.step = millis(); + SEGENV.step = strip.now; } } return FRAMETIME; @@ -2378,33 +2384,35 @@ uint16_t mode_meteor() { byte* trail = SEGENV.data; - byte meteorSize= 1+ SEGLEN / 10; + const unsigned meteorSize= 1 + SEGLEN / 20; // 5% uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); uint16_t in = counter * SEGLEN >> 16; + const int max = SEGMENT.palette==5 || !SEGMENT.check1 ? 240 : 255; // fade all leds to colors[1] in LEDs one step for (int i = 0; i < SEGLEN; i++) { - if (random8() <= 255 - SEGMENT.intensity) - { - byte meteorTrailDecay = 128 + random8(127); + if (random8() <= 255 - SEGMENT.intensity) { + byte meteorTrailDecay = 162 + random8(92); trail[i] = scale8(trail[i], meteorTrailDecay); - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, false, 0, trail[i])); + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); + SEGMENT.setPixelColor(i, col); } } // draw meteor - for (int j = 0; j < meteorSize; j++) { + for (unsigned j = 0; j < meteorSize; j++) { uint16_t index = in + j; if (index >= SEGLEN) { index -= SEGLEN; } - trail[index] = 240; - SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, false, 0, 255)); + trail[index] = max; + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255); + SEGMENT.setPixelColor(index, col); } return FRAMETIME; } -static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail length;!;!"; +static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient;;!;1"; // smooth meteor effect @@ -2416,35 +2424,35 @@ uint16_t mode_meteor_smooth() { byte* trail = SEGENV.data; - byte meteorSize= 1+ SEGLEN / 10; + const unsigned meteorSize= 1+ SEGLEN / 20; // 5% uint16_t in = map((SEGENV.step >> 6 & 0xFF), 0, 255, 0, SEGLEN -1); + const int max = SEGMENT.palette==5 || !SEGMENT.check1 ? 240 : 255; // fade all leds to colors[1] in LEDs one step for (int i = 0; i < SEGLEN; i++) { - if (trail[i] != 0 && random8() <= 255 - SEGMENT.intensity) - { - int change = 3 - random8(12); //change each time between -8 and +3 - trail[i] += change; - if (trail[i] > 245) trail[i] = 0; - if (trail[i] > 240) trail[i] = 240; - SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, false, 0, trail[i])); + if (/*trail[i] != 0 &&*/ random8() <= 255 - SEGMENT.intensity) { + int change = trail[i] + 4 - random8(24); //change each time between -20 and +4 + trail[i] = constrain(change, 0, max); + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); + SEGMENT.setPixelColor(i, col); } } // draw meteor - for (int j = 0; j < meteorSize; j++) { + for (unsigned j = 0; j < meteorSize; j++) { uint16_t index = in + j; if (index >= SEGLEN) { index -= SEGLEN; } - trail[index] = 240; - SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, false, 0, 255)); + trail[index] = max; + uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255); + SEGMENT.setPixelColor(index, col); } SEGENV.step += SEGMENT.speed +1; return FRAMETIME; } -static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail length;!;!"; +static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail,,,,Gradient;;!;1"; //Railway Crossing / Christmas Fairy lights @@ -2519,9 +2527,10 @@ uint16_t ripple_base() #ifndef WLED_DISABLE_2D if (SEGMENT.is2D()) { + propI /= 2; uint16_t cx = rippleorigin >> 8; uint16_t cy = rippleorigin & 0xFF; - uint8_t mag = scale8(cubicwave8((propF>>2)), amp); + uint8_t mag = scale8(sin8((propF>>2)), amp); if (propI > 0) SEGMENT.draw_circle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag)); } else #endif @@ -2539,7 +2548,7 @@ uint16_t ripple_base() ripplestate += rippledecay; ripples[i].state = (ripplestate > 254) ? 0 : ripplestate; } else {//randomly create new wave - if (random16(IBN + 10000) <= SEGMENT.intensity) { + if (random16(IBN + 10000) <= (SEGMENT.intensity >> (SEGMENT.is2D()*3))) { ripples[i].state = 1; ripples[i].pos = SEGMENT.is2D() ? ((random8(SEGENV.virtualWidth())<<8) | (random8(SEGENV.virtualHeight()))) : random16(SEGLEN); ripples[i].color = random8(); //color @@ -2555,6 +2564,7 @@ uint16_t ripple_base() uint16_t mode_ripple(void) { if (SEGLEN == 1) return mode_static(); if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); + else SEGMENT.fade_out(250); return ripple_base(); } static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple@!,Wave #,,,,,Overlay;,!;!;12"; @@ -2713,14 +2723,14 @@ uint16_t mode_twinklefox() { return twinklefox_base(false); } -static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate;;!"; +static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate;!,!;!"; uint16_t mode_twinklecat() { return twinklefox_base(true); } -static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate;;!"; +static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate;!,!;!"; //inspired by https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectBlinkingHalloweenEyes @@ -2913,7 +2923,7 @@ uint16_t mode_bouncing_balls(void) { uint16_t numBalls = (SEGMENT.intensity * (maxNumBalls - 1)) / 255 + 1; // minimum 1 ball const float gravity = -9.81f; // standard value of gravity const bool hasCol2 = SEGCOLOR(2); - const unsigned long time = millis(); + const unsigned long time = strip.now; if (SEGENV.call == 0) { for (size_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; @@ -2961,6 +2971,103 @@ uint16_t mode_bouncing_balls(void) { static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravity,# of balls,,,,,Overlay;!,!,!;!;1.5d;m12=1"; //bar WLEDMM 1.5d +/* + * bouncing balls on a track track Effect modified from Aircoookie's bouncing balls + * Courtesy of pjhatch (https://github.com/pjhatch) + * https://github.com/Aircoookie/WLED/pull/1039 + */ +// modified for balltrack mode +typedef struct RollingBall { + unsigned long lastBounceUpdate; + float mass; // could fix this to be = 1. if memory is an issue + float velocity; + float height; +} rball_t; + +static uint16_t rolling_balls(void) { + //allocate segment data + const uint16_t maxNumBalls = 16; // 255/16 + 1 + uint16_t dataSize = sizeof(rball_t) * maxNumBalls; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + + rball_t *balls = reinterpret_cast(SEGENV.data); + + // number of balls based on intensity setting to max of 16 (cycles colors) + // non-chosen color is a random color + uint8_t numBalls = SEGMENT.intensity/16 + 1; + + if (SEGENV.call == 0) { + for (int i = 0; i < maxNumBalls; i++) { + balls[i].lastBounceUpdate = strip.now; + balls[i].velocity = 20.0f * float(random16(1000, 10000))/10000.0f; // number from 1 to 10 + if (random8()<128) balls[i].velocity = -balls[i].velocity; // 50% chance of reverse direction + balls[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0. to 1. + balls[i].mass = (float(random16(1000, 10000)) / 10000.0f); // from .1 to 1. + } + } + + float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider + + bool hasCol2 = SEGCOLOR(2); + if (!SEGMENT.check2) SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); + + for (int i = 0; i < numBalls; i++) { + float timeSinceLastUpdate = float((strip.now - balls[i].lastBounceUpdate))/cfac; + float thisHeight = balls[i].height + balls[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution + // test if intensity level was increased and some balls are way off the track then put them back + if (thisHeight < -0.5f || thisHeight > 1.5f){ + thisHeight = balls[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0. to 1. + balls[i].lastBounceUpdate = strip.now; + } + // check if reached ends of the strip + if ((thisHeight <= 0.0f && balls[i].velocity < 0.0f) || (thisHeight >= 1.0f && balls[i].velocity > 0.0f)) { + balls[i].velocity = -balls[i].velocity; // reverse velocity + balls[i].lastBounceUpdate = strip.now; + balls[i].height = thisHeight; + } + // check for collisions + if (SEGMENT.check1) { + for (int j = i+1; j < numBalls; j++) { + if (balls[j].velocity != balls[i].velocity) { + // tcollided + balls[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions) + float tcollided = (cfac*(balls[i].height - balls[j].height) + + balls[i].velocity*float(balls[j].lastBounceUpdate - balls[i].lastBounceUpdate))/(balls[j].velocity - balls[i].velocity); + + if ((tcollided > 2.0f) && (tcollided < float(strip.now - balls[j].lastBounceUpdate))) { // 2ms minimum to avoid duplicate bounces + balls[i].height = balls[i].height + balls[i].velocity*(tcollided + float(balls[j].lastBounceUpdate - balls[i].lastBounceUpdate))/cfac; + balls[j].height = balls[i].height; + balls[i].lastBounceUpdate = (unsigned long)(tcollided + 0.5f) + balls[j].lastBounceUpdate; + balls[j].lastBounceUpdate = balls[i].lastBounceUpdate; + float vtmp = balls[i].velocity; + balls[i].velocity = ((balls[i].mass - balls[j].mass)*vtmp + 2.0f*balls[j].mass*balls[j].velocity)/(balls[i].mass + balls[j].mass); + balls[j].velocity = ((balls[j].mass - balls[i].mass)*balls[j].velocity + 2.0f*balls[i].mass*vtmp) /(balls[i].mass + balls[j].mass); + thisHeight = balls[i].height + balls[i].velocity*(strip.now - balls[i].lastBounceUpdate)/cfac; + } + } + } + } + + uint32_t color = SEGCOLOR(0); + if (SEGMENT.palette) { + //color = SEGMENT.color_wheel(i*(256/MAX(numBalls, 8))); + color = SEGMENT.color_from_palette(i*255/numBalls, false, PALETTE_SOLID_WRAP, 0); + } else if (hasCol2) { + color = SEGCOLOR(i % NUM_COLORS); + } + + if (thisHeight < 0.0f) thisHeight = 0.0f; + if (thisHeight > 1.0f) thisHeight = 1.0f; + uint16_t pos = round(thisHeight * (SEGLEN - 1)); + SEGMENT.setPixelColor(pos, color); + balls[i].lastBounceUpdate = strip.now; + balls[i].height = thisHeight; + } + + return FRAMETIME; +} +static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collisions,Overlay;!,!,!;!;1;m12=1"; //bar + + /* * Sinelon stolen from FASTLED examples */ @@ -3251,7 +3358,7 @@ uint16_t mode_starburst(void) { if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - uint32_t it = millis(); + uint32_t it = strip.now; star* stars = reinterpret_cast(SEGENV.data); @@ -3608,7 +3715,7 @@ uint16_t mode_tetrix(void) { // initialize dropping on first call or segment full if (SEGENV.call == 0) { drop->stack = 0; // reset brick stack size - drop->step = millis() + 2000; // start by fading out strip + drop->step = strip.now + 2000; // start by fading out strip if (SEGMENT.check1) drop->col = 0;// use only one color from palette } @@ -3642,13 +3749,13 @@ uint16_t mode_tetrix(void) { } else { // we hit bottom drop->step = 0; // proceed with next brick, go back to init drop->stack += drop->brick; // increase the stack size - if (drop->stack >= SEGLEN) drop->step = millis() + 2000; // fade out stack + if (drop->stack >= SEGLEN) drop->step = strip.now + 2000; // fade out stack } } if (drop->step > 2) { // fade strip drop->brick = 0; // reset brick size (no more growing) - if (drop->step > millis()) { + if (drop->step > strip.now) { // allow fading of virtual strip for (int i = 0; i < SEGLEN; i++) SEGMENT.blendPixelColor(indexToVStrip(i, stripNr), SEGCOLOR(1), 25); // 10% blend } else { @@ -3907,14 +4014,14 @@ uint16_t mode_sunrise() { //speed 60 - 120 : sunset time in minutes - 60; //speed above: "breathing" rise and set if (SEGENV.call == 0 || SEGMENT.speed != SEGENV.aux0) { - SEGENV.step = millis(); //save starting time, millis() because now can change from sync + SEGENV.step = strip.now; //save starting time, strip.now because now can change from sync SEGENV.aux0 = SEGMENT.speed; } SEGMENT.fill(BLACK); uint16_t stage = 0xFFFF; - uint32_t s10SinceStart = (millis() - SEGENV.step) /100; //tenths of seconds + uint32_t s10SinceStart = (strip.now - SEGENV.step) /100; //tenths of seconds if (SEGMENT.speed > 120) { //quick sunrise and sunset uint16_t counter = (strip.now >> 1) * (((SEGMENT.speed -120) >> 1) +1); @@ -4018,9 +4125,9 @@ uint16_t mode_noisepal(void) { // Slow noise CRGBPalette16* palettes = reinterpret_cast(SEGENV.data); uint16_t changePaletteMs = 4000 + SEGMENT.speed *10; //between 4 - 6.5sec - if (millis() - SEGENV.step > changePaletteMs) + if (strip.now - SEGENV.step > changePaletteMs) { - SEGENV.step = millis(); + SEGENV.step = strip.now; uint8_t baseI = random8(); palettes[1] = CRGBPalette16(CHSV(baseI+random8(64), 255, random8(128,255)), CHSV(baseI+128, 255, random8(128,255)), CHSV(baseI+random8(92), 192, random8(128,255)), CHSV(baseI+random8(92), 255, random8(128,255))); @@ -4177,7 +4284,7 @@ uint16_t mode_dancing_shadows(void) SEGMENT.fill(BLACK); - unsigned long time = millis(); + unsigned long time = strip.now; bool respawn = false; for (size_t i = 0; i < numSpotlights; i++) { @@ -4371,8 +4478,8 @@ uint16_t mode_tv_simulator(void) { } // create a new sceene - if (((millis() - tvSimulator->sceeneStart) >= tvSimulator->sceeneDuration) || SEGENV.aux1 == 0) { - tvSimulator->sceeneStart = millis(); // remember the start of the new sceene + if (((strip.now - tvSimulator->sceeneStart) >= tvSimulator->sceeneDuration) || SEGENV.aux1 == 0) { + tvSimulator->sceeneStart = strip.now; // remember the start of the new sceene tvSimulator->sceeneDuration = random16(60* 250* colorSpeed, 60* 750 * colorSpeed); // duration of a "movie sceene" which has similar colors (5 to 15 minutes with max speed slider) tvSimulator->sceeneColorHue = random16( 0, 768); // random start color-tone for the sceene tvSimulator->sceeneColorSat = random8 ( 100, 130 + colorIntensity); // random start color-saturation for the sceene @@ -4423,11 +4530,11 @@ uint16_t mode_tv_simulator(void) { tvSimulator->fadeTime = random16(0, tvSimulator->totalTime); // Pixel-to-pixel transition time if (random8(10) < 3) tvSimulator->fadeTime = 0; // Force scene cut 30% of time - tvSimulator->startTime = millis(); + tvSimulator->startTime = strip.now; } // end of initialization // how much time is elapsed ? - tvSimulator->elapsed = millis() - tvSimulator->startTime; + tvSimulator->elapsed = strip.now - tvSimulator->startTime; // fade from prev volor to next color if (tvSimulator->elapsed < tvSimulator->fadeTime) { @@ -4632,7 +4739,7 @@ uint16_t mode_perlinmove(void) { if (SEGENV.call == 0) SEGENV.setUpLeds(); // WLEDMM use lossless getPixelColor() SEGMENT.fade_out(255-SEGMENT.custom1); for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) { - uint16_t locn = inoise16(millis()*128/(260-SEGMENT.speed)+i*15000, millis()*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. + uint16_t locn = inoise16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. uint16_t pixloc = map(locn, 50*256, 192*256, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. SEGMENT.setPixelColor(pixloc, SEGMENT.color_from_palette(pixloc%255, false, PALETTE_SOLID_WRAP, 0)); } @@ -4649,7 +4756,7 @@ static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixel uint16_t mode_wavesins(void) { for (int i = 0; i < SEGLEN; i++) { - uint8_t bri = sin8(millis()/4 + i * SEGMENT.intensity); + uint8_t bri = sin8(strip.now/4 + i * SEGMENT.intensity); uint8_t index = beatsin8(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, bri)); @@ -4667,8 +4774,8 @@ static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness vari uint16_t mode_FlowStripe(void) { const uint16_t hl = SEGLEN * 10 / 13; - uint8_t hue = millis() / (SEGMENT.speed+1); - uint32_t t = millis() / (SEGMENT.intensity/8+1); + uint8_t hue = strip.now / (SEGMENT.speed+1); + uint32_t t = strip.now / (SEGMENT.intensity/8+1); for (int i = 0; i < SEGLEN; i++) { int c = (abs(i - hl) / hl) * 127; @@ -4704,7 +4811,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma } SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails - unsigned long t = millis()/128; // timebase + unsigned long t = strip.now/128; // timebase // outer stars for (size_t i = 0; i < 8; i++) { x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); @@ -4798,8 +4905,8 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa SEGMENT.fadeToBlackBy(64); for (int i = 0; i < cols; i++) { - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+millis()/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+millis()/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>3); @@ -4825,7 +4932,7 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma uint8_t speeds = SEGMENT.speed/2 + 1; uint8_t freq = SEGMENT.intensity/8; - uint32_t ms = millis() / 20; + uint32_t ms = strip.now / 20; SEGMENT.fadeToBlackBy(135); for (int i = 0; i < rows; i++) { @@ -4871,7 +4978,7 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli SEGMENT.fadeToBlackBy(128); const uint16_t maxDim = MAX(cols, rows)/2; - unsigned long t = millis() / (32 - (SEGMENT.speed>>3)); + unsigned long t = strip.now / (32 - (SEGMENT.speed>>3)); unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup for (float i = 1; i < maxDim; i += 0.25) { float angle = radians(t * (maxDim - i)); @@ -4911,7 +5018,7 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline for (int j=0; j < cols; j++) { for (int i=0; i < rows; i++) { - indexx = inoise8(j*yscale*rows/255, i*xscale+millis()/4); // We're moving along our Perlin map. + indexx = inoise8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map. SEGMENT.setPixelColorXY(j, i, ColorFromPalette(SEGPALETTE, min(i*(indexx)>>4, 255), i*255/cols, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED. } // for i } // for j @@ -4975,7 +5082,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) { SEGENV.step = strip.now; SEGENV.aux0 = 0; - random16_set_seed(millis()>>2); //seed the random generator + random16_set_seed(strip.now>>2); //seed the random generator //give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen) for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { @@ -5151,8 +5258,8 @@ uint16_t mode_2DJulia(void) { // An animated Julia set reAl = -0.94299f; // PixelBlaze example imAg = 0.3162f; - reAl += sin_t((float)millis()/305.f)/20.f; - imAg += sin_t((float)millis()/405.f)/20.f; + reAl += sin_t((float)strip.now/305.f)/20.f; + imAg += sin_t((float)strip.now/405.f)/20.f; dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. dy = (ymax - ymin) / (rows); @@ -5221,7 +5328,7 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline for (int i=0; i < maxLoops; i ++) { float xlocn = float(sin8(phase/2 + (i* SEGMENT.speed)/64)) / 255.0f; // WLEDMM align speed with original effect float ylocn = float(cos8(phase/2 + i*2)) / 255.0f; - //SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(millis()/100+i, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing + //SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing unsigned palIndex = (256*ylocn) + phase/2 + (i* SEGMENT.speed)/64; SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing - color follows rotation } @@ -5232,7 +5339,7 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline uint_fast8_t ylocn = cos8(phase/2 + i*2); xlocn = (cols < 2) ? 1 : (map(2*xlocn, 0,511, 0,2*(cols-1)) +1) /2; // softhack007: "*2 +1" for proper rounding ylocn = (rows < 2) ? 1 : (map(2*ylocn, 0,511, 0,2*(rows-1)) +1) /2; // "rows > 2" is needed to avoid div/0 in map() - SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(millis()/100+i, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; @@ -5243,7 +5350,7 @@ static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous ☾@X frequen /////////////////////// // 2D Matrix // /////////////////////// -uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi. +uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi, and softhack007. if (!strip.isMatrix) return mode_static(); // not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); @@ -5252,6 +5359,8 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. if (SEGENV.call == 0) { SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); + SEGENV.aux0 = SEGENV.aux1 = UINT16_MAX; + SEGENV.step = 0; } uint8_t fade = map(SEGMENT.custom1, 0, 255, 50, 250); // equals trail size @@ -5269,32 +5378,43 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. if (strip.now - SEGENV.step >= speed) { SEGENV.step = strip.now; + // find out what color value is returned by gPC for a "falling code" example pixel + // the color values returned may differ from the previously set values, due to + // - auto brightness limiter (dimming) + // - lossy color buffer (when not using global buffer) + // - color balance correction + // - segment opacity + CRGB oldSpawnColor = spawnColor; + if ((SEGENV.aux0 < cols) && (SEGENV.aux1 < rows)) { // we have a hint from last run + oldSpawnColor = SEGMENT.getPixelColorXY(SEGENV.aux0, SEGENV.aux1); // find color of previous spawns + SEGENV.aux1 ++; // our sample pixel will be one row down the next time + } + if ((oldSpawnColor == CRGB::Black) || (oldSpawnColor == trailColor)) oldSpawnColor = spawnColor; // reject "black", as it would mean that ALL pixels create trails + + // move pixels one row down. Falling codes keep color and add trail pixels; all others pixels are faded for (int row=rows-1; row>=0; row--) { for (int col=0; col= rows); // empty screen means that the last falling code has moved out of screen area // spawn new falling code - if (random8() < SEGMENT.intensity || emptyScreen) { + if (random8() <= SEGMENT.intensity || emptyScreen) { uint8_t spawnX = random8(cols); SEGMENT.setPixelColorXY(spawnX, 0, spawnColor); + // update hint for next run + SEGENV.aux0 = spawnX; + SEGENV.aux1 = 0; } } // if millis @@ -5375,7 +5495,7 @@ uint16_t mode_2Dnoise(void) { // By Andrew Tuline for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { - uint8_t pixelHue8 = inoise8(x * scale, y * scale, millis() / (16 - SEGMENT.speed/16)); + uint8_t pixelHue8 = inoise8(x * scale, y * scale, strip.now / (16 - SEGMENT.speed/16)); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, pixelHue8)); } } @@ -5401,7 +5521,7 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); - uint_fast32_t t = (millis() * 8) / (256 - SEGMENT.speed); // optimized to avoid float + uint_fast32_t t = (strip.now * 8) / (256 - SEGMENT.speed); // optimized to avoid float for (int i = 0; i < cols; i++) { uint16_t thisVal = inoise8(i * 30, t, t); uint16_t thisMax = map(thisVal, 0, 255, 0, cols-1); @@ -5440,15 +5560,14 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; + const CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); - SEGENV.step = 0; } - float adjustHeight = (float)map(rows, 8, 32, 28, 12); // maybe use mapf() ??? + float adjustHeight = mapf(rows, 8, 32, 28, 12); // maybe use mapf() ??? // WLEDMM yes! uint16_t adjScale = map(cols, 8, 64, 310, 63); /* if (SEGENV.aux1 != SEGMENT.custom1/12) { // Hacky palette rotation. We need that black. @@ -5468,19 +5587,36 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https uint16_t _scale = map(SEGMENT.intensity, 0, 255, 30, adjScale); byte _speed = map(SEGMENT.speed, 0, 255, 128, 16); - for (int x = 0; x < cols; x++) { - for (int y = 0; y < rows; y++) { - SEGENV.step++; - SEGMENT.setPixelColorXY(x, y, ColorFromPalette(auroraPalette, - qsub8( - inoise8((SEGENV.step%2) + x * _scale, y * 16 + SEGENV.step % 16, SEGENV.step / _speed), - fabsf((float)rows / 2.0f - (float)y) * adjustHeight))); + //WLEDMM add SuperSync control + uint16_t xStart, xEnd, yStart, yEnd; + if (SEGMENT.check1) { //Master (sync on needs to show the whole effect, children only their first panel) + xStart = strip.panel[0].xOffset; + xEnd = strip.panel[0].xOffset + strip.panel[0].width; + yStart = strip.panel[0].yOffset; + yEnd = strip.panel[0].yOffset + strip.panel[0].height; + } + else { + xStart = 0; + xEnd = cols; + yStart = 0; + yEnd = rows; + } + + SEGENV.step = (strip.now * (cols * rows)) / 25; // baseline 40fps + const float rows_2 = (float)rows / 2.0f; // WLEDMM faster to pre-calculate this + for (int x = xStart; x < xEnd; x++) { + for (int y = yStart; y < yEnd; y++) { + SEGENV.step++; + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(auroraPalette, + qsub8( + inoise8((SEGENV.step%2) + x * _scale, y * 16 + SEGENV.step % 16, SEGENV.step / _speed), + fabsf(rows_2 - (float)y) * adjustHeight))); // WLEDMM } } return FRAMETIME; } // mode_2DPolarLights() -static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale;;;2"; +static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale,,,,SuperSync;;;2"; ///////////////////////// @@ -5527,7 +5663,7 @@ uint16_t mode_2DSindots(void) { // By: ldirko http SEGMENT.fadeToBlackBy(SEGMENT.custom1>>3); - byte t1 = millis() / (257 - SEGMENT.speed); // 20; + byte t1 = strip.now / (257 - SEGMENT.speed); // 20; byte t2 = sin8(t1) / 4 * 2; for (int i = 0; i < 13; i++) { byte x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! @@ -5572,7 +5708,7 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g uint8_t n = beatsin8(15, kBorderWidth, rows-kBorderWidth); uint8_t p = beatsin8(20, kBorderWidth, rows-kBorderWidth); - uint16_t ms = millis(); + uint16_t ms = strip.now; SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, ms/29, 255, LINEARBLEND)); SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, ms/41, 255, LINEARBLEND)); @@ -5600,7 +5736,7 @@ uint16_t mode_2DSunradiation(void) { // By: ldirko https://edi SEGMENT.fill(BLACK); } - unsigned long t = millis() / 4; + unsigned long t = strip.now / 4; int index = 0; uint8_t someVal = SEGMENT.speed/4; // Was 25. for (int j = 0; j < (rows + 2); j++) { @@ -5737,7 +5873,8 @@ uint16_t mode_2Dcrazybees(void) { uint8_t posX, posY, aimX, aimY, hue; int8_t deltaX, deltaY, signX, signY, error; void aimed(uint16_t w, uint16_t h) { - random16_set_seed(millis()); + if (!true) //WLEDMM SuperSync + random16_set_seed(strip.now); aimX = random8(0, w); aimY = random8(0, h); hue = random8(); @@ -5753,6 +5890,8 @@ uint16_t mode_2Dcrazybees(void) { bee_t *bee = reinterpret_cast(SEGENV.data); if (SEGENV.call == 0) { + if (true) //WLEDMM SuperSync + random16_set_seed(strip.now); SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); for (size_t i = 0; i < n; i++) { @@ -5762,8 +5901,8 @@ uint16_t mode_2Dcrazybees(void) { } } - if (millis() > SEGENV.step) { - SEGENV.step = millis() + (FRAMETIME * 8 / ((SEGMENT.speed>>5)+1)); + if (strip.now > SEGENV.step) { + SEGENV.step = strip.now + (FRAMETIME * 8 / ((SEGMENT.speed>>5)+1)); SEGMENT.fadeToBlackBy(32); @@ -5840,8 +5979,8 @@ uint16_t mode_2Dghostrider(void) { } } - if (millis() > SEGENV.step) { - SEGENV.step = millis() + 1024 / (cols+rows); + if (strip.now > SEGENV.step) { + SEGENV.step = strip.now + 1024 / (cols+rows); SEGMENT.fadeToBlackBy((SEGMENT.speed>>2)+64); @@ -5930,7 +6069,7 @@ uint16_t mode_2Dfloatingblobs(void) { // Bounce balls around for (size_t i = 0; i < Amount; i++) { - if (SEGENV.step < millis()) blob->color[i] = add8(blob->color[i], 4); // slowly change color + if (SEGENV.step < strip.now) blob->color[i] = add8(blob->color[i], 4); // slowly change color // change radius if needed if (blob->grow[i]) { // enlarge radius until it is >= 4 @@ -5977,7 +6116,7 @@ uint16_t mode_2Dfloatingblobs(void) { } SEGMENT.blur(SEGMENT.custom1>>2); - if (SEGENV.step < millis()) SEGENV.step = millis() + 2000; // change colors every 2 seconds + if (SEGENV.step < strip.now) SEGENV.step = strip.now + 2000; // change colors every 2 seconds return FRAMETIME; } @@ -6012,9 +6151,10 @@ uint16_t mode_2Dscrollingtext(void) { char text[33] = {'\0'}; unsigned maxLen = (SEGMENT.name) ? min(32, (int)strlen(SEGMENT.name)) : 0; // WLEDMM make it robust against too long segment names if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; + const bool zero = strchr(text, '0') != nullptr; - if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HHMM"),5)) { // fallback if empty segment name: display date and time - char sec[5]; + if (!strlen(text) || !strncmp_P(text,PSTR("#F"),2) || !strncmp_P(text,PSTR("#P"),2) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#DDMM"),5) || !strncmp_P(text,PSTR("#MMDD"),5) || !strncmp_P(text,PSTR("#TIME"),5) || !strncmp_P(text,PSTR("#HH"),3) || !strncmp_P(text,PSTR("#MM"),3)) { // fallback if empty segment name: display date and time + char sec[5]= {'\0'}; byte AmPmHour = hour(localTime); boolean isitAM = true; if (useAMPM) { @@ -6023,22 +6163,24 @@ uint16_t mode_2Dscrollingtext(void) { } if (useAMPM) sprintf_P(sec, PSTR(" %2s"), (isitAM ? "AM" : "PM")); else sprintf_P(sec, PSTR(":%02d"), second(localTime)); - if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); - else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, PSTR("%d.%d"), day(localTime), month(localTime)); - else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, PSTR("%d/%d"), month(localTime), day(localTime)); - else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); - else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, PSTR("%2d:%02d"), AmPmHour, minute(localTime)); + if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); + else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d") :PSTR("%d.%d"), day(localTime), month(localTime)); + else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime)); + else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); + else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); + else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour); + else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime)); else if (!strncmp_P(text,PSTR("#FPS"),4)) sprintf_P(text, PSTR("%2d"), (int) strip.getFps()); // WLEDMM else if (!strncmp_P(text,PSTR("#POW"),4)) sprintf_P(text, PSTR("%3.2fA"), float(strip.currentMilliamps)/1000.0f); // WLEDMM - else sprintf_P(text, PSTR("%s %d, %d %2d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); + else sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); } const int numberOfLetters = strlen(text); - if (SEGENV.step < millis()) { + if (SEGENV.step < strip.now) { if ((numberOfLetters * letterWidth) > cols) ++SEGENV.aux0 %= (numberOfLetters * letterWidth) + cols; // offset else SEGENV.aux0 = (cols + (numberOfLetters * letterWidth))/2; ++SEGENV.aux1 &= 0xFF; // color shift - SEGENV.step = millis() + map(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); + SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); if (!SEGMENT.check2) { for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++ ) SEGMENT.blendPixelColorXY(x, y, SEGCOLOR(1), 255 - (SEGMENT.custom1>>1)); @@ -6254,7 +6396,7 @@ uint16_t mode_2DSwirl(void) { uint8_t j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); uint8_t ni = (cols - 1) - i; uint8_t nj = (cols - 1) - j; - uint16_t ms = millis(); + uint16_t ms = strip.now; um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6307,7 +6449,7 @@ uint16_t mode_2DWaverly(void) { if ((SEGENV.check2) && (volumeSmth > 0.5f)) volumeSmth = soundPressure; // show sound pressure instead of volume if (SEGENV.check3) volumeSmth = 255.0 - agcSensitivity; // show AGC level instead of volume - long t = millis() / 2; + long t = strip.now / 2; for (int i = 0; i < cols; i++) { uint16_t thisVal = volumeSmth*SEGMENT.intensity/64 * inoise8(i * 45 , t , t)/64; // WLEDMM back to SR code uint16_t thisMax = map(thisVal, 0, 512, 0, rows); @@ -6326,11 +6468,6 @@ static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly ☾@Amplification #endif // WLED_DISABLE_2D -// 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; -} - // Gravity struct requited for GRAV* effects typedef struct Gravity { int topLED; @@ -6368,7 +6505,7 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. uint8_t gravity = 8 - SEGMENT.speed/32; for (int i=0; itopLED--; if (gravcen->topLED >= 0) { - SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); - SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0)); } gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; @@ -6423,7 +6560,7 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew uint8_t gravity = 8 - SEGMENT.speed/32; for (int i=0; i 1) && ((blendVal < 1) || (blendVal > 254))) blendVal = millis() % 192; // provides flickering when overtuned + //if ((realVolume > 1) && ((blendVal < 1) || (blendVal > 254))) blendVal = strip.now % 192; // provides flickering when overtuned //else blendVal = constrain(blendVal, 32, 255); // and saturation for all if (realVolume > 0.85) // hide main "bar" in silence for (int i=0; itopLED > 0) && (SEGMENT.speed < 255)){ // hide top pixel if speed = 255 if (SEGENV.check2 || SEGENV.check3) - SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(max(uint16_t(millis()/16),(uint16_t)2), false, PALETTE_SOLID_WRAP, 0)); // flicker a bit slower + SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(max(uint16_t(strip.now/16),(uint16_t)2), false, PALETTE_SOLID_WRAP, 0)); // flicker a bit slower else - SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(max(uint16_t(millis()/2),(uint16_t)2), false, PALETTE_SOLID_WRAP, 0)); // normal flickering + SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(max(uint16_t(strip.now/2),(uint16_t)2), false, PALETTE_SOLID_WRAP, 0)); // normal flickering } gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; @@ -6542,7 +6679,7 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); for (size_t i=0; i 0.5f) ? 255 : 0; @@ -6665,7 +6802,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. if (SEGENV.call == 0) SEGMENT.fill(BLACK); for (int i = 0; i < SEGLEN; i++) { - uint16_t index = inoise8(i*SEGMENT.speed/64,millis()*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. + uint16_t index = inoise8(i*SEGMENT.speed/64,strip.now*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of strip.now. By Andrew Tuline. index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. @@ -6740,7 +6877,7 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline. rawPixel = rawPixel*rawPixel / 256.0f; // WLEDMM square scaling to emphasize peaks int pixBri = rawPixel * (SEGMENT.intensity+1) / 96; - SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(millis()/5, false, PALETTE_SOLID_WRAP, 0), pixBri)); + SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now/5, false, PALETTE_SOLID_WRAP, 0), pixBri)); for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } @@ -6836,7 +6973,7 @@ uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. } for (int i=0; iu_data[0]; if (SEGENV.call == 0) SEGENV.setUpLeds(); // WLEDMM use lossless getPixelColor() - myVals[millis()%32] = volumeSmth; // filling values semi randomly + myVals[strip.now%32] = volumeSmth; // filling values semi randomly SEGMENT.fade_out(64+(SEGMENT.speed>>1)); @@ -7364,7 +7501,7 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. for (int i=0; i= (256U - SEGMENT.intensity)) { - SEGENV.step = millis(); + if (strip.now - SEGENV.step >= (256U - SEGMENT.intensity)) { + SEGENV.step = strip.now; rippleTime = true; } @@ -7751,7 +7888,7 @@ uint16_t mode_2Ddistortionwaves() { uint8_t w = 2; - uint16_t a = millis()/32; + uint16_t a = strip.now/32; uint16_t a2 = a/2; uint16_t a3 = a/3; @@ -7815,21 +7952,38 @@ uint16_t mode_2Dsoap() { // init if (SEGENV.call == 0) { + if (true) {//WLEDMM SuperSync + random16_set_seed(535); + USER_PRINTF("SuperSync\n"); + } SEGMENT.setUpLeds(); *noise32_x = random16(); *noise32_y = random16(); *noise32_z = random16(); } else { - *noise32_x += mov; - *noise32_y += mov; - *noise32_z += mov; + if (!true) { //WLEDMM SuperSync + *noise32_x += mov; + *noise32_y += mov; + *noise32_z += mov; + } + } + + //WLEDMM: changing noise calculation for SuperSync to make it deterministic using strip.now + uint32_t noise32_x_MM = *noise32_x; + uint32_t noise32_y_MM = *noise32_y; + uint32_t noise32_z_MM = *noise32_z; + + if (true) { //WLEDMM SuperSync + noise32_x_MM = *noise32_x + mov * strip.now / 100; //10 fps (original 20-40 fps, depending on realized fps) + noise32_y_MM = *noise32_y + mov * strip.now / 100; + noise32_z_MM = *noise32_z + mov * strip.now / 100; } for (int i = 0; i < cols; i++) { int32_t ioffset = scale32_x * (i - cols / 2); for (int j = 0; j < rows; j++) { int32_t joffset = scale32_y * (j - rows / 2); - uint8_t data = inoise16(*noise32_x + ioffset, *noise32_y + joffset, *noise32_z) >> 8; + uint8_t data = inoise16(noise32_x_MM + ioffset, noise32_y_MM + joffset, noise32_z_MM) >> 8; //WLEDMM SuperSync noise3d[XY(i,j)] = scale8(noise3d[XY(i,j)], smoothness) + scale8(data, 255 - smoothness); } } @@ -7926,26 +8080,46 @@ uint16_t mode_2Doctopus() { uint8_t *offsX = reinterpret_cast(SEGENV.data + dataSize); uint8_t *offsY = reinterpret_cast(SEGENV.data + dataSize + 1); + //WLEDMM add SuperSync control + uint16_t xStart, xEnd, yStart, yEnd; + if (SEGMENT.check1) { //Master (sync on needs to show the whole effect, children only their first panel) + xStart = strip.panel[0].xOffset; + xEnd = strip.panel[0].xOffset + strip.panel[0].width; + yStart = strip.panel[0].yOffset; + yEnd = strip.panel[0].yOffset + strip.panel[0].height; + } + else { + xStart = 0; + xEnd = cols; + yStart = 0; + yEnd = rows; + } + // re-init if SEGMENT dimensions or offset changed if (SEGENV.call == 0 || SEGENV.aux0 != cols || SEGENV.aux1 != rows || SEGMENT.custom1 != *offsX || SEGMENT.custom2 != *offsY) { - SEGENV.step = 0; // t + if (!true) //WLEDMM SuperSync + SEGENV.step = 0; // t SEGENV.aux0 = cols; SEGENV.aux1 = rows; *offsX = SEGMENT.custom1; *offsY = SEGMENT.custom2; const uint8_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255; const uint8_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255; - for (int x = 0; x < cols; x++) { - for (int y = 0; y < rows; y++) { + for (int x = xStart; x < xEnd; x++) { + for (int y = yStart; y < yEnd; y++) { rMap[XY(x, y)].angle = 40.7436f * atan2f(y - C_Y, x - C_X); // avoid 128*atan2()/PI rMap[XY(x, y)].radius = hypotf(x - C_X, y - C_Y) * mapp; //thanks Sutaburosu } } } - SEGENV.step += SEGMENT.speed / 32 + 1; // 1-4 range - for (int x = 0; x < cols; x++) { - for (int y = 0; y < rows; y++) { + if (true) // WLEDMM SuperSync + SEGENV.step = strip.now * (SEGMENT.speed / 32 + 1) / 25; // WLEDMM 40fps + else + SEGENV.step += SEGMENT.speed / 32 + 1; // 1-4 range + + for (int x = xStart; x < xEnd; x++) { + for (int y = yStart; y < yEnd; y++) { byte angle = rMap[XY(x,y)].angle; byte radius = rMap[XY(x,y)].radius; //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); @@ -7957,7 +8131,7 @@ uint16_t mode_2Doctopus() { } return FRAMETIME; } -static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs;;!;2;"; +static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs, SuperSync;;!;2;"; //Waving Cell @@ -7969,7 +8143,7 @@ uint16_t mode_2Dwavingcell() { const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - uint32_t t = millis()/(257-SEGMENT.speed); + uint32_t t = strip.now/(257-SEGMENT.speed); uint8_t aX = SEGMENT.custom1/16 + 9; uint8_t aY = SEGMENT.custom2/16 + 1; uint8_t aZ = SEGMENT.custom3 + 1; @@ -8064,6 +8238,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_FIRE_FLICKER, &mode_fire_flicker, _data_FX_MODE_FIRE_FLICKER); addEffect(FX_MODE_GRADIENT, &mode_gradient, _data_FX_MODE_GRADIENT); addEffect(FX_MODE_LOADING, &mode_loading, _data_FX_MODE_LOADING); + addEffect(FX_MODE_ROLLINGBALLS, &rolling_balls, _data_FX_MODE_ROLLINGBALLS); addEffect(FX_MODE_FAIRY, &mode_fairy, _data_FX_MODE_FAIRY); addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); diff --git a/wled00/FX.h b/wled00/FX.h index ff9ee811..353b40b8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -190,7 +190,7 @@ void strip_wait_until_idle(String whoCalledMe); // WLEDMM implemented in FX_fcn. #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 -// #define FX_MODE_POLICE 48 // removed in 0.14! +#define FX_MODE_ROLLINGBALLS 48 //was Police before 0.14 #define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity) #define FX_MODE_TWO_DOTS 50 #define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 1452de7d..d9955d5b 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -51,8 +51,9 @@ void WS2812FX::setUpMatrix() { } } - // safety check - if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { + // safety check + // WLEDMM no chech on Segment::maxWidth * Segment::maxHeight > MAX_LEDS || + if (Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { DEBUG_PRINTF("2D Bounds error. %d x %d\n", Segment::maxWidth, Segment::maxHeight); isMatrix = false; Segment::maxWidth = _length; @@ -158,7 +159,7 @@ void WS2812FX::setUpMatrix() { } else { // memory allocation error customMappingTableSize = 0; USER_PRINTLN(F("Ledmap alloc error.")); - isMatrix = false; + isMatrix = false; //WLEDMM does not like this done in teh background while end users are confused whats happened... panels = 0; panel.clear(); Segment::maxWidth = _length; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 6b45977f..baed4c15 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -294,7 +294,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { switch (pal) { case 0: //default palette. Exceptions for specific effects above targetPalette = PartyColors_p; break; - case 1: {//periodically replace palette with a random one. Transition palette change in 500ms + case 1: {//Random smooth: periodically replace palette with a random one. Transition palette change in 500ms uint32_t timeSinceLastChange = millis() - _lastPaletteChange; if (timeSinceLastChange > randomPaletteChangeTime * 1000U) { prevRandomPalette = randomPalette; @@ -1976,7 +1976,7 @@ void WS2812FX::purgeSegments(bool force) { } if (deleted) { _segments.shrink_to_fit(); - if (_mainSegment >= _segments.size()) setMainSegmentId(0); + /*if (_mainSegment >= _segments.size())*/ setMainSegmentId(0); } } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 3426d124..8473938e 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1177,10 +1177,11 @@ class PolyBus { if (num > 3) return I_NONE; //if (num > 3) offset = num -4; // I2S not supported yet #else - #ifndef WLEDMM_FASTPATH // standard ESP32 has 8 RMT and 2 I2S channels + #ifndef WLEDMM_FASTPATH if (num > 9) return I_NONE; - if (num > 7) offset = num -7; + if (num == 8) offset = 2; // first use I2S#1 (so #0 stays available for audio) + if (num == 9) offset = 1; // use I2S#0 as the last driver #else // ESP32 "audio_fastpath" - 8 RMT and 1 I2S channels. RMT 5-8 have sending delays, so use I2S#1 before going for RMT 5-8 if (num > 8) return I_NONE; diff --git a/wled00/button.cpp b/wled00/button.cpp index 69c54bcf..053d59dd 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -156,6 +156,7 @@ void handleAnalog(uint8_t b) #ifdef ESP8266 rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit #else + if ((btnPin[b] < 0) || (digitalPinToAnalogChannel(btnPin[b]) < 0)) return; // pin must support analog ADC - newer esp32 frameworks throw lots of warnings otherwise rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution #endif yield(); // keep WiFi task running - analog read may take several millis on ESP8266 @@ -188,7 +189,7 @@ void handleAnalog(uint8_t b) if (aRead == 0) { briLast = bri; bri = 0; - } else{ + } else { bri = aRead; } } else if (macroDoublePress[b] == 249) { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d694c9cc..07a06945 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -364,45 +364,49 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { //int hw_status_pin = hw[F("status")]["pin"]; // -1 JsonObject light = doc[F("light")]; - CJSON(briMultiplier, light[F("scale-bri")]); - CJSON(strip.paletteBlend, light[F("pal-mode")]); - CJSON(autoSegments, light[F("aseg")]); + byte prev; //WLEDMM + int tdd; //WLEDMM + if (!light.isNull()) { //WLEDMM: in case cfg string does not contain light! (solves issue that somethimes gamma correction dissappears) + CJSON(briMultiplier, light[F("scale-bri")]); + CJSON(strip.paletteBlend, light[F("pal-mode")]); + CJSON(autoSegments, light[F("aseg")]); - CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8 - float light_gc_bri = light["gc"]["bri"]; - float light_gc_col = light["gc"]["col"]; - float light_gc_prev = light["gc"]["prev"]; // WLEDMM - if (light_gc_bri > 1.0f) gammaCorrectBri = true; - else gammaCorrectBri = false; - if (light_gc_col > 1.0f) gammaCorrectCol = true; - else gammaCorrectCol = false; - if (light_gc_prev > 1.0f) gammaCorrectPreview = true; // WLEDMM - else gammaCorrectPreview = false; // WLEDMM - if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { - if (gammaCorrectVal != 2.8f) calcGammaTable(gammaCorrectVal); - } else { - gammaCorrectVal = 1.0f; // no gamma correction - gammaCorrectBri = false; - gammaCorrectCol = false; - gammaCorrectPreview = false; // WLEDMM + CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8 + float light_gc_bri = light["gc"]["bri"]; + float light_gc_col = light["gc"]["col"]; + float light_gc_prev = light["gc"]["prev"]; // WLEDMM + if (light_gc_bri > 1.0f) gammaCorrectBri = true; + else gammaCorrectBri = false; + if (light_gc_col > 1.0f) gammaCorrectCol = true; + else gammaCorrectCol = false; + if (light_gc_prev > 1.0f) gammaCorrectPreview = true; // WLEDMM + else gammaCorrectPreview = false; // WLEDMM + if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { + if (gammaCorrectVal != 2.8f) calcGammaTable(gammaCorrectVal); + } else { + gammaCorrectVal = 1.0f; // no gamma correction + gammaCorrectBri = false; + gammaCorrectCol = false; + gammaCorrectPreview = false; // WLEDMM + } + + JsonObject light_tr = light["tr"]; + CJSON(fadeTransition, light_tr["mode"]); + tdd = light_tr["dur"] | -1; + if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; + CJSON(strip.paletteFade, light_tr["pal"]); + CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); + + JsonObject light_nl = light["nl"]; + CJSON(nightlightMode, light_nl["mode"]); + prev = nightlightDelayMinsDefault; + CJSON(nightlightDelayMinsDefault, light_nl["dur"]); + if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault; + + CJSON(nightlightTargetBri, light_nl[F("tbri")]); + CJSON(macroNl, light_nl["macro"]); } - JsonObject light_tr = light["tr"]; - CJSON(fadeTransition, light_tr["mode"]); - int tdd = light_tr["dur"] | -1; - if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; - CJSON(strip.paletteFade, light_tr["pal"]); - CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); - - JsonObject light_nl = light["nl"]; - CJSON(nightlightMode, light_nl["mode"]); - byte prev = nightlightDelayMinsDefault; - CJSON(nightlightDelayMinsDefault, light_nl["dur"]); - if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault; - - CJSON(nightlightTargetBri, light_nl[F("tbri")]); - CJSON(macroNl, light_nl["macro"]); - JsonObject def = doc["def"]; CJSON(bootPreset, def["ps"]); CJSON(turnOnAtBoot, def["on"]); // true @@ -786,6 +790,11 @@ void serializeConfig() { matrix[F("pv")] = strip.matrix.vertical; matrix["ps"] = strip.matrix.serpentine; + matrix[F("pbl")] = strip.panelO.bottomStart; + matrix[F("prl")] = strip.panelO.rightStart; + matrix[F("pvl")] = strip.panelO.vertical; + matrix["psl"] = strip.panelO.serpentine; + JsonArray panels = matrix.createNestedArray(F("panels")); for (uint8_t i=0; i 3000) requestJson();} function sCol(na, col) {d.documentElement.style.setProperty(na, col);} @@ -866,7 +865,7 @@ function populateSegments(s) } if (segCount < 2) { gId(`segd${lSeg}`).classList.add("hide"); - gId(`segp0`).classList.add("hide"); + if (parseInt(gId("seg0bri").value)==255) gId(`segp0`).classList.add("hide"); } if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value) 1) ? "block":"none"; // rsbtn parent @@ -1055,9 +1054,9 @@ function bname(o) //WLEDMM call a node with json api command function callNode(ip, type, json) { - console.log("callNode", ip, json); + console.log("callNode", ip, type, json); - fetch('http://'+ip+'/json/'+type, { + fetch('http://' + ip + '/json/' + type, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -1075,99 +1074,265 @@ function callNode(ip, type, json) { } function ddpAll() { + if (!confirm('Press Yes/OK if you know what you are doing!')) return; ins = []; start = 0; order = 0; - for (var node of extendedNodes) { - console.log(node); - output = {}; - output.start = start; //increase with count - output.len = node.count; - output.pin = node.ip.split("."); - output.order = order++; - output.rev = false; - output.skip = 0; - output.type = 80; - output.ref = false; - output.rgbm = 0; - // "ins":[{"start":0,"len":24,"pin":[2],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0}, - // {"start":24,"len":241,"pin":[192,168,121,57],"order":1,"rev":false,"skip":0,"type":80,"ref":false,"rgbwm":0}] - ins.push(output); - start+=node.count; + for (var node of nodesData) { + if (node.info.ip != lastinfo.ip) { //do not add to self + console.log(node); + output = {}; + output.start = start; //increase with count + output.len = node.info.leds.count; + output.pin = node.info.ip.split("."); + output.order = order++; + output.rev = false; + output.skip = 0; + output.type = 80; + output.ref = false; + output.rgbm = 0; + // "ins":[{"start":0,"len":24,"pin":[2],"order":0,"rev":false,"skip":0,"type":22,"ref":false,"rgbwm":0}, + // {"start":24,"len":241,"pin":[192,168,121,57],"order":1,"rev":false,"skip":0,"type":80,"ref":false,"rgbwm":0}] + ins.push(output); + start+=node.info.leds.count; + } + } + // console.log("ins", lastinfo.ip,JSON.stringify({"hw":{"led":{"ins":ins}}})); + + //update own cfg.json + callNode(lastinfo.ip, "cfg", {"hw":{"led":{"ins":ins}}}); //self +} + +//curl -s -F "update=@/Users/ewoudwijma/Developer/GitHub/MoonModules/WLED/build_output/release/WLEDMM_0.14.0-b27.31_esp32_4MB_M.bin" 192.168.8.105/update >nul & + +//WLEDMM +function SuperSync() { + if (!confirm('Press Yes/OK if you know what you are doing!')) return; + + for (i=0; i 2) { + errFound = true; + gId(`pnlC${nodeNr}`).style.color = "red"; + } + } + + //set the SuperSync Update needed column + gId(`ssu${nodeNr}`).innerText = errFound?"yes":"no"; + if (errFound) { + gId(`ssu${nodeNr}`).style.color = "red"; + } + } + + //fetch both cfg.json and info from a node and store in nodesData array + function fetchInfoAndCfg(ip, nodeNr, callback) { + //add td placeholders + urows += ``; + for (let nm of ["ins", "pwr", "ip", "type", "rel", "ver", "vid", "fx", "scale-bri", "gcc", "fps", "fpsr", "lpc", "lvc", "mrx", "pnl0", "pnlC", "pnlX", "ssu"]) + urows += ``; + urows += ``; + + //fetch info, state and effects + fetchAndExecute(`http://${ip}/`, "json/si", nodeNr, function(nodeNr, text) { + let info = JSON.parse(text)["info"]; + let state = JSON.parse(text)["state"]; + + //set values + let name = n.nodes[nodeNr].name; + let url = ``; + gId(`ins${nodeNr}`).innerHTML = url; + gId(`ip${nodeNr}`).innerText = ip; + gId(`pwr${nodeNr}`).innerHTML = ""; + gId(`type${nodeNr}`).innerText = info.arch; + gId(`vid${nodeNr}`).innerText = info.vid; + gId(`rel${nodeNr}`).innerText = info.rel; + gId(`ver${nodeNr}`).innerText = info.ver; + gId(`lvc${nodeNr}`).innerText = info.leds.count; + gId(`lpc${nodeNr}`).innerText = info.leds.countP; + gId(`fpsr${nodeNr}`).innerText = info.leds.fps; + gId(`fx${nodeNr}`).innerText = eJson.find((o)=>{return o.id==state.seg[0].fx}).name; + + //store data + if (!nodesData[nodeNr]) nodesData[nodeNr] = {}; + nodesData[nodeNr].info = info; + + //if the node has a matrix, show matrix info + if (info.leds.matrix) { + gId(`mrx${nodeNr}`).innerText = info.leds.matrix.w + "x" + info.leds.matrix.h; + } + + //fetch cfg.json + fetchAndExecute(`http://${ip}/`, "cfg.json", nodeNr, function(nodeNr, text) { + let cfg = JSON.parse(text); + + //set values + gId(`scale-bri${nodeNr}`).innerText = cfg.light["scale-bri"]; + gId(`gcc${nodeNr}`).innerText = cfg.light.gc.col > 1; + gId(`fps${nodeNr}`).innerText = cfg.hw.led.fps; + + //if the node has a matrix, show matrix info + if (cfg.hw.led.matrix) { + gId(`pnl0${nodeNr}`).innerText = showPanel(cfg.hw.led.matrix.panels[0]); //show the first panel + gId(`pnlC${nodeNr}`).innerText = cfg.hw.led.matrix.panels.length; //show nr of panels + + //if self, assign it's matrix details to all others + if (ip == lastinfo.ip) { + let panelIndex = 0; //loop over panels + for (let i=0; i (a.name).localeCompare(b.name)); + + n.nodes.sort((a,b) => (a.name).localeCompare(b.name)); //alphabetic on name // console.log("populateNodes",i,n); - //loop over nodes e.g. {name: "MM 32 L", type: 32, ip: "192.168.121.249", age: 1, vid: 2305080} - for (var o of n.nodes) { + + //set table header + urows += ``; + for (let nm of ["Instance", "Power", "IP", "Type", "Release", "Version", "Build", "Effect", "Bri%", "Gamma", "FPS", "FPS Real", "LedsP#", "LedsV#", "Matrix", "Panel0", "Panels", "PanelX", "SSync"]) + urows += `${nm}`; + urows += ``; + + //show other nodes e.g. {name: "MM 32 L", type: 32, ip: "192.168.121.249", age: 1, vid: 2305080} + var nodesDone = 0; + for (let o of n.nodes) { if (o.name) { - var url = ``; - // urows += inforow(url,`${btype(o.type)}
${o.vid==0?"N/A":o.vid} ${o.mode}`); - - //WLEDMM fetch json from nodes and add in table rows - urows += `${url}${btype(o.type)}
${o.vid==0?"N/A":o.vid}`; - // console.log("Node", o); if (o.ip) { //in ap mode no ip... - //fetch the rest of the nodes info - fetchAndExecute(`http://${o.ip}/`, "json", nnodes, function(nnodes,text) { - // console.log(text); - let state = JSON.parse(text)["state"]; - let info = JSON.parse(text)["info"]; - let effects = JSON.parse(text)["effects"]; - let nodeInfo = {}; - // console.log(nnodes, state, info, effects); - nodeInfo.ip = info.ip; - nodeInfo.count = info.leds.count; - //append to table row - - // gId(`node${nnodes}`).appendChild(addEl('td', ``)); - gId(`node${nnodes}`).appendChild(addEl('td', "")); - // ${i.opt&0x100?inforow("Net Print ☾",""):''} - gId(`node${nnodes}`).appendChild(addEl('td', info["ip"])); - gId(`node${nnodes}`).appendChild(addEl('td', info["rel"])); - gId(`node${nnodes}`).appendChild(addEl('td', info["ver"])); - gId(`node${nnodes}`).appendChild(addEl('td', info["leds"]["count"])); - gId(`node${nnodes}`).appendChild(addEl('td', effects[state["seg"][0]["fx"]])); - if (info["leds"]["matrix"]) - gId(`node${nnodes}`).appendChild(addEl('td', info["leds"]["matrix"]["w"] + "x" + info["leds"]["matrix"]["h"])); - if (nodeInfo.ip != thisNode.ip) - extendedNodes.push(nodeInfo); + fetchInfoAndCfg(o.ip, nnodes, function(nodeNr) { + nodesDone++; + console.log("nodesDone", nodesDone, nodeNr, n.nodes.length, nnodes, n.nodes[nodeNr].name); + //if all done + if (nodesDone == n.nodes.length ) { + for (let i=0; i - ${urows} - `; - cn += "" + else if (!n.nodes) cn += `No other instances found.`; + cn += `${urows}
`; + cn += ``; + cn += ``; gId('kn').innerHTML = cn; // ${inforow("Current instance:",i.name)} //WLEDMM current instance is now also shown as node } @@ -1196,7 +1361,7 @@ function updateTrail(e) { if (e==null) return; let sd = e.parentNode.getElementsByClassName('sliderdisplay')[0]; - if (sd && getComputedStyle(sd).getPropertyValue("--bg") !== "none") { + if (sd && getComputedStyle(sd).getPropertyValue("--bg").trim() !== "none") { // trim() for Safari var max = e.hasAttribute('max') ? e.attributes.max.value : 255; var perc = Math.round(e.value * 100 / max); if (perc < 50) perc += 2; @@ -1556,7 +1721,7 @@ function updateUI() if (hasRGB) { updateTrail(gId('sliderR')); updateTrail(gId('sliderG')); - updateTrail(gId('sliderB')); + updateTrail(gId('sliderB')); } if (hasWhite) updateTrail(gId('sliderW')); @@ -1595,7 +1760,7 @@ function updateSelectedPalette(s) if (s > 1 && s < 6) { cd[0].classList.remove('hide'); // * Color 1 if (s > 2) cd[1].classList.remove('hide'); // * Color 1 & 2 - if (s == 5) cd[2].classList.remove('hide'); // all colors + if (s > 3) cd[2].classList.remove('hide'); // all colors } else { for (let i of cd) if (i.dataset.hide == '1') i.classList.add('hide'); } @@ -2269,7 +2434,7 @@ function makeP(i,pl) end: 0 }; var rep = plJson[i].repeat ? plJson[i].repeat : 0; - content = + content = `