diff --git a/README.md b/README.md index 8f4030c..7e86136 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ The component exposes an RGB light and supports all - **chipset** (*Optional*, string): `APA102` or `WS2801`. Defaults to `APA102`. - **num_leds** (*Optional*, int): Number of LEDs in the strip. Defaults to `1`. +- **rgb_order** (*Optional*, string): The order in which the color bytes of each LED are sent. + One of `RGB`, `RBG`, `GRB`, `GBR`, `BGR`, `BRG`. Defaults to `BGR` for `APA102` and `RGB` for `WS2801`. + Change this if the colors on your strip come out swapped. - **data_rate** (*Optional*): SPI clock frequency. Defaults to `1MHz`. - **spi_id** (*Optional*, ID): The SPI bus to use, if more than one is configured. - All other options from [Light](https://esphome.io/components/light/#config-light). @@ -69,6 +72,7 @@ light: name: "WS2801 Strip" chipset: WS2801 num_leds: 32 + rgb_order: RGB data_rate: 1MHz effects: - addressable_rainbow: diff --git a/components/spi_led_strip/light.py b/components/spi_led_strip/light.py index ea2f2d0..dc6cc9d 100644 --- a/components/spi_led_strip/light.py +++ b/components/spi_led_strip/light.py @@ -1,7 +1,12 @@ import esphome.codegen as cg from esphome.components import light, spi import esphome.config_validation as cv -from esphome.const import CONF_CHIPSET, CONF_NUM_LEDS, CONF_OUTPUT_ID +from esphome.const import ( + CONF_CHIPSET, + CONF_NUM_LEDS, + CONF_OUTPUT_ID, + CONF_RGB_ORDER, +) from esphome.types import ConfigType spi_led_strip_ns = cg.esphome_ns.namespace("spi_led_strip") @@ -9,6 +14,7 @@ SpiLedStrip = spi_led_strip_ns.class_( "SpiLedStrip", light.AddressableLight, spi.SPIDevice ) SpiLedChipset = spi_led_strip_ns.enum("SpiLedChipset", is_class=True) +RGBOrder = spi_led_strip_ns.enum("RGBOrder", is_class=True) CHIPSET_APA102 = "APA102" CHIPSET_WS2801 = "WS2801" @@ -18,6 +24,15 @@ CHIPSETS = { CHIPSET_WS2801: SpiLedChipset.SPI_LED_CHIPSET_WS2801, } +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, +} + BASE_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpiLedStrip), @@ -25,11 +40,24 @@ BASE_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( } ) + +def rgb_order_schema(default: str) -> cv.Schema: + return cv.Schema( + { + cv.Optional(CONF_RGB_ORDER, default=default): cv.enum( + RGB_ORDERS, upper=True + ), + } + ) + + CONFIG_SCHEMA = cv.typed_schema( { - CHIPSET_APA102: BASE_SCHEMA.extend(spi.spi_device_schema(False, "1MHz")), + CHIPSET_APA102: BASE_SCHEMA.extend(rgb_order_schema("BGR")).extend( + spi.spi_device_schema(False, "1MHz") + ), # WS2801 latches the data when the clock stays low, so the clock must idle low - CHIPSET_WS2801: BASE_SCHEMA.extend( + CHIPSET_WS2801: BASE_SCHEMA.extend(rgb_order_schema("RGB")).extend( spi.spi_device_schema(False, "1MHz", "MODE0") ), }, @@ -45,6 +73,7 @@ async def to_code(config: ConfigType) -> None: if chipset == CHIPSET_WS2801: cg.add_define("USE_SPI_LED_STRIP_WS2801") var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_NUM_LEDS], chipset) + cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) await light.register_light(var, config) await spi.register_spi_device(var, config) await cg.register_component(var, config) diff --git a/components/spi_led_strip/spi_led_strip.cpp b/components/spi_led_strip/spi_led_strip.cpp index 13c3570..073a8da 100644 --- a/components/spi_led_strip/spi_led_strip.cpp +++ b/components/spi_led_strip/spi_led_strip.cpp @@ -3,6 +3,25 @@ namespace esphome::spi_led_strip { +static const LogString *rgb_order_to_string(RGBOrder rgb_order) { + switch (rgb_order) { + case RGBOrder::ORDER_RGB: + return LOG_STR("RGB"); + case RGBOrder::ORDER_RBG: + return LOG_STR("RBG"); + case RGBOrder::ORDER_GRB: + return LOG_STR("GRB"); + case RGBOrder::ORDER_GBR: + return LOG_STR("GBR"); + case RGBOrder::ORDER_BGR: + return LOG_STR("BGR"); + case RGBOrder::ORDER_BRG: + return LOG_STR("BRG"); + default: + return LOG_STR("UNKNOWN"); + } +} + SpiLedStrip::SpiLedStrip(uint16_t num_leds, SpiLedChipset chipset) : num_leds_(num_leds), chipset_(chipset) { RAMAllocator allocator; #ifdef USE_SPI_LED_STRIP_WS2801 @@ -50,9 +69,10 @@ void SpiLedStrip::dump_config() { TAG, "SPI LED Strip:\n" " Chipset: %s\n" - " LEDs: %d", + " LEDs: %d\n" + " RGB order: %s", this->chipset_ == SpiLedChipset::SPI_LED_CHIPSET_WS2801 ? LOG_STR_LITERAL("WS2801") : LOG_STR_LITERAL("APA102"), - this->num_leds_); + this->num_leds_, LOG_STR_ARG(rgb_order_to_string(this->rgb_order_))); if (this->data_rate_ >= spi::DATA_RATE_1MHZ) { esph_log_config(TAG, " Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000)); } else { @@ -75,15 +95,38 @@ void SpiLedStrip::write_state(light::LightState *state) { this->disable(); } light::ESPColorView SpiLedStrip::get_view_internal(int32_t index) const { + // byte offsets of red, green and blue within the color bytes of one LED + uint8_t r, g, b; + switch (this->rgb_order_) { + case RGBOrder::ORDER_RGB: + r = 0, g = 1, b = 2; + break; + case RGBOrder::ORDER_RBG: + r = 0, g = 2, b = 1; + break; + case RGBOrder::ORDER_GRB: + r = 1, g = 0, b = 2; + break; + case RGBOrder::ORDER_GBR: + r = 2, g = 0, b = 1; + break; + case RGBOrder::ORDER_BRG: + r = 1, g = 2, b = 0; + break; + case RGBOrder::ORDER_BGR: + default: + r = 2, g = 1, b = 0; + break; + } #ifdef USE_SPI_LED_STRIP_WS2801 if (this->chipset_ == SpiLedChipset::SPI_LED_CHIPSET_WS2801) { size_t pos = index * 3; - return {this->buf_ + pos + 0, this->buf_ + pos + 1, this->buf_ + pos + 2, nullptr, + return {this->buf_ + pos + r, this->buf_ + pos + g, this->buf_ + pos + b, nullptr, this->effect_data_ + index, &this->correction_}; } #endif size_t pos = index * 4 + 5; - return {this->buf_ + pos + 2, this->buf_ + pos + 1, this->buf_ + pos + 0, nullptr, + return {this->buf_ + pos + r, this->buf_ + pos + g, this->buf_ + pos + b, nullptr, this->effect_data_ + index, &this->correction_}; } } // namespace esphome::spi_led_strip diff --git a/components/spi_led_strip/spi_led_strip.h b/components/spi_led_strip/spi_led_strip.h index 02df930..8746f2b 100644 --- a/components/spi_led_strip/spi_led_strip.h +++ b/components/spi_led_strip/spi_led_strip.h @@ -16,6 +16,16 @@ enum class SpiLedChipset : uint8_t { SPI_LED_CHIPSET_WS2801, }; +// Order in which the color bytes of one LED are sent over the wire +enum class RGBOrder : uint8_t { + ORDER_RGB, + ORDER_RBG, + ORDER_GRB, + ORDER_GBR, + ORDER_BGR, + ORDER_BRG, +}; + class SpiLedStrip final : public light::AddressableLight, public spi::SPIDevice { @@ -24,6 +34,8 @@ class SpiLedStrip final : public light::AddressableLight, void setup() override; float get_setup_priority() const override { return setup_priority::IO; } + void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } + int32_t size() const override { return this->num_leds_; } light::LightTraits get_traits() override; @@ -42,6 +54,7 @@ class SpiLedStrip final : public light::AddressableLight, uint8_t *buf_{nullptr}; uint16_t num_leds_; SpiLedChipset chipset_; + RGBOrder rgb_order_{RGBOrder::ORDER_BGR}; }; } // namespace esphome::spi_led_strip