Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
87 changes: 53 additions & 34 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,9 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
c = color_fade(c, _bri, true); // apply brightness

if (BusManager::_useABL) {
// if using ABL, sum all color channels to estimate current and limit brightness in show()
// Always sum color channels for power monitoring (used for ABL and power consumption tracking)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. this is slow.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

// Only skip if LED current is not configured (0mA per led)
if (_milliAmpsPerLed > 0) {
uint8_t r = R(c), g = G(c), b = B(c);
if (_milliAmpsPerLed < 255) { // normal ABL
_colorSum += r + g + b + W(c);
Expand Down Expand Up @@ -360,6 +361,10 @@ size_t BusDigital::getBusSize() const {
return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); // does not include common I2S DMA buffer
}

float BusDigital::getVoltage() const {
return BusManager::getVoltage();
}

void BusDigital::setColorOrder(uint8_t colorOrder) {
// upper nibble contains W swap information
if ((colorOrder & 0x0F) > 5) return;
Expand Down Expand Up @@ -1393,46 +1398,59 @@ void BusManager::initializeABL() {
}

void BusManager::applyABL() {
if (_useABL) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert unnecessary changes, it makes it hard to review and spot issues.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted changes, added "if", but now there is code duplication and no early return use (ok, I guess)

unsigned milliAmpsSum = 0; // use temporary variable to always return a valid _gMilliAmpsUsed to UI
unsigned totalLEDs = 0;
// Always estimate current for monitoring, but only apply limiting when ABL is enabled
unsigned milliAmpsSum = 0;
unsigned totalLEDs = 0;

for (auto &bus : busses) {
if (bus->isDigital() && bus->isOk()) {
BusDigital &busd = static_cast<BusDigital&>(*bus);
busd.estimateCurrent(); // sets _milliAmpsTotal, current is estimated for all buses even if they have the limit set to 0
if (_useABL && _gMilliAmpsMax == 0)
busd.applyBriLimit(0); // apply per bus ABL limit, updates _milliAmpsTotal if limit reached
milliAmpsSum += busd.getUsedCurrent();
totalLEDs += busd.getLength(); // sum total number of LEDs for global Limit
}
}

// Apply brightness limiting only when ABL is enabled
if (_useABL && _gMilliAmpsMax > 0) {
uint8_t newBri = 255;
uint32_t globalMax = _gMilliAmpsMax > MA_FOR_ESP ? _gMilliAmpsMax - MA_FOR_ESP : 1; // subtract ESP current consumption, fully limit if too low
if (globalMax > totalLEDs) { // check if budget is larger than standby current
if (milliAmpsSum > globalMax) {
newBri = globalMax * 255 / milliAmpsSum + 1; // scale brightness down to stay in current limit, +1 to avoid 0 brightness
milliAmpsSum = globalMax; // update total used current
}
} else {
newBri = 1; // limit too low, set brightness to minimum
milliAmpsSum = totalLEDs; // estimate total used current as minimum
}

// apply brightness limit to each bus, if its 255 it will only reset _colorSum
for (auto &bus : busses) {
if (bus->isDigital() && bus->isOk()) {
BusDigital &busd = static_cast<BusDigital&>(*bus);
busd.estimateCurrent(); // sets _milliAmpsTotal, current is estimated for all buses even if they have the limit set to 0
if (_gMilliAmpsMax == 0)
busd.applyBriLimit(0); // apply per bus ABL limit, updates _milliAmpsTotal if limit reached
milliAmpsSum += busd.getUsedCurrent();
totalLEDs += busd.getLength(); // sum total number of LEDs for global Limit
if (busd.getLEDCurrent() > 0) // skip buses with LED current set to 0
busd.applyBriLimit(newBri);
}
}
// check global current limit and apply global ABL limit, total current is summed above
if (_gMilliAmpsMax > 0) {
uint8_t newBri = 255;
uint32_t globalMax = _gMilliAmpsMax > MA_FOR_ESP ? _gMilliAmpsMax - MA_FOR_ESP : 1; // subtract ESP current consumption, fully limit if too low
if (globalMax > totalLEDs) { // check if budget is larger than standby current
if (milliAmpsSum > globalMax) {
newBri = globalMax * 255 / milliAmpsSum + 1; // scale brightness down to stay in current limit, +1 to avoid 0 brightness
milliAmpsSum = globalMax; // update total used current
}
} else {
newBri = 1; // limit too low, set brightness to minimum
milliAmpsSum = totalLEDs; // estimate total used current as minimum
}

// apply brightness limit to each bus, if its 255 it will only reset _colorSum
for (auto &bus : busses) {
if (bus->isDigital() && bus->isOk()) {
BusDigital &busd = static_cast<BusDigital&>(*bus);
if (busd.getLEDCurrent() > 0) // skip buses with LED current set to 0
busd.applyBriLimit(newBri);
}
} else {
// ABL is disabled, but we still need to reset _colorSum for next frame
for (auto &bus : busses) {
if (bus->isDigital() && bus->isOk()) {
BusDigital &busd = static_cast<BusDigital&>(*bus);
if (busd.getLEDCurrent() > 0) // skip buses with LED current set to 0
busd.applyBriLimit(255); // 255 = no limiting, just reset _colorSum
}
}
_gMilliAmpsUsed = milliAmpsSum;
}
else
_gMilliAmpsUsed = 0; // reset, we have no current estimation without ABL

_gMilliAmpsUsed = milliAmpsSum; // always update current usage for monitoring
}

float BusManager::currentWatts() {
return (currentMilliamps() * _gVoltage) / 1000.0;
}

ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; }
Expand All @@ -1450,4 +1468,5 @@ uint16_t BusDigital::_milliAmpsTotal = 0;
std::vector<std::unique_ptr<Bus>> BusManager::busses;
uint16_t BusManager::_gMilliAmpsUsed = 0;
uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT;
uint8_t BusManager::_gVoltage = LED_VOLTAGE_DEFAULT;
bool BusManager::_useABL = false;
7 changes: 7 additions & 0 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class Bus {
virtual uint16_t getLEDCurrent() const { return 0; }
virtual uint16_t getUsedCurrent() const { return 0; }
virtual uint16_t getMaxCurrent() const { return 0; }
virtual float getVoltage() const { return LED_VOLTAGE_DEFAULT; }
virtual size_t getBusSize() const { return sizeof(Bus); }
virtual const String getCustomText() const { return String(); }

Expand Down Expand Up @@ -258,6 +259,7 @@ class BusDigital : public Bus {
uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; }
uint16_t getUsedCurrent() const override { return _milliAmpsTotal; }
uint16_t getMaxCurrent() const override { return _milliAmpsMax; }
float getVoltage() const override;
void setCurrentLimit(uint16_t milliAmps) { _milliAmpsLimit = milliAmps; }
void estimateCurrent(); // estimate used current from summed colors
void applyBriLimit(uint8_t newBri);
Expand Down Expand Up @@ -480,6 +482,7 @@ namespace BusManager {
//extern std::vector<Bus*> busses;
extern uint16_t _gMilliAmpsUsed;
extern uint16_t _gMilliAmpsMax;
extern uint8_t _gVoltage;
extern bool _useABL;

#ifdef ESP32_DATA_IDLE_HIGH
Expand All @@ -496,6 +499,10 @@ namespace BusManager {
//inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; }
inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL)
inline void setMilliampsMax(uint16_t max) { _gMilliAmpsMax = max;}
inline uint8_t getVoltage() { return _gVoltage; }
inline void setVoltage(uint8_t v) { _gVoltage = v; }
float currentWatts(); // calculate total power consumption in Watts
inline float ablWattsMax() { return (_gMilliAmpsMax * _gVoltage) / 1000.0; }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this used for?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

void initializeABL(); // setup automatic brightness limiter parameters, call once after buses are initialized
void applyABL(); // apply automatic brightness limiter, global or per bus

Expand Down
3 changes: 3 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint16_t total = hw_led[F("total")] | strip.getLengthTotal();
uint16_t ablMilliampsMax = hw_led[F("maxpwr")] | BusManager::ablMilliampsMax();
BusManager::setMilliampsMax(ablMilliampsMax);
uint8_t ledVoltage = hw_led[F("voltage")] | LED_VOLTAGE_DEFAULT;
BusManager::setVoltage(ledVoltage);
Bus::setGlobalAWMode(hw_led[F("rgbwm")] | AW_GLOBAL_DISABLED);
CJSON(strip.correctWB, hw_led["cct"]);
CJSON(strip.cctFromRgb, hw_led[F("cr")]);
Expand Down Expand Up @@ -921,6 +923,7 @@ void serializeConfig(JsonObject root) {
JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL
hw_led[F("maxpwr")] = BusManager::ablMilliampsMax();
hw_led[F("voltage")] = BusManager::getVoltage();
// hw_led[F("ledma")] = 0; // no longer used
hw_led["cct"] = strip.correctWB;
hw_led[F("cr")] = strip.cctFromRgb;
Expand Down
4 changes: 4 additions & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,10 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#endif
#endif

#ifndef LED_VOLTAGE_DEFAULT
#define LED_VOLTAGE_DEFAULT 5 // 5V is most common for WS281x
#endif

// PWM settings
#ifndef WLED_PWM_FREQ
#ifdef ESP8266
Expand Down
19 changes: 11 additions & 8 deletions wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,10 @@
// enable and update LED Amps
function enLA(s,n)
{
const abl = d.Sf.ABL.checked;
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
gId('LAdis'+n).style.display = s.selectedIndex==5 ? "inline" : "none"; // show/hide custom mA field
if (s.value!=="0") d.Sf["LA"+n].value = s.value; // set value from select object
d.Sf["LA"+n].min = (!isDig(t) || !abl) ? 0 : 1; // set minimum value for validation
d.Sf["LA"+n].min = (!isDig(t)) ? 0 : 1; // set minimum value for validation (required for power monitoring)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a reason the field is hidden if its not used - to not confuse unexperienced users (which there are a lot of)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made it appear only when the abl/power fields are selected

}
function setABL()
{
Expand Down Expand Up @@ -287,11 +286,11 @@
memu += getMem(t, n); // calc memory
dC += (isDig(t) && !isD2P(t));
setPinConfig(n,t);
gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings
gId("dig"+n+"ma").style.display = isDig(t) ? "inline" : "none"; // show mA/LED for digital LEDs (for power monitoring)
if (change) { // did we change LED type?
gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state (mandatory for TM1814)
if (isAna(t)) d.Sf["LC"+n].value = 1; // for sanity change analog count just to 1 LED
d.Sf["LA"+n].min = (!isDig(t) || !abl) ? 0 : 1; // set minimum value for LED mA
d.Sf["LA"+n].min = (!isDig(t)) ? 0 : 1; // set minimum value for LED mA (always required for power monitoring)
d.Sf["MA"+n].min = (!isDig(t)) ? 0 : 250; // set minimum value for PSU mA
}
gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory
Expand Down Expand Up @@ -469,18 +468,18 @@
<hr class="sml">
${i+1}:
<select name="LT${s}" onchange="updateTypeDropdowns();UI(true)"></select><br>
<div id="abl${s}">
mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
<div id="dig${s}ma" style="display:none">
mA/LED (for power monitoring): <select name="LAsel${s}" onchange="enLA(this,'${s}');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${s}" style="display: none;">max. mA/LED: <input name="LA${s}" type="number" min="1" max="255" oninput="UI()"> mA<br></div>
<div id="PSU${s}">PSU: <input name="MA${s}" type="number" class="xl" min="250" max="65000" oninput="UI()" value="250"> mA<br></div>
<div id="LAdis${s}" style="display: none;">Custom mA/LED: <input name="LA${s}" type="number" min="1" max="255" oninput="UI()"> mA<br></div>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert unnecessary change

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

</div>
<div id="PSU${s}">PSU: <input name="MA${s}" type="number" class="xl" min="250" max="65000" oninput="UI()" value="250"> mA<br></div>
<div id="co${s}" style="display:inline">Color Order:
<select name="CO${s}">
<option value="0">GRB</option>
Expand Down Expand Up @@ -695,6 +694,7 @@
});
d.getElementsByName("PR")[0].checked = l.prl | 0;
d.getElementsByName("MA")[0].value = l.maxpwr;
if (l.voltage) d.getElementsByName("LV")[0].value = l.voltage;
d.getElementsByName("ABL")[0].checked = l.maxpwr > 0;
}
if(c.hw.com) {
Expand Down Expand Up @@ -868,6 +868,9 @@ <h2>LED &amp; Hardware setup</h2>
<b><span id="psu">?</span></b><br>
<span id="psu2"><br></span>
<br>
LED Strip Voltage: <input name="LV" type="number" class="m" step="1" min="0" max="50" value="5" oninput="UI()"> V<br>
<i>Used for approximate power consumption calculations. Typical: 5V for WS281x, 12V or 24V for others</i><br>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
<br>
Enable automatic brightness limiter: <input type="checkbox" name="ABL" onchange="enABL()"><br>
<div id="abl">
<i>Automatically limits brightness to stay close to the limit.<br>
Expand Down
3 changes: 3 additions & 0 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,11 @@ void serializeInfo(JsonObject root)
JsonObject leds = root.createNestedObject(F("leds"));
leds[F("count")] = strip.getLengthTotal();
leds[F("pwr")] = BusManager::currentMilliamps();
leds[F("pwr_w")] = BusManager::currentWatts();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name it "watts" to avoid confustion

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

leds["fps"] = strip.getFps();
leds[F("maxpwr")] = BusManager::currentMilliamps()>0 ? BusManager::ablMilliampsMax() : 0;
leds[F("maxpwr_w")] = BusManager::ablWattsMax();
leds[F("voltage")] = BusManager::getVoltage();
leds[F("maxseg")] = WS2812FX::getMaxSegments();
//leds[F("actseg")] = strip.getActiveSegmentsNum();
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
Expand Down
3 changes: 3 additions & 0 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
unsigned ablMilliampsMax = request->arg(F("MA")).toInt();
BusManager::setMilliampsMax(ablMilliampsMax);

uint8_t ledVoltage = request->arg(F("LV")).toInt();
if (ledVoltage > 0 && ledVoltage <= 50) BusManager::setVoltage(ledVoltage);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why restrict to 50?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could only find 48v leds (UCS2903). So 50V seemed like an appropriate choice.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there is no technical reason, leave it to the user. there may be 120V strips one day, who knows.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed this check. In html bumped up to 255.


strip.autoSegments = request->hasArg(F("MS"));
strip.correctWB = request->hasArg(F("CCT"));
strip.cctFromRgb = request->hasArg(F("CR"));
Expand Down
1 change: 1 addition & 0 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
sumMa += bus->getMaxCurrent();
}
printSetFormValue(settingsScript,PSTR("MA"),BusManager::ablMilliampsMax() ? BusManager::ablMilliampsMax() : sumMa);
printSetFormValue(settingsScript,PSTR("LV"),BusManager::getVoltage());
printSetFormCheckbox(settingsScript,PSTR("ABL"),BusManager::ablMilliampsMax() || sumMa > 0);
printSetFormCheckbox(settingsScript,PSTR("PPL"),!BusManager::ablMilliampsMax() && sumMa > 0);

Expand Down