embedded_simple_ui/
led.rs

1use embedded_hal::digital::{PinState, StatefulOutputPin};
2use embedded_time::duration::Milliseconds;
3use embedded_time::rate::Rate;
4use embedded_time::{Clock, Instant};
5
6use self::effects::LedEffect;
7
8pub mod effects {
9    use embedded_time::{duration::Milliseconds, rate::Hertz, Clock, Instant, TimeInt};
10
11    /// LED Effect type
12    #[derive(Copy, Clone, Debug)]
13    pub enum EffectType<T: TimeInt = u32> {
14        /// Single pulse. Effects does not repeat
15        Pulse(Milliseconds<T>),
16        /// Blink at given Hz value
17        Blink(Hertz<T>),
18    }
19
20    /// LED Effect instance
21    ///
22    /// Stores some additional metadata alongside with the effect type
23    /// used during the effect processing.
24    #[derive(Copy, Clone, Debug)]
25    pub struct LedEffect<C: Clock> {
26        current_cycle_started_at: Option<Instant<C>>,
27        started_at: Option<Instant<C>>,
28        duration: Option<Milliseconds<C::T>>,
29        fx_type: EffectType<C::T>,
30    }
31
32    impl<C: Clock> LedEffect<C> {
33        /// Create new LED Effect instance with the assigned effect type
34        pub fn new(fx_type: EffectType<C::T>) -> Self {
35            Self {
36                current_cycle_started_at: None,
37                fx_type,
38                duration: None,
39                started_at: None
40            }
41        }
42
43        /// Indicates whether the effect has started
44        pub fn has_started(&self) -> bool {
45            self.started_at.is_some()
46        }
47
48        /// Get the timestamp at which the effect started
49        pub fn started_at(&self) -> Option<Instant<C>> {
50            self.started_at
51        }
52
53        /// Sets the effect start point
54        ///
55        /// This should be only called by the poll functions of [`Led`] implementations
56        pub fn set_started_at(&mut self, now: Instant<C>) {
57            self.started_at = Some(now);
58            self.current_cycle_started_at = self.started_at;
59        }
60
61        /// Returns the effect type of this LED effect instance
62        pub fn get_type(&self) -> &EffectType<C::T> {
63            &self.fx_type
64        }
65
66        /// Returns the duration for which the effect should last
67        pub fn get_duration(&self) -> Option<Milliseconds<C::T>> {
68            self.duration
69        }
70
71        /// Sets the effect duration
72        pub fn set_duration(&mut self, dur: Milliseconds<C::T>) {
73            self.duration = Some(dur)
74        }
75
76        /// Returns elapsed duration since the effect has started
77        pub fn time_elapsed(&self, now: Instant<C>) -> Option<Milliseconds<C::T>> {
78            if let Some(started_at) = &self.started_at {
79                return now
80                    .checked_duration_since(started_at)
81                    .map(|d| Milliseconds::<C::T>::try_from(d).unwrap());
82            }
83            None
84        }
85
86        /// Returns the duration of current cycle
87        pub fn current_cycle_duration(&self, now: Instant<C>) -> Option<Milliseconds<C::T>> {
88            if let Some(started_at) = &self.current_cycle_started_at {
89                return now
90                    .checked_duration_since(started_at)
91                    .map(|d| Milliseconds::<C::T>::try_from(d).unwrap());
92            }
93            None
94        }
95
96        /// Start new cycle at an timestamp
97        pub fn start_new_cycle(&mut self, now: Instant<C>) {
98            self.current_cycle_started_at = Some(now);
99        }
100    }
101
102    #[inline]
103    pub fn pulse<C: Clock>(duration_ms: u16) -> EffectType<C::T> {
104        let v = C::T::from(duration_ms.into());
105        EffectType::Pulse::<C::T>(Milliseconds::<C::T>::new(v))
106    }
107
108    #[inline]
109    pub fn blink<C: Clock>(rate_hz: u8) -> EffectType<C::T> {
110        let v = C::T::from(rate_hz.into());
111        EffectType::Blink::<C::T>(Hertz::<C::T>::new(v))
112    }
113}
114
115/// UI LED
116///
117/// This LED abstraction tracks it's status and provides
118/// an interface for setting visual effects such as blinking
119/// on the LED.
120///
121/// Implementors should own their resources
122/// TODO: implement ability to set the default state for the user
123pub trait Led<C: Clock> {
124    // Indicates whether the current state is on or off
125    fn is_on(&mut self) -> bool;
126
127    /// Turns on the LED
128    ///
129    /// Has no effect if the LED is already turned on
130    fn turn_on(&mut self);
131
132    /// Turns off the LED
133    ///
134    /// has no effect if the LED is already turned off
135    fn turn_off(&mut self);
136
137    /// Toggles the led on/off
138    fn toggle(&mut self);
139
140    /// Sets the effect on this LED instance
141    ///
142    /// By default effect will have infinite duration unless set otherwise by
143    /// [set_effect_duration](#method.set_effect_duration) call
144    ///
145    /// Setting the effect while another one is active will overwrite it on the next
146    /// [poll](#method.poll) call
147    fn set_effect(&mut self, effect: effects::LedEffect<C>);
148
149    /// Sets the current effect duration on this LED instance
150    ///
151    /// Can be used to prolong current effect duration
152    ///
153    /// Does nothing if no effect is currently in place
154    fn set_effect_duration(&mut self, dur: Milliseconds<C::T>);
155
156    /// Returns the current LED effect
157    ///
158    /// Returns [`None`] if no effect is in place
159    fn get_effect(&self) -> Option<&LedEffect<C>>;
160
161    /// Clears current the effect
162    ///
163    /// This should also revert the LED to the state it was in
164    /// before the effect took place
165    fn clear_effect(&mut self);
166
167    /// Polls the LED, updating it's state tracking and hardware state
168    ///
169    /// This must be done in regular intervals in order to make this abstraction
170    /// work properly. There might be limits on what this abstraction can track based
171    /// on how small / large the intervals are.
172    fn poll(&mut self, now: Instant<C>);
173}
174
175pub struct PinLed<P: StatefulOutputPin, C: Clock> {
176    pin: P,
177    effect: Option<effects::LedEffect<C>>,
178    is_on: bool,
179}
180
181impl<P: StatefulOutputPin, C: Clock> PinLed<P, C> {
182    pub fn new(pin: P) -> Self {
183        Self { pin, effect: None, is_on: false }
184    }
185}
186
187impl<P: StatefulOutputPin, C: Clock> Led<C> for PinLed<P, C> {
188    fn is_on(&mut self) -> bool {
189        self.is_on
190    }
191
192    fn turn_on(&mut self) {
193        self.is_on = true;
194    }
195
196    fn turn_off(&mut self) {
197        self.is_on = false;
198    }
199
200    fn toggle(&mut self) {
201        self.is_on = !self.is_on;
202    }
203
204    fn set_effect(&mut self, effect: effects::LedEffect<C>) {
205        self.effect = Some(effect);
206    }
207
208    fn set_effect_duration(&mut self, dur: Milliseconds<<C as Clock>::T>) {
209        if let Some(fx) = &mut self.effect {
210            fx.set_duration(dur)
211        }
212    }
213
214    fn clear_effect(&mut self) {
215        self.effect = None;
216        self.turn_off();
217    }
218
219    fn poll(&mut self, now: Instant<C>) {
220        if let Some(fx) = &mut self.effect {
221            // LED has an effect, process effect
222
223            let elapsed = fx.time_elapsed(now);
224
225            // check if effect should finish
226            if let Some(fx_dur) = fx.get_duration() {
227                if let Some(elapsed) = elapsed {
228                    if elapsed > fx_dur {
229                        // effect is over
230                        self.clear_effect();
231                        self.pin.set_low().unwrap();
232                        return;
233                    }
234                }
235            }
236
237            let mut clear_effect = false;
238
239            match fx.get_type() {
240                effects::EffectType::Pulse(dur) => {
241                    if let Some(current_dur) = fx.current_cycle_duration(now) {
242                        if current_dur > *dur {
243                            // effect is over
244                            clear_effect = true;
245                            self.pin.set_low().unwrap();
246                        } else if ! fx.has_started() {
247                            self.pin.set_high().unwrap();
248                        }
249                    }
250                }
251                effects::EffectType::Blink(rate) => {
252                    if let Some(current_dur) = fx.current_cycle_duration(now) {
253                        if current_dur > rate.to_duration::<Milliseconds<C::T>>().unwrap() {
254                            // toggle the led on/off on each state change
255                            if self.pin.is_set_low().unwrap() {
256                                self.pin.set_high().unwrap();
257                            } else {
258                                self.pin.set_low().unwrap();
259                            }
260                            fx.start_new_cycle(now);
261                        }
262                    }
263                }
264            }
265
266            if clear_effect {
267                self.clear_effect();
268                return;
269            }
270
271            // Effect is just starting, save current timestamp
272            if ! fx.has_started() {
273                fx.set_started_at(now);
274            }
275        } else {
276            // No effect on led, proceed as normal
277            let state = self.is_on;
278            self.pin
279                .set_state(match state {
280                    false => PinState::High,
281                    true => PinState::Low,
282                })
283                .unwrap()
284        }
285    }
286
287    fn get_effect(&self) -> Option<&LedEffect<C>> {
288        self.effect.as_ref()
289    }
290}