-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
FEATURE: WLED Power Consumption in JSON API #5261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
a80fe6e
949e3a7
c8794ec
59d6190
7b55a15
8a0603e
3b63054
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
| // 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); | ||
|
|
@@ -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; | ||
|
|
@@ -1393,46 +1398,59 @@ void BusManager::initializeABL() { | |
| } | ||
|
|
||
| void BusManager::applyABL() { | ||
| if (_useABL) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; } | ||
|
|
@@ -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; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); } | ||
|
|
||
|
|
@@ -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); | ||
|
|
@@ -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 | ||
|
|
@@ -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; } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's this used for?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
| { | ||
|
|
@@ -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 | ||
|
|
@@ -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> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert unnecessary change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
|
@@ -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) { | ||
|
|
@@ -868,6 +868,9 @@ <h2>LED & 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> | ||
|
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> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. name it "watts" to avoid confustion
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why restrict to 50?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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")); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no. this is slow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed