Merge branch 'MoonModules:mdev' into Strip_Level_Color_Adjust

This commit is contained in:
Troy
2024-04-17 10:04:54 -04:00
committed by GitHub
25 changed files with 789 additions and 71 deletions

View File

@@ -48,7 +48,7 @@ body:
attributes: attributes:
label: What version/release of MM WLED? label: What version/release of MM WLED?
description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message" description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message"
placeholder: "e.g. build 2401290, WLEDMM_0.14.1-b30.36_esp32_4MB_M.bin" placeholder: "e.g. build 2401290, WLEDMM_0.14.1-b30.37_esp32_4MB_M.bin"
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@@ -1,5 +1,37 @@
## [WLED upstream](https://github.com/Aircoookie/WLED/tree/0_14_1) changelog ## [WLED upstream](https://github.com/Aircoookie/WLED/tree/0_14_1) changelog
#### Build 2403290
- WLED 0.14.3 release
- Fix for transition 0 (#3854, #3832, #3720)
#### Build 2403170
- WLED 0.14.2 release
#### Build 2403110
- Beta WLED 0.14.2-b2
- Fixing a potential array bounds violation in ESPDMX
- LockedJsonResponse: Release early if possible (by @willmmiles)
#### Build 2402120
- Beta WLED 0.14.2-b1
- Possible fix for #3589 & partial fix for #3605
- Prevent JSON buffer clear after failed lock attempt
- Multiple analog button fix for #3549
- UM Audioreactive: add two compiler options (#3732 by @wled-install)
- Fix for #3693
#### Build 2401141
- Official release of WLED 0.14.1
- Fix for #3566, #3665, #3672
- Sorting of palettes in custom palette editor (#3674 by @WoodyLetsCode)
#### Build 2401060
- Version bump: 0.14.1-b3
- Global JSON buffer guarding (#3648 by @willmmiles, resolves #3641, #3312, #3367, #3637, #3646, #3447)
- Fix for #3632
- Custom palette editor mobile UI enhancement (#3617 by @imeszaros)
- changelog update
#### Build 2312290 #### Build 2312290
- Fix for #3622 - Fix for #3622
- NB: fix for #3613 #3609 are not needed in MoonModules fork - NB: fix for #3613 #3609 are not needed in MoonModules fork
@@ -61,6 +93,12 @@
- Improved ESP8266 stability by reducing WebSocket response resends - Improved ESP8266 stability by reducing WebSocket response resends
- Updated ESP8266 core to 3.1.2 - Updated ESP8266 core to 3.1.2
#### Build 2306180
- Added client-side option for applying effect defaults from metadata
- Improved ESP8266 stability by reducing WebSocket response resends
- Updated ESP8266 core to 3.1.2
#### Build 2306141 #### Build 2306141
- Lissajous improvements - Lissajous improvements
- Scrolling Text improvements (leading 0) - Scrolling Text improvements (leading 0)

View File

@@ -2,6 +2,16 @@
Here are a few suggestions to make it easier for you to contribute! Here are a few suggestions to make it easier for you to contribute!
### Describe your PR
Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing.
A good description helps us to review and understand your proposed changes. For example, you could say a few words about
* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.)
* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code)
* testing you performed, known limitations, open ends you possibly could not solve.
* any areas where you like to get help from an experienced maintainer (yes WLEDMM has become big 😉)
### Code style ### Code style
When in doubt, it is easiest to replicate the code style you find in the files you want to edit :) When in doubt, it is easiest to replicate the code style you find in the files you want to edit :)
@@ -79,4 +89,5 @@ Good:
There is no set character limit for a comment within a line, There is no set character limit for a comment within a line,
though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window. though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window.
Inline comments are OK if they describe that line only and are not exceedingly wide. Inline comments are OK if they describe that line only and are not exceedingly wide.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "wled", "name": "wled",
"version": "0.14.1-b30.36", "version": "0.14.1-b30.37",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wled", "name": "wled",
"version": "0.14.1-b30.36", "version": "0.14.1-b30.37",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"clean-css": "^4.2.3", "clean-css": "^4.2.3",

View File

@@ -1,6 +1,6 @@
{ {
"name": "wled", "name": "wled",
"version": "0.14.1-b30.36", "version": "0.14.1-b30.37",
"description": "Tools for WLED project", "description": "Tools for WLED project",
"main": "tools/cdata.js", "main": "tools/cdata.js",
"directories": { "directories": {

View File

@@ -113,7 +113,7 @@ arduino_core_2_7_4 = espressif8266@2.6.2
arduino_core_3_0_0 = espressif8266@3.0.0 arduino_core_3_0_0 = espressif8266@3.0.0
arduino_core_3_2_0 = espressif8266@3.2.0 arduino_core_3_2_0 = espressif8266@3.2.0
arduino_core_4_1_0 = espressif8266@4.1.0 arduino_core_4_1_0 = espressif8266@4.1.0
arduino_core_3_1_2 = espressif8266@4.2.0 arduino_core_3_1_2 = espressif8266@4.2.1
# Development platforms # Development platforms
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
@@ -123,8 +123,7 @@ arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/
platform_wled_default = ${common.arduino_core_3_1_2} platform_wled_default = ${common.arduino_core_3_1_2}
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization # We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
#platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 #platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
platform_packages = platformio/framework-arduinoespressif8266 platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502
platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502
platformio/tool-esptool #@ ~1.413.0 platformio/tool-esptool #@ ~1.413.0
platformio/tool-esptoolpy #@ ~1.30000.0 platformio/tool-esptoolpy #@ ~1.30000.0
@@ -241,6 +240,7 @@ lib_deps =
;;makuna/NeoPixelBus @ 2.7.5 ;; WLEDMM will be added in board specific sections ;;makuna/NeoPixelBus @ 2.7.5 ;; WLEDMM will be added in board specific sections
;;https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 ;;https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7
https://github.com/lost-hope/ESPAsyncWebServer.git#master ;; WLEDMM to display .log and .wled files in /edit https://github.com/lost-hope/ESPAsyncWebServer.git#master ;; WLEDMM to display .log and .wled files in /edit
;; https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.2.1
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
#TFT_eSPI #TFT_eSPI
#For compatible OLED display uncomment following #For compatible OLED display uncomment following
@@ -452,6 +452,11 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder monitor_filters = esp8266_exception_decoder
[env:nodemcuv2_160]
extends = env:nodemcuv2
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D
[env:esp8266_2m] [env:esp8266_2m]
board = esp_wroom_02 board = esp_wroom_02
platform = ${common.platform_wled_default} platform = ${common.platform_wled_default}
@@ -461,6 +466,11 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m_160]
extends = env:esp8266_2m
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02_160
;WLEDMM: see below ;WLEDMM: see below
; [env:esp01_1m_full] ; [env:esp01_1m_full]
; board = esp01_1m ; board = esp01_1m
@@ -471,6 +481,11 @@ lib_deps = ${esp8266.lib_deps}
; build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA ; 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 ; ; -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} ; lib_deps = ${esp8266.lib_deps}
; [env:esp01_1m_full_160]
; extends = env:esp01_1m_full
; board_build.f_cpu = 160000000L
; build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA
; ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM
[env:esp07] [env:esp07]
board = esp07 board = esp07
@@ -963,6 +978,7 @@ build_flags_S =
; -D WLED_DISABLE_2D ;; un-comment to build a firmware without 2D matrix support ; -D WLED_DISABLE_2D ;; un-comment to build a firmware without 2D matrix support
; -D WLED_USE_CIE_BRIGHTNESS_TABLE ;; experimental: use different color / brightness lookup table ; -D WLED_USE_CIE_BRIGHTNESS_TABLE ;; experimental: use different color / brightness lookup table
-D USERMOD_AUDIOREACTIVE -D USERMOD_AUDIOREACTIVE
-D USERMOD_AUTO_PLAYLIST
-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 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 - temporarily moved into "_M", due to problems in "_S" when compiling with -O2 ; -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 WLEDMM_FASTPATH ;; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates. May break compatibility with previous versions.

View File

@@ -7,6 +7,7 @@
#include <driver/i2s.h> #include <driver/i2s.h>
#include <driver/adc.h> #include <driver/adc.h>
#include <math.h>
#endif #endif
#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) #if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG))
@@ -141,6 +142,8 @@ static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0}; // Our calculated freq. chann
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. (also used by dynamics limiter) static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. (also used by dynamics limiter)
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
static uint16_t zeroCrossingCount = 0; // number of zero crossings in the current batch of 512 samples
// TODO: probably best not used by receive nodes // TODO: probably best not used by receive nodes
static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255
@@ -543,15 +546,27 @@ void FFTcode(void * parameter)
} }
} }
// find highest sample in the batch // set imaginary parts to 0
memset(vImag, 0, sizeof(vImag));
// find highest sample in the batch, and count zero crossings
float maxSample = 0.0f; // max sample from FFT batch float maxSample = 0.0f; // max sample from FFT batch
uint_fast16_t newZeroCrossingCount = 0;
for (int i=0; i < samplesFFT; i++) { for (int i=0; i < samplesFFT; i++) {
// set imaginary parts to 0
vImag[i] = 0;
// pick our our current mic sample - we take the max value from all samples that go into FFT // pick our our current mic sample - we take the max value from all samples that go into FFT
if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts
if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]);
// WLED-MM/TroyHacks: Calculate zero crossings
//
if (i < (samplesFFT-1)) {
if (__builtin_signbit(vReal[i]) != __builtin_signbit(vReal[i+1])) // test sign bit: sign changed -> zero crossing
newZeroCrossingCount++;
}
} }
newZeroCrossingCount = (newZeroCrossingCount*2)/3; // reduce value so it typicially stays below 256
zeroCrossingCount = newZeroCrossingCount; // update only once, to avoid that effects pick up an intermediate value
// release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function // release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function
// early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results.
micDataReal = maxSample; micDataReal = maxSample;
@@ -770,8 +785,7 @@ void FFTcode(void * parameter)
// run peak detection // run peak detection
autoResetPeak(); autoResetPeak();
detectSamplePeak(); detectSamplePeak();
// we have new results - notify UDP sound send
haveNewFFTResult = true; haveNewFFTResult = true;
#if !defined(I2S_GRAB_ADC1_COMPLETELY) #if !defined(I2S_GRAB_ADC1_COMPLETELY)
@@ -1007,7 +1021,7 @@ class AudioReactive : public Usermod {
uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude
uint8_t frameCounter; // 01 Bytes offset 17 - track duplicate/out of order packets uint8_t frameCounter; // 01 Bytes offset 17 - track duplicate/out of order packets
uint8_t fftResult[16]; // 16 Bytes offset 18 uint8_t fftResult[16]; // 16 Bytes offset 18
uint8_t gap2[2]; // gap added by compiler: 02 Bytes, offset 34 uint16_t zeroCrossingCount; // 02 Bytes, offset 34
float FFT_Magnitude; // 04 Bytes offset 36 float FFT_Magnitude; // 04 Bytes offset 36
float FFT_MajorPeak; // 04 Bytes offset 40 float FFT_MajorPeak; // 04 Bytes offset 40
}; };
@@ -1547,6 +1561,7 @@ class AudioReactive : public Usermod {
transmitData.samplePeak = udpSamplePeak ? 1:0; transmitData.samplePeak = udpSamplePeak ? 1:0;
udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it
transmitData.frameCounter = frameCounter; transmitData.frameCounter = frameCounter;
transmitData.zeroCrossingCount = zeroCrossingCount;
for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { for (int i = 0; i < NUM_GEQ_CHANNELS; i++) {
transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254);
@@ -1619,6 +1634,7 @@ class AudioReactive : public Usermod {
FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
soundPressure = volumeSmth; // substitute - V2 format does not (yet) include this value soundPressure = volumeSmth; // substitute - V2 format does not (yet) include this value
agcSensitivity = 128.0f; // substitute - V2 format does not (yet) include this value agcSensitivity = 128.0f; // substitute - V2 format does not (yet) include this value
zeroCrossingCount = receivedPacket->zeroCrossingCount;
return true; return true;
} }
@@ -1720,7 +1736,7 @@ class AudioReactive : public Usermod {
// usermod exchangeable data // usermod exchangeable data
// we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers
um_data = new um_data_t; um_data = new um_data_t;
um_data->u_size = 11; um_data->u_size = 12;
um_data->u_type = new um_types_t[um_data->u_size]; um_data->u_type = new um_types_t[um_data->u_size];
um_data->u_data = new void*[um_data->u_size]; um_data->u_data = new void*[um_data->u_size];
um_data->u_data[0] = &volumeSmth; //*used (New) um_data->u_data[0] = &volumeSmth; //*used (New)
@@ -1746,6 +1762,8 @@ class AudioReactive : public Usermod {
um_data->u_type[9] = UMT_FLOAT; um_data->u_type[9] = UMT_FLOAT;
um_data->u_data[10] = &agcSensitivity; // used (New) um_data->u_data[10] = &agcSensitivity; // used (New)
um_data->u_type[10] = UMT_FLOAT; um_data->u_type[10] = UMT_FLOAT;
um_data->u_data[11] = &zeroCrossingCount;
um_data->u_type[11] = UMT_UINT16;
#else #else
// ESP8266 // ESP8266
// See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explanation of these alternative sources of data // See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explanation of these alternative sources of data
@@ -1760,6 +1778,8 @@ class AudioReactive : public Usermod {
um_data->u_type[9] = UMT_FLOAT; um_data->u_type[9] = UMT_FLOAT;
um_data->u_data[10] = &agcSensitivity; // used (New) - dummy value (128 => 50%) um_data->u_data[10] = &agcSensitivity; // used (New) - dummy value (128 => 50%)
um_data->u_type[10] = UMT_FLOAT; um_data->u_type[10] = UMT_FLOAT;
um_data->u_data[11] = &zeroCrossingCount;
um_data->u_type[11] = UMT_UINT16;
#endif #endif
} }
@@ -1921,7 +1941,7 @@ class AudioReactive : public Usermod {
USER_PRINTF("| uint8_t samplePeak offset = %2d size = %2d\n", offsetof(audioSyncPacket, samplePeak), sizeof(data.samplePeak)); // offset 16 size 1 USER_PRINTF("| uint8_t samplePeak offset = %2d size = %2d\n", offsetof(audioSyncPacket, samplePeak), sizeof(data.samplePeak)); // offset 16 size 1
USER_PRINTF("| uint8_t frameCounter offset = %2d size = %2d\n", offsetof(audioSyncPacket, frameCounter), sizeof(data.frameCounter)); // offset 17 size 1 USER_PRINTF("| uint8_t frameCounter offset = %2d size = %2d\n", offsetof(audioSyncPacket, frameCounter), sizeof(data.frameCounter)); // offset 17 size 1
USER_PRINTF("| uint8_t fftResult[16] offset = %2d size = %2d\n", offsetof(audioSyncPacket, fftResult[0]), sizeof(data.fftResult)); // offset 18 size 16 USER_PRINTF("| uint8_t fftResult[16] offset = %2d size = %2d\n", offsetof(audioSyncPacket, fftResult[0]), sizeof(data.fftResult)); // offset 18 size 16
USER_PRINTF("| uint8_t gap2[2] offset = %2d size = %2d\n", offsetof(audioSyncPacket, gap2[0]), sizeof(data.gap2)); // offset 34 size 2 USER_PRINTF("| uint16_t zeroCrossingCount offset = %2d size = %2d\n", offsetof(audioSyncPacket, zeroCrossingCount), sizeof(data.zeroCrossingCount)); // offset 34 size 2
USER_PRINTF("| float FFT_Magnitude offset = %2d size = %2d\n", offsetof(audioSyncPacket, FFT_Magnitude), sizeof(data.FFT_Magnitude));// offset 36 size 4 USER_PRINTF("| float FFT_Magnitude offset = %2d size = %2d\n", offsetof(audioSyncPacket, FFT_Magnitude), sizeof(data.FFT_Magnitude));// offset 36 size 4
USER_PRINTF("| float FFT_MajorPeak offset = %2d size = %2d\n", offsetof(audioSyncPacket, FFT_MajorPeak), sizeof(data.FFT_MajorPeak));// offset 40 size 4 USER_PRINTF("| float FFT_MajorPeak offset = %2d size = %2d\n", offsetof(audioSyncPacket, FFT_MajorPeak), sizeof(data.FFT_MajorPeak));// offset 40 size 4
USER_PRINTLN(); USER_FLUSH(); USER_PRINTLN(); USER_FLUSH();

View File

@@ -667,7 +667,13 @@ class WM8978Source : public I2SSource {
_wm8978I2cWrite( 1,0b000111110); // Power Management 1 - power off most things, but enable mic bias and I/O tie-off to help mitigate mic leakage. _wm8978I2cWrite( 1,0b000111110); // Power Management 1 - power off most things, but enable mic bias and I/O tie-off to help mitigate mic leakage.
_wm8978I2cWrite( 2,0b110111111); // Power Management 2 - enable output and amp stages (amps may lift signal but it works better on the ADCs) _wm8978I2cWrite( 2,0b110111111); // 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( 3,0b000001100); // Power Management 3 - enable L&R output mixers
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
_wm8978I2cWrite( 4,0b001010000); // Audio Interface - standard I2S, 24-bit _wm8978I2cWrite( 4,0b001010000); // Audio Interface - standard I2S, 24-bit
#else
_wm8978I2cWrite( 4,0b001001000); // Audio Interface - left-justified I2S, 24-bit
#endif
_wm8978I2cWrite( 6,0b000000000); // Clock generation control - use external mclk _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( 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(14,0b010001000); // 128x ADC oversampling - high pass filter disabled as it kills the bass response

View File

@@ -0,0 +1,510 @@
#pragma once
#ifdef WLED_DEBUG
#ifndef USERMOD_AUTO_PLAYLIST_DEBUG
#define USERMOD_AUTO_PLAYLIST_DEBUG
#endif
#endif
#include "wled.h"
class AutoPlaylistUsermod : public Usermod {
private:
// experimental parameters by softhack007 - more balanced but need testing
const uint_fast32_t MAX_DISTANCE_TRACKER = 184; // maximum accepted distance_tracker
const uint_fast32_t ENERGY_SCALE = 1500;
const float FILTER_SLOW1 = 0.0075f; // for "slow" energy - was 0.01f
const float FILTER_SLOW2 = 0.005f; // for "slow" lfc / zcr - was 0.01f
const float FILTER_FAST1 = 0.2f; // for "fast" energy - was 0.10f
const float FILTER_FAST2 = 0.25f; // for "fast" lfc / zcr - was 0.10f
const float FILTER_VOLUME = 0.03f; // for volumeSmth averaging - takes 8-10sec until "silence"
bool initDone = false;
bool functionality_enabled = false;
bool silenceDetected = true;
byte ambientPlaylist = 1;
byte musicPlaylist = 2;
int timeout = 60;
bool autoChange = false;
byte lastAutoPlaylist = 0;
unsigned long lastSoundTime = millis()-(timeout*1000)-100;
unsigned long change_timer = millis();
unsigned long autochange_timer = millis();
float avg_volumeSmth = 0;
// fftesult de-scaling factors: 2.8f / fftResultPink[]
const float fftDeScaler[NUM_GEQ_CHANNELS] = {2.8/2.35, 2.8/1.32, 2.8/1.32, 2.8/1.40, 2.8/1.48, 2.8/1.57, 2.8/1.68, 2.8/1.80, 2.8/1.89, 2.8/1.95, 2.8/2.14, 2.8/2.26, 2.8/2.50, 2.8/2.90, 2.8/4.20, 2.8/6.50};
uint_fast32_t energy = 0;
float avg_long_energy = 250;
float avg_long_lfc = 1000;
float avg_long_zcr = 500;
float avg_short_energy = 250;
float avg_short_lfc = 1000;
float avg_short_zcr = 500;
bool resetFilters = true; // to (re)initialize filters on first run
uint_fast32_t vector_energy = 0;
uint_fast32_t vector_lfc = 0;
uint_fast32_t vector_zcr = 0;
uint_fast32_t distance = 0;
uint_fast32_t distance_tracker = UINT_FAST32_MAX;
unsigned long lastchange = millis();
int_fast16_t change_threshold = 50; // arbitrary starting point.
uint_fast16_t change_threshold_change = 0;
int change_lockout = 1000; // never change below this number of millis. Ideally 60000/your_average_bpm*beats_to_skip = change_lockout (1000 = skip 2 beats at 120bpm)
int ideal_change_min = 10000; // ideally change patterns no less than this number of millis
int ideal_change_max = 20000; // ideally change patterns no more than this number of millis
std::vector<int> autoChangeIds;
static const char _name[];
static const char _autoPlaylistEnabled[];
static const char _ambientPlaylist[];
static const char _musicPlaylist[];
static const char _timeout[];
static const char _autoChange[];
static const char _change_lockout[];
static const char _ideal_change_min[];
static const char _ideal_change_max[];
public:
AutoPlaylistUsermod(bool enabled):Usermod("AutoPlaylist", enabled) {
// noop
}
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup() {
USER_PRINTLN("AutoPlaylistUsermod");
initDone = true;
}
// gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here
void connected() {
// noop
}
void change(um_data_t *um_data) {
uint8_t *fftResult = (uint8_t*)um_data->u_data[2];
energy = 0;
for (int i=0; i < NUM_GEQ_CHANNELS; i++) {
// make an attempt to undo some "trying to look better" FFT manglings in AudioReactive postProcessFFTResults()
float amplitude = float(fftResult[i]) * fftDeScaler[i]; // undo "pink noise" scaling
amplitude /= 0.85f + (float(i)/4.5f); // undo extra up-scaling for high frequencies
energy += roundf(amplitude * amplitude); // calc energy from amplitude
}
energy /= ENERGY_SCALE; // scale down so we get 0 sometimes
uint16_t lfc = float(fftResult[0]) * fftDeScaler[0] / 0.85f; // might as well undo pink noise here too.
uint16_t zcr = *(uint16_t*)um_data->u_data[11];
// WLED-MM/TroyHacks: Calculate the long- and short-running averages
// and the individual vector distances.
if (volumeSmth > 1.0f) {
// initialize filters on first run
if (resetFilters) {
avg_short_energy = avg_long_energy = energy;
avg_short_lfc = avg_long_lfc = lfc;
avg_short_zcr = avg_long_zcr = zcr;
resetFilters = false;
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTLN("AutoPlaylist: Filters reset.");
#endif
}
avg_long_energy = avg_long_energy + FILTER_SLOW1 * (float(energy) - avg_long_energy);
avg_long_lfc = avg_long_lfc + FILTER_SLOW2 * (float(lfc) - avg_long_lfc);
avg_long_zcr = avg_long_zcr + FILTER_SLOW2 * (float(zcr) - avg_long_zcr);
avg_short_energy = avg_short_energy + FILTER_FAST1 * (float(energy) - avg_short_energy);
avg_short_lfc = avg_short_lfc + FILTER_FAST2 * (float(lfc) - avg_short_lfc);
avg_short_zcr = avg_short_zcr + FILTER_FAST2 * (float(zcr) - avg_short_zcr);
// allegedly this is faster than pow(whatever,2)
vector_lfc = (avg_short_lfc-avg_long_lfc)*(avg_short_lfc-avg_long_lfc);
vector_energy = (avg_short_energy-avg_long_energy)*(avg_short_energy-avg_long_energy);
vector_zcr = (avg_short_zcr-avg_long_zcr)*(avg_short_zcr-avg_long_zcr);
}
distance = vector_lfc + vector_energy + vector_zcr;
long change_interval = millis()-lastchange;
if (distance < distance_tracker && change_interval > change_lockout && volumeSmth > 1.0f) {
distance_tracker = distance;
}
// Debug for adjusting formulas, etc:
// USER_PRINTF("Distance: %5lu - v_lfc: %5lu v_energy: %5lu v_zcr: %5lu\n",(unsigned long)distance,(unsigned long)vector_lfc,(unsigned long)vector_energy,(unsigned long)vector_zcr);
if ((millis() - change_timer) > ideal_change_min) { // softhack007 same result as "millis() > change_timer + ideal_change_min", but more robust against unsigned overflow
// Make the analysis less sensitive if we miss the window.
// Sometimes the analysis lowers the change_threshold too much for
// the current music, especially after track changes or during
// sparse intros and breakdowns.
if (change_interval > ideal_change_min && distance_tracker <= MAX_DISTANCE_TRACKER) {
if (distance_tracker >= change_threshold) {
change_threshold_change = distance_tracker-change_threshold;
} else {
change_threshold_change = change_threshold-distance_tracker;
}
change_threshold = distance_tracker;
if (change_threshold_change > 9999) change_threshold_change = 0; // cosmetic for debug
if (functionality_enabled) {
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("--- lowest distance = %4lu - no changes done in %6ldms - next change_threshold is %4u (%4u diff approx)\n", (unsigned long)distance_tracker,change_interval,change_threshold,change_threshold_change);
#endif
}
distance_tracker = UINT_FAST32_MAX;
}
change_timer = millis();
}
if (distance <= change_threshold && change_interval > change_lockout && volumeSmth > 1.0f) {
change_threshold_change = max(1.0f, roundf(change_threshold-(distance*0.9f))); // exclude negatives, ensure change_threshold_change is always >= 1
if (change_interval > ideal_change_max) {
change_threshold += change_threshold_change; // make changes more sensitive
} else if (change_interval < ideal_change_min) {
change_threshold -= change_threshold_change; // make changes less sensitive
} else {
change_threshold_change = 0; // change was within our window, no sensitivity change
}
if (change_threshold < 1) change_threshold = 0; // we need change_threshold to be signed because otherwise this wraps to UINT_FAST16_MAX
distance_tracker = UINT_FAST32_MAX;
if (functionality_enabled) {
if (autoChangeIds.size() == 0) {
if(currentPlaylist < 1) return;
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("Loading presets from playlist: %3d\n", currentPlaylist);
#endif
JsonObject playtlistOjb = doc.to<JsonObject>();
serializePlaylist(playtlistOjb);
JsonArray playlistArray = playtlistOjb["playlist"]["ps"];
for(JsonVariant v : playlistArray) {
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("Adding %3u to autoChangeIds\n", v.as<int>());
#endif
autoChangeIds.push_back(v.as<int>());
}
}
uint8_t newpreset = 0;
do {
newpreset = autoChangeIds.at(random(0, autoChangeIds.size())); // random() is *exclusive* of the last value, so it's OK to use the full size.
} while ((currentPreset == newpreset) && (autoChangeIds.size() > 1)); // make sure we get a different random preset. Unless there is only one.
if (change_interval > change_lockout+3) {
// Make sure we have a statistically significant change and we aren't
// just bouncing off change_lockout. That's valid for changing the
// thresholds, but might be a bit crazy for lighting changes.
// When the music changes quite a bit, the distance calculation can
// go into freefall - this logic stops that from triggering right
// after change_lockout. Better for smaller change_lockout values.
suspendPlaylist(); // suspend the playlist engine before changing to another preset
applyPreset(newpreset);
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("*** CHANGE distance = %4lu - change_interval was %5ldms - next change_threshold is %4u (%4u diff aprox)\n",(unsigned long)distance,change_interval,change_threshold,change_threshold_change);
#endif
} else {
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("^^^ SKIP!! distance = %4lu - change_interval was %5ldms - next change_threshold is %4u (%4u diff aprox)\n",(unsigned long)distance,change_interval,change_threshold,change_threshold_change);
#endif
}
}
lastchange = millis();
change_timer = millis();
}
}
/*
* Da loop.
*/
void loop() {
if (!enabled) return;
if (millis() < 10000) return; // Wait for device to settle
if (lastAutoPlaylist > 0 && currentPlaylist != lastAutoPlaylist && currentPreset != 0) {
if (functionality_enabled) {
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("AutoPlaylist: disable due to manual change of playlist from %u to %d, preset:%u\n", lastAutoPlaylist, currentPlaylist, currentPreset);
#endif
functionality_enabled = false;
} else if (currentPlaylist == musicPlaylist) {
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("AutoPlaylist: enabled due to manual change of playlist back to %u\n", currentPlaylist);
#endif
functionality_enabled = true;
lastAutoPlaylist = currentPlaylist;
}
}
if (!functionality_enabled && currentPlaylist == musicPlaylist) {
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("AutoPlaylist: enabled due selecting musicPlaylist(%u)\n", musicPlaylist);
#endif
functionality_enabled = true;
}
if (bri == 0) return;
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
// No Audio Reactive
silenceDetected = true;
return;
}
float volumeSmth = *(float*)um_data->u_data[0];
avg_volumeSmth = avg_volumeSmth + FILTER_VOLUME * (volumeSmth - avg_volumeSmth);
if (avg_volumeSmth >= 1.0f) {
lastSoundTime = millis();
}
if (millis() - lastSoundTime > (long(timeout) * 1000)) {
if (!silenceDetected) {
silenceDetected = true;
USER_PRINTLN("AutoPlaylist: Silence detected");
changePlaylist(ambientPlaylist);
}
} else {
if (silenceDetected) {
silenceDetected = false;
USER_PRINTLN("AutoPlaylist: Sound detected");
changePlaylist(musicPlaylist);
}
if (autoChange && millis() >= autochange_timer+22) {
change(um_data);
autochange_timer = millis();
}
}
}
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void addToJsonInfo(JsonObject& root) {
JsonObject user = root["u"];
if (user.isNull()) {
user = root.createNestedObject("u");
}
if (!enabled) return; // usermod disabled -> don't add to info page
String uiNameString = FPSTR(_name);
if (enabled && functionality_enabled) {
uiNameString += F(" Running");
} else if (!enabled) {
uiNameString += F(" Disabled");
} else {
uiNameString += F(" Suspended");
}
JsonArray infoArr = user.createNestedArray(uiNameString); // name + status
String uiDomString = (currentPlaylist > 0) ? String("#") + String(currentPlaylist) + String(" ") : String("");
if (currentPlaylist == musicPlaylist && currentPlaylist > 0) {
uiDomString += F("Music Playlist");
} else if (currentPlaylist == ambientPlaylist && currentPlaylist > 0) {
uiDomString += F("Ambient Playlist");
} else {
uiDomString += F("Playlist Overridden");
}
uiDomString += F("<br />");
if (enabled && autoChange && currentPlaylist == musicPlaylist && functionality_enabled) {
uiDomString += F("AutoChange is Active");
} else if (autoChange && (currentPlaylist != musicPlaylist || !functionality_enabled || !enabled)) {
uiDomString += F("AutoChange on Stand-by");
} else if (!autoChange) {
uiDomString += F("AutoChange is Disabled");
}
// #ifdef USERMOD_AUTO_PLAYLIST_DEBUG
// uiDomString += F("<br />");
// uiDomString += F("Change Threshold: ");
// uiDomString += String(change_threshold);
// #endif
infoArr.add(uiDomString);
}
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root) {
return;
}
void appendConfigData() {
oappend(SET_F("addHB('AutoPlaylist');"));
}
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
* If you want to force saving the current state, use serializeConfig() in your loop().
*
* CAUTION: serializeConfig() will initiate a filesystem write operation.
* It might cause the LEDs to stutter and will cause flash wear if called too often.
* Use it sparingly and always in the loop, never in network callbacks!
*
* addToConfig() will also not yet add your setting to one of the settings pages automatically.
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
*
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/
void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_autoPlaylistEnabled)] = enabled;
top[FPSTR(_timeout)] = timeout;
top[FPSTR(_ambientPlaylist)] = ambientPlaylist; // usermodparam
top[FPSTR(_musicPlaylist)] = musicPlaylist; // usermodparam
top[FPSTR(_autoChange)] = autoChange;
top[FPSTR(_change_lockout)] = change_lockout;
top[FPSTR(_ideal_change_min)] = ideal_change_min;
top[FPSTR(_ideal_change_max)] = ideal_change_max;
lastAutoPlaylist = 0;
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTLN(F("AutoPlaylist config saved."));
#endif
}
/*
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
* This is called by WLED when settings are loaded (currently this only happens once immediately after boot)
*
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject& root) {
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
USER_PRINT(FPSTR(_name));
USER_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
enabled = top[FPSTR(_autoPlaylistEnabled)] | enabled;
timeout = top[FPSTR(_timeout)] | timeout;
ambientPlaylist = top[FPSTR(_ambientPlaylist)] | ambientPlaylist;
musicPlaylist = top[FPSTR(_musicPlaylist)] | musicPlaylist;
autoChange = top[FPSTR(_autoChange)] | autoChange;
change_lockout = top[FPSTR(_change_lockout)] | change_lockout;
ideal_change_min = top[FPSTR(_ideal_change_min)] | ideal_change_min;
ideal_change_max = top[FPSTR(_ideal_change_max)] | ideal_change_max;
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINT(FPSTR(_name));
USER_PRINTLN(F(" config (re)loaded."));
#endif
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_AUTOPLAYLIST;
}
private:
void changePlaylist(byte id) {
String name = "";
getPresetName(id, name);
#ifdef USERMOD_AUTO_PLAYLIST_DEBUG
USER_PRINTF("AutoPlaylist: Applying \"%s\"\n", name.c_str());
#endif
// if (currentPlaylist != id) { // un-comment to only change on "real" changes
unloadPlaylist(); // applying a preset requires to unload previous playlist
applyPreset(id, CALL_MODE_NOTIFICATION);
// }
lastAutoPlaylist = id;
}
};
const char AutoPlaylistUsermod::_name[] PROGMEM = "AutoPlaylist";
const char AutoPlaylistUsermod::_autoPlaylistEnabled[] PROGMEM = "enabled";
const char AutoPlaylistUsermod::_ambientPlaylist[] PROGMEM = "ambientPlaylist";
const char AutoPlaylistUsermod::_musicPlaylist[] PROGMEM = "musicPlaylist";
const char AutoPlaylistUsermod::_timeout[] PROGMEM = "timeout";
const char AutoPlaylistUsermod::_autoChange[] PROGMEM = "autoChange";
const char AutoPlaylistUsermod::_change_lockout[] PROGMEM = "change_lockout";
const char AutoPlaylistUsermod::_ideal_change_min[] PROGMEM = "ideal_change_min";
const char AutoPlaylistUsermod::_ideal_change_max[] PROGMEM = "ideal_change_max";

View File

@@ -3194,7 +3194,13 @@ static uint16_t mode_popcorn_core(bool useaudio) {
if (SEGLEN == 1) return mode_static(); if (SEGLEN == 1) return mode_static();
//allocate segment data //allocate segment data
uint16_t strips = SEGMENT.nrOfVStrips(); uint16_t strips = SEGMENT.nrOfVStrips();
uint16_t dataSize = sizeof(spark) * maxNumPopcorn; size_t dataSize = sizeof(spark) * maxNumPopcorn;
uint8_t neededPopcorn = maxNumPopcorn; // WLEDMM
if (strips > 8) { // WLEDMM more than 8 virtual strips --> reduce memory requirements to minimum necessary
neededPopcorn = (SEGMENT.intensity*maxNumPopcorn)/255;
neededPopcorn = min(max(neededPopcorn, uint8_t(2)), uint8_t(maxNumPopcorn));
dataSize = sizeof(spark) * neededPopcorn;
}
if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed
Spark* popcorn = reinterpret_cast<Spark*>(SEGENV.data); Spark* popcorn = reinterpret_cast<Spark*>(SEGENV.data);
@@ -3212,7 +3218,7 @@ static uint16_t mode_popcorn_core(bool useaudio) {
struct virtualStrip { struct virtualStrip {
static void runStrip(uint16_t stripNr, Spark* popcorn, bool useaudio, um_data_t *um_data) { // WLEDMM added useaudio and um_data static void runStrip(uint16_t stripNr, Spark* popcorn, bool useaudio, um_data_t *um_data) { // WLEDMM added useaudio and um_data
float gravity = -0.0001 - (SEGMENT.speed/200000.0); // m/s/s float gravity = -0.0001f - (SEGMENT.speed/200000.0f); // m/s/s
gravity *= SEGLEN; gravity *= SEGLEN;
uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255;
@@ -3267,7 +3273,7 @@ static uint16_t mode_popcorn_core(bool useaudio) {
}; };
for (int stripNr=0; stripNr<strips; stripNr++) for (int stripNr=0; stripNr<strips; stripNr++)
virtualStrip::runStrip(stripNr, &popcorn[stripNr * maxNumPopcorn], useaudio, um_data); // WLEDMM added useaudio and um_data virtualStrip::runStrip(stripNr, &popcorn[stripNr * neededPopcorn], useaudio, um_data); // WLEDMM added useaudio and um_data
return FRAMETIME; return FRAMETIME;
} }

View File

@@ -65,10 +65,10 @@ void WS2812FX::setUpMatrix() {
} }
USER_PRINTF("setUpMatrix %d x %d\n", Segment::maxWidth, Segment::maxHeight); USER_PRINTF("setUpMatrix %d x %d\n", Segment::maxWidth, Segment::maxHeight);
//WLEDMM recreate customMappingTable if more space needed //WLEDMM recreate customMappingTable if more space needed
if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) { if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) {
size_t size = max(ledmapMaxSize, size_t(Segment::maxWidth * Segment::maxHeight));//TroyHack size_t size = max(ledmapMaxSize, size_t(Segment::maxWidth * Segment::maxHeight)); // TroyHacks
USER_PRINTF("setupmatrix customMappingTable alloc %d from %d\n", size, customMappingTableSize); USER_PRINTF("setupmatrix customMappingTable alloc %d from %d\n", size, customMappingTableSize);
//if (customMappingTable != nullptr) delete[] customMappingTable; //if (customMappingTable != nullptr) delete[] customMappingTable;
//customMappingTable = new uint16_t[size]; //customMappingTable = new uint16_t[size];
@@ -80,7 +80,10 @@ void WS2812FX::setUpMatrix() {
if ((size > 0) && (customMappingTable == nullptr)) { // second try if ((size > 0) && (customMappingTable == nullptr)) { // second try
DEBUG_PRINTLN("setUpMatrix: trying to get fresh memory block."); DEBUG_PRINTLN("setUpMatrix: trying to get fresh memory block.");
customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t)); customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t));
if (customMappingTable == nullptr) USER_PRINTLN("setUpMatrix: alloc failed"); if (customMappingTable == nullptr) {
USER_PRINTLN("setUpMatrix: alloc failed");
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
}
} }
if (customMappingTable != nullptr) customMappingTableSize = size; if (customMappingTable != nullptr) customMappingTableSize = size;
} }
@@ -159,6 +162,7 @@ void WS2812FX::setUpMatrix() {
} else { // memory allocation error } else { // memory allocation error
customMappingTableSize = 0; customMappingTableSize = 0;
USER_PRINTLN(F("Ledmap alloc error.")); USER_PRINTLN(F("Ledmap alloc error."));
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
isMatrix = false; //WLEDMM does not like this done in teh background while end users are confused whats happened... isMatrix = false; //WLEDMM does not like this done in teh background while end users are confused whats happened...
panels = 0; panels = 0;
panel.clear(); panel.clear();

View File

@@ -115,7 +115,7 @@ Segment::Segment(const Segment &orig) {
//WLEDMM: recreate ledsrgb if more space needed (will not free ledsrgb!) //WLEDMM: recreate ledsrgb if more space needed (will not free ledsrgb!)
void Segment::allocLeds() { void Segment::allocLeds() {
size_t size = sizeof(CRGB)*max((size_t) length(), ledmapMaxSize); //TroyHack size_t size = sizeof(CRGB)*max((size_t) length(), ledmapMaxSize); // TroyHacks
if ((size < sizeof(CRGB)) || (size > 164000)) { //softhack too small (<3) or too large (>160Kb) if ((size < sizeof(CRGB)) || (size > 164000)) { //softhack too small (<3) or too large (>160Kb)
DEBUG_PRINTF("allocLeds warning: size == %u !!\n", size); DEBUG_PRINTF("allocLeds warning: size == %u !!\n", size);
if (ledsrgb && (ledsrgbSize == 0)) { if (ledsrgb && (ledsrgbSize == 0)) {
@@ -128,10 +128,13 @@ void Segment::allocLeds() {
if (ledsrgb) free(ledsrgb); // we need a bigger buffer, so free the old one first if (ledsrgb) free(ledsrgb); // we need a bigger buffer, so free the old one first
ledsrgb = (CRGB*)calloc(size, 1); ledsrgb = (CRGB*)calloc(size, 1);
ledsrgbSize = ledsrgb?size:0; ledsrgbSize = ledsrgb?size:0;
if (ledsrgb == nullptr) USER_PRINTLN("allocLeds failed!!"); if (ledsrgb == nullptr) {
USER_PRINTLN("allocLeds failed!!");
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
}
} }
else { else {
USER_PRINTF("reuse Leds %u from %u\n", size, ledsrgb?ledsrgbSize:0); //USER_PRINTF("reuse Leds %u from %u\n", size, ledsrgb?ledsrgbSize:0);
} }
} }
@@ -212,7 +215,11 @@ bool Segment::allocateData(size_t len) {
//DEBUG_PRINTF("allocateData(%u) start %d, stop %d, vlen %d\n", len, start, stop, virtualLength()); //DEBUG_PRINTF("allocateData(%u) start %d, stop %d, vlen %d\n", len, start, stop, virtualLength());
deallocateData(); deallocateData();
if (len == 0) return false; // nothing to do if (len == 0) return false; // nothing to do
if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) return false; //not enough memory if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) {
//USER_PRINTF("Segment::allocateData: Segment data quota exceeded! used:%u request:%u max:%d\n", Segment::getUsedSegmentData(), len, MAX_SEGMENT_DATA);
if (len > 0) errorFlag = ERR_LOW_SEG_MEM; // WLEDMM raise errorflag
return false; //not enough memory
}
// do not use SPI RAM on ESP32 since it is slow // do not use SPI RAM on ESP32 since it is slow
//#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM) //#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM)
//if (psramFound()) //if (psramFound())
@@ -220,10 +227,17 @@ bool Segment::allocateData(size_t len) {
//else //else
//#endif //#endif
data = (byte*) malloc(len); data = (byte*) malloc(len);
if (!data) { _dataLen = 0; return false;} //allocation failed // WLEDMM reset dataLen if (!data) {
_dataLen = 0; // WLEDMM reset dataLen
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
USER_PRINT(F("Segment::allocateData: FAILED to allocate "));
USER_PRINT(len); USER_PRINTLN(F(" bytes."));
return false;
} //allocation failed
Segment::addUsedSegmentData(len); Segment::addUsedSegmentData(len);
_dataLen = len; _dataLen = len;
memset(data, 0, len); memset(data, 0, len);
if (errorFlag == ERR_LOW_SEG_MEM) errorFlag = ERR_NONE; // WLEDMM reset errorflag on success
return true; return true;
} }
@@ -231,7 +245,7 @@ void Segment::deallocateData() {
if (!data) {_dataLen = 0; return;} // WLEDMM reset dataLen if (!data) {_dataLen = 0; return;} // WLEDMM reset dataLen
free(data); free(data);
data = nullptr; data = nullptr;
//DEBUG_PRINTLN("deallocateData() called free()."); //USER_PRINTF("Segment::deallocateData: free'd %d bytes.\n", _dataLen);
Segment::addUsedSegmentData(-_dataLen); Segment::addUsedSegmentData(-_dataLen);
_dataLen = 0; _dataLen = 0;
} }
@@ -482,6 +496,8 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
&& (!grp || (grouping == grp && spacing == spc)) && (!grp || (grouping == grp && spacing == spc))
&& (ofs == UINT16_MAX || ofs == offset)) return; && (ofs == UINT16_MAX || ofs == offset)) return;
stateChanged = true; // send UDP/WS broadcast
if (stop>start) fill(BLACK); //turn old segment range off // WLEDMM stop > start if (stop>start) fill(BLACK); //turn old segment range off // WLEDMM stop > start
if (i2 <= i1) { //disable segment if (i2 <= i1) { //disable segment
stop = 0; stop = 0;
@@ -1536,7 +1552,7 @@ void WS2812FX::enumerateLedmaps() {
USER_PRINTF("enumerateLedmaps %s \"%s\"", fileName, name); USER_PRINTF("enumerateLedmaps %s \"%s\"", fileName, name);
if (isMatrix) { if (isMatrix) {
//WLEDMM calc ledmapMaxSize (TroyHack) //WLEDMM calc ledmapMaxSize (TroyHacks)
char dim[34] = { '\0' }; char dim[34] = { '\0' };
f.find("\"width\":"); f.find("\"width\":");
f.readBytesUntil('\n', dim, sizeof(dim)-1); //hack: use fileName as we have this allocated already f.readBytesUntil('\n', dim, sizeof(dim)-1); //hack: use fileName as we have this allocated already
@@ -1654,6 +1670,7 @@ void WS2812FX::finalizeInit(void)
//#endif //#endif
if (arrSize > 0) Segment::_globalLeds = (CRGB*) malloc(arrSize); // WLEDMM avoid malloc(0) if (arrSize > 0) Segment::_globalLeds = (CRGB*) malloc(arrSize); // WLEDMM avoid malloc(0)
if ((Segment::_globalLeds != nullptr) && (arrSize > 0)) memset(Segment::_globalLeds, 0, arrSize); // WLEDMM avoid dereferencing nullptr if ((Segment::_globalLeds != nullptr) && (arrSize > 0)) memset(Segment::_globalLeds, 0, arrSize); // WLEDMM avoid dereferencing nullptr
if ((Segment::_globalLeds == nullptr) && (arrSize > 0)) errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
} }
//segments are created in makeAutoSegments(); //segments are created in makeAutoSegments();
@@ -2273,16 +2290,16 @@ void WS2812FX::loadCustomPalettes() {
if (readObjectFromFile(fileName, nullptr, &pDoc)) { if (readObjectFromFile(fileName, nullptr, &pDoc)) {
JsonArray pal = pDoc[F("palette")]; JsonArray pal = pDoc[F("palette")];
if (!pal.isNull() && pal.size()>4) { // not an empty palette (at least 2 entries) if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries)
if (pal[0].is<int>() && pal[1].is<const char *>()) { if (pal[0].is<int>() && pal[1].is<const char *>()) {
// we have an array of index & hex strings // we have an array of index & hex strings
size_t palSize = min(pal.size(), (size_t)36); // WLEDMM use native min/max size_t palSize = min(pal.size(), (size_t)36); // WLEDMM use native min/max
palSize -= palSize % 2; // make sure size is multiple of 2 palSize -= palSize % 2; // make sure size is multiple of 2
for (size_t i=0, j=0; i<palSize && pal[i].as<int>()<256; i+=2, j+=4) { for (unsigned i=0, j=0; i<palSize && pal[i].as<int>()<256; i+=2, j+=4) {
uint8_t rgbw[] = {0,0,0,0}; uint8_t rgbw[] = {0,0,0,0};
tcp[ j ] = (uint8_t) pal[ i ].as<int>(); // index tcp[ j ] = (uint8_t) pal[ i ].as<int>(); // index
colorFromHexString(rgbw, pal[i+1].as<const char *>()); // will catch non-string entires colorFromHexString(rgbw, pal[i+1].as<const char *>()); // will catch non-string entires
for (size_t c=0; c<3; c++) tcp[j+1+c] = rgbw[c]; // only use RGB component for (unsigned c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component
DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3]));
} }
} else { } else {
@@ -2290,13 +2307,15 @@ void WS2812FX::loadCustomPalettes() {
palSize -= palSize % 4; // make sure size is multiple of 4 palSize -= palSize % 4; // make sure size is multiple of 4
for (size_t i=0; i<palSize && pal[i].as<int>()<256; i+=4) { for (size_t i=0; i<palSize && pal[i].as<int>()<256; i+=4) {
tcp[ i ] = (uint8_t) pal[ i ].as<int>(); // index tcp[ i ] = (uint8_t) pal[ i ].as<int>(); // index
tcp[i+1] = (uint8_t) pal[i+1].as<int>(); // R tcp[i+1] = gamma8((uint8_t) pal[i+1].as<int>()); // R
tcp[i+2] = (uint8_t) pal[i+2].as<int>(); // G tcp[i+2] = gamma8((uint8_t) pal[i+2].as<int>()); // G
tcp[i+3] = (uint8_t) pal[i+3].as<int>(); // B tcp[i+3] = gamma8((uint8_t) pal[i+3].as<int>()); // B
DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3]));
} }
} }
customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp));
} else {
DEBUG_PRINTLN(F("Wrong palette format."));
} }
} }
} else { } else {
@@ -2387,7 +2406,7 @@ bool WS2812FX::deserializeMap(uint8_t n) {
//WLEDMM recreate customMappingTable if more space needed //WLEDMM recreate customMappingTable if more space needed
if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) { if (Segment::maxWidth * Segment::maxHeight > customMappingTableSize) {
size_t size = max(ledmapMaxSize, size_t(Segment::maxWidth * Segment::maxHeight));//TroyHack size_t size = max(ledmapMaxSize, size_t(Segment::maxWidth * Segment::maxHeight)); // TroyHacks
USER_PRINTF("deserializemap customMappingTable alloc %u from %u\n", size, customMappingTableSize); USER_PRINTF("deserializemap customMappingTable alloc %u from %u\n", size, customMappingTableSize);
//if (customMappingTable != nullptr) delete[] customMappingTable; //if (customMappingTable != nullptr) delete[] customMappingTable;
//customMappingTable = new uint16_t[size]; //customMappingTable = new uint16_t[size];
@@ -2399,7 +2418,10 @@ bool WS2812FX::deserializeMap(uint8_t n) {
if ((size > 0) && (customMappingTable == nullptr)) { // second try if ((size > 0) && (customMappingTable == nullptr)) { // second try
DEBUG_PRINTLN("deserializeMap: trying to get fresh memory block."); DEBUG_PRINTLN("deserializeMap: trying to get fresh memory block.");
customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t)); customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t));
if (customMappingTable == nullptr) DEBUG_PRINTLN("deserializeMap: alloc failed!"); if (customMappingTable == nullptr) {
DEBUG_PRINTLN("deserializeMap: alloc failed!");
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
}
} }
if (customMappingTable != nullptr) customMappingTableSize = size; if (customMappingTable != nullptr) customMappingTableSize = size;
} }

View File

@@ -142,6 +142,7 @@
#define USERMOD_ID_WEATHER 91 //Usermod "usermod_v2_weather.h" #define USERMOD_ID_WEATHER 91 //Usermod "usermod_v2_weather.h"
#define USERMOD_ID_GAMES 92 //Usermod "usermod_v2_games.h" #define USERMOD_ID_GAMES 92 //Usermod "usermod_v2_games.h"
#define USERMOD_ID_ANIMARTRIX 93 //Usermod "usermod_v2_animartrix.h" #define USERMOD_ID_ANIMARTRIX 93 //Usermod "usermod_v2_animartrix.h"
#define USERMOD_ID_AUTOPLAYLIST 94 // Usermod usermod_v2_auto_playlist.h
//Access point behavior //Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@@ -347,7 +348,9 @@
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented) #define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented) #define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented) #define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
#define ERR_LOW_MEM 33 // low memory (RAM) #define ERR_LOW_MEM 33 // WLEDMM: low memory (RAM)
#define ERR_LOW_SEG_MEM 34 // WLEDMM: low memory (segment data RAM)
#define ERR_LOW_WS_MEM 35 // WLEDMM: low memory (ws)
// Timer mode types // Timer mode types
#define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness
@@ -494,7 +497,10 @@
#define DEFAULT_LED_COUNT 30 #define DEFAULT_LED_COUNT 30
#endif #endif
#define INTERFACE_UPDATE_COOLDOWN 2000 //time in ms to wait between websockets, alexa, and MQTT updates #define INTERFACE_UPDATE_COOLDOWN 1200 // time in ms to wait between websockets, alexa, and MQTT updates
#define PIN_RETRY_COOLDOWN 3000 // time in ms after an incorrect attempt PIN and OTA pass will be rejected even if correct
#define PIN_TIMEOUT 900000 // time in ms after which the PIN will be required again, 15 minutes
// HW_PIN_SCL & HW_PIN_SDA are used for information in usermods settings page and usermods themselves // HW_PIN_SCL & HW_PIN_SDA are used for information in usermods settings page and usermods themselves
// which GPIO pins are actually used in a hardware layout (controller board) // which GPIO pins are actually used in a hardware layout (controller board)

View File

@@ -1968,8 +1968,14 @@ function readState(s,command=false)
errstr = "A filesystem error has occured."; errstr = "A filesystem error has occured.";
break; break;
case 33: case 33:
errstr = "Warning: Low Memory (RAM)."; errstr = "Low Memory (generic RAM).";
break; break;
case 34:
errstr = "Low Memory (effect data).";
break;
case 35:
errstr = "Low Memory (WS data).";
break;
} }
showToast('Error ' + s.error + ": " + errstr, true); showToast('Error ' + s.error + ": " + errstr, true);
} }

View File

@@ -231,11 +231,16 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8
if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3]; if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3];
if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]); if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]);
uint8_t segOption = (uint8_t)floor(e131_data[dataOffset+5]/64.0); if ((e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.setOption(SEG_OPTION_REVERSED_Y, e131_data[dataOffset+5] & 0b00000010); }
if (segOption == 0 && (seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, false);} if ((e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.setOption(SEG_OPTION_MIRROR_Y, e131_data[dataOffset+5] & 0b00000100); }
if (segOption == 1 && (seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, false); seg.setOption(SEG_OPTION_REVERSED, true);} if ((e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.setOption(SEG_OPTION_TRANSPOSED, e131_data[dataOffset+5] & 0b00001000); }
if (segOption == 2 && (!seg.mirror || seg.reverse )) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, false);} if ((e131_data[dataOffset+5] & 0b00110000) / 8 != seg.map1D2D) {
if (segOption == 3 && (!seg.mirror || !seg.reverse)) {seg.setOption(SEG_OPTION_MIRROR, true); seg.setOption(SEG_OPTION_REVERSED, true);} seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) / 8;
}
// To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000
if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.setOption(SEG_OPTION_REVERSED, e131_data[dataOffset+5] & 0b01000000); }
// To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000
if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.setOption(SEG_OPTION_MIRROR, e131_data[dataOffset+5] & 0b10000000); }
uint32_t colors[3]; uint32_t colors[3];
byte whites[3] = {0,0,0}; byte whites[3] = {0,0,0};

View File

@@ -208,6 +208,7 @@ void _overlayAnalogCountdown();
void _overlayAnalogClock(); void _overlayAnalogClock();
//playlist.cpp //playlist.cpp
void suspendPlaylist(); // WLEDMM support function for auto playlist usermod
void shufflePlaylist(); void shufflePlaylist();
void unloadPlaylist(); void unloadPlaylist();
int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0); int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0);
@@ -370,9 +371,11 @@ void releaseJSONBufferLock();
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen);
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr); uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr);
int16_t extractModeDefaults(uint8_t mode, const char *segVar); int16_t extractModeDefaults(uint8_t mode, const char *segVar);
void checkSettingsPIN(const char *pin);
uint16_t __attribute__((pure)) crc16(const unsigned char* data_p, size_t length); // WLEDMM: added attribute pure uint16_t __attribute__((pure)) crc16(const unsigned char* data_p, size_t length); // WLEDMM: added attribute pure
um_data_t* simulateSound(uint8_t simulationId); um_data_t* simulateSound(uint8_t simulationId);
// WLEDMM enumerateLedmaps(); moved to FX.h // WLEDMM enumerateLedmaps(); moved to FX.h
uint8_t get_random_wheel_index(uint8_t pos);
CRGB getCRGBForBand(int x, uint8_t *fftResult, int pal); //WLEDMM netmindz ar palette CRGB getCRGBForBand(int x, uint8_t *fftResult, int pal); //WLEDMM netmindz ar palette
char *cleanUpName(char *in); // to clean up a name that was read from file char *cleanUpName(char *in); // to clean up a name that was read from file

View File

@@ -210,7 +210,7 @@ void sendImprovInfoResponse() {
//Use serverDescription if it has been changed from the default "WLED", else mDNS name //Use serverDescription if it has been changed from the default "WLED", else mDNS name
bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0);
char vString[32]; char vString[32];
snprintf_P(vString, sizeof(vString)-1, PSTR("0.14.1-b30.36/%i"),VERSION); snprintf_P(vString, sizeof(vString)-1, PSTR("0.14.1-b30.37/%i"),VERSION);
const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription};
sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str);

View File

@@ -105,6 +105,7 @@ void stateUpdated(byte callMode) {
if (stateChanged) currentPreset = 0; //something changed, so we are no longer in the preset if (stateChanged) currentPreset = 0; //something changed, so we are no longer in the preset
if (callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) notify(callMode); if (callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) notify(callMode);
if (bri != briOld && nodeBroadcastEnabled) sendSysInfoUDP(); // update on state
//set flag to update ws and mqtt //set flag to update ws and mqtt
interfaceUpdateCallMode = callMode; interfaceUpdateCallMode = callMode;
@@ -139,6 +140,8 @@ void stateUpdated(byte callMode) {
jsonTransitionOnce = false; jsonTransitionOnce = false;
strip.setTransition(transitionDelayTemp); strip.setTransition(transitionDelayTemp);
if (transitionDelayTemp == 0) { if (transitionDelayTemp == 0) {
jsonTransitionOnce = false;
transitionActive = false;
applyFinalBri(); applyFinalBri();
strip.trigger(); strip.trigger();
return; return;
@@ -161,8 +164,12 @@ void stateUpdated(byte callMode) {
void updateInterfaces(uint8_t callMode) void updateInterfaces(uint8_t callMode)
{ {
if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return;
sendDataWs(); sendDataWs();
lastInterfaceUpdate = millis(); lastInterfaceUpdate = millis();
interfaceUpdateCallMode = 0; //disable
if (callMode == CALL_MODE_WS_SEND) return; if (callMode == CALL_MODE_WS_SEND) return;
#ifndef WLED_DISABLE_ALEXA #ifndef WLED_DISABLE_ALEXA
@@ -172,14 +179,13 @@ void updateInterfaces(uint8_t callMode)
} }
#endif #endif
doPublishMqtt = true; doPublishMqtt = true;
interfaceUpdateCallMode = 0; //disable
} }
void handleTransitions() void handleTransitions()
{ {
//handle still pending interface update //handle still pending interface update
if (interfaceUpdateCallMode && millis() - lastInterfaceUpdate > INTERFACE_UPDATE_COOLDOWN) updateInterfaces(interfaceUpdateCallMode); updateInterfaces(interfaceUpdateCallMode);
#ifndef WLED_DISABLE_MQTT #ifndef WLED_DISABLE_MQTT
if (doPublishMqtt) publishMqtt(); if (doPublishMqtt) publishMqtt();
#endif #endif
@@ -190,12 +196,15 @@ void handleTransitions()
if (tper >= 1.0f) if (tper >= 1.0f)
{ {
strip.setTransitionMode(false); strip.setTransitionMode(false);
// restore (global) transition time if not called from UDP notifier or single/temporary transition from JSON (also playlist)
if (jsonTransitionOnce) strip.setTransition(transitionDelay);
transitionActive = false; transitionActive = false;
jsonTransitionOnce = false;
tperLast = 0; tperLast = 0;
applyFinalBri(); applyFinalBri();
return; return;
} }
if (tper - tperLast < 0.004) return; if (tper - tperLast < 0.004f) return;
tperLast = tper; tperLast = tper;
briT = briOld + ((bri - briOld) * tper); briT = briOld + ((bri - briOld) * tper);
@@ -205,7 +214,7 @@ void handleTransitions()
// legacy method, applies values from col, effectCurrent, ... to selected segments // legacy method, applies values from col, effectCurrent, ... to selected segments
void colorUpdated(byte callMode){ void colorUpdated(byte callMode) {
applyValuesToSelectedSegs(); applyValuesToSelectedSegs();
stateUpdated(callMode); stateUpdated(callMode);
} }

View File

@@ -41,6 +41,12 @@ void shufflePlaylist() {
DEBUG_PRINTLN(F("Playlist shuffle.")); DEBUG_PRINTLN(F("Playlist shuffle."));
} }
// WLEDMM supporting function for auto_playlist usermod
// prevents the active playlist from progressing (until it gets unloaded)
static bool playlistSuspended = false;
void suspendPlaylist() {
playlistSuspended = true;
}
void unloadPlaylist() { void unloadPlaylist() {
if (playlistEntries != nullptr) { if (playlistEntries != nullptr) {
@@ -49,6 +55,7 @@ void unloadPlaylist() {
} }
currentPlaylist = playlistIndex = -1; currentPlaylist = playlistIndex = -1;
playlistLen = playlistEntryDur = playlistOptions = 0; playlistLen = playlistEntryDur = playlistOptions = 0;
playlistSuspended = false; // WLEDMM
DEBUG_PRINTLN(F("Playlist unloaded.")); DEBUG_PRINTLN(F("Playlist unloaded."));
} }
@@ -125,6 +132,11 @@ void handlePlaylist() {
// if fileDoc is not null JSON buffer is in use so just quit // if fileDoc is not null JSON buffer is in use so just quit
if (currentPlaylist < 0 || playlistEntries == nullptr || fileDoc != nullptr) return; if (currentPlaylist < 0 || playlistEntries == nullptr || fileDoc != nullptr) return;
if (playlistSuspended) { // WLEDMM
if (millis() - presetCycledTime > (100*playlistEntryDur)) presetCycledTime = millis(); // keep updating timer
return; // but don't progress to next extry, and don't shuffle
}
if (millis() - presetCycledTime > (100*playlistEntryDur)) { if (millis() - presetCycledTime > (100*playlistEntryDur)) {
presetCycledTime = millis(); presetCycledTime = millis();
if (bri == 0 || nightlightActive) return; if (bri == 0 || nightlightActive) return;

View File

@@ -712,6 +712,7 @@ void sendSysInfoUDP()
#else #else
data[38] = NODE_TYPE_ID_UNDEFINED; data[38] = NODE_TYPE_ID_UNDEFINED;
#endif #endif
if (bri) data[38] |= 0x80U; // add on/off state
data[39] = ip[3]; // unit ID == last IP number data[39] = ip[3]; // unit ID == last IP number
uint32_t build = VERSION; uint32_t build = VERSION;

View File

@@ -203,6 +203,9 @@
#ifdef USERMOD_ANIMARTRIX #ifdef USERMOD_ANIMARTRIX
#include "../usermods/usermod_v2_animartrix/usermod_v2_animartrix.h" #include "../usermods/usermod_v2_animartrix/usermod_v2_animartrix.h"
#endif #endif
#ifdef USERMOD_AUTO_PLAYLIST
#include "../usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h"
#endif
void registerUsermods() void registerUsermods()
{ {
@@ -402,4 +405,9 @@ void registerUsermods()
usermods.add(new AnimartrixUsermod("Animartrix", false)); usermods.add(new AnimartrixUsermod("Animartrix", false));
#endif #endif
#ifdef USERMOD_AUTO_PLAYLIST
usermods.add(new AutoPlaylistUsermod(false));
#endif
} }

View File

@@ -204,7 +204,7 @@ bool requestJSONBufferLock(uint8_t module)
{ {
unsigned long now = millis(); unsigned long now = millis();
while (jsonBufferLock && millis()-now < 1200) delay(1); // wait for fraction for buffer lock while (jsonBufferLock && millis()-now < 1100) delay(1); // wait for fraction for buffer lock
if (jsonBufferLock) { if (jsonBufferLock) {
USER_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by ")); USER_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by "));
@@ -239,9 +239,10 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
{ {
if (src == JSON_mode_names || src == nullptr) { if (src == JSON_mode_names || src == nullptr) {
if (mode < strip.getModeCount()) { if (mode < strip.getModeCount()) {
char lineBuffer[256]; char lineBuffer[256] = { '\0' };
//strcpy_P(lineBuffer, (const char*)pgm_read_dword(&(WS2812FX::_modeData[mode]))); //strcpy_P(lineBuffer, (const char*)pgm_read_dword(&(WS2812FX::_modeData[mode])));
strcpy_P(lineBuffer, strip.getModeData(mode)); strncpy_P(lineBuffer, strip.getModeData(mode), sizeof(lineBuffer)/sizeof(char)-1);
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
size_t len = strlen(lineBuffer); size_t len = strlen(lineBuffer);
size_t j = 0; size_t j = 0;
for (; j < maxLen && j < len; j++) { for (; j < maxLen && j < len; j++) {
@@ -253,6 +254,12 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
} else return 0; } else return 0;
} }
if (src == JSON_palette_names && mode > GRADIENT_PALETTE_COUNT) {
snprintf_P(dest, maxLen, PSTR("~ Custom %d~"), 255-mode);
dest[maxLen-1] = '\0';
return strlen(dest);
}
uint8_t qComma = 0; uint8_t qComma = 0;
bool insideQuotes = false; bool insideQuotes = false;
uint8_t printedChars = 0; uint8_t printedChars = 0;
@@ -363,9 +370,9 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
int16_t extractModeDefaults(uint8_t mode, const char *segVar) int16_t extractModeDefaults(uint8_t mode, const char *segVar)
{ {
if (mode < strip.getModeCount()) { if (mode < strip.getModeCount()) {
char lineBuffer[128] = ""; char lineBuffer[256] = { '\0' };
strncpy_P(lineBuffer, strip.getModeData(mode), 127); strncpy_P(lineBuffer, strip.getModeData(mode), sizeof(lineBuffer)/sizeof(char)-1);
lineBuffer[127] = '\0'; // terminate string lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
if (lineBuffer[0] != 0) { if (lineBuffer[0] != 0) {
char* startPtr = strrchr(lineBuffer, ';'); // last ";" in FX data char* startPtr = strrchr(lineBuffer, ';'); // last ";" in FX data
if (!startPtr) return -1; if (!startPtr) return -1;
@@ -381,6 +388,16 @@ int16_t extractModeDefaults(uint8_t mode, const char *segVar)
} }
void checkSettingsPIN(const char* pin) {
if (!pin) return;
if (!correctPIN && millis() - lastEditTime < PIN_RETRY_COOLDOWN) return; // guard against PIN brute force
bool correctBefore = correctPIN;
correctPIN = (strlen(settingsPIN) == 0 || strncmp(settingsPIN, pin, 4) == 0);
if (correctBefore != correctPIN) createEditHandler(correctPIN);
lastEditTime = millis();
}
uint16_t crc16(const unsigned char* data_p, size_t length) { uint16_t crc16(const unsigned char* data_p, size_t length) {
uint8_t x; uint8_t x;
uint16_t crc = 0xFFFF; uint16_t crc = 0xFFFF;
@@ -401,9 +418,9 @@ uint16_t crc16(const unsigned char* data_p, size_t length) {
// (only 2 used as stored in 1 bit in segment options, consider switching to a single global simulation type) // (only 2 used as stored in 1 bit in segment options, consider switching to a single global simulation type)
typedef enum UM_SoundSimulations { typedef enum UM_SoundSimulations {
UMS_BeatSin = 0, UMS_BeatSin = 0,
UMS_WeWillRockYou UMS_WeWillRockYou,
//UMS_10_13, UMS_10_13,
//UMS_14_3 UMS_14_3
} um_soundSimulations_t; } um_soundSimulations_t;
um_data_t* simulateSound(uint8_t simulationId) um_data_t* simulateSound(uint8_t simulationId)
@@ -491,7 +508,7 @@ um_data_t* simulateSound(uint8_t simulationId)
fftResult[i] = 0; fftResult[i] = 0;
} }
break; break;
/*case UMS_10_3: case UMS_10_13:
for (int i = 0; i<16; i++) for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3); fftResult[i] = inoise8(beatsin8(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
volumeSmth = fftResult[8]; volumeSmth = fftResult[8];
@@ -500,11 +517,11 @@ um_data_t* simulateSound(uint8_t simulationId)
for (int i = 0; i<16; i++) for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3); fftResult[i] = inoise8(beatsin8(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
volumeSmth = fftResult[8]; volumeSmth = fftResult[8];
break;*/ break;
} }
samplePeak = random8() > 250; samplePeak = random8() > 250;
FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // WLEDMM 21hz...8200hz FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz
maxVol = 31; // this gets feedback fro UI maxVol = 31; // this gets feedback fro UI
binNum = 8; // this gets feedback fro UI binNum = 8; // this gets feedback fro UI
volumeRaw = volumeSmth; volumeRaw = volumeSmth;
@@ -545,6 +562,20 @@ CRGB getCRGBForBand(int x, uint8_t *fftResult, int pal) {
return value; return value;
} }
/*
* Returns a new, random color wheel index with a minimum distance of 42 from pos.
*/
uint8_t get_random_wheel_index(uint8_t pos) {
uint8_t r = 0, x = 0, y = 0, d = 0;
while (d < 42) {
r = random8();
x = abs(pos - r);
y = 255 - x;
d = MIN(x, y);
}
return r;
}
// WLEDMM extended "trim string" function to support enumerateLedmaps // WLEDMM extended "trim string" function to support enumerateLedmaps
// The function takes char* as input, and removes all leading and trailing "decorations" like spaces, tabs, line endings, quotes, colons // The function takes char* as input, and removes all leading and trailing "decorations" like spaces, tabs, line endings, quotes, colons
// The conversion is "in place" (destructive). // The conversion is "in place" (destructive).

