Skip to main content

smart_leds_fx/
lib.rs

1#![no_std]
2
3pub mod effect;
4pub(crate) mod effects;
5pub mod prelude;
6pub mod segment;
7pub mod utils;
8
9use heapless::Vec;
10use smart_leds_trait::RGB8;
11
12use crate::effect::Effect;
13use crate::segment::{Segment, SegmentOptions};
14
15const MAX_SEGMENTS: usize = 10;
16
17/// Hardware-agnostic WS2812FX effect engine.
18///
19/// `N` is the number of LEDs on the strip. Call [`service`](Self::service) from
20/// your main loop with the current timestamp, then feed [`iter`](Self::iter) to
21/// your [`SmartLedsWrite`](smart_leds_trait::SmartLedsWrite) driver when it
22/// returns `true`.
23pub struct StripFx<const N: usize> {
24    pixels: [RGB8; N],
25    segments: Vec<Segment, MAX_SEGMENTS>,
26    brightness: u8,
27    running: bool,
28    triggered: bool,
29}
30
31impl<const N: usize> StripFx<N> {
32    /// Creates an instance covering the whole strip with `Static` at full brightness.
33    /// Starts running immediately — call [`stop`](Self::stop) if you need to delay playback.
34    pub fn new(brightness: u8) -> Self {
35        let mut fx = Self {
36            pixels: [RGB8 { r: 0, g: 0, b: 0 }; N],
37            segments: Vec::new(),
38            brightness,
39            running: true,
40            triggered: false,
41        };
42        let _ = fx.segments.push(Segment::new(0, N - 1, Effect::Static));
43        fx
44    }
45
46    /// Start the animation engine.
47    pub fn start(&mut self) {
48        self.running = true;
49    }
50
51    /// Stop the animation engine and clear the strip.
52    pub fn stop(&mut self) {
53        self.running = false;
54        self.clear();
55    }
56
57    /// Pause without clearing the display.
58    pub fn pause(&mut self) {
59        self.running = false;
60    }
61
62    /// Resume after [`pause`](Self::pause).
63    pub fn resume(&mut self) {
64        self.running = true;
65    }
66
67    /// Force one render frame on the next [`service`](Self::service) call,
68    /// regardless of timing.
69    pub fn trigger(&mut self) {
70        self.triggered = true;
71    }
72
73    pub fn is_running(&self) -> bool {
74        self.running
75    }
76
77    pub fn is_triggered(&self) -> bool {
78        self.triggered
79    }
80
81    /// Set all pixels to black without stopping the engine.
82    pub fn clear(&mut self) {
83        for p in self.pixels.iter_mut() {
84            *p = RGB8 { r: 0, g: 0, b: 0 };
85        }
86    }
87
88    /// Drive the effect engine. Pass the current time in milliseconds.
89    ///
90    /// Returns `true` when at least one segment was re-rendered and the strip
91    /// should be written to hardware via [`iter`](Self::iter).
92    pub fn service(&mut self, now_ms: u64) -> bool {
93        if !self.running && !self.triggered {
94            return false;
95        }
96        let force = self.triggered;
97        self.triggered = false;
98
99        let mut updated = false;
100        for i in 0..self.segments.len() {
101            let elapsed = now_ms.wrapping_sub(self.segments[i].last_update);
102            if force || elapsed >= self.segments[i].config.speed as u64 {
103                let start = self.segments[i].start;
104                let stop = self.segments[i].stop;
105                let effect = self.segments[i].effect;
106                let config = self.segments[i].config;
107                let mut state = self.segments[i].state;
108
109                let end = (stop + 1).min(N);
110                effect.render(&mut self.pixels[start..end], &mut state, &config);
111
112                self.segments[i].state = state;
113                self.segments[i].last_update = now_ms;
114                updated = true;
115            }
116        }
117        updated
118    }
119
120    /// Returns an iterator of brightness-scaled pixels ready for a
121    /// `SmartLedsWrite` driver.
122    pub fn iter(&self) -> impl Iterator<Item = RGB8> + '_ {
123        let brightness = self.brightness;
124        self.pixels.iter().map(move |c| RGB8 {
125            r: (c.r as u16 * brightness as u16 / 255) as u8,
126            g: (c.g as u16 * brightness as u16 / 255) as u8,
127            b: (c.b as u16 * brightness as u16 / 255) as u8,
128        })
129    }
130
131    pub fn set_brightness(&mut self, brightness: u8) {
132        self.brightness = brightness;
133    }
134
135    pub fn get_brightness(&self) -> u8 {
136        self.brightness
137    }
138
139    pub fn increase_brightness(&mut self, amount: u8) {
140        self.brightness = self.brightness.saturating_add(amount);
141    }
142
143    pub fn decrease_brightness(&mut self, amount: u8) {
144        self.brightness = self.brightness.saturating_sub(amount);
145    }
146
147    /// Sum of all RGB channel values after brightness scaling — useful for
148    /// estimating power draw before writing to hardware.
149    pub fn intensity_sum(&self) -> u32 {
150        self.iter()
151            .map(|c| c.r as u32 + c.g as u32 + c.b as u32)
152            .sum()
153    }
154
155    /// Add a new segment to the pool. Returns `Err` if the pool (max 10) is full.
156    pub fn add_segment(&mut self, segment: Segment) -> Result<(), Segment> {
157        self.segments.push(segment).map_err(|s| s)
158    }
159
160    /// Configure a segment by index using a fully built [`Segment`].
161    /// If `idx` equals the current segment count and the pool is not full, appends it.
162    /// Returns `false` if `idx` is out of range or the pool is full.
163    pub fn set_segment(&mut self, idx: usize, segment: Segment) -> bool {
164        if let Some(slot) = self.segments.get_mut(idx) {
165            *slot = segment;
166            true
167        } else if idx == self.segments.len() {
168            self.segments.push(segment).is_ok()
169        } else {
170            false
171        }
172    }
173
174    /// Reset all segments to a single full-strip `Static` segment.
175    pub fn reset_segments(&mut self) {
176        self.segments.clear();
177        let _ = self.segments.push(Segment::new(0, N - 1, Effect::Static));
178    }
179
180    /// Reset the animation state of one segment (keeps its effect and colors).
181    pub fn reset_segment(&mut self, idx: usize) {
182        if let Some(seg) = self.segments.get_mut(idx) {
183            seg.state = Default::default();
184            seg.last_update = 0;
185        }
186    }
187
188    pub fn segment_count(&self) -> usize {
189        self.segments.len()
190    }
191
192    pub fn get_segment(&self, idx: usize) -> Option<&Segment> {
193        self.segments.get(idx)
194    }
195
196    pub fn get_segment_mut(&mut self, idx: usize) -> Option<&mut Segment> {
197        self.segments.get_mut(idx)
198    }
199
200    /// Set the effect for a segment, resetting its animation state.
201    pub fn set_effect(&mut self, idx: usize, effect: Effect) {
202        if let Some(seg) = self.segments.get_mut(idx) {
203            seg.effect = effect;
204            seg.state = Default::default();
205        }
206    }
207
208    pub fn get_effect(&self, idx: usize) -> Option<Effect> {
209        self.segments.get(idx).map(|s| s.effect)
210    }
211
212    pub fn set_speed(&mut self, idx: usize, speed: u16) {
213        if let Some(seg) = self.segments.get_mut(idx) {
214            seg.config.speed = speed;
215        }
216    }
217
218    pub fn get_speed(&self, idx: usize) -> Option<u16> {
219        self.segments.get(idx).map(|s| s.config.speed)
220    }
221
222    /// Decrease the step interval, making the animation play faster.
223    pub fn faster(&mut self, idx: usize, amount: u16) {
224        if let Some(seg) = self.segments.get_mut(idx) {
225            seg.config.speed = seg.config.speed.saturating_sub(amount);
226        }
227    }
228
229    /// Increase the step interval, making the animation play slower.
230    pub fn slower(&mut self, idx: usize, amount: u16) {
231        if let Some(seg) = self.segments.get_mut(idx) {
232            seg.config.speed = seg.config.speed.saturating_add(amount);
233        }
234    }
235
236    /// Set the primary color (`colors[0]`) of a segment.
237    pub fn set_color(&mut self, idx: usize, color: RGB8) {
238        if let Some(seg) = self.segments.get_mut(idx) {
239            seg.config.colors[0] = color;
240        }
241    }
242
243    pub fn get_color(&self, idx: usize) -> Option<RGB8> {
244        self.segments.get(idx).map(|s| s.config.colors[0])
245    }
246
247    /// Set all three colors of a segment at once.
248    pub fn set_colors(&mut self, idx: usize, colors: [RGB8; 3]) {
249        if let Some(seg) = self.segments.get_mut(idx) {
250            seg.config.colors = colors;
251        }
252    }
253
254    pub fn get_colors(&self, idx: usize) -> Option<[RGB8; 3]> {
255        self.segments.get(idx).map(|s| s.config.colors)
256    }
257
258    pub fn set_options(&mut self, idx: usize, options: SegmentOptions) {
259        if let Some(seg) = self.segments.get_mut(idx) {
260            seg.options = options;
261        }
262    }
263
264    pub fn get_options(&self, idx: usize) -> Option<SegmentOptions> {
265        self.segments.get(idx).map(|s| s.options)
266    }
267
268    /// Seed the PRNG state of every segment. Useful for reproducible patterns.
269    pub fn set_random_seed(&mut self, seed: u32) {
270        for seg in self.segments.iter_mut() {
271            seg.state.aux = seed;
272        }
273    }
274
275    /// Total number of built-in effects.
276    pub fn mode_count() -> usize {
277        Effect::count()
278    }
279
280    /// Name string for any effect.
281    pub fn mode_name(effect: Effect) -> &'static str {
282        effect.name()
283    }
284}