From db310c662fb5e6bc0901577d18f7e8ce9e2f9d71 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 16 Sep 2025 19:56:26 +0200 Subject: [PATCH 1/7] Safety Checks for UI, fix for cfg exceeding LED limit - added some safety checks for UI to not throw errors if JSCON parameters are missing - add all buses in config even if they exceed RAM limit, truncates long buses --- wled00/FX_fcn.cpp | 16 +++++++++++----- wled00/data/index.js | 45 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 2f8d5515fd..c4142fae68 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1173,11 +1173,17 @@ void WS2812FX::finalizeInit() { // create buses/outputs unsigned mem = 0; - for (const auto &bus : busConfigs) { - mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer - if (mem <= MAX_LED_MEMORY) { - if (BusManager::add(bus) == -1) break; - } else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); + for (auto bus : busConfigs) { + unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); + // if memory exceeds limit, add it anyway but reduce LED count to default if it is larger (a bit dangerous, but prevents no buses being created at all) + if (mem + busMemUsage > MAX_LED_MEMORY) { + bus.count = min((int)bus.count, DEFAULT_LED_COUNT); + DEBUG_PRINTF_P(PSTR("Bus %d memory usage exceeds limit, setting count to %d\n"), (int)bus.type, bus.count); + } + if (BusManager::add(bus) != -1) { + mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); + if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) digitalCount++; + } else break; } busConfigs.clear(); busConfigs.shrink_to_fit(); diff --git a/wled00/data/index.js b/wled00/data/index.js index fe154783d0..f7bc79cfcf 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -668,9 +668,20 @@ function parseInfo(i) { if (loc) name = "(L) " + name; d.title = name; simplifiedUI = i.simplifiedui; - ledCount = i.leds.count; + // safety checks for LED count data to prevent UI crashes + if (i.leds && typeof i.leds.count !== 'undefined') { + ledCount = i.leds.count; + } else { + console.warn('No LED count'); + ledCount = 30; // fallback value + } //syncTglRecv = i.str; - maxSeg = i.leds.maxseg; + if (i.leds && typeof i.leds.maxseg !== 'undefined') { + maxSeg = i.leds.maxseg; + } else { + console.warn('No max segment data'); + maxSeg = 16; // Reasonable fallback for max segments + } pmt = i.fs.pmt; if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide"); gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none"; @@ -912,12 +923,24 @@ function populateSegments(s) gId(`segr${i}`).classList.add("hide"); } if (segCount < 2) { - gId(`segd${lSeg}`).classList.add("hide"); // hide delete if only one segment - if (parseInt(gId("seg0bri").value)==255) gId(`segp0`).classList.add("hide"); + // safety check for segment elements to prevent UI crashes + const segdElement = gId(`segd${lSeg}`); + if (segdElement) segdElement.classList.add("hide"); // hide delete if only one segment + const seg0briElement = gId("seg0bri"); + const segp0Element = gId(`segp0`); + if (seg0briElement && segp0Element && parseInt(seg0briElement.value)==255) segp0Element.classList.add("hide"); // hide segment controls if there is only one segment in simplified UI if (simplifiedUI) gId("segcont").classList.add("hide"); } - if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value) 1) ? "block":"none"; // rsbtn parent if (Array.isArray(li.maps) && li.maps.length>1) { @@ -2253,7 +2276,9 @@ function rptSeg(s) var rev = gId(`seg${s}rev`).checked; var mi = gId(`seg${s}mi`).checked; var sel = gId(`seg${s}sel`).checked; - var pwr = gId(`seg${s}pwr`).classList.contains('act'); + // safety check for segment power element to prevent UI crashes + const segPwrElement = gId(`seg${s}pwr`); + var pwr = segPwrElement ? segPwrElement.classList.contains('act') : false; var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop, "rev": rev, "mi": mi, "on": pwr, "bri": parseInt(gId(`seg${s}bri`).value), "sel": sel}}; if (gId(`seg${s}grp`)) { var grp = parseInt(gId(`seg${s}grp`).value); @@ -2380,7 +2405,13 @@ function setGrp(s, g) function setSegPwr(s) { - var pwr = gId(`seg${s}pwr`).classList.contains('act'); + // safety check for segment power element to prevent UI crashes + const segPwrElement = gId(`seg${s}pwr`); + if (!segPwrElement) { + console.warn('No power elemen'); + return; + } + var pwr = segPwrElement.classList.contains('act'); var obj = {"seg": {"id": s, "on": !pwr}}; requestJson(obj); } From f8ca0f94293277d31edd32960ff39ede178580fa Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 17 Sep 2025 22:47:27 -0400 Subject: [PATCH 2/7] Keep bus length when failed In the event that a Bus fails to initialize, or the memory validation fails, keep the configuration around so the settings contents don't change out from under the user. --- wled00/FX_fcn.cpp | 16 ++++++++-------- wled00/bus_manager.cpp | 32 +++++++++++++++++++++++++++----- wled00/bus_manager.h | 35 +++++++++++++++++++++++++++++++++-- wled00/cfg.cpp | 2 +- wled00/xml.cpp | 2 +- 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f54c816004..9cf14b921f 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1181,14 +1181,14 @@ void WS2812FX::finalizeInit() { // create buses/outputs unsigned mem = 0; for (auto bus : busConfigs) { - unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); - // if memory exceeds limit, add it anyway but reduce LED count to default if it is larger (a bit dangerous, but prevents no buses being created at all) - if (mem + busMemUsage > MAX_LED_MEMORY) { - bus.count = min((int)bus.count, DEFAULT_LED_COUNT); - DEBUG_PRINTF_P(PSTR("Bus %d memory usage exceeds limit, setting count to %d\n"), (int)bus.type, bus.count); + bool use_placeholder = false; + unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); + if (mem + busMemUsage > MAX_LED_MEMORY) { + DEBUG_PRINTF_P(PSTR("Bus %d with %d LEDS memory usage exceeds limit\n"), (int)bus.type, bus.count); + use_placeholder = true; } - if (BusManager::add(bus) != -1) { - mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); + if (BusManager::add(bus, use_placeholder) != -1) { + mem += BusManager::busses.back()->getBusSize(); if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) digitalCount++; } else break; } @@ -1807,7 +1807,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) { for (size_t i = s; i < BusManager::getNumBusses(); i++) { const Bus *bus = BusManager::getBus(i); - if (!bus || !bus->isOk()) break; + if (!bus) break; segStarts[s] = bus->getStart(); segStops[s] = segStarts[s] + bus->getLength(); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 612c0f3aa8..4e63409466 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -755,6 +755,26 @@ void BusNetwork::cleanup() { } +BusPlaceholder::BusPlaceholder(const BusConfig &bc) +: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, bc.refreshReq) +, _colorOrder(bc.colorOrder) +, _skipAmount(bc.skipAmount) +, _frequency(bc.frequency) +, _milliAmpsPerLed(bc.milliAmpsPerLed) +, _milliAmpsMax(bc.milliAmpsMax) +, _text(bc.text) +{ + memcpy(_pins, bc.pins, sizeof(_pins)); +} + +size_t BusPlaceholder::getPins(uint8_t* pinArray) const { + size_t nPins = Bus::getNumberOfPins(_type); + if (pinArray) { + for (size_t i = 0; i < nPins; i++) pinArray[i] = _pins[i]; + } + return nPins; +} + //utility to get the approx. memory usage of a given BusConfig size_t BusConfig::memUsage(unsigned nr) const { if (Bus::isVirtual(type)) { @@ -797,7 +817,7 @@ size_t BusManager::memUsage() { return size + maxI2S; } -int BusManager::add(const BusConfig &bc) { +int BusManager::add(const BusConfig &bc, bool placeholder) { DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (p:%d v:%d)\n"), getNumBusses(), getNumVirtualBusses()); unsigned digital = 0; unsigned analog = 0; @@ -807,8 +827,10 @@ int BusManager::add(const BusConfig &bc) { if (bus->isDigital() && !bus->is2Pin()) digital++; if (bus->is2Pin()) twoPin++; } - if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1; - if (Bus::isVirtual(bc.type)) { + if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) placeholder = true; + if (placeholder) { + busses.push_back(make_unique(bc)); + } else if (Bus::isVirtual(bc.type)) { busses.push_back(make_unique(bc)); } else if (Bus::isDigital(bc.type)) { busses.push_back(make_unique(bc, Bus::is2Pin(bc.type) ? twoPin : digital)); @@ -907,7 +929,7 @@ void BusManager::on() { if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (auto &bus : busses) { uint8_t pins[2] = {255,255}; - if (bus->isDigital() && bus->getPins(pins)) { + if (bus->isDigital() && bus->getPins(pins) && bus->isOk()) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { BusDigital &b = static_cast(*bus); b.begin(); @@ -1002,7 +1024,7 @@ void BusManager::initializeABL() { _useABL = true; // at least one bus has ABL set uint32_t ESPshare = MA_FOR_ESP / numABLbuses; // share of ESP current per ABL bus for (auto &bus : busses) { - if (bus->isDigital()) { + if (bus->isDigital() && bus->isOk()) { BusDigital &busd = static_cast(*bus); uint32_t busLength = busd.getLength(); uint32_t busDemand = busLength * busd.getLEDCurrent(); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index fe70a05170..1adad34036 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -125,7 +125,7 @@ class Bus { virtual void setColorOrder(uint8_t co) {} virtual uint32_t getPixelColor(unsigned pix) const { return 0; } virtual size_t getPins(uint8_t* pinArray = nullptr) const { return 0; } - virtual uint16_t getLength() const { return isOk() ? _len : 0; } + virtual uint16_t getLength() const { return _len; } virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } virtual unsigned skippedLeds() const { return 0; } virtual uint16_t getFrequency() const { return 0U; } @@ -363,6 +363,37 @@ class BusNetwork : public Bus { #endif }; +// Placeholder for buses that we can't construct due to resource limitations +// This preserves the configuration so it can be read back to the settings pages +class BusPlaceholder : public Bus { + public: + BusPlaceholder(const BusConfig &bc); + + // Actual calls are stubbed out + void setPixelColor(unsigned pix, uint32_t c) override {}; + void show() override {}; + + // Accessors + uint8_t getColorOrder() const override { return _colorOrder; } + size_t getPins(uint8_t* pinArray) const override; + unsigned skippedLeds() const override { return _skipAmount; } + uint16_t getFrequency() const override { return _frequency; } + uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } + uint16_t getMaxCurrent() const override { return _milliAmpsMax; } + const String getCustomText() const override { return _text; } + + size_t getBusSize() const override { return sizeof(BusPlaceholder); } + + private: + uint8_t _colorOrder; + uint8_t _skipAmount; + uint8_t _pins[5]; + uint16_t _frequency; + uint8_t _milliAmpsPerLed; + uint16_t _milliAmpsMax; + String _text; +}; + //temporary struct for passing bus configuration to bus struct BusConfig { @@ -464,7 +495,7 @@ namespace BusManager { //do not call this method from system context (network callback) void removeAll(); - int add(const BusConfig &bc); + int add(const BusConfig &bc, bool placeholder); void on(); void off(); diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 9d7477c7a7..ababcf7b58 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -973,7 +973,7 @@ void serializeConfig(JsonObject root) { for (size_t s = 0; s < BusManager::getNumBusses(); s++) { DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s); const Bus *bus = BusManager::getBus(s); - if (!bus || !bus->isOk()) break; + if (!bus) break; // Memory corruption, iterator invalid DEBUG_PRINTF_P(PSTR(" (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"), (int)bus->getStart(), (int)(bus->getStart()+bus->getLength()), (int)(bus->getType() & 0x7F), diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 40bd992443..32759eaf8c 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -314,7 +314,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) unsigned sumMa = 0; for (size_t s = 0; s < BusManager::getNumBusses(); s++) { const Bus *bus = BusManager::getBus(s); - if (!bus || !bus->isOk()) break; // should not happen but for safety + if (!bus) break; // should not happen but for safety int offset = s < 10 ? '0' : 'A' - 10; char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length From 465d8160d13cb4ed5a9e9b3c7a6f21f03c8175be Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 27 Sep 2025 17:00:37 +0200 Subject: [PATCH 3/7] improvements to low heap check --- wled00/FX_fcn.cpp | 8 +++++-- wled00/wled.cpp | 47 ++++++++++++++++++++++++++++-------------- wled00/wled_server.cpp | 2 ++ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 9cf14b921f..e85873efc3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1182,8 +1182,8 @@ void WS2812FX::finalizeInit() { unsigned mem = 0; for (auto bus : busConfigs) { bool use_placeholder = false; - unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); - if (mem + busMemUsage > MAX_LED_MEMORY) { + unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount : 0); + if (mem + busMemUsage > MAX_LED_MEMORY) { DEBUG_PRINTF_P(PSTR("Bus %d with %d LEDS memory usage exceeds limit\n"), (int)bus.type, bus.count); use_placeholder = true; } @@ -1786,6 +1786,10 @@ Segment& WS2812FX::getSegment(unsigned id) { void WS2812FX::resetSegments() { _segments.clear(); // destructs all Segment as part of clearing _segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1); + if(_segments.size() == 0) { + _segments.emplace_back(); // if out of heap, create a default segment + errorFlag = ERR_NORAM_PX; + } _segments.shrink_to_fit(); // just in case ... _mainSegment = 0; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 923688106d..40db0bfeac 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -39,8 +39,8 @@ void WLED::reset() void WLED::loop() { - static uint32_t lastHeap = UINT32_MAX; - static unsigned long heapTime = 0; + static uint16_t heapTime = 0; // timestamp for heap check + static uint8_t heapDanger = 0; // counter for consecutive low-heap readings #ifdef WLED_DEBUG static unsigned long lastRun = 0; unsigned long loopMillis = millis(); @@ -169,19 +169,36 @@ void WLED::loop() createEditHandler(false); } - // reconnect WiFi to clear stale allocations if heap gets too low - if (millis() - heapTime > 15000) { - uint32_t heap = getFreeHeapSize(); - if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) { - DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap); - forceReconnect = true; - strip.resetSegments(); // remove all but one segments from memory - } else if (heap < MIN_HEAP_SIZE) { - DEBUG_PRINTLN(F("Heap low, purging segments.")); - strip.purgeSegments(); + // free memory and reconnect WiFi to clear stale allocations if heap is too low for too long, check once per second + if ((uint16_t)(millis() - heapTime) > 1000) { + #ifdef ESP8266 + uint32_t heap = getFreeHeapSize(); // ESP8266 needs ~8k of free heap for UI to work properly + #else + uint32_t heap = getContiguousFreeHeap(); // ESP32 family needs ~10k of contiguous free heap for UI to work properly + #endif + if (heap < MIN_HEAP_SIZE) heapDanger++; + else heapDanger = 0; + switch (heapDanger) { + case 15: // 15 consecutive seconds + DEBUG_PRINTLN(F("Heap low, purging segments")); + strip.purgeSegments(); + strip.setTransition(0); // disable transitions + strip.getMainSegment().setMode(FX_MODE_STATIC); // set static mode to free effect memory + break; + case 30: // 30 consecutive seconds + DEBUG_PRINTLN(F("Heap low, reset segments")); + strip.resetSegments(); // remove all but one segments from memory + break; + case 45: // 45 consecutive seconds + DEBUG_PRINTF_P(PSTR("Heap panic! Reset strip, reset connection\n")); + strip.~WS2812FX(); // deallocate strip and all its memory + new(&strip) WS2812FX(); // re-create strip object, respecting current memory limits + forceReconnect = true; // in case wifi is broken, make sure UI comes back, set disableForceReconnect = true to avert + break; + default: + break; } - lastHeap = heap; - heapTime = millis(); + heapTime = (uint16_t)millis(); } //LED settings have been saved, re-init busses @@ -790,7 +807,7 @@ void WLED::handleConnection() if ((wifiConfigured && multiWiFi.size() > 1 && WiFi.scanComplete() < 0) || (now < 2000 && (!wifiConfigured || apBehavior == AP_BEHAVIOR_ALWAYS))) return; - if (lastReconnectAttempt == 0 || forceReconnect) { + if (lastReconnectAttempt == 0 || (forceReconnect && !disableForceReconnect)) { DEBUG_PRINTF_P(PSTR("Initial connect or forced reconnect (@ %lus).\n"), nowS); selectedWiFi = findWiFi(); // find strongest WiFi initConnection(); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 65d9585900..5dc2869471 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -434,6 +434,7 @@ void initServer() strip.suspend(); backupConfig(); // backup current config in case the update ends badly strip.resetSegments(); // free as much memory as you can + disableForceReconnect = true; // prevent wifi reconnect during OTA #ifdef ESP8266 Update.runAsync(true); #endif @@ -451,6 +452,7 @@ void initServer() WLED::instance().enableWatchdog(); #endif } + disableForceReconnect = false; // allow wifi reconnects again } }); #else From 52e443d05a5ed40b5bb969e2d0990cdf2cc4e20c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 28 Sep 2025 11:25:27 +0200 Subject: [PATCH 4/7] add `isPlaceholder()` to bus, some fixes --- wled00/FX_fcn.cpp | 4 ++-- wled00/bus_manager.cpp | 2 +- wled00/bus_manager.h | 3 +++ wled00/wled.cpp | 7 ++++++- wled00/wled.h | 1 + 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 619e84b39e..d921deb62f 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1202,12 +1202,12 @@ void WS2812FX::finalizeInit() { #endif if (mem + busMemUsage + maxI2S > MAX_LED_MEMORY) { DEBUG_PRINTF_P(PSTR("Bus %d with %d LEDS memory usage exceeds limit\n"), (int)bus.type, bus.count); - errorFlag = ERR_NORAM_PX; // alert UI TODO: make this a distinct error + errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: not enough memory for bus use_placeholder = true; } if (BusManager::add(bus, use_placeholder) != -1) { mem += BusManager::busses.back()->getBusSize(); - if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) digitalCount++; //TODO: make it count-- if bus is placeholder + if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && BusManager::busses.back()->isPlaceholder()) digitalCount--; // remove placeholder from digital count } } DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage()); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index f63d85eb7f..311c9b0435 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -1194,7 +1194,7 @@ int BusManager::add(const BusConfig &bc, bool placeholder) { if (bus->isDigital() && !bus->is2Pin()) digital++; if (bus->is2Pin()) twoPin++; } - if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) placeholder = true; + if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) placeholder = true; // TODO: add errorFlag here if (placeholder) { busses.push_back(make_unique(bc)); } else if (Bus::isVirtual(bc.type)) { diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index b71c136812..625707049e 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -152,6 +152,7 @@ class Bus { inline bool isPWM() const { return isPWM(_type); } inline bool isVirtual() const { return isVirtual(_type); } inline bool is16bit() const { return is16bit(_type); } + virtual bool isPlaceholder() const { return false; } inline bool mustRefresh() const { return mustRefresh(_type); } inline void setReversed(bool reversed) { _reversed = reversed; } inline void setStart(uint16_t start) { _start = start; } @@ -374,6 +375,7 @@ class BusNetwork : public Bus { // Placeholder for buses that we can't construct due to resource limitations // This preserves the configuration so it can be read back to the settings pages +// Function calls "mimic" the replaced bus, isPlaceholder() can be used to identify a placeholder class BusPlaceholder : public Bus { public: BusPlaceholder(const BusConfig &bc); @@ -390,6 +392,7 @@ class BusPlaceholder : public Bus { uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } const String getCustomText() const override { return _text; } + bool isPlaceholder() const override { return true; } size_t getBusSize() const override { return sizeof(BusPlaceholder); } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 40db0bfeac..aad4355473 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -183,17 +183,22 @@ void WLED::loop() DEBUG_PRINTLN(F("Heap low, purging segments")); strip.purgeSegments(); strip.setTransition(0); // disable transitions - strip.getMainSegment().setMode(FX_MODE_STATIC); // set static mode to free effect memory + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { + strip.getSegments()[i].setMode(FX_MODE_STATIC); // set static mode to free effect memory + } + errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: segment reset break; case 30: // 30 consecutive seconds DEBUG_PRINTLN(F("Heap low, reset segments")); strip.resetSegments(); // remove all but one segments from memory + errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: segment reset break; case 45: // 45 consecutive seconds DEBUG_PRINTF_P(PSTR("Heap panic! Reset strip, reset connection\n")); strip.~WS2812FX(); // deallocate strip and all its memory new(&strip) WS2812FX(); // re-create strip object, respecting current memory limits forceReconnect = true; // in case wifi is broken, make sure UI comes back, set disableForceReconnect = true to avert + errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: strip reset break; default: break; diff --git a/wled00/wled.h b/wled00/wled.h index a783986a87..2ecb138c56 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -609,6 +609,7 @@ WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use i WLED_GLOBAL bool apActive _INIT(false); WLED_GLOBAL byte apClients _INIT(0); WLED_GLOBAL bool forceReconnect _INIT(false); +WLED_GLOBAL bool disableForceReconnect _INIT(false); WLED_GLOBAL unsigned long lastReconnectAttempt _INIT(0); WLED_GLOBAL bool interfacesInited _INIT(false); WLED_GLOBAL bool wasConnected _INIT(false); From ac529e7653b94b646a63484c7f80809c93390e68 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 28 Sep 2025 15:47:36 +0200 Subject: [PATCH 5/7] remove `disableForceReconnect` for better future implementation --- wled00/wled.cpp | 2 +- wled00/wled.h | 1 - wled00/wled_server.cpp | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index aad4355473..94cf087f0b 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -812,7 +812,7 @@ void WLED::handleConnection() if ((wifiConfigured && multiWiFi.size() > 1 && WiFi.scanComplete() < 0) || (now < 2000 && (!wifiConfigured || apBehavior == AP_BEHAVIOR_ALWAYS))) return; - if (lastReconnectAttempt == 0 || (forceReconnect && !disableForceReconnect)) { + if (lastReconnectAttempt == 0 || forceReconnect) { DEBUG_PRINTF_P(PSTR("Initial connect or forced reconnect (@ %lus).\n"), nowS); selectedWiFi = findWiFi(); // find strongest WiFi initConnection(); diff --git a/wled00/wled.h b/wled00/wled.h index 2ecb138c56..a783986a87 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -609,7 +609,6 @@ WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use i WLED_GLOBAL bool apActive _INIT(false); WLED_GLOBAL byte apClients _INIT(0); WLED_GLOBAL bool forceReconnect _INIT(false); -WLED_GLOBAL bool disableForceReconnect _INIT(false); WLED_GLOBAL unsigned long lastReconnectAttempt _INIT(0); WLED_GLOBAL bool interfacesInited _INIT(false); WLED_GLOBAL bool wasConnected _INIT(false); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 2355686b7d..75b4ae3f5a 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -439,7 +439,6 @@ void initServer() strip.suspend(); backupConfig(); // backup current config in case the update ends badly strip.resetSegments(); // free as much memory as you can - disableForceReconnect = true; // prevent wifi reconnect during OTA #ifdef ESP8266 Update.runAsync(true); #endif @@ -457,7 +456,6 @@ void initServer() WLED::instance().enableWatchdog(); #endif } - disableForceReconnect = false; // allow wifi reconnects again } }); #else From 70015bf2cc281589b55a142c0c6e905cd5fd4bae Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 17 Oct 2025 18:30:21 +0200 Subject: [PATCH 6/7] revert changes to index.js - reverting changes as they are related to parallel loading, I have found a better way to only use sequential loading which makes these changes obsolete --- wled00/data/index.js | 47 ++++++++------------------------------------ 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index 3ff6376a22..05d8f56aa8 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -668,20 +668,9 @@ function parseInfo(i) { if (loc) name = "(L) " + name; d.title = name; simplifiedUI = i.simplifiedui; - // safety checks for LED count data to prevent UI crashes - if (i.leds && typeof i.leds.count !== 'undefined') { - ledCount = i.leds.count; - } else { - console.warn('No LED count'); - ledCount = 30; // fallback value - } + ledCount = i.leds.count; //syncTglRecv = i.str; - if (i.leds && typeof i.leds.maxseg !== 'undefined') { - maxSeg = i.leds.maxseg; - } else { - console.warn('No max segment data'); - maxSeg = 16; // Reasonable fallback for max segments - } + maxSeg = i.leds.maxseg; pmt = i.fs.pmt; if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide"); gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none"; @@ -923,24 +912,12 @@ function populateSegments(s) gId(`segr${i}`).classList.add("hide"); } if (segCount < 2) { - // safety check for segment elements to prevent UI crashes - const segdElement = gId(`segd${lSeg}`); - if (segdElement) segdElement.classList.add("hide"); // hide delete if only one segment - const seg0briElement = gId("seg0bri"); - const segp0Element = gId(`segp0`); - if (seg0briElement && segp0Element && parseInt(seg0briElement.value)==255) segp0Element.classList.add("hide"); + gId(`segd${lSeg}`).classList.add("hide"); // hide delete if only one segment + if (parseInt(gId("seg0bri").value)==255) gId(`segp0`).classList.add("hide"); // hide segment controls if there is only one segment in simplified UI if (simplifiedUI) gId("segcont").classList.add("hide"); } - // safety checks for segment control elements - const segSElement = gId(`seg${lSeg}s`); - const segEElement = gId(`seg${lSeg}e`); - const segrElement = gId(`segr${lSeg}`); - if (!isM && !noNewSegs && segSElement && segEElement && segrElement) { - const segLen = cfg.comp.seglen ? parseInt(segSElement.value) : 0; - const segEnd = parseInt(segEElement.value); - if (segLen + segEnd < ledCount) segrElement.classList.remove("hide"); - } + if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value) 1) ? "block":"none"; // rsbtn parent if (Array.isArray(li.maps) && li.maps.length>1) { @@ -2274,9 +2251,7 @@ function rptSeg(s) var rev = gId(`seg${s}rev`).checked; var mi = gId(`seg${s}mi`).checked; var sel = gId(`seg${s}sel`).checked; - // safety check for segment power element to prevent UI crashes - const segPwrElement = gId(`seg${s}pwr`); - var pwr = segPwrElement ? segPwrElement.classList.contains('act') : false; + var pwr = gId(`seg${s}pwr`).classList.contains('act'); var obj = {"seg": {"id": s, "n": name, "start": start, "stop": (cfg.comp.seglen?start:0)+stop, "rev": rev, "mi": mi, "on": pwr, "bri": parseInt(gId(`seg${s}bri`).value), "sel": sel}}; if (gId(`seg${s}grp`)) { var grp = parseInt(gId(`seg${s}grp`).value); @@ -2403,13 +2378,7 @@ function setGrp(s, g) function setSegPwr(s) { - // safety check for segment power element to prevent UI crashes - const segPwrElement = gId(`seg${s}pwr`); - if (!segPwrElement) { - console.warn('No power elemen'); - return; - } - var pwr = segPwrElement.classList.contains('act'); + var pwr = gId(`seg${s}pwr`).classList.contains('act'); var obj = {"seg": {"id": s, "on": !pwr}}; requestJson(obj); } @@ -3348,4 +3317,4 @@ _C.addEventListener('touchstart', lock, false); _C.addEventListener('mouseout', move, false); _C.addEventListener('mouseup', move, false); -_C.addEventListener('touchend', move, false); +_C.addEventListener('touchend', move, false); \ No newline at end of file From 40039bf806ffa8c30bb1e66f34017641a276d82f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 18 Oct 2025 11:32:30 +0200 Subject: [PATCH 7/7] add hack to fix glitching on C3, added wiggle-room the "wiggle room" is to not reset segments if min heap is just slightly underrun: alloc functions respect min heap, but UI or other functions may grab some extra heap, leading to unnecessary resets. --- wled00/wled.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 94cf087f0b..e635c544f0 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -174,9 +174,14 @@ void WLED::loop() #ifdef ESP8266 uint32_t heap = getFreeHeapSize(); // ESP8266 needs ~8k of free heap for UI to work properly #else + #ifdef CONFIG_IDF_TARGET_ESP32C3 + // calling getContiguousFreeHeap() during led update causes glitches on C3, this is a dirty workaround (tested up to 1500 LEDs) + // this can (probably) be removed once RMT driver for C3 is fixed + delay(15); + #endif uint32_t heap = getContiguousFreeHeap(); // ESP32 family needs ~10k of contiguous free heap for UI to work properly #endif - if (heap < MIN_HEAP_SIZE) heapDanger++; + if (heap < MIN_HEAP_SIZE - 1024) heapDanger++; // allow 1k of "wiggle room" for things that do not respect min heap limits else heapDanger = 0; switch (heapDanger) { case 15: // 15 consecutive seconds