ESPHome 2025.5.0
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"
4#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
27const int DEFAULT_BLANK_TIME = 1000;
28
29static const char *const TAG = "wled_light_effect";
30
31WLEDLightEffect::WLEDLightEffect(const std::string &name) : AddressableLightEffect(name) {}
32
34 AddressableLightEffect::start();
35
36 if (this->blank_on_start_) {
37 this->blank_at_ = 0;
38 } else {
39 this->blank_at_ = UINT32_MAX;
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_) {
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 // FIXME: Use roll-over safe arithmetic
85 if (blank_at_ < 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 blank_at_ = UINT32_MAX;
146 } else if (timeout > 0) {
147 blank_at_ = millis() + timeout * 1000;
148 } else {
150 }
151
152 it.schedule_show();
153 return true;
154}
155
156bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
157 // Receive at least RGBW and Brightness for all LEDs from WLED Sync Notification
158 // https://kno.wled.ge/interfaces/udp-notifier/
159 // https://github.com/Aircoookie/WLED/blob/main/wled00/udp.cpp
160
161 if (size < 34) {
162 return false;
163 }
164
165 uint8_t payload_sync_group_mask = payload[34];
166
167 if (this->sync_group_mask_ && !(payload_sync_group_mask & this->sync_group_mask_)) {
168 ESP_LOGD(TAG, "sync group mask does not match");
169 return false;
170 }
171
172 uint8_t bri = payload[0];
173 uint8_t r = esp_scale8(payload[1], bri);
174 uint8_t g = esp_scale8(payload[2], bri);
175 uint8_t b = esp_scale8(payload[3], bri);
176 uint8_t w = esp_scale8(payload[8], bri);
177
178 for (auto &&led : it) {
179 led.set(Color(r, g, b, w));
180 }
181
182 return true;
183}
184
185bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
186 // packet: index, r, g, b
187 if ((size % 4) != 0) {
188 return false;
189 }
190
191 auto count = size / 4;
192 auto max_leds = it.size();
193
194 for (; count > 0; count--, payload += 4) {
195 uint8_t led = payload[0];
196 uint8_t r = payload[1];
197 uint8_t g = payload[2];
198 uint8_t b = payload[3];
199
200 if (led < max_leds) {
201 it[led].set(Color(r, g, b));
202 }
203 }
204
205 return true;
206}
207
208bool WLEDLightEffect::parse_drgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
209 // packet: r, g, b
210 if ((size % 3) != 0) {
211 return false;
212 }
213
214 auto count = size / 3;
215 auto max_leds = it.size();
216
217 for (uint16_t led = 0; led < count; ++led, payload += 3) {
218 uint8_t r = payload[0];
219 uint8_t g = payload[1];
220 uint8_t b = payload[2];
221
222 if (led < max_leds) {
223 it[led].set(Color(r, g, b));
224 }
225 }
226
227 return true;
228}
229
230bool WLEDLightEffect::parse_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
231 // packet: r, g, b, w
232 if ((size % 4) != 0) {
233 return false;
234 }
235
236 auto count = size / 4;
237 auto max_leds = it.size();
238
239 for (uint16_t led = 0; led < count; ++led, payload += 4) {
240 uint8_t r = payload[0];
241 uint8_t g = payload[1];
242 uint8_t b = payload[2];
243 uint8_t w = payload[3];
244
245 if (led < max_leds) {
246 it[led].set(Color(r, g, b, w));
247 }
248 }
249
250 return true;
251}
252
253bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
254 // offset: high, low
255 if (size < 2) {
256 return false;
257 }
258
259 uint16_t led = (uint16_t(payload[0]) << 8) + payload[1];
260 payload += 2;
261 size -= 2;
262
263 // packet: r, g, b
264 if ((size % 3) != 0) {
265 return false;
266 }
267
268 auto count = size / 3;
269 auto max_leds = it.size();
270
271 for (; count > 0; count--, payload += 3, led++) {
272 uint8_t r = payload[0];
273 uint8_t g = payload[1];
274 uint8_t b = payload[2];
275
276 if (led < max_leds) {
277 it[led].set(Color(r, g, b));
278 }
279 }
280
281 return true;
282}
283
284} // namespace wled
285} // namespace esphome
286
287#endif // USE_ARDUINO
virtual int32_t size() const =0
bool parse_dnrgb_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_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size)
void blank_all_leds_(light::AddressableLight &it)
WLEDLightEffect(const std::string &name)
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)
const int DEFAULT_BLANK_TIME
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::unique_ptr< T > make_unique(Args &&...args)
Definition helpers.h:85
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
static const Color BLACK
Definition color.h:168