[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
@@ -300,46 +300,12 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
} }
light::ESPColorView BekenSPILEDStripLightOutput::get_view_internal(int32_t index) const { light::ESPColorView BekenSPILEDStripLightOutput::get_view_internal(int32_t index) const {
int32_t r = 0, g = 0, b = 0; const light::ChannelColors &colors = this->channel_colors_;
switch (this->rgb_order_) { uint8_t *led = this->buf_ + (index * colors.bytes_per_led());
case ORDER_RGB: return {led + colors.r,
r = 0; led + colors.g,
g = 1; led + colors.b,
b = 2; colors.has_white() ? led + colors.w : nullptr,
break;
case ORDER_RBG:
r = 0;
g = 2;
b = 1;
break;
case ORDER_GRB:
r = 1;
g = 0;
b = 2;
break;
case ORDER_GBR:
r = 2;
g = 0;
b = 1;
break;
case ORDER_BGR:
r = 2;
g = 1;
b = 0;
break;
case ORDER_BRG:
r = 1;
g = 2;
b = 0;
break;
}
uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3;
uint8_t white = this->is_wrgb_ ? 0 : 3;
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
this->buf_ + (index * multiplier) + g + this->is_wrgb_,
this->buf_ + (index * multiplier) + b + this->is_wrgb_,
this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
&this->effect_data_[index], &this->effect_data_[index],
&this->correction_}; &this->correction_};
} }
@@ -349,35 +315,12 @@ void BekenSPILEDStripLightOutput::dump_config() {
"Beken SPI LED Strip:\n" "Beken SPI LED Strip:\n"
" Pin: %u", " Pin: %u",
this->pin_); this->pin_);
const char *rgb_order; char channel_colors[5];
switch (this->rgb_order_) {
case ORDER_RGB:
rgb_order = "RGB";
break;
case ORDER_RBG:
rgb_order = "RBG";
break;
case ORDER_GRB:
rgb_order = "GRB";
break;
case ORDER_GBR:
rgb_order = "GBR";
break;
case ORDER_BGR:
rgb_order = "BGR";
break;
case ORDER_BRG:
rgb_order = "BRG";
break;
default:
rgb_order = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" RGB Order: %s\n" " Channel colors: %s\n"
" Max refresh rate: %" PRIu32 "\n" " Max refresh rate: %" PRIu32 "\n"
" Number of LEDs: %u", " Number of LEDs: %u",
rgb_order, this->max_refresh_rate_.value_or(0), this->num_leds_); this->channel_colors_.to_string(channel_colors), this->max_refresh_rate_.value_or(0), this->num_leds_);
} }
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; } float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
@@ -3,6 +3,7 @@
#ifdef USE_BK72XX #ifdef USE_BK72XX
#include "esphome/components/light/addressable_light.h" #include "esphome/components/light/addressable_light.h"
#include "esphome/components/light/channel_colors.h"
#include "esphome/components/light/light_output.h" #include "esphome/components/light/light_output.h"
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
@@ -10,15 +11,6 @@
namespace esphome::beken_spi_led_strip { namespace esphome::beken_spi_led_strip {
enum RGBOrder : uint8_t {
ORDER_RGB,
ORDER_RBG,
ORDER_GRB,
ORDER_GBR,
ORDER_BGR,
ORDER_BRG,
};
class BekenSPILEDStripLightOutput final : public light::AddressableLight { class BekenSPILEDStripLightOutput final : public light::AddressableLight {
public: public:
void setup() override; void setup() override;
@@ -28,7 +20,7 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
int32_t size() const override { return this->num_leds_; } int32_t size() const override { return this->num_leds_; }
light::LightTraits get_traits() override { light::LightTraits get_traits() override {
auto traits = light::LightTraits(); auto traits = light::LightTraits();
if (this->is_rgbw_ || this->is_wrgb_) { if (this->channel_colors_.has_white()) {
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}); traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
} else { } else {
traits.set_supported_color_modes({light::ColorMode::RGB}); traits.set_supported_color_modes({light::ColorMode::RGB});
@@ -38,16 +30,13 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
void set_pin(uint8_t pin) { this->pin_ = pin; } void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } void set_channel_colors(light::ChannelColors channel_colors) { this->channel_colors_ = channel_colors; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
/// Set a maximum refresh rate in µs as some lights do not like being updated too often. /// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
void set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency); void set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
void clear_effect_data() override { void clear_effect_data() override {
for (int i = 0; i < this->size(); i++) for (int i = 0; i < this->size(); i++)
this->effect_data_[i] = 0; this->effect_data_[i] = 0;
@@ -58,7 +47,7 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
protected: protected:
light::ESPColorView get_view_internal(int32_t index) const override; light::ESPColorView get_view_internal(int32_t index) const override;
size_t get_buffer_size_() const { return this->num_leds_ * (this->is_rgbw_ || this->is_wrgb_ ? 4 : 3); } size_t get_buffer_size_() const { return this->num_leds_ * this->channel_colors_.bytes_per_led(); }
uint8_t *buf_{nullptr}; uint8_t *buf_{nullptr};
uint8_t *effect_data_{nullptr}; uint8_t *effect_data_{nullptr};
@@ -66,13 +55,11 @@ class BekenSPILEDStripLightOutput final : public light::AddressableLight {
uint8_t pin_; uint8_t pin_;
uint16_t num_leds_; uint16_t num_leds_;
bool is_rgbw_;
bool is_wrgb_;
uint32_t spi_frequency_{6666666}; uint32_t spi_frequency_{6666666};
uint8_t bit0_{0xE0}; uint8_t bit0_{0xE0};
uint8_t bit1_{0xFC}; uint8_t bit1_{0xFC};
RGBOrder rgb_order_; light::ChannelColors channel_colors_{0, 1, 2, light::ChannelColors::NO_WHITE};
uint32_t last_refresh_{0}; uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{}; optional<uint32_t> max_refresh_rate_{};
+17 -24
View File
@@ -3,6 +3,7 @@ from dataclasses import dataclass
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import libretiny, light from esphome.components import libretiny, light
from esphome.components.const import CONF_CHANNEL_COLORS, CONF_IS_WRGB
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CHIPSET, CONF_CHIPSET,
@@ -13,6 +14,7 @@ from esphome.const import (
CONF_PIN, CONF_PIN,
CONF_RGB_ORDER, CONF_RGB_ORDER,
) )
from esphome.types import ConfigType
CODEOWNERS = ["@Mat931"] CODEOWNERS = ["@Mat931"]
DEPENDENCIES = ["libretiny"] DEPENDENCIES = ["libretiny"]
@@ -22,17 +24,6 @@ BekenSPILEDStripLightOutput = beken_spi_led_strip_ns.class_(
"BekenSPILEDStripLightOutput", light.AddressableLight "BekenSPILEDStripLightOutput", light.AddressableLight
) )
RGBOrder = beken_spi_led_strip_ns.enum("RGBOrder")
RGB_ORDERS = {
"RGB": RGBOrder.ORDER_RGB,
"RBG": RGBOrder.ORDER_RBG,
"GRB": RGBOrder.ORDER_GRB,
"GBR": RGBOrder.ORDER_GBR,
"BGR": RGBOrder.ORDER_BGR,
"BRG": RGBOrder.ORDER_BRG,
}
@dataclass @dataclass
class LEDStripTimings: class LEDStripTimings:
@@ -57,8 +48,6 @@ CHIPSETS = {
} }
CONF_IS_WRGB = "is_wrgb"
SUPPORTED_PINS = { SUPPORTED_PINS = {
libretiny.const.FAMILY_BK7231N: [16], libretiny.const.FAMILY_BK7231N: [16],
libretiny.const.FAMILY_BK7231T: [16], libretiny.const.FAMILY_BK7231T: [16],
@@ -79,10 +68,9 @@ def _validate_pin(value):
return value return value
def _validate_num_leds(value): def _validate_num_leds(value: ConfigType) -> ConfigType:
max_num_leds = 165 # 170 # A white channel makes each LED one byte wider, so fewer of them fit in the DMA buffer.
if value[CONF_IS_RGBW] or value[CONF_IS_WRGB]: max_num_leds = 123 if "W" in value[CONF_CHANNEL_COLORS] else 165 # 127 / 170
max_num_leds = 123 # 127
if value[CONF_NUM_LEDS] > max_num_leds: if value[CONF_NUM_LEDS] > max_num_leds:
raise cv.Invalid( raise cv.Invalid(
f"The maximum number of LEDs for this configuration is {max_num_leds}.", f"The maximum number of LEDs for this configuration is {max_num_leds}.",
@@ -99,18 +87,23 @@ CONFIG_SCHEMA = cv.All(
pins.internal_gpio_output_pin_number, _validate_pin pins.internal_gpio_output_pin_number, _validate_pin
), ),
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.Optional(CONF_CHANNEL_COLORS): light.validate_channel_colors,
# Deprecated in favour of CONF_CHANNEL_COLORS, remove in 2027.3.0
cv.Optional(CONF_RGB_ORDER): cv.one_of(*light.RGB_ORDERS, upper=True),
cv.Optional(CONF_IS_RGBW): cv.boolean,
cv.Optional(CONF_IS_WRGB): cv.boolean,
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
} }
), ),
light.migrate_channel_colors(
removed_in="2027.3.0", component="beken_spi_led_strip"
),
_validate_num_leds, _validate_num_leds,
) )
async def to_code(config): async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await light.register_light(var, config) await light.register_light(var, config)
await cg.register_component(var, config) await cg.register_component(var, config)
@@ -130,6 +123,6 @@ async def to_code(config):
) )
) )
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) cg.add(
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) var.set_channel_colors(light.channel_colors_struct(config[CONF_CHANNEL_COLORS]))
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) )
+2
View File
@@ -10,6 +10,7 @@ CONF_ACCELEROMETER_RANGE = "accelerometer_range"
CONF_B_CONSTANT = "b_constant" CONF_B_CONSTANT = "b_constant"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
CONF_BYTE_ORDER = "byte_order" CONF_BYTE_ORDER = "byte_order"
CONF_CHANNEL_COLORS = "channel_colors"
CONF_CLIMATE_ID = "climate_id" CONF_CLIMATE_ID = "climate_id"
CONF_CO2_EQUIVALENT = "co2_equivalent" CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_COLOR_DEPTH = "color_depth" CONF_COLOR_DEPTH = "color_depth"
@@ -22,6 +23,7 @@ CONF_GYROSCOPE_ODR = "gyroscope_odr"
CONF_GYROSCOPE_RANGE = "gyroscope_range" CONF_GYROSCOPE_RANGE = "gyroscope_range"
CONF_IAQ = "iaq" CONF_IAQ = "iaq"
CONF_IGNORE_NOT_FOUND = "ignore_not_found" CONF_IGNORE_NOT_FOUND = "ignore_not_found"
CONF_IS_WRGB = "is_wrgb"
CONF_LABEL = "label" CONF_LABEL = "label"
CONF_LIBRETINY = "libretiny" CONF_LIBRETINY = "libretiny"
CONF_LOOP = "loop" CONF_LOOP = "loop"
@@ -221,46 +221,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
} }
light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const { light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const {
int32_t r = 0, g = 0, b = 0; const light::ChannelColors &colors = this->channel_colors_;
switch (this->rgb_order_) { uint8_t *led = this->buf_ + (index * colors.bytes_per_led());
case ORDER_RGB: return {led + colors.r,
r = 0; led + colors.g,
g = 1; led + colors.b,
b = 2; colors.has_white() ? led + colors.w : nullptr,
break;
case ORDER_RBG:
r = 0;
g = 2;
b = 1;
break;
case ORDER_GRB:
r = 1;
g = 0;
b = 2;
break;
case ORDER_GBR:
r = 2;
g = 0;
b = 1;
break;
case ORDER_BGR:
r = 2;
g = 1;
b = 0;
break;
case ORDER_BRG:
r = 1;
g = 2;
b = 0;
break;
}
uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3;
uint8_t white = this->is_wrgb_ ? 0 : this->white_index_;
return {this->buf_ + (index * multiplier) + r + (white <= r),
this->buf_ + (index * multiplier) + g + (white <= g),
this->buf_ + (index * multiplier) + b + (white <= b),
this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
&this->effect_data_[index], &this->effect_data_[index],
&this->correction_}; &this->correction_};
} }
@@ -271,46 +237,12 @@ void ESP32RMTLEDStripLightOutput::dump_config() {
" Pin: %u", " Pin: %u",
this->pin_); this->pin_);
ESP_LOGCONFIG(TAG, " RMT Symbols: %" PRIu32, this->rmt_symbols_); ESP_LOGCONFIG(TAG, " RMT Symbols: %" PRIu32, this->rmt_symbols_);
const char *rgb_order; char channel_colors[5];
switch (this->rgb_order_) {
case ORDER_RGB:
rgb_order = "RGB";
break;
case ORDER_RBG:
rgb_order = "RBG";
break;
case ORDER_GRB:
rgb_order = "GRB";
break;
case ORDER_GBR:
rgb_order = "GBR";
break;
case ORDER_BGR:
rgb_order = "BGR";
break;
case ORDER_BRG:
rgb_order = "BRG";
break;
default:
rgb_order = "UNKNOWN";
break;
}
if (this->is_rgbw_ || this->is_wrgb_) {
char rgbw_order[5];
uint8_t white = this->is_wrgb_ ? 0 : this->white_index_;
uint8_t rgb_index = 0;
for (uint8_t i = 0; i < 4; i++) {
rgbw_order[i] = i == white ? 'W' : rgb_order[rgb_index++];
}
rgbw_order[4] = '\0';
ESP_LOGCONFIG(TAG, " RGBW Order: %s", rgbw_order);
} else {
ESP_LOGCONFIG(TAG, " RGB Order: %s", rgb_order);
}
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Channel colors: %s\n"
" Max refresh rate: %" PRIu32 "\n" " Max refresh rate: %" PRIu32 "\n"
" Number of LEDs: %u", " Number of LEDs: %u",
this->max_refresh_rate_.value_or(0), this->num_leds_); this->channel_colors_.to_string(channel_colors), this->max_refresh_rate_.value_or(0), this->num_leds_);
} }
float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; } float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
@@ -3,6 +3,7 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "esphome/components/light/addressable_light.h" #include "esphome/components/light/addressable_light.h"
#include "esphome/components/light/channel_colors.h"
#include "esphome/components/light/light_output.h" #include "esphome/components/light/light_output.h"
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
@@ -15,15 +16,6 @@
namespace esphome::esp32_rmt_led_strip { namespace esphome::esp32_rmt_led_strip {
enum RGBOrder : uint8_t {
ORDER_RGB,
ORDER_RBG,
ORDER_GRB,
ORDER_GBR,
ORDER_BGR,
ORDER_BRG,
};
struct LedParams { struct LedParams {
rmt_symbol_word_t bit0; rmt_symbol_word_t bit0;
rmt_symbol_word_t bit1; rmt_symbol_word_t bit1;
@@ -39,7 +31,7 @@ class ESP32RMTLEDStripLightOutput final : public light::AddressableLight {
int32_t size() const override { return this->num_leds_; } int32_t size() const override { return this->num_leds_; }
light::LightTraits get_traits() override { light::LightTraits get_traits() override {
auto traits = light::LightTraits(); auto traits = light::LightTraits();
if (this->is_rgbw_ || this->is_wrgb_) { if (this->channel_colors_.has_white()) {
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}); traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
} else { } else {
traits.set_supported_color_modes({light::ColorMode::RGB}); traits.set_supported_color_modes({light::ColorMode::RGB});
@@ -50,13 +42,7 @@ class ESP32RMTLEDStripLightOutput final : public light::AddressableLight {
void set_pin(uint8_t pin) { this->pin_ = pin; } void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->invert_out_ = inverted; } void set_inverted(bool inverted) { this->invert_out_ = inverted; }
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } void set_channel_colors(light::ChannelColors channel_colors) { this->channel_colors_ = channel_colors; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
void set_rgbw_order(uint8_t white_index) {
this->is_rgbw_ = true;
this->is_wrgb_ = false;
this->white_index_ = white_index;
}
void set_use_dma(bool use_dma) { this->use_dma_ = use_dma; } void set_use_dma(bool use_dma) { this->use_dma_ = use_dma; }
void set_use_psram(bool use_psram) { this->use_psram_ = use_psram; } void set_use_psram(bool use_psram) { this->use_psram_ = use_psram; }
@@ -66,7 +52,6 @@ class ESP32RMTLEDStripLightOutput final : public light::AddressableLight {
void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low, void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low,
uint32_t reset_time_high, uint32_t reset_time_low); uint32_t reset_time_high, uint32_t reset_time_low);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; } void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; }
void clear_effect_data() override { void clear_effect_data() override {
@@ -79,7 +64,7 @@ class ESP32RMTLEDStripLightOutput final : public light::AddressableLight {
protected: protected:
light::ESPColorView get_view_internal(int32_t index) const override; light::ESPColorView get_view_internal(int32_t index) const override;
size_t get_buffer_size_() const { return this->num_leds_ * (this->is_rgbw_ || this->is_wrgb_ ? 4 : 3); } size_t get_buffer_size_() const { return this->num_leds_ * this->channel_colors_.bytes_per_led(); }
uint8_t *buf_{nullptr}; uint8_t *buf_{nullptr};
uint8_t *effect_data_{nullptr}; uint8_t *effect_data_{nullptr};
@@ -94,15 +79,11 @@ class ESP32RMTLEDStripLightOutput final : public light::AddressableLight {
uint32_t rmt_symbols_{48}; uint32_t rmt_symbols_{48};
uint8_t pin_; uint8_t pin_;
uint16_t num_leds_; uint16_t num_leds_;
bool is_rgbw_{false};
bool is_wrgb_{false};
// An index after the RGB channels makes offset adjustment a no-op for three-channel strips.
uint8_t white_index_{3};
bool use_dma_{false}; bool use_dma_{false};
bool use_psram_{false}; bool use_psram_{false};
bool invert_out_{false}; bool invert_out_{false};
RGBOrder rgb_order_{ORDER_RGB}; light::ChannelColors channel_colors_{0, 1, 2, light::ChannelColors::NO_WHITE};
uint32_t last_refresh_{0}; uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{}; optional<uint32_t> max_refresh_rate_{};
+13 -52
View File
@@ -1,10 +1,9 @@
from dataclasses import dataclass from dataclasses import dataclass
import logging
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32, esp32_rmt, light from esphome.components import esp32, esp32_rmt, light
from esphome.components.const import CONF_USE_PSRAM from esphome.components.const import CONF_CHANNEL_COLORS, CONF_IS_WRGB, CONF_USE_PSRAM
from esphome.components.esp32 import include_builtin_idf_component from esphome.components.esp32 import include_builtin_idf_component
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@@ -22,8 +21,6 @@ from esphome.const import (
) )
from esphome.types import ConfigType from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
@@ -32,17 +29,6 @@ ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
"ESP32RMTLEDStripLightOutput", light.AddressableLight "ESP32RMTLEDStripLightOutput", light.AddressableLight
) )
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
RGB_ORDERS = {
"RGB": RGBOrder.ORDER_RGB,
"RBG": RGBOrder.ORDER_RBG,
"GRB": RGBOrder.ORDER_GRB,
"GBR": RGBOrder.ORDER_GBR,
"BGR": RGBOrder.ORDER_BGR,
"BRG": RGBOrder.ORDER_BRG,
}
@dataclass @dataclass
class LEDStripTimings: class LEDStripTimings:
@@ -62,8 +48,6 @@ CHIPSETS = {
"SM16703": LEDStripTimings(300, 900, 900, 300, 0, 0), "SM16703": LEDStripTimings(300, 900, 900, 300, 0, 0),
} }
CONF_IS_WRGB = "is_wrgb"
CONF_RGBW_ORDER = "rgbw_order"
CONF_BIT0_HIGH = "bit0_high" CONF_BIT0_HIGH = "bit0_high"
CONF_BIT0_LOW = "bit0_low" CONF_BIT0_LOW = "bit0_low"
CONF_BIT1_HIGH = "bit1_high" CONF_BIT1_HIGH = "bit1_high"
@@ -72,26 +56,6 @@ CONF_RESET_HIGH = "reset_high"
CONF_RESET_LOW = "reset_low" CONF_RESET_LOW = "reset_low"
def _validate_rgbw_order(value: str) -> str:
value = cv.string(value).upper()
if len(value) != 4 or set(value) != set("RGBW"):
raise cv.Invalid("RGBW order must be a permutation of RGBW")
return value
def _split_rgbw_order(rgbw_order: str) -> tuple[str, int]:
return rgbw_order.replace("W", ""), rgbw_order.index("W")
def _validate_rgbw_order_exclusivity(config: ConfigType) -> ConfigType:
if CONF_RGBW_ORDER in config and (config[CONF_IS_RGBW] or config[CONF_IS_WRGB]):
raise cv.Invalid(
f"'{CONF_RGBW_ORDER}' cannot be used with '{CONF_IS_RGBW}' or "
f"'{CONF_IS_WRGB}'"
)
return config
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
esp32.only_on_variant( esp32.only_on_variant(
unsupported=list(esp32_rmt.VARIANTS_NO_RMT), unsupported=list(esp32_rmt.VARIANTS_NO_RMT),
@@ -102,8 +66,11 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Optional(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.Optional(CONF_CHANNEL_COLORS): light.validate_channel_colors,
cv.Optional(CONF_RGBW_ORDER): _validate_rgbw_order, # Deprecated in favour of CONF_CHANNEL_COLORS, remove in 2027.3.0
cv.Optional(CONF_RGB_ORDER): cv.one_of(*light.RGB_ORDERS, upper=True),
cv.Optional(CONF_IS_RGBW): cv.boolean,
cv.Optional(CONF_IS_WRGB): cv.boolean,
cv.SplitDefault( cv.SplitDefault(
CONF_RMT_SYMBOLS, CONF_RMT_SYMBOLS,
esp32=192, esp32=192,
@@ -117,8 +84,6 @@ CONFIG_SCHEMA = cv.All(
): cv.int_range(min=2), ): cv.int_range(min=2),
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
cv.Optional(CONF_USE_DMA): cv.All( cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant( esp32.only_on_variant(
supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3] supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3]
@@ -153,12 +118,13 @@ CONFIG_SCHEMA = cv.All(
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
cv.has_exactly_one_key(CONF_RGB_ORDER, CONF_RGBW_ORDER), light.migrate_channel_colors(
_validate_rgbw_order_exclusivity, removed_in="2027.3.0", component="esp32_rmt_led_strip"
),
) )
async def to_code(config): async def to_code(config: ConfigType) -> None:
# Re-enable ESP-IDF's RMT driver (excluded by default to save compile time) # Re-enable ESP-IDF's RMT driver (excluded by default to save compile time)
include_builtin_idf_component("esp_driver_rmt") include_builtin_idf_component("esp_driver_rmt")
@@ -198,14 +164,9 @@ async def to_code(config):
) )
) )
if (rgbw_order := config.get(CONF_RGBW_ORDER)) is not None: cg.add(
rgb_order, white_index = _split_rgbw_order(rgbw_order) var.set_channel_colors(light.channel_colors_struct(config[CONF_CHANNEL_COLORS]))
cg.add(var.set_rgb_order(RGB_ORDERS[rgb_order])) )
cg.add(var.set_rgbw_order(white_index))
else:
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) cg.add(var.set_use_psram(config[CONF_USE_PSRAM]))
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
if CONF_USE_DMA in config: if CONF_USE_DMA in config:
+106
View File
@@ -1,9 +1,12 @@
from collections.abc import Callable
from dataclasses import dataclass, field from dataclasses import dataclass, field
import enum import enum
import logging
import esphome.automation as auto import esphome.automation as auto
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import mqtt, power_supply, web_server from esphome.components import mqtt, power_supply, web_server
from esphome.components.const import CONF_CHANNEL_COLORS, CONF_IS_WRGB
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_BLUE, CONF_BLUE,
@@ -23,6 +26,7 @@ from esphome.const import (
CONF_ICON, CONF_ICON,
CONF_ID, CONF_ID,
CONF_INITIAL_STATE, CONF_INITIAL_STATE,
CONF_IS_RGBW,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_NAME, CONF_NAME,
CONF_ON_STATE, CONF_ON_STATE,
@@ -32,6 +36,7 @@ from esphome.const import (
CONF_POWER_SUPPLY, CONF_POWER_SUPPLY,
CONF_RED, CONF_RED,
CONF_RESTORE_MODE, CONF_RESTORE_MODE,
CONF_RGB_ORDER,
CONF_STATE, CONF_STATE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WARM_WHITE, CONF_WARM_WHITE,
@@ -61,6 +66,7 @@ from .effects import (
from .types import ( # noqa: F401 from .types import ( # noqa: F401
AddressableLight, AddressableLight,
AddressableLightState, AddressableLightState,
ChannelColors,
ColorMode, ColorMode,
LightOutput, LightOutput,
LightState, LightState,
@@ -71,6 +77,8 @@ from .types import ( # noqa: F401
light_ns, light_ns,
) )
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@@ -165,6 +173,104 @@ def available_effects_str(effects: list) -> str:
return ", ".join(f"'{name}'" for name in available) if available else "none" return ", ".join(f"'{name}'" for name in available) if available else "none"
# Accepted values of the deprecated `rgb_order` key.
RGB_ORDERS = ("RGB", "RBG", "GRB", "GBR", "BGR", "BRG")
_RGB_CHANNELS = frozenset("RGB")
_RGBW_CHANNELS = frozenset("RGBW")
def validate_channel_colors(value: str) -> str:
"""Validate the channel order of an addressable strip, e.g. "GRB" or "WRGB"."""
value = cv.string_strict(value).upper()
channels = frozenset(value)
if len(channels) != len(value) or channels not in (_RGB_CHANNELS, _RGBW_CHANNELS):
raise cv.Invalid(
f"'{value}' is not a valid channel order. List each of R, G and B exactly "
"once, optionally with a single W, in the order the strip expects them "
"(for example GRB, GRBW or WRGB)"
)
return value
def channel_colors_struct(value: str) -> cg.StructInitializer:
"""Build the C++ `light::ChannelColors` for a validated channel order string."""
return cg.StructInitializer(
ChannelColors,
("r", value.index("R")),
("g", value.index("G")),
("b", value.index("B")),
(
"w",
value.index("W")
if "W" in value
else cg.RawExpression(f"{ChannelColors}::NO_WHITE"),
),
)
def _quote_and_join(keys: list[str]) -> str:
"""Quote each key and join them into a readable list, e.g. "'a', 'b' and 'c'"."""
quoted = [f"'{key}'" for key in keys]
if len(quoted) == 1:
return quoted[0]
return f"{', '.join(quoted[:-1])} and {quoted[-1]}"
def migrate_channel_colors(
*, removed_in: str, component: str
) -> Callable[[ConfigType], ConfigType]:
"""Fold the deprecated `rgb_order`, `is_rgbw` and `is_wrgb` keys into `channel_colors`.
This also enforces that `channel_colors` is set, which the schema cannot do on its
own while the deprecated keys are still accepted. After this runs, `to_code` only
ever sees `channel_colors`.
"""
def validator(config: ConfigType) -> ConfigType:
config = config.copy()
deprecated = [
key for key in (CONF_RGB_ORDER, CONF_IS_RGBW, CONF_IS_WRGB) if key in config
]
if CONF_CHANNEL_COLORS in config:
if deprecated:
raise cv.Invalid(
f"'{CONF_CHANNEL_COLORS}' cannot be combined with "
f"{_quote_and_join(deprecated)}"
)
return config
if CONF_RGB_ORDER not in config:
raise cv.Invalid(
f"'{CONF_CHANNEL_COLORS}' is required", path=[CONF_CHANNEL_COLORS]
)
rgb_order = config.pop(CONF_RGB_ORDER)
is_rgbw = config.pop(CONF_IS_RGBW, False)
is_wrgb = config.pop(CONF_IS_WRGB, False)
if is_rgbw and is_wrgb:
raise cv.Invalid(
f"'{CONF_IS_RGBW}' and '{CONF_IS_WRGB}' cannot both be enabled"
)
if is_wrgb:
channel_colors = f"W{rgb_order}"
elif is_rgbw:
channel_colors = f"{rgb_order}W"
else:
channel_colors = rgb_order
_LOGGER.warning(
"[%s] %s %s deprecated, use '%s: %s'. Will be removed in %s",
component,
_quote_and_join(deprecated),
"are" if len(deprecated) > 1 else "is",
CONF_CHANNEL_COLORS,
channel_colors,
removed_in,
)
config[CONF_CHANNEL_COLORS] = channel_colors
return config
return validator
def _final_validate(config: ConfigType) -> None: def _final_validate(config: ConfigType) -> None:
"""Validate all recorded effect name references against their target lights. """Validate all recorded effect name references against their target lights.
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
namespace esphome::light {
/// Which byte of an addressable LED's data carries each colour.
///
/// Built from a configuration string such as "GRB" or "WRGB": every field holds the
/// position that colour occupies in the bytes the strip expects. `w` is NO_WHITE when
/// the strip has no separate white channel.
struct ChannelColors {
/// Value of `w` for a strip that only has red, green and blue channels.
static constexpr uint8_t NO_WHITE = 0xFF;
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t w;
bool has_white() const { return this->w != NO_WHITE; }
uint8_t bytes_per_led() const { return this->has_white() ? 4 : 3; }
/// Write the order back out as text, e.g. "GRBW".
///
/// `buf` must have room for at least 5 characters. Returns `buf` so the result can be
/// passed straight to a log call.
const char *to_string(char *buf) const {
buf[this->r] = 'R';
buf[this->g] = 'G';
buf[this->b] = 'B';
if (this->has_white()) {
buf[this->w] = 'W';
}
buf[this->bytes_per_led()] = '\0';
return buf;
}
};
} // namespace esphome::light
+3
View File
@@ -16,6 +16,9 @@ LightColorValues = light_ns.class_("LightColorValues")
LightStateRTCState = light_ns.struct("LightStateRTCState") LightStateRTCState = light_ns.struct("LightStateRTCState")
LightCall = light_ns.class_("LightCall") LightCall = light_ns.class_("LightCall")
# Addressable strips
ChannelColors = light_ns.struct("ChannelColors")
# Color modes # Color modes
ColorMode = light_ns.enum("ColorMode", is_class=True) ColorMode = light_ns.enum("ColorMode", is_class=True)
COLOR_MODES = { COLOR_MODES = {
@@ -107,10 +107,10 @@ void RP2040PIOLEDStripLightOutput::setup() {
pio_get_dreq(this->pio_, this->sm_, true)); // set the DREQ to the state machine's TX FIFO pio_get_dreq(this->pio_, this->sm_, true)); // set the DREQ to the state machine's TX FIFO
dma_channel_configure(this->dma_chan_, &this->dma_config_, dma_channel_configure(this->dma_chan_, &this->dma_config_,
&this->pio_->txf[this->sm_], // write to the state machine's TX FIFO &this->pio_->txf[this->sm_], // write to the state machine's TX FIFO
this->buf_, // read from memory this->buf_, // read from memory
this->is_rgbw_ ? num_leds_ * 4 : num_leds_ * 3, // number of bytes to transfer this->get_buffer_size_(), // number of bytes to transfer
false // don't start yet false // don't start yet
); );
// Initialize the semaphore for this DMA channel // Initialize the semaphore for this DMA channel
@@ -142,58 +142,25 @@ void RP2040PIOLEDStripLightOutput::write_state(light::LightState *state) {
} }
light::ESPColorView RP2040PIOLEDStripLightOutput::get_view_internal(int32_t index) const { light::ESPColorView RP2040PIOLEDStripLightOutput::get_view_internal(int32_t index) const {
int32_t r = 0, g = 0, b = 0; const light::ChannelColors &colors = this->channel_colors_;
switch (this->rgb_order_) { uint8_t *led = this->buf_ + (index * colors.bytes_per_led());
case ORDER_RGB: return {led + colors.r,
r = 0; led + colors.g,
g = 1; led + colors.b,
b = 2; colors.has_white() ? led + colors.w : nullptr,
break;
case ORDER_RBG:
r = 0;
g = 2;
b = 1;
break;
case ORDER_GRB:
r = 1;
g = 0;
b = 2;
break;
case ORDER_GBR:
r = 2;
g = 0;
b = 1;
break;
case ORDER_BGR:
r = 2;
g = 1;
b = 0;
break;
case ORDER_BRG:
r = 1;
g = 2;
b = 0;
break;
}
uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
return {this->buf_ + (index * multiplier) + r,
this->buf_ + (index * multiplier) + g,
this->buf_ + (index * multiplier) + b,
this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
&this->effect_data_[index], &this->effect_data_[index],
&this->correction_}; &this->correction_};
} }
void RP2040PIOLEDStripLightOutput::dump_config() { void RP2040PIOLEDStripLightOutput::dump_config() {
char channel_colors[5];
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
"RP2040 PIO LED Strip Light Output:\n" "RP2040 PIO LED Strip Light Output:\n"
" Pin: GPIO%d\n" " Pin: GPIO%d\n"
" Number of LEDs: %d\n" " Number of LEDs: %d\n"
" RGBW: %s\n" " Channel colors: %s\n"
" RGB Order: %s\n"
" Max Refresh Rate: %f Hz", " Max Refresh Rate: %f Hz",
this->pin_, this->num_leds_, YESNO(this->is_rgbw_), rgb_order_to_string(this->rgb_order_), this->pin_, this->num_leds_, this->channel_colors_.to_string(channel_colors), this->max_refresh_rate_);
this->max_refresh_rate_);
} }
float RP2040PIOLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; } float RP2040PIOLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
@@ -7,6 +7,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/components/light/addressable_light.h" #include "esphome/components/light/addressable_light.h"
#include "esphome/components/light/channel_colors.h"
#include "esphome/components/light/light_output.h" #include "esphome/components/light/light_output.h"
#include <hardware/dma.h> #include <hardware/dma.h>
@@ -18,15 +19,6 @@
namespace esphome::rp2040_pio_led_strip { namespace esphome::rp2040_pio_led_strip {
enum RGBOrder : uint8_t {
ORDER_RGB,
ORDER_RBG,
ORDER_GRB,
ORDER_GBR,
ORDER_BGR,
ORDER_BRG,
};
enum Chipset : uint8_t { enum Chipset : uint8_t {
CHIPSET_WS2812, CHIPSET_WS2812,
CHIPSET_WS2812B, CHIPSET_WS2812B,
@@ -36,25 +28,6 @@ enum Chipset : uint8_t {
CHIPSET_CUSTOM = 0xFF, CHIPSET_CUSTOM = 0xFF,
}; };
inline const char *rgb_order_to_string(RGBOrder order) {
switch (order) {
case ORDER_RGB:
return "RGB";
case ORDER_RBG:
return "RBG";
case ORDER_GRB:
return "GRB";
case ORDER_GBR:
return "GBR";
case ORDER_BGR:
return "BGR";
case ORDER_BRG:
return "BRG";
default:
return "UNKNOWN";
}
}
using init_fn = void (*)(PIO pio, uint sm, uint offset, uint pin, float freq); using init_fn = void (*)(PIO pio, uint sm, uint offset, uint pin, float freq);
class RP2040PIOLEDStripLightOutput final : public light::AddressableLight { class RP2040PIOLEDStripLightOutput final : public light::AddressableLight {
@@ -66,13 +39,14 @@ class RP2040PIOLEDStripLightOutput final : public light::AddressableLight {
int32_t size() const override { return this->num_leds_; } int32_t size() const override { return this->num_leds_; }
light::LightTraits get_traits() override { light::LightTraits get_traits() override {
auto traits = light::LightTraits(); auto traits = light::LightTraits();
this->is_rgbw_ ? traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}) this->channel_colors_.has_white()
: traits.set_supported_color_modes({light::ColorMode::RGB}); ? traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE})
: traits.set_supported_color_modes({light::ColorMode::RGB});
return traits; return traits;
} }
void set_pin(uint8_t pin) { this->pin_ = pin; } void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_num_leds(uint32_t num_leds) { this->num_leds_ = num_leds; } void set_num_leds(uint32_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } void set_channel_colors(light::ChannelColors channel_colors) { this->channel_colors_ = channel_colors; }
void set_max_refresh_rate(float interval_us) { this->max_refresh_rate_ = interval_us; } void set_max_refresh_rate(float interval_us) { this->max_refresh_rate_ = interval_us; }
@@ -81,7 +55,6 @@ class RP2040PIOLEDStripLightOutput final : public light::AddressableLight {
void set_init_function(init_fn init) { this->init_ = init; } void set_init_function(init_fn init) { this->init_ = init; }
void set_chipset(Chipset chipset) { this->chipset_ = chipset; }; void set_chipset(Chipset chipset) { this->chipset_ = chipset; };
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
void clear_effect_data() override { void clear_effect_data() override {
for (int i = 0; i < this->size(); i++) { for (int i = 0; i < this->size(); i++) {
this->effect_data_[i] = 0; this->effect_data_[i] = 0;
@@ -93,7 +66,7 @@ class RP2040PIOLEDStripLightOutput final : public light::AddressableLight {
protected: protected:
light::ESPColorView get_view_internal(int32_t index) const override; light::ESPColorView get_view_internal(int32_t index) const override;
size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); } size_t get_buffer_size_() const { return this->num_leds_ * this->channel_colors_.bytes_per_led(); }
static void dma_write_complete_handler(); static void dma_write_complete_handler();
@@ -102,14 +75,13 @@ class RP2040PIOLEDStripLightOutput final : public light::AddressableLight {
uint8_t pin_; uint8_t pin_;
uint32_t num_leds_; uint32_t num_leds_;
bool is_rgbw_;
pio_hw_t *pio_; pio_hw_t *pio_;
uint sm_; uint sm_;
uint dma_chan_; uint dma_chan_;
dma_channel_config dma_config_; dma_channel_config dma_config_;
RGBOrder rgb_order_{ORDER_RGB}; light::ChannelColors channel_colors_{0, 1, 2, light::ChannelColors::NO_WHITE};
Chipset chipset_{CHIPSET_CUSTOM}; Chipset chipset_{CHIPSET_CUSTOM};
uint32_t last_refresh_{0}; uint32_t last_refresh_{0};
@@ -3,6 +3,7 @@ from dataclasses import dataclass
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import light, rp2 from esphome.components import light, rp2
from esphome.components.const import CONF_CHANNEL_COLORS
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CHIPSET, CONF_CHIPSET,
@@ -13,6 +14,7 @@ from esphome.const import (
CONF_PIN, CONF_PIN,
CONF_RGB_ORDER, CONF_RGB_ORDER,
) )
from esphome.types import ConfigType
from esphome.util import _LOGGER from esphome.util import _LOGGER
@@ -37,7 +39,7 @@ def get_nops(timing):
return nops return nops
def generate_assembly_code(id, rgbw, t0h, t0l, t1h, t1l): def generate_assembly_code(id, t0h, t0l, t1h, t1l):
""" """
Generate assembly code with the given timing values. Generate assembly code with the given timing values.
""" """
@@ -139,8 +141,6 @@ RP2040PIOLEDStripLightOutput = rp2040_pio_led_strip_ns.class_(
"RP2040PIOLEDStripLightOutput", light.AddressableLight "RP2040PIOLEDStripLightOutput", light.AddressableLight
) )
RGBOrder = rp2040_pio_led_strip_ns.enum("RGBOrder")
Chipset = rp2040_pio_led_strip_ns.enum("Chipset") Chipset = rp2040_pio_led_strip_ns.enum("Chipset")
CHIPSETS = { CHIPSETS = {
@@ -159,15 +159,6 @@ class LEDStripTimings:
T1L: int T1L: int
RGB_ORDERS = {
"RGB": RGBOrder.ORDER_RGB,
"RBG": RGBOrder.ORDER_RBG,
"GRB": RGBOrder.ORDER_GRB,
"GBR": RGBOrder.ORDER_GBR,
"BGR": RGBOrder.ORDER_BGR,
"BRG": RGBOrder.ORDER_BRG,
}
CHIPSET_TIMINGS = { CHIPSET_TIMINGS = {
"WS2812": LEDStripTimings(20, 40, 46, 34), "WS2812": LEDStripTimings(20, 40, 46, 34),
"WS2812B": LEDStripTimings(23, 49, 46, 26), "WS2812B": LEDStripTimings(23, 49, 46, 26),
@@ -199,10 +190,12 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RP2040PIOLEDStripLightOutput), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RP2040PIOLEDStripLightOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.Optional(CONF_CHANNEL_COLORS): light.validate_channel_colors,
# Deprecated in favour of CONF_CHANNEL_COLORS, remove in 2027.3.0
cv.Optional(CONF_RGB_ORDER): cv.one_of(*light.RGB_ORDERS, upper=True),
cv.Optional(CONF_IS_RGBW): cv.boolean,
cv.Required(CONF_PIO): cv.one_of(0, 1, int=True), cv.Required(CONF_PIO): cv.one_of(0, 1, int=True),
cv.Optional(CONF_CHIPSET): cv.enum(CHIPSETS, upper=True), cv.Optional(CONF_CHIPSET): cv.enum(CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Inclusive( cv.Inclusive(
CONF_BIT0_HIGH, CONF_BIT0_HIGH,
"custom", "custom",
@@ -222,10 +215,13 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
light.migrate_channel_colors(
removed_in="2027.3.0", component="rp2040_pio_led_strip"
),
) )
async def to_code(config): async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
id = config[CONF_ID].id id = config[CONF_ID].id
await light.register_light(var, config) await light.register_light(var, config)
@@ -234,8 +230,9 @@ async def to_code(config):
cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
cg.add(var.set_pin(config[CONF_PIN])) cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) cg.add(
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) var.set_channel_colors(light.channel_colors_struct(config[CONF_CHANNEL_COLORS]))
)
cg.add(var.set_pio(config[CONF_PIO])) cg.add(var.set_pio(config[CONF_PIO]))
cg.add(var.set_program(cg.RawExpression(f"&rp2040_pio_led_strip_{id}_program"))) cg.add(var.set_program(cg.RawExpression(f"&rp2040_pio_led_strip_{id}_program")))
@@ -255,7 +252,6 @@ async def to_code(config):
key, key,
generate_assembly_code( generate_assembly_code(
id, id,
config[CONF_IS_RGBW],
CHIPSET_TIMINGS[chipset].T0H, CHIPSET_TIMINGS[chipset].T0H,
CHIPSET_TIMINGS[chipset].T0L, CHIPSET_TIMINGS[chipset].T0L,
CHIPSET_TIMINGS[chipset].T1H, CHIPSET_TIMINGS[chipset].T1H,
@@ -270,7 +266,6 @@ async def to_code(config):
key, key,
generate_assembly_code( generate_assembly_code(
id, id,
config[CONF_IS_RGBW],
time_to_cycles(config[CONF_BIT0_HIGH]), time_to_cycles(config[CONF_BIT0_HIGH]),
time_to_cycles(config[CONF_BIT0_LOW]), time_to_cycles(config[CONF_BIT0_LOW]),
time_to_cycles(config[CONF_BIT1_HIGH]), time_to_cycles(config[CONF_BIT1_HIGH]),
@@ -3,7 +3,7 @@ light:
id: led_matrix_32x8 id: led_matrix_32x8
default_transition_length: 500ms default_transition_length: 500ms
chipset: ws2812 chipset: ws2812
rgb_order: GRB channel_colors: GRB
num_leds: 256 num_leds: 256
pin: ${pin} pin: ${pin}
@@ -3,7 +3,7 @@ light:
id: led_matrix_32x8 id: led_matrix_32x8
default_transition_length: 500ms default_transition_length: 500ms
chipset: ws2812 chipset: ws2812
rgb_order: GRB channel_colors: GRB
num_leds: 256 num_leds: 256
pin: ${pin} pin: ${pin}
@@ -1,6 +1,6 @@
light: light:
- platform: beken_spi_led_strip - platform: beken_spi_led_strip
rgb_order: GRB channel_colors: GRB
pin: P16 pin: P16
num_leds: 30 num_leds: 30
chipset: ws2812 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 id: led_matrix_32x8
default_transition_length: 500ms default_transition_length: 500ms
chipset: ws2812 chipset: ws2812
rgb_order: GRB channel_colors: GRB
num_leds: 256 num_leds: 256
pin: ${pin} pin: ${pin}
effects: effects:
+1 -1
View File
@@ -5,7 +5,7 @@ light:
id: led_matrix_32x8 id: led_matrix_32x8
default_transition_length: 500ms default_transition_length: 500ms
chipset: ws2812 chipset: ws2812
rgb_order: GRB channel_colors: GRB
num_leds: 256 num_leds: 256
pin: ${pin} pin: ${pin}
effects: effects:
+1 -1
View File
@@ -6,7 +6,7 @@ light:
pin: 2 pin: 2
pio: 0 pio: 0
num_leds: 256 num_leds: 256
rgb_order: GRB channel_colors: GRB
chipset: WS2812 chipset: WS2812
effects: effects:
- e131: - e131:
@@ -3,13 +3,13 @@ light:
id: led_strip1 id: led_strip1
pin: ${pin1} pin: ${pin1}
num_leds: 60 num_leds: 60
rgb_order: GRB channel_colors: GRB
chipset: ws2812 chipset: ws2812
- platform: esp32_rmt_led_strip - platform: esp32_rmt_led_strip
id: led_strip2 id: led_strip2
pin: ${pin2} pin: ${pin2}
num_leds: 60 num_leds: 60
rgbw_order: RWGB channel_colors: RWGB
bit0_high: 100us bit0_high: 100us
bit0_low: 100us bit0_low: 100us
bit1_high: 100us bit1_high: 100us
@@ -8,14 +8,14 @@ light:
id: led_strip1 id: led_strip1
pin: ${pin1} pin: ${pin1}
num_leds: 60 num_leds: 60
rgb_order: GRB channel_colors: GRB
chipset: ws2812 chipset: ws2812
use_dma: "true" use_dma: "true"
- platform: esp32_rmt_led_strip - platform: esp32_rmt_led_strip
id: led_strip2 id: led_strip2
pin: ${pin2} pin: ${pin2}
num_leds: 60 num_leds: 60
rgb_order: RGB channel_colors: RGB
bit0_high: 100us bit0_high: 100us
bit0_low: 100us bit0_low: 100us
bit1_high: 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 default_transition_length: 500ms
chipset: ws2812 chipset: ws2812
num_leds: 256 num_leds: 256
rgb_order: GRB channel_colors: GRB
pin: ${pin} pin: ${pin}
- platform: partition - platform: partition
name: Partition Light name: Partition Light
+1 -1
View File
@@ -4,7 +4,7 @@ light:
default_transition_length: 500ms default_transition_length: 500ms
chipset: ws2812 chipset: ws2812
num_leds: 256 num_leds: 256
rgb_order: GRB channel_colors: GRB
pin: ${pin} pin: ${pin}
- platform: partition - platform: partition
name: Partition Light name: Partition Light
@@ -4,14 +4,14 @@ light:
pin: 4 pin: 4
num_leds: 60 num_leds: 60
pio: 0 pio: 0
rgb_order: GRB channel_colors: GRB
chipset: WS2812 chipset: WS2812
- platform: rp2040_pio_led_strip - platform: rp2040_pio_led_strip
id: led_strip_custom_timings id: led_strip_custom_timings
pin: 5 pin: 5
num_leds: 60 num_leds: 60
pio: 1 pio: 1
rgb_order: GRB channel_colors: GRB
bit0_high: .1us bit0_high: .1us
bit0_low: 1.2us bit0_low: 1.2us
bit1_high: .69us 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 id: led_matrix_32x8
default_transition_length: 500ms default_transition_length: 500ms
chipset: ws2812 chipset: ws2812
rgb_order: GRB channel_colors: GRB
num_leds: 256 num_leds: 256
pin: 2 pin: 2
effects: 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