embedded_controls/
debounced_input.rs

1use crate::{Control, Error};
2
3use core::marker::PhantomData;
4use switch_hal::InputSwitch;
5use timestamp_source::{ElapsedTimer, Timestamp};
6
7/// Represents a config for [`DebouncedInput`](crate::DebouncedInput).
8pub trait DebouncedInputConfig {
9    /// Elapsed timer type that used for [`DebouncedInput`](crate::DebouncedInput).
10    type Timer: ElapsedTimer;
11
12    /// Elapsed timer instance that used for [`DebouncedInput`](crate::DebouncedInput).
13    /// This timer is used for debounce of input by timeout after disturbance start.
14    const DEBOUNCE_TIMER: Self::Timer;
15}
16
17/// The state machine of [`DebouncedInput`](crate::DebouncedInput).
18pub enum DebouncedInputState<T> {
19    FixedLow,
20    FixedHigh,
21    RiseDisturbance(T),
22    FallDisturbance(T),
23}
24
25/// Concrete implementation of debounced input.
26///
27/// # Type Params
28/// `Switch` - [`InputSwitch`](switch_hal::InputSwitch) that provides input for debouncing.
29///
30/// `Config` - [`DebouncedInputConfig`](crate::DebouncedInputConfig) that provides configs for debouncing.
31///
32/// # Example
33/// ```ignore
34/// debounced_input_config!(
35///     SomeDebouncedInputConfig,
36///     debounce_timer: MyElapsedTimer = MyElapsedTimer::new(20.millis())
37/// );
38///
39/// type MyDebouncedInput<Switch> = DebouncedInput<Switch, SomeDebouncedInputConfig>;
40///
41/// let mut debounced_input = MyDebouncedInput::new(pin.into_active_low_switch());
42///
43/// loop {
44///     match debounced_input.update().unwrap() {
45///         DebouncedInputEvent::Low => do_something_when_low(),
46///         DebouncedInputEvent::High => do_something_when_high(),
47///         DebouncedInputEvent::Rise => do_something_upon_rise(),
48///         DebouncedInputEvent::Fall => do_something_upon_fall(),
49///     }
50/// }
51/// ```
52pub struct DebouncedInput<Switch: InputSwitch, Config: DebouncedInputConfig> {
53    input_switch: Switch,
54    state: DebouncedInputState<<Config::Timer as ElapsedTimer>::Timestamp>,
55    config: PhantomData<Config>,
56}
57
58/// The event result of update [`DebouncedInput`](crate::DebouncedInput).
59#[derive(Debug, Clone, Copy, PartialEq)]
60pub enum DebouncedInputEvent {
61    /// Stable low state, the input is inactive.
62    Low,
63    /// Stable high state, the input is active.
64    High,
65    /// Rise event, the input is rised from inactive to active state.
66    Rise,
67    /// Fall event, the input is rised from active to inactive state.
68    Fall,
69}
70
71impl<Switch: InputSwitch, Config: DebouncedInputConfig> DebouncedInput<Switch, Config> {
72    /// Creates a new [`DebouncedInput<Switch, Config>`] from a concrete `Switch`.
73    ///
74    /// `input_switch` - an concrete instance of `Switch`.
75    pub fn new(input_switch: Switch) -> Self {
76        let init_state = if input_switch.is_active().unwrap_or(false) {
77            DebouncedInputState::FixedHigh
78        } else {
79            DebouncedInputState::FixedLow
80        };
81
82        DebouncedInput {
83            input_switch,
84            state: init_state,
85            config: PhantomData::<Config>,
86        }
87    }
88
89    /// Returns the is stable high state.
90    pub fn is_high(&self) -> bool {
91        match self.state {
92            DebouncedInputState::FixedLow | DebouncedInputState::RiseDisturbance(_) => false,
93            DebouncedInputState::FixedHigh | DebouncedInputState::FallDisturbance(_) => true,
94        }
95    }
96
97    /// Returns the is stable low state.
98    pub fn is_low(&self) -> bool {
99        !self.is_high()
100    }
101
102    /// Borrow `Switch`.
103    pub fn borrow_input_switch(&self) -> &Switch {
104        &self.input_switch
105    }
106
107    /// Consumes `self` and release `Switch`.
108    pub fn release_input_switch(self) -> Switch {
109        self.input_switch
110    }
111}
112
113impl<Switch: InputSwitch, Config: DebouncedInputConfig> Control for DebouncedInput<Switch, Config> {
114    type Event = DebouncedInputEvent;
115    type Error =
116        Error<<<Config::Timer as ElapsedTimer>::Timestamp as Timestamp>::Error, Switch::Error>;
117
118    fn update(&mut self) -> Result<Self::Event, Self::Error> {
119        let now = <Config::Timer as ElapsedTimer>::Timestamp::now();
120        let input_switch_state = self
121            .input_switch
122            .is_active()
123            .map_err(|err| Error::InputSwitch(err))?;
124
125        Ok(match &self.state {
126            DebouncedInputState::FixedLow => {
127                if input_switch_state {
128                    self.state = DebouncedInputState::RiseDisturbance(now)
129                }
130                DebouncedInputEvent::Low
131            }
132            DebouncedInputState::FixedHigh => {
133                if !input_switch_state {
134                    self.state = DebouncedInputState::FallDisturbance(now)
135                }
136                DebouncedInputEvent::High
137            }
138            DebouncedInputState::RiseDisturbance(start) => {
139                if !input_switch_state {
140                    self.state = DebouncedInputState::FixedLow;
141                    DebouncedInputEvent::Low
142                } else if Config::DEBOUNCE_TIMER
143                    .timeout(start, &now)
144                    .map_err(|err| Error::ElapsedTimer(err))?
145                {
146                    self.state = DebouncedInputState::FixedHigh;
147                    DebouncedInputEvent::Rise
148                } else {
149                    DebouncedInputEvent::Low
150                }
151            }
152            DebouncedInputState::FallDisturbance(start) => {
153                if input_switch_state {
154                    self.state = DebouncedInputState::FixedHigh;
155                    DebouncedInputEvent::High
156                } else if Config::DEBOUNCE_TIMER
157                    .timeout(start, &now)
158                    .map_err(|err| Error::ElapsedTimer(err))?
159                {
160                    self.state = DebouncedInputState::FixedLow;
161                    DebouncedInputEvent::Fall
162                } else {
163                    DebouncedInputEvent::High
164                }
165            }
166        })
167    }
168}