View File

@@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2402252 #define VERSION 2404161
// WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED. // WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED.
#define _MoonModules_WLED_ #define _MoonModules_WLED_

View File

@@ -220,6 +220,8 @@ void initServer()
if (verboseResponse) { if (verboseResponse) {
if (!isConfig) { if (!isConfig) {
lastInterfaceUpdate = millis(); // prevent WS update until cooldown
interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update
serveJson(request); return; //if JSON contains "v" serveJson(request); return; //if JSON contains "v"
} else { } else {
doSerializeConfig = true; //serializeConfig(); //Save new settings to FS doSerializeConfig = true; //serializeConfig(); //Save new settings to FS

View File

@@ -62,14 +62,15 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
} }
releaseJSONBufferLock(); // will clean fileDoc releaseJSONBufferLock(); // will clean fileDoc
// force broadcast in 500ms after updating client if (!interfaceUpdateCallMode) { // individual client response only needed if no WS broadcast soon
if (verboseResponse) { if (verboseResponse) {
sendDataWs(client); sendDataWs(client);
lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); } else {
} else { // we have to send something back otherwise WS connection closes
// we have to send something back otherwise WS connection closes client->text(F("{\"success\":true}"));
client->text(F("{\"success\":true}")); }
lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); // force broadcast in 500ms after updating client
//lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); // ESP8266 does not like this
} }
} }
} else { } else {
@@ -161,6 +162,7 @@ void sendDataWs(AsyncWebSocketClient * client)
ws.closeAll(1013); //code 1013 = temporary overload, try again later ws.closeAll(1013); //code 1013 = temporary overload, try again later
ws.cleanupClients(0); //disconnect all clients to release memory ws.cleanupClients(0); //disconnect all clients to release memory
ws._cleanBuffers(); ws._cleanBuffers();
errorFlag = ERR_LOW_WS_MEM;
return; //out of memory return; //out of memory
} }