[light] Replace rgb_order/is_rgbw/is_wrgb with channel_colors (#18474)

This commit is contained in:
Jesse Hills
2026-08-18 13:19:48 +12:00
committed by GitHub
parent 4416aacebb
commit 463e3833da
30 changed files with 454 additions and 433 deletions
@@ -3,7 +3,7 @@ light:
id: led_matrix_32x8
default_transition_length: 500ms
chipset: ws2812
rgb_order: GRB
channel_colors: GRB
num_leds: 256
pin: ${pin}
@@ -3,7 +3,7 @@ light:
id: led_matrix_32x8
default_transition_length: 500ms
chipset: ws2812
rgb_order: GRB
channel_colors: GRB
num_leds: 256
pin: ${pin}
@@ -1,6 +1,6 @@
light:
- platform: beken_spi_led_strip
rgb_order: GRB
channel_colors: GRB
pin: P16
num_leds: 30
chipset: ws2812
@@ -0,0 +1,10 @@
# The deprecated rgb_order / is_rgbw / is_wrgb keys, kept working until 2027.3.0.
# Config-only, and only one strip because P16 is the sole supported pin.
light:
- platform: beken_spi_led_strip
name: Legacy RGBW
pin: P16
num_leds: 30
chipset: sk6812
rgb_order: GRB
is_rgbw: true # -> GRBW
+1 -1
View File
@@ -5,7 +5,7 @@ light:
id: led_matrix_32x8
default_transition_length: 500ms
chipset: ws2812
rgb_order: GRB
channel_colors: GRB
num_leds: 256
pin: ${pin}
effects:
+1 -1
View File
@@ -5,7 +5,7 @@ light:
id: led_matrix_32x8
default_transition_length: 500ms
chipset: ws2812
rgb_order: GRB
channel_colors: GRB
num_leds: 256
pin: ${pin}
effects:
+1 -1
View File
@@ -6,7 +6,7 @@ light:
pin: 2
pio: 0
num_leds: 256
rgb_order: GRB
channel_colors: GRB
chipset: WS2812
effects:
- e131:
@@ -3,13 +3,13 @@ light:
id: led_strip1
pin: ${pin1}
num_leds: 60
rgb_order: GRB
channel_colors: GRB
chipset: ws2812
- platform: esp32_rmt_led_strip
id: led_strip2
pin: ${pin2}
num_leds: 60
rgbw_order: RWGB
channel_colors: RWGB
bit0_high: 100us
bit0_low: 100us
bit1_high: 100us
@@ -8,14 +8,14 @@ light:
id: led_strip1
pin: ${pin1}
num_leds: 60
rgb_order: GRB
channel_colors: GRB
chipset: ws2812
use_dma: "true"
- platform: esp32_rmt_led_strip
id: led_strip2
pin: ${pin2}
num_leds: 60
rgb_order: RGB
channel_colors: RGB
bit0_high: 100us
bit0_low: 100us
bit1_high: 100us
@@ -0,0 +1,23 @@
# The deprecated rgb_order / is_rgbw / is_wrgb keys, kept working until 2027.3.0.
# Config-only: each strip below must migrate to the channel_colors shown in the comment.
light:
- platform: esp32_rmt_led_strip
id: legacy_rgb
pin: GPIO13
num_leds: 60
chipset: ws2812
rgb_order: GRB # -> GRB
- platform: esp32_rmt_led_strip
id: legacy_rgbw
pin: GPIO14
num_leds: 60
chipset: sk6812
rgb_order: GRB
is_rgbw: true # -> GRBW
- platform: esp32_rmt_led_strip
id: legacy_wrgb
pin: GPIO15
num_leds: 60
chipset: sk6812
rgb_order: GRB
is_wrgb: true # -> WGRB
+1 -1
View File
@@ -4,7 +4,7 @@ light:
default_transition_length: 500ms
chipset: ws2812
num_leds: 256
rgb_order: GRB
channel_colors: GRB
pin: ${pin}
- platform: partition
name: Partition Light
+1 -1
View File
@@ -4,7 +4,7 @@ light:
default_transition_length: 500ms
chipset: ws2812
num_leds: 256
rgb_order: GRB
channel_colors: GRB
pin: ${pin}
- platform: partition
name: Partition Light
@@ -4,14 +4,14 @@ light:
pin: 4
num_leds: 60
pio: 0
rgb_order: GRB
channel_colors: GRB
chipset: WS2812
- platform: rp2040_pio_led_strip
id: led_strip_custom_timings
pin: 5
num_leds: 60
pio: 1
rgb_order: GRB
channel_colors: GRB
bit0_high: .1us
bit0_low: 1.2us
bit1_high: .69us
@@ -0,0 +1,18 @@
# The deprecated rgb_order / is_rgbw keys, kept working until 2027.3.0.
# Config-only: each strip below must migrate to the channel_colors shown in the comment.
light:
- platform: rp2040_pio_led_strip
id: legacy_rgb
pin: 4
num_leds: 60
pio: 0
chipset: WS2812
rgb_order: GRB # -> GRB
- platform: rp2040_pio_led_strip
id: legacy_rgbw
pin: 5
num_leds: 60
pio: 1
chipset: SK6812
rgb_order: GRB
is_rgbw: true # -> GRBW
+1 -1
View File
@@ -9,7 +9,7 @@ light:
id: led_matrix_32x8
default_transition_length: 500ms
chipset: ws2812
rgb_order: GRB
channel_colors: GRB
num_leds: 256
pin: 2
effects:
@@ -0,0 +1,144 @@
"""Tests for the shared addressable-strip channel order helpers."""
import logging
import pytest
from esphome.components.const import CONF_CHANNEL_COLORS, CONF_IS_WRGB
from esphome.components.light import (
channel_colors_struct,
migrate_channel_colors,
validate_channel_colors,
)
import esphome.config_validation as cv
from esphome.const import CONF_IS_RGBW, CONF_RGB_ORDER
from esphome.types import ConfigType
NO_WHITE = "light::ChannelColors::NO_WHITE"
@pytest.mark.parametrize(
("value", "expected"),
[
("RGB", "RGB"),
("grb", "GRB"),
("BRG", "BRG"),
("rgbw", "RGBW"),
("WRGB", "WRGB"),
("GWRB", "GWRB"),
],
)
def test_validate_channel_colors(value: str, expected: str) -> None:
assert validate_channel_colors(value) == expected
@pytest.mark.parametrize(
"value",
[
"RG", # missing a channel
"RGBB", # duplicate channel
"RRGB", # duplicate channel, correct length
"RGBWW", # two white channels
"RGBX", # unknown channel
"RGBWX", # unknown channel, correct length
"",
],
)
def test_validate_channel_colors_rejects_invalid(value: str) -> None:
with pytest.raises(cv.Invalid, match="is not a valid channel order"):
validate_channel_colors(value)
@pytest.mark.parametrize(
("value", "expected"),
[
("RGB", (0, 1, 2, NO_WHITE)),
("GRB", (1, 0, 2, NO_WHITE)),
("BRG", (1, 2, 0, NO_WHITE)),
("RGBW", (0, 1, 2, 3)),
("GRBW", (1, 0, 2, 3)),
("WRGB", (1, 2, 3, 0)),
("GWRB", (2, 0, 3, 1)),
],
)
def test_channel_colors_struct(value: str, expected: tuple[int, int, int, int]) -> None:
struct = channel_colors_struct(value)
assert str(struct.base) == "light::ChannelColors"
assert tuple(str(arg) for arg in struct.args.values()) == tuple(
str(field) for field in expected
)
def _migrate(config: ConfigType) -> ConfigType:
return migrate_channel_colors(removed_in="2027.3.0", component="test_strip")(config)
def test_migrate_passes_through_channel_colors() -> None:
config = {CONF_CHANNEL_COLORS: "GRBW"}
assert _migrate(config) == {CONF_CHANNEL_COLORS: "GRBW"}
@pytest.mark.parametrize(
("deprecated", "expected", "named"),
[
({}, "GRB", "'rgb_order' is"),
(
{CONF_IS_RGBW: False, CONF_IS_WRGB: False},
"GRB",
"'rgb_order', 'is_rgbw' and 'is_wrgb' are",
),
({CONF_IS_RGBW: True}, "GRBW", "'rgb_order' and 'is_rgbw' are"),
({CONF_IS_WRGB: True}, "WGRB", "'rgb_order' and 'is_wrgb' are"),
],
)
def test_migrate_folds_deprecated_keys(
deprecated: ConfigType,
expected: str,
named: str,
caplog: pytest.LogCaptureFixture,
) -> None:
config = {CONF_RGB_ORDER: "GRB", "num_leds": 1, **deprecated}
with caplog.at_level(logging.WARNING):
result = _migrate(config)
assert result == {CONF_CHANNEL_COLORS: expected, "num_leds": 1}
assert f"[test_strip] {named} deprecated" in caplog.text
assert f"'{CONF_CHANNEL_COLORS}: {expected}'" in caplog.text
assert "2027.3.0" in caplog.text
def test_migrate_does_not_mutate_input() -> None:
config = {CONF_RGB_ORDER: "GRB", CONF_IS_RGBW: True}
_migrate(config)
assert config == {CONF_RGB_ORDER: "GRB", CONF_IS_RGBW: True}
@pytest.mark.parametrize("deprecated", [CONF_RGB_ORDER, CONF_IS_RGBW, CONF_IS_WRGB])
def test_migrate_rejects_mixing_old_and_new(deprecated: str) -> None:
config = {CONF_CHANNEL_COLORS: "GRBW", deprecated: "GRB"}
with pytest.raises(cv.Invalid, match=f"cannot be combined with '{deprecated}'"):
_migrate(config)
def test_migrate_reports_every_conflicting_key() -> None:
config = {
CONF_CHANNEL_COLORS: "GRBW",
CONF_RGB_ORDER: "GRB",
CONF_IS_RGBW: True,
CONF_IS_WRGB: False,
}
with pytest.raises(
cv.Invalid, match="cannot be combined with 'rgb_order', 'is_rgbw' and 'is_wrgb'"
):
_migrate(config)
def test_migrate_requires_channel_colors() -> None:
with pytest.raises(cv.Invalid, match=f"'{CONF_CHANNEL_COLORS}' is required"):
_migrate({"num_leds": 1})
def test_migrate_rejects_is_rgbw_with_is_wrgb() -> None:
config = {CONF_RGB_ORDER: "GRB", CONF_IS_RGBW: True, CONF_IS_WRGB: True}
with pytest.raises(cv.Invalid, match="cannot both be enabled"):
_migrate(config)
@@ -1,57 +0,0 @@
import pytest
from esphome.components.esp32_rmt_led_strip.light import (
CONF_IS_WRGB,
CONF_RGBW_ORDER,
_split_rgbw_order,
_validate_rgbw_order,
_validate_rgbw_order_exclusivity,
)
import esphome.config_validation as cv
from esphome.const import CONF_IS_RGBW
def test_validate_rgbw_order() -> None:
assert _validate_rgbw_order("rwgb") == "RWGB"
@pytest.mark.parametrize("rgbw_order", ["RGB", "RRGB", "RGBWW"])
def test_validate_rgbw_order_rejects_invalid_order(rgbw_order: str) -> None:
with pytest.raises(cv.Invalid, match="permutation of RGBW"):
_validate_rgbw_order(rgbw_order)
@pytest.mark.parametrize(
("rgbw_order", "expected"),
[
("WRGB", ("RGB", 0)),
("RWGB", ("RGB", 1)),
("GWRB", ("GRB", 1)),
("RGBW", ("RGB", 3)),
],
)
def test_split_rgbw_order(rgbw_order: str, expected: tuple[str, int]) -> None:
assert _split_rgbw_order(rgbw_order) == expected
@pytest.mark.parametrize("conflict", [CONF_IS_RGBW, CONF_IS_WRGB])
def test_rgbw_order_is_mutually_exclusive(conflict: str) -> None:
with pytest.raises(cv.Invalid, match="cannot be used with"):
_validate_rgbw_order_exclusivity(
{
CONF_RGBW_ORDER: "RGBW",
CONF_IS_RGBW: conflict == CONF_IS_RGBW,
CONF_IS_WRGB: conflict == CONF_IS_WRGB,
}
)
@pytest.mark.parametrize("legacy_option", [CONF_IS_RGBW, CONF_IS_WRGB])
def test_rgbw_order_allows_disabled_legacy_options(legacy_option: str) -> None:
config = {
CONF_RGBW_ORDER: "RGBW",
CONF_IS_RGBW: False,
CONF_IS_WRGB: False,
}
config[legacy_option] = False
assert _validate_rgbw_order_exclusivity(config) is config