1use crate::BLACK;
4use crate::command::SequencerAction;
5use crate::sequence::RgbSequence;
6use crate::time::{TimeDuration, TimeInstant, TimeSource};
7use palette::Srgb;
8
9pub trait RgbLed {
11 fn set_color(&mut self, color: Srgb);
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21pub enum SequencerState {
22 Idle,
24 Loaded,
26 Running,
28 Paused,
30 Complete,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[cfg_attr(feature = "defmt", derive(defmt::Format))]
37pub enum ServiceTiming<D> {
38 Continuous,
40 Delay(D),
42 Complete,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[cfg_attr(feature = "defmt", derive(defmt::Format))]
49pub struct Position {
50 pub step_index: usize,
52 pub loop_number: u32,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[cfg_attr(feature = "defmt", derive(defmt::Format))]
59pub enum SequencerError {
60 InvalidState {
62 expected: &'static str,
64 actual: SequencerState,
66 },
67 NoSequenceLoaded,
69}
70
71impl core::fmt::Display for SequencerError {
72 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74 match self {
75 SequencerError::InvalidState { expected, actual } => {
76 write!(
77 f,
78 "invalid state: expected {}, but sequencer is in {:?}",
79 expected, actual
80 )
81 }
82 SequencerError::NoSequenceLoaded => {
83 write!(f, "no sequence loaded")
84 }
85 }
86 }
87}
88
89pub struct RgbSequencer<'t, I: TimeInstant, L: RgbLed, T: TimeSource<I>, const N: usize> {
91 led: L,
92 time_source: &'t T,
93 state: SequencerState,
94 sequence: Option<RgbSequence<I::Duration, N>>,
95 start_time: Option<I>,
96 pause_start_time: Option<I>,
97 current_color: Srgb,
98 color_epsilon: f32,
99 brightness: f32,
100}
101
102pub const DEFAULT_COLOR_EPSILON: f32 = 0.001;
104
105#[inline]
107fn colors_approximately_equal(a: Srgb, b: Srgb, epsilon: f32) -> bool {
108 (a.red - b.red).abs() < epsilon
109 && (a.green - b.green).abs() < epsilon
110 && (a.blue - b.blue).abs() < epsilon
111}
112
113impl<'t, I: TimeInstant, L: RgbLed, T: TimeSource<I>, const N: usize> RgbSequencer<'t, I, L, T, N> {
114 pub fn new(mut led: L, time_source: &'t T) -> Self {
116 led.set_color(BLACK);
117
118 Self {
119 led,
120 time_source,
121 state: SequencerState::Idle,
122 sequence: None,
123 start_time: None,
124 pause_start_time: None,
125 current_color: BLACK,
126 color_epsilon: DEFAULT_COLOR_EPSILON,
127 brightness: 1.0,
128 }
129 }
130
131 pub fn with_epsilon(mut led: L, time_source: &'t T, epsilon: f32) -> Self {
133 led.set_color(BLACK);
134
135 Self {
136 led,
137 time_source,
138 state: SequencerState::Idle,
139 sequence: None,
140 start_time: None,
141 pause_start_time: None,
142 current_color: BLACK,
143 color_epsilon: epsilon,
144 brightness: 1.0,
145 }
146 }
147
148 pub fn handle_action(
150 &mut self,
151 action: SequencerAction<I::Duration, N>,
152 ) -> Result<(), SequencerError> {
153 match action {
154 SequencerAction::Load(sequence) => {
155 self.load(sequence);
156 Ok(())
157 }
158 SequencerAction::Start => self.start(),
159 SequencerAction::Stop => self.stop(),
160 SequencerAction::Pause => self.pause(),
161 SequencerAction::Resume => self.resume(),
162 SequencerAction::Restart => self.restart(),
163 SequencerAction::Clear => {
164 self.clear();
165 Ok(())
166 }
167 SequencerAction::SetBrightness(brightness) => {
168 self.set_brightness(brightness);
169 Ok(())
170 }
171 }
172 }
173
174 pub fn load(&mut self, sequence: RgbSequence<I::Duration, N>) {
176 self.sequence = Some(sequence);
177 self.start_time = None;
178 self.pause_start_time = None;
179 self.state = SequencerState::Loaded;
180 }
181
182 pub fn start(&mut self) -> Result<(), SequencerError> {
186 if self.state != SequencerState::Loaded {
187 return Err(SequencerError::InvalidState {
188 expected: "Loaded",
189 actual: self.state,
190 });
191 }
192
193 if self.sequence.is_none() {
194 return Err(SequencerError::NoSequenceLoaded);
195 }
196
197 self.start_time = Some(self.time_source.now());
198 self.state = SequencerState::Running;
199 Ok(())
200 }
201
202 pub fn load_and_start(
206 &mut self,
207 sequence: RgbSequence<I::Duration, N>,
208 ) -> Result<(), SequencerError> {
209 self.load(sequence);
210 self.start()
211 }
212
213 pub fn restart(&mut self) -> Result<(), SequencerError> {
217 match self.state {
218 SequencerState::Running | SequencerState::Paused | SequencerState::Complete => {
219 if self.sequence.is_none() {
220 return Err(SequencerError::NoSequenceLoaded);
221 }
222
223 self.start_time = Some(self.time_source.now());
224 self.pause_start_time = None;
225 self.state = SequencerState::Running;
226 Ok(())
227 }
228 _ => Err(SequencerError::InvalidState {
229 expected: "Running, Paused, or Complete",
230 actual: self.state,
231 }),
232 }
233 }
234
235 #[inline]
239 pub fn service(&mut self) -> Result<ServiceTiming<I::Duration>, SequencerError> {
240 if self.state != SequencerState::Running {
241 return Err(SequencerError::InvalidState {
242 expected: "Running",
243 actual: self.state,
244 });
245 }
246
247 let sequence = self.sequence.as_ref().unwrap();
248 let start_time = self.start_time.unwrap();
249 let current_time = self.time_source.now();
250 let elapsed = current_time.duration_since(start_time);
251
252 let (new_color, next_service) = sequence.evaluate(elapsed);
254
255 let dimmed_color = Srgb::new(
257 new_color.red * self.brightness,
258 new_color.green * self.brightness,
259 new_color.blue * self.brightness,
260 );
261
262 if !colors_approximately_equal(dimmed_color, self.current_color, self.color_epsilon) {
267 self.led.set_color(dimmed_color);
268 self.current_color = dimmed_color;
269 }
270
271 match next_service {
273 None => {
274 self.state = SequencerState::Complete;
275 Ok(ServiceTiming::Complete)
276 }
277 Some(duration) if duration == I::Duration::ZERO => Ok(ServiceTiming::Continuous),
278 Some(duration) => Ok(ServiceTiming::Delay(duration)),
279 }
280 }
281
282 #[inline]
286 pub fn peek_next_timing(&self) -> Result<ServiceTiming<I::Duration>, SequencerError> {
287 if self.state != SequencerState::Running {
288 return Err(SequencerError::InvalidState {
289 expected: "Running",
290 actual: self.state,
291 });
292 }
293
294 let sequence = self.sequence.as_ref().unwrap();
295 let start_time = self.start_time.unwrap();
296 let current_time = self.time_source.now();
297 let elapsed = current_time.duration_since(start_time);
298
299 let (_color, next_service) = sequence.evaluate(elapsed);
301
302 match next_service {
304 None => Ok(ServiceTiming::Complete),
305 Some(duration) if duration == I::Duration::ZERO => Ok(ServiceTiming::Continuous),
306 Some(duration) => Ok(ServiceTiming::Delay(duration)),
307 }
308 }
309
310 pub fn stop(&mut self) -> Result<(), SequencerError> {
312 match self.state {
313 SequencerState::Running | SequencerState::Paused | SequencerState::Complete => {
314 self.start_time = None;
315 self.pause_start_time = None;
316 self.state = SequencerState::Loaded;
317
318 self.led.set_color(BLACK);
319 self.current_color = BLACK;
320
321 Ok(())
322 }
323 _ => Err(SequencerError::InvalidState {
324 expected: "Running, Paused, or Complete",
325 actual: self.state,
326 }),
327 }
328 }
329
330 pub fn pause(&mut self) -> Result<(), SequencerError> {
334 if self.state != SequencerState::Running {
335 return Err(SequencerError::InvalidState {
336 expected: "Running",
337 actual: self.state,
338 });
339 }
340
341 self.pause_start_time = Some(self.time_source.now());
342 self.state = SequencerState::Paused;
343 Ok(())
344 }
345
346 pub fn resume(&mut self) -> Result<(), SequencerError> {
350 if self.state != SequencerState::Paused {
351 return Err(SequencerError::InvalidState {
352 expected: "Paused",
353 actual: self.state,
354 });
355 }
356
357 let pause_start = self.pause_start_time.unwrap();
358 let current_time = self.time_source.now();
359 let pause_duration = current_time.duration_since(pause_start);
360
361 let old_start = self.start_time.unwrap();
367 self.start_time = Some(old_start.checked_add(pause_duration).unwrap_or(old_start));
368
369 self.pause_start_time = None;
370 self.state = SequencerState::Running;
371 Ok(())
372 }
373
374 pub fn clear(&mut self) {
376 self.sequence = None;
377 self.start_time = None;
378 self.pause_start_time = None;
379 self.state = SequencerState::Idle;
380
381 self.led.set_color(BLACK);
382 self.current_color = BLACK;
383 }
384
385 #[inline]
387 pub fn state(&self) -> SequencerState {
388 self.state
389 }
390
391 #[inline]
393 pub fn current_color(&self) -> Srgb {
394 self.current_color
395 }
396
397 #[inline]
399 pub fn is_paused(&self) -> bool {
400 self.state == SequencerState::Paused
401 }
402
403 #[inline]
405 pub fn is_running(&self) -> bool {
406 self.state == SequencerState::Running
407 }
408
409 #[inline]
411 pub fn current_sequence(&self) -> Option<&RgbSequence<I::Duration, N>> {
412 self.sequence.as_ref()
413 }
414
415 pub fn elapsed_time(&self) -> Option<I::Duration> {
417 self.start_time.map(|start| {
418 let now = self.time_source.now();
419 now.duration_since(start)
420 })
421 }
422
423 #[inline]
425 pub fn color_epsilon(&self) -> f32 {
426 self.color_epsilon
427 }
428
429 #[inline]
433 pub fn set_color_epsilon(&mut self, epsilon: f32) {
434 self.color_epsilon = epsilon;
435 }
436
437 #[inline]
439 pub fn brightness(&self) -> f32 {
440 self.brightness
441 }
442
443 #[inline]
445 pub fn set_brightness(&mut self, brightness: f32) {
446 self.brightness = brightness.clamp(0.0, 1.0);
447 }
448
449 #[inline]
455 pub fn current_position(&self) -> Option<Position> {
456 match self.state {
457 SequencerState::Running | SequencerState::Paused => {
458 let sequence = self.sequence.as_ref()?;
459 let start_time = self.start_time?;
460
461 let reference_time = if self.state == SequencerState::Paused {
464 self.pause_start_time?
465 } else {
466 self.time_source.now()
467 };
468
469 let elapsed = reference_time.duration_since(start_time);
470
471 let step_position = sequence.find_step_position(elapsed)?;
472 Some(Position {
473 step_index: step_position.step_index,
474 loop_number: step_position.current_loop,
475 })
476 }
477 _ => None,
478 }
479 }
480
481 #[inline]
483 pub fn into_led(self) -> L {
484 self.led
485 }
486
487 #[inline]
489 pub fn into_parts(self) -> (L, Option<RgbSequence<I::Duration, N>>) {
490 (self.led, self.sequence)
491 }
492}