Merge branch 'mdev' into audio_fastpath
This commit is contained in:
@@ -819,6 +819,7 @@ build_flags_S =
|
|||||||
-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
|
-D USERMOD_ARTIFX ; WLEDMM usermod
|
||||||
-D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates
|
-D WLEDMM_FASTPATH ; WLEDMM experimental option. Reduces audio lag (latency), and allows for faster LED framerates
|
||||||
|
-D WLEDMM_PROTECT_SERVICE ;; WLEDMM experimental feature to prevent crashes when effects are drawing while async_tcp tries to modify segment or strip objects.
|
||||||
; -D WLED_DISABLE_LOXONE
|
; -D WLED_DISABLE_LOXONE
|
||||||
; -D WLED_DISABLE_ALEXA
|
; -D WLED_DISABLE_ALEXA
|
||||||
; -D WLED_DISABLE_HUESYNC
|
; -D WLED_DISABLE_HUESYNC
|
||||||
|
|||||||
11
wled00/FX.h
11
wled00/FX.h
@@ -31,6 +31,8 @@
|
|||||||
|
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
|
|
||||||
|
void strip_wait_until_idle(String whoCalledMe); // WLEDMM implemented in FX_fcn.cpp
|
||||||
|
|
||||||
#define FASTLED_INTERNAL //remove annoying pragma messages
|
#define FASTLED_INTERNAL //remove annoying pragma messages
|
||||||
#define USE_GET_MILLISECOND_TIMER
|
#define USE_GET_MILLISECOND_TIMER
|
||||||
#include "FastLED.h"
|
#include "FastLED.h"
|
||||||
@@ -505,6 +507,12 @@ typedef struct Segment {
|
|||||||
if (ledsrgb) Serial.printf(" [%u]", length()*sizeof(CRGB));
|
if (ledsrgb) Serial.printf(" [%u]", length()*sizeof(CRGB));
|
||||||
Serial.println();
|
Serial.println();
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
// WLEDMM only delete segments when they are not in use
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
strip_wait_until_idle("~Segment()");
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!Segment::_globalLeds && ledsrgb) free(ledsrgb);
|
if (!Segment::_globalLeds && ledsrgb) free(ledsrgb);
|
||||||
if (name) delete[] name;
|
if (name) delete[] name;
|
||||||
if (_t) delete _t;
|
if (_t) delete _t;
|
||||||
@@ -748,6 +756,9 @@ class WS2812FX { // 96 bytes
|
|||||||
}
|
}
|
||||||
|
|
||||||
~WS2812FX() {
|
~WS2812FX() {
|
||||||
|
//#ifdef WLED_DEBUG
|
||||||
|
if (Serial) Serial.println(F("~WS2812FX destroying strip.")); // WLEDMM can't use DEBUG_PRINTLN here
|
||||||
|
//#endif
|
||||||
if (customMappingTable) delete[] customMappingTable;
|
if (customMappingTable) delete[] customMappingTable;
|
||||||
_mode.clear();
|
_mode.clear();
|
||||||
_modeData.clear();
|
_modeData.clear();
|
||||||
|
|||||||
@@ -72,6 +72,17 @@
|
|||||||
#error "Max segments must be at least max number of busses!"
|
#error "Max segments must be at least max number of busses!"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// WLEDMM experimental . this is a "C style" wrapper for strip.waitUntilIdle()
|
||||||
|
// This workaround is just needed for the segment class, that does't know about "strip"
|
||||||
|
void strip_wait_until_idle(String whoCalledMe) {
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE) // WLEDMM experimental
|
||||||
|
if (strip.isServicing() && (strncmp(pcTaskGetTaskName(NULL), "loopTask", 8) != 0)) { // if we are in looptask (arduino loop), its safe to proceed without waiting
|
||||||
|
USER_PRINTLN(whoCalledMe + String(": strip is still drawing effects."));
|
||||||
|
strip.waitUntilIdle();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Segment class implementation
|
// Segment class implementation
|
||||||
@@ -1512,12 +1523,12 @@ void WS2812FX::finalizeInit(void)
|
|||||||
// on 8266 this function does nothing, because we can only do "buisy waiting" on ESP32
|
// on 8266 this function does nothing, because we can only do "buisy waiting" on ESP32
|
||||||
#define MAX_IDLE_WAIT_MS 50 // seems to work in most cases
|
#define MAX_IDLE_WAIT_MS 50 // seems to work in most cases
|
||||||
void WS2812FX::waitUntilIdle(void) {
|
void WS2812FX::waitUntilIdle(void) {
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE)
|
||||||
if (isServicing()) {
|
if (isServicing()) {
|
||||||
unsigned long waitStarted = millis();
|
unsigned long waitStarted = millis();
|
||||||
do {
|
do {
|
||||||
delay(1);
|
delay(1); // Suspending for 1 tick or more gives other tasks a chance to run.
|
||||||
yield();
|
//yield(); // seems to be a no-op on esp32
|
||||||
} while (isServicing() && (millis() - waitStarted < MAX_IDLE_WAIT_MS));
|
} while (isServicing() && (millis() - waitStarted < MAX_IDLE_WAIT_MS));
|
||||||
USER_PRINTF("strip.waitUntilIdle(): strip %sidle after %d ms. (task %s)\n", isServicing()?"not ":"", int(millis() - waitStarted), pcTaskGetTaskName(NULL));
|
USER_PRINTF("strip.waitUntilIdle(): strip %sidle after %d ms. (task %s)\n", isServicing()?"not ":"", int(millis() - waitStarted), pcTaskGetTaskName(NULL));
|
||||||
}
|
}
|
||||||
@@ -2166,6 +2177,12 @@ bool WS2812FX::deserializeMap(uint8_t n) {
|
|||||||
|
|
||||||
if (!requestJSONBufferLock(7)) return false;
|
if (!requestJSONBufferLock(7)) return false;
|
||||||
|
|
||||||
|
// WLEDMM: before changing maps, make sure our strip is _not_ servicing effects in parallel
|
||||||
|
if (strip.isServicing()) {
|
||||||
|
USER_PRINTLN(F("deserializeMap(): strip is still drawing effects, waiting ..."));
|
||||||
|
strip.waitUntilIdle();
|
||||||
|
}
|
||||||
|
|
||||||
//WLEDMM: change upstream code: do not load complete ledmaps in json as this blows up memory, use file read instead
|
//WLEDMM: change upstream code: do not load complete ledmaps in json as this blows up memory, use file read instead
|
||||||
//read the file
|
//read the file
|
||||||
File f;
|
File f;
|
||||||
|
|||||||
@@ -95,9 +95,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
|||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
if (strip.isServicing() && (strncmp(pcTaskGetTaskName(NULL), "loopTask", 8) != 0)) { // if we are in looptask (arduino loop), its safe to proceed without waiting
|
if (strip.isServicing() && (strncmp(pcTaskGetTaskName(NULL), "loopTask", 8) != 0)) { // if we are in looptask (arduino loop), its safe to proceed without waiting
|
||||||
if (fromFS) {
|
if (fromFS) {
|
||||||
USER_PRINTLN(F("deserializeConfig(fromFS): strip is still drawing effects, waiting ..."));
|
USER_PRINTLN(F("deserializeConfig(fromFS): strip is still drawing effects."));
|
||||||
} else {
|
} else {
|
||||||
USER_PRINTLN(F("deserializeConfig(): strip is still drawing effects, waiting ..."));
|
USER_PRINTLN(F("deserializeConfig(): strip is still drawing effects."));
|
||||||
}
|
}
|
||||||
strip.waitUntilIdle();
|
strip.waitUntilIdle();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,11 @@
|
|||||||
* JSON API (De)serialization
|
* JSON API (De)serialization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
static bool inDeepCall = false; // WLEDMM needed so that recursive deserializeSegment() does not remove locks too early
|
||||||
|
|
||||||
bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
||||||
{
|
{
|
||||||
|
const bool iAmGroot = !inDeepCall; // WLEDMM will only be true if this is the toplevel of the recursion.
|
||||||
//WLEDMM add USER_PRINT
|
//WLEDMM add USER_PRINT
|
||||||
if (elem.size()!=1 || elem["stop"] != 0) { // not for {"stop":0}
|
if (elem.size()!=1 || elem["stop"] != 0) { // not for {"stop":0}
|
||||||
String temp;
|
String temp;
|
||||||
@@ -88,7 +91,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
|||||||
// WLEDMM: before changing segments, make sure our strip is _not_ servicing effects in parallel
|
// WLEDMM: before changing segments, make sure our strip is _not_ servicing effects in parallel
|
||||||
suspendStripService = true; // temporarily lock out strip updates
|
suspendStripService = true; // temporarily lock out strip updates
|
||||||
if (strip.isServicing()) {
|
if (strip.isServicing()) {
|
||||||
USER_PRINTLN(F("deserializeSegment(): strip is still drawing effects, waiting ..."));
|
USER_PRINTLN(F("deserializeSegment(): strip is still drawing effects."));
|
||||||
strip.waitUntilIdle();
|
strip.waitUntilIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +121,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
|||||||
elem["start"] = start;
|
elem["start"] = start;
|
||||||
elem["stop"] = start + len;
|
elem["stop"] = start + len;
|
||||||
elem["rev"] = !elem["rev"]; // alternate reverse on even/odd segments
|
elem["rev"] = !elem["rev"]; // alternate reverse on even/odd segments
|
||||||
deserializeSegment(elem, i, presetId); // recursive call with new id
|
inDeepCall = true; // WLEDMM remember that we are going into recursion
|
||||||
|
deserializeSegment(elem, i, presetId); // recursive call with new id // WLEDMM expect problems like heap overflow
|
||||||
|
if (iAmGroot) inDeepCall = false; // WLEDMM toplevel -> reset recursion flag
|
||||||
}
|
}
|
||||||
suspendStripService = false; // WLEDMM release lock
|
if (iAmGroot) suspendStripService = false; // WLEDMM release lock
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +184,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
|||||||
seg.set(start, stop, grp, spc, of, startY, stopY);
|
seg.set(start, stop, grp, spc, of, startY, stopY);
|
||||||
|
|
||||||
if (seg.reset && seg.stop == 0) {
|
if (seg.reset && seg.stop == 0) {
|
||||||
suspendStripService = false; // WLEDMM release lock
|
if (iAmGroot) suspendStripService = false; // WLEDMM release lock
|
||||||
return true; // segment was deleted & is marked for reset, no need to change anything else
|
return true; // segment was deleted & is marked for reset, no need to change anything else
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +350,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
|||||||
// send UDP/WS if segment options changed (except selection; will also deselect current preset)
|
// send UDP/WS if segment options changed (except selection; will also deselect current preset)
|
||||||
if (seg.differs(prev) & 0x7F) stateChanged = true;
|
if (seg.differs(prev) & 0x7F) stateChanged = true;
|
||||||
|
|
||||||
suspendStripService = false; // WLEDMM release lock
|
if (iAmGroot) suspendStripService = false; // WLEDMM release lock
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +358,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
|||||||
// presetId is non-0 if called from handlePreset()
|
// presetId is non-0 if called from handlePreset()
|
||||||
bool deserializeState(JsonObject root, byte callMode, byte presetId)
|
bool deserializeState(JsonObject root, byte callMode, byte presetId)
|
||||||
{
|
{
|
||||||
|
const bool iAmGroot = !inDeepCall; // WLEDMM will only be true if this is the toplevel of the recursion.
|
||||||
//WLEDMM add USER_PRINT
|
//WLEDMM add USER_PRINT
|
||||||
String temp;
|
String temp;
|
||||||
serializeJson(root, temp);
|
serializeJson(root, temp);
|
||||||
@@ -404,7 +410,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
|
|||||||
// WLEDMM: before changing strip, make sure our strip is _not_ servicing effects in parallel
|
// WLEDMM: before changing strip, make sure our strip is _not_ servicing effects in parallel
|
||||||
suspendStripService = true; // temporarily lock out strip updates
|
suspendStripService = true; // temporarily lock out strip updates
|
||||||
if (strip.isServicing()) {
|
if (strip.isServicing()) {
|
||||||
USER_PRINTLN(F("deserializeState(): strip is still drawing effects, waiting ..."));
|
USER_PRINTLN(F("deserializeState(): strip is still drawing effects."));
|
||||||
strip.waitUntilIdle();
|
strip.waitUntilIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,22 +477,28 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
|
|||||||
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
|
for (size_t s = 0; s < strip.getSegmentsNum(); s++) {
|
||||||
Segment &sg = strip.getSegment(s);
|
Segment &sg = strip.getSegment(s);
|
||||||
if (sg.isSelected()) {
|
if (sg.isSelected()) {
|
||||||
|
inDeepCall = true; // WLEDMM remember that we are going into recursion
|
||||||
deserializeSegment(segVar, s, presetId);
|
deserializeSegment(segVar, s, presetId);
|
||||||
|
if (iAmGroot) inDeepCall = false; // WLEDMM toplevel -> reset recursion flag
|
||||||
//didSet = true;
|
//didSet = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: not sure if it is good idea to change first active but unselected segment
|
//TODO: not sure if it is good idea to change first active but unselected segment
|
||||||
//if (!didSet) deserializeSegment(segVar, strip.getMainSegmentId(), presetId);
|
//if (!didSet) deserializeSegment(segVar, strip.getMainSegmentId(), presetId);
|
||||||
} else {
|
} else {
|
||||||
|
inDeepCall = true; // WLEDMM remember that we are going into recursion
|
||||||
deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID
|
deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID
|
||||||
|
if (iAmGroot) inDeepCall = false; // WLEDMM toplevel -> reset recursion flag
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size_t deleted = 0;
|
size_t deleted = 0;
|
||||||
JsonArray segs = segVar.as<JsonArray>();
|
JsonArray segs = segVar.as<JsonArray>();
|
||||||
|
inDeepCall = true; // WLEDMM remember that we are going into recursion
|
||||||
for (JsonObject elem : segs) {
|
for (JsonObject elem : segs) {
|
||||||
if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++;
|
if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++;
|
||||||
}
|
}
|
||||||
if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments
|
if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments
|
||||||
|
if (iAmGroot) inDeepCall = false; // WLEDMM toplevel -> reset recursion flag
|
||||||
}
|
}
|
||||||
|
|
||||||
usermods.readFromJsonState(root);
|
usermods.readFromJsonState(root);
|
||||||
@@ -524,7 +536,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
|
|||||||
presetCycCurr = ps;
|
presetCycCurr = ps;
|
||||||
unloadPlaylist(); // applying a preset unloads the playlist
|
unloadPlaylist(); // applying a preset unloads the playlist
|
||||||
applyPreset(ps, callMode); // async load from file system (only preset ID was specified)
|
applyPreset(ps, callMode); // async load from file system (only preset ID was specified)
|
||||||
suspendStripService = false; // WLEDMM release lock
|
if (iAmGroot) suspendStripService = false; // WLEDMM release lock
|
||||||
return stateResponse;
|
return stateResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -548,7 +560,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
|
|||||||
stateUpdated(callMode);
|
stateUpdated(callMode);
|
||||||
if (presetToRestore) currentPreset = presetToRestore;
|
if (presetToRestore) currentPreset = presetToRestore;
|
||||||
|
|
||||||
suspendStripService = false; // WLEDMM release lock
|
if (iAmGroot) suspendStripService = false; // WLEDMM release lock
|
||||||
return stateResponse;
|
return stateResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
//0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec 7: DMX 8: usermods 9: N/A 10: 2D
|
//0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec 7: DMX 8: usermods 9: N/A 10: 2D
|
||||||
if (subPage <1 || subPage >10 || !correctPIN) return;
|
if (subPage <1 || subPage >10 || !correctPIN) return;
|
||||||
|
|
||||||
// WLEDMM: before changing bus or strip settings, make sure our strip is _not_ servicing effects in parallel
|
// WLEDMM: before changing bus, ledmap, strip or 2D settings, make sure our strip is _not_ servicing effects in parallel
|
||||||
if ((subPage == 2) || (subPage == 10)) {
|
if ((subPage == 2) || (subPage == 3) || (subPage == 10)) {
|
||||||
suspendStripService = true; // temporarily lock out strip updates
|
suspendStripService = true; // temporarily lock out strip updates
|
||||||
if (strip.isServicing()) {
|
if (strip.isServicing()) {
|
||||||
USER_PRINTLN(F("handleSettingsSet(): strip is still drawing effects, waiting ..."));
|
USER_PRINTLN(F("handleSettingsSet(): strip is still drawing effects."));
|
||||||
strip.waitUntilIdle();
|
strip.waitUntilIdle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -775,7 +775,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((subPage == 2) || (subPage == 10)) {
|
if ((subPage == 2) || (subPage == 3) || (subPage == 10)) {
|
||||||
suspendStripService = false; // WLEDMM release lock
|
suspendStripService = false; // WLEDMM release lock
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,7 +817,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
|
|||||||
|
|
||||||
// WLEDMM: before changing segment settings, make sure our strip is _not_ servicing effects in parallel
|
// WLEDMM: before changing segment settings, make sure our strip is _not_ servicing effects in parallel
|
||||||
if (strip.isServicing()) {
|
if (strip.isServicing()) {
|
||||||
USER_PRINTLN(F("handleSet(): strip is still drawing effects, waiting ..."));
|
USER_PRINTLN(F("handleSet(): strip is still drawing effects."));
|
||||||
strip.waitUntilIdle();
|
strip.waitUntilIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,15 +165,19 @@ void WLED::loop()
|
|||||||
unsigned long stripMillis = millis();
|
unsigned long stripMillis = millis();
|
||||||
#endif
|
#endif
|
||||||
if (!offMode || strip.isOffRefreshRequired()) {
|
if (!offMode || strip.isOffRefreshRequired()) {
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE) // WLEDMM experimental
|
||||||
static unsigned long lastTimeService = 0; // WLEMM needed to remove stale lock
|
static unsigned long lastTimeService = 0; // WLEMM needed to remove stale lock
|
||||||
if (!suspendStripService && !doInitBusses && !loadLedmap) { // WLEDMM prevent effect drawing while strip or segments are being updated
|
if (!suspendStripService && !doInitBusses && !loadLedmap) { // WLEDMM prevent effect drawing while strip or segments are being updated
|
||||||
|
#endif
|
||||||
strip.service();
|
strip.service();
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_PROTECT_SERVICE)
|
||||||
lastTimeService = millis();
|
lastTimeService = millis();
|
||||||
} else {
|
} else {
|
||||||
if (suspendStripService && (millis() - lastTimeService > 1500)) { // WLEDMM remove stale lock after 1.5 seconds
|
if (suspendStripService && (millis() - lastTimeService > 1500)) { // WLEDMM remove stale lock after 1.5 seconds
|
||||||
USER_PRINTLN("--> looptask: stale suspendStripService lock removed after 1500 ms."); // should not happen - check for missing "suspendStripService = false"
|
USER_PRINTLN("--> looptask: stale suspendStripService lock removed after 1500 ms."); // should not happen - check for missing "suspendStripService = false"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
else if (!noWifiSleep)
|
else if (!noWifiSleep)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// version code in format yymmddb (b = daily build)
|
// version code in format yymmddb (b = daily build)
|
||||||
#define VERSION 2306010
|
#define VERSION 2306011
|
||||||
|
|
||||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||||
//#define WLED_USE_MY_CONFIG
|
//#define WLED_USE_MY_CONFIG
|
||||||
|
|||||||
Reference in New Issue
Block a user