button_driver/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5use core::time::Duration;
6
7pub use config::{ButtonConfig, Mode};
8pub use instant::InstantProvider;
9pub use pin_wrapper::PinWrapper;
10
11/// Button configuration.
12pub mod config;
13/// Different current global time sources.
14pub mod instant;
15/// Wrappers for different APIs.
16mod pin_wrapper;
17
18#[cfg(all(test, feature = "std"))]
19mod tests;
20
21/// Generic button abstraction.
22///
23/// The crate is designed to provide a finished ([`released`](ButtonConfig#structfield.release)) state by the accessor methods.
24/// However, it is also possible to get the `raw` state using the corresponding methods.
25#[derive(Clone, Debug)]
26pub struct Button<P, I, D = Duration> {
27    /// An inner pin.
28    pub pin: P,
29    state: State<I>,
30    clicks: usize,
31    held: Option<D>,
32    holds: usize,
33    config: ButtonConfig<D>,
34}
35
36/// Represents current button state.
37///
38///
39/// State machine diagram:
40///```ignore
41/// Down => Pressed | Released
42/// Pressed => Held => Up
43/// Up => Released | Down
44/// Held => Released
45/// Released => Down
46/// Unknown => Down | Released
47/// ```
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum State<I> {
50    /// The button has been just pressed, so it is in *down* position.
51    Down(I),
52    /// Debounced press.
53    Pressed(I),
54    /// The button has been just released, so it is in *up* position.
55    Up(I),
56    /// The button is being held.
57    Held(I),
58    /// Fully released state, idle.
59    Released,
60    /// Initial state.
61    Unknown,
62}
63
64impl<I: PartialEq> State<I> {
65    /// Returns [true] if the state is [Down](State::Down).
66    pub fn is_down(&self) -> bool {
67        matches!(self, Self::Down(_))
68    }
69
70    /// Returns [true] if the state is [Pressed](State::Pressed).
71    pub fn is_pressed(&self) -> bool {
72        matches!(self, Self::Pressed(_))
73    }
74
75    /// Returns [true] if the state is [Up](State::Up).
76    pub fn is_up(&self) -> bool {
77        matches!(self, Self::Up(_))
78    }
79
80    /// Returns [true] if the state is [Held](State::Held).
81    pub fn is_held(&self) -> bool {
82        matches!(self, Self::Held(_))
83    }
84
85    /// Returns [true] if the state is [Released](State::Released).
86    pub fn is_released(&self) -> bool {
87        *self == Self::Released
88    }
89
90    /// Returns [true] if the state is [Unknown](State::Unknown).
91    pub fn is_unknown(&self) -> bool {
92        *self == Self::Unknown
93    }
94}
95
96impl<P, I, D> Button<P, I, D>
97where
98    P: PinWrapper,
99    I: InstantProvider<D> + PartialEq,
100    D: Clone + Ord,
101{
102    /// Creates a new [Button].
103    pub const fn new(pin: P, config: ButtonConfig<D>) -> Self {
104        Self {
105            pin,
106            config,
107            state: State::Unknown,
108            clicks: 0,
109            holds: 0,
110            held: None,
111        }
112    }
113
114    /// Returns the number of clicks that happened before the last release.
115    /// Returns 0 if clicks are still being counted or a new streak has started.
116    pub fn clicks(&self) -> usize {
117        if self.state == State::Released {
118            self.clicks
119        } else {
120            0
121        }
122    }
123
124    /// Returns the number of holds (how many times the button was held) that happened before the last release.
125    /// Returns 0 if clicks or holds are still being counted or a new streak has started.
126    pub fn holds(&self) -> usize {
127        if self.state == State::Released {
128            self.holds
129        } else {
130            0
131        }
132    }
133
134    /// Resets clicks amount and held time after release.
135    ///
136    /// Example:
137    ///
138    /// In this example, reset method makes "Clicked!" print once per click.
139    /// ```ignore
140    /// let mut button = Button::new(pin, ButtonConfig::default());
141    ///
142    /// loop {
143    ///     button.tick();
144    ///     
145    ///     if button.is_clicked() {
146    ///         println!("Clicked!");
147    ///     }
148    ///
149    ///     button.reset();
150    /// }
151    /// ```
152    pub fn reset(&mut self) {
153        if self.state == State::Released {
154            self.clicks = 0;
155            self.holds = 0;
156            self.held = None;
157        }
158    }
159
160    /// Returns [true] if the button was pressed once before release.
161    pub fn is_clicked(&self) -> bool {
162        self.clicks() == 1
163    }
164
165    /// Returns [true] if the button was pressed twice before release.
166    pub fn is_double_clicked(&self) -> bool {
167        self.clicks() == 2
168    }
169
170    /// Returns [true] if the button was pressed three times before release.
171    pub fn is_triple_clicked(&self) -> bool {
172        self.clicks() == 3
173    }
174
175    /// Returns holding duration before the last release.
176    /// Returns [None] if the button is still being held, not released or was not held at all.
177    pub fn held_time(&self) -> Option<D> {
178        if self.state == State::Released {
179            self.held.clone()
180        } else {
181            None
182        }
183    }
184
185    /// Returns current holding duration.
186    /// Returns [None] if the button is not being held.
187    pub fn current_holding_time(&self) -> Option<D> {
188        if let State::Held(dur) = &self.state {
189            Some(dur.elapsed())
190        } else {
191            None
192        }
193    }
194
195    /// Returns current button state.
196    pub const fn raw_state(&self) -> &State<I> {
197        &self.state
198    }
199
200    /// Returns current amount of clicks, ignoring release timeout.
201    pub const fn raw_clicks(&self) -> usize {
202        self.clicks
203    }
204
205    /// Returns current amount of holds (how many times the button was held), ignoring release timeout.
206    pub const fn raw_holds(&self) -> usize {
207        self.holds
208    }
209
210    /// Updates button state.
211    /// Call as frequently as you can, ideally in a loop in separate thread or interrupt.
212    pub fn tick(&mut self) {
213        match self.state.clone() {
214            State::Unknown if self.is_pin_pressed() => {
215                self.clicks += 1;
216                self.state = State::Down(I::now());
217            }
218            State::Unknown if self.is_pin_released() => self.state = State::Released,
219
220            State::Down(elapsed) => {
221                if self.is_pin_pressed() {
222                    if elapsed.elapsed() >= self.config.debounce {
223                        self.state = State::Pressed(elapsed.clone());
224                    } else {
225                        // debounce
226                    }
227                } else {
228                    self.state = State::Released;
229                }
230            }
231            State::Pressed(elapsed) => {
232                if self.is_pin_pressed() {
233                    if elapsed.elapsed() >= self.config.hold {
234                        // Do not count a click that leads to a hold
235                        self.clicks -= 1;
236                        self.holds += 1;
237                        self.state = State::Held(elapsed.clone());
238                    } else {
239                        // holding
240                    }
241                } else {
242                    self.state = State::Up(I::now())
243                }
244            }
245            State::Up(elapsed) => {
246                if elapsed.elapsed() < self.config.release {
247                    if self.is_pin_pressed() {
248                        self.clicks += 1;
249                        self.state = State::Down(I::now());
250                    } else {
251                        // waiting for the release timeout
252                    }
253                } else {
254                    self.state = State::Released;
255                }
256            }
257
258            State::Released if self.is_pin_pressed() => {
259                self.clicks += 1;
260                self.held = None;
261                self.state = State::Down(I::now());
262            }
263            State::Held(elapsed) => {
264                if self.is_pin_released() {
265                    // TODO: save prior held time?
266                    self.held = Some(elapsed.elapsed());
267                    self.state = State::Up(I::now());
268                } else {
269                    // holding
270                }
271            }
272            _ => {}
273        }
274    }
275
276    /// Reads current pin status, returns [true] if the button pin is released without debouncing.
277    fn is_pin_released(&mut self) -> bool {
278        self.pin.is_high() == self.config.mode.is_pullup()
279    }
280
281    /// Reads current pin status, returns [true] if the button pin is pressed without debouncing.
282    fn is_pin_pressed(&mut self) -> bool {
283        !self.is_pin_released()
284    }
285}