Feature: Per-port ABL

This commit is contained in:
Blaz Kristan 2023-11-15 19:37:07 +01:00
parent 21de8f2284
commit 8f7f9ec367
10 changed files with 794 additions and 693 deletions

View File

@ -933,9 +933,6 @@ class WS2812FX { // 96 bytes
uint8_t _qGrouping, _qSpacing; uint8_t _qGrouping, _qSpacing;
uint16_t _qOffset; uint16_t _qOffset;
uint8_t
estimateCurrentAndLimitBri(void);
void void
setUpSegmentFromQueuedChanges(void); setUpSegmentFromQueuedChanges(void);
}; };

View File

@ -1225,104 +1225,16 @@ uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) {
return busses.getPixelColor(i); return busses.getPixelColor(i);
} }
//DISCLAIMER
//The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold.
//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin.
//Stay safe with high amperage and have a reasonable safety margin!
//I am NOT to be held liable for burned down garages!
//fine tune power estimation constants for your setup
#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA)
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
uint8_t WS2812FX::estimateCurrentAndLimitBri() {
//power limit calculation
//each LED can draw up 195075 "power units" (approx. 53mA)
//one PU is the power it takes to have 1 channel 1 step brighter per brightness step
//so A=2,R=255,G=0,B=0 would use 510 PU per LED (1mA is about 3700 PU)
bool useWackyWS2815PowerModel = false;
byte actualMilliampsPerLed = milliampsPerLed;
if (ablMilliampsMax < 150 || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
currentMilliamps = 0;
return _brightness;
}
if (milliampsPerLed == 255) {
useWackyWS2815PowerModel = true;
actualMilliampsPerLed = 12; // from testing an actual strip
}
size_t powerBudget = (ablMilliampsMax - MA_FOR_ESP); //100mA for ESP power
size_t pLen = 0; //getLengthPhysical();
size_t powerSum = 0;
for (unsigned bNum = 0; bNum < busses.getNumBusses(); bNum++) {
Bus *bus = busses.getBus(bNum);
if (!IS_DIGITAL(bus->getType())) continue; //exclude non-digital network busses
uint16_t len = bus->getLength();
pLen += len;
uint32_t busPowerSum = 0;
for (unsigned i = 0; i < len; i++) { //sum up the usage of each LED
uint32_t c = bus->getPixelColor(i); // always returns original or restored color without brightness scaling
byte r = R(c), g = G(c), b = B(c), w = W(c);
if(useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
busPowerSum += (MAX(MAX(r,g),b)) * 3;
} else {
busPowerSum += (r + g + b + w);
}
}
if (bus->hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
busPowerSum *= 3;
busPowerSum >>= 2; //same as /= 4
}
powerSum += busPowerSum;
}
if (powerBudget > pLen) { //each LED uses about 1mA in standby, exclude that from power budget
powerBudget -= pLen;
} else {
powerBudget = 0;
}
// powerSum has all the values of channels summed (max would be pLen*765 as white is excluded) so convert to milliAmps
powerSum = (powerSum * actualMilliampsPerLed) / 765;
uint8_t newBri = _brightness;
if (powerSum * _brightness / 255 > powerBudget) { //scale brightness down to stay in current limit
float scale = (float)(powerBudget * 255) / (float)(powerSum * _brightness);
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
newBri = scale8(_brightness, scaleB) + 1;
}
currentMilliamps = (powerSum * newBri) / 255;
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
currentMilliamps += pLen; //add standby power (1mA/LED) back to estimate
return newBri;
}
void WS2812FX::show(void) { void WS2812FX::show(void) {
// avoid race condition, caputre _callback value // avoid race condition, caputre _callback value
show_callback callback = _callback; show_callback callback = _callback;
if (callback) callback(); if (callback) callback();
uint8_t newBri = estimateCurrentAndLimitBri();
busses.setBrightness(newBri); // "repaints" all pixels if brightness changed
// some buses send asynchronously and this method will return before // some buses send asynchronously and this method will return before
// all of the data has been sent. // all of the data has been sent.
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
busses.show(); busses.show();
// restore bus brightness to its original value
// this is done right after show, so this is only OK if LED updates are completed before show() returns
// or async show has a separate buffer (ESP32 RMT and I2S are ok)
if (newBri < _brightness) busses.setBrightness(_brightness);
unsigned long showNow = millis(); unsigned long showNow = millis();
size_t diff = showNow - _lastShow; size_t diff = showNow - _lastShow;
size_t fpsCurr = 200; size_t fpsCurr = 200;

View File

@ -101,6 +101,8 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814)) : Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
, _skip(bc.skipAmount) //sacrificial pixels , _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder) , _colorOrder(bc.colorOrder)
, _milliAmpsPerLed(bc.milliAmpsPerLed)
, _milliAmpsMax(bc.milliAmpsMax)
, _colorOrderMap(com) , _colorOrderMap(com)
{ {
if (!IS_DIGITAL(bc.type) || !bc.count) return; if (!IS_DIGITAL(bc.type) || !bc.count) return;
@ -126,8 +128,81 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
DEBUG_PRINTF("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n", _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], _pins[1], _iType); DEBUG_PRINTF("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n", _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], _pins[1], _iType);
} }
//fine tune power estimation constants for your setup
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
#ifndef MA_FOR_ESP
#ifdef ESP8266
#define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA)
#else
#define MA_FOR_ESP 120 //how much mA does the ESP use (ESP32 about 120mA)
#endif
#endif
//DISCLAIMER
//The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold.
//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin.
//Stay safe with high amperage and have a reasonable safety margin!
//I am NOT to be held liable for burned down garages or houses!
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
uint8_t BusDigital::estimateCurrentAndLimitBri() {
bool useWackyWS2815PowerModel = false;
byte actualMilliampsPerLed = _milliAmpsPerLed;
if (_milliAmpsMax < MA_FOR_ESP || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
return _bri;
}
if (_milliAmpsPerLed == 255) {
useWackyWS2815PowerModel = true;
actualMilliampsPerLed = 12; // from testing an actual strip
}
size_t powerBudget = (_milliAmpsMax - MA_FOR_ESP); //100mA for ESP power
uint32_t busPowerSum = 0;
for (unsigned i = 0; i < getLength(); i++) { //sum up the usage of each LED
uint32_t c = getPixelColor(i); // always returns original or restored color without brightness scaling
byte r = R(c), g = G(c), b = B(c), w = W(c);
if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
busPowerSum += (max(max(r,g),b)) * 3;
} else {
busPowerSum += (r + g + b + w);
}
}
if (hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
busPowerSum *= 3;
busPowerSum >>= 2; //same as /= 4
}
if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget
powerBudget -= getLength();
} else {
powerBudget = 0;
}
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
busPowerSum = (busPowerSum * actualMilliampsPerLed) / 765;
uint8_t newBri = _bri;
if (busPowerSum * _bri / 255 > powerBudget) { //scale brightness down to stay in current limit
float scale = (float)(powerBudget * 255) / (float)(busPowerSum * _bri);
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
newBri = unsigned(_bri * scaleB) / 256 + 1;
}
return newBri;
}
void BusDigital::show() { void BusDigital::show() {
if (!_valid) return; if (!_valid) return;
uint8_t newBri = estimateCurrentAndLimitBri();
if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits
if (_data) { // use _buffering this causes ~20% FPS drop if (_data) { // use _buffering this causes ~20% FPS drop
size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type); size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type);
for (size_t i=0; i<_len; i++) { for (size_t i=0; i<_len; i++) {
@ -152,8 +227,22 @@ void BusDigital::show() {
if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black
#endif #endif
for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black
} else {
if (newBri < _bri) {
uint16_t hwLen = _len;
if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
for (unsigned i = 0; i < hwLen; i++) {
// use 0 as color order, actual order does not matter here as we just update the channel values as-is
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri);
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0); // repaint all pixels with new brightness
}
}
} }
PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important (use !_buffering this causes 20% FPS drop) PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important (use !_buffering this causes 20% FPS drop)
// restore bus brightness to its original value
// this is done right after show, so this is only OK if LED updates are completed before show() returns
// or async show has a separate buffer (ESP32 RMT and I2S are ok)
if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri);
} }
bool BusDigital::canShow() { bool BusDigital::canShow() {
@ -172,7 +261,7 @@ void BusDigital::setBrightness(uint8_t b) {
uint8_t prevBri = _bri; uint8_t prevBri = _bri;
Bus::setBrightness(b); Bus::setBrightness(b);
PolyBus::setBrightness(_busPtr, _iType, b); PolyBus::setBrightness(_busPtr, _iType, b);
/*
if (_data) return; // use _buffering this causes ~20% FPS drop if (_data) return; // use _buffering this causes ~20% FPS drop
// must update/repaint every LED in the NeoPixelBus buffer to the new brightness // must update/repaint every LED in the NeoPixelBus buffer to the new brightness
@ -185,6 +274,7 @@ void BusDigital::setBrightness(uint8_t b) {
uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), prevBri); uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), prevBri);
PolyBus::setPixelColor(_busPtr, _iType, i, c, 0); PolyBus::setPixelColor(_busPtr, _iType, i, c, 0);
} }
*/
} }
//If LEDs are skipped, it is possible to use the first as a status LED. //If LEDs are skipped, it is possible to use the first as a status LED.

View File

@ -35,8 +35,10 @@ struct BusConfig {
uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255};
uint16_t frequency; uint16_t frequency;
bool doubleBuffer; bool doubleBuffer;
uint8_t milliAmpsPerLed;
uint16_t milliAmpsMax;
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false) BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=55, uint16_t maMax=ABL_MILLIAMPS_DEFAULT)
: count(len) : count(len)
, start(pstart) , start(pstart)
, colorOrder(pcolorOrder) , colorOrder(pcolorOrder)
@ -45,6 +47,8 @@ struct BusConfig {
, autoWhite(aw) , autoWhite(aw)
, frequency(clock_kHz) , frequency(clock_kHz)
, doubleBuffer(dblBfr) , doubleBuffer(dblBfr)
, milliAmpsPerLed(maPerLed)
, milliAmpsMax(maMax)
{ {
refreshReq = (bool) GET_BIT(busType,7); refreshReq = (bool) GET_BIT(busType,7);
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
@ -132,6 +136,8 @@ class Bus {
virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t getColorOrder() { return COL_ORDER_RGB; }
virtual uint8_t skippedLeds() { return 0; } virtual uint8_t skippedLeds() { return 0; }
virtual uint16_t getFrequency() { return 0U; } virtual uint16_t getFrequency() { return 0U; }
virtual uint16_t getLEDCurrent() { return 0; }
virtual uint16_t getMaxCurrent() { return 0; }
inline void setReversed(bool reversed) { _reversed = reversed; } inline void setReversed(bool reversed) { _reversed = reversed; }
inline uint16_t getStart() { return _start; } inline uint16_t getStart() { return _start; }
inline void setStart(uint16_t start) { _start = start; } inline void setStart(uint16_t start) { _start = start; }
@ -211,6 +217,9 @@ class BusDigital : public Bus {
uint8_t getPins(uint8_t* pinArray); uint8_t getPins(uint8_t* pinArray);
uint8_t skippedLeds() { return _skip; } uint8_t skippedLeds() { return _skip; }
uint16_t getFrequency() { return _frequencykHz; } uint16_t getFrequency() { return _frequencykHz; }
uint8_t estimateCurrentAndLimitBri();
uint16_t getLEDCurrent() { return _milliAmpsPerLed; }
uint16_t getMaxCurrent() { return _milliAmpsMax; }
void reinit(); void reinit();
void cleanup(); void cleanup();
@ -220,6 +229,8 @@ class BusDigital : public Bus {
uint8_t _pins[2]; uint8_t _pins[2];
uint8_t _iType; uint8_t _iType;
uint16_t _frequencykHz; uint16_t _frequencykHz;
uint8_t _milliAmpsPerLed;
uint16_t _milliAmpsMax;
void * _busPtr; void * _busPtr;
const ColorOrderMap &_colorOrderMap; const ColorOrderMap &_colorOrderMap;
//bool _buffering; // temporary until we figure out why comparison "_data" causes severe FPS drop //bool _buffering; // temporary until we figure out why comparison "_data" causes severe FPS drop

View File

@ -89,8 +89,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject hw_led = hw["led"]; JsonObject hw_led = hw["led"];
uint8_t autoWhiteMode = RGBW_MODE_MANUAL_ONLY; uint8_t autoWhiteMode = RGBW_MODE_MANUAL_ONLY;
uint16_t total = hw_led[F("total")] | strip.getLengthTotal();
CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]);
CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); // no longer used
Bus::setGlobalAWMode(hw_led[F("rgbwm")] | 255); Bus::setGlobalAWMode(hw_led[F("rgbwm")] | 255);
CJSON(correctWB, hw_led["cct"]); CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]); CJSON(cctFromRgb, hw_led[F("cr")]);
@ -167,8 +168,15 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully) uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully)
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
uint8_t AWmode = elm[F("rgbwm")] | autoWhiteMode; uint8_t AWmode = elm[F("rgbwm")] | autoWhiteMode;
uint8_t maPerLed = elm[F("ledma")] | strip.milliampsPerLed; // replace with 55 when removing strip.milliampsPerLed
uint16_t maMax = elm[F("maxpwr")] | (strip.ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists
// To disable brightness limiter we either set output max current to 0 or single LED current to 0 (we choose output max current)
if ((ledType > TYPE_TM1814 && ledType < TYPE_WS2801) || ledType >= TYPE_NET_DDP_RGB) { // analog and virtual
maPerLed = 0;
maMax = 0;
}
if (fromFS) { if (fromFS) {
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer); BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax);
mem += BusManager::memUsage(bc); mem += BusManager::memUsage(bc);
if (useGlobalLedBuffer && start + length > maxlen) { if (useGlobalLedBuffer && start + length > maxlen) {
maxlen = start + length; maxlen = start + length;
@ -177,7 +185,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (mem + globalBufMem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip() if (mem + globalBufMem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip()
} else { } else {
if (busConfigs[s] != nullptr) delete busConfigs[s]; if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer); busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax);
busesChanged = true; busesChanged = true;
} }
s++; s++;
@ -717,9 +725,9 @@ void serializeConfig() {
JsonObject hw = doc.createNestedObject("hw"); JsonObject hw = doc.createNestedObject("hw");
JsonObject hw_led = hw.createNestedObject("led"); JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL
hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("maxpwr")] = strip.ablMilliampsMax;
hw_led[F("ledma")] = strip.milliampsPerLed; hw_led[F("ledma")] = strip.milliampsPerLed; // no longer used
hw_led["cct"] = correctWB; hw_led["cct"] = correctWB;
hw_led[F("cr")] = cctFromRgb; hw_led[F("cr")] = cctFromRgb;
hw_led[F("cb")] = strip.cctBlending; hw_led[F("cb")] = strip.cctBlending;
@ -766,6 +774,8 @@ void serializeConfig() {
ins["ref"] = bus->isOffRefreshRequired(); ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAutoWhiteMode(); ins[F("rgbwm")] = bus->getAutoWhiteMode();
ins[F("freq")] = bus->getFrequency(); ins[F("freq")] = bus->getFrequency();
ins[F("maxpwr")] = bus->getMaxCurrent();
ins[F("ledma")] = bus->getLEDCurrent();
} }
JsonArray hw_com = hw.createNestedArray(F("com")); JsonArray hw_com = hw.createNestedArray(F("com"));

