ESPHome 2026.1.0-dev
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 if (direction_) {
175 this->at_led_++;
176 if (this->at_led_ == it.size() - this->scan_width_)
177 this->direction_ = false;
178 } else {
179 this->at_led_--;
180 if (this->at_led_ == 0)
181 this->direction_ = true;
182 }
183 this->last_move_ = now;
184
185 it.all() = Color::BLACK;
186 for (uint32_t i = 0; i < this->scan_width_; i++) {
187 it[this->at_led_ + i] = current_color;
188 }
189
190 it.schedule_show();
191 }
192
193 protected:
198 bool direction_{true};
199};
200
202 public:
203 explicit AddressableTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
204 void apply(AddressableLight &addressable, const Color &current_color) override {
205 const uint32_t now = millis();
206 uint8_t pos_add = 0;
207 if (now - this->last_progress_ > this->progress_interval_) {
208 const uint32_t pos_add32 = (now - this->last_progress_) / this->progress_interval_;
209 pos_add = pos_add32;
210 this->last_progress_ += pos_add32 * this->progress_interval_;
211 }
212 for (auto view : addressable) {
213 if (view.get_effect_data() != 0) {
214 const uint8_t sine = half_sin8(view.get_effect_data());
215 view = current_color * sine;
216 const uint8_t new_pos = view.get_effect_data() + pos_add;
217 if (new_pos < view.get_effect_data()) {
218 view.set_effect_data(0);
219 } else {
220 view.set_effect_data(new_pos);
221 }
222 } else {
223 view = Color::BLACK;
224 }
225 }
226 while (random_float() < this->twinkle_probability_) {
227 const size_t pos = random_uint32() % addressable.size();
228 if (addressable[pos].get_effect_data() != 0)
229 continue;
230 addressable[pos].set_effect_data(1);
231 }
232 addressable.schedule_show();
233 }
234 void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
235 void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
236
237 protected:
241};
242
244 public:
245 explicit AddressableRandomTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
246 void apply(AddressableLight &it, const Color &current_color) override {
247 const uint32_t now = millis();
248 uint8_t pos_add = 0;
249 if (now - this->last_progress_ > this->progress_interval_) {
250 pos_add = (now - this->last_progress_) / this->progress_interval_;
251 this->last_progress_ = now;
252 }
253 uint8_t subsine = ((8 * (now - this->last_progress_)) / this->progress_interval_) & 0b111;
254 for (auto view : it) {
255 if (view.get_effect_data() != 0) {
256 const uint8_t x = (view.get_effect_data() >> 3) & 0b11111;
257 const uint8_t color = view.get_effect_data() & 0b111;
258 const uint16_t sine = half_sin8((x << 3) | subsine);
259 if (color == 0) {
260 view = current_color * sine;
261 } else {
262 view = Color(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine);
263 }
264 const uint8_t new_x = x + pos_add;
265 if (new_x > 0b11111) {
266 view.set_effect_data(0);
267 } else {
268 view.set_effect_data((new_x << 3) | color);
269 }
270 } else {
271 view = Color(0, 0, 0, 0);
272 }
273 }
274 while (random_float() < this->twinkle_probability_) {
275 const size_t pos = random_uint32() % it.size();
276 if (it[pos].get_effect_data() != 0)
277 continue;
278 const uint8_t color = random_uint32() & 0b111;
279 it[pos].set_effect_data(0b1000 | color);
280 }
281 it.schedule_show();
282 }
283 void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
284 void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
285
286 protected:
290};
291
293 public:
294 explicit AddressableFireworksEffect(const char *name) : AddressableLightEffect(name) {}
295 void start() override {
296 auto &it = *this->get_addressable_();
297 it.all() = Color::BLACK;
298 }
299 void apply(AddressableLight &it, const Color &current_color) override {
300 const uint32_t now = millis();
302 return;
303 this->last_update_ = now;
304 // "invert" the fade out parameter so that higher values make fade out faster
305 const uint8_t fade_out_mult = 255u - this->fade_out_rate_;
306 for (auto view : it) {
307 Color target = view.get() * fade_out_mult;
308 if (target.r < 64)
309 target *= 170;
310 view = target;
311 }
312 int last = it.size() - 1;
313 it[0].set(it[0].get() + (it[1].get() * 128));
314 for (int i = 1; i < last; i++) {
315 it[i] = (it[i - 1].get() * 64) + it[i].get() + (it[i + 1].get() * 64);
316 }
317 it[last] = it[last].get() + (it[last - 1].get() * 128);
318 if (random_float() < this->spark_probability_) {
319 const size_t pos = random_uint32() % it.size();
320 if (this->use_random_color_) {
321 it[pos] = Color::random_color();
322 } else {
323 it[pos] = current_color;
324 }
325 }
326 it.schedule_show();
327 }
328 void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
329 void set_spark_probability(float spark_probability) { this->spark_probability_ = spark_probability; }
330 void set_use_random_color(bool random_color) { this->use_random_color_ = random_color; }
331 void set_fade_out_rate(uint8_t fade_out_rate) { this->fade_out_rate_ = fade_out_rate; }
332
333 protected:
334 uint8_t fade_out_rate_{};
339};
340
342 public:
343 explicit AddressableFlickerEffect(const char *name) : AddressableLightEffect(name) {}
344 void apply(AddressableLight &it, const Color &current_color) override {
345 const uint32_t now = millis();
346 const uint8_t intensity = this->intensity_;
347 const uint8_t inv_intensity = 255 - intensity;
349 return;
350
351 this->last_update_ = now;
352 uint32_t rng_state = random_uint32();
353 for (auto var : it) {
354 rng_state = (rng_state * 0x9E3779B9) + 0x9E37;
355 const uint8_t flicker = (rng_state & 0xFF) % intensity;
356 // scale down by random factor
357 var = var.get() * (255 - flicker);
358
359 // slowly fade back to "real" value
360 var = (var.get() * inv_intensity) + (current_color * intensity);
361 }
362 it.schedule_show();
363 }
364 void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
365 void set_intensity(float intensity) { this->intensity_ = to_uint8_scale(intensity); }
366
367 protected:
370 uint8_t intensity_{13};
371};
372
373} // 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:184
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
float random_float()
Return a random float between 0 and 1.
Definition helpers.cpp:158
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:17
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
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