ESPHome 2025.12.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 {
11namespace light {
12
13inline static int16_t sin16_c(uint16_t theta) {
14 static const uint16_t BASE[] = {0, 6393, 12539, 18204, 23170, 27245, 30273, 32137};
15 static const uint8_t SLOPE[] = {49, 48, 44, 38, 31, 23, 14, 4};
16 uint16_t offset = (theta & 0x3FFF) >> 3; // 0..2047
17 if (theta & 0x4000)
18 offset = 2047 - offset;
19 uint8_t section = offset / 256; // 0..7
20 uint16_t b = BASE[section];
21 uint8_t m = SLOPE[section];
22 uint8_t secoffset8 = uint8_t(offset) / 2;
23 uint16_t mx = m * secoffset8;
24 int16_t y = mx + b;
25 if (theta & 0x8000)
26 return -y;
27 return y;
28}
29inline static uint8_t half_sin8(uint8_t v) { return sin16_c(uint16_t(v) * 128u) >> 8; }
30
32 public:
33 explicit AddressableLightEffect(const char *name) : LightEffect(name) {}
34 void start_internal() override {
37 this->start();
38 }
39 void stop() override { this->get_addressable_()->set_effect_active(false); }
40 virtual void apply(AddressableLight &it, const Color &current_color) = 0;
41 void apply() override {
42 // not using any color correction etc. that will be handled by the addressable layer through ESPColorCorrection
44 this->apply(*this->get_addressable_(), current_color);
45 }
46
49 uint32_t get_effect_index() const { return this->get_index(); }
50
52 bool is_current_effect() const { return this->is_active() && this->get_addressable_()->is_effect_active(); }
53
54 protected:
56};
57
59 public:
60 AddressableLambdaLightEffect(const char *name, void (*f)(AddressableLight &, Color, bool initial_run),
61 uint32_t update_interval)
62 : AddressableLightEffect(name), f_(f), update_interval_(update_interval) {}
63 void start() override { this->initial_run_ = true; }
64 void apply(AddressableLight &it, const Color &current_color) override {
65 const uint32_t now = millis();
66 if (now - this->last_run_ >= this->update_interval_ || this->initial_run_) {
67 this->last_run_ = now;
68 this->f_(it, current_color, this->initial_run_);
69 this->initial_run_ = false;
70 it.schedule_show();
71 }
72 }
73
74 protected:
75 void (*f_)(AddressableLight &, Color, bool initial_run);
79};
80
82 public:
83 explicit AddressableRainbowLightEffect(const char *name) : AddressableLightEffect(name) {}
84 void apply(AddressableLight &it, const Color &current_color) override {
85 ESPHSVColor hsv;
86 hsv.value = 255;
87 hsv.saturation = 240;
88 uint16_t hue = (millis() * this->speed_) % 0xFFFF;
89 const uint16_t add = 0xFFFF / this->width_;
90 for (auto var : it) {
91 hsv.hue = hue >> 8;
92 var = hsv;
93 hue += add;
94 }
95 it.schedule_show();
96 }
97 void set_speed(uint32_t speed) { this->speed_ = speed; }
98 void set_width(uint16_t width) { this->width_ = width; }
99
100 protected:
102 uint16_t width_{50};
103};
104
106 uint8_t r, g, b, w;
107 bool random;
108 size_t num_leds;
110};
111
113 public:
114 explicit AddressableColorWipeEffect(const char *name) : AddressableLightEffect(name) {}
115 void set_colors(const std::initializer_list<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; }
116 void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; }
117 void set_reverse(bool reverse) { this->reverse_ = reverse; }
118 void apply(AddressableLight &it, const Color &current_color) override {
119 const uint32_t now = millis();
121 return;
122 this->last_add_ = now;
123 if (this->reverse_) {
124 it.shift_left(1);
125 } else {
126 it.shift_right(1);
127 }
128 const AddressableColorWipeEffectColor &color = this->colors_[this->at_color_];
129 Color esp_color = Color(color.r, color.g, color.b, color.w);
130 if (color.gradient) {
131 size_t next_color_index = (this->at_color_ + 1) % this->colors_.size();
132 const AddressableColorWipeEffectColor &next_color = this->colors_[next_color_index];
133 const Color next_esp_color = Color(next_color.r, next_color.g, next_color.b, next_color.w);
134 uint8_t gradient = 255 * ((float) this->leds_added_ / color.num_leds);
135 esp_color = esp_color.gradient(next_esp_color, gradient);
136 }
137 if (this->reverse_) {
138 it[-1] = esp_color;
139 } else {
140 it[0] = esp_color;
141 }
142 if (++this->leds_added_ >= color.num_leds) {
143 this->leds_added_ = 0;
144 this->at_color_ = (this->at_color_ + 1) % this->colors_.size();
145 AddressableColorWipeEffectColor &new_color = this->colors_[this->at_color_];
146 if (new_color.random) {
148 new_color.r = c.r;
149 new_color.g = c.g;
150 new_color.b = c.b;
151 }
152 }
153 it.schedule_show();
154 }
155
156 protected:
158 size_t at_color_{0};
161 size_t leds_added_{0};
162 bool reverse_{};
163};
164
166 public:
167 explicit AddressableScanEffect(const char *name) : AddressableLightEffect(name) {}
168 void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
169 void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; }
170 void apply(AddressableLight &it, const Color &current_color) override {
171 const uint32_t now = millis();
172 if (now - this->last_move_ < this->move_interval_)
173 return;
174
175 if (direction_) {
176 this->at_led_++;
177 if (this->at_led_ == it.size() - this->scan_width_)
178 this->direction_ = false;
179 } else {
180 this->at_led_--;
181 if (this->at_led_ == 0)
182 this->direction_ = true;
183 }
184 this->last_move_ = now;
185
186 it.all() = Color::BLACK;
187 for (uint32_t i = 0; i < this->scan_width_; i++) {
188 it[this->at_led_ + i] = current_color;
189 }
190
191 it.schedule_show();
192 }
193
194 protected:
199 bool direction_{true};
200};
201
203 public:
204 explicit AddressableTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
205 void apply(AddressableLight &addressable, const Color &current_color) override {
206 const uint32_t now = millis();
207 uint8_t pos_add = 0;
208 if (now - this->last_progress_ > this->progress_interval_) {
209 const uint32_t pos_add32 = (now - this->last_progress_) / this->progress_interval_;
210 pos_add = pos_add32;
211 this->last_progress_ += pos_add32 * this->progress_interval_;
212 }
213 for (auto view : addressable) {
214 if (view.get_effect_data() != 0) {
215 const uint8_t sine = half_sin8(view.get_effect_data());
216 view = current_color * sine;
217 const uint8_t new_pos = view.get_effect_data() + pos_add;
218 if (new_pos < view.get_effect_data()) {
219 view.set_effect_data(0);
220 } else {
221 view.set_effect_data(new_pos);
222 }
223 } else {
224 view = Color::BLACK;
225 }
226 }
227 while (random_float() < this->twinkle_probability_) {
228 const size_t pos = random_uint32() % addressable.size();
229 if (addressable[pos].get_effect_data() != 0)
230 continue;
231 addressable[pos].set_effect_data(1);
232 }
233 addressable.schedule_show();
234 }
235 void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
236 void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
237
238 protected:
242};
243
245 public:
246 explicit AddressableRandomTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
247 void apply(AddressableLight &it, const Color &current_color) override {
248 const uint32_t now = millis();
249 uint8_t pos_add = 0;
250 if (now - this->last_progress_ > this->progress_interval_) {
251 pos_add = (now - this->last_progress_) / this->progress_interval_;
252 this->last_progress_ = now;
253 }
254 uint8_t subsine = ((8 * (now - this->last_progress_)) / this->progress_interval_) & 0b111;
255 for (auto view : it) {
256 if (view.get_effect_data() != 0) {
257 const uint8_t x = (view.get_effect_data() >> 3) & 0b11111;
258 const uint8_t color = view.get_effect_data() & 0b111;
259 const uint16_t sine = half_sin8((x << 3) | subsine);
260 if (color == 0) {
261 view = current_color * sine;
262 } else {
263 view = Color(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine);
264 }
265 const uint8_t new_x = x + pos_add;
266 if (new_x > 0b11111) {
267 view.set_effect_data(0);
268 } else {
269 view.set_effect_data((new_x << 3) | color);
270 }
271 } else {
272 view = Color(0, 0, 0, 0);
273 }
274 }
275 while (random_float() < this->twinkle_probability_) {
276 const size_t pos = random_uint32() % it.size();
277 if (it[pos].get_effect_data() != 0)
278 continue;
279 const uint8_t color = random_uint32() & 0b111;
280 it[pos].set_effect_data(0b1000 | color);
281 }
282 it.schedule_show();
283 }
284 void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; }
285 void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; }
286
287 protected:
291};
292
294 public:
295 explicit AddressableFireworksEffect(const char *name) : AddressableLightEffect(name) {}
296 void start() override {
297 auto &it = *this->get_addressable_();
298 it.all() = Color::BLACK;
299 }
300 void apply(AddressableLight &it, const Color &current_color) override {
301 const uint32_t now = millis();
303 return;
304 this->last_update_ = now;
305 // "invert" the fade out parameter so that higher values make fade out faster
306 const uint8_t fade_out_mult = 255u - this->fade_out_rate_;
307 for (auto view : it) {
308 Color target = view.get() * fade_out_mult;
309 if (target.r < 64)
310 target *= 170;
311 view = target;
312 }
313 int last = it.size() - 1;
314 it[0].set(it[0].get() + (it[1].get() * 128));
315 for (int i = 1; i < last; i++) {
316 it[i] = (it[i - 1].get() * 64) + it[i].get() + (it[i + 1].get() * 64);
317 }
318 it[last] = it[last].get() + (it[last - 1].get() * 128);
319 if (random_float() < this->spark_probability_) {
320 const size_t pos = random_uint32() % it.size();
321 if (this->use_random_color_) {
322 it[pos] = Color::random_color();
323 } else {
324 it[pos] = current_color;
325 }
326 }
327 it.schedule_show();
328 }
329 void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
330 void set_spark_probability(float spark_probability) { this->spark_probability_ = spark_probability; }
331 void set_use_random_color(bool random_color) { this->use_random_color_ = random_color; }
332 void set_fade_out_rate(uint8_t fade_out_rate) { this->fade_out_rate_ = fade_out_rate; }
333
334 protected:
335 uint8_t fade_out_rate_{};
340};
341
343 public:
344 explicit AddressableFlickerEffect(const char *name) : AddressableLightEffect(name) {}
345 void apply(AddressableLight &it, const Color &current_color) override {
346 const uint32_t now = millis();
347 const uint8_t intensity = this->intensity_;
348 const uint8_t inv_intensity = 255 - intensity;
350 return;
351
352 this->last_update_ = now;
353 uint32_t rng_state = random_uint32();
354 for (auto var : it) {
355 rng_state = (rng_state * 0x9E3779B9) + 0x9E37;
356 const uint8_t flicker = (rng_state & 0xFF) % intensity;
357 // scale down by random factor
358 var = var.get() * (255 - flicker);
359
360 // slowly fade back to "real" value
361 var = (var.get() * inv_intensity) + (current_color * intensity);
362 }
363 it.schedule_show();
364 }
365 void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
366 void set_intensity(float intensity) { this->intensity_ = to_uint8_scale(intensity); }
367
368 protected:
371 uint8_t intensity_{13};
372};
373
374} // namespace light
375} // namespace esphome
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
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
float random_float()
Return a random float between 0 and 1.
Definition helpers.cpp:157
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:17
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30
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.h:177
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
static const Color BLACK
Definition color.h:192
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6