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
46 changes: 44 additions & 2 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
cw = (w * cw) / 255;
}

// AI: below section was generated by an AI
// recompute cached W-LED RGB equivalent when the configured Kelvin changes;
// 0 means "treat the W LED as neutral white" which preserves legacy behavior
// where autoWhiteCalc subtracted the same value from R, G, B.
void Bus::setWhiteKelvin(uint16_t k) {
_whiteKelvin = k;
if (k == 0) {
_wR = _wG = _wB = 255; // legacy: treat W as neutral
} else {
byte rgb[4];
colorKtoRGB(k, rgb);
_wR = rgb[0]; _wG = rgb[1]; _wB = rgb[2];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
// AI: end

// calculates white channel and CCT values based on given settings
uint32_t Bus::autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const {
unsigned aWM = _autoWhiteMode;
Expand All @@ -112,9 +128,34 @@ uint32_t Bus::autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const {
//ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0)
} else if (aWM == RGBW_MODE_MAX) {
w = r > g ? (r > b ? r : b) : (g > b ? g : b); // brightest RGB channel
} else if (_whiteKelvin == 0) {
// Fast path: per-bus W-LED CCT feature is off. Identical to the
// pre-feature behavior — pick darkest RGB channel as W and (for
// ACCURATE) subtract it equally. Avoids three divisions per pixel
// in the default case, since most strips never enable the feature.
w = r < g ? (r < b ? r : b) : (g < b ? g : b);
if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; }
} else {
w = r < g ? (r < b ? r : b) : (g < b ? g : b); // darkest RGB channel
if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode
// AI: below section was generated by an AI
// Per-channel cap path (feature on): pick the largest w such that
// (w * _wX)/255 <= channel for every X in {R,G,B}, preventing
// underflow when subtracting the W LED's RGB contribution. Floor
// division composes back through the subtract — i.e.
// floor((r*255)/_wR) * _wR <= r*255 — so the subtraction is safe.
// _wB is 0 at/below 1900 K (and _wG could reach 0 at extreme lows),
// hence the per-channel zero guards.
unsigned wMaxR = _wR ? (r * 255U) / _wR : 255U;
unsigned wMaxG = _wG ? (g * 255U) / _wG : 255U;
unsigned wMaxB = _wB ? (b * 255U) / _wB : 255U;
unsigned wCap = wMaxR < wMaxG ? (wMaxR < wMaxB ? wMaxR : wMaxB) : (wMaxG < wMaxB ? wMaxG : wMaxB);
if (wCap > 255U) wCap = 255U;
w = wCap;
if (aWM == RGBW_MODE_AUTO_ACCURATE) {
r -= (w * _wR) / 255; // subtract W LED's R contribution
g -= (w * _wG) / 255; // subtract W LED's G contribution
b -= (w * _wB) / 255; // subtract W LED's B contribution
}
// AI: end
}
c = RGBW32(r, g, b, w);
}
Expand Down Expand Up @@ -1226,6 +1267,7 @@ int BusManager::add(const BusConfig &bc, bool placeholder) {
} else {
busses.push_back(make_unique<BusPwm>(bc));
}
if (!busses.empty()) busses.back()->setWhiteKelvin(bc.whiteKelvin);
return busses.size();
}

Expand Down
15 changes: 14 additions & 1 deletion wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ make_unique(Args&&... args)

//colors.cpp
uint16_t approximateKelvinFromRGB(uint32_t rgb);
void colorKtoRGB(uint16_t kelvin, byte* rgb);

