mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add Adalight support (#956)
A component to support [Adalight](https://learn.adafruit.com/adalight-diy-ambient-tv-lighting). This allows to control addressable LEDs over UART, by pushing data right into LEDs. The most useful to use [Prismatik](https://github.com/psieg/Lightpack) to create an immersive effect on PC. Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
		
							
								
								
									
										24
									
								
								esphome/components/adalight/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/adalight/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import uart | ||||||
|  | from esphome.components.light.types import AddressableLightEffect | ||||||
|  | from esphome.components.light.effects import register_addressable_effect | ||||||
|  | from esphome.const import CONF_NAME, CONF_UART_ID | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ['uart'] | ||||||
|  |  | ||||||
|  | adalight_ns = cg.esphome_ns.namespace('adalight') | ||||||
|  | AdalightLightEffect = adalight_ns.class_( | ||||||
|  |     'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema({}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_addressable_effect('adalight', AdalightLightEffect, "Adalight", { | ||||||
|  |     cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent) | ||||||
|  | }) | ||||||
|  | def adalight_light_effect_to_code(config, effect_id): | ||||||
|  |     effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) | ||||||
|  |     yield uart.register_uart_device(effect, config) | ||||||
|  |  | ||||||
|  |     yield effect | ||||||
							
								
								
									
										140
									
								
								esphome/components/adalight/adalight_light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								esphome/components/adalight/adalight_light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | #include "adalight_light_effect.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adalight { | ||||||
