use crate::BLACK;
use crate::command::SequencerAction;
use crate::sequence::RgbSequence;
use crate::time::{TimeDuration, TimeInstant, TimeSource};
use palette::Srgb;
pub trait RgbLed {
fn set_color(&mut self, color: Srgb);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SequencerState {
Idle,
Loaded,
Running,
Paused,
Complete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ServiceTiming<D> {
Continuous,
Delay(D),
Complete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Position {
pub step_index: usize,
pub loop_number: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SequencerError {
InvalidState {
expected: &'static str,
actual: SequencerState,
},
NoSequenceLoaded,
}
impl core::fmt::Display for SequencerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SequencerError::InvalidState { expected, actual } => {
write!(
f,
"invalid state: expected {}, but sequencer is in {:?}",
expected, actual
)
}
SequencerError::NoSequenceLoaded => {
write!(f, "no sequence loaded")
}
}
}
}
pub struct RgbSequencer<'t, I: TimeInstant, L: RgbLed, T: TimeSource<I>, const N: usize> {
led: L,
time_source: &'t T,
state: SequencerState,
sequence: Option<RgbSequence<I::Duration, N>>,
start_time: Option<I>,
pause_start_time: Option<I>,
current_color: Srgb,
color_epsilon: f32,
brightness: f32,
}
pub const DEFAULT_COLOR_EPSILON: f32 = 0.001;
#[inline]
fn colors_approximately_equal(a: Srgb, b: Srgb, epsilon: f32) -> bool {
(a.red - b.red).abs() < epsilon
&& (a.green - b.green).abs() < epsilon
&& (a.blue - b.blue).abs() < epsilon
}
impl<'t, I: TimeInstant, L: RgbLed, T: TimeSource<I>, const N: usize> RgbSequencer<'t, I, L, T, N> {
pub fn new(mut led: L, time_source: &'t T) -> Self {
led.set_color(BLACK);
Self {
led,
time_source,
state: SequencerState::Idle,
sequence: None,
start_time: None,
pause_start_time: None,
current_color: BLACK,
color_epsilon: DEFAULT_COLOR_EPSILON,
brightness: 1.0,
}
}
pub fn with_epsilon(mut led: L, time_source: &'t T, epsilon: f32) -> Self {
led.set_color(BLACK);
Self {
led,
time_source,
state: SequencerState::Idle,
sequence: None,
start_time: None,
pause_start_time: None,
current_color: BLACK,
color_epsilon: epsilon,
brightness: 1.0,
}
}
pub fn handle_action(
&mut self,
action: SequencerAction<I::Duration, N>,
) -> Result<(), SequencerError> {
match action {
SequencerAction::Load(sequence) => {
self.load(sequence);
Ok(())
}
SequencerAction::Start => self.start(),
SequencerAction::Stop => self.stop(),
SequencerAction::Pause => self.pause(),
SequencerAction::Resume => self.resume(),
SequencerAction::Restart => self.restart(),
SequencerAction::Clear => {
self.clear();
Ok(())
}
SequencerAction::SetBrightness(brightness) => {
self.set_brightness(brightness);
Ok(())
}
}
}
pub fn load(&mut self, sequence: RgbSequence<I::Duration, N>) {
self.sequence = Some(sequence);
self.start_time = None;
self.pause_start_time = None;
self.state = SequencerState::Loaded;
}
pub fn start(&mut self) -> Result<(), SequencerError> {
if self.state != SequencerState::Loaded {
return Err(SequencerError::InvalidState {
expected: "Loaded",
actual: self.state,
});
}
if self.sequence.is_none() {
return Err(SequencerError::NoSequenceLoaded);
}
self.start_time = Some(self.time_source.now());
self.state = SequencerState::Running;
Ok(())
}
pub fn load_and_start(
&mut self,
sequence: RgbSequence<I::Duration, N>,
) -> Result<(), SequencerError> {
self.load(sequence);
self.start()
}
pub fn restart(&mut self) -> Result<(), SequencerError> {
match self.state {
SequencerState::Running | SequencerState::Paused | SequencerState::Complete => {
if self.sequence.is_none() {
return Err(SequencerError::NoSequenceLoaded);
}
self.start_time = Some(self.time_source.now());
self.pause_start_time = None;
self.state = SequencerState::Running;
Ok(())
}
_ => Err(SequencerError::InvalidState {
expected: "Running, Paused, or Complete",
actual: self.state,
}),
}
}
#[inline]
pub fn service(&mut self) -> Result<ServiceTiming<I::Duration>, SequencerError> {
if self.state != SequencerState::Running {
return Err(SequencerError::InvalidState {
expected: "Running",
actual: self.state,
});
}
let sequence = self.sequence.as_ref().unwrap();
let start_time = self.start_time.unwrap();
let current_time = self.time_source.now();
let elapsed = current_time.duration_since(start_time);
let (new_color, next_service) = sequence.evaluate(elapsed);
let dimmed_color = Srgb::new(
new_color.red * self.brightness,
new_color.green * self.brightness,
new_color.blue * self.brightness,
);
if !colors_approximately_equal(dimmed_color, self.current_color, self.color_epsilon) {
self.led.set_color(dimmed_color);
self.current_color = dimmed_color;
}
match next_service {
None => {
self.state = SequencerState::Complete;
Ok(ServiceTiming::Complete)
}
Some(duration) if duration == I::Duration::ZERO => Ok(ServiceTiming::Continuous),
Some(duration) => Ok(ServiceTiming::Delay(duration)),
}
}
#[inline]
pub fn peek_next_timing(&self) -> Result<ServiceTiming<I::Duration>, SequencerError> {
if self.state != SequencerState::Running {
return Err(SequencerError::InvalidState {
expected: "Running",
actual: self.state,
});
}
let sequence = self.sequence.as_ref().unwrap();
let start_time = self.start_time.unwrap();
let current_time = self.time_source.now();
let elapsed = current_time.duration_since(start_time);
let (_color, next_service) = sequence.evaluate(elapsed);
match next_service {
None => Ok(ServiceTiming::Complete),
Some(duration) if duration == I::Duration::ZERO => Ok(ServiceTiming::Continuous),
Some(duration) => Ok(ServiceTiming::Delay(duration)),
}
}
pub fn stop(&mut self) -> Result<(), SequencerError> {
match self.state {
SequencerState::Running | SequencerState::Paused | SequencerState::Complete => {
self.start_time = None;
self.pause_start_time = None;
self.state = SequencerState::Loaded;
self.led.set_color(BLACK);
self.current_color = BLACK;
Ok(())
}
_ => Err(SequencerError::InvalidState {
expected: "Running, Paused, or Complete",
actual: self.state,
}),
}
}
pub fn pause(&mut self) -> Result<(), SequencerError> {
if self.state != SequencerState::Running {
return Err(SequencerError::InvalidState {
expected: "Running",
actual: self.state,
});
}
self.pause_start_time = Some(self.time_source.now());
self.state = SequencerState::Paused;
Ok(())
}
pub fn resume(&mut self) -> Result<(), SequencerError> {
if self.state != SequencerState::Paused {
return Err(SequencerError::InvalidState {
expected: "Paused",
actual: self.state,
});
}
let pause_start = self.pause_start_time.unwrap();
let current_time = self.time_source.now();
let pause_duration = current_time.duration_since(pause_start);
let old_start = self.start_time.unwrap();
self.start_time = Some(old_start.checked_add(pause_duration).unwrap_or(old_start));
self.pause_start_time = None;
self.state = SequencerState::Running;
Ok(())
}
pub fn clear(&mut self) {
self.sequence = None;
self.start_time = None;
self.pause_start_time = None;
self.state = SequencerState::Idle;
self.led.set_color(BLACK);
self.current_color = BLACK;
}
#[inline]
pub fn state(&self) -> SequencerState {
self.state
}
#[inline]
pub fn current_color(&self) -> Srgb {
self.current_color
}
#[inline]
pub fn is_paused(&self) -> bool {
self.state == SequencerState::Paused
}
#[inline]
pub fn is_running(&self) -> bool {
self.state == SequencerState::Running
}
#[inline]
pub fn current_sequence(&self) -> Option<&RgbSequence<I::Duration, N>> {
self.sequence.as_ref()
}
pub fn elapsed_time(&self) -> Option<I::Duration> {
self.start_time.map(|start| {
let now = self.time_source.now();
now.duration_since(start)
})
}
#[inline]
pub fn color_epsilon(&self) -> f32 {
self.color_epsilon
}
#[inline]
pub fn set_color_epsilon(&mut self, epsilon: f32) {
self.color_epsilon = epsilon;
}
#[inline]
pub fn brightness(&self) -> f32 {
self.brightness
}
#[inline]
pub fn set_brightness(&mut self, brightness: f32) {
self.brightness = brightness.clamp(0.0, 1.0);
}
#[inline]
pub fn current_position(&self) -> Option<Position> {
match self.state {
SequencerState::Running | SequencerState::Paused => {
let sequence = self.sequence.as_ref()?;
let start_time = self.start_time?;
let reference_time = if self.state == SequencerState::Paused {
self.pause_start_time?
} else {
self.time_source.now()
};
let elapsed = reference_time.duration_since(start_time);
let step_position = sequence.find_step_position(elapsed)?;
Some(Position {
step_index: step_position.step_index,
loop_number: step_position.current_loop,
})
}
_ => None,
}
}
#[inline]
pub fn into_led(self) -> L {
self.led
}
#[inline]
pub fn into_parts(self) -> (L, Option<RgbSequence<I::Duration, N>>) {
(self.led, self.sequence)
}
}