Skip to content

Comments

extended CCT blending: exclusive blend, bugfix in CCT calculation (fixes color jump) #5382

Open
DedeHai wants to merge 8 commits intowled:mainfrom
DedeHai:extendedCCTblending
Open

extended CCT blending: exclusive blend, bugfix in CCT calculation (fixes color jump) #5382
DedeHai wants to merge 8 commits intowled:mainfrom
DedeHai:extendedCCTblending

Conversation

@DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Feb 19, 2026

  • CCT from RGB has to be calculated from original color to be accurate, otherwise there is a jump at the center when going from warm to cold
  • negative blend values create "exclusive" zones where only one channel is on, blending happens in the center, total is always 255 (non additive)

Tested on digital strip only, transitions and color accuracy is much improved in "auto white calc: accurate" mode

Fixes #5377

Summary by CodeRabbit

  • New Features

    • CCT blending now supports ±100% with signed behavior (positive = additive, negative = exclusive).
    • Automatic white calculation now produces explicit warm/cool outputs so multi-white LEDs render WW/CW more accurately across brightness and per-pixel processing.
  • Documentation / UI

    • CCT control relabeled to "CCT blending (±100%)", accepts negative values and shows explanatory note about blend direction.

- CCT from RGB has to be calculated from original color to be accurate
- negative blend values create "exclusive" zones where only one channel is on, blending happens in the center, total is always 255 (non additive)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

CCT blending moved from unsigned 0–100 to signed −100..+100 and now drives multi-branch WW/CW computation. Bus::autoWhiteCalc was changed to output per-pixel WW and CW via reference params; bus internals, accessors, call sites, and the LED settings UI were updated to propagate the new WW/CW and blending semantics.

Changes

Cohort / File(s) Summary
Core Bus Manager Logic
wled00/bus_manager.cpp
Replaced legacy per-channel CCT blending with signed, multi-branch math using ww_val/cw_val, explicit clamping and deferred brightness scaling. autoWhiteCalc signature changed to output ww and cw; per-pixel propagation/ABL paths updated to use returned WW/CW.
Bus Manager Header / API
wled00/bus_manager.h
Changed _cctBlend from uint8_t to int8_t; getCCTBlend()/setCCTBlend() now use signed −100..+100 semantics with rounding/clamping. autoWhiteCalc() and calculateCCT() updated to return WW/CW via uint8_t & params.
Call sites / Pixel mappings
wled00/... (setPixelColor, brightness, ABL, PixelColor constructors)
Updated callers to request WW/CW from autoWhiteCalc when hasWhite() is true, propagate WW/CW through ABL/brightness adjustments, and pass WW/CW to RGBW / RGB+CCT / PixelColor constructions; replaced direct calculateCCT usage where applicable.
UI Configuration
wled00/data/settings_leds.htm
CCT control label changed to "CCT blending (±100%)"; numeric input now allows min="-100" max="100" and includes note clarifying positive vs. negative blending semantics.
Comments & Documentation
wled00/bus_manager.*
Updated comments describing new CCT blending interpretation, behavior of negative vs positive blend, and how auto-white calculation now outputs WW/CW.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • blazoncek
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% 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 title accurately describes the main changes: extended CCT blending with exclusive blend mode and a CCT calculation bugfix to prevent color jumps.
Linked Issues check ✅ Passed The changes implement the core bugfix (using original RGB for CCT calculation to avoid color jump) and introduce exclusive blending modes, addressing the linked issue's requirements for Philips Hue-like RGB+CCT behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing extended CCT blending and fixing the CCT calculation. HTML UI updates reflect the new signed blending range, and method signature changes support the new functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
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: 1

🧹 Nitpick comments (3)
wled00/bus_manager.cpp (3)

706-707: Uninitialized dummy variables. Since _hasCCT is always false for network buses, ww/cw are never written or read. Consider zero-initializing for consistency with BusDigital::setPixelColor (line 265).

Proposed fix
-  uint8_t ww, cw; // dummy, unused
+  uint8_t ww = 0, cw = 0; // dummy, unused (network buses have no CCT)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/bus_manager.cpp` around lines 706 - 707, The dummy variables ww and cw
used as arguments to autoWhiteCalc are not initialized when _hasCCT is false
(network buses), causing undefined behavior; initialize ww and cw to 0 before
calling autoWhiteCalc (similar to BusDigital::setPixelColor) or otherwise ensure
they are assigned a defined value when passed, and keep the existing conditional
on _hasWhite so autoWhiteCalc receives valid inputs.

463-464: Initialize CCTww/CCTcw to avoid relying on implicit initialization path.

These variables are uninitialized but are read in the TYPE_ANALOG_2CH and TYPE_ANALOG_5CH switch cases. While calculateCCT will set them for those bus types (since they hasCCT()), zero-initializing matches the pattern used in BusDigital::setPixelColor (line 265) and avoids compiler warnings or accidental UB if bus type logic changes.

