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,7 +128,7 @@ spi: | ||||
|   miso_pin: GPIO23 | ||||
|  | ||||
| uart: | ||||
|   tx_pin: GPIO22 | ||||
|   - tx_pin: GPIO22 | ||||
|     rx_pin: GPIO23 | ||||
|     baud_rate: 115200 | ||||
|     id: uart0 | ||||
| @@ -137,6 +137,12 @@ uart: | ||||
|     stop_bits: 1 | ||||
|     rx_buffer_size: 512 | ||||
|  | ||||
|   - id: adalight_uart | ||||
|     tx_pin: GPIO25 | ||||
|     rx_pin: GPIO26 | ||||
|     baud_rate: 115200 | ||||
|     rx_buffer_size: 1024 | ||||
|  | ||||
| ota: | ||||
|   safe_mode: True | ||||
|   password: 'superlongpasswordthatnoonewillknow' | ||||
| @@ -179,6 +185,8 @@ as3935_spi: | ||||
|   cs_pin: GPIO12 | ||||
|   irq_pin: GPIO13 | ||||
|  | ||||
| adalight: | ||||
|  | ||||
| sensor: | ||||
|   - platform: adc | ||||
|     pin: A0 | ||||
| @@ -1177,6 +1185,8 @@ light: | ||||
|             if (initial_run) { | ||||
|               it[0] = current_color; | ||||
|             } | ||||
|     - adalight: | ||||
|         uart_id: adalight_uart | ||||
|     - automation: | ||||
|         name: Custom Effect | ||||
|         sequence: | ||||
|   | ||||
| @@ -183,7 +183,11 @@ spi: | ||||
|   miso_pin: GPIO14 | ||||
|  | ||||
| uart: | ||||
|   tx_pin: GPIO1 | ||||
|   - tx_pin: GPIO1 | ||||
|     rx_pin: GPIO3 | ||||
|     baud_rate: 115200 | ||||
|  | ||||
|   - id: adalight_uart | ||||
|     rx_pin: GPIO3 | ||||
|     baud_rate: 115200 | ||||
|  | ||||
| @@ -203,6 +207,8 @@ deep_sleep: | ||||
|   run_duration: 20s | ||||
|   sleep_duration: 50s | ||||
|  | ||||
| adalight: | ||||
|  | ||||
| sensor: | ||||
|   - platform: apds9960 | ||||
|     type: proximity | ||||
| @@ -708,6 +714,8 @@ light: | ||||
|     method: ESP8266_UART0 | ||||
|     num_leds: 100 | ||||
|     effects: | ||||
|       - adalight: | ||||
|           uart_id: adalight_uart | ||||
|       - e131: | ||||
|           universe: 1 | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user