Skip to content

Commit af8db57

Browse files
DedeHaiwillmmiles
andauthored
Fix for cfg exceeding LED limit (#4939)
* Safety Checks for UI, fix for cfg exceeding LED limit * improvements to low heap check * add `isPlaceholder()` to bus, some fixes * remove `disableForceReconnect` for better future implementation * add "glitch gating" for C3 and check heapy every 5 seconds instead of every secondd * replace magic number with the correct define, more robust bus defer by look-ahead 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. --------- Co-authored-by: Will Miles <will@willmiles.net>
1 parent 1773f61 commit af8db57

7 files changed

Lines changed: 126 additions & 35 deletions

File tree

wled00/FX_fcn.cpp

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,9 +1187,9 @@ void WS2812FX::finalizeInit() {
11871187
// create buses/outputs
11881188
unsigned mem = 0;
11891189
unsigned maxI2S = 0;
1190-
for (const auto &bus : busConfigs) {
1191-
unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer
1192-
mem += memB;
1190+
for (auto bus : busConfigs) {
1191+
bool use_placeholder = false;
1192+
unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer
11931193
// estimate maximum I2S memory usage (only relevant for digital non-2pin busses)
11941194
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
11951195
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
@@ -1209,13 +1209,14 @@ void WS2812FX::finalizeInit() {
12091209
if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize;
12101210
}
12111211
#endif
1212-
if (mem + maxI2S <= MAX_LED_MEMORY) {
1213-
BusManager::add(bus);
1214-
DEBUG_PRINTF_P(PSTR("Bus memory: %uB\n"), memB);
1215-
} else {
1216-
errorFlag = ERR_NORAM_PX; // alert UI
1217-
DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
1218-
break;
1212+
if (mem + busMemUsage + maxI2S > MAX_LED_MEMORY) {
1213+
DEBUG_PRINTF_P(PSTR("Bus %d with %d LEDS memory usage exceeds limit\n"), (int)bus.type, bus.count);
1214+
errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: not enough memory for bus
1215+
use_placeholder = true;
1216+
}
1217+
if (BusManager::add(bus, use_placeholder) != -1) {
1218+
mem += BusManager::busses.back()->getBusSize();
1219+
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && BusManager::busses.back()->isPlaceholder()) digitalCount--; // remove placeholder from digital count
12191220
}
12201221
}
12211222
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage());
@@ -1824,6 +1825,10 @@ void WS2812FX::resetSegments() {
18241825
if (isServicing()) return;
18251826
_segments.clear(); // destructs all Segment as part of clearing
18261827
_segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1);
1828+
if(_segments.size() == 0) {
1829+
_segments.emplace_back(); // if out of heap, create a default segment
1830+
errorFlag = ERR_NORAM_PX;
1831+
}
18271832
_segments.shrink_to_fit(); // just in case ...
18281833
_mainSegment = 0;
18291834
}
@@ -1846,7 +1851,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) {
18461851

18471852
for (size_t i = s; i < BusManager::getNumBusses(); i++) {
18481853
const Bus *bus = BusManager::getBus(i);
1849-
if (!bus || !bus->isOk()) break;
1854+
if (!bus) break;
18501855

18511856
segStarts[s] = bus->getStart();
18521857
segStops[s] = segStarts[s] + bus->getLength();

wled00/bus_manager.cpp

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,26 @@ size_t BusHub75Matrix::getPins(uint8_t* pinArray) const {
11051105
#endif
11061106
// ***************************************************************************
11071107

1108+
BusPlaceholder::BusPlaceholder(const BusConfig &bc)
1109+
: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, bc.refreshReq)
1110+
, _colorOrder(bc.colorOrder)
1111+
, _skipAmount(bc.skipAmount)
1112+
, _frequency(bc.frequency)
1113+
, _milliAmpsPerLed(bc.milliAmpsPerLed)
1114+
, _milliAmpsMax(bc.milliAmpsMax)
1115+
, _text(bc.text)
1116+
{
1117+
memcpy(_pins, bc.pins, sizeof(_pins));
1118+
}
1119+
1120+
size_t BusPlaceholder::getPins(uint8_t* pinArray) const {
1121+
size_t nPins = Bus::getNumberOfPins(_type);
1122+
if (pinArray) {
1123+
for (size_t i = 0; i < nPins; i++) pinArray[i] = _pins[i];
1124+
}
1125+
return nPins;
1126+
}
1127+
11081128
//utility to get the approx. memory usage of a given BusConfig
11091129
size_t BusConfig::memUsage(unsigned nr) const {
11101130
if (Bus::isVirtual(type)) {
@@ -1148,7 +1168,7 @@ size_t BusManager::memUsage() {
11481168
return size + maxI2S;
11491169
}
11501170

1151-
int BusManager::add(const BusConfig &bc) {
1171+
int BusManager::add(const BusConfig &bc, bool placeholder) {
11521172
DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (p:%d v:%d)\n"), getNumBusses(), getNumVirtualBusses());
11531173
unsigned digital = 0;
11541174
unsigned analog = 0;
@@ -1158,8 +1178,12 @@ int BusManager::add(const BusConfig &bc) {
11581178
if (bus->isDigital() && !bus->is2Pin()) digital++;
11591179
if (bus->is2Pin()) twoPin++;
11601180
}
1161-
if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1;
1162-
if (Bus::isVirtual(bc.type)) {
1181+
digital += (Bus::isDigital(bc.type) && !Bus::is2Pin(bc.type));
1182+
analog += (Bus::isPWM(bc.type) ? Bus::numPWMPins(bc.type) : 0);
1183+
if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) placeholder = true; // TODO: add errorFlag here
1184+
if (placeholder) {
1185+
busses.push_back(make_unique<BusPlaceholder>(bc));
1186+
} else if (Bus::isVirtual(bc.type)) {
11631187
busses.push_back(make_unique<BusNetwork>(bc));
11641188
#ifdef WLED_ENABLE_HUB75MATRIX
11651189
} else if (Bus::isHub75(bc.type)) {
@@ -1266,7 +1290,7 @@ void BusManager::on() {
12661290
if (PinManager::getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) {
12671291
for (auto &bus : busses) {
12681292
uint8_t pins[2] = {255,255};
1269-
if (bus->isDigital() && bus->getPins(pins)) {
1293+
if (bus->isDigital() && bus->getPins(pins) && bus->isOk()) {
12701294
if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) {
12711295
BusDigital &b = static_cast<BusDigital&>(*bus);
12721296
b.begin();
@@ -1361,7 +1385,7 @@ void BusManager::initializeABL() {
13611385
_useABL = true; // at least one bus has ABL set
13621386
uint32_t ESPshare = MA_FOR_ESP / numABLbuses; // share of ESP current per ABL bus
13631387
for (auto &bus : busses) {
1364-
if (bus->isDigital()) {
1388+
if (bus->isDigital() && bus->isOk()) {
13651389
BusDigital &busd = static_cast<BusDigital&>(*bus);
13661390
uint32_t busLength = busd.getLength();
13671391
uint32_t busDemand = busLength * busd.getLEDCurrent();

wled00/bus_manager.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class Bus {
133133
virtual void setColorOrder(uint8_t co) {}
134134
virtual uint32_t getPixelColor(unsigned pix) const { return 0; }
135135
virtual size_t getPins(uint8_t* pinArray = nullptr) const { return 0; }
136-
virtual uint16_t getLength() const { return isOk() ? _len : 0; }
136+
virtual uint16_t getLength() const { return _len; }
137137
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
138138
virtual unsigned skippedLeds() const { return 0; }
139139
virtual uint16_t getFrequency() const { return 0U; }
@@ -152,6 +152,7 @@ class Bus {
152152
inline bool isPWM() const { return isPWM(_type); }
153153
inline bool isVirtual() const { return isVirtual(_type); }
154154
inline bool is16bit() const { return is16bit(_type); }
155+
virtual bool isPlaceholder() const { return false; }
155156
inline bool mustRefresh() const { return mustRefresh(_type); }
156157
inline void setReversed(bool reversed) { _reversed = reversed; }
157158
inline void setStart(uint16_t start) { _start = start; }
@@ -372,6 +373,39 @@ class BusNetwork : public Bus {
372373
#endif
373374
};
374375

376+
// Placeholder for buses that we can't construct due to resource limitations
377+
// This preserves the configuration so it can be read back to the settings pages
378+
// Function calls "mimic" the replaced bus, isPlaceholder() can be used to identify a placeholder
379+
class BusPlaceholder : public Bus {
380+
public:
381+
BusPlaceholder(const BusConfig &bc);
382+
383+
// Actual calls are stubbed out
384+
void setPixelColor(unsigned pix, uint32_t c) override {};
385+
void show() override {};
386+
387+
// Accessors
388+
uint8_t getColorOrder() const override { return _colorOrder; }
389+
size_t getPins(uint8_t* pinArray) const override;
390+
unsigned skippedLeds() const override { return _skipAmount; }
391+
uint16_t getFrequency() const override { return _frequency; }
392+
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
393+
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
394+
const String getCustomText() const override { return _text; }
395+
bool isPlaceholder() const override { return true; }
396+
397+
size_t getBusSize() const override { return sizeof(BusPlaceholder); }
398+
399+
private:
400+
uint8_t _colorOrder;
401+
uint8_t _skipAmount;
402+
uint8_t _pins[OUTPUT_MAX_PINS];
403+
uint16_t _frequency;
404+
uint8_t _milliAmpsPerLed;
405+
uint16_t _milliAmpsMax;
406+
String _text;
407+
};
408+
375409
#ifdef WLED_ENABLE_HUB75MATRIX
376410
class BusHub75Matrix : public Bus {
377411
public:
@@ -507,7 +541,7 @@ namespace BusManager {
507541

508542
//do not call this method from system context (network callback)
509543
void removeAll();
510-
int add(const BusConfig &bc);
544+
int add(const BusConfig &bc, bool placeholder);
511545

512546
void on();
513547
void off();

wled00/cfg.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,7 @@ void serializeConfig(JsonObject root) {
958958
for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
959959
DEBUG_PRINTF_P(PSTR("Cfg: Saving bus #%u\n"), s);
960960
const Bus *bus = BusManager::getBus(s);
961-
if (!bus || !bus->isOk()) break;
961+
if (!bus) break; // Memory corruption, iterator invalid
962962
DEBUG_PRINTF_P(PSTR(" (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d)\n"),
963963
(int)bus->getStart(), (int)(bus->getStart()+bus->getLength()),
964964
(int)(bus->getType() & 0x7F),

wled00/data/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3455,4 +3455,4 @@ _C.addEventListener('touchstart', lock, false);
34553455

34563456
_C.addEventListener('mouseout', move, false);
34573457
_C.addEventListener('mouseup', move, false);
3458-
_C.addEventListener('touchend', move, false);
3458+
_C.addEventListener('touchend', move, false);

wled00/wled.cpp

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ void WLED::reset()
4040

4141
void WLED::loop()
4242
{
43-
static uint32_t lastHeap = UINT32_MAX;
44-
static unsigned long heapTime = 0;
43+
static uint16_t heapTime = 0; // timestamp for heap check
44+
static uint8_t heapDanger = 0; // counter for consecutive low-heap readings
4545
#ifdef WLED_DEBUG
4646
static unsigned long lastRun = 0;
4747
unsigned long loopMillis = millis();
@@ -169,19 +169,47 @@ void WLED::loop()
169169
correctPIN = false;
170170
}
171171

172-
// reconnect WiFi to clear stale allocations if heap gets too low
173-
if (millis() - heapTime > 15000) {
174-
uint32_t heap = getFreeHeapSize();
175-
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
176-
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
177-
strip.resetSegments(); // remove all but one segments from memory
178-
if (!Update.isRunning()) forceReconnect = true;
179-
} else if (heap < MIN_HEAP_SIZE) {
180-
DEBUG_PRINTLN(F("Heap low, purging segments."));
181-
strip.purgeSegments();
172+
// free memory and reconnect WiFi to clear stale allocations if heap is too low for too long, check once every 5s
173+
if ((uint16_t)(millis() - heapTime) > 5000) {
174+
#ifdef ESP8266
175+
uint32_t heap = getFreeHeapSize(); // ESP8266 needs ~8k of free heap for UI to work properly
176+
#else
177+
#ifdef CONFIG_IDF_TARGET_ESP32C3
178+
// calling getContiguousFreeHeap() during led update causes glitches on C3
179+
// this can (probably) be removed once RMT driver for C3 is fixed
180+
unsigned t0 = millis();
181+
while (strip.isUpdating() && (millis() - t0 < 15)) delay(1); // be nice, but not too nice. Waits up to 15ms
182+
#endif
183+
uint32_t heap = getContiguousFreeHeap(); // ESP32 family needs ~10k of contiguous free heap for UI to work properly
184+
#endif
185+
if (heap < MIN_HEAP_SIZE - 1024) heapDanger+=5; // allow 1k of "wiggle room" for things that do not respect min heap limits
186+
else heapDanger = 0;
187+
switch (heapDanger) {
188+
case 15: // 15 consecutive seconds
189+
DEBUG_PRINTLN(F("Heap low, purging segments"));
190+
strip.purgeSegments();
191+
strip.setTransition(0); // disable transitions
192+
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
193+
strip.getSegments()[i].setMode(FX_MODE_STATIC); // set static mode to free effect memory
194+
}
195+
errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: segment reset
196+
break;
197+
case 30: // 30 consecutive seconds
198+
DEBUG_PRINTLN(F("Heap low, reset segments"));
199+
strip.resetSegments(); // remove all but one segments from memory
200+
errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: segment reset
201+
break;
202+
case 45: // 45 consecutive seconds
203+
DEBUG_PRINTF_P(PSTR("Heap panic! Reset strip, reset connection\n"));
204+
strip.~WS2812FX(); // deallocate strip and all its memory
205+
new(&strip) WS2812FX(); // re-create strip object, respecting current memory limits
206+
if (!Update.isRunning()) forceReconnect = true; // in case wifi is broken, make sure UI comes back, set disableForceReconnect = true to avert
207+
errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: strip reset
208+
break;
209+
default:
210+
break;
182211
}
183-
lastHeap = heap;
184-
heapTime = millis();
212+
heapTime = (uint16_t)millis();
185213
}
186214

187215
//LED settings have been saved, re-init busses

wled00/xml.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
315315
unsigned sumMa = 0;
316316
for (size_t s = 0; s < BusManager::getNumBusses(); s++) {
317317
const Bus *bus = BusManager::getBus(s);
318-
if (!bus || !bus->isOk()) break; // should not happen but for safety
318+
if (!bus) break; // should not happen but for safety
319319
int offset = s < 10 ? '0' : 'A' - 10;
320320
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
321321
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length

0 commit comments

Comments
 (0)