Proposed fix
-  uint8_t CCTww, CCTcw;
+  uint8_t CCTww = 0, CCTcw = 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/bus_manager.cpp` around lines 463 - 464, The variables CCTww and CCTcw
are used uninitialized for some bus types; initialize them to zero where they
are declared before calling autoWhiteCalc in setPixelColor: change the
declaration of uint8_t CCTww, CCTcw to explicitly zero-initialize (e.g., uint8_t
CCTww = 0, CCTcw = 0) so that TYPE_ANALOG_2CH and TYPE_ANALOG_5CH cases that
read them (and calculateCCT when hasCCT() is true) do not rely on implicit
initialization—follow the same pattern as BusDigital::setPixelColor and keep
calling autoWhiteCalc(c, CCTww, CCTcw) for non-TYPE_ANALOG_3CH.

81-93: New CCT blending logic looks correct for the expected _cctBlend range (−127 to +127).

One minor robustness concern: line 85 computes range as uint16_t. If _cctBlend were ever −128 (the minimum int8_t), then 2 * (uint8_t)(128) = 256 and 255 − 256 underflows to 65535 as uint16_t. While setCCTBlend clamps to ±127, _cctBlend is a protected static so derived classes or future changes could theoretically set it directly. Consider a static_assert or a defensive clamp.

Optional: defensive clamp on line 85
-    uint16_t range = 255 - 2 * (uint8_t)(-_cctBlend);
+    int blendAbs = _cctBlend > -128 ? -_cctBlend : 127; // guard against int8_t minimum
+    uint16_t range = (255 > 2 * blendAbs) ? 255 - 2 * blendAbs : 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/bus_manager.cpp` around lines 81 - 93, The computation of range uses
uint16_t and can underflow if _cctBlend ever equals -128; modify the CCT
blending code around _cctBlend and ww_val (and the local variable range) to
defensively ensure _cctBlend is within [-127,127] before using it (either by
clamping the value into a local int8_t/int or by changing range to a signed type
and guarding the -128 case), or add a static_assert that _cctBlend cannot be
-128 plus a runtime clamp in setCCTBlend and/or before the range calculation to
prevent the 255 - 2*(uint8_t)(-_cctBlend) underflow. Ensure you update the logic
that computes ww_val and cw_val to use the clamped/local-safe _cctBlend so
behavior remains identical for valid inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/bus_manager.h`:
- Line 204: Typo in the inline comment for getCCTBlend: change "returns -100 t
+100" to "returns -100 to +100" on the line with static inline int8_t
getCCTBlend(), leaving the rest of the explanatory text ("/+/-100% = +/-127.
+/-64 for rounding") intact; update the comment string near _cctBlend to read
"returns -100 to +100" to clarify the range.

---

Nitpick comments:
In `@wled00/bus_manager.cpp`:
- Around line 706-707: The dummy variables ww and cw used as arguments to
autoWhiteCalc are not initialized when _hasCCT is false (network buses), causing
undefined behavior; initialize ww and cw to 0 before calling autoWhiteCalc
(similar to BusDigital::setPixelColor) or otherwise ensure they are assigned a
defined value when passed, and keep the existing conditional on _hasWhite so
autoWhiteCalc receives valid inputs.
- Around line 463-464: The variables CCTww and CCTcw are used uninitialized for
some bus types; initialize them to zero where they are declared before calling
autoWhiteCalc in setPixelColor: change the declaration of uint8_t CCTww, CCTcw
to explicitly zero-initialize (e.g., uint8_t CCTww = 0, CCTcw = 0) so that
TYPE_ANALOG_2CH and TYPE_ANALOG_5CH cases that read them (and calculateCCT when
hasCCT() is true) do not rely on implicit initialization—follow the same pattern
as BusDigital::setPixelColor and keep calling autoWhiteCalc(c, CCTww, CCTcw) for
non-TYPE_ANALOG_3CH.
- Around line 81-93: The computation of range uses uint16_t and can underflow if
_cctBlend ever equals -128; modify the CCT blending code around _cctBlend and
ww_val (and the local variable range) to defensively ensure _cctBlend is within
[-127,127] before using it (either by clamping the value into a local int8_t/int
or by changing range to a signed type and guarding the -128 case), or add a
static_assert that _cctBlend cannot be -128 plus a runtime clamp in setCCTBlend
and/or before the range calculation to prevent the 255 - 2*(uint8_t)(-_cctBlend)
underflow. Ensure you update the logic that computes ww_val and cw_val to use
the clamped/local-safe _cctBlend so behavior remains identical for valid inputs.

@softhack007
Copy link
Member

softhack007 commented Feb 19, 2026

@babadam2002 please test, your feedback is welcome 😃

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 19, 2026

I found a bug: now global brightness is no longer applied to CCT. easy fix though.

@babadam2002
Copy link

I test it. When the cct bending is at -100 and if I adjust it minimally towards warm or cold, it suddenly jumps to the other white LED with max brightness. There is no nice transition between them. For example, when I turn the color to completely white, only the cold white LED lights up, which is good, but when I turn it very slightly towards warmer, the cold white LED does not fade out nicely, but suddenly turns off and the warm white LED suddenly turns on at max.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 19, 2026

that is what -100% means. no transition just jump.

@babadam2002
Copy link

I just need the cold white without jumping.

@babadam2002
Copy link

babadam2002 commented Feb 19, 2026

With your modified code and my code with "0 cct blending" I was able to do the white led treatment, with this the two white leds have a nice transition. but this is only optimized for rgb+cct. The program may not be very accurate, but you can improve it.

void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) {
unsigned cct = 0;
unsigned w = W(c);

// Source definition (normalized to 0-255 range)
if (_cct > -1) {
if (_cct >= 1900) cct = (_cct - 1900) >> 5;
else if (_cct < 256) cct = _cct;
} else {
cct = (approximateKelvinFromRGB(c) - 1900) >> 5;
}

// My CUSTOM ZONE LOGIC (No jumps, just smooth transitions)
if (cct <= 127) {
// ZONE 1: Warm to Pure White (0 - 127)
float rel_cct = cct / 127.0f;

// WW: Quadratic decay (this makes it silky and doesn't jump)
float ww_factor = (1.0f - rel_cct) * (1.0f - rel_cct);
ww = (uint8_t)(ww_factor * 255.0f);

// CW: Linear ramp up
cw = (uint8_t)(rel_cct * 255.0f);
}
else {
// ZONE 2: From Pure White to Extra Cold (128 - 255)
float extra_cold_factor = (cct - 127) / 128.0f;
ww = 0; // Warm white is dead here

// We move the CW LED back a little at the end of the blue zone
cw = (uint8_t)(255 - (extra_cold_factor * 55));
}

// Brightness scaling (based on the calculated white channel)
ww = (w * ww) / 255;
cw = (w * cw) / 255;
}

uint32_t Bus::autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const {
unsigned aWM = _autoWhiteMode;
if (_gAWM < AW_GLOBAL_DISABLED) aWM = _gAWM; 

CRGBW cIn = c; // SAVE THE ORIGINAL COLOR (This is the essence of the bugfix!) 
unsigned w = W(c); 

if (aWM != RGBW_MODE_MANUAL_ONLY) { 
unsigned r = R(c); 
unsigned g = G(c); 
unsigned b = B(c); 

if (aWM == RGBW_MODE_DUAL && w > 0) { 
// In DUAL mode, if there is white, we do not touch it 
} else if (aWM == RGBW_MODE_MAX) { 
w = r > g ? (r > b ? r : b) : (g > b ? g : b); 
} else { 
w = r < g ? (r < b ? r : b) : (g < b ? g : b); 
// ACCURATE mode: Subtract white from RGB
if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; }
}
c = RGBW32(r, g, b, w);
}

if (_hasCCT) {
// Here's the trick: we use the original RGB (cIn) for the calculation,
// but we pass the white brightness (w) to it as fresh.
cIn.w = w;
calculateCCT(cIn, ww, cw);
}
return c;
}

@softhack007
Copy link
Member

softhack007 commented Feb 19, 2026

// WW: Quadratic decay (this makes it silky and doesn't jump)

// CW: Linear ramp up

// We move the CW LED back a little at the end of the blue zone

This is a different (asymmetric) blending logic, as it does not sum up to 100% for every CCT value. I'm not against it, but its a special case that should be "opt-in" with a user setting.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 19, 2026

I just need the cold white without jumping.

it does not, it buttery smooth if you set it right.

@softhack007
Copy link
Member

softhack007 commented Feb 19, 2026

I just need the cold white without jumping.

@babadam2002 I'm not sure that you only "need the cold white". Your code example says something different - it modifies the WW, and CW. It means you'll also need the warm white - otherwise why would we do all this experiments, if you could simply buy an SK6812 "RGBW" strip with a single cold white channel 🤔.

Please forgive us simple engineers - a few extra words would really help us to understand what is necessary 😉

@babadam2002
Copy link

Sorry, my English is not very good.
I actually want Philips Hue-like color management, rgb+cct for downlights. You can see videos online, for example, of Hue led strips, how the 5 led chips work together. I want to make something very similar to this, it works almost as well with our code, but I think you can do it much better. :) Thank you if you can implement it.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 19, 2026

Thank you if you can implement it.

I already did. your math is actually incorrect or at least not at all based on color theory / black body radiation.
Try it with blendig set to -80%

Copy link
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/bus_manager.cpp`:
- Around line 475-476: The local variables cctWW and cctCW are declared but not
initialized before use; initialize them to 0 where declared and update the TODO
comment typo to "use cw and ww". Specifically, in the scope declaring uint8_t
cctWW, cctCW (used around the call to autoWhiteCalc and the switch handling
TYPE_ANALOG_1CH/3CH/4CH), change the declaration to initialize both to 0 and
correct the inline TODO from "use cw ad ww" to "use cw and ww" so intent is
explicit and compiler warnings are avoided.
- Around line 233-239: The CW value is being truncated because cctCW is declared
as uint8_t but the expression "((cctCW + 1) * _bri) & 0xFF00" produces an
upper-byte 16-bit value; fix by performing the brightness scaling into a 16-bit
temporary (mirror setPixelColor pattern) so the CW upper byte is preserved and
then combine into wwcw (e.g., keep cctWW as uint8_t, cast/compute cctCW scaled
into uint16_t before ORing into wwcw after Bus::calculateCCT), and also correct
the comment typo "brightess" → "brightness".

