mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add WLED support (#1092)
A component to support [WLED](https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control). This allows to control addressable LEDs over WiFi/UDP, by pushing data right into LEDs. The most useful to use [Prismatik](https://github.com/psieg/Lightpack) to create an immersive effect on PC. It supports all WLED protocols: - WARLS - DRGB - DRGBW - DNRGB - WLED Notifier Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
		
							
								
								
									
										20
									
								
								esphome/components/wled/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/wled/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components.light.types import AddressableLightEffect | ||||
| from esphome.components.light.effects import register_addressable_effect | ||||
| from esphome.const import CONF_NAME, CONF_PORT | ||||
|  | ||||
| wled_ns = cg.esphome_ns.namespace('wled') | ||||
| WLEDLightEffect = wled_ns.class_('WLEDLightEffect', AddressableLightEffect) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({}) | ||||
|  | ||||
|  | ||||
| @register_addressable_effect('wled', WLEDLightEffect, "WLED", { | ||||
|     cv.Optional(CONF_PORT, default=21324): cv.port, | ||||
| }) | ||||
| def wled_light_effect_to_code(config, effect_id): | ||||
|     effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) | ||||
|     cg.add(effect.set_port(config[CONF_PORT])) | ||||
|  | ||||
|     yield effect | ||||
							
								
								
									
										237
									
								
								esphome/components/wled/wled_light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								esphome/components/wled/wled_light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | ||||
| #include "wled_light_effect.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| #include <WiFi.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| #include <ESP8266WiFi.h> | ||||
| #include <WiFiUdp.h> | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace wled { | ||||
|  | ||||
| // Description of protocols: | ||||
| // https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control | ||||
| enum Protocol { WLED_NOTIFIER = 0, WARLS = 1, DRGB = 2, DRGBW = 3, DNRGB = 4 }; | ||||
|  | ||||
| const int DEFAULT_BLANK_TIME = 1000; | ||||
|  | ||||
| static const char *TAG = "wled_light_effect"; | ||||
|  | ||||
| WLEDLightEffect::WLEDLightEffect(const std::string &name) : AddressableLightEffect(name) {} | ||||
|  | ||||
| void WLEDLightEffect::start() { | ||||
|   AddressableLightEffect::start(); | ||||
|  | ||||
|   blank_at_ = 0; | ||||
| } | ||||
|  | ||||
| void WLEDLightEffect::stop() { | ||||
|   AddressableLightEffect::stop(); | ||||
|  | ||||
|   if (udp_) { | ||||
|     udp_->stop(); | ||||
|     udp_.reset(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) { | ||||
|   for (int led = it.size(); led-- > 0;) { | ||||
|     it[led].set(light::ESPColor::BLACK); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void WLEDLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { | ||||
|   // Init UDP lazily | ||||
|   if (!udp_) { | ||||
|     udp_.reset(new WiFiUDP()); | ||||
|  | ||||
|     if (!udp_->begin(port_)) { | ||||
|       ESP_LOGE(TAG, "Cannot bind WLEDLightEffect to %d.", port_); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   while (uint16_t packet_size = udp_->parsePacket()) { | ||||
|     std::vector<uint8_t> payload; | ||||
|     payload.resize(packet_size); | ||||
|  | ||||
|     if (!udp_->read(&payload[0], payload.size())) { | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     if (!this->parse_frame_(it, &payload[0], payload.size())) { | ||||
|       ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=%c/%d).", payload.size(), payload[0], payload[0]); | ||||
|       continue; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (blank_at_ < millis()) { | ||||
|     blank_all_leds_(it); | ||||
|     blank_at_ = millis() + DEFAULT_BLANK_TIME; | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { | ||||
|   // At minimum frame needs to have: | ||||
|   // 1b - protocol | ||||
|   // 1b - timeout | ||||
|   if (size < 2) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   uint8_t protocol = payload[0]; | ||||
|   uint8_t timeout = payload[1]; | ||||
|  | ||||
|   payload += 2; | ||||
|   size -= 2; | ||||
|  | ||||
|   switch (protocol) { | ||||
|     case WLED_NOTIFIER: | ||||
|       if (!parse_notifier_frame_(it, payload, size)) | ||||
|         return false; | ||||
|       break; | ||||
|  | ||||
|     case WARLS: | ||||
|       if (!parse_warls_frame_(it, payload, size)) | ||||
|         return false; | ||||
|       break; | ||||
|  | ||||
|     case DRGB: | ||||
|       if (!parse_drgb_frame_(it, payload, size)) | ||||
|         return false; | ||||
|       break; | ||||
|  | ||||
|     case DRGBW: | ||||
|       if (!parse_drgbw_frame_(it, payload, size)) | ||||
|         return false; | ||||
|       break; | ||||
|  | ||||
|     case DNRGB: | ||||
|       if (!parse_dnrgb_frame_(it, payload, size)) | ||||
|         return false; | ||||
|       break; | ||||
|  | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
|  | ||||
|   if (timeout == UINT8_MAX) { | ||||
|     blank_at_ = UINT32_MAX; | ||||
|   } else if (timeout > 0) { | ||||
|     blank_at_ = millis() + timeout * 1000; | ||||
|   } else { | ||||
|     blank_at_ = millis() + DEFAULT_BLANK_TIME; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { | ||||
|   // Packet needs to be empty | ||||
|   return size == 0; | ||||
| } | ||||
|  | ||||
| bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { | ||||
|   // packet: index, r, g, b | ||||
|   if ((size % 4) != 0) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto count = size / 4; | ||||
|   auto max_leds = it.size(); | ||||
|  | ||||
|   for (; count > 0; count--, payload += 4) { | ||||
|     uint8_t led = payload[0]; | ||||
|     uint8_t r = payload[1]; | ||||
|     uint8_t g = payload[2]; | ||||
|     uint8_t b = payload[3]; | ||||
|  | ||||
|     if (led < max_leds) { | ||||
|       it[led].set(light::ESPColor(r, g, b)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool WLEDLightEffect::parse_drgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { | ||||
|   // packet: r, g, b | ||||
|   if ((size % 3) != 0) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto count = size / 3; | ||||
|   auto max_leds = it.size(); | ||||
|  | ||||
|   for (uint16_t led = 0; led < count; ++led, payload += 3) { | ||||
|     uint8_t r = payload[0]; | ||||
|     uint8_t g = payload[1]; | ||||
|     uint8_t b = payload[2]; | ||||
|  | ||||
|     if (led < max_leds) { | ||||
|       it[led].set(light::ESPColor(r, g, b)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool WLEDLightEffect::parse_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { | ||||
|   // packet: r, g, b, w | ||||
|   if ((size % 4) != 0) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto count = size / 4; | ||||
|   auto max_leds = it.size(); | ||||
|  | ||||
|   for (uint16_t led = 0; led < count; ++led, payload += 4) { | ||||
|     uint8_t r = payload[0]; | ||||
|     uint8_t g = payload[1]; | ||||
|     uint8_t b = payload[2]; | ||||
|     uint8_t w = payload[3]; | ||||
|  | ||||
|     if (led < max_leds) { | ||||
|       it[led].set(light::ESPColor(r, g, b, w)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) { | ||||
|   // offset: high, low | ||||
|   if (size < 2) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   uint16_t led = (uint16_t(payload[0]) << 8) + payload[1]; | ||||
|   payload += 2; | ||||
|   size -= 2; | ||||
|  | ||||
|   // packet: r, g, b | ||||
|   if ((size % 3) != 0) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto count = size / 3; | ||||
|   auto max_leds = it.size(); | ||||
|  | ||||
|   for (; count > 0; count--, payload += 3, led++) { | ||||
|     uint8_t r = payload[0]; | ||||
|     uint8_t g = payload[1]; | ||||
|     uint8_t b = payload[2]; | ||||
|  | ||||
|     if (led < max_leds) { | ||||
|       it[led].set(light::ESPColor(r, g, b)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace wled | ||||
| }  // namespace esphome | ||||
							
								
								
									
										41
									
								
								esphome/components/wled/wled_light_effect.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/wled/wled_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 <vector> | ||||
| #include <memory> | ||||
|  | ||||
| class UDP; | ||||
|  | ||||
| namespace esphome { | ||||
| namespace wled { | ||||
|  | ||||
| class WLEDLightEffect : public light::AddressableLightEffect { | ||||
|  public: | ||||
|   WLEDLightEffect(const std::string &name); | ||||
|  | ||||
|  public: | ||||
|   void start() override; | ||||
|   void stop() override; | ||||
|   void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; | ||||
|   void set_port(uint16_t port) { this->port_ = port; } | ||||
|  | ||||
|  protected: | ||||
|   void blank_all_leds_(light::AddressableLight &it); | ||||
|   bool parse_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); | ||||
|   bool parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); | ||||
|   bool parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); | ||||
|   bool parse_drgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); | ||||
|   bool parse_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); | ||||
|   bool parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); | ||||
|  | ||||
|  protected: | ||||
|   uint16_t port_{0}; | ||||
|   std::unique_ptr<UDP> udp_; | ||||
|   uint32_t blank_at_{0}; | ||||
|   uint32_t dropped_{0}; | ||||
| }; | ||||
|  | ||||
| }  // namespace wled | ||||
| }  // namespace esphome | ||||
| @@ -185,6 +185,8 @@ as3935_spi: | ||||
|   cs_pin: GPIO12 | ||||
|   irq_pin: GPIO13 | ||||
|  | ||||
| wled: | ||||
|  | ||||
| adalight: | ||||
|  | ||||
| sensor: | ||||
| @@ -1185,8 +1187,13 @@ light: | ||||
|             if (initial_run) { | ||||
|               it[0] = current_color; | ||||
|             } | ||||
|              | ||||
|     - wled: | ||||
|         port: 11111 | ||||
|          | ||||
|     - adalight: | ||||
|         uart_id: adalight_uart | ||||
|          | ||||
|     - automation: | ||||
|         name: Custom Effect | ||||
|         sequence: | ||||
|   | ||||
| @@ -207,6 +207,8 @@ deep_sleep: | ||||
|   run_duration: 20s | ||||
|   sleep_duration: 50s | ||||
|  | ||||
| wled: | ||||
|  | ||||
| adalight: | ||||
|  | ||||
| sensor: | ||||
| @@ -714,6 +716,7 @@ light: | ||||
|     method: ESP8266_UART0 | ||||
|     num_leds: 100 | ||||
|     effects: | ||||
|       - wled: | ||||
|       - adalight: | ||||
|           uart_id: adalight_uart | ||||
|       - e131: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user