-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Add per-bus white-LED color temperature for accurate auto-white #5654
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 6 commits
d7e45b4
01fe430
113b3dc
cd00e56
cd6bf0e
38d19b0
5f61870
b42e4e1
5312bd0
15016d6
adefb08
7f7e78a
9965ca6
c26fc17
18bdf30
988d833
d9881b8
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 |
|---|---|---|
|
|
@@ -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]; | ||
| } | ||
| } | ||
| // 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; | ||
|
|
@@ -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; | ||
|
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. this changes the MODE_MIN behaviour. intentional? if so why only min and not MODE_MAX?
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. It was primarily intended for MODE_AUTO_ACCURATE and DUAL because they advertise themselves as an "accurate" translation of RGB into RGBW (and ACCURATE is the mode I use on my daily-driver WLED controllers). But it may be extended to include MODE_MAX if desired, although the Please let me know if I am misunderstanding what you are referring to as MODE_MIN and MODE_MAX |
||
| if (aWM == RGBW_MODE_AUTO_ACCURATE) { | ||
| r -= (w * _wR) / 255; // subtract W LED's R contribution | ||
|
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. still uses division, why not right shift?
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. Since it divided by a constant, the compiler already converts that to a shift at compile time. But it can just as easily be explicitly written as a right shift |
||
| 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); | ||
| } | ||
|
|
@@ -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(); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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; | ||
|
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. the first condition is redundant |
||
| uint16_t freq = request->arg(sp).toInt(); | ||
| if (Bus::isPWM(type)) { | ||
| switch (freq) { | ||
|
|
@@ -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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.