ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
wled_light_effect.cpp
Go to the documentation of this file.
1#ifdef USE_ARDUINO
2
3#include "wled_light_effect.h"
5#include "esphome/core/log.h"
6
7#ifdef USE_ESP32
8#include <WiFi.h>
9#endif
10
11#ifdef USE_ESP8266
12#include <ESP8266WiFi.h>
13#include <WiFiUdp.h>
14#endif
15
16#ifdef USE_BK72XX
17#include <WiFiUdp.h>
18#endif
19
20namespace esphome::wled {
21
22// Description of protocols:
23// https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control
24enum Protocol { WLED_NOTIFIER = 0, WARLS = 1, DRGB = 2, DRGBW = 3, DNRGB = 4 };
25
27
28static const char *const TAG = "wled_light_effect";
29
30WLEDLightEffect::WLEDLightEffect(const char *name) : AddressableLightEffect(name) {}
31
33 AddressableLightEffect::start();
34
35 if (this->blank_on_start_) {
36 this->blank_start_ = millis();
37 this->blank_timeout_ = 0;
38 } else {
39 this->blank_start_.reset();
40 }
41}
42
44 AddressableLightEffect::stop();
45
46 if (udp_) {
47 udp_->stop();
48 udp_.reset();
49 }
50}
51
53 for (int led = it.size(); led-- > 0;) {
54 it[led].set(Color::BLACK);
55 }
56 it.schedule_show();
57}
58
59void WLEDLightEffect::apply(light::AddressableLight &it, const Color &current_color) {
60 // Init UDP lazily
61 if (!udp_) {
62 udp_ = make_unique<WiFiUDP>();
63
64 if (!udp_->begin(port_)) {
65 ESP_LOGW(TAG, "Cannot bind WLEDLightEffect to %d.", port_);
66 return;
67 }
68 }
69
70 std::vector<uint8_t> payload;
71 while (uint16_t packet_size = udp_->parsePacket()) {
72 payload.resize(packet_size);
73
74 if (!udp_->read(&payload[0], payload.size())) {
75 continue;
76 }
77
78 if (!this->parse_frame_(it, &payload[0], payload.size())) {
79 ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=0x%02X).", payload.size(), payload[0]);
80 continue;
81 }
82 }
83
84 if (this->blank_start_.has_value() && millis() - *this->blank_start_ >= this->blank_timeout_) {
86 this->blank_start_ = millis();
88 }
89}
90
91bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
92 // At minimum frame needs to have:
93 // 1b - protocol
94 // 1b - timeout
95 if (size < 2) {
96 return false;
97 }
98
99 uint8_t protocol = payload[0];
100 uint8_t timeout = payload[1];
101
102 payload += 2;
103 size -= 2;
104
105 switch (protocol) {
106 case WLED_NOTIFIER:
107 // Hyperion Port
108 if (port_ == 19446) {
109 if (!parse_drgb_frame_(it, payload, size))
110 return false;
111 } else {
112 if (!parse_notifier_frame_(it, payload, size)) {
113 return false;
114 } else {
115 timeout = UINT8_MAX;
116 }
117 }
118 break;
119
120 case WARLS:
121 if (!parse_warls_frame_(it, payload, size))
122 return false;
123 break;
124
125 case DRGB:
126 if (!parse_drgb_frame_(it, payload, size))
127 return false;
128 break;
129
130 case DRGBW:
131 if (!parse_drgbw_frame_(it, payload, size))
132 return false;
133 break;
134
135 case DNRGB:
136 if (!parse_dnrgb_frame_(it, payload, size))
137 return false;
138 break;
139
140 default:
141 return false;
142 }
143
144 if (timeout == UINT8_MAX) {
145 this->blank_start_.reset();
146 } else if (timeout > 0) {
147 this->blank_start_ = millis();
148 this->blank_timeout_ = timeout * 1000;
149 } else {
150 this->blank_start_ = millis();
152 }
153
154 it.schedule_show();
155 return true;
156}
157
158bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
159 // Receive at least RGBW and Brightness for all LEDs from WLED Sync Notification
160 // https://kno.wled.ge/interfaces/udp-notifier/
161 // https://github.com/Aircoookie/WLED/blob/main/wled00/udp.cpp
162
163 if (size <= 34) {
164 return false;
165 }
166
167 uint8_t payload_sync_group_mask = payload[34];
168
169 if (this->sync_group_mask_ && !(payload_sync_group_mask & this->sync_group_mask_)) {
170 ESP_LOGD(TAG, "sync group mask does not match");
171 return false;
172 }
173
174 uint8_t bri = payload[0];
175 uint8_t r = esp_scale8(payload[1], bri);
176 uint8_t g = esp_scale8(payload[2], bri);
177 uint8_t b = esp_scale8(payload[3], bri);
178 uint8_t w = esp_scale8(payload[8], bri);
179
180 for (auto &&led : it) {
181 led.set(Color(r, g, b, w));
182 }
183
184 return true;
185}
186
187bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
188 // packet: index, r, g, b
189 if ((size % 4) != 0) {
190 return false;
191 }
192
193 auto count = size / 4;
194 auto max_leds = it.size();
195
196 for (; count > 0; count--, payload += 4) {
197 uint8_t led = payload[0];
198 uint8_t r = payload[1];
199 uint8_t g = payload[2];
200 uint8_t b = payload[3];
201
202 if (led < max_leds) {
203 it[led].set(Color(r, g, b));
204 }
205 }
206
207 return true;
208}
209
210bool WLEDLightEffect::parse_drgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
211 // packet: r, g, b
212 if ((size % 3) != 0) {
213 return false;
214 }
215
216 auto count = size / 3;
217 auto max_leds = it.size();
218
219 for (uint16_t led = 0; led < count; ++led, payload += 3) {
220 uint8_t r = payload[0];
221 uint8_t g = payload[1];
222 uint8_t b = payload[2];
223
224 if (led < max_leds) {
225 it[led].set(Color(r, g, b));
226 }
227 }
228
229 return true;
230}
231
232bool WLEDLightEffect::parse_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
233 // packet: r, g, b, w
234 if ((size % 4) != 0) {
235 return false;
236 }
237
238 auto count = size / 4;
239 auto max_leds = it.size();
240
241 for (uint16_t led = 0; led < count; ++led, payload += 4) {
242 uint8_t r = payload[0];
243 uint8_t g = payload[1];
244 uint8_t b = payload[2];
245 uint8_t w = payload[3];
246
247 if (led < max_leds) {
248 it[led].set(Color(r, g, b, w));
249 }
250 }
251
252 return true;
253}
254
255bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
256 // offset: high, low
257 if (size < 2) {
258 return false;
259 }
260
261 uint16_t led = (uint16_t(payload[0]) << 8) + payload[1];
262 payload += 2;
263 size -= 2;
264
265 // packet: r, g, b
266 if ((size % 3) != 0) {
267 return false;
268 }
269
270 auto count = size / 3;
271 auto max_leds = it.size();
272
273 for (; count > 0; count--, payload += 3, led++) {
274 uint8_t r = payload[0];
275 uint8_t g = payload[1];
276 uint8_t b = payload[2];
277
278 if (led < max_leds) {
279 it[led].set(Color(r, g, b));
280 }
281 }
282
283 return true;
284}
285
286} // namespace esphome::wled
287
288#endif // USE_ARDUINO
virtual int32_t size() const =0
bool parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size)
optional< uint32_t > blank_start_
bool parse_drgb_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)
void blank_all_leds_(light::AddressableLight &it)
bool parse_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_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size)
constexpr uint32_t DEFAULT_BLANK_TIME
uint16_t size
Definition helpers.cpp:25
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
static void uint32_t
static const Color BLACK
Definition color.h:184