mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[light] Replace rgb_order/is_rgbw/is_wrgb with channel_colors (#18474)
This commit is contained in:
@@ -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_{};
|
||||||
|
|||||||
@@ -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]))
|
)
|
||||||
|
|||||||
@@ -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_{};
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,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
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user