Copy link
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 (2)

715-716: Dummy WW/CW variables — consider initializing for safety.

ww and cw are passed by reference to autoWhiteCalc but only written when _hasCCT is true (which is false for BusNetwork). They're correctly documented as dummy/unused, but initializing to 0 would silence potential compiler warnings and prevent any future regression if _hasCCT logic changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/bus_manager.cpp` around lines 715 - 716, Initialize the dummy
variables ww and cw to zero before passing them to autoWhiteCalc to avoid
compiler warnings and future regressions: change the declaration of ww and cw
(used when _hasWhite is true and passed by reference to autoWhiteCalc) from
uninitialized uint8_t ww, cw to initialized values (e.g., uint8_t ww = 0, cw =
0) so BusNetwork cases where _hasCCT is false remain safe; locate the
declaration near the call site in bus_manager.cpp where autoWhiteCalc(c, ww, cw)
is invoked.

566-566: PWM dead-time condition should also cover negative _cctBlend values.

Now that _cctBlend is int8_t, negative values (exclusive mode) also guarantee non-overlapping WW/CW signals. For both _cctBlend == 0 (standard crossfade) and _cctBlend < 0 (exclusive), the relationship cw_val = 255 - ww_val ensures the sum never exceeds 255. Dead-time optimization applies equally to both cases but currently only == 0 triggers it.