#define GET_BIT(var,bit) (((var)>>(bit))&0x01)
#define SET_BIT(var,bit) ((var)|=(uint16_t)(0x0001<<(bit)))
Expand Down Expand Up @@ -121,6 +122,10 @@ class Bus {
, _reversed(reversed)
, _valid(false)
, _needsRefresh(refresh)
, _whiteKelvin(0)
, _wR(255)
, _wG(255)
, _wB(255)
{
_autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY;
};
Expand Down Expand Up @@ -162,6 +167,8 @@ class Bus {
inline void setStart(uint16_t start) { _start = start; }
inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; }
inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; }
inline uint16_t getWhiteKelvin() const { return _whiteKelvin; }
void setWhiteKelvin(uint16_t k);
inline size_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); }
inline uint16_t getStart() const { return _start; }
inline uint8_t getType() const { return _type; }
Expand Down Expand Up @@ -221,6 +228,10 @@ class Bus {
uint8_t _autoWhiteMode; // global Auto White Calculation override
uint16_t _start;
uint16_t _len;
uint16_t _whiteKelvin; // physical W-channel CCT in Kelvin (0 = neutral/legacy behavior)
uint8_t _wR; // cached W LED RGB equivalent (255,255,255 when _whiteKelvin==0)
uint8_t _wG;
uint8_t _wB;
//struct { //using bitfield struct adds abour 250 bytes to binary size
bool _reversed;// : 1;
bool _valid;// : 1;
Expand Down Expand Up @@ -461,6 +472,7 @@ struct BusConfig {
uint8_t skipAmount;
bool refreshReq;
uint8_t autoWhite;
uint16_t whiteKelvin; // physical W-channel CCT in Kelvin (0 = neutral/legacy behavior)
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
uint16_t frequency;
uint8_t milliAmpsPerLed;
Expand All @@ -469,13 +481,14 @@ struct BusConfig {
uint8_t iType; // internal bus type (I_*) determined during memory estimation, used for bus creation
String text;

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, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, uint8_t driver=0, String sometext = "")
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, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, uint8_t driver=0, String sometext = "", uint16_t whiteK=0)
: count(std::max(len,(uint16_t)1))
, start(pstart)
, colorOrder(pcolorOrder)
, reversed(rev)
, skipAmount(skip)
, autoWhite(aw)
, whiteKelvin(whiteK)
, frequency(clock_kHz)
, milliAmpsPerLed(maPerLed)
, milliAmpsMax(maMax)
Expand Down
4 changes: 3 additions & 1 deletion wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
bool refresh = elm["ref"] | false;
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM
uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY;
uint16_t whiteK = elm[F("wk")] | 0; // physical W-channel CCT in K (0 = neutral/legacy)
uint8_t maPerLed = elm[F("ledma")] | LED_MILLIAMPS_DEFAULT;
uint16_t maMax = elm[F("maxpwr")] | (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)
Expand All @@ -249,7 +250,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
uint8_t driverType = elm[F("drv")] | 0; // 0=RMT (default), 1=I2S note: polybus may override this if driver is not available

String host = elm[F("text")] | String();
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, driverType, host);
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, driverType, host, whiteK);
doInitBusses = true; // finalization done in beginStrip()
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
}
Expand Down Expand Up @@ -999,6 +1000,7 @@ void serializeConfig(JsonObject root) {
ins["type"] = bus->getType() & 0x7F;
ins["ref"] = bus->isOffRefreshRequired();
ins[F("rgbwm")] = bus->getAutoWhiteMode();
ins[F("wk")] = bus->getWhiteKelvin();
ins[F("freq")] = bus->getFrequency();
ins[F("maxpwr")] = bus->getMaxCurrent();
ins[F("ledma")] = bus->getLEDCurrent();
Expand Down
49 changes: 48 additions & 1 deletion wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,22 @@
});
if (ppl) d.Sf.MA.value = sumMA; // populate UI ABL value if PPL used
}
// AI: below section was generated by an AI
// Per-bus W-LED color temperature toggle. The Kelvin input lives in a
// wrapper div (dig<n>wkv) that UI() shows/hides based on the checkbox;
// the input itself is also disabled when off, so it isn't submitted
// with the form — backend then sees no WK<n> arg and stores wk=0
// (legacy fast path). Seed the field to 6500 K when re-enabling from
// a blank or sub-min value so the UI default matches the sRGB white
// point.
function wkChk(n)
{
const wke = d.Sf["WKE"+n], wk = d.Sf["WK"+n];
if (!wke || !wk) return;
if (wke.checked && !(parseInt(wk.value, 10) >= 1000)) wk.value = 6500;
UI();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// AI: end
// enable and update LED Amps
function enLA(s,n)
{
Expand Down Expand Up @@ -369,6 +385,27 @@
gId("dig"+n+"s").style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide skip 1st for virtual & analog
gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32)
gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white
// AI: below section was generated by an AI
// W-channel CCT controls are only meaningful when autoWhiteCalc
// uses the per-channel-cap path that consumes _wR/_wG/_wB —
// i.e. AW mode is Brighter (1), Accurate (2), or Dual (3, where
// manual w==0 falls through to the Brighter path). Hide the
// whole toggle otherwise. The Kelvin input lives in a child
// block that's shown only when the checkbox is on; the input
// is disabled (and so not submitted) when off, so the backend
// stores wk=0 and the legacy autoWhite path is used.
{
const awEl = d.Sf["AW"+n];
const awv = awEl ? parseInt(awEl.value) : 0;
const wkBox = gId("dig"+n+"wk");
if (wkBox) wkBox.style.display = (hasW(t) && (awv === 1 || awv === 2 || awv === 3)) ? "inline" : "none";
const wke = d.Sf["WKE"+n], wk = d.Sf["WK"+n], wkv = gId("dig"+n+"wkv");
if (wke && wk) {
wk.disabled = !wke.checked;
if (wkv) wkv.style.display = wke.checked ? "inline" : "none";
}
}
// AI: end
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
Expand Down Expand Up @@ -593,7 +630,7 @@
<div id="dig${s}r" style="display:inline"><br><span id="rev${s}">Reversed</span>: <input type="checkbox" name="CV${s}"></div>
<div id="dig${s}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${s}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${s}f" style="display:inline"><br><span id="off${s}">Off Refresh</span>: <input id="rf${s}" type="checkbox" name="RF${s}"></div>
<div id="dig${s}a" style="display:inline"><br>Auto-calculate W channel from RGB:<br><select name="AW${s}"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option><option value=4>Max</option></select>&nbsp;</div>
<div id="dig${s}a" style="display:inline"><br>Auto-calculate W channel from RGB:<br><select name="AW${s}" onchange="UI()"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option><option value=4>Max</option></select><div id="dig${s}wk" style="display:none"><br>Correct auto-white for W channel color temperature: <input type="checkbox" name="WKE${s}" onchange="wkChk('${s}')"><div id="dig${s}wkv" style="display:none"><br>W channel color temperature: <input type="number" name="WK${s}" min="1000" max="10000" step="50" class="l" value="6500" disabled> K</div></div></div>
</div>`;
f.insertAdjacentHTML("beforeend", cn);
// fill led types (credit @netmindz)
Expand Down Expand Up @@ -784,6 +821,16 @@
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
d.getElementsByName("AW"+i)[0].value = v.rgbwm;
// AI: below section was generated by an AI
// derive WKE checkbox + WK seed from stored wk (0 = feature off)
{
const wkChkEl = d.getElementsByName("WKE"+i)[0];
const wkEl = d.getElementsByName("WK"+i)[0];
const wkv = parseInt(v.wk) | 0;
if (wkChkEl) wkChkEl.checked = wkv > 0;
if (wkEl) wkEl.value = wkv > 0 ? wkv : 6500;
}
// AI: end
d.getElementsByName("WO"+i)[0].value = (v.order>>4) & 0x0F;
d.getElementsByName("SP"+i)[0].value = v.freq;
d.getElementsByName("LA"+i)[0].value = v.ledma;
Expand Down
6 changes: 5 additions & 1 deletion wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char sl[4] = "SL"; sl[2] = offset+s; sl[3] = 0; //skip first N LEDs
char rf[4] = "RF"; rf[2] = offset+s; rf[3] = 0; //refresh required
char aw[4] = "AW"; aw[2] = offset+s; aw[3] = 0; //auto white mode
char wk[4] = "WK"; wk[2] = offset+s; wk[3] = 0; //W-channel CCT (Kelvin)
char wo[4] = "WO"; wo[2] = offset+s; wo[3] = 0; //channel swap
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
Expand All @@ -230,6 +231,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
break; // no parameter
}
awmode = request->arg(aw).toInt();
uint16_t whiteK = request->hasArg(wk) ? (uint16_t)request->arg(wk).toInt() : 0;
// Reject out-of-range or sub-1000K Kelvin values; 0 means "neutral/legacy"
if (whiteK != 0 && (whiteK < 1000 || whiteK > 10000)) whiteK = 0;
uint16_t freq = request->arg(sp).toInt();
if (Bus::isPWM(type)) {
switch (freq) {
Expand Down Expand Up @@ -265,7 +269,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
text = request->arg(hs).substring(0,31);
// 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
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, driverType, text);
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, driverType, text, whiteK);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
Expand Down
4 changes: 4 additions & 0 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
char sl[4] = "SL"; sl[2] = offset+s; sl[3] = 0; //skip 1st LED
char rf[4] = "RF"; rf[2] = offset+s; rf[3] = 0; //off refresh
char aw[4] = "AW"; aw[2] = offset+s; aw[3] = 0; //auto white mode
char wke[5] = "WKE"; wke[3] = offset+s; wke[4] = 0; //W-channel CCT enabled (UI checkbox)
char wk[4] = "WK"; wk[2] = offset+s; wk[3] = 0; //W-channel CCT (Kelvin)
char wo[4] = "WO"; wo[2] = offset+s; wo[3] = 0; //swap channels
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current
Expand All @@ -392,6 +394,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,sl,bus->skippedLeds());
printSetFormCheckbox(settingsScript,rf,bus->isOffRefreshRequired());
printSetFormValue(settingsScript,aw,bus->getAutoWhiteMode());
printSetFormCheckbox(settingsScript,wke,bus->getWhiteKelvin() > 0);
printSetFormValue(settingsScript,wk,bus->getWhiteKelvin() > 0 ? bus->getWhiteKelvin() : 6500);
printSetFormValue(settingsScript,wo,bus->getColorOrder() >> 4);
unsigned speed = bus->getFrequency();
if (bus->isPWM()) {
Expand Down