Add per-bus white-LED color temperature for accurate auto-white#5654
Add per-bus white-LED color temperature for accurate auto-white#5654NerdyGriffin wants to merge 16 commits into
Conversation
The Auto-Calculate White "Accurate" mode subtracts the W channel value equally from R, G, B — which implicitly assumes the physical W LED emits RGB(255, 255, 255), i.e. the sRGB white point near D65 / ~6500 K. For 2700 K WW or 5000 K CW strips this shifts the resulting color visibly. Add a per-bus configurable white-LED color temperature (Kelvin) that feeds into autoWhiteCalc, so the W LED's actual R/G/B contribution is computed via colorKtoRGB and used to (a) cap the W channel without overflowing any RGB channel and (b) subtract the correct per-channel amount in ACCURATE mode. The feature is opt-in per bus via a UI checkbox; when off (the default, wk = 0) autoWhiteCalc behaves as before, so existing configs render identically. UI lives next to the per-bus "Auto-calculate W channel from RGB" selector and is only shown when AW mode is Brighter or Accurate. The Kelvin number input is disabled (and not submitted) until the user checks the enable box, at which point it defaults to 6500 K — matching the implicit legacy reference white point. https://claude.ai/code/session_019b31kdwp79ouA3gD5Tox9A Co-authored-by: Claude <noreply@anthropic.com>
DUAL mode (RGBW_MODE_DUAL) falls through to the per-channel-cap branch of autoWhiteCalc whenever the caller hasn't set the manual white value (w == 0) — the same path used by BRIGHTER and ACCURATE. The UI gate was only revealing the WKE checkbox and Kelvin input for modes 1 and 2, so users on DUAL had no way to configure the W-LED color temperature even though their output was affected by _wR/_wG/_wB. Include awv === 3 in the visibility condition and update the comment to reflect the actual code path in bus_manager.cpp. Co-authored-by: Claude <noreply@anthropic.com>
The per-channel-cap branch added in the prior commit ran three integer divisions per pixel even when _whiteKelvin == 0 (the default), because the cached _wR/_wG/_wB values were always read as locals — the compiler could not constant-fold them. For RGBW strips this is a measurable hot- path regression vs the original min(r,g,b) implementation, paid by every user regardless of whether they enabled the feature. Split the else-branch in two: - _whiteKelvin == 0 (feature off, default): identical math to the pre-feature WLED code (w = min RGB, equal subtraction in ACCURATE). - _whiteKelvin > 0 (feature on): the per-channel-cap path that uses the cached W-LED RGB equivalent. Documents the underflow argument (floor division composes back through the subtraction) and the _wB == 0 case near 1900 K explicitly in the comments. Behavior is unchanged for both paths. Co-authored-by: Claude <noreply@anthropic.com>
Rename the enable checkbox label to "Tune RGB to W channel color temperature" so it mirrors the adjacent "Auto-calculate W channel from RGB" selector and reads as a refinement of it. Move the Kelvin number input out of the checkbox line into its own wrapper div (dig<n>wkv) with a dedicated "W channel color temperature:" label, matching the dominant WLED pattern of a checkbox revealing a sub-options block on the following line. UI() now toggles the wrapper's visibility instead of the bare input. Co-authored-by: Claude <noreply@anthropic.com>
Very warm white LEDs can read as neutral even when fed a fairly warm RGB mask, so users need to dial the configured temperature below the previous 1900 K floor to compensate. colorKtoRGB() is well-defined down to 1000 K (blue already clamps to 0 below 1900 K, green stays positive -> 255,68,0), and the per-channel-cap path's existing zero guards already handle the resulting zero channels. Lower the bound in the form validator (set.cpp), the number input's min attribute and the re-enable seed threshold (settings_leds.htm), and update the zero-guard comment in bus_manager.cpp to note that blue is zero across the whole sub-1900 K range now, not just near 1900 K. Co-authored-by: Claude <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds per-bus white-channel color temperature (Kelvin): Bus stores whiteKelvin and cached RGB coefficients, autoWhiteCalc uses either a neutral fast path or per-channel capped subtraction, and the value is persisted, parsed from settings, emitted to UI JSON, and exposed in the settings page UI/JS. ChangesPer-Bus White Kelvin Feature
Sequence DiagramsequenceDiagram
participant BusManager
participant Bus
participant autoWhiteCalc
BusManager->>Bus: setWhiteKelvin(k)
Bus->>Bus: colorKtoRGB(k) -> cache _wR/_wG/_wB
Note over Bus: _whiteKelvin stored
autoWhiteCalc->>Bus: read _whiteKelvin & cached _wR/_wG/_wB
alt _whiteKelvin == 0
autoWhiteCalc->>autoWhiteCalc: fast path (darkest channel)
else _whiteKelvin > 0
autoWhiteCalc->>autoWhiteCalc: compute per-channel caps
autoWhiteCalc->>autoWhiteCalc: subtract scaled RGB contributions
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
I plan to submit a PR to |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
wled00/bus_manager.cpp (1)
101-115: ⚡ Quick winAdd attribution for the AI-generated blocks.
These sections are marked as AI-generated, but the required source/inspiration attribution is still missing.
As per coding guidelines "Document attribution of inspiration / knowledge / sources used in AI-generated code, e.g. link to GitHub repositories or other websites".
Also applies to: 139-158
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wled00/bus_manager.cpp` around lines 101 - 115, The AI-generated blocks (including the setWhiteKelvin function and the other block at the section covering lines 139-158) need an attribution comment added immediately above each AI-marked region: include a brief "Generated with assistance from [AI tool]" line plus links or identifiers for any external sources or repositories used to inspire the implementation (e.g., the AI model name and any GitHub/URL references), and summarize what was taken from the source (e.g., colorKtoRGB usage/logic). Update the comments around the unique symbols Bus::setWhiteKelvin and the other AI-labeled block to contain that attribution text so reviewers can trace provenance.wled00/data/settings_leds.htm (1)
824-830: 💤 Low valueUse the standard AI block markers for consistency.
The other two new AI blocks in this file (Lines 208/223 and 388/408) use
// AI: below section was generated by an AI…// AI: end. This loadCfg block uses a single inline// AI:instead, deviating from the convention.♻️ Align with the AI marker convention
- { // AI: derive WKE checkbox + WK seed from stored wk (0 = feature off) + // 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: endAs per coding guidelines: "Mark AI-generated source code blocks with
// AI: below section was generated by an AI/// AI: endcomments".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wled00/data/settings_leds.htm` around lines 824 - 830, Replace the single inline AI comment in the loadCfg block that handles WKE/WK (the block creating wkChkEl, wkEl and computing wkv) with the standard AI block markers: add a starting comment "// AI: below section was generated by an AI" immediately before the block and an ending comment "// AI: end" after it, so the block wrapping the wkChkEl/wkEl/wkv logic matches the other AI-marked sections; keep the existing code unchanged and follow the same spacing/comment style as the other AI blocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@wled00/bus_manager.cpp`:
- Around line 105-113: The setter Bus::setWhiteKelvin allows any non-zero k
through and thus can accept out-of-range kelvin values; clamp/normalize non-zero
k to the supported 1000–10000K range before storing and using it: if k==0 keep
legacy behavior, otherwise bound k to [1000,10000], assign the normalized value
to _whiteKelvin, then call colorKtoRGB(normalizedK, rgb) and update _wR/_wG/_wB
accordingly so runtime behavior matches the shared contract.
In `@wled00/data/settings_leds.htm`:
- Around line 213-222: The comment says wkChk should seed WK when re-enabling
from a blank or sub-min value but parseInt("") yields NaN so the current test
misses blanks; update function wkChk to parse wk.value with an explicit radix
(parseInt(wk.value, 10)) and change the condition to treat empty or non-numeric
values as needing seeding (e.g., if (!wk.value || isNaN(parsed) || parsed <
1000) wk.value = 6500), keeping the existing checks for the WKE checkbox
(d.Sf["WKE"+n]) and the UI() call.
---
Nitpick comments:
In `@wled00/bus_manager.cpp`:
- Around line 101-115: The AI-generated blocks (including the setWhiteKelvin
function and the other block at the section covering lines 139-158) need an
attribution comment added immediately above each AI-marked region: include a
brief "Generated with assistance from [AI tool]" line plus links or identifiers
for any external sources or repositories used to inspire the implementation
(e.g., the AI model name and any GitHub/URL references), and summarize what was
taken from the source (e.g., colorKtoRGB usage/logic). Update the comments
around the unique symbols Bus::setWhiteKelvin and the other AI-labeled block to
contain that attribution text so reviewers can trace provenance.
In `@wled00/data/settings_leds.htm`:
- Around line 824-830: Replace the single inline AI comment in the loadCfg block
that handles WKE/WK (the block creating wkChkEl, wkEl and computing wkv) with
the standard AI block markers: add a starting comment "// AI: below section was
generated by an AI" immediately before the block and an ending comment "// AI:
end" after it, so the block wrapping the wkChkEl/wkEl/wkv logic matches the
other AI-marked sections; keep the existing code unchanged and follow the same
spacing/comment style as the other AI blocks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 46aeac26-38de-4191-b2cd-81f03787a86f
📒 Files selected for processing (6)
wled00/bus_manager.cppwled00/bus_manager.hwled00/cfg.cppwled00/data/settings_leds.htmwled00/set.cppwled00/xml.cpp
wkChk() promised to reseed the Kelvin field to 6500 K "from a blank or
sub-min value", but parseInt("") is NaN and NaN < 1000 is false, so a
cleared field was never reseeded — contradicting the comment. Invert the
test to !(parsed >= 1000) so NaN (blank/non-numeric) also seeds, and add
an explicit radix per the review.
Spotted by CodeRabbit on wled#5654.
Co-authored-by: Claude <noreply@anthropic.com>
"Tune RGB to W channel color temperature" implied the setting adjusts RGB, but it primarily changes how the W value is calculated (in Brighter/Accurate/Dual) and only modifies RGB in Accurate mode — in Brighter/Dual the RGB channels are left untouched. Rename to "Correct auto-white for W channel color temperature", which is accurate across all modes and matches the sibling "Auto-calculate W channel from RGB". Co-authored-by: Claude <noreply@anthropic.com>
The block that derives the WKE checkbox / WK seed from stored wk used a single inline "// AI:" comment instead of the start/end markers the other AI-generated sections use. Wrap it with the standard "// AI: below section was generated by an AI" / "// AI: end" pair for consistency. Comment-only; no behavior change. Spotted by CodeRabbit on wled#5654. Co-authored-by: Claude <noreply@anthropic.com>
|
Looks good in general, the hot path needs speed optimization. |
A fixed W-LED color temperature only makes sense for single-white RGBW buses. Dual-white CCT buses (RGBCW, RGB+CCT) have a variable white point set via the CCT/white-balance control, and non-RGB buses have nothing to derive the correction from — so the control was previously shown (gated on hasW only, like "Auto-calculate W channel from RGB") for bus types where it is meaningless or redundant. Tighten the UI gate to hasW && hasRGB && !hasCCT so the control appears only for true RGBW types. Also guard autoWhiteCalc: route _hasCCT and !_hasRgb buses through the legacy fast path even when wk != 0, so a hand-edited config can't trigger the per-channel-cap math on a bus type it doesn't apply to. The _whiteKelvin == 0 check stays first to keep the common feature-off path branch-cheap. Co-authored-by: Claude <noreply@anthropic.com>
Thanks for the update. I see how this would overlap with general-purpose color correction, though depending on the implementation they might not achieve the same thing. I implemented this specifically for the use-case of an RGBW strip with In the meantime, I will continue daily-driving my build with this feature on my DigUno and dig2go until something like this or a compatible color correction is implemented in a future release. |
"W-channel CCT controls" was ambiguous — "CCT" overlaps with WLED's
separate dual-white white-balance system. Quote the actual checkbox
label ("Correct auto-white for W channel color temperature") so the
comment names this specific control unambiguously. Comment-only.
Co-authored-by: Claude <noreply@anthropic.com>
The backend comments described the new feature as "W-channel CCT", which is ambiguous (collides with WLED's separate dual-white white-balance CCT system) and doesn't match the actual identifiers (_whiteKelvin, wk, WK, WKE). Rename to "W channel color temperature" across the comments added in this PR, matching the UI label. Pre-existing _cct/_cctBlend/ setSegmentCCT comments (the real white-balance system) are left as-is. Comment-only. Co-authored-by: Claude <noreply@anthropic.com>
When the per-bus W channel color temperature feature is enabled, the per-channel-cap path divided each RGB channel by the runtime-variable _wR/_wG/_wB once per pixel (3 real divisions; the /255 terms use a constant divisor the compiler already strength-reduces). Integer division is multi-cycle on ESP32/ESP8266. Precompute Q15 fixed-point reciprocals of _wR/_wG/_wB once in setWhiteKelvin and replace the per-pixel divisions with a multiply + shift. The reciprocals are floor-biased so the derived w cap is never larger than the exact value, preserving underflow safety. Verified across all 65280 (channel,_wX) pairs (max error 1, no over-estimate) and 5.68M full-pipeline cases (<=1/255 output delta). S=15 keeps the worst-case product (channel=255, _wX=1) inside uint32 with ~50% margin at the same accuracy as S=16. Addresses maintainer review feedback on hot-path speed. Co-authored-by: Claude <noreply@anthropic.com>
|
Pushed the hot-path optimization. The per-pixel divisions by I also checked the generated Xtensa assembly to be sure: the per-pixel path now has zero divide instructions. The Separately — relevant to your point about where general color correction should sit in the pipeline: gamma is applied before — Claude (Anthropic's Claude Code; this work was reviewed and hardware-tested by @NerdyGriffin) |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
wled00/data/settings_leds.htm (2)
831-835:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize imported
wkvalues the same way the backend does.
loadCfg()treats any positivev.wkas enabled, buthandleSettingsSet()later rejects non-zero values outside1000..10000and stores0instead (wled00/set.cpp, Lines 234-236). That makes config-template import show states that will silently flip off on save.Suggested fix
{ const wkChkEl = d.getElementsByName("WKE"+i)[0]; const wkEl = d.getElementsByName("WK"+i)[0]; - const wkv = parseInt(v.wk) | 0; + const rawWk = parseInt(v.wk, 10) | 0; + const wkv = (rawWk >= 1000 && rawWk <= 10000) ? rawWk : 0; if (wkChkEl) wkChkEl.checked = wkv > 0; if (wkEl) wkEl.value = wkv > 0 ? wkv : 6500; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wled00/data/settings_leds.htm` around lines 831 - 835, Imported wk values must be normalized the same way the backend does: parse v.wk to an integer (fallback 0), then treat it as enabled only if it's within 1000..10000; update the WKE checkbox (wkChkEl) to checked only when wk is in that range and set the WK input (wkEl) to the wk value when enabled or to the UI default (6500) when disabled so the displayed state matches what handleSettingsSet() will accept and store.
399-410:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHonor the global auto-white override when deciding whether WK applies.
autoWhiteCalc()uses the global AW mode first (wled00/bus_manager.cpp, Lines 129-130), but this UI gate only looks atAW<n>. If the global override isNoneorMax, the Kelvin controls can still appear even though runtime will ignore them.Suggested fix
{ const awEl = d.Sf["AW"+n]; - const awv = awEl ? parseInt(awEl.value) : 0; + const awv = awEl ? parseInt(awEl.value, 10) : 0; + const gaw = parseInt(d.Sf.AW.value, 10); + const effectiveAw = gaw < 255 ? gaw : awv; const wkBox = gId("dig"+n+"wk"); // only true single-white RGBW types: a fixed W-LED color temperature is // meaningless for dual-white CCT buses (variable white point) and for // non-RGB buses (nothing to derive the correction from) - if (wkBox) wkBox.style.display = (hasW(t) && hasRGB(t) && !hasCCT(t) && (awv === 1 || awv === 2 || awv === 3)) ? "inline" : "none"; + if (wkBox) wkBox.style.display = (hasW(t) && hasRGB(t) && !hasCCT(t) && (effectiveAw === 1 || effectiveAw === 2 || effectiveAw === 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"; } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wled00/data/settings_leds.htm` around lines 399 - 410, The UI currently checks only the per-bus AW control (AW+n) when deciding to show the Kelvin controls (wkBox) but must also honor the global auto-white override used by autoWhiteCalc(); read the global AW control (e.g. d.Sf["AW"] or gId("AW") as used for the global setting), get its value, and include a guard that hides wkBox when the global mode is "None" or "Max" (match the same string/enum names used by autoWhiteCalc()), in addition to the existing per-bus checks (awEl/awv, hasW/hasRGB/hasCCT) so WK is hidden whenever the runtime will ignore per-bus Kelvin settings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@wled00/bus_manager.h`:
- Around line 235-237: The reciprocal cache fields _rwR, _rwG, and _rwB in class
Bus are added but not initialized; update the Bus constructor (the Bus::Bus
initializer list or body) to initialize _rwR, _rwG, and _rwB to 0 so they have
defined values before any call to setWhiteKelvin(), ensuring the autoWhiteCalc
hot path won't read uninitialized reciprocals.
In `@wled00/cfg.cpp`:
- Line 241: The code reads whiteK from JSON as uint16_t without validation
(uint16_t whiteK = elm[F("wk")] | 0), allowing invalid values into BusConfig;
update the ingress parsing for wk so that you accept 0 (disabled) or clamp
values into the supported range [1000,10000] before constructing BusConfig—i.e.,
parse elm[F("wk")] into a temporary integer/default 0, if value != 0 then if
value < 1000 set to 1000, if value > 10000 set to 10000, and only then assign to
whiteK (or reject non-numeric inputs by treating as 0). Ensure you apply this
change where whiteK is read/used (reference variable whiteK and the JSON key
elm[F("wk")]) so runtime state cannot contain out-of-range temps.
---
Outside diff comments:
In `@wled00/data/settings_leds.htm`:
- Around line 831-835: Imported wk values must be normalized the same way the
backend does: parse v.wk to an integer (fallback 0), then treat it as enabled
only if it's within 1000..10000; update the WKE checkbox (wkChkEl) to checked
only when wk is in that range and set the WK input (wkEl) to the wk value when
enabled or to the UI default (6500) when disabled so the displayed state matches
what handleSettingsSet() will accept and store.
- Around line 399-410: The UI currently checks only the per-bus AW control
(AW+n) when deciding to show the Kelvin controls (wkBox) but must also honor the
global auto-white override used by autoWhiteCalc(); read the global AW control
(e.g. d.Sf["AW"] or gId("AW") as used for the global setting), get its value,
and include a guard that hides wkBox when the global mode is "None" or "Max"
(match the same string/enum names used by autoWhiteCalc()), in addition to the
existing per-bus checks (awEl/awv, hasW/hasRGB/hasCCT) so WK is hidden whenever
the runtime will ignore per-bus Kelvin settings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 557a77c7-2bef-4039-89ab-3a616de00f00
📒 Files selected for processing (6)
wled00/bus_manager.cppwled00/bus_manager.hwled00/cfg.cppwled00/data/settings_leds.htmwled00/set.cppwled00/xml.cpp
| uint32_t _rwR; // Q15 reciprocal of _wR (floor((255<<15)/_wR), 0 if _wR==0) for autoWhiteCalc hot path | ||
| uint32_t _rwG; | ||
| uint32_t _rwB; |
There was a problem hiding this comment.
Initialize reciprocal cache members in Bus constructor.
_rwR/_rwG/_rwB are newly added but not initialized in the constructor, which risks undefined values being consumed before setWhiteKelvin() runs.
Proposed fix
, _whiteKelvin(0)
, _wR(255)
, _wG(255)
, _wB(255)
+ , _rwR(0)
+ , _rwG(0)
+ , _rwB(0)
{🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wled00/bus_manager.h` around lines 235 - 237, The reciprocal cache fields
_rwR, _rwG, and _rwB in class Bus are added but not initialized; update the Bus
constructor (the Bus::Bus initializer list or body) to initialize _rwR, _rwG,
and _rwB to 0 so they have defined values before any call to setWhiteKelvin(),
ensuring the autoWhiteCalc hot path won't read uninitialized reciprocals.
| 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 color temperature in K (0 = neutral/legacy) |
There was a problem hiding this comment.
Clamp wk at config ingress to the supported domain.
wk is accepted as-is here. Please enforce 0 (disabled) or [1000, 10000] before constructing BusConfig, so invalid JSON values don’t leak into runtime state.
Proposed fix
- uint16_t whiteK = elm[F("wk")] | 0; // physical W channel color temperature in K (0 = neutral/legacy)
+ uint16_t whiteK = elm[F("wk")] | 0; // physical W channel color temperature in K (0 = neutral/legacy)
+ if (whiteK != 0 && (whiteK < 1000 || whiteK > 10000)) whiteK = 0;As per coding guidelines, enforce input-validation at the first untrusted ingress point (HTTP/JSON request bodies and query parameters).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wled00/cfg.cpp` at line 241, The code reads whiteK from JSON as uint16_t
without validation (uint16_t whiteK = elm[F("wk")] | 0), allowing invalid values
into BusConfig; update the ingress parsing for wk so that you accept 0
(disabled) or clamp values into the supported range [1000,10000] before
constructing BusConfig—i.e., parse elm[F("wk")] into a temporary integer/default
0, if value != 0 then if value < 1000 set to 1000, if value > 10000 set to
10000, and only then assign to whiteK (or reject non-numeric inputs by treating
as 0). Ensure you apply this change where whiteK is read/used (reference
variable whiteK and the JSON key elm[F("wk")]) so runtime state cannot contain
out-of-range temps.
What this does
Adds an opt-in, per-bus "white LED color temperature" setting that makes the
auto-white calculation account for the actual color of a strip's W LED.
Why
WLED's "Accurate" auto-white mode subtracts the white channel value equally
from R, G and B. That implicitly assumes the physical W LED emits neutral
white — RGB(255,255,255), i.e. the sRGB white point near D65 / ~6500 K. Real "RGBW"
strips often use 3000 K warm-white or 4000-5000 K "natural-white" or 5000 K+ cool-white LEDs, so the equal
subtraction shifts the resulting color visibly. This lets the calculation use
the W LED's true per-channel contribution instead.
How it works
RGB" (shown for Brighter / Accurate / Dual modes). Disabled by default.
colorKtoRGB()) to the W LED's RGB equivalent and cached on the bus.autoWhiteCalc, instead ofw = min(r,g,b)and an equal subtract, itpicks the largest W whose per-channel RGB contribution won't overflow any
channel, then (ACCURATE mode) subtracts that correct per-channel amount.
Floor division composes back through the subtract so it can't underflow;
per-channel zero guards handle channels that are 0 (blue is 0 at/below
1900 K).
wk = 0)autoWhiteCalctakesa fast path that is identical to current behavior, so existing configs
render unchanged and there's no added per-pixel cost.
wkin the LED config; range 1000–10000 K.Testing
esp32dev; flashed via OTA to a QuinLED Dig-Uno (ESP32 + RGBW).power-cycle; Accurate-mode saturation with smooth color transitions and no
regressions; low end down to the 1000 K floor with no underflow artifacts.
Notes / limitations
colorKtoRGB()is a blackbody-curve approximation; real phosphor LEDsdiffer from a true blackbody, so the temperature is a user-tunable value
rather than a fixed per-LED-type table. (In my own testing a 3000 K-rated
strip looked when set to 2700 K or 2500 K instead.)
hot path.
AI assistance
This change was developed with AI assistance (Claude). AI-generated sections
are marked with
// AI:comments per the contribution guidelines. All codeand this description were reviewed and proof-read by @NerdyGriffin, who takes
responsibility for the contribution.
Summary by CodeRabbit
New Features
Improvements