diff --git a/platformio.ini b/platformio.ini index 76928d91..056774e8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -879,6 +879,7 @@ build_flags_XL = ; -D USERMOD_SHT ;; experimental -D USERMOD_VL53L0X_GESTURES -D WLED_ENABLE_PIXART + -D USERMOD_ANIMARTRIX ; WLEDMM usermod: CC BY-NC 3.0 licensed effects by Stefan Petrick lib_deps_XL = claws/BH1750 @^1.2.0 ; used for USERMOD_BH1750 diff --git a/wled00/FX.h b/wled00/FX.h index dcccf850..93293c30 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -727,7 +727,7 @@ class WS2812FX { // 96 bytes _cumulativeFps500(2*500), // WLEDMM more accurate FPS measurement for ESP32 _lastShow500(0), #endif - _isServicing(false), + _isServicing(true), // WLEDMM start with "true" - flag will be reset by strip.finalizeInit() _isOffRefreshRequired(false), _hasWhiteChannel(false), _triggered(false), @@ -766,6 +766,7 @@ class WS2812FX { // 96 bytes printSize(), #endif finalizeInit(), + waitUntilIdle(void), // WLEDMM service(void), setMode(uint8_t segid, uint8_t m), setColor(uint8_t slot, uint32_t c), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 097fbd64..d3e8213b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1424,6 +1424,7 @@ void WS2812FX::enumerateLedmaps() { void WS2812FX::finalizeInit(void) { //reset segment runtimes + suspendStripService = true; // WELDMM avoid running effects on an incomplete strip for (segment &seg : _segments) { seg.markForReset(); seg.resetIfRequired(); @@ -1503,6 +1504,27 @@ void WS2812FX::finalizeInit(void) loadCustomPalettes(); // (re)load all custom palettes DEBUG_PRINTLN(F("Loading custom ledmaps")); deserializeMap(); // (re)load default ledmap + _isServicing = false; // WLEDMM + suspendStripService = false; // WELDMM ready, run ! +} + +// WLEDMM wait until strip is idle (=not servicing). +// 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 +void WS2812FX::waitUntilIdle(void) { +#ifdef ARDUINO_ARCH_ESP32 + if (isServicing()) { + unsigned long waitStarted = millis(); + do { + delay(1); + yield(); + } 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)); + } + return; +#else + return; +#endif } void WS2812FX::service() { diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 32712fb5..32231ffd 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -90,6 +90,19 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // initialize LED pins and lengths prior to other HW (except for ethernet) JsonObject hw_led = hw["led"]; + // WLEDMM: before changing strip, make sure our strip is _not_ servicing effects in parallel + suspendStripService = true; // temporarily lock out strip updates +#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 (fromFS) { + USER_PRINTLN(F("deserializeConfig(fromFS): strip is still drawing effects, waiting ...")); + } else { + USER_PRINTLN(F("deserializeConfig(): strip is still drawing effects, waiting ...")); + } + strip.waitUntilIdle(); + } +#endif + uint8_t autoWhiteMode = RGBW_MODE_MANUAL_ONLY; CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); @@ -599,6 +612,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { needsSave = !usermods.readFromConfig(usermods_settings); } + suspendStripService = false; // WLEDMM release lock + if (fromFS) return needsSave; // if from /json/cfg doReboot = doc[F("rb")] | doReboot; diff --git a/wled00/json.cpp b/wled00/json.cpp index 5b4db6af..c0ccbb22 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -85,8 +85,15 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) id = strip.getSegmentsNum()-1; // segments are added at the end of list } + // WLEDMM: before changing segments, make sure our strip is _not_ servicing effects in parallel + suspendStripService = true; // temporarily lock out strip updates + if (strip.isServicing()) { + USER_PRINTLN(F("deserializeSegment(): strip is still drawing effects, waiting ...")); + strip.waitUntilIdle(); + } + Segment& seg = strip.getSegment(id); - Segment prev = seg; //make a backup so we can tell if something changed + Segment prev = seg; //make a backup so we can tell if something changed // WLEDMM fixMe: copy constructor = waste of memory uint16_t start = elem["start"] | seg.start; if (stop < 0) { @@ -113,6 +120,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) elem["rev"] = !elem["rev"]; // alternate reverse on even/odd segments deserializeSegment(elem, i, presetId); // recursive call with new id } + suspendStripService = false; // WLEDMM release lock return true; } @@ -153,7 +161,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (map1D2D != M12_jMap && seg.jMap) seg.deletejMap(); - if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps + if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps // WLEDMM softhack007: this line sometimes crashes with "Stack canary watchpoint triggered (async_tcp)" seg.map1D2D = constrain(map1D2D, 0, 7); seg.soundSim = constrain(soundSim, 0, 7); @@ -170,7 +178,10 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (stop > start && of > len -1) of = len -1; seg.set(start, stop, grp, spc, of, startY, stopY); - if (seg.reset && seg.stop == 0) return true; // segment was deleted & is marked for reset, no need to change anything else + if (seg.reset && seg.stop == 0) { + suspendStripService = false; // WLEDMM release lock + return true; // segment was deleted & is marked for reset, no need to change anything else + } byte segbri = seg.opacity; if (getVal(elem["bri"], &segbri)) { @@ -293,7 +304,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) // freeze and init to black if (!seg.freeze) { seg.freeze = true; - seg.fill(BLACK); + seg.fill(BLACK); // WLEDMM why now? } uint16_t start = 0, stop = 0; @@ -334,6 +345,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) // send UDP/WS if segment options changed (except selection; will also deselect current preset) if (seg.differs(prev) & 0x7F) stateChanged = true; + suspendStripService = false; // WLEDMM release lock return true; } @@ -389,6 +401,13 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } + // WLEDMM: before changing strip, make sure our strip is _not_ servicing effects in parallel + suspendStripService = true; // temporarily lock out strip updates + if (strip.isServicing()) { + USER_PRINTLN(F("deserializeState(): strip is still drawing effects, waiting ...")); + strip.waitUntilIdle(); + } + // temporary transition (applies only once) tr = root[F("tt")] | -1; if (tr >= 0) @@ -505,6 +524,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) presetCycCurr = ps; unloadPlaylist(); // applying a preset unloads the playlist applyPreset(ps, callMode); // async load from file system (only preset ID was specified) + suspendStripService = false; // WLEDMM release lock return stateResponse; } } @@ -528,6 +548,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) stateUpdated(callMode); if (presetToRestore) currentPreset = presetToRestore; + suspendStripService = false; // WLEDMM release lock return stateResponse; } diff --git a/wled00/set.cpp b/wled00/set.cpp index a26e2d78..60ed8ee1 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -21,6 +21,15 @@ 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 if (subPage <1 || subPage >10 || !correctPIN) return; + // WLEDMM: before changing bus or strip settings, make sure our strip is _not_ servicing effects in parallel + if ((subPage == 2) || (subPage == 10)) { + suspendStripService = true; // temporarily lock out strip updates + if (strip.isServicing()) { + USER_PRINTLN(F("handleSettingsSet(): strip is still drawing effects, waiting ...")); + strip.waitUntilIdle(); + } + } + //WIFI SETTINGS if (subPage == 1) { @@ -766,6 +775,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } #endif + if ((subPage == 2) || (subPage == 10)) { + suspendStripService = false; // WLEDMM release lock + } + lastEditTime = millis(); if (subPage != 2 && !doReboot) doSerializeConfig = true; //serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init) #ifndef WLED_DISABLE_ALEXA @@ -802,6 +815,12 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } } + // WLEDMM: before changing segment settings, make sure our strip is _not_ servicing effects in parallel + if (strip.isServicing()) { + USER_PRINTLN(F("handleSet(): strip is still drawing effects, waiting ...")); + strip.waitUntilIdle(); + } + Segment& selseg = strip.getSegment(selectedSeg); pos = req.indexOf(F("SV=")); //segment selected if (pos > 0) { diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 3774fe77..38d38555 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -164,8 +164,17 @@ void WLED::loop() #ifdef WLED_DEBUG unsigned long stripMillis = millis(); #endif - if (!offMode || strip.isOffRefreshRequired()) - strip.service(); + if (!offMode || strip.isOffRefreshRequired()) { + 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 + strip.service(); + lastTimeService = millis(); + } else { + 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" + } + } + } #ifdef ESP8266 else if (!noWifiSleep) delay(1); //required to make sure ESP enters modem sleep (see #1184) diff --git a/wled00/wled.h b/wled00/wled.h index f8ce04e3..6bb401f1 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2305301 +#define VERSION 2306010 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -690,9 +690,11 @@ WLED_GLOBAL bool e131NewData _INIT(false); WLED_GLOBAL BusManager busses _INIT(BusManager()); WLED_GLOBAL WS2812FX strip _INIT(WS2812FX()); WLED_GLOBAL BusConfig* busConfigs[WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES] _INIT({nullptr}); //temporary, to remember values from network callback until after -WLED_GLOBAL bool doInitBusses _INIT(false); -WLED_GLOBAL bool loadLedmap _INIT(false); //WLEDMM use as bool and use loadedLedmap for Nr -WLED_GLOBAL uint8_t loadedLedmap _INIT(0); //WLEDMM default 0 +// WLEDMM a few "poor man's" mutal exclusion (mutex) flags, because there are not mutex objects on 8266. +WLED_GLOBAL volatile bool doInitBusses _INIT(false); // WLEDMM "volatile" added - needed as we want to sync parallel tasks +WLED_GLOBAL volatile bool loadLedmap _INIT(false); // WLEDMM use as bool and use loadedLedmap for Nr +WLED_GLOBAL volatile uint8_t loadedLedmap _INIT(0); // WLEDMM default 0 +WLED_GLOBAL volatile bool suspendStripService _INIT(false); // WLEDMM temporarily prevent running strip.service, when strip or segments are "under update" and inconsistent #ifndef ESP8266 WLED_GLOBAL char *ledmapNames[WLED_MAX_LEDMAPS-1] _INIT_N(({nullptr})); #endif