View File

@ -107,32 +107,55 @@
} }
function enABL() function enABL()
{ {
var en = gId('able').checked; var en = d.Sf["ABL"].checked;
d.Sf.LA.value = (en) ? laprev:0; d.Sf["MA"].min = en ? 250 : 0;
gId('abl').style.display = (en) ? 'inline':'none'; gId('abl').style.display = (en) ? 'inline':'none';
gId('psu2').style.display = (en) ? 'inline':'none'; gId('psu2').style.display = (en) ? 'inline':'none';
if (d.Sf.LA.value > 0) setABL(); if (!en) d.Sf["PPL"].checked = false;
enPPL();
UI(); UI();
} }
function enLA() function enPPL()
{ {
var val = d.Sf.LAsel.value; const abl = d.Sf["ABL"].checked;
d.Sf.LA.value = val; const en = d.Sf["PPL"].checked;
gId('LAdis').style.display = (val == 50) ? 'inline':'none'; d.Sf["MA"].readonly = en;
UI(); gId("ppldis").style.display = en ? 'inline' : 'none';
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,n)=>{
gId("PSU"+n).style.display = en ? "inline" : "none";
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
i.min = en && !((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? 250 : 0;
if (!abl) i.value = 0;
});
}
function enLA(s,n)
{
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
gId('LAdis'+n).style.display = s.selectedIndex==5 ? "inline" : "none";
d.Sf["LA"+n].value = s.value==="0" ? 55 : s.value;
d.Sf["LA"+n].min = ((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? 0 : 1;
} }
function setABL() function setABL()
{ {
gId('able').checked = true; d.Sf["ABL"].checked = false;
d.Sf.LAsel.value = 50; // check if ABL is enabled (max mA entered per output)
switch (parseInt(d.Sf.LA.value)) { d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,n)=>{
case 0: gId('able').checked = false; enABL(); break; if (parseInt(i.value) > 0) d.Sf["ABL"].checked = true;
case 30: d.Sf.LAsel.value = 30; break; });
case 35: d.Sf.LAsel.value = 35; break; // select appropriate LED current
case 55: d.Sf.LAsel.value = 55; break; d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,n)=>{
case 255: d.Sf.LAsel.value = 255; break; sel.value = 0; // set custom
default: gId('LAdis').style.display = 'inline'; switch (parseInt(d.Sf["LA"+n].value)) {
case 0: break; // disable ABL
case 15: sel.value = 15; break;
case 30: sel.value = 30; break;
case 35: sel.value = 35; break;
case 55: sel.value = 55; break;
case 255: sel.value = 255; break;
} }
enLA(sel,n);
});
enABL();
gId('m1').innerHTML = maxM; gId('m1').innerHTML = maxM;
} }
//returns mem usage //returns mem usage
@ -161,19 +184,22 @@
function UI(change=false) function UI(change=false)
{ {
let isRGBW = false, gRGBW = false, memu = 0; let isRGBW = false, gRGBW = false, memu = 0;
let sumMA = 0, busMA = 0;
gId('ampwarning').style.display = (d.Sf.MA.value > 7200) ? 'inline':'none'; let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
const ablEN = d.Sf["ABL"].checked;
if (d.Sf.LA.value == 255) laprev = 12; const pplEN = d.Sf["PPL"].checked;
else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value;
// enable/disable LED fields // enable/disable LED fields
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{ d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{
// is the field a LED type? // is the field a LED type?
var n = s.name.substring(2); var n = s.name.substring(2);
var t = parseInt(s.value); var t = parseInt(s.value);
let isDig = ((t >= 16 && t < 32) || (t >= 50 && t < 64));
let isVir = (t >= 80 && t < 96);
let isPwm = (t >= 40 && t < 48);
gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:"; gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:";
gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : ""; gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : "";
gId("abl"+n).style.display = ((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? "none" : "inline";
//var LK = d.getElementsByName("L1"+n)[0]; // clock pin //var LK = d.getElementsByName("L1"+n)[0]; // clock pin
memu += getMem(t, n); // calc memory memu += getMem(t, n); // calc memory
@ -197,6 +223,8 @@
if (change) { if (change) {
gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state
if (t > 31 && t < 48) d.getElementsByName("LC"+n)[0].value = 1; // for sanity change analog count just to 1 LED if (t > 31 && t < 48) d.getElementsByName("LC"+n)[0].value = 1; // for sanity change analog count just to 1 LED
d.Sf["LA"+n].min = ((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? 0 : 1;
d.Sf["MA"+n].min = ((t >= 80 && t < 96) || (t >= 40 && t < 48)) ? 0 : 250;
} }
gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814 gId("rf"+n).onclick = (t == 31) ? (()=>{return false}) : (()=>{}); // prevent change for TM1814
gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 28 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 28 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h
@ -211,6 +239,9 @@
gId("dig"+n+"l").style.display = (t > 48 && t < 64) ? "inline":"none"; // bus clock speed gId("dig"+n+"l").style.display = (t > 48 && t < 64) ? "inline":"none"; // bus clock speed
gId("rev"+n).innerHTML = (t >= 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog gId("rev"+n).innerHTML = (t >= 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog
gId("psd"+n).innerHTML = (t >= 40 && t < 48) ? "Index:":"Start:"; // change analog start description gId("psd"+n).innerHTML = (t >= 40 && t < 48) ? "Index:":"Start:"; // change analog start description
if (ablEN && pplEN && !((t >= 80 && t < 96) || (t >= 40 && t < 48))) {
sumMA += parseInt(d.Sf["MA"+n].value);
}
}); });
// display global white channel overrides // display global white channel overrides
gId("wc").style.display = (gRGBW) ? 'inline':'none'; gId("wc").style.display = (gRGBW) ? 'inline':'none';
@ -220,7 +251,6 @@
} }
// check for pin conflicts // check for pin conflicts
var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields
var sLC = 0, sPC = 0, maxLC = 0;
for (i=0; i<LCs.length; i++) { for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2); // field name var nm = LCs[i].name.substring(0,2); // field name
var n = LCs[i].name.substring(2); // bus number var n = LCs[i].name.substring(2); // bus number
@ -233,19 +263,25 @@
var s = parseInt(gId("ls"+n).value); //start value var s = parseInt(gId("ls"+n).value); //start value
if (s+c > sLC) sLC = s+c; //update total count if (s+c > sLC) sLC = s+c; //update total count
if(c>maxLC)maxLC=c; //max per output if(c>maxLC)maxLC=c; //max per output
var t = parseInt(d.getElementsByName("LT"+n)[0].value); // LED type SELECT var t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
if (t<80) sPC+=c; //virtual out busses do not count towards physical LEDs if (t<80) sPC+=c; //virtual out busses do not count towards physical LEDs
if (!((t >= 80 && t < 96) || (t >= 40 && t < 48))) sDI+=c;
if (!((t >= 80 && t < 96) || (t >= 40 && t < 48))) {
let maPL = parseInt(d.Sf["LA"+n].value);
if (maPL==255) maPL = 12;
busMA += maPL*c;
}
} // increase led count } // increase led count
continue; continue;
} }
// do we have led pins for digital leds // do we have led pins for digital leds
if (nm=="L0" || nm=="L1") { if (nm=="L0" || nm=="L1") {
var lc=d.getElementsByName("LC"+n)[0]; var lc=d.Sf["LC"+n];
lc.max=maxPB; // update max led count value lc.max=maxPB; // update max led count value
} }
// ignore IP address (stored in pins for virtual busses) // ignore IP address (stored in pins for virtual busses)
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") {
var t = parseInt(d.getElementsByName("LT"+n)[0].value); // LED type SELECT var t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
if (t>=80) { if (t>=80) {
LCs[i].max = 255; LCs[i].max = 255;
LCs[i].min = 0; LCs[i].min = 0;
@ -267,7 +303,7 @@
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4"/* || n2=="RL" || n2=="BT" || n2=="IR"*/) { if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4"/* || n2=="RL" || n2=="BT" || n2=="IR"*/) {
if (n2.substring(0,1)==="L") { if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2); var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10); var t2 = parseInt(d.Sf["LT"+m].value, 10);
if (t2>=80) continue; if (t2>=80) continue;
} }
if (LCs[j].value!="" && LCs[j].value!="-1") p.push(parseInt(LCs[j].value,10)); // add current pin if (LCs[j].value!="" && LCs[j].value!="-1") p.push(parseInt(LCs[j].value,10)); // add current pin
@ -282,6 +318,14 @@
// LCs[i].min = -1; // LCs[i].min = -1;
//} //}
} }
d.Sf.querySelectorAll("#mLC input[name^=LC]").forEach((s,n)=>{
let c = parseInt(s.value,10); //get LED count
let t = parseInt(d.Sf["LT"+n].value);
if (ablEN) {
let v = Math.round(parseInt(d.Sf["MA"].value,10)*c/sDI);
if (!pplEN && !((t >= 80 && t < 96) || (t >= 40 && t < 48))) d.Sf["MA"+n].value = v;
} else d.Sf["MA"+n].value = 0;
});
// update total led count // update total led count
gId("lc").textContent = sLC; gId("lc").textContent = sLC;
gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)"; gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)";
@ -294,27 +338,20 @@
gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange'; gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output"; gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (<b>ERROR: Using over ${maxM}B!</b>)` : "") : "800 LEDs per output";
// calculate power // calculate power
var val = Math.ceil((100 + sPC * laprev)/500)/2; if (pplEN) d.Sf.MA.value = sumMA;
gId('ampwarning').style.display = (parseInt(d.Sf.MA.value,10) > 7200) ? 'inline':'none';
var val = Math.ceil((100 + busMA)/500)/2;
val = (val > 5) ? Math.ceil(val) : val; val = (val > 5) ? Math.ceil(val) : val;
var s = ""; var s = "A power supply with total of ";
var is12V = (d.Sf.LAsel.value == 30);
var isWS2815 = (d.Sf.LAsel.value == 255);
if (val < 1.02 && !is12V && !isWS2815)
{
s = "ESP 5V pin with 1A USB supply";
} else
{
s += is12V ? "12V ": isWS2815 ? "WS2815 12V " : "5V ";
s += val; s += val;
s += "A supply connected to LEDs"; s += "A is required.";
} var val2 = Math.ceil((100 + busMA)/1500)/2;
var val2 = Math.ceil((100 + sPC * laprev)/1500)/2;
val2 = (val2 > 5) ? Math.ceil(val2) : val2; val2 = (val2 > 5) ? Math.ceil(val2) : val2;
var s2 = "(for most effects, ~"; var s2 = "(for most effects, ~";
s2 += val2; s2 += val2;
s2 += "A is enough)<br>"; s2 += "A is enough)<br>";
gId('psu').innerHTML = s; gId('psu').innerHTML = s;
gId('psu2').innerHTML = isWS2815 ? "" : s2; gId('psu2').innerHTML = s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none"; gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
} }
function lastEnd(i) { function lastEnd(i) {
@ -364,6 +401,18 @@ ${i+1}:
<option value="82">Art-Net RGB (network)</option> <option value="82">Art-Net RGB (network)</option>
<option value="88">DDP RGBW (network)</option> <option value="88">DDP RGBW (network)</option>
</select><br> </select><br>
<div id="abl${i}">
mA/LED: <select name="LAsel${i}" onchange="enLA(this,${i});UI();">
<option value="55" selected>55mA (typ. 5V WS281x)</option>
<option value="35">35mA (eco WS2812)</option>
<option value="30">30mA (typ. 12V)</option>
<option value="255">12mA (WS2815)</option>
<option value="15">15mA (seed/fairy pixels)</option>
<option value="0">Custom</option>
</select><br>
<div id="LAdis${i}" style="display: none;">max. mA/LED: <input name="LA${i}" type="number" min="1" max="254" oninput="UI()"> mA<br></div>
<div id="PSU${i}">PSU: <input name="MA${i}" type="number" class="xl" min="250" max="65000" oninput="UI()"> mA<br></div>
</div>
<div id="co${i}" style="display:inline">Color Order: <div id="co${i}" style="display:inline">Color Order:
<select name="CO${i}"> <select name="CO${i}">
<option value="0">GRB</option> <option value="0">GRB</option>
@ -697,29 +746,23 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<b><span id="psu">?</span></b><br> <b><span id="psu">?</span></b><br>
<span id="psu2"><br></span> <span id="psu2"><br></span>
<br> <br>
Enable automatic brightness limiter: <input type="checkbox" name="ABen" onchange="enABL()" id="able"><br> Enable automatic brightness limiter: <input type="checkbox" name="ABL" onchange="enABL()"><br>
<div id="abl"> <div id="abl">
Maximum Current: <input name="MA" type="number" class="l" min="250" max="65000" oninput="UI()" required> mA<br> <i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if poweing LEDs directly from the ESP 5V pin!<br>
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i>
Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br>
Use per-output limiter: <input type="checkbox" name="PPL" onchange="enPPL()"><br>
<div id="ppldis" style="display:none;">
<i>Make sure you enter correct values in each LED output.<br>
If using multiple outputs with only one PSU, distribute its power proportionally amongst ouputs.</i><br>
</div>
<div id="ampwarning" class="warn" style="display: none;"> <div id="ampwarning" class="warn" style="display: none;">
&#9888; Your power supply provides high current.<br> &#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br> To improve the safety of your setup,<br>
please use thick cables,<br> please use thick cables,<br>
multiple power injection points and a fuse!<br> multiple power injection points and a fuse!<br>
</div> </div>
<i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if powering LEDs directly from the ESP 5V pin!<br>
If you are using an external power supply, enter its rating.<br>
(Current estimated usage: <span class="pow">unknown</span>)</i><br><br>
LED voltage (Max. current for a single LED):<br>
<select name="LAsel" onchange="enLA()">
<option value="55" selected>5V default (55mA)</option>
<option value="35">5V efficient (35mA)</option>
<option value="30">12V (30mA)</option>
<option value="255">WS2815 (12mA)</option>
<option value="50">Custom</option>
</select><br>
<span id="LAdis" style="display: none;">Custom max. current per LED: <input name="LA" type="number" min="0" max="255" id="la" oninput="UI()" required> mA<br></span>
<i>Keep at default if you are unsure about your type of LEDs.</i><br>
</div> </div>
<h3>Hardware setup</h3> <h3>Hardware setup</h3>
<div id="mLC">LED outputs:</div> <div id="mLC">LED outputs:</div>

File diff suppressed because it is too large Load Diff

View File

@ -94,10 +94,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} }
} }
uint8_t colorOrder, type, skip, awmode, channelSwap; uint8_t colorOrder, type, skip, awmode, channelSwap, maPerLed;
uint16_t length, start; uint16_t length, start, maMax;
uint8_t pins[5] = {255, 255, 255, 255, 255}; uint8_t pins[5] = {255, 255, 255, 255, 255};
strip.ablMilliampsMax = request->arg(F("MA")).toInt();
//strip.milliampsPerLed = request->arg(F("LA")).toInt();
autoSegments = request->hasArg(F("MS")); autoSegments = request->hasArg(F("MS"));
correctWB = request->hasArg(F("CCT")); correctWB = request->hasArg(F("CCT"));
cctFromRgb = request->hasArg(F("CR")); cctFromRgb = request->hasArg(F("CR"));
@ -120,6 +123,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //channel swap char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //channel swap
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed (DotStar & PWM) char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
char la[4] = "LA"; la[2] = 48+s; la[3] = 0; //LED mA
char ma[4] = "MA"; ma[2] = 48+s; ma[3] = 0; //max mA
if (!request->hasArg(lp)) { if (!request->hasArg(lp)) {
DEBUG_PRINT(F("No data for ")); DEBUG_PRINT(F("No data for "));
DEBUG_PRINTLN(s); DEBUG_PRINTLN(s);
@ -164,10 +169,17 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
freqHz = 0; freqHz = 0;
} }
channelSwap = (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) ? request->arg(wo).toInt() : 0; channelSwap = (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) ? request->arg(wo).toInt() : 0;
if ((type > TYPE_TM1814 && type < TYPE_WS2801) || type >= TYPE_NET_DDP_RGB) { // analog and virtual
maPerLed = 0;
maMax = 0;
} else {
maPerLed = request->arg(la).toInt();
maMax = request->arg(ma).toInt(); // if ABL is disabled this will be 0
}
// actual finalization is done in WLED::loop() (removing old busses and adding new) // actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop // this may happen even before this loop is finished so we do "doInitBusses" after the loop
if (busConfigs[s] != nullptr) delete busConfigs[s]; if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freqHz, useGlobalLedBuffer); busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freqHz, useGlobalLedBuffer, maPerLed, maMax);
busesChanged = true; busesChanged = true;
} }
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
@ -241,9 +253,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} }
touchThreshold = request->arg(F("TT")).toInt(); touchThreshold = request->arg(F("TT")).toInt();
strip.ablMilliampsMax = request->arg(F("MA")).toInt();
strip.milliampsPerLed = request->arg(F("LA")).toInt();
briS = request->arg(F("CA")).toInt(); briS = request->arg(F("CA")).toInt();
turnOnAtBoot = request->hasArg(F("BO")); turnOnAtBoot = request->hasArg(F("BO"));

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2311120 #define VERSION 2311150
//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

