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