diff --git a/CODEOWNERS b/CODEOWNERS index c630db7948..77acedfda0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -127,6 +127,7 @@ esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb esphome/components/ezo_pmp/* @carlos-sarmiento esphome/components/factory_reset/* @anatoly-savchenkov +esphome/components/fastled/light/* @NielsNL68 @OttoWinter esphome/components/fastled_base/* @OttoWinter esphome/components/feedback/* @ianchi esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh diff --git a/esphome/components/fastled/__init__.py b/esphome/components/fastled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/fastled/light/__init__.py b/esphome/components/fastled/light/__init__.py new file mode 100644 index 0000000000..57b28034f2 --- /dev/null +++ b/esphome/components/fastled/light/__init__.py @@ -0,0 +1,153 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import light +from esphome.const import ( + CONF_CHIPSET, + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_DATA_RATE, + CONF_PIN, + CONF_RGB_ORDER, + CONF_OUTPUT_ID, + CONF_NUM_LEDS, + CONF_MAX_REFRESH_RATE, +) + +CODEOWNERS = ["@OttoWinter", "@NielsNL68"] + +fastled_base_ns = cg.esphome_ns.namespace("fastled") +FastLEDLightOutput = fastled_base_ns.class_( + "FastLEDLightOutput", light.AddressableLight +) + +RGB_ORDERS = [ + "RGB", + "RBG", + "GRB", + "GBR", + "BRG", + "BGR", +] + +SPI_CHIPSETS = [ + "LPD6803", + "LPD8806", + "WS2801", + "WS2803", + "SM16716", + "P9813", + "APA102", + "SK9822", + "DOTSTAR", +] + +CLOCKLESS_CHIPSETS = [ + "NEOPIXEL", + "TM1829", + "TM1809", + "TM1804", + "TM1803", + "UCS1903", + "UCS1903B", + "UCS1904", + "UCS2903", + "WS2812", + "WS2852", + "WS2812B", + "SK6812", + "SK6822", + "APA106", + "PL9823", + "WS2811", + "WS2813", + "APA104", + "WS2811_400", + "GW6205", + "GW6205_400", + "LPD1886", + "LPD1886_8BIT", + "SM16703", +] + +CHIPSETS = CLOCKLESS_CHIPSETS + SPI_CHIPSETS + + +def _validate(config): + if config[CONF_CHIPSET] == "NEOPIXEL" and CONF_RGB_ORDER in config: + raise cv.Invalid("NEOPIXEL doesn't support RGB order") + if config[CONF_CHIPSET] in SPI_CHIPSETS and config[CONF_CLOCK_PIN] == -1: + raise cv.Invalid("The clock_pin is required for SPI devices.") + + return config + + +def validate_gpio_output_pin_number(value): + if value == -1: + return value + return pins.internal_gpio_output_pin_number(value) + + +CONFIG_SCHEMA = cv.All( + light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(FastLEDLightOutput), + cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), + cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, + cv.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True), + cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, + cv.Optional(CONF_DATA_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_PIN): cv.invalid("This pin is renamed to 'data_pin'"), + cv.Optional(CONF_CLOCK_PIN, default=-1): validate_gpio_output_pin_number, + cv.Optional(CONF_DATA_RATE): cv.frequency, + } + ).extend(cv.COMPONENT_SCHEMA), + _validate, + cv.only_with_arduino, +) + + +async def new_fastled_light(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await cg.register_component(var, config) + + if CONF_MAX_REFRESH_RATE in config: + cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) + + await light.register_light(var, config) + cg.add_library("fastled/FastLED", "3.6.0") + return var + + +async def to_code(config): + var = await new_fastled_light(config) + + rgb_order = cg.RawExpression( + config[CONF_RGB_ORDER] if CONF_RGB_ORDER in config else None + ) + rgb_order = None + if CONF_RGB_ORDER in config: + rgb_order = cg.RawExpression(config[CONF_RGB_ORDER]) + + if config[CONF_CHIPSET] in SPI_CHIPSETS: + if CONF_DATA_RATE in config: + data_rate_khz = int(config[CONF_DATA_RATE] / 1000) + if data_rate_khz < 1000: + data_rate = cg.RawExpression(f"DATA_RATE_KHZ({data_rate_khz})") + else: + data_rate_mhz = int(data_rate_khz / 1000) + data_rate = cg.RawExpression(f"DATA_RATE_MHZ({data_rate_mhz})") + else: + data_rate = None + template_args = cg.TemplateArguments( + cg.RawExpression(config[CONF_CHIPSET]), + config[CONF_DATA_PIN], + config[CONF_CLOCK_PIN], + rgb_order, + data_rate, + ) + else: + template_args = cg.TemplateArguments( + cg.RawExpression(config[CONF_CHIPSET]), config[CONF_DATA_PIN], rgb_order + ) + cg.add(var.add_leds(template_args, config[CONF_NUM_LEDS])) diff --git a/esphome/components/fastled/light/fastled_light.cpp b/esphome/components/fastled/light/fastled_light.cpp new file mode 100644 index 0000000000..6742af6a75 --- /dev/null +++ b/esphome/components/fastled/light/fastled_light.cpp @@ -0,0 +1,43 @@ +#ifdef USE_ARDUINO + +#include "fastled_light.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace fastled { + +static const char *const TAG = "fastled"; + +void FastLEDLightOutput::setup() { + ESP_LOGCONFIG(TAG, "Setting up FastLED light..."); + this->controller_->init(); + this->controller_->setLeds(this->leds_, this->num_leds_); + this->effect_data_ = new uint8_t[this->num_leds_]; // NOLINT + if (!this->max_refresh_rate_.has_value()) { + this->set_max_refresh_rate(this->controller_->getMaxRefreshRate()); + } +} +void FastLEDLightOutput::dump_config() { + ESP_LOGCONFIG(TAG, "FastLED light:"); + ESP_LOGCONFIG(TAG, " Num LEDs: %u", this->num_leds_); + ESP_LOGCONFIG(TAG, " Max refresh rate: %u", *this->max_refresh_rate_); +} +void FastLEDLightOutput::write_state(light::LightState *state) { + // protect from refreshing too often + uint32_t now = micros(); + if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) { + // try again next loop iteration, so that this change won't get lost + this->schedule_show(); + return; + } + this->last_refresh_ = now; + this->mark_shown_(); + + ESP_LOGVV(TAG, "Writing RGB values to bus..."); + this->controller_->showLeds(); +} + +} // namespace fastled +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/fastled/light/fastled_light.h b/esphome/components/fastled/light/fastled_light.h new file mode 100644 index 0000000000..b7fceb6c14 --- /dev/null +++ b/esphome/components/fastled/light/fastled_light.h @@ -0,0 +1,243 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/components/light/addressable_light.h" + +#define FASTLED_ESP8266_RAW_PIN_ORDER +#define FASTLED_ESP32_RAW_PIN_ORDER +#define FASTLED_RMT_BUILTIN_DRIVER true + +// Avoid annoying compiler messages +#define FASTLED_INTERNAL + +#include "FastLED.h" + +namespace esphome { +namespace fastled { + +class FastLEDLightOutput : public light::AddressableLight { + public: + /// Only for custom effects: Get the internal controller. + CLEDController *get_controller() const { return this->controller_; } + + inline int32_t size() const override { return this->num_leds_; } + + /// 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; } + + /// Add some LEDS, can only be called once. + CLEDController &add_leds(CLEDController *controller, int num_leds) { + this->controller_ = controller; + this->num_leds_ = num_leds; + this->leds_ = new CRGB[num_leds]; // NOLINT + + for (int i = 0; i < this->num_leds_; i++) + this->leds_[i] = CRGB::Black; + + return *this->controller_; + } + + template + CLEDController &add_leds(int num_leds) { + switch (CHIPSET) { + case LPD8806: { + static LPD8806Controller controller; + return add_leds(&controller, num_leds); + } + case WS2801: { + static WS2801Controller controller; + return add_leds(&controller, num_leds); + } + case WS2803: { + static WS2803Controller controller; + return add_leds(&controller, num_leds); + } + case SM16716: { + static SM16716Controller controller; + return add_leds(&controller, num_leds); + } + case P9813: { + static P9813Controller controller; + return add_leds(&controller, num_leds); + } + case DOTSTAR: + case APA102: { + static APA102Controller controller; + return add_leds(&controller, num_leds); + } + case SK9822: { + static SK9822Controller controller; + return add_leds(&controller, num_leds); + } + } + } + + template CLEDController &add_leds(int num_leds) { + switch (CHIPSET) { + case LPD8806: { + static LPD8806Controller controller; + return add_leds(&controller, num_leds); + } + case WS2801: { + static WS2801Controller controller; + return add_leds(&controller, num_leds); + } + case WS2803: { + static WS2803Controller controller; + return add_leds(&controller, num_leds); + } + case SM16716: { + static SM16716Controller controller; + return add_leds(&controller, num_leds); + } + case P9813: { + static P9813Controller controller; + return add_leds(&controller, num_leds); + } + case DOTSTAR: + case APA102: { + static APA102Controller controller; + return add_leds(&controller, num_leds); + } + case SK9822: { + static SK9822Controller controller; + return add_leds(&controller, num_leds); + } + } + } + + template + CLEDController &add_leds(int num_leds) { + switch (CHIPSET) { + case LPD8806: { + static LPD8806Controller controller; + return add_leds(&controller, num_leds); + } + case WS2801: { + static WS2801Controller controller; + return add_leds(&controller, num_leds); + } + case WS2803: { + static WS2803Controller controller; + return add_leds(&controller, num_leds); + } + case SM16716: { + static SM16716Controller controller; + return add_leds(&controller, num_leds); + } + case P9813: { + static P9813Controller controller; + return add_leds(&controller, num_leds); + } + case DOTSTAR: + case APA102: { + static APA102Controller controller; + return add_leds(&controller, num_leds); + } + case SK9822: { + static SK9822Controller controller; + return add_leds(&controller, num_leds); + } + } + } + +#ifdef FASTLED_HAS_CLOCKLESS + template class CHIPSET, uint8_t DATA_PIN, EOrder RGB_ORDER> + CLEDController &add_leds(int num_leds) { + static CHIPSET controller; + return add_leds(&controller, num_leds); + } + + template class CHIPSET, uint8_t DATA_PIN> + CLEDController &add_leds(int num_leds) { + static CHIPSET controller; + return add_leds(&controller, num_leds); + } + + template class CHIPSET, uint8_t DATA_PIN> CLEDController &add_leds(int num_leds) { + static CHIPSET controller; + return add_leds(&controller, num_leds); + } +#endif + + template class CHIPSET, EOrder RGB_ORDER> CLEDController &add_leds(int num_leds) { + static CHIPSET controller; + return add_leds(&controller, num_leds); + } + + template class CHIPSET> CLEDController &add_leds(int num_leds) { + static CHIPSET controller; + return add_leds(&controller, num_leds); + } + +#ifdef FASTLED_HAS_BLOCKLESS + template CLEDController &add_leds(int num_leds) { + switch (CHIPSET) { +#ifdef PORTA_FIRST_PIN + case WS2811_PORTA: + return add_leds( + new InlineBlockClocklessController(), + num_leds); + case WS2811_400_PORTA: + return add_leds( + new InlineBlockClocklessController(), + num_leds); + case WS2813_PORTA: + return add_leds(new InlineBlockClocklessController(), + num_leds); + case TM1803_PORTA: + return add_leds( + new InlineBlockClocklessController(), + num_leds); + case UCS1903_PORTA: + return add_leds( + new InlineBlockClocklessController(), + num_leds); +#endif + } + } + + template CLEDController &add_leds(int num_leds) { + return add_leds(num_leds); + } +#endif + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::RGB}); + return traits; + } + void setup() override; + void dump_config() override; + void write_state(light::LightState *state) override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + void clear_effect_data() override { + for (int i = 0; i < this->size(); i++) + this->effect_data_[i] = 0; + } + + protected: + light::ESPColorView get_view_internal(int32_t index) const override { + return {&this->leds_[index].r, &this->leds_[index].g, &this->leds_[index].b, nullptr, + &this->effect_data_[index], &this->correction_}; + } + + CLEDController *controller_{nullptr}; + CRGB *leds_{nullptr}; + uint8_t *effect_data_{nullptr}; + int num_leds_{0}; + uint32_t last_refresh_{0}; + optional max_refresh_rate_{}; +}; + +} // namespace fastled +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index 62de036e62..5a3cf92e70 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -44,5 +44,5 @@ async def new_fastled_light(config): # https://github.com/FastLED/FastLED/blob/master/library.json # 3.3.3 has an issue on ESP32 with RMT and fastled_clockless: # https://github.com/esphome/issues/issues/1375 - cg.add_library("fastled/FastLED", "3.3.2") + cg.add_library("fastled/FastLED", "3.6.0") return var diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index dc456d4959..bd62121fc5 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -50,7 +50,7 @@ CONFIG_SCHEMA = cv.All( ), _validate, cv.require_framework_version( - esp8266_arduino=cv.Version(2, 7, 4), + esp8266_arduino=cv.Version(99, 7, 4), esp32_arduino=cv.Version(99, 0, 0), max_version=True, extra_message="Please see note on documentation for FastLED", diff --git a/platformio.ini b/platformio.ini index 5fedd14086..8f009b0ac9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -59,7 +59,7 @@ lib_deps = Wire ; i2c (Arduino built-int) heman/AsyncMqttClient-esphome@1.0.0 ; mqtt esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base - fastled/FastLED@3.3.2 ; fastled_base + fastled/FastLED@3.6.0 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 glmnet/Dsmr@0.7 ; dsmr diff --git a/tests/test1.1.yaml b/tests/test1.1.yaml index c71aa6e0ef..928e896b1d 100644 --- a/tests/test1.1.yaml +++ b/tests/test1.1.yaml @@ -85,10 +85,10 @@ light: red: pca_0 green: pca_1 blue: pca_2 - - platform: fastled_clockless + - platform: fastled id: addr1 chipset: WS2811 - pin: + data_pin: allow_other_uses: true number: GPIO23 num_leds: 60 @@ -169,7 +169,7 @@ light: green: 100% blue: 0% - - platform: fastled_spi + - platform: fastled id: addr2 chipset: WS2801 data_pin: diff --git a/tests/test4.yaml b/tests/test4.yaml index 993ce126a8..f9c3221d0e 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -551,11 +551,11 @@ switch: name: Tuya Switch Copy light: - - platform: fastled_clockless + - platform: fastled id: led_matrix_32x8 name: led_matrix_32x8 chipset: WS2812B - pin: + data_pin: allow_other_uses: true number: GPIO15 num_leds: 256