Skip to content

Add per-bus white-LED color temperature for accurate auto-white#5654

Open
NerdyGriffin wants to merge 12 commits into
wled:mainfrom
NerdyGriffin:claude/wled-cct-conversion-research-qmgU7
Open

Add per-bus white-LED color temperature for accurate auto-white#5654
NerdyGriffin wants to merge 12 commits into
wled:mainfrom
NerdyGriffin:claude/wled-cct-conversion-research-qmgU7

Conversation

@NerdyGriffin
Copy link
Copy Markdown

@NerdyGriffin NerdyGriffin commented May 29, 2026

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
strips often use 2700 K warm-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

  • New per-bus checkbox + Kelvin input below "Auto-calculate W channel from
    RGB" (shown for Brighter / Accurate / Dual modes). Disabled by default.
  • When enabled, the configured Kelvin is converted once (via the existing
    colorKtoRGB()) to the W LED's RGB equivalent and cached on the bus.
  • In autoWhiteCalc, instead of w = min(r,g,b) and an equal subtract, it
    picks 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).
  • Opt-in via the checkbox: when off (default, wk = 0) autoWhiteCalc takes
    a fast path that is identical to current behavior, so existing configs
    render unchanged and there's no added per-pixel cost.
  • Persisted per-bus as wk in the LED config; range 1000–10000 K.

Testing

  • Built esp32dev; flashed via OTA to a QuinLED Dig-Uno (ESP32 + RGBW).
  • Verified: UI show/hide across all AW modes; value persists across a full
    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 LEDs
    differ 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.)
  • Happy to take maintainer guidance on the per-channel-cap approach in the
    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 code
and this description were reviewed and proof-read by @NerdyGriffin, who takes
responsibility for the contribution.

Summary by CodeRabbit

  • New Features

    • Per-bus white-channel color temperature (Kelvin) control (0 = legacy neutral, or 1000–10000K)
    • New per-bus UI controls to enable/configure W Kelvin (auto-seeds 6500K when enabled)
  • Improvements

    • Kelvin values validated, saved, and restored in configuration
    • Faster neutral-path for kelvin=0 and safer per-channel capping for configured kelvins, improving W extraction accuracy and robustness

Review Change Stack

NerdyGriffin and others added 5 commits May 28, 2026 05:05
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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7c67eea-5bdd-4576-81ed-96f534573cd3

📥 Commits

Reviewing files that changed from the base of the PR and between 5312bd0 and 7f7e78a.

📒 Files selected for processing (1)
  • wled00/data/settings_leds.htm

Walkthrough

Adds per-bus white-channel color temperature (Kelvin) support: Bus stores a 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 the UI JSON, and exposed in the settings page UI/JS.

Changes

Per-Bus White Kelvin Feature

Layer / File(s) Summary
Bus class white Kelvin API & data structure
wled00/bus_manager.h
Adds colorKtoRGB prototype; Bus gains _whiteKelvin, _wR/_wG/_wB, constructor init, getWhiteKelvin() and setWhiteKelvin() declarations; BusConfig adds whiteKelvin and constructor param.
setWhiteKelvin implementation and autoWhiteCalc refactor
wled00/bus_manager.cpp
Implements Bus::setWhiteKelvin() caching Kelvin→RGB (0 = neutral); refactors Bus::autoWhiteCalc() to branch on _whiteKelvin (darkest-channel fast path when 0; per-channel capped W subtraction when non-zero); BusManager::add() applies configured Kelvin to new buses.
Configuration persistence (JSON read/write)
wled00/cfg.cpp
Reads per-bus "wk" during cfg.json deserialization and writes ins["wk"] during serialization via bus->getWhiteKelvin().
Settings form input handling
wled00/set.cpp
handleSettingsSet() parses per-bus WK, defaults to 0, clamps non-zero values outside 1000–10000K to 0, and passes validated whiteK into busConfigs.
JSON API output for UI
wled00/xml.cpp
getSettingsJS() emits per-bus WKE and WK fields based on getWhiteKelvin(), using 6500 fallback when disabled.
UI controls and configuration load
wled00/data/settings_leds.htm
Adds wkChk(n) helper, per-bus WKE checkbox and WK numeric input; UI shows/hides/enables WK based on LED type and AW mode; loadCfg() maps stored v.wk into WKE/WK fields.

Sequence Diagram

sequenceDiagram
  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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • wled/WLED#5382: Modifies white/CCT computation in wled00/bus_manager.cpp, overlapping on autoWhiteCalc changes.
  • wled/WLED#4529: Previously refactored bus creation/storage and touches BusManager::add, related to the add/apply-Kelvin change.

Suggested reviewers

  • DedeHai
  • softhack007
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Add per-bus white-LED color temperature for accurate auto-white' accurately and clearly summarizes the main change: introducing a per-bus white LED color temperature (Kelvin) setting to improve auto-white calculations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@softhack007 softhack007 added the AI Partly generated by an AI. Make sure that the contributor fully understands the code! label May 29, 2026
@NerdyGriffin
Copy link
Copy Markdown
Author

I plan to submit a PR to WLED-Docs if/when this gets reviewed. Just holding off in case there are any requested changes.

@softhack007 softhack007 requested a review from DedeHai May 29, 2026 15:30
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
wled00/bus_manager.cpp (1)

101-115: ⚡ Quick win

Add 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 value

Use 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: end

As per coding guidelines: "Mark AI-generated source code blocks with // AI: below section was generated by an AI / // AI: end comments".

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 555d0cf and 38d19b0.

📒 Files selected for processing (6)
  • wled00/bus_manager.cpp
  • wled00/bus_manager.h
  • wled00/cfg.cpp
  • wled00/data/settings_leds.htm
  • wled00/set.cpp
  • wled00/xml.cpp

Comment thread wled00/bus_manager.cpp
Comment thread wled00/data/settings_leds.htm
NerdyGriffin and others added 3 commits May 29, 2026 12:29
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>
@coderabbitai coderabbitai Bot added the effect label May 29, 2026
NerdyGriffin and others added 3 commits May 29, 2026 13:32
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Partly generated by an AI. Make sure that the contributor fully understands the code! effect enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants