-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Expand file tree
/
Copy pathutil.cpp
More file actions
1269 lines (1128 loc) · 47.4 KB
/
util.cpp
File metadata and controls
1269 lines (1128 loc) · 47.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "wled.h"
#include "fcn_declare.h"
#include "const.h"
#ifdef ESP8266
#include "user_interface.h" // for bootloop detection
#include <Hash.h> // for SHA1 on ESP8266
#else
#include <Update.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
#include "esp32/rtc.h" // for bootloop detection
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
#include "soc/rtc.h"
#endif
#include "mbedtls/sha1.h" // for SHA1 on ESP32
#include "esp_efuse.h"
#endif
//helper to get int value at a position in string
int getNumVal(const String &req, uint16_t pos)
{
return req.substring(pos+3).toInt();
}
//helper to get int value with in/decrementing support via ~ syntax
void parseNumber(const char* str, byte &val, byte minv, byte maxv)
{
if (str == nullptr || str[0] == '\0') return;
if (str[0] == 'r') {val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
bool wrap = false;
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
if (str[0] == '~') {
int out = atoi(str +1);
if (out == 0) {
if (str[1] == '0') return;
if (str[1] == '-') {
val = (int)(val -1) < (int)minv ? maxv : min((int)maxv,(val -1)); //-1, wrap around
} else {
val = (int)(val +1) > (int)maxv ? minv : max((int)minv,(val +1)); //+1, wrap around
}
} else {
if (wrap && val == maxv && out > 0) out = minv;
else if (wrap && val == minv && out < 0) out = maxv;
else {
out += val;
if (out > maxv) out = maxv;
if (out < minv) out = minv;
}
val = out;
}
return;
} else if (minv == maxv && minv == 0) { // limits "unset" i.e. both 0
byte p1 = atoi(str);
const char* str2 = strchr(str,'~'); // min/max range (for preset cycle, e.g. "1~5~")
if (str2) {
byte p2 = atoi(++str2); // skip ~
if (p2 > 0) {
while (isdigit(*(++str2))); // skip digits
parseNumber(str2, val, p1, p2);
return;
}
}
}
val = atoi(str);
}
//getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
bool getVal(JsonVariant elem, byte &val, byte vmin, byte vmax) {
if (elem.is<int>()) {
if (elem < 0) return false; //ignore e.g. {"ps":-1}
val = elem;
return true;
} else if (elem.is<const char*>()) {
const char* str = elem;
size_t len = strnlen(str, 14);
if (len == 0 || len > 12) return false;
// fix for #3605 & #4346
// ignore vmin and vmax and use as specified in API
if (len > 3 && (strchr(str,'r') || strchr(str,'~') != strrchr(str,'~'))) vmax = vmin = 0; // we have "X~Y(r|~[w][-][Z])" form
// end fix
parseNumber(str, val, vmin, vmax);
return true;
}
return false; //key does not exist
}
bool getBoolVal(const JsonVariant &elem, bool dflt) {
if (elem.is<const char*>() && elem.as<const char*>()[0] == 't') {
return !dflt;
} else {
return elem | dflt;
}
}
bool updateVal(const char* req, const char* key, byte &val, byte minv, byte maxv)
{
const char *v = strstr(req, key);
if (v) v += strlen(key);
else return false;
parseNumber(v, val, minv, maxv);
return true;
}
static size_t printSetFormInput(Print& settingsScript, const char* key, const char* selector, int value) {
return settingsScript.printf_P(PSTR("d.Sf.%s.%s=%d;"), key, selector, value);
}
size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val) {
return printSetFormInput(settingsScript, key, PSTR("checked"), val);
}
size_t printSetFormValue(Print& settingsScript, const char* key, int val) {
return printSetFormInput(settingsScript, key, PSTR("value"), val);
}
size_t printSetFormIndex(Print& settingsScript, const char* key, int index) {
return printSetFormInput(settingsScript, key, PSTR("selectedIndex"), index);
}
size_t printSetFormValue(Print& settingsScript, const char* key, const char* val) {
return settingsScript.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val);
}
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val) {
return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val);
}
// in-place hostname sanitizer, extracted from prepareHostname()
static void sanitizeHostname(char* hostname, size_t maxLen) {
if (hostname == nullptr || maxLen < 1 || strlen(hostname) < 1) return;
char *pC = hostname;
unsigned pos = 0;
while (*pC && pos < maxLen) {
char c = *pC;
if (isalnum((unsigned char)c)) {
hostname[pos++] = c;
} else if (c == ' ' || c == '_' || c == '-' || c == '+' || c == '!' || c == '?' || c == '*') { // convert certain characters to hyphens
if (pos > 0 && hostname[pos -1] != '-') hostname[pos++] = '-'; // keep single non-leading hyphens only
}
// else: drop any character not valid in a DNS hostname label
pC++;
}
// Hostname must not end with a hyphen.
while (pos > 0 && hostname[pos -1] == '-') pos--;
hostname[min(pos, maxLen-1)] = '\0'; // terminate string
}
/*
* Stores sanitized hostname into buffer provided by caller
* maxLen = hostname buffer size including \0
* preferMDNSname -> use mDNS name if set, otherwise fall back to WLED "server description" name (legacy behaviour)
*/
void getWLEDhostname(char* hostname, size_t maxLen, bool preferMDNS) {
if (maxLen <= 6) { strlcpy(hostname, "wled", maxLen); return; } // buffer too small (should not happen)
if (preferMDNS && strlen(cmDNS) > 0) {
strlcpy(hostname, cmDNS, maxLen);
sanitizeHostname(hostname, maxLen); // sanitize cmDNS name
if (strlen(hostname) < 1) { // if result is empty -> fall back to wled-MAC
snprintf_P(hostname, maxLen, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6);
hostname[maxLen -1] = '\0'; // ensure string termination
}
} else {
prepareHostname(hostname, maxLen); // use legacy hostname based on "server description" - already sanitized
}
}
/* Legacy hostname construction:
* Start with "wled-" + serverDescription as suffix.
* Sanitize only the suffix (always keep wled- prefix intact).
* If the sanitized suffix ends up empty, fall back to wled-<mac>.
*/
void prepareHostname(char* hostname, size_t maxLen)
{
if (maxLen <= 6) { strlcpy(hostname, "wled", maxLen); return; } // buffer too small (should not happen)
if (strncasecmp_P(serverDescription, PSTR("wled"), 4) == 0) // avoid wled-WLED-... as a hostname
strlcpy(hostname, serverDescription, maxLen);
else
snprintf_P(hostname, maxLen, PSTR("wled-%s"), serverDescription);
hostname[maxLen -1] = '\0'; // ensure string termination
size_t sanOffset = hostname[4] != '-' ? 4 : 5; // ensure that "WLED foo" and "WLED!foo" get sanitized
sanitizeHostname(hostname+sanOffset, maxLen-sanOffset); // sanitize name, keep "wled-" intact
if (strlen(hostname) <= sanOffset)
snprintf_P(hostname, maxLen, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); // fallback to wled-MAC if sanitization cleaned everything
}
bool isAsterisksOnly(const char* str, byte maxLen)
{
for (unsigned i = 0; i < maxLen; i++) {
if (str[i] == 0) break;
if (str[i] != '*') return false;
}
//at this point the password contains asterisks only
return (str[0] != 0); //false on empty string
}
//threading/network callback details: https://github.com/wled-dev/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t moduleID)
{
if (pDoc == nullptr) {
DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!"));
return false;
}
#if defined(ARDUINO_ARCH_ESP32)
// Use a recursive mutex type in case our task is the one holding the JSON buffer.
// This can happen during large JSON web transactions. In this case, we continue immediately
// and then will return out below if the lock is still held.
if (xSemaphoreTakeRecursive(jsonBufferLockMutex, 250) == pdFALSE) return false; // timed out waiting
#elif defined(ARDUINO_ARCH_ESP8266)
// If we're in system context, delay() won't return control to the user context, so there's
// no point in waiting.
if (can_yield()) {
unsigned long now = millis();
while (jsonBufferLock && (millis()-now < 250)) delay(1); // wait for fraction for buffer lock
}
#else
#error Unsupported task framework - fix requestJSONBufferLock
#endif
// If the lock is still held - by us, or by another task
if (jsonBufferLock) {
DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), moduleID, jsonBufferLock);
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
return false;
}
jsonBufferLock = moduleID ? moduleID : 255;
DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock);
pDoc->clear();
return true;
}
void releaseJSONBufferLock()
{
DEBUG_PRINTF_P(PSTR("JSON buffer released. (%d)\n"), jsonBufferLock);
jsonBufferLock = 0;
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
}
// extracts effect mode (or palette) name from names serialized string
// caller must provide large enough buffer for name (including SR extensions)! maxLen is (buffersize - 1)
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen)
{
if (src == JSON_mode_names || src == nullptr) {
if (mode < strip.getModeCount()) {
char lineBuffer[256];
//strcpy_P(lineBuffer, (const char*)pgm_read_dword(&(WS2812FX::_modeData[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 j = 0;
for (; j < maxLen && j < len; j++) {
if (lineBuffer[j] == '\0' || lineBuffer[j] == '@') break;
dest[j] = lineBuffer[j];
}
dest[j] = 0; // terminate string
return strlen(dest);
} else return 0;
}
if (src == JSON_palette_names && mode > 255-customPalettes.size()) {
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
dest[maxLen] = '\0';
return strlen(dest);
}
unsigned qComma = 0;
bool insideQuotes = false;
unsigned printedChars = 0;
char singleJsonSymbol;
size_t len = strlen_P(src);
// Find the mode name in JSON
for (size_t i = 0; i < len; i++) {
singleJsonSymbol = pgm_read_byte_near(src + i);
if (singleJsonSymbol == '\0') break;
if (singleJsonSymbol == '@' && insideQuotes && qComma == mode) break; //stop when SR extension encountered
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
if (!insideQuotes) qComma++;
default:
if (!insideQuotes || (qComma != mode)) break;
dest[printedChars++] = singleJsonSymbol;
}
if ((qComma > mode) || (printedChars >= maxLen)) break;
}
dest[printedChars] = '\0';
return strlen(dest);
}
// extracts effect slider data (1st group after @)
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var)
{
dest[0] = '\0'; // start by clearing buffer
if (mode < strip.getModeCount()) {
String lineBuffer = FPSTR(strip.getModeData(mode));
if (lineBuffer.length() > 0) {
int start = lineBuffer.indexOf('@'); // String::indexOf() returns an int, not an unsigned; -1 means "not found"
int stop = lineBuffer.indexOf(';', start);
if (start>0 && stop>0) {
String names = lineBuffer.substring(start, stop); // include @
int nameBegin = 1, nameEnd, nameDefault;
if (slider < 10) {
for (size_t i=0; i<=slider; i++) {
const char *tmpstr;
dest[0] = '\0'; //clear dest buffer
if (nameBegin <= 0) break; // there are no more names
nameEnd = names.indexOf(',', nameBegin);
if (i == slider) {
nameDefault = names.indexOf('=', nameBegin); // find default value
if (nameDefault > 0 && var && ((nameEnd>0 && nameDefault<nameEnd) || nameEnd<0)) {
*var = (uint8_t)atoi(names.substring(nameDefault+1).c_str());
}
if (names.charAt(nameBegin) == '!') {
switch (slider) {
case 0: tmpstr = PSTR("FX Speed"); break;
case 1: tmpstr = PSTR("FX Intensity"); break;
case 2: tmpstr = PSTR("FX Custom 1"); break;
case 3: tmpstr = PSTR("FX Custom 2"); break;
case 4: tmpstr = PSTR("FX Custom 3"); break;
default: tmpstr = PSTR("FX Custom"); break;
}
strncpy_P(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
dest[maxLen-1] = '\0';
} else {
if (nameEnd<0) tmpstr = names.substring(nameBegin).c_str(); // did not find ",", last name?
else tmpstr = names.substring(nameBegin, nameEnd).c_str();
strlcpy(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
}
}
nameBegin = nameEnd+1; // next name (if "," is not found it will be 0)
} // next slider
} else if (slider == 255) {
// palette
strlcpy(dest, "pal", maxLen);
names = lineBuffer.substring(stop+1); // stop has index of color slot names
nameBegin = names.indexOf(';'); // look for palette
if (nameBegin >= 0) {
nameEnd = names.indexOf(';', nameBegin+1);
if (!isdigit(names[nameBegin+1])) nameBegin = names.indexOf('=', nameBegin+1); // look for default value
if (nameEnd >= 0 && nameBegin > nameEnd) nameBegin = -1;
if (nameBegin >= 0 && var) {
*var = (uint8_t)atoi(names.substring(nameBegin+1).c_str());
}
}
}
// we have slider name (including default value) in the dest buffer
for (size_t i=0; i<strlen(dest); i++) if (dest[i]=='=') { dest[i]='\0'; break; } // truncate default value
} else {
// defaults to just speed and intensity since there is no slider data
switch (slider) {
case 0: strncpy_P(dest, PSTR("FX Speed"), maxLen); break;
case 1: strncpy_P(dest, PSTR("FX Intensity"), maxLen); break;
}
dest[maxLen-1] = '\0'; // strncpy does not necessarily null terminate string
}
}
return strlen(dest);
}
return 0;
}
// extracts mode parameter defaults from last section of mode data (e.g. "Juggle@!,Trail;!,!,;!;012;sx=16,ix=240")
int16_t extractModeDefaults(uint8_t mode, const char *segVar)
{
if (mode < strip.getModeCount()) {
char lineBuffer[256];
strncpy_P(lineBuffer, strip.getModeData(mode), sizeof(lineBuffer)/sizeof(char)-1);
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
if (lineBuffer[0] != 0) {
char* startPtr = strrchr(lineBuffer, ';'); // last ";" in FX data
if (!startPtr) return -1;
char* stopPtr = strstr(startPtr, segVar);
if (!stopPtr) return -1;
stopPtr += strlen(segVar) +1; // skip "="
return atoi(stopPtr);
}
}
return -1;
}
void checkSettingsPIN(const char* pin) {
if (!pin) return;
if (!correctPIN && millis() - lastEditTime < PIN_RETRY_COOLDOWN) return; // guard against PIN brute force
//bool correctBefore = correctPIN; // unused
correctPIN = (strlen(settingsPIN) == 0 || strncmp(settingsPIN, pin, 4) == 0);
lastEditTime = millis();
}
uint16_t crc16(const unsigned char* data_p, size_t length) {
uint8_t x;
uint16_t crc = 0xFFFF;
if (!length) return 0x1D0F;
while (length--) {
x = crc >> 8 ^ *data_p++;
x ^= x>>4;
crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x);
}
return crc;
}
// fastled beatsin: 1:1 replacements to remove the use of fastled sin16()
// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat88( beats_per_minute_88, timebase);
uint16_t beatsin (sin16_t( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat16( beats_per_minute, timebase);
uint16_t beatsin = (sin16_t( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
// Generates an 8-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset)
{
uint8_t beat = beat8( beats_per_minute, timebase);
uint8_t beatsin = sin8_t( beat + phase_offset);
uint8_t rangewidth = highest - lowest;
uint8_t scaledbeat = scale8( beatsin, rangewidth);
uint8_t result = lowest + scaledbeat;
return result;
}
///////////////////////////////////////////////////////////////////////////////
// Begin simulateSound (to enable audio enhanced effects to display something)
///////////////////////////////////////////////////////////////////////////////
// Currently 4 types defined, to be fine tuned and new types added
// (only 2 used as stored in 1 bit in segment options, consider switching to a single global simulation type)
typedef enum UM_SoundSimulations {
UMS_BeatSin = 0,
UMS_WeWillRockYou,
UMS_10_13,
UMS_14_3
} um_soundSimulations_t;
um_data_t* simulateSound(uint8_t simulationId)
{
static uint8_t samplePeak;
static float FFT_MajorPeak;
static uint8_t maxVol;
static uint8_t binNum;
static float volumeSmth;
static uint16_t volumeRaw;
static float my_magnitude;
//arrays
uint8_t *fftResult;
static um_data_t* um_data = nullptr;
if (!um_data) {
//claim storage for arrays
fftResult = (uint8_t *)malloc(sizeof(uint8_t) * 16);
// initialize um_data pointer structure
// NOTE!!!
// This may change as AudioReactive usermod may change
um_data = new um_data_t;
um_data->u_size = 8;
um_data->u_type = new um_types_t[um_data->u_size];
um_data->u_data = new void*[um_data->u_size];
um_data->u_data[0] = &volumeSmth;
um_data->u_data[1] = &volumeRaw;
um_data->u_data[2] = fftResult;
um_data->u_data[3] = &samplePeak;
um_data->u_data[4] = &FFT_MajorPeak;
um_data->u_data[5] = &my_magnitude;
um_data->u_data[6] = &maxVol;
um_data->u_data[7] = &binNum;
} else {
// get arrays from um_data
fftResult = (uint8_t*)um_data->u_data[2];
}
uint32_t ms = millis();
switch (simulationId) {
default:
case UMS_BeatSin:
for (int i = 0; i<16; i++)
fftResult[i] = beatsin8_t(120 / (i+1), 0, 255);
// fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256;
volumeSmth = fftResult[8];
break;
case UMS_WeWillRockYou:
if (ms%2000 < 200) {
volumeSmth = hw_random8();
for (int i = 0; i<5; i++)
fftResult[i] = hw_random8();
}
else if (ms%2000 < 400) {
volumeSmth = 0;
for (int i = 0; i<16; i++)
fftResult[i] = 0;
}
else if (ms%2000 < 600) {
volumeSmth = hw_random8();
for (int i = 5; i<11; i++)
fftResult[i] = hw_random8();
}
else if (ms%2000 < 800) {
volumeSmth = 0;
for (int i = 0; i<16; i++)
fftResult[i] = 0;
}
else if (ms%2000 < 1000) {
volumeSmth = hw_random8();
for (int i = 11; i<16; i++)
fftResult[i] = hw_random8();
}
else {
volumeSmth = 0;
for (int i = 0; i<16; i++)
fftResult[i] = 0;
}
break;
case UMS_10_13:
for (int i = 0; i<16; i++)
fftResult[i] = perlin8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
volumeSmth = fftResult[8];
break;
case UMS_14_3:
for (int i = 0; i<16; i++)
fftResult[i] = perlin8(beatsin8_t(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
volumeSmth = fftResult[8];
break;
}
samplePeak = hw_random8() > 250;
FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz
maxVol = 31; // this gets feedback fro UI
binNum = 8; // this gets feedback fro UI
volumeRaw = volumeSmth;
my_magnitude = 10000.0f / 8.0f; //no idea if 10000 is a good value for FFT_Magnitude ???
if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute
return um_data;
}
static const char s_ledmap_tmpl[] PROGMEM = "ledmap%d.json";
// enumerate all ledmapX.json files on FS and extract ledmap names if existing
void enumerateLedmaps() {
StaticJsonDocument<64> filter;
filter["n"] = true;
ledMaps = 1;
for (size_t i=1; i<WLED_MAX_LEDMAPS; i++) {
char fileName[33] = "/";
sprintf_P(fileName+1, s_ledmap_tmpl, i);
bool isFile = WLED_FS.exists(fileName);
#ifndef ESP8266
if (ledmapNames[i-1]) { //clear old name
free(ledmapNames[i-1]);
ledmapNames[i-1] = nullptr;
}
#endif
if (isFile) {
ledMaps |= 1 << i;
#ifndef ESP8266
if (requestJSONBufferLock(JSON_LOCK_LEDMAP_ENUM)) {
if (readObjectFromFile(fileName, nullptr, pDoc, &filter)) {
size_t len = 0;
JsonObject root = pDoc->as<JsonObject>();
if (!root["n"].isNull()) {
// name field exists
const char *name = root["n"].as<const char*>();
if (name != nullptr) len = strlen(name);
if (len > 0 && len < 33) {
ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
}
}
if (!ledmapNames[i-1]) {
char tmp[33];
snprintf_P(tmp, 32, s_ledmap_tmpl, i);
len = strlen(tmp);
ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
}
}
releaseJSONBufferLock();
}
#endif
}
}
}
/*
* 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 = hw_random8();
x = abs(pos - r);
y = 255 - x;
d = MIN(x, y);
}
return r;
}
// float version of map()
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;
}
uint32_t hashInt(uint32_t s) {
// borrowed from https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
s = ((s >> 16) ^ s) * 0x45d9f3b;
s = ((s >> 16) ^ s) * 0x45d9f3b;
return (s >> 16) ^ s;
}
// 32 bit random number generator, inlining uses more code, use hw_random16() if speed is critical (see fcn_declare.h)
uint32_t hw_random(uint32_t upperlimit) {
uint32_t rnd = hw_random();
uint64_t scaled = uint64_t(rnd) * uint64_t(upperlimit);
return scaled >> 32;
}
int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
if(lowerlimit >= upperlimit) {
return lowerlimit;
}
uint32_t diff = upperlimit - lowerlimit;
return hw_random(diff) + lowerlimit;
}
// PSRAM compile time checks to provide info for misconfigured env
#if defined(BOARD_HAS_PSRAM)
#if defined(IDF_TARGET_ESP32C3) || defined(ESP8266)
#error "ESP32-C3 and ESP8266 with PSRAM is not supported, please remove BOARD_HAS_PSRAM definition"
#else
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) // PSRAM fix only needed for classic esp32
// BOARD_HAS_PSRAM also means that compiler flag "-mfix-esp32-psram-cache-issue" has to be used for old "rev.1" esp32
#warning "BOARD_HAS_PSRAM defined, make sure to use -mfix-esp32-psram-cache-issue to prevent issues on rev.1 ESP32 boards \
see https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html#esp32-rev-v1-0"
#endif
#endif
#else
#if !defined(IDF_TARGET_ESP32C3) && !defined(ESP8266)
#pragma message("BOARD_HAS_PSRAM not defined, not using PSRAM.")
#endif
#endif
// memory allocation functions with minimum free heap size check
#ifdef ESP8266
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
// note: ESP826 needs very little contiguous heap for webserver, checking total free heap works better
if (getFreeHeapSize() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return buffer;
}
void *d_malloc(size_t size) {
// note: using "if (getContiguousFreeHeap() > MIN_HEAP_SIZE + size)" did perform worse in tests with regards to keeping heap healthy and UI working
void *buffer = malloc(size);
return validateFreeHeap(buffer);
}
void *d_calloc(size_t count, size_t size) {
void *buffer = calloc(count, size);
return validateFreeHeap(buffer);
}
// realloc with malloc fallback, note: on ESPS8266 there is no safe way to ensure MIN_HEAP_SIZE during realloc()s, free buffer and allocate new one
void *d_realloc_malloc(void *ptr, size_t size) {
//void *buffer = realloc(ptr, size);
//buffer = validateFreeHeap(buffer);
//if (buffer) return buffer; // realloc successful
//d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
//return d_malloc(size); // fallback to malloc
free(ptr);
return d_malloc(size);
}
#else
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
// TODO: between allocate and free, heap can run low (async web access), only IDF V5 allows for a pre-allocation-check of all free blocks
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH && getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return buffer;
}
#ifdef BOARD_HAS_PSRAM
#define RTC_RAM_THRESHOLD 1024 // use RTC RAM for allocations smaller than this size
#else
#define RTC_RAM_THRESHOLD 65535 // without PSRAM, allow any size into RTC RAM (useful especially on S2 without PSRAM)
#endif
void *d_malloc(size_t size) {
void *buffer = nullptr;
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
// the newer ESP32 variants have byte-accessible fast RTC memory that can be used as heap, access speed is on-par with DRAM
// the system does prefer normal DRAM until full, since free RTC memory is ~7.5k only, its below the minimum heap threshold and needs to be allocated explicitly
// use RTC RAM for small allocations or if DRAM is running low to improve fragmentation
if (size <= RTC_RAM_THRESHOLD || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + size)
buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_RTCRAM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (buffer == nullptr) // no RTC RAM allocation: use DRAM
#endif
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory
buffer = validateFreeHeap(buffer); // make sure there is enough free heap left
#ifdef BOARD_HAS_PSRAM
if (!buffer)
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // DRAM failed, use PSRAM if available
#endif
return buffer;
}
void *d_calloc(size_t count, size_t size) {
void *buffer = d_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *d_realloc_malloc(void *ptr, size_t size) {
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
if (buffer) return buffer; // realloc successful
d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
return d_malloc(size); // fallback to malloc
}
#ifdef BOARD_HAS_PSRAM
// p_xalloc: prefer PSRAM, use DRAM as fallback
void *p_malloc(size_t size) {
void *buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
return validateFreeHeap(buffer);
}
void *p_calloc(size_t count, size_t size) {
void *buffer = p_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *p_realloc_malloc(void *ptr, size_t size) {
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (buffer) return buffer; // realloc successful
p_free(ptr); // free old buffer if realloc failed
return p_malloc(size); // fallback to malloc
}
#endif
#endif
// allocation function for buffers like pixel-buffers and segment data
// optimises the use of memory types to balance speed and heap availability, always favours DRAM if possible
// if multiple conflicting types are defined, the lowest bits of "type" take priority (see fcn_declare.h for types)
void *allocate_buffer(size_t size, uint32_t type) {
void *buffer = nullptr;
#ifdef CONFIG_IDF_TARGET_ESP32
// only classic ESP32 has "32bit accessible only" aka IRAM type. Using it frees up normal DRAM for other purposes
// this memory region is used for IRAM_ATTR functions, whatever is left is unused and can be used for pixel buffers
// prefer this type over PSRAM as it is slightly faster, except for _pixels where it is on-par as PSRAM-caching does a good job for mostly sequential access
if (type & BFRALLOC_NOBYTEACCESS) {
// prefer 32bit region, then PSRAM, fallback to any heap. Note: if adding "INTERNAL"-flag this wont work
buffer = heap_caps_malloc_prefer(size, 3, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM, MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
}
else
#endif
#if !defined(BOARD_HAS_PSRAM)
buffer = d_malloc(size);
#else
if (type & BFRALLOC_PREFER_DRAM) {
if (getContiguousFreeHeap() < 3*(MIN_HEAP_SIZE/2) + size && size > PSRAM_THRESHOLD)
buffer = p_malloc(size); // prefer PSRAM for large allocations & when DRAM is low
else
buffer = d_malloc(size); // allocate in DRAM if enough free heap is available, PSRAM as fallback
}
else if (type & BFRALLOC_ENFORCE_DRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // use DRAM only, otherwise return nullptr
else if (type & BFRALLOC_PREFER_PSRAM) {
// if DRAM is plenty, prefer it over PSRAM for speed, reserve enough DRAM for segment data: if MAX_SEGMENT_DATA is exceeded, always uses PSRAM
if (getContiguousFreeHeap() > 4*MIN_HEAP_SIZE + size + ((uint32_t)(MAX_SEGMENT_DATA - Segment::getUsedSegmentData())))
buffer = d_malloc(size);
else
buffer = p_malloc(size); // prefer PSRAM
}
else if (type & BFRALLOC_ENFORCE_PSRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // use PSRAM only, otherwise return nullptr
buffer = validateFreeHeap(buffer);
#endif
if (buffer && (type & BFRALLOC_CLEAR))
memset(buffer, 0, size); // clear allocated buffer
/*
#if !defined(ESP8266) && defined(WLED_DEBUG)
if (buffer) {
DEBUG_PRINTF_P(PSTR("*Buffer allocated: size:%d, address:%p"), size, (uintptr_t)buffer);
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in DRAM"));
#ifndef CONFIG_IDF_TARGET_ESP32C3
else if ((uintptr_t)buffer > SOC_EXTRAM_DATA_LOW && (uintptr_t)buffer < SOC_EXTRAM_DATA_HIGH)
DEBUG_PRINTLN(F(" in PSRAM"));
#endif
#ifdef CONFIG_IDF_TARGET_ESP32
else if ((uintptr_t)buffer > SOC_IRAM_LOW && (uintptr_t)buffer < SOC_IRAM_HIGH)
DEBUG_PRINTLN(F(" in IRAM")); // only used on ESP32 (MALLOC_CAP_32BIT)
#else
else if ((uintptr_t)buffer > SOC_RTC_DRAM_LOW && (uintptr_t)buffer < SOC_RTC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in RTCRAM")); // not available on ESP32
#endif
else
DEBUG_PRINTLN(F(" in ???")); // unknown (check soc.h for other memory regions)
} else
DEBUG_PRINTF_P(PSTR("Buffer allocation failed: size:%d\n"), size);
#endif
*/
return buffer;
}
// bootloop detection and handling
// checks if the ESP reboots multiple times due to a crash or watchdog timeout
// if a bootloop is detected: restore settings from backup, then reset settings, then switch boot image (and repeat)
#define BOOTLOOP_INTERVAL_MILLIS 120000 // time limit between crashes: 120 seconds (2 minutes)
#define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection
#define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /bkp.cfg.json
#define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /rst.cfg.json)
#define BOOTLOOP_ACTION_OTA 2 // swap the boot partition
#define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset)
// Platform-agnostic abstraction
enum class ResetReason {
Power,
Software,
Crash,
Brownout
};
#ifdef ESP8266
// Place variables in RTC memory via references, since RTC memory is not exposed via the linker in the Non-OS SDK
// Use an offset of 32 as there's some hints that the first 128 bytes of "user" memory are used by the OTA system
// Ref: https://github.com/esp8266/Arduino/blob/78d0d0aceacc1553f45ad8154592b0af22d1eede/cores/esp8266/Esp.cpp#L168
static volatile uint32_t& bl_last_boottime = *(RTC_USER_MEM + 32);
static volatile uint32_t& bl_crashcounter = *(RTC_USER_MEM + 33);
static volatile uint32_t& bl_actiontracker = *(RTC_USER_MEM + 34);
static inline ResetReason rebootReason() {
uint32_t resetReason = system_get_rst_info()->reason;
if (resetReason == REASON_EXCEPTION_RST
|| resetReason == REASON_WDT_RST
|| resetReason == REASON_SOFT_WDT_RST)
return ResetReason::Crash;
if (resetReason == REASON_SOFT_RESTART)
return ResetReason::Software;
return ResetReason::Power;
}
static inline uint32_t getRtcMillis() { return system_get_rtc_time() / 160; }; // rtc ticks ~160000Hz
#else
// variables in RTC_NOINIT memory persist between reboots (but not on hardware reset)
RTC_NOINIT_ATTR static uint32_t bl_last_boottime;
RTC_NOINIT_ATTR static uint32_t bl_crashcounter;
RTC_NOINIT_ATTR static uint32_t bl_actiontracker;
static inline ResetReason rebootReason() {
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_BROWNOUT) return ResetReason::Brownout;
if (reason == ESP_RST_SW) return ResetReason::Software;
if (reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT) return ResetReason::Crash;
return ResetReason::Power;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
static inline uint32_t getRtcMillis() { return esp_rtc_get_time_us() / 1000; }
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
static inline uint32_t getRtcMillis() { return rtc_time_slowclk_to_us(rtc_time_get(), rtc_clk_slow_freq_get_hz()) / 1000; }
#endif
void bootloopCheckOTA() { bl_actiontracker = BOOTLOOP_ACTION_OTA; } // swap boot image if bootloop is detected instead of restoring config
#endif
// detect bootloop by checking the reset reason and the time since last boot
static bool detectBootLoop() {
uint32_t rtctime = getRtcMillis();
bool result = false;
switch(rebootReason()) {
case ResetReason::Power:
bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
// fall through
case ResetReason::Software:
// no crash detected, reset counter
bl_crashcounter = 0;
break;
case ResetReason::Crash:
{
DEBUG_PRINTLN(F("crash detected!"));
uint32_t rebootinterval = rtctime - bl_last_boottime;
if (rebootinterval < BOOTLOOP_INTERVAL_MILLIS) {
bl_crashcounter++;
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
bl_crashcounter = 0;
if(bl_actiontracker > BOOTLOOP_ACTION_DUMP) bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // reset action tracker if out of bounds
result = true;
}
} else {
// Reset counter on long intervals to track only consecutive short-interval crashes
bl_crashcounter = 0;
// TODO: crash reporting goes here
}
break;
}
case ResetReason::Brownout:
// crash due to brownout can't be detected unless using flash memory to store bootloop variables
DEBUG_PRINTLN(F("brownout detected"));
//restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
break;
}
bl_last_boottime = rtctime; // store current runtime for next reboot
return result;
}
void handleBootLoop() {
DEBUG_PRINTF_P(PSTR("checking for bootloop: time %d, counter %d, action %d\n"), bl_last_boottime, bl_crashcounter, bl_actiontracker);
if (!detectBootLoop()) return; // no bootloop detected
switch(bl_actiontracker) {
case BOOTLOOP_ACTION_RESTORE:
restoreConfig();
++bl_actiontracker;
break;
case BOOTLOOP_ACTION_RESET:
resetConfig();
++bl_actiontracker;
break;
case BOOTLOOP_ACTION_OTA:
#ifndef ESP8266
if(Update.canRollBack()) {
DEBUG_PRINTLN(F("Swapping boot partition..."));
Update.rollBack(); // swap boot partition
}
++bl_actiontracker;
break;
#else
// fall through
#endif
case BOOTLOOP_ACTION_DUMP:
dumpFilesToSerial();
break;
}
ESP.restart(); // restart cleanly and don't wait for another crash
}