View File

@ -354,6 +354,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("AW"),Bus::getGlobalAWMode()); sappend('v',SET_F("AW"),Bus::getGlobalAWMode());
sappend('c',SET_F("LD"),useGlobalLedBuffer); sappend('c',SET_F("LD"),useGlobalLedBuffer);
uint16_t sumMa = 0;
for (uint8_t s=0; s < busses.getNumBusses(); s++) { for (uint8_t s=0; s < busses.getNumBusses(); s++) {
Bus* bus = busses.getBus(s); Bus* bus = busses.getBus(s);
if (bus == nullptr) continue; if (bus == nullptr) continue;
@ -368,6 +369,8 @@ void getSettingsJS(byte subPage, char* dest)
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //swap channels char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //swap channels
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed
char la[4] = "LA"; la[2] = 48+s; la[3] = 0; //LED current
char ma[4] = "MA"; ma[2] = 48+s; ma[3] = 0; //max per-port PSU current
oappend(SET_F("addLEDs(1);")); oappend(SET_F("addLEDs(1);"));
uint8_t pins[5]; uint8_t pins[5];
uint8_t nPins = bus->getPins(pins); uint8_t nPins = bus->getPins(pins);
@ -405,8 +408,13 @@ void getSettingsJS(byte subPage, char* dest)
} }
} }
sappend('v',sp,speed); sappend('v',sp,speed);
sappend('v',la,bus->getLEDCurrent());
sappend('v',ma,bus->getMaxCurrent());
sumMa += bus->getMaxCurrent();
} }
sappend('c',SET_F("PPL"),(sumMa>0 && abs(sumMa - strip.ablMilliampsMax)>2)); // approxiamte detection if per-output limiter is enabled
sappend('v',SET_F("MA"),strip.ablMilliampsMax); sappend('v',SET_F("MA"),strip.ablMilliampsMax);
/*
sappend('v',SET_F("LA"),strip.milliampsPerLed); sappend('v',SET_F("LA"),strip.milliampsPerLed);
if (strip.currentMilliamps) if (strip.currentMilliamps)
{ {
@ -415,7 +423,7 @@ void getSettingsJS(byte subPage, char* dest)
oappendi(strip.currentMilliamps); oappendi(strip.currentMilliamps);
oappend(SET_F("mA\";")); oappend(SET_F("mA\";"));
} }
*/
oappend(SET_F("resetCOM(")); oappend(SET_F("resetCOM("));
oappend(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10)); oappend(itoa(WLED_MAX_COLOR_ORDER_MAPPINGS,nS,10));
oappend(SET_F(");")); oappend(SET_F(");"));