embedded_simple_ui/
switch.rs

1use core::marker::PhantomData;
2use embedded_hal::digital::InputPin;
3use embedded_time::duration::Milliseconds;
4use embedded_time::{Clock, Instant};
5
6/// UI Switch
7///
8/// This advanced switch tracks it's own state and can provide
9/// some additional information on it's current state, how long
10/// the current state is in effect and how long the last state was in
11/// effect.
12///
13/// Implementors of this trait should own their resources.
14/// TODO: implement ability to set the default state for the user
15pub trait Switch<C: Clock> {
16    /// Reset the switch state to initial values
17    fn reset(&mut self);
18
19    /// Poll the switch for the hardware state changes
20    ///
21    /// This must be done in regular intervals in order to make this abstraction
22    /// work properly. There might be limits on what this abstraction can track based
23    /// on how small / large the intervals are.
24    fn poll(&mut self, now: Instant<C>);
25
26    /// Indicates that the switch state has has_changed since the last poll
27    fn has_changed(&self) -> bool;
28
29    /// Indicates that the switch is in pressed state
30    fn is_pressed(&self) -> bool;
31
32    /// Indicates that the switch is in released state
33    fn is_released(&self) -> bool;
34
35    /// Returns the duration for which the switch has been pressed for
36    ///
37    /// This is a wrapper for [last_state_lasted_for](#method.last_state_lasted_for)
38    /// that conveniently returns [`None`] if current state is not the opposite
39    /// of the state the function name indicates
40    ///
41    /// Returns [`None`] if the switch is not in released state
42    fn pressed_for(&self) -> Option<Milliseconds<C::T>>;
43
44    /// Returns the duration for which the switch has been released for
45    ///
46    /// This is a wrapper for [last_state_lasted_for](#method.last_state_lasted_for)
47    /// that conveniently returns [`None`] if current state is not the opposite
48    /// of the state the function name indicates
49    ///
50    /// Returns [`None`] if the switch is not in the pressed state
51    fn released_for(&self) -> Option<Milliseconds<C::T>>;
52
53    /// Returns the duration for which the last switch state lasted for
54    fn prev_state_lasted_for(&self) -> Milliseconds<C::T>;
55
56    /// Returns the duration for which the current state is held
57    ///
58    /// Requires an instant to be passed in to compare against the switch state
59    fn current_state(&self, now: Instant<C>) -> Milliseconds<C::T>;
60
61    /// Wait for the state to change
62    ///
63    /// Polls the switch until it's state has been changed
64    ///
65    /// This operation is blocking
66    fn wait(&mut self, clock: &C);
67}
68
69pub mod switch_state {
70    use embedded_hal::digital::InputPin;
71
72    /// [`PressedState`] defines the switch behavior on pin raw values
73    ///
74    /// This is internally implemented for [`PressedOnHigh`] and [`PressedOnLow`]
75    /// to implement different behaviors.
76    pub trait PressedState {
77        fn get_pressed_state<P: InputPin>(pin: &mut P) -> bool;
78    }
79
80    /// Sets the switch behavior to be in pressed state when the pin is high
81    pub struct PressedOnHigh {}
82
83    /// Sets the switch behavior to be in pressed state when the pin is is low
84    pub struct PressedOnLow {}
85
86    impl PressedState for PressedOnHigh {
87        fn get_pressed_state<P: InputPin>(pin: &mut P) -> bool {
88            pin.is_high().unwrap()
89        }
90    }
91    impl PressedState for PressedOnLow {
92        fn get_pressed_state<P: InputPin>(pin: &mut P) -> bool {
93            pin.is_low().unwrap()
94        }
95    }
96}
97
98// TODO: instead of bools check if we can use bitflags crate to get more efficient and ergonomic
99
100/// Switch implementation for [`InputPin`] of `embedded_hal`
101pub struct PinSwitch<P: InputPin, S: switch_state::PressedState, C: Clock> {
102    pin: P,
103    is_pressed: bool,
104    has_changed: bool,
105    last_change_at: Instant<C>,
106    prev_state_lasted: Milliseconds<C::T>,
107    pressed_state: PhantomData<S>,
108}
109
110impl<P: InputPin, S: switch_state::PressedState, C: Clock> PinSwitch<P, S, C> {
111    /// Create new [`PinSwitch`] instance for the passed in `pin`
112    pub fn new(pin: P) -> Self {
113        Self {
114            pin,
115            is_pressed: false,
116            has_changed: false,
117            last_change_at: Instant::<C>::new(C::T::from(0)),
118            prev_state_lasted: Milliseconds::<C::T>::new(C::T::from(0)),
119            pressed_state: Default::default(),
120        }
121    }
122}
123
124impl<P: InputPin, S: switch_state::PressedState, C: Clock> Switch<C> for PinSwitch<P, S, C> {
125    fn poll(&mut self, now: Instant<C>) {
126        let new_state = S::get_pressed_state(&mut self.pin);
127
128        if new_state == self.is_pressed {
129            self.has_changed = false;
130            return;
131        }
132
133        self.is_pressed = new_state;
134        self.has_changed = true;
135        self.prev_state_lasted = self.current_state(now);
136        self.last_change_at = now;
137    }
138
139    fn has_changed(&self) -> bool {
140        self.has_changed
141    }
142
143    fn is_pressed(&self) -> bool {
144        self.is_pressed
145    }
146
147    fn is_released(&self) -> bool {
148        !self.is_pressed
149    }
150
151    fn pressed_for(&self) -> Option<Milliseconds<C::T>> {
152        if !self.is_pressed {
153            return Some(self.prev_state_lasted);
154        }
155        None
156    }
157
158    fn released_for(&self) -> Option<Milliseconds<C::T>> {
159        if self.is_pressed {
160            return Some(self.prev_state_lasted);
161        }
162        None
163    }
164
165    fn wait(&mut self, clock: &C) {
166        loop {
167            self.poll(clock.try_now().unwrap());
168            if self.has_changed {
169                break;
170            }
171        }
172    }
173
174    fn reset(&mut self) {
175        self.last_change_at = Instant::<C>::new(C::T::from(0));
176        self.prev_state_lasted = Milliseconds::<C::T>::new(C::T::from(0));
177        self.has_changed = false;
178        self.is_pressed = false;
179    }
180
181    fn prev_state_lasted_for(&self) -> Milliseconds<<C as Clock>::T> {
182        self.prev_state_lasted
183    }
184
185    fn current_state(&self, now: Instant<C>) -> Milliseconds<<C as Clock>::T> {
186        now
187            .checked_duration_since(&self.last_change_at)
188            .unwrap()
189            .try_into()
190            .unwrap()
191    }
192}