ESPHome  2022.8.0
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 namespace esphome {
17 namespace wled {
18 
19 // Description of protocols:
20 // https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control
21 enum Protocol { WLED_NOTIFIER = 0, WARLS = 1, DRGB = 2, DRGBW = 3, DNRGB = 4 };
22 
23 const int DEFAULT_BLANK_TIME = 1000;
24 
25 static const char *const TAG = "wled_light_effect";
26 
27 WLEDLightEffect::WLEDLightEffect(const std::string &name) : AddressableLightEffect(name) {}
28 
30  AddressableLightEffect::start();
31 
32  blank_at_ = 0;
33 }
34 
36  AddressableLightEffect::stop();
37 
38  if (udp_) {
39  udp_->stop();
40  udp_.reset();
41  }
42 }
43 
45  for (int led = it.size(); led-- > 0;) {
46  it[led].set(Color::BLACK);
47  }
48  it.schedule_show();
49 }
50 
51 void WLEDLightEffect::apply(light::AddressableLight &it, const Color &current_color) {
52  // Init UDP lazily
53  if (!udp_) {
54  udp_ = make_unique<WiFiUDP>();
55 
56  if (!udp_->begin(port_)) {
57  ESP_LOGW(TAG, "Cannot bind WLEDLightEffect to %d.", port_);
58  return;
59  }
60  }
61 
62  std::vector<uint8_t> payload;
63  while (uint16_t packet_size = udp_->parsePacket()) {
64  payload.resize(packet_size);
65 
66  if (!udp_->read(&payload[0], payload.size())) {
67  continue;
68  }
69 
70  if (!this->parse_frame_(it, &payload[0], payload.size())) {
71  ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=0x%02X).", payload.size(), payload[0]);
72  continue;
73  }
74  }
75 
76  // FIXME: Use roll-over safe arithmetic
77  if (blank_at_ < millis()) {
78  blank_all_leds_(it);
80  }
81 }
82 
83 bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
84  // At minimum frame needs to have:
85  // 1b - protocol
86  // 1b - timeout
87  if (size < 2) {
88  return false;
89  }
90 
91  uint8_t protocol = payload[0];
92  uint8_t timeout = payload[1];
93 
94  payload += 2;
95  size -= 2;
96 
97  switch (protocol) {
98  case WLED_NOTIFIER:
99  // Hyperion Port
100  if (port_ == 19446) {
101  if (!parse_drgb_frame_(it, payload, size))
102  return false;
103  } else {
104  if (!parse_notifier_frame_(it, payload, size))
105  return false;
106  }
107  break;
108 
109  case WARLS:
110  if (!parse_warls_frame_(it, payload, size))
111  return false;
112  break;
113 
114  case DRGB:
115  if (!parse_drgb_frame_(it, payload, size))
116  return false;
117  break;
118 
119  case DRGBW:
120  if (!parse_drgbw_frame_(it, payload, size))
121  return false;
122  break;
123 
124  case DNRGB:
125  if (!parse_dnrgb_frame_(it, payload, size))
126  return false;
127  break;
128 
129  default:
130  return false;
131  }
132 
133  if (timeout == UINT8_MAX) {
134  blank_at_ = UINT32_MAX;
135  } else if (timeout > 0) {
136  blank_at_ = millis() + timeout * 1000;
137  } else {
139  }
140 
141  it.schedule_show();
142  return true;
143 }
144 
145 bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
146  // Packet needs to be empty
147  return size == 0;
148 }
149 
150 bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
151  // packet: index, r, g, b
152  if ((size % 4) != 0) {
153  return false;
154  }
155 
156  auto count = size / 4;
157  auto max_leds = it.size();
158 
159  for (; count > 0; count--, payload += 4) {
160  uint8_t led = payload[0];
161  uint8_t r = payload[1];
162  uint8_t g = payload[2];
163  uint8_t b = payload[3];
164 
165  if (led < max_leds) {
166  it[led].set(Color(r, g, b));
167  }
168  }
169 
170  return true;
171 }
172 
173 bool WLEDLightEffect::parse_drgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
174  // packet: r, g, b
175  if ((size % 3) != 0) {
176  return false;
177  }
178 
179  auto count = size / 3;
180  auto max_leds = it.size();
181 
182  for (uint16_t led = 0; led < count; ++led, payload += 3) {
183  uint8_t r = payload[0];
184  uint8_t g = payload[1];
185  uint8_t b = payload[2];
186 
187  if (led < max_leds) {
188  it[led].set(Color(r, g, b));
189  }
190  }
191 
192  return true;
193 }
194 
195 bool WLEDLightEffect::parse_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
196  // packet: r, g, b, w
197  if ((size % 4) != 0) {
198  return false;
199  }
200 
201  auto count = size / 4;
202  auto max_leds = it.size();
203 
204  for (uint16_t led = 0; led < count; ++led, payload += 4) {
205  uint8_t r = payload[0];
206  uint8_t g = payload[1];
207  uint8_t b = payload[2];
208  uint8_t w = payload[3];
209 
210  if (led < max_leds) {
211  it[led].set(Color(r, g, b, w));
212  }
213  }
214 
215  return true;
216 }
217 
218 bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size) {
219  // offset: high, low
220  if (size < 2) {
221  return false;
222  }
223 
224  uint16_t led = (uint16_t(payload[0]) << 8) + payload[1];
225  payload += 2;
226  size -= 2;
227 
228  // packet: r, g, b
229  if ((size % 3) != 0) {
230  return false;
231  }
232 
233  auto count = size / 3;
234  auto max_leds = it.size();
235 
236  for (; count > 0; count--, payload += 3, led++) {
237  uint8_t r = payload[0];
238  uint8_t g = payload[1];
239  uint8_t b = payload[2];
240 
241  if (led < max_leds) {
242  it[led].set(Color(r, g, b));
243  }
244  }
245 
246  return true;
247 }
248 
249 } // namespace wled
250 } // namespace esphome
251 
252 #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:26
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:158
Definition: a4988.cpp:4
bool parse_warls_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size)