1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use embedded_hal::digital::InputPin;
5
6pub type Millis = u64;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ActiveLevel {
12 High,
14 Low,
16}
17
18impl ActiveLevel {
19 #[inline]
20 const fn state_from_high(self, is_high: bool) -> ButtonState {
21 match (self, is_high) {
22 (Self::High, true) | (Self::Low, false) => ButtonState::Pressed,
23 (Self::High, false) | (Self::Low, true) => ButtonState::Released,
24 }
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ButtonState {
31 Pressed,
33 Released,
35}
36
37impl ButtonState {
38 #[inline]
40 pub const fn is_pressed(self) -> bool {
41 matches!(self, Self::Pressed)
42 }
43
44 #[inline]
46 pub const fn is_released(self) -> bool {
47 matches!(self, Self::Released)
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct ButtonConfig {
54 pub debounce_ms: Millis,
57 pub double_click_ms: Millis,
60 pub hold_ms: Millis,
62 pub active_level: ActiveLevel,
64}
65
66impl ButtonConfig {
67 pub const DEFAULT_DEBOUNCE_MS: Millis = 20;
69 pub const DEFAULT_DOUBLE_CLICK_MS: Millis = 300;
71 pub const DEFAULT_HOLD_MS: Millis = 800;
73
74 #[inline]
77 pub const fn active_low() -> Self {
78 Self {
79 debounce_ms: Self::DEFAULT_DEBOUNCE_MS,
80 double_click_ms: Self::DEFAULT_DOUBLE_CLICK_MS,
81 hold_ms: Self::DEFAULT_HOLD_MS,
82 active_level: ActiveLevel::Low,
83 }
84 }
85
86 #[inline]
88 pub const fn active_high() -> Self {
89 Self {
90 active_level: ActiveLevel::High,
91 ..Self::active_low()
92 }
93 }
94
95 #[inline]
97 pub const fn with_debounce_ms(mut self, debounce_ms: Millis) -> Self {
98 self.debounce_ms = debounce_ms;
99 self
100 }
101
102 #[inline]
104 pub const fn with_double_click_ms(mut self, double_click_ms: Millis) -> Self {
105 self.double_click_ms = double_click_ms;
106 self
107 }
108
109 #[inline]
111 pub const fn with_hold_ms(mut self, hold_ms: Millis) -> Self {
112 self.hold_ms = hold_ms;
113 self
114 }
115}
116
117impl Default for ButtonConfig {
118 #[inline]
119 fn default() -> Self {
120 Self::active_low()
121 }
122}
123
124#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
126pub struct ButtonEvents {
127 pub pressed: bool,
129 pub released: bool,
131 pub click: bool,
133 pub double_click: bool,
135 pub hold: bool,
137}
138
139impl ButtonEvents {
140 #[inline]
142 pub const fn any(self) -> bool {
143 self.pressed || self.released || self.click || self.double_click || self.hold
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct ButtonUpdate {
150 pub state: ButtonState,
152 pub events: ButtonEvents,
154 pub held_ms: Option<Millis>,
156 pub released_after_ms: Option<Millis>,
158}
159
160impl ButtonUpdate {
161 #[inline]
162 const fn idle(state: ButtonState, held_ms: Option<Millis>) -> Self {
163 Self {
164 state,
165 events: ButtonEvents {
166 pressed: false,
167 released: false,
168 click: false,
169 double_click: false,
170 hold: false,
171 },
172 held_ms,
173 released_after_ms: None,
174 }
175 }
176}
177
178pub struct Button<PIN> {
184 pin: PIN,
185 config: ButtonConfig,
186 raw_state: ButtonState,
187 raw_changed_at: Millis,
188 stable_state: ButtonState,
189 pressed_at: Option<Millis>,
190 pending_click_at: Option<Millis>,
191 hold_reported: bool,
192}
193
194impl<PIN> Button<PIN> {
195 #[inline]
197 pub const fn new(pin: PIN) -> Self {
198 Self::with_config(pin, ButtonConfig::active_low())
199 }
200
201 #[inline]
203 pub const fn with_config(pin: PIN, config: ButtonConfig) -> Self {
204 Self {
205 pin,
206 config,
207 raw_state: ButtonState::Released,
208 raw_changed_at: 0,
209 stable_state: ButtonState::Released,
210 pressed_at: None,
211 pending_click_at: None,
212 hold_reported: false,
213 }
214 }
215
216 #[inline]
218 pub const fn config(&self) -> ButtonConfig {
219 self.config
220 }
221
222 #[inline]
224 pub fn set_config(&mut self, config: ButtonConfig) {
225 self.config = config;
226 }
227
228 #[inline]
230 pub const fn debounce_ms(&self) -> Millis {
231 self.config.debounce_ms
232 }
233
234 #[inline]
236 pub fn set_debounce_ms(&mut self, debounce_ms: Millis) {
237 self.config.debounce_ms = debounce_ms;
238 }
239
240 #[inline]
242 pub const fn double_click_ms(&self) -> Millis {
243 self.config.double_click_ms
244 }
245
246 #[inline]
248 pub fn set_double_click_ms(&mut self, double_click_ms: Millis) {
249 self.config.double_click_ms = double_click_ms;
250 }
251
252 #[inline]
254 pub const fn hold_ms(&self) -> Millis {
255 self.config.hold_ms
256 }
257
258 #[inline]
260 pub fn set_hold_ms(&mut self, hold_ms: Millis) {
261 self.config.hold_ms = hold_ms;
262 }
263
264 #[inline]
266 pub const fn state(&self) -> ButtonState {
267 self.stable_state
268 }
269
270 #[inline]
273 pub const fn is_pressed(&self) -> bool {
274 self.stable_state.is_pressed()
275 }
276
277 #[inline]
280 pub const fn is_released(&self) -> bool {
281 self.stable_state.is_released()
282 }
283
284 #[inline]
286 pub fn held_ms(&self, now_ms: Millis) -> Option<Millis> {
287 self.pressed_at
288 .map(|pressed_at| elapsed(now_ms, pressed_at))
289 }
290
291 #[inline]
293 pub fn pin_mut(&mut self) -> &mut PIN {
294 &mut self.pin
295 }
296
297 #[inline]
299 pub fn into_inner(self) -> PIN {
300 self.pin
301 }
302}
303
304impl<PIN> Button<PIN>
305where
306 PIN: InputPin,
307{
308 pub fn update(&mut self, now_ms: Millis) -> Result<ButtonUpdate, PIN::Error> {
338 let raw_state = self.read_raw_state()?;
339
340 if raw_state != self.raw_state {
341 self.raw_state = raw_state;
342 self.raw_changed_at = now_ms;
343 }
344
345 let mut update = ButtonUpdate::idle(self.stable_state, self.held_ms(now_ms));
346
347 if self.stable_state != self.raw_state
348 && elapsed(now_ms, self.raw_changed_at) >= self.config.debounce_ms
349 {
350 self.accept_stable_state(now_ms, &mut update);
351 }
352
353 if self.stable_state == ButtonState::Pressed {
354 let held_ms = self.held_ms(now_ms).unwrap_or(0);
355 update.held_ms = Some(held_ms);
356
357 if !self.hold_reported && held_ms >= self.config.hold_ms {
358 update.events.hold = true;
359 self.hold_reported = true;
360 }
361 }
362
363 self.report_pending_click(now_ms, &mut update);
364 update.state = self.stable_state;
365 Ok(update)
366 }
367
368 #[inline]
369 fn read_raw_state(&mut self) -> Result<ButtonState, PIN::Error> {
370 self.pin
371 .is_high()
372 .map(|is_high| self.config.active_level.state_from_high(is_high))
373 }
374
375 fn accept_stable_state(&mut self, now_ms: Millis, update: &mut ButtonUpdate) {
376 self.stable_state = self.raw_state;
377
378 match self.stable_state {
379 ButtonState::Pressed => {
380 self.pressed_at = Some(now_ms);
381 self.hold_reported = false;
382 update.events.pressed = true;
383 update.held_ms = Some(0);
384 }
385 ButtonState::Released => {
386 let held_ms = self
387 .pressed_at
388 .map(|pressed_at| elapsed(now_ms, pressed_at));
389
390 self.pressed_at = None;
391 self.hold_reported = false;
392 update.events.released = true;
393 update.held_ms = None;
394 update.released_after_ms = held_ms;
395
396 if held_ms.is_some_and(|held_ms| held_ms < self.config.hold_ms) {
397 self.register_short_release(now_ms, update);
398 }
399 }
400 }
401 }
402
403 fn register_short_release(&mut self, now_ms: Millis, update: &mut ButtonUpdate) {
404 if self.pending_click_at.is_some_and(|pending_click_at| {
405 elapsed(now_ms, pending_click_at) <= self.config.double_click_ms
406 }) {
407 update.events.double_click = true;
408 self.pending_click_at = None;
409 } else {
410 self.report_pending_click(now_ms, update);
411 self.pending_click_at = Some(now_ms);
412 }
413 }
414
415 fn report_pending_click(&mut self, now_ms: Millis, update: &mut ButtonUpdate) {
416 if self.pending_click_at.is_some_and(|pending_click_at| {
417 elapsed(now_ms, pending_click_at) > self.config.double_click_ms
418 }) {
419 update.events.click = true;
420 self.pending_click_at = None;
421 }
422 }
423}
424
425#[inline]
426const fn elapsed(now_ms: Millis, earlier_ms: Millis) -> Millis {
427 now_ms.saturating_sub(earlier_ms)
428}
429
430#[cfg(test)]
431extern crate std;
432
433#[cfg(test)]
434mod tests {
435 use std::io::ErrorKind;
436
437 use embedded_hal_mock::eh1::{
438 digital::{Mock as PinMock, State as PinState, Transaction as PinTransaction},
439 MockError,
440 };
441
442 use super::*;
443
444 fn config() -> ButtonConfig {
445 ButtonConfig::active_low()
446 .with_debounce_ms(10)
447 .with_double_click_ms(200)
448 .with_hold_ms(1000)
449 }
450
451 #[test]
452 fn filters_bounce_and_reports_press_release() {
453 let expectations = [
454 PinTransaction::get(PinState::High),
455 PinTransaction::get(PinState::Low),
456 PinTransaction::get(PinState::High),
457 PinTransaction::get(PinState::Low),
458 PinTransaction::get(PinState::Low),
459 PinTransaction::get(PinState::Low),
460 PinTransaction::get(PinState::High),
461 PinTransaction::get(PinState::High),
462 PinTransaction::get(PinState::High),
463 PinTransaction::get(PinState::High),
464 ];
465 let pin = PinMock::new(&expectations);
466 let mut button = Button::with_config(pin, config());
467
468 assert_eq!(button.update(0).unwrap().state, ButtonState::Released);
469 assert!(!button.update(1).unwrap().events.any());
470 assert!(!button.update(5).unwrap().events.any());
471 assert!(!button.update(7).unwrap().events.any());
472 assert!(!button.update(16).unwrap().events.any());
473
474 let update = button.update(17).unwrap();
475 assert_eq!(update.state, ButtonState::Pressed);
476 assert!(update.events.pressed);
477 assert_eq!(update.held_ms, Some(0));
478
479 assert!(!button.update(30).unwrap().events.any());
480 assert!(!button.update(35).unwrap().events.any());
481
482 let update = button.update(40).unwrap();
483 assert_eq!(update.state, ButtonState::Released);
484 assert!(update.events.released);
485 assert!(!update.events.click);
486 assert!(!update.events.double_click);
487 assert_eq!(update.released_after_ms, Some(23));
488
489 let update = button.update(241).unwrap();
490 assert_eq!(update.state, ButtonState::Released);
491 assert!(update.events.click);
492 assert!(!update.events.double_click);
493 assert_eq!(update.released_after_ms, None);
494
495 button.into_inner().done();
496 }
497
498 #[test]
499 fn detects_double_click() {
500 let expectations = [
501 PinTransaction::get(PinState::Low),
502 PinTransaction::get(PinState::Low),
503 PinTransaction::get(PinState::High),
504 PinTransaction::get(PinState::High),
505 PinTransaction::get(PinState::Low),
506 PinTransaction::get(PinState::Low),
507 PinTransaction::get(PinState::High),
508 PinTransaction::get(PinState::High),
509 ];
510 let pin = PinMock::new(&expectations);
511 let mut button = Button::with_config(pin, config().with_debounce_ms(5).with_hold_ms(1000));
512
513 assert!(!button.update(0).unwrap().events.any());
514 assert!(button.update(5).unwrap().events.pressed);
515
516 assert!(!button.update(20).unwrap().events.any());
517 let update = button.update(25).unwrap();
518 assert!(update.events.released);
519 assert!(!update.events.click);
520 assert!(!update.events.double_click);
521
522 assert!(!button.update(80).unwrap().events.any());
523 assert!(button.update(85).unwrap().events.pressed);
524
525 assert!(!button.update(100).unwrap().events.any());
526 let update = button.update(105).unwrap();
527 assert!(update.events.released);
528 assert!(!update.events.click);
529 assert!(update.events.double_click);
530
531 button.into_inner().done();
532 }
533
534 #[test]
535 fn reports_hold_once_and_tracks_hold_time() {
536 let expectations = [
537 PinTransaction::get(PinState::Low),
538 PinTransaction::get(PinState::Low),
539 PinTransaction::get(PinState::Low),
540 PinTransaction::get(PinState::Low),
541 PinTransaction::get(PinState::Low),
542 PinTransaction::get(PinState::High),
543 PinTransaction::get(PinState::High),
544 ];
545 let pin = PinMock::new(&expectations);
546 let mut button = Button::with_config(pin, config().with_debounce_ms(5).with_hold_ms(50));
547
548 assert!(!button.update(0).unwrap().events.any());
549 assert!(button.update(5).unwrap().events.pressed);
550
551 let update = button.update(54).unwrap();
552 assert_eq!(update.held_ms, Some(49));
553 assert!(!update.events.hold);
554
555 let update = button.update(55).unwrap();
556 assert_eq!(update.held_ms, Some(50));
557 assert!(update.events.hold);
558
559 let update = button.update(80).unwrap();
560 assert_eq!(update.held_ms, Some(75));
561 assert!(!update.events.hold);
562
563 assert!(!button.update(100).unwrap().events.any());
564 let update = button.update(105).unwrap();
565 assert!(update.events.released);
566 assert!(!update.events.click);
567 assert_eq!(update.released_after_ms, Some(100));
568
569 button.into_inner().done();
570 }
571
572 #[test]
573 fn supports_active_high_and_runtime_debounce_setting() {
574 let expectations = [
575 PinTransaction::get(PinState::Low),
576 PinTransaction::get(PinState::High),
577 PinTransaction::get(PinState::High),
578 ];
579 let pin = PinMock::new(&expectations);
580 let mut button = Button::with_config(pin, ButtonConfig::active_high());
581
582 button.set_debounce_ms(3);
583 assert_eq!(button.debounce_ms(), 3);
584
585 assert_eq!(button.update(0).unwrap().state, ButtonState::Released);
586 assert!(!button.update(1).unwrap().events.any());
587
588 let update = button.update(4).unwrap();
589 assert_eq!(update.state, ButtonState::Pressed);
590 assert!(update.events.pressed);
591
592 button.into_inner().done();
593 }
594
595 #[test]
596 fn propagates_pin_errors() {
597 let expectations =
598 [PinTransaction::get(PinState::High).with_error(MockError::Io(ErrorKind::Other))];
599 let pin = PinMock::new(&expectations);
600 let mut button = Button::new(pin);
601
602 assert!(button.update(0).is_err());
603
604 button.into_inner().done();
605 }
606}