|  |  | ||||||
|  | static const char *TAG = "adalight_light_effect"; | ||||||
|  |  | ||||||
|  | static const uint32_t ADALIGHT_ACK_INTERVAL = 1000; | ||||||
|  | static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000; | ||||||
|  |  | ||||||
|  | AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {} | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::start() { | ||||||
|  |   AddressableLightEffect::start(); | ||||||
|  |  | ||||||
|  |   last_ack_ = 0; | ||||||
|  |   last_byte_ = 0; | ||||||
|  |   last_reset_ = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::stop() { | ||||||
|  |   frame_.resize(0); | ||||||
|  |  | ||||||
|  |   AddressableLightEffect::stop(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int AdalightLightEffect::get_frame_size_(int led_count) const { | ||||||
|  |   // 3 bytes: Ada | ||||||
|  |   // 2 bytes: LED count | ||||||
|  |   // 1 byte: checksum | ||||||
|  |   // 3 bytes per LED | ||||||
|  |   return 3 + 2 + 1 + led_count * 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::reset_frame_(light::AddressableLight &it) { | ||||||
|  |   int buffer_capacity = get_frame_size_(it.size()); | ||||||
|  |  | ||||||
|  |   frame_.clear(); | ||||||
|  |   frame_.reserve(buffer_capacity); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { | ||||||
|  |   for (int led = it.size(); led-- > 0;) { | ||||||
|  |     it[led].set(light::ESPColor::BLACK); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { | ||||||
|  |   const uint32_t now = millis(); | ||||||
|  |  | ||||||
|  |   if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) { | ||||||
|  |     ESP_LOGV(TAG, "Sending ACK"); | ||||||
|  |     this->write_str("Ada\n"); | ||||||
|  |     this->last_ack_ = now; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->last_reset_) { | ||||||
|  |     ESP_LOGW(TAG, "Frame: Reset."); | ||||||
|  |     reset_frame_(it); | ||||||
|  |     blank_all_leds_(it); | ||||||
|  |     this->last_reset_ = now; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->frame_.empty() && now - this->last_byte_ >= ADALIGHT_RECEIVE_TIMEOUT) { | ||||||
|  |     ESP_LOGW(TAG, "Frame: Receive timeout (size=%zu).", this->frame_.size()); | ||||||
|  |     reset_frame_(it); | ||||||
|  |     blank_all_leds_(it); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->available() > 0) { | ||||||
|  |     ESP_LOGV(TAG, "Frame: Available (size=%d).", this->available()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   while (this->available() != 0) { | ||||||
|  |     uint8_t data; | ||||||
|  |     if (!this->read_byte(&data)) | ||||||
|  |       break; | ||||||
|  |     this->frame_.push_back(data); | ||||||
|  |     this->last_byte_ = now; | ||||||
|  |  | ||||||
|  |     switch (this->parse_frame_(it)) { | ||||||
|  |       case INVALID: | ||||||
|  |         ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=%d).", this->frame_.size(), this->frame_[0]); | ||||||
|  |         reset_frame_(it); | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case PARTIAL: | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case CONSUMED: | ||||||
|  |         ESP_LOGV(TAG, "Frame: Consumed (size=%zu).", this->frame_.size()); | ||||||
|  |         reset_frame_(it); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableLight &it) { | ||||||
|  |   if (frame_.empty()) | ||||||
|  |     return INVALID; | ||||||
|  |  | ||||||
|  |   // Check header: `Ada` | ||||||
|  |   if (frame_[0] != 'A') | ||||||
|  |     return INVALID; | ||||||
|  |   if (frame_.size() > 1 && frame_[1] != 'd') | ||||||
|  |     return INVALID; | ||||||
|  |   if (frame_.size() > 2 && frame_[2] != 'a') | ||||||
|  |     return INVALID; | ||||||
|  |  | ||||||
|  |   // 3 bytes: Count Hi, Count Lo, Checksum | ||||||
|  |   if (frame_.size() < 6) | ||||||
|  |     return PARTIAL; | ||||||
|  |  | ||||||
|  |   // Check checksum | ||||||
|  |   uint16_t checksum = frame_[3] ^ frame_[4] ^ 0x55; | ||||||
|  |   if (checksum != frame_[5]) | ||||||
|  |     return INVALID; | ||||||
|  |  | ||||||
|  |   // Check if we received the full frame | ||||||
|  |   uint16_t led_count = (frame_[3] << 8) + frame_[4] + 1; | ||||||
|  |   auto buffer_size = get_frame_size_(led_count); | ||||||
|  |   if (frame_.size() < buffer_size) | ||||||
|  |     return PARTIAL; | ||||||
|  |  | ||||||
|  |   // Apply lights | ||||||
|  |   auto accepted_led_count = std::min<int>(led_count, it.size()); | ||||||
|  |   uint8_t *led_data = &frame_[6]; | ||||||
|  |  | ||||||
|  |   for (int led = 0; led < accepted_led_count; led++, led_data += 3) { | ||||||
|  |     auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]); | ||||||
|  |  | ||||||
|  |     it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return CONSUMED; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace adalight | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										41
									
								
								esphome/components/adalight/adalight_light_effect.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/adalight/adalight_light_effect.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/light/addressable_light_effect.h" | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adalight { | ||||||
|  |  | ||||||
|  | class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice { | ||||||
|  |  public: | ||||||
|  |   AdalightLightEffect(const std::string &name); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   void start() override; | ||||||
|  |   void stop() override; | ||||||
|  |   void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   enum Frame { | ||||||
|  |     INVALID, | ||||||
|  |     PARTIAL, | ||||||
|  |     CONSUMED, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   int get_frame_size_(int led_count) const; | ||||||
|  |   void reset_frame_(light::AddressableLight &it); | ||||||
|  |   void blank_all_leds_(light::AddressableLight &it); | ||||||
|  |   Frame parse_frame_(light::AddressableLight &it); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint32_t last_ack_{0}; | ||||||
|  |   uint32_t last_byte_{0}; | ||||||
|  |   uint32_t last_reset_{0}; | ||||||
|  |   std::vector<uint8_t> frame_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace adalight | ||||||
|  | }  // namespace esphome | ||||||
| @@ -128,14 +128,20 @@ spi: | |||||||
|   miso_pin: GPIO23 |   miso_pin: GPIO23 | ||||||
|  |  | ||||||
| uart: | uart: | ||||||
|   tx_pin: GPIO22 |   - tx_pin: GPIO22 | ||||||
|   rx_pin: GPIO23 |     rx_pin: GPIO23 | ||||||
|   baud_rate: 115200 |     baud_rate: 115200 | ||||||
|   id: uart0 |     id: uart0 | ||||||
|   parity: NONE |     parity: NONE | ||||||
|   data_bits: 8 |     data_bits: 8 | ||||||
|   stop_bits: 1 |     stop_bits: 1 | ||||||
|   rx_buffer_size: 512 |     rx_buffer_size: 512 | ||||||
|  |  | ||||||
|  |   - id: adalight_uart | ||||||
|  |     tx_pin: GPIO25 | ||||||
|  |     rx_pin: GPIO26 | ||||||
|  |     baud_rate: 115200 | ||||||
|  |     rx_buffer_size: 1024 | ||||||
|  |  | ||||||
| ota: | ota: | ||||||
|   safe_mode: True |   safe_mode: True | ||||||
| @@ -179,6 +185,8 @@ as3935_spi: | |||||||
|   cs_pin: GPIO12 |   cs_pin: GPIO12 | ||||||
|   irq_pin: GPIO13 |   irq_pin: GPIO13 | ||||||
|  |  | ||||||
|  | adalight: | ||||||
|  |  | ||||||
| sensor: | sensor: | ||||||
|   - platform: adc |   - platform: adc | ||||||
|     pin: A0 |     pin: A0 | ||||||
| @@ -1177,6 +1185,8 @@ light: | |||||||
|             if (initial_run) { |             if (initial_run) { | ||||||
|               it[0] = current_color; |               it[0] = current_color; | ||||||
|             } |             } | ||||||
|  |     - adalight: | ||||||
|  |         uart_id: adalight_uart | ||||||
|     - automation: |     - automation: | ||||||
|         name: Custom Effect |         name: Custom Effect | ||||||
|         sequence: |         sequence: | ||||||
|   | |||||||
| @@ -183,9 +183,13 @@ spi: | |||||||
|   miso_pin: GPIO14 |   miso_pin: GPIO14 | ||||||
|  |  | ||||||
| uart: | uart: | ||||||
|   tx_pin: GPIO1 |   - tx_pin: GPIO1 | ||||||
|   rx_pin: GPIO3 |     rx_pin: GPIO3 | ||||||
|   baud_rate: 115200 |     baud_rate: 115200 | ||||||
|  |  | ||||||
|  |   - id: adalight_uart | ||||||
|  |     rx_pin: GPIO3 | ||||||
|  |     baud_rate: 115200 | ||||||
|  |  | ||||||
| ota: | ota: | ||||||
|   safe_mode: True |   safe_mode: True | ||||||
| @@ -203,6 +207,8 @@ deep_sleep: | |||||||
|   run_duration: 20s |   run_duration: 20s | ||||||
|   sleep_duration: 50s |   sleep_duration: 50s | ||||||
|  |  | ||||||
|  | adalight: | ||||||
|  |  | ||||||
| sensor: | sensor: | ||||||
|   - platform: apds9960 |   - platform: apds9960 | ||||||
|     type: proximity |     type: proximity | ||||||
| @@ -708,6 +714,8 @@ light: | |||||||
|     method: ESP8266_UART0 |     method: ESP8266_UART0 | ||||||
|     num_leds: 100 |     num_leds: 100 | ||||||
|     effects: |     effects: | ||||||
|  |       - adalight: | ||||||
|  |           uart_id: adalight_uart | ||||||
|       - e131: |       - e131: | ||||||
|           universe: 1 |           universe: 1 | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user