♻️ Proposed fix
-    if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend == 0) {
+    if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend <= 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/bus_manager.cpp` at line 566, The PWM dead-time optimization currently
only activates when Bus::_cctBlend == 0, but since Bus::_cctBlend is int8_t
negative values (exclusive mode) also guarantee non-overlapping WW/CW (cw_val =
255 - ww_val); change the condition in the branch that checks _type ==
TYPE_ANALOG_2CH so it tests Bus::_cctBlend <= 0 instead of == 0, ensuring both
standard crossfade (0) and exclusive (<0) use the dead-time path; update any
adjacent comment to reflect the new <= 0 logic and verify behavior where cw_val
and ww_val are computed remains correct for negative blend.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/bus_manager.cpp`:
- Around line 233-241: The CCT channels are being scaled with the old _bri
(double-applying ABL) — in the hasCCT() block where Bus::calculateCCT(c, cctWW,
cctCW) is called and wwcw is computed, replace uses of _bri with newBri so the
computed CCT upper/lower bytes are scaled by the same final brightness as the
RGB path (matching the subsequent color_fade(c, newBri, true) and
PolyBus::setPixelColor call); update the expressions that build wwcw (which
currently multiply by _bri) to multiply by newBri instead.
- Around line 84-87: _cctBlend can be -128 (int8_t) and causes uint16_t range
underflow in the block computing range and ww_val/cw_val; fix by clamping
_cctBlend into [-127,127] at its setter (the function that assigns _cctBlend)
or, if you prefer a local guard, compute range using a signed intermediate and
ensure range is >= 0 before casting (e.g. compute int r = 255 - 2 *
(int8_t)(-_cctBlend); if (r < 0) r = 0; then use r for ww_val/cw_val), and
update references to ww_val and cw_val in this calculation accordingly.

---

Duplicate comments:
In `@wled00/bus_manager.cpp`:
- Around line 474-475: The variables cctWW and cctCW declared before the
conditional are left uninitialized and the TODO has a typo; initialize both to 0
when declared (uint8_t cctWW = 0, cctCW = 0) and then call autoWhiteCalc(c,
cctWW, cctCW) only when _type != TYPE_ANALOG_3CH as written; also update the
TODO text from "use cw ad ww" to "use cw and ww". Ensure the change is made in
the same scope where the current declaration and call to autoWhiteCalc occur.

---

Nitpick comments:
In `@wled00/bus_manager.cpp`:
- Around line 715-716: Initialize the dummy variables ww and cw to zero before
passing them to autoWhiteCalc to avoid compiler warnings and future regressions:
change the declaration of ww and cw (used when _hasWhite is true and passed by
reference to autoWhiteCalc) from uninitialized uint8_t ww, cw to initialized
values (e.g., uint8_t ww = 0, cw = 0) so BusNetwork cases where _hasCCT is false
remain safe; locate the declaration near the call site in bus_manager.cpp where
autoWhiteCalc(c, ww, cw) is invoked.
- Line 566: The PWM dead-time optimization currently only activates when
Bus::_cctBlend == 0, but since Bus::_cctBlend is int8_t negative values
(exclusive mode) also guarantee non-overlapping WW/CW (cw_val = 255 - ww_val);
change the condition in the branch that checks _type == TYPE_ANALOG_2CH so it
tests Bus::_cctBlend <= 0 instead of == 0, ensuring both standard crossfade (0)
and exclusive (<0) use the dead-time path; update any adjacent comment to
reflect the new <= 0 logic and verify behavior where cw_val and ww_val are
computed remains correct for negative blend.

Copy link
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.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@wled00/bus_manager.cpp`:
- Around line 476-477: The variables cctWW and cctCW are declared but not
initialized before being passed to autoWhiteCalc; initialize them to 0 at
declaration (uint8_t cctWW = 0, cctCW = 0) so the compiler warning is silenced
and intent is explicit—leave the conditional call to autoWhiteCalc(c, cctWW,
cctCW) unchanged and keep the TYPE_ANALOG_3CH branch logic as-is.
- Around line 235-242: Fix the typo "brightess" → "brightness" in the two inline
comments: update the comment next to the CCT brightness application where wwcw
is computed (in the hasCCT() block that calls Bus::calculateCCT and uses
newBri/wwcw) and also correct the same typo in the comment associated with
PolyBus::setPixelColor (the comment referenced around the pixel
repaint/setPixelColor usage). Keep the wording consistent (use "brightness") and
do not change code logic.
- Around line 85-89: The calculation of range uses unsigned arithmetic so when
_cctBlend == -128 it underflows and the hard-switch branch is never hit; change
range to a signed type and compute using signed negation so negative results are
preserved, then clamp into 0..255 to allow the ternary fallback to run for range
== 0. Specifically, update the declaration and computation of range (currently
"uint16_t range = 255 - 2 * (uint8_t)(-_cctBlend);") to use a signed integer
(e.g., int16_t) and compute with (int16_t)(-_cctBlend), then add explicit clamps
like "if (range < 0) range = 0; if (range > 255) range = 255;" so that the
ww_val/cw_val branch (the ternary using cct) executes for maximum exclusivity.

@softhack007
Copy link
Member

@DedeHai is the new CCT blending working already for analog leds? If yes, I can surely did out a few "classic" LEDs and resistors to help with testing.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 21, 2026

@softhack007 yes, the changes are already there for PWM, it was pretty straight forward. if you have hardware to test that it would be great. I only have digital RGBCCT.

edit: if you check, please confirm WW and CW are not flipped. hm. could actually just ask the rabbit to confirm that :)

@babadam2002
Copy link

babadam2002 commented Feb 21, 2026

Another problem is that when I change the color selector from any color to cold white, the brightness of the cold LED increases nicely, when it reaches the maximum transition, the brightness of the LED jumps a little higher. This was present before.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 21, 2026

Another problem is that when I change the color selector from any color to cold white, the brightness of the cold LED increases nicely, when it reaches the maximum transition, the brightness of the LED jumps a little higher.

I have seen this before but was not able to reproduce it reliably. can you please share on how exactly to make this happen? what led settings, what brightness, what starting and what ending color

@babadam2002
Copy link

Another problem is that when I change the color selector from any color to cold white, the brightness of the cold LED increases nicely, when it reaches the maximum transition, the brightness of the LED jumps a little higher.

I have seen this before but was not able to reproduce it reliably. can you please share on how exactly to make this happen? what led settings, what brightness, what starting and what ending color

I've seen this with the pwm led mode before. The settings are almost the same as the error above. Maybe I should tell you. When "auto calculate cct from rgb" is turned on and it's set to "None" mode, there's no ugly last jump in brightness, but in "accurate" mode, when the transition from color to white has already happened, it jumps a little brighter at the very end.

Maybe the problem could be the brightness code of the white LEDs from the RGB color (if there is one)

Copy link
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: 1

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

717-718: Initialize dummy ww/cw to 0 for consistency.

These are correctly annotated as unused, but initializing them matches the pattern in BusDigital::setPixelColor (line 273) and silences potential compiler warnings.

Suggested fix
-  uint8_t ww, cw; // dummy, unused
+  uint8_t ww = 0, cw = 0; // dummy, unused
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/bus_manager.cpp` around lines 717 - 718, The dummy variables ww and cw
in the BusManager code path where autoWhiteCalc is called should be explicitly
initialized to 0 to match the pattern used in BusDigital::setPixelColor and
avoid compiler warnings; locate the declaration "uint8_t ww, cw;" near the call
to autoWhiteCalc(c, ww, cw) and change it so ww and cw are initialized (e.g.,
uint8_t ww = 0, cw = 0) before calling autoWhiteCalc.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/bus_manager.cpp`:
- Around line 235-242: The ABL path recalculates CCT using modified/stored pixel
RGB which can produce Kelvin drift when _cct < 0 and auto-white is ACCURATE;
update the ABL branch in Bus::calculateCCT / the block guarded by hasCCT() so it
does not call approximateKelvinFromRGB on white-subtracted RGB (options: skip
recalculation when _cct < 0 and ACCURATE, or use stored wwcw if available from
setPixelColor, or add a fallback that treats near-zero RGB as preserving
previous wwcw), and correct the comment typos from "brightess" to "brightness"
near color_fade and related comments; ensure adjustments reference hasCCT(),
Bus::calculateCCT(), color_fade(), PolyBus::setPixelColor(), and the _cct flag
so reviewers can find the change.

---

Duplicate comments:
In `@wled00/bus_manager.cpp`:
- Around line 476-477: The variables cctWW and cctCW are declared but not
initialized before being passed to autoWhiteCalc; initialize them (e.g., uint8_t
cctWW = 0, cctCW = 0) or compute/assign proper values before the call, or move
their declaration/assignment inside the branch that calls autoWhiteCalc so they
are always initialized when passed to autoWhiteCalc(c, cctWW, cctCW); update the
line using TYPE_ANALOG_3CH accordingly to ensure no uninitialized use.
- Around line 85-88: The exclusive CCT blending still misbehaves for _cctBlend
== -128 because clamping range to 255 makes the math behave like 0; fix by
enforcing that _cctBlend is clamped to the range [-127, 127] at the setter
(where _cctBlend is assigned) so the computation using range and ww_val never
sees -128; update the setter that assigns _cctBlend (and any public API that
writes it) to coerce values outside that range into [-127,127] and add a brief
comment explaining why -128 is invalid for the exclusive blend path.

---

Nitpick comments:
In `@wled00/bus_manager.cpp`:
- Around line 717-718: The dummy variables ww and cw in the BusManager code path
where autoWhiteCalc is called should be explicitly initialized to 0 to match the
pattern used in BusDigital::setPixelColor and avoid compiler warnings; locate
the declaration "uint8_t ww, cw;" near the call to autoWhiteCalc(c, ww, cw) and
change it so ww and cw are initialized (e.g., uint8_t ww = 0, cw = 0) before
calling autoWhiteCalc.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 21, 2026

The settings are almost the same as the error above.

please we need EXACT settings, there are thousands of possibilities.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 21, 2026

@coderabbitai please confirm cw and ww channels in all led types are unchanged and there are no unwanted side effects with this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the PR diff to analyze the changes
gh pr diff 5382 --repo wled/WLED

Length of output: 12939


@DedeHai, I've thoroughly analyzed the PR diff and can confirm:

✅ WW and CW channels are correctly maintained across all LED types

Channel ordering verification:

Digital buses (RGBCCT/WWA):

  • wwcw packing format unchanged: (CW << 8) | WW (CW in upper byte, WW in lower byte)
  • TYPE_WS2812_WWA mapping unchanged: R=WW, G=CW, B=0
  • Comment in code confirms: "ww,cw, 0, w"

PWM/Analog buses:

  • TYPE_ANALOG_2CH: _data[0]=WW, _data[1]=CW
  • TYPE_ANALOG_5CH: _data[3]=WW, _data[4]=CW

All call sites:

  • calculateCCT(c, ww, cw) - always WW first, CW second
  • autoWhiteCalc(c, cctWW, cctCW) - consistent parameter order

✅ Core bugfix properly implemented:

The PR correctly addresses the known issue (from learning ce8d9c5b) where approximateKelvinFromRGB() was receiving modified RGB values:

CRGBW cIn = c; // save original color for CCT calculation
// ... white calculation modifies c ...
if (_hasCCT) {
    cIn.w = w; // use original RGB with updated white brightness
    calculateCCT(cIn, ww, cw);
}

This ensures CCT is calculated from the original RGB before autoWhiteCalc() subtracts the white component in accurate mode.

Intentional side effects (improvements):

  1. Brightness now applied separately to WW/CW after calculation rather than before - this is more accurate as noted in the code comment
  2. applyBriLimit calculates CCT before fade with explicit comment "// calculate CCT before fade (more accurate)"
  3. deadTime condition extended from _cctBlend == 0 to _cctBlend <= 0 - correct for exclusive blending mode
  4. BusOnOff simplified - no longer needs autoWhiteCalc, just checks if any color channel is on

No unwanted side effects or channel flipping detected. All changes maintain backward compatibility while fixing the CCT calculation bug and adding the new exclusive blending feature. 🐰


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-02-19T10:36:46.916Z
Learning: When "Calculate CCT from RGB" mode (_cct < 0) is combined with "Accurate white" mode (RGBW_MODE_AUTO_ACCURATE), there is a bug in WLED's bus_manager.cpp where approximateKelvinFromRGB() receives RGB values that have already been modified (r -= w, g -= w, b -= w) by autoWhiteCalc(), causing incorrect Kelvin temperature estimation. The original color is not preserved, and calculateCCT() is called after autoWhiteCalc() modifies the RGB values. This affects BusDigital::setPixelColor() execution order (lines 245, 275).

Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/bus_manager.cpp:549-556
Timestamp: 2026-02-11T16:20:02.872Z
Learning: ESP-IDF V5 requires a complete rewrite of the LEDC (PWM/analog LED) subsystem in WLED. The framework now manages LEDC channels internally (unlike V4 where WLED managed them directly), and dithering functionality may not work as it did before. Direct LEDC struct access (e.g., `LEDC.channel_group[gr].channel[ch].duty.duty`) used in the current implementation will need to be replaced with proper HAL API calls. This affects `wled00/bus_manager.cpp` BusPwm class and related pin manager LEDC allocation code.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-13T21:23:35.514Z
Learning: In WLED, the global `paletteBlend` variable (wled.h:603) and the `WS2812FX::paletteBlend` member (FX.h:940) are duplicates without synchronization code. The global is loaded/saved in cfg.cpp and set via UI in set.cpp, but never copied to the strip member. This is the only such case in the codebase; other settings are either strip-only members (autoSegments, correctWB, cctFromRgb, isMatrix) or global-only (gammaCorrectCol/Bri/Val, blendingStyle).

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-15T20:25:25.815Z
Learning: The CSS palette preview in wled00/data/index.js genPalPrevCss() function uses raw RGB values in CSS linear-gradient() without applying WLED's gamma correction, while actual LED output goes through NeoGammaWLEDMethod gamma correction. This causes inherent discrepancies between the web UI palette preview and actual LED colors, especially noticeable with different gamma settings.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:149-151
Timestamp: 2026-01-24T18:51:09.740Z
Learning: In WLED's Spinning Wheel effect (usermods/user_fx/user_fx.cpp), the color scale calculation (SEGENV.aux1) must match the position modulo range. When position uses `pos = (pos_fixed >> 16) % SEGLEN`, the color scale must be `aux1 = (255 << 8) / SEGLEN` to distribute the palette evenly across the full position range. Using vstripLen for color scaling while position uses SEGLEN causes palette colors to cycle too quickly and appear incomplete.

Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/wled_eeprom.cpp:0-0
Timestamp: 2025-09-01T10:26:17.959Z
Learning: In WLED PR `#4876`, the DMXStartLED EEPROM backward compatibility issue was partially addressed by keeping it at address 2550 and reading it as a 16-bit value, with DMXChannelsValue array moved to addresses 2552-2566. This maintains compatibility with pre-0.11 EEPROM layouts for DMXStartLED, though legacy "Set to 255" (code 6) configurations may still need migration logic.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.

Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.

@babadam2002
Copy link

babadam2002 commented Feb 22, 2026

@coderabbitai

Messenger_creation_D31A794C-E0F8-4D39-A268-5886E13FBA6D.mp4

I have attached a video. In which you can see that I set the lamp to all points of the original Philips HUE rgb+cct led strip + color wheel. In the video you can see the cold white led on the left, the rgb led in the middle, and the warm white led on the right. You can see how they work together. My request is, how can you reproduce this in wled "accurate" mode?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Tip

For best results, initiate chat on the files or code changes.

@babadam2002, I appreciate you sharing the video showing the Philips Hue behavior! Unfortunately, I'm unable to view video attachments directly. However, based on the discussion context and what I know about Philips Hue's RGBWW system allowing complete control over the warmth and brightness of white light, I can provide guidance on achieving similar behavior in WLED.

Understanding Philips Hue RGB+CCT Behavior

From your previous comments and the web research, Philips Hue RGB+CCT strips use:

  • RGB LEDs for saturated colors
  • Warm White (WW) and Cold White (CW) LEDs that blend smoothly
  • Non-additive blending where WW and CW transition smoothly without both being at maximum simultaneously

Reproducing This in WLED with This PR

Based on the discussion in this PR, here's how to configure WLED to achieve Hue-like behavior:

Settings to Try:

  1. Auto White Mode: Set to "Accurate" (RGBW_MODE_AUTO_ACCURATE)

    • This ensures white is properly extracted from RGB colors
  2. Calculate CCT from RGB: Enable this option

    • This allows WLED to automatically determine the color temperature based on your color selection
  3. CCT Blending: Try values between -60% to -80%

    • As @DedeHai mentioned, the negative values create "exclusive" zones
    • Start with -80% as suggested by DedeHai
    • This creates smooth transitions between WW and CW similar to Hue

What the PR Fixes:

This PR addresses two key issues that improve Hue-like behavior:

  1. No more color jump: CCT is now calculated from the original RGB color (before white extraction)
  2. Exclusive blending: Negative blend values create zones where only one white channel is active, with smooth transitions

Could You Help Me Understand:

Since I cannot view the video, could you describe:

  1. When moving the color selector to pure white - what happens to CW, RGB, and WW LEDs?
  2. When adjusting temperature from cold to warm - how do the CW and WW LEDs transition?
  3. With saturated colors (e.g., pure red) - which LEDs are active?

This will help me provide more specific guidance on the exact settings to use!

Testing Recommendation:

Try these steps in WLED with this PR:

  1. Set Auto White Mode to "Accurate"
  2. Enable "Calculate CCT from RGB"
  3. Set CCT Blending to -80%
  4. Move your color selector around the color wheel and observe the behavior
  5. Adjust the CCT blending value between -60% and -100% to fine-tune the transition smoothness

The behavior should now be much closer to Philips Hue, with smooth transitions and no abrupt jumps at the center. Let me know how this works for you! 🐰


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-02-19T10:36:46.916Z
Learning: When "Calculate CCT from RGB" mode (_cct < 0) is combined with "Accurate white" mode (RGBW_MODE_AUTO_ACCURATE), there is a bug in WLED's bus_manager.cpp where approximateKelvinFromRGB() receives RGB values that have already been modified (r -= w, g -= w, b -= w) by autoWhiteCalc(), causing incorrect Kelvin temperature estimation. The original color is not preserved, and calculateCCT() is called after autoWhiteCalc() modifies the RGB values. This affects BusDigital::setPixelColor() execution order (lines 245, 275).

Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-15T20:25:25.815Z
Learning: The CSS palette preview in wled00/data/index.js genPalPrevCss() function uses raw RGB values in CSS linear-gradient() without applying WLED's gamma correction, while actual LED output goes through NeoGammaWLEDMethod gamma correction. This causes inherent discrepancies between the web UI palette preview and actual LED colors, especially noticeable with different gamma settings.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:149-151
Timestamp: 2026-01-24T18:51:09.740Z
Learning: In WLED's Spinning Wheel effect (usermods/user_fx/user_fx.cpp), the color scale calculation (SEGENV.aux1) must match the position modulo range. When position uses `pos = (pos_fixed >> 16) % SEGLEN`, the color scale must be `aux1 = (255 << 8) / SEGLEN` to distribute the palette evenly across the full position range. Using vstripLen for color scaling while position uses SEGLEN causes palette colors to cycle too quickly and appear incomplete.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-13T21:23:35.514Z
Learning: In WLED, the global `paletteBlend` variable (wled.h:603) and the `WS2812FX::paletteBlend` member (FX.h:940) are duplicates without synchronization code. The global is loaded/saved in cfg.cpp and set via UI in set.cpp, but never copied to the strip member. This is the only such case in the codebase; other settings are either strip-only members (autoSegments, correctWB, cctFromRgb, isMatrix) or global-only (gammaCorrectCol/Bri/Val, blendingStyle).

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/bus_manager.cpp:549-556
Timestamp: 2026-02-11T16:20:02.872Z
Learning: ESP-IDF V5 requires a complete rewrite of the LEDC (PWM/analog LED) subsystem in WLED. The framework now manages LEDC channels internally (unlike V4 where WLED managed them directly), and dithering functionality may not work as it did before. Direct LEDC struct access (e.g., `LEDC.channel_group[gr].channel[ch].duty.duty`) used in the current implementation will need to be replaced with proper HAL API calls. This affects `wled00/bus_manager.cpp` BusPwm class and related pin manager LEDC allocation code.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.

Learnt from: freakintoddles2
Repo: wled/WLED PR: 4904
File: wled00/FX.cpp:3915-3930
Timestamp: 2025-09-02T19:42:57.475Z
Learning: WLED wled00/FX.cpp::mode_percent — For time-based options (% of Minute/Hour/Day), behavior is clock-like: at rollover (elapsed==0) intentionally show SEGLEN lit for one tick to represent completion, then move to 1 LED at the next second. This is by design (not a progress bar) and should not be “fixed”.

Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🌐 Web query:

Philips Hue RGB+CCT LED strip white channel blending behavior color temperature

💡 Result:

Philips Hue “White and color ambiance” lightstrips (e.g., Lightstrip Plus / Gradient families) are effectively 5‑channel strips: R, G, B + warm‑white + cool‑white LEDs. They’re not “RGB only” and not “RGBW with a single fixed-white”; they have separate warm and cool white emitters. [1], [2]

What that means for white/CCT blending

  • In color‑temperature (CCT/“ct”) mode: the strip primarily achieves the requested CCT by mixing the warm‑white and cool‑white channels. At “mid” whites, both white channels can be on at the same time (a crossfade/mix rather than an exclusive switch). [1], [2]
  • In color (XY/HS) mode (including pastel colors and some whites selected from the color wheel): Hue’s internal color engine can drive all five channels together (RGB plus the warm/cool whites) to hit the requested chromaticity/brightness more efficiently or with better perceived color. So it’s normal to see non‑intuitive channel activity (e.g., some green/blue contribution) even when you think you’re choosing “white.” [1], [2]

Important exception: Hue “Essential” lightstrip

Hue’s lower‑cost Essential strip is reported to lack dedicated white LEDs (RGB only), so any “white/CCT” it offers is produced by RGB mixing, not WW/CW blending. [3]

Sources:
[1] Reddit discussion describing Hue lightstrip using warm + cool white and that Hue “uses all five LEDs together” for colors (r/Hue, 2019). (reddit.com)
[2] Home Assistant Community thread noting Lightstrip Plus has dedicated warm/cool white LEDs and showing non‑obvious LED activation when making whites. (community.home-assistant.io)
[3] Hueblog report stating Hue Essential Lightstrip has no dedicated white LEDs (RGB only). (hueblog.com)

Citations:


@babadam2002, thank you for sharing this helpful visual reference!

What I see in your screenshot

The Comment image shows a video recording interface with three LEDs visible at the top:

  • Left LED (cold white): slightly lit, dim
  • Middle LED (RGB): bright cyan/turquoise color
  • Right LED (warm white): very dim, nearly off

At the bottom is the Philips Hue app interface with a circular color wheel selector positioned in the cyan region, brightness at 25%, and a video timeline (4:12/4:30) showing multiple color keyframes demonstrating the RGB+CCT transitions across different colors.

How Philips Hue handles RGB+CCT blending

Based on technical documentation, Philips Hue "White and color ambiance" strips are 5-channel devices (R, G, B, warm-white, cool-white) that can drive all five channels simultaneously in color mode to achieve the requested color more efficiently. This means even when selecting a saturated color like cyan, Hue may add some white channel contribution to boost brightness and efficiency—exactly what you see in your video with the cold white LED slightly active.

How to reproduce this in WLED "accurate" mode

With this PR's improvements, you can achieve similar behavior:

Required settings:

  1. LED Settings → White Handling:

    • ✅ Enable "Calculate CCT from RGB" (this PR's core bugfix ensures CCT is calculated from your original RGB color, not the modified values)
    • Set "Auto White mode" to "Accurate" (this extracts the white component: w = min(r,g,b))
  2. LED Settings → CCT Blending:

    • Start with CCT blending = 0 to +30 for smooth additive transitions (both WW and CW can be on simultaneously, like Hue)
    • If you want more separation with smoother exclusive zones, try -60 to -80 as DedeHai suggested
    • Avoid -100 as that creates hard switches without transitions
  3. Configure your CCT range:

    • Set your warm white Kelvin (e.g., 2700K) and cool white Kelvin (e.g., 6500K) to match your actual LED hardware

Expected behavior:

  • Saturated colors (like your cyan): RGB LEDs provide the color, with small white channel contribution for brightness boost
  • Moving toward white: RGB gradually reduces as white channels ramp up using the extracted white component
  • Pure whites: WW and CW blend smoothly based on the color temperature you select
  • No jump at transitions: The bugfix in this PR ensures smooth CCT calculation throughout

Try starting with CCT blending = +20 and Auto White = Accurate mode, then fine-tune the blending value to match your preference! 🎨


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-02-19T10:36:46.916Z
Learning: When "Calculate CCT from RGB" mode (_cct < 0) is combined with "Accurate white" mode (RGBW_MODE_AUTO_ACCURATE), there is a bug in WLED's bus_manager.cpp where approximateKelvinFromRGB() receives RGB values that have already been modified (r -= w, g -= w, b -= w) by autoWhiteCalc(), causing incorrect Kelvin temperature estimation. The original color is not preserved, and calculateCCT() is called after autoWhiteCalc() modifies the RGB values. This affects BusDigital::setPixelColor() execution order (lines 245, 275).

Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-15T20:25:25.815Z
Learning: The CSS palette preview in wled00/data/index.js genPalPrevCss() function uses raw RGB values in CSS linear-gradient() without applying WLED's gamma correction, while actual LED output goes through NeoGammaWLEDMethod gamma correction. This causes inherent discrepancies between the web UI palette preview and actual LED colors, especially noticeable with different gamma settings.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:149-151
Timestamp: 2026-01-24T18:51:09.740Z
Learning: In WLED's Spinning Wheel effect (usermods/user_fx/user_fx.cpp), the color scale calculation (SEGENV.aux1) must match the position modulo range. When position uses `pos = (pos_fixed >> 16) % SEGLEN`, the color scale must be `aux1 = (255 << 8) / SEGLEN` to distribute the palette evenly across the full position range. Using vstripLen for color scaling while position uses SEGLEN causes palette colors to cycle too quickly and appear incomplete.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-13T21:23:35.514Z
Learning: In WLED, the global `paletteBlend` variable (wled.h:603) and the `WS2812FX::paletteBlend` member (FX.h:940) are duplicates without synchronization code. The global is loaded/saved in cfg.cpp and set via UI in set.cpp, but never copied to the strip member. This is the only such case in the codebase; other settings are either strip-only members (autoSegments, correctWB, cctFromRgb, isMatrix) or global-only (gammaCorrectCol/Bri/Val, blendingStyle).

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/bus_manager.cpp:549-556
Timestamp: 2026-02-11T16:20:02.872Z
Learning: ESP-IDF V5 requires a complete rewrite of the LEDC (PWM/analog LED) subsystem in WLED. The framework now manages LEDC channels internally (unlike V4 where WLED managed them directly), and dithering functionality may not work as it did before. Direct LEDC struct access (e.g., `LEDC.channel_group[gr].channel[ch].duty.duty`) used in the current implementation will need to be replaced with proper HAL API calls. This affects `wled00/bus_manager.cpp` BusPwm class and related pin manager LEDC allocation code.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: freakintoddles2
Repo: wled/WLED PR: 4904
File: wled00/FX.cpp:3915-3930
Timestamp: 2025-09-02T19:42:57.475Z
Learning: WLED wled00/FX.cpp::mode_percent — For time-based options (% of Minute/Hour/Day), behavior is clock-like: at rollover (elapsed==0) intentionally show SEGLEN lit for one tick to represent completion, then move to 1 LED at the next second. This is by design (not a progress bar) and should not be “fixed”.

Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.

Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Philips HUE like white blending (RGB+CCT) between WW and CW channel

3 participants