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 |   cs_pin: GPIO12 | ||||||
|   irq_pin: GPIO13 |   irq_pin: GPIO13 | ||||||
|  |  | ||||||
|  | wled: | ||||||
|  |  | ||||||
| adalight: | adalight: | ||||||
|  |  | ||||||
| sensor: | sensor: | ||||||
| @@ -1185,8 +1187,13 @@ light: | |||||||
|             if (initial_run) { |             if (initial_run) { | ||||||
|               it[0] = current_color; |               it[0] = current_color; | ||||||
|             } |             } | ||||||
|  |              | ||||||
|  |     - wled: | ||||||
|  |         port: 11111 | ||||||
|  |          | ||||||
|     - adalight: |     - adalight: | ||||||
|         uart_id: adalight_uart |         uart_id: adalight_uart | ||||||
|  |          | ||||||
|     - automation: |     - automation: | ||||||
|         name: Custom Effect |         name: Custom Effect | ||||||
|         sequence: |         sequence: | ||||||
|   | |||||||
| @@ -207,6 +207,8 @@ deep_sleep: | |||||||
|   run_duration: 20s |   run_duration: 20s | ||||||
|   sleep_duration: 50s |   sleep_duration: 50s | ||||||
|  |  | ||||||
|  | wled: | ||||||
|  |  | ||||||
| adalight: | adalight: | ||||||
|  |  | ||||||
| sensor: | sensor: | ||||||
| @@ -714,6 +716,7 @@ light: | |||||||
|     method: ESP8266_UART0 |     method: ESP8266_UART0 | ||||||
|     num_leds: 100 |     num_leds: 100 | ||||||
|     effects: |     effects: | ||||||
|  |       - wled: | ||||||
|       - adalight: |       - adalight: | ||||||
|           uart_id: adalight_uart |           uart_id: adalight_uart | ||||||
|       - e131: |       - e131: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user