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}