ESPHome 2026.3.3
Loading...
Searching...
No Matches
addressable_light_effect.h
Go to the documentation of this file.
1#pragma once
2
3#include <utility>
4
9
10namespace esphome::light {
11
12inline static int16_t sin16_c(uint16_t theta) {
13 static const uint16_t BASE[] = {0, 6393, 12539, 18204, 23170, 27245, 30273, 32137};
14 static const uint8_t SLOPE[] = {49, 48, 44, 38, 31, 23, 14, 4};
15 uint16_t offset = (theta & 0x3FFF) >> 3; // 0..2047
16 if (theta & 0x4000)
17 offset = 2047 - offset;
18 uint8_t section = offset / 256; // 0..7
19 uint16_t b = BASE[section];
20 uint8_t m = SLOPE[section];
21 uint8_t secoffset8 = uint8_t(offset) / 2;
22 uint16_t mx = m * secoffset8;
23 int16_t y = mx + b;
24 if (theta & 0x8000)
25 return -y;
26 return y;
27}
28inline static uint8_t half_sin8(uint8_t v) { return sin16_c(uint16_t(v) * 128u) >> 8; }
29
31 public:
32 explicit AddressableLightEffect(const char *name) : LightEffect(name) {}
33 void start_internal() override {
36 this->start();
37 }
38 void stop() override { this->get_addressable_()->set_effect_active(false); }
39 virtual void apply(AddressableLight &it, const Color &current_color) = 0;
40 void apply() override {
41 // not using any color correction etc. that will be handled by the addressable layer through ESPColorCorrection
43 this->apply(*this->get_addressable_(), current_color);
44 }
45
48 uint32_t get_effect_index() const { return this->get_index(); }
49
51 bool is_current_effect() const { return this->is_active() && this->get_addressable_()->is_effect_active(); }
52
53 protected:
55};
56
58 public:
59 AddressableLambdaLightEffect(const char *name, void (*f)(AddressableLight &, Color, bool initial_run),
60 uint32_t update_interval)
61 : AddressableLightEffect(name), f_(f), update_interval_(update_interval) {}
62 void start() override { this->initial_run_ = true; }
63 void apply(AddressableLight &it, const Color &current_color) override {
64 const uint32_t now = millis();
65 if (now - this->last_run_ >= this->update_interval_ || this->initial_run_) {
66 this->last_run_ = now;
67 this->f_(it, current_color, this->initial_run_);
68 this->initial_run_ = false;
69 it.schedule_show();
70 }
71 }
72
73 protected:
74 void (*f_)(AddressableLight &, Color, bool initial_run);
78};
79
81 public:
82 explicit AddressableRainbowLightEffect(const char *name) : AddressableLightEffect(name) {}
83 void apply(AddressableLight &it, const Color &current_color) override {
84 ESPHSVColor hsv;
85 hsv.value = 255;
86 hsv.saturation = 240;
87 uint16_t hue = (millis() * this->speed_) % 0xFFFF;
88 const uint16_t add = 0xFFFF / this->width_;
89 for (auto var : it) {
90 hsv.hue = hue >> 8;
91 var = hsv;
92 hue += add;
93 }
94 it.schedule_show();
95 }
96 void set_speed(uint32_t speed) { this->speed_ = speed; }
97 void set_width(uint16_t width) { this->width_ = width; }
98
99 protected:
101 uint16_t width_{50};
102};
103
105 uint8_t r, g, b, w;
106 bool random;
107 size_t num_leds;
109};
110
112 public:
113 explicit AddressableColorWipeEffect(const char *name) : AddressableLightEffect(name) {}
114 void set_colors(const std::initializer_list<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; }
115 void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; }
116 void set_reverse(bool reverse) { this->reverse_ = reverse; }
117 void apply(AddressableLight &it, const Color &current_color) override {
118 const uint32_t now = millis();
120 return;
121 this->last_add_ = now;
122 if (this->reverse_) {
123 it.shift_left(1);
124 } else {
125 it.shift_right(1);
126 }
127 const AddressableColorWipeEffectColor &color = this->colors_[this->at_color_];
128 Color esp_color = Color(color.r, color.g, color.b, color.w);
129 if (color.gradient) {
130 size_t next_color_index = (this->at_color_ + 1) % this->colors_.size();
131 const AddressableColorWipeEffectColor &next_color = this->colors_[next_color_index];
132 const Color next_esp_color = Color(next_color.r, next_color.g, next_color.b, next_color.w);
133 uint8_t gradient = 255 * ((float) this->leds_added_ / color.num_leds);
134 esp_color = esp_color.gradient(next_esp_color, gradient);
135 }
136 if (this->reverse_) {
137 it[-1] = esp_color;
138 } else {
139 it[0] = esp_color;
140 }
141 if (++this->leds_added_ >= color.num_leds) {
142 this->leds_added_ = 0;
143 this->at_color_ = (this->at_color_ + 1) % this->colors_.size();
144 AddressableColorWipeEffectColor &new_color = this->colors_[this->at_color_];
145 if (new_color.random) {
147 new_color.r = c.r;
148 new_color.g = c.g;
149 new_color.b = c.b;
150 }
151 }
152 it.schedule_show();
153 }
154
155 protected:
157 size_t at_color_{0};
160 size_t leds_added_{0};
161 bool reverse_{};
162};
163
165 public:
166 explicit AddressableScanEffect(const char *name) : AddressableLightEffect(name) {}
167 void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
168 void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; }
169 void apply(AddressableLight &it, const Color &current_color) override {
170 const uint32_t now = millis();
171 if (now - this->last_move_ < this->move_interval_)
172 return;
173
174 const auto num_leds = static_cast<uint32_t>(it.size());
175 if (this->scan_width_ >= num_leds) {
176 it.all() = current_color;
177 it.schedule_show();
178 this->last_move_ = now;
179 return;
180 }
181
182 const uint32_t max_pos = num_leds - this->scan_width_;
183 if (this->at_led_ >= max_pos) {
184 this->at_led_ = max_pos;
185 this->direction_ = false;
186 }
187
188 if (this->direction_) {
189 this->at_led_++;
190 if (this->at_led_ >= max_pos)
191 this->direction_ = false;
192 } else {
193 if (this->at_led_ > 0)
194 this->at_led_--;
195 if (this->at_led_ == 0)
196 this->direction_ = true;
197 }
198 this->last_move_ = now;
199
200 it.all() = Color::BLACK;
201 for (uint32_t i = 0; i < this->scan_width_; i++) {
202 it[this->at_led_ + i] = current_color;
203 }
204
205 it.schedule_show();
206 }
207
208 protected:
213 bool direction_{true};
214};
215
217 public:
218 explicit AddressableTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
219 void apply(AddressableLight &addressable, const Color &current_color) override {
220 const uint32_t now = millis();
221 uint8_t pos_add = 0;
222 if (now - this->last_progress_ > this->progress_interval_) {
223 const uint32_t pos_add32 = (now - this->last_progress_) / this->progress_interval_;
224 pos_add = pos_add32;
225 this->last_progress_ += pos_add32 * this->progress_interval_;
226 }
227 for (auto view : addressable) {
228 if (view.get_effect_data() != 0) {
229 const uint8_t sine = half_sin8(view.get_effect_data());
230 view = current_color * sine;
231 const uint8_t new_pos = view.get_effect_data() + pos_add;
232 if (new_pos < view.get_effect_data()) {
233 view.set_effect_data(0);
234 } else {
235 view.set_effect_data(new_pos);
236 }
237 } else {
238 view = Color::BLACK;
239 }
240 }
241 while (random_float() < this->twinkle_probability_) {
242 const size_t pos = random_uint32() % addressable.size();
243 if (addressable[pos].get_effect_data() != 0)
244 continue;
245 addressable[pos].set_effect_data(1);
246 }
247 addressable.schedule_show();
248 }
249 void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
250 void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
251
252 protected:
256};
257
259 public:
260 explicit AddressableRandomTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
261 void apply(AddressableLight &it, const Color &current_color) override {
262 const uint32_t now = millis();
263 uint8_t pos_add = 0;
264 if (now - this->last_progress_ > this->progress_interval_) {
265 pos_add = (now - this->last_progress_) / this->progress_interval_;
266 this->last_progress_ = now;
267 }
268 uint8_t subsine = ((8 * (now - this->last_progress_)) / this->progress_interval_) & 0b111;
269 for (auto view : it) {
270 if (view.get_effect_data() != 0) {
271 const uint8_t x = (view.get_effect_data() >> 3) & 0b11111;
272 const uint8_t color = view.get_effect_data() & 0b111;
273 const uint16_t sine = half_sin8((x << 3) | subsine);
274 if (color == 0) {
275 view = current_color * sine;
276 } else {
277 view = Color(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine);
278 }
279 const uint8_t new_x = x + pos_add;
280 if (new_x > 0b11111) {
281 view.set_effect_data(0);
282 } else {
283 view.set_effect_data((new_x << 3) | color);
284 }
285 } else {
286 view = Color(0, 0, 0, 0);
287 }
288 }
289 while (random_float() < this->twinkle_probability_) {
290 const size_t pos = random_uint32() % it.size();
291 if (it[pos].get_effect_data() != 0)
292 continue;
293 const uint8_t color = random_uint32() & 0b111;
294 it[pos].set_effect_data(0b1000 | color);
295 }
296 it.schedule_show();
297 }
298 void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
299 void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
300
301 protected:
305};
306
308 public:
309 explicit AddressableFireworksEffect(const char *name) : AddressableLightEffect(name) {}
310 void start() override {
311 auto &it = *this->get_addressable_();
312 it.all() = Color::BLACK;
313 }
314 void apply(AddressableLight &it, const Color &current_color) override {
315 const uint32_t now = millis();
317 return;
318 this->last_update_ = now;
319 // "invert" the fade out parameter so that higher values make fade out faster
320 const uint8_t fade_out_mult = 255u - this->fade_out_rate_;
321 for (auto view : it) {
322 Color target = view.get() * fade_out_mult;
323 if (target.r < 64)
324 target *= 170;
325 view = target;
326 }
327 if (it.size() < 2)
328 return;
329 int last = it.size() - 1;
330 it[0].set(it[0].get() + (it[1].get() * 128));
331 for (int i = 1; i < last; i++) {
332 it[i] = (it[i - 1].get() * 64) + it[i].get() + (it[i + 1].get() * 64);
333 }
334 it[last] = it[last].get() + (it[last - 1].get() * 128);
335 if (random_float() < this->spark_probability_) {
336 const size_t pos = random_uint32() % it.size();
337 if (this->use_random_color_) {
338 it[pos] = Color::random_color();
339 } else {
340 it[pos] = current_color;
341 }
342 }
343 it.schedule_show();
344 }
345 void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
346 void set_spark_probability(float spark_probability) { this->spark_probability_ = spark_probability; }
347 void set_use_random_color(bool random_color) { this->use_random_color_ = random_color; }
348 void set_fade_out_rate(uint8_t fade_out_rate) { this->fade_out_rate_ = fade_out_rate; }
349
350 protected:
351 uint8_t fade_out_rate_{};
356};
357
359 public:
360 explicit AddressableFlickerEffect(const char *name) : AddressableLightEffect(name) {}
361 void apply(AddressableLight &it, const Color &current_color) override {
362 const uint32_t now = millis();
363 const uint8_t intensity = this->intensity_;
364 const uint8_t inv_intensity = 255 - intensity;
366 return;
367
368 this->last_update_ = now;
369 uint32_t rng_state = random_uint32();
370 for (auto var : it) {
371 rng_state = (rng_state * 0x9E3779B9) + 0x9E37;
372 const uint8_t flicker = (rng_state & 0xFF) % intensity;
373 // scale down by random factor
374 var = var.get() * (255 - flicker);
375
376 // slowly fade back to "real" value
377 var = (var.get() * inv_intensity) + (current_color * intensity);
378 }
379 it.schedule_show();
380 }
381 void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
382 void set_intensity(float intensity) { this->intensity_ = to_uint8_scale(intensity); }
383
384 protected:
387 uint8_t intensity_{13};
388};
389
390} // namespace esphome::light
uint8_t m
Definition bl0906.h:1
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:374
void apply(AddressableLight &it, const Color &current_color) override
FixedVector< AddressableColorWipeEffectColor > colors_
void set_colors(const std::initializer_list< AddressableColorWipeEffectColor > &colors)
void set_add_led_interval(uint32_t add_led_interval)
void apply(AddressableLight &it, const Color &current_color) override
void apply(AddressableLight &it, const Color &current_color) override
void set_update_interval(uint32_t update_interval)
void(* f_)(AddressableLight &, Color, bool initial_run)
AddressableLambdaLightEffect(const char *name, void(*f)(AddressableLight &, Color, bool initial_run), uint32_t update_interval)
void apply(AddressableLight &it, const Color &current_color) override
uint32_t get_effect_index() const
Get effect index specifically for addressable effects.
virtual void apply(AddressableLight &it, const Color &current_color)=0
bool is_current_effect() const
Check if this is the currently running addressable effect.
virtual void clear_effect_data()=0
ESPColorView get(int32_t index)
void set_effect_active(bool effect_active)
virtual int32_t size() const =0
void apply(AddressableLight &it, const Color &current_color) override
void apply(AddressableLight &it, const Color &current_color) override
void apply(AddressableLight &it, const Color &current_color) override
void set_progress_interval(uint32_t progress_interval)
void set_twinkle_probability(float twinkle_probability)
void apply(AddressableLight &addressable, const Color &current_color) override
virtual void start()
Initialize this LightEffect. Will be called once after creation.
uint32_t get_index() const
Get the index of this effect in the parent light's effect list.
bool is_active() const
Check if this effect is currently active.
LightColorValues remote_values
The remote color values reported to the frontend.
LightOutput * get_output() const
Get the light output associated with this object.
Color color_from_light_color_values(LightColorValues val)
Convert the color information from a LightColorValues object to a Color object (does not apply bright...
FLAG_HAS_TRANSITION float
static float float b
float random_float()
Return a random float between 0 and 1.
Definition helpers.cpp:159
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:17
size_t size_t pos
Definition helpers.h:929
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
static Color random_color()
Definition color.h:166
uint8_t g
Definition color.h:34
Color gradient(const Color &to_color, uint8_t amnt)
Definition color.cpp:9
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
static const Color BLACK
Definition color.h:184
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6