Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1178,9 +1178,9 @@ void WS2812FX::finalizeInit() {
// create buses/outputs
unsigned mem = 0;
unsigned maxI2S = 0;
for (const auto &bus : busConfigs) {
unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer
mem += memB;
for (auto bus : busConfigs) {
bool use_placeholder = false;
unsigned busMemUsage = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer
// estimate maximum I2S memory usage (only relevant for digital non-2pin busses)
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
Expand All @@ -1200,13 +1200,14 @@ void WS2812FX::finalizeInit() {
if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize;
}
#endif
if (mem + maxI2S <= MAX_LED_MEMORY) {
BusManager::add(bus);
DEBUG_PRINTF_P(PSTR("Bus memory: %uB\n"), memB);
} else {
errorFlag = ERR_NORAM_PX; // alert UI
DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
break;
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; // 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) && BusManager::busses.back()->isPlaceholder()) digitalCount--; // remove placeholder from digital count
}
}
DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage());
Expand Down Expand Up @@ -1804,6 +1805,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;
}
Expand All @@ -1825,7 +1830,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();
Expand Down
32 changes: 27 additions & 5 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,26 @@ size_t BusHub75Matrix::getPins(uint8_t* pinArray) const {
#endif
// ***************************************************************************

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)) {
Expand Down Expand Up @@ -1164,7 +1184,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;
Expand All @@ -1174,8 +1194,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; // TODO: add errorFlag here
if (placeholder) {
busses.push_back(make_unique<BusPlaceholder>(bc));
} else if (Bus::isVirtual(bc.type)) {
busses.push_back(make_unique<BusNetwork>(bc));
#ifdef WLED_ENABLE_HUB75MATRIX
} else if (Bus::isHub75(bc.type)) {
Expand Down Expand Up @@ -1282,7 +1304,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<BusDigital&>(*bus);
b.begin();
Expand Down Expand Up @@ -1377,7 +1399,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<BusDigital&>(*bus);
uint32_t busLength = busd.getLength();
uint32_t busDemand = busLength * busd.getLEDCurrent();
Expand Down
38 changes: 36 additions & 2 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,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; }
Expand All @@ -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; }
Expand Down Expand Up @@ -372,6 +373,39 @@ 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
// Function calls "mimic" the replaced bus, isPlaceholder() can be used to identify a placeholder
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; }
bool isPlaceholder() const override { return true; }

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;
};

#ifdef WLED_ENABLE_HUB75MATRIX
class BusHub75Matrix : public Bus {
public:
Expand Down Expand Up @@ -504,7 +538,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();
Expand Down
2 changes: 1 addition & 1 deletion wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,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),
Expand Down
45 changes: 38 additions & 7 deletions wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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)<ledCount) gId(`segr${lSeg}`).classList.remove("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");
}
gId('segutil2').style.display = (segCount > 1) ? "block":"none"; // rsbtn parent

if (Array.isArray(li.maps) && li.maps.length>1) {
Expand Down Expand Up @@ -2251,7 +2274,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);
Expand Down Expand Up @@ -2378,7 +2403,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);
}
Expand Down
50 changes: 36 additions & 14 deletions wled00/wled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -169,19 +169,41 @@ 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
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;
}
lastHeap = heap;
heapTime = millis();
heapTime = (uint16_t)millis();
}

//LED settings have been saved, re-init busses
Expand Down
2 changes: 1 addition & 1 deletion wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down