ESPHome  2024.12.2
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"
5 #include "esphome/core/helpers.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 
20 namespace esphome {
21 namespace wled {
22 
23 // Description of protocols:
24 // https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control
25 enum Protocol { WLED_NOTIFIER = 0, WARLS = 1, DRGB = 2, DRGBW = 3, DNRGB = 4 };
26 
27 const int DEFAULT_BLANK_TIME = 1000;
28 
29 static const char *const TAG = "wled_light_effect";
30 
31 WLEDLightEffect::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 
59 void 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  // FIXME: Use roll-over safe arithmetic
85  if (blank_at_ < millis()) {
86  blank_all_leds_(it);
88  }
89 }
90 
91 bool 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 
156 bool 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 
185 bool 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 
208 bool 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 
230 bool 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 
253 bool 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
const char * name
Definition: stm32flash.h:78
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_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size)
std::unique_ptr< UDP > udp_
const int DEFAULT_BLANK_TIME
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
bool parse_drgbw_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)
virtual int32_t size() const =0
void blank_all_leds_(light::AddressableLight &it)
WLEDLightEffect(const std::string &name)
static const Color BLACK
Definition: color.h:168
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size)