Skip to main content

debounce_button_eng/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use embedded_hal::digital::InputPin;
5
6/// Monotonic time in milliseconds.
7pub type Millis = u64;
8
9/// Electrical level that represents a pressed button.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ActiveLevel {
12    /// The button is pressed when the input pin reads high.
13    High,
14    /// The button is pressed when the input pin reads low.
15    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/// Stable button state after debounce filtering.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ButtonState {
31    /// The button is pressed.
32    Pressed,
33    /// The button is released.
34    Released,
35}
36
37impl ButtonState {
38    /// Returns `true` when the state is [`ButtonState::Pressed`].
39    #[inline]
40    pub const fn is_pressed(self) -> bool {
41        matches!(self, Self::Pressed)
42    }
43
44    /// Returns `true` when the state is [`ButtonState::Released`].
45    #[inline]
46    pub const fn is_released(self) -> bool {
47        matches!(self, Self::Released)
48    }
49}
50
51/// Button timing and polarity configuration.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct ButtonConfig {
54    /// Time the raw pin state must remain unchanged before it is accepted as
55    /// the stable debounced state.
56    pub debounce_ms: Millis,
57    /// Maximum interval between two short releases that still counts as a
58    /// double click.
59    pub double_click_ms: Millis,
60    /// Time after which a continuously pressed button reports a hold event.
61    pub hold_ms: Millis,
62    /// Number of short clicks required to emit [`ButtonEvents::multi_click`].
63    ///
64    /// Values `0` and `1` disable the multi-click detector.
65    pub multi_click_count: u8,
66    /// Maximum pause between short clicks in one multi-click sequence.
67    pub multi_click_timeout_ms: Millis,
68    /// Electrical level that represents the pressed state.
69    pub active_level: ActiveLevel,
70}
71
72impl ButtonConfig {
73    /// Default debounce time.
74    pub const DEFAULT_DEBOUNCE_MS: Millis = 20;
75    /// Default double-click interval.
76    pub const DEFAULT_DOUBLE_CLICK_MS: Millis = 300;
77    /// Default hold threshold.
78    pub const DEFAULT_HOLD_MS: Millis = 800;
79    /// Default multi-click target count. `0` keeps the detector disabled.
80    pub const DEFAULT_MULTI_CLICK_COUNT: u8 = 0;
81    /// Default timeout for the multi-click detector.
82    pub const DEFAULT_MULTI_CLICK_TIMEOUT_MS: Millis = 3_000;
83
84    /// Creates a configuration for the common pull-up wiring where the pressed
85    /// state is low.
86    #[inline]
87    pub const fn active_low() -> Self {
88        Self {
89            debounce_ms: Self::DEFAULT_DEBOUNCE_MS,
90            double_click_ms: Self::DEFAULT_DOUBLE_CLICK_MS,
91            hold_ms: Self::DEFAULT_HOLD_MS,
92            multi_click_count: Self::DEFAULT_MULTI_CLICK_COUNT,
93            multi_click_timeout_ms: Self::DEFAULT_MULTI_CLICK_TIMEOUT_MS,
94            active_level: ActiveLevel::Low,
95        }
96    }
97
98    /// Creates a configuration where the pressed state is high.
99    #[inline]
100    pub const fn active_high() -> Self {
101        Self {
102            active_level: ActiveLevel::High,
103            ..Self::active_low()
104        }
105    }
106
107    /// Returns the configuration with a different debounce time.
108    #[inline]
109    pub const fn with_debounce_ms(mut self, debounce_ms: Millis) -> Self {
110        self.debounce_ms = debounce_ms;
111        self
112    }
113
114    /// Returns the configuration with a different double-click interval.
115    #[inline]
116    pub const fn with_double_click_ms(mut self, double_click_ms: Millis) -> Self {
117        self.double_click_ms = double_click_ms;
118        self
119    }
120
121    /// Returns the configuration with a different hold threshold.
122    #[inline]
123    pub const fn with_hold_ms(mut self, hold_ms: Millis) -> Self {
124        self.hold_ms = hold_ms;
125        self
126    }
127
128    /// Returns the configuration with a different multi-click target count.
129    ///
130    /// Values `0` and `1` disable the detector. Use `2` for a configurable
131    /// double click, `5` for five short clicks, and so on.
132    #[inline]
133    pub const fn with_multi_click_count(mut self, multi_click_count: u8) -> Self {
134        self.multi_click_count = multi_click_count;
135        self
136    }
137
138    /// Returns the configuration with a different multi-click timeout.
139    ///
140    /// The timeout is the maximum pause between two short clicks in the same
141    /// sequence. If the pause is greater than this value, the sequence count is
142    /// reset.
143    #[inline]
144    pub const fn with_multi_click_timeout_ms(mut self, multi_click_timeout_ms: Millis) -> Self {
145        self.multi_click_timeout_ms = multi_click_timeout_ms;
146        self
147    }
148
149    /// Returns the configuration with a multi-click target and timeout.
150    #[inline]
151    pub const fn with_multi_click(
152        mut self,
153        multi_click_count: u8,
154        multi_click_timeout_ms: Millis,
155    ) -> Self {
156        self.multi_click_count = multi_click_count;
157        self.multi_click_timeout_ms = multi_click_timeout_ms;
158        self
159    }
160}
161
162impl Default for ButtonConfig {
163    #[inline]
164    fn default() -> Self {
165        Self::active_low()
166    }
167}
168
169/// Events produced by one [`Button::update`] call.
170#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
171pub struct ButtonEvents {
172    /// The stable state changed to pressed.
173    pub pressed: bool,
174    /// The stable state changed to released.
175    pub released: bool,
176    /// A short single click was confirmed.
177    pub click: bool,
178    /// Two short clicks completed inside the configured interval.
179    pub double_click: bool,
180    /// The configured number of short clicks completed before the pause
181    /// between clicks exceeded `multi_click_timeout_ms`.
182    pub multi_click: bool,
183    /// The button reached the configured hold threshold.
184    pub hold: bool,
185}
186
187impl ButtonEvents {
188    /// Returns `true` if at least one event flag is set.
189    #[inline]
190    pub const fn any(self) -> bool {
191        self.pressed
192            || self.released
193            || self.click
194            || self.double_click
195            || self.multi_click
196            || self.hold
197    }
198}
199
200/// Result of one button update.
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub struct ButtonUpdate {
203    /// Current stable debounced state after the update.
204    pub state: ButtonState,
205    /// Events produced by this update.
206    pub events: ButtonEvents,
207    /// Current hold time while the button is pressed.
208    pub held_ms: Option<Millis>,
209    /// Total hold time when this update released the button.
210    pub released_after_ms: Option<Millis>,
211    /// Current short-click count in the active multi-click sequence.
212    ///
213    /// The value is `0` when the detector is disabled or no sequence is active.
214    /// When [`ButtonEvents::multi_click`] is true, this field reports the
215    /// target count that fired the event.
216    pub multi_click_count: u8,
217}
218
219impl ButtonUpdate {
220    #[inline]
221    const fn idle(state: ButtonState, held_ms: Option<Millis>, multi_click_count: u8) -> Self {
222        Self {
223            state,
224            events: ButtonEvents {
225                pressed: false,
226                released: false,
227                click: false,
228                double_click: false,
229                multi_click: false,
230                hold: false,
231            },
232            held_ms,
233            released_after_ms: None,
234            multi_click_count,
235        }
236    }
237}
238
239/// Debounced wrapper around an `embedded-hal` input pin.
240///
241/// Call [`Button::update`] regularly and pass a monotonic millisecond
242/// timestamp. The timestamp can come from a hardware timer, a system tick, or
243/// any other counter that never goes backwards.
244pub struct Button<PIN> {
245    pin: PIN,
246    config: ButtonConfig,
247    raw_state: ButtonState,
248    raw_changed_at: Millis,
249    stable_state: ButtonState,
250    pressed_at: Option<Millis>,
251    pending_click_at: Option<Millis>,
252    multi_click_seen: u8,
253    multi_click_last_at: Option<Millis>,
254    hold_reported: bool,
255}
256
257impl<PIN> Button<PIN> {
258    /// Creates an active-low button with default timings.
259    #[inline]
260    pub const fn new(pin: PIN) -> Self {
261        Self::with_config(pin, ButtonConfig::active_low())
262    }
263
264    /// Creates a button with custom timings and polarity.
265    #[inline]
266    pub const fn with_config(pin: PIN, config: ButtonConfig) -> Self {
267        Self {
268            pin,
269            config,
270            raw_state: ButtonState::Released,
271            raw_changed_at: 0,
272            stable_state: ButtonState::Released,
273            pressed_at: None,
274            pending_click_at: None,
275            multi_click_seen: 0,
276            multi_click_last_at: None,
277            hold_reported: false,
278        }
279    }
280
281    /// Returns the current configuration.
282    #[inline]
283    pub const fn config(&self) -> ButtonConfig {
284        self.config
285    }
286
287    /// Replaces the whole configuration.
288    #[inline]
289    pub fn set_config(&mut self, config: ButtonConfig) {
290        self.config = config;
291
292        if config.multi_click_count < 2 {
293            self.reset_multi_click();
294        }
295    }
296
297    /// Returns the current debounce time.
298    #[inline]
299    pub const fn debounce_ms(&self) -> Millis {
300        self.config.debounce_ms
301    }
302
303    /// Sets the debounce time.
304    #[inline]
305    pub fn set_debounce_ms(&mut self, debounce_ms: Millis) {
306        self.config.debounce_ms = debounce_ms;
307    }
308
309    /// Returns the current double-click interval.
310    #[inline]
311    pub const fn double_click_ms(&self) -> Millis {
312        self.config.double_click_ms
313    }
314
315    /// Sets the double-click interval.
316    #[inline]
317    pub fn set_double_click_ms(&mut self, double_click_ms: Millis) {
318        self.config.double_click_ms = double_click_ms;
319    }
320
321    /// Returns the current hold threshold.
322    #[inline]
323    pub const fn hold_ms(&self) -> Millis {
324        self.config.hold_ms
325    }
326
327    /// Sets the hold threshold.
328    #[inline]
329    pub fn set_hold_ms(&mut self, hold_ms: Millis) {
330        self.config.hold_ms = hold_ms;
331    }
332
333    /// Returns the configured multi-click target count.
334    ///
335    /// Values `0` and `1` mean that multi-click detection is disabled.
336    #[inline]
337    pub const fn multi_click_count(&self) -> u8 {
338        self.config.multi_click_count
339    }
340
341    /// Sets the multi-click target count.
342    ///
343    /// Values `0` and `1` disable detection and clear the active sequence.
344    #[inline]
345    pub fn set_multi_click_count(&mut self, multi_click_count: u8) {
346        self.config.multi_click_count = multi_click_count;
347
348        if multi_click_count < 2 {
349            self.reset_multi_click();
350        }
351    }
352
353    /// Returns the maximum pause between short clicks in one multi-click
354    /// sequence.
355    #[inline]
356    pub const fn multi_click_timeout_ms(&self) -> Millis {
357        self.config.multi_click_timeout_ms
358    }
359
360    /// Sets the maximum pause between short clicks in one multi-click sequence.
361    #[inline]
362    pub fn set_multi_click_timeout_ms(&mut self, multi_click_timeout_ms: Millis) {
363        self.config.multi_click_timeout_ms = multi_click_timeout_ms;
364    }
365
366    /// Clears the active multi-click sequence.
367    #[inline]
368    pub fn reset_multi_click(&mut self) {
369        self.multi_click_seen = 0;
370        self.multi_click_last_at = None;
371    }
372
373    #[inline]
374    fn visible_multi_click_count(&self) -> u8 {
375        if self.config.multi_click_count < 2 {
376            0
377        } else {
378            self.multi_click_seen
379        }
380    }
381
382    /// Returns the stable debounced state.
383    #[inline]
384    pub const fn state(&self) -> ButtonState {
385        self.stable_state
386    }
387
388    /// Returns `true` if the stable debounced state is
389    /// [`ButtonState::Pressed`].
390    #[inline]
391    pub const fn is_pressed(&self) -> bool {
392        self.stable_state.is_pressed()
393    }
394
395    /// Returns `true` if the stable debounced state is
396    /// [`ButtonState::Released`].
397    #[inline]
398    pub const fn is_released(&self) -> bool {
399        self.stable_state.is_released()
400    }
401
402    /// Returns the current debounced hold time.
403    #[inline]
404    pub fn held_ms(&self, now_ms: Millis) -> Option<Millis> {
405        self.pressed_at
406            .map(|pressed_at| elapsed(now_ms, pressed_at))
407    }
408
409    /// Returns a mutable reference to the inner pin.
410    #[inline]
411    pub fn pin_mut(&mut self) -> &mut PIN {
412        &mut self.pin
413    }
414
415    /// Returns the inner pin.
416    #[inline]
417    pub fn into_inner(self) -> PIN {
418        self.pin
419    }
420}
421
422impl<PIN> Button<PIN>
423where
424    PIN: InputPin,
425{
426    /// Reads the pin once, updates the debouncer, and returns the current state
427    /// with events.
428    ///
429    /// This method does not block and does not wait for debounce to finish. It
430    /// should be called regularly with a fresh monotonic timestamp in
431    /// `now_ms`.
432    ///
433    /// One call:
434    ///
435    /// 1. Reads the physical pin through `InputPin::is_high`.
436    /// 2. Converts the electrical level to [`ButtonState`] using
437    ///    [`ActiveLevel`].
438    /// 3. Records a raw state change and its timestamp.
439    /// 4. Accepts a raw state as stable when it has been unchanged for at least
440    ///    `debounce_ms`.
441    /// 5. Emits `events.pressed` when the stable state becomes
442    ///    [`ButtonState::Pressed`].
443    /// 6. Emits `events.released` and `released_after_ms` when the stable state
444    ///    becomes [`ButtonState::Released`].
445    /// 7. Stores a pending single click after a short release.
446    /// 8. Emits `events.double_click` if a second short click arrives inside
447    ///    `double_click_ms`, without emitting an earlier `events.click`.
448    /// 9. Emits `events.click` once if the pending click is not followed by a
449    ///    second click before `double_click_ms` expires.
450    /// 10. Updates the multi-click sequence after each short release and emits
451    ///     `events.multi_click` when the configured count is reached before
452    ///     `multi_click_timeout_ms` expires between clicks.
453    /// 11. Updates `held_ms` while the button is pressed and emits
454    ///     `events.hold` once the hold threshold is reached.
455    ///
456    /// Event flags are valid only for the returned update. Read the stable
457    /// state from [`ButtonUpdate::state`].
458    pub fn update(&mut self, now_ms: Millis) -> Result<ButtonUpdate, PIN::Error> {
459        let raw_state = self.read_raw_state()?;
460        self.reset_expired_multi_click(now_ms);
461
462        if raw_state != self.raw_state {
463            self.raw_state = raw_state;
464            self.raw_changed_at = now_ms;
465        }
466
467        let mut update = ButtonUpdate::idle(
468            self.stable_state,
469            self.held_ms(now_ms),
470            self.visible_multi_click_count(),
471        );
472
473        if self.stable_state != self.raw_state
474            && elapsed(now_ms, self.raw_changed_at) >= self.config.debounce_ms
475        {
476            self.accept_stable_state(now_ms, &mut update);
477        }
478
479        if self.stable_state == ButtonState::Pressed {
480            let held_ms = self.held_ms(now_ms).unwrap_or(0);
481            update.held_ms = Some(held_ms);
482
483            if !self.hold_reported && held_ms >= self.config.hold_ms {
484                update.events.hold = true;
485                self.hold_reported = true;
486            }
487        }
488
489        self.report_pending_click(now_ms, &mut update);
490        update.state = self.stable_state;
491        Ok(update)
492    }
493
494    #[inline]
495    fn read_raw_state(&mut self) -> Result<ButtonState, PIN::Error> {
496        self.pin
497            .is_high()
498            .map(|is_high| self.config.active_level.state_from_high(is_high))
499    }
500
501    fn accept_stable_state(&mut self, now_ms: Millis, update: &mut ButtonUpdate) {
502        self.stable_state = self.raw_state;
503
504        match self.stable_state {
505            ButtonState::Pressed => {
506                self.pressed_at = Some(now_ms);
507                self.hold_reported = false;
508                update.events.pressed = true;
509                update.held_ms = Some(0);
510            }
511            ButtonState::Released => {
512                let held_ms = self
513                    .pressed_at
514                    .map(|pressed_at| elapsed(now_ms, pressed_at));
515
516                self.pressed_at = None;
517                self.hold_reported = false;
518                update.events.released = true;
519                update.held_ms = None;
520                update.released_after_ms = held_ms;
521
522                if held_ms.is_some_and(|held_ms| held_ms < self.config.hold_ms) {
523                    self.register_short_release(now_ms, update);
524                } else {
525                    self.reset_multi_click();
526                }
527            }
528        }
529    }
530
531    fn register_short_release(&mut self, now_ms: Millis, update: &mut ButtonUpdate) {
532        let multi_click_fired = self.register_multi_click(now_ms, update);
533
534        if self.pending_click_at.is_some_and(|pending_click_at| {
535            elapsed(now_ms, pending_click_at) <= self.config.double_click_ms
536        }) {
537            update.events.double_click = true;
538            self.pending_click_at = None;
539        } else if multi_click_fired {
540            self.pending_click_at = None;
541        } else {
542            self.report_pending_click(now_ms, update);
543            self.pending_click_at = Some(now_ms);
544        }
545    }
546
547    fn report_pending_click(&mut self, now_ms: Millis, update: &mut ButtonUpdate) {
548        if self.has_active_multi_click_sequence(now_ms) {
549            return;
550        }
551
552        if self.pending_click_at.is_some_and(|pending_click_at| {
553            elapsed(now_ms, pending_click_at) > self.config.double_click_ms
554        }) {
555            update.events.click = true;
556            self.pending_click_at = None;
557        }
558    }
559
560    fn register_multi_click(&mut self, now_ms: Millis, update: &mut ButtonUpdate) -> bool {
561        if self.config.multi_click_count < 2 {
562            update.multi_click_count = 0;
563            return false;
564        }
565
566        self.reset_expired_multi_click(now_ms);
567
568        self.multi_click_seen = self.multi_click_seen.saturating_add(1);
569        self.multi_click_last_at = Some(now_ms);
570        update.multi_click_count = self.multi_click_seen;
571
572        if self.multi_click_seen >= self.config.multi_click_count {
573            update.events.multi_click = true;
574            update.multi_click_count = self.config.multi_click_count;
575            self.reset_multi_click();
576            return true;
577        }
578
579        false
580    }
581
582    fn reset_expired_multi_click(&mut self, now_ms: Millis) {
583        if self
584            .multi_click_last_at
585            .is_some_and(|last_at| elapsed(now_ms, last_at) > self.config.multi_click_timeout_ms)
586        {
587            self.reset_multi_click();
588        }
589    }
590
591    fn has_active_multi_click_sequence(&self, now_ms: Millis) -> bool {
592        self.config.multi_click_count >= 2
593            && self.multi_click_seen > 0
594            && self.multi_click_last_at.is_some_and(|last_at| {
595                elapsed(now_ms, last_at) <= self.config.multi_click_timeout_ms
596            })
597    }
598}
599
600#[inline]
601const fn elapsed(now_ms: Millis, earlier_ms: Millis) -> Millis {
602    now_ms.saturating_sub(earlier_ms)
603}
604
605#[cfg(test)]
606extern crate std;
607
608#[cfg(test)]
609mod tests {
610    use std::io::ErrorKind;
611
612    use embedded_hal_mock::eh1::{
613        digital::{Mock as PinMock, State as PinState, Transaction as PinTransaction},
614        MockError,
615    };
616
617    use super::*;
618
619    fn config() -> ButtonConfig {
620        ButtonConfig::active_low()
621            .with_debounce_ms(10)
622            .with_double_click_ms(200)
623            .with_hold_ms(1000)
624    }
625
626    #[test]
627    fn filters_bounce_and_reports_press_release() {
628        let expectations = [
629            PinTransaction::get(PinState::High),
630            PinTransaction::get(PinState::Low),
631            PinTransaction::get(PinState::High),
632            PinTransaction::get(PinState::Low),
633            PinTransaction::get(PinState::Low),
634            PinTransaction::get(PinState::Low),
635            PinTransaction::get(PinState::High),
636            PinTransaction::get(PinState::High),
637            PinTransaction::get(PinState::High),
638            PinTransaction::get(PinState::High),
639        ];
640        let pin = PinMock::new(&expectations);
641        let mut button = Button::with_config(pin, config());
642
643        assert_eq!(button.update(0).unwrap().state, ButtonState::Released);
644        assert!(!button.update(1).unwrap().events.any());
645        assert!(!button.update(5).unwrap().events.any());
646        assert!(!button.update(7).unwrap().events.any());
647        assert!(!button.update(16).unwrap().events.any());
648
649        let update = button.update(17).unwrap();
650        assert_eq!(update.state, ButtonState::Pressed);
651        assert!(update.events.pressed);
652        assert_eq!(update.held_ms, Some(0));
653
654        assert!(!button.update(30).unwrap().events.any());
655        assert!(!button.update(35).unwrap().events.any());
656
657        let update = button.update(40).unwrap();
658        assert_eq!(update.state, ButtonState::Released);
659        assert!(update.events.released);
660        assert!(!update.events.click);
661        assert!(!update.events.double_click);
662        assert_eq!(update.released_after_ms, Some(23));
663
664        let update = button.update(241).unwrap();
665        assert_eq!(update.state, ButtonState::Released);
666        assert!(update.events.click);
667        assert!(!update.events.double_click);
668        assert_eq!(update.released_after_ms, None);
669
670        button.into_inner().done();
671    }
672
673    #[test]
674    fn detects_double_click() {
675        let expectations = [
676            PinTransaction::get(PinState::Low),
677            PinTransaction::get(PinState::Low),
678            PinTransaction::get(PinState::High),
679            PinTransaction::get(PinState::High),
680            PinTransaction::get(PinState::Low),
681            PinTransaction::get(PinState::Low),
682            PinTransaction::get(PinState::High),
683            PinTransaction::get(PinState::High),
684        ];
685        let pin = PinMock::new(&expectations);
686        let mut button = Button::with_config(pin, config().with_debounce_ms(5).with_hold_ms(1000));
687
688        assert!(!button.update(0).unwrap().events.any());
689        assert!(button.update(5).unwrap().events.pressed);
690
691        assert!(!button.update(20).unwrap().events.any());
692        let update = button.update(25).unwrap();
693        assert!(update.events.released);
694        assert!(!update.events.click);
695        assert!(!update.events.double_click);
696
697        assert!(!button.update(80).unwrap().events.any());
698        assert!(button.update(85).unwrap().events.pressed);
699
700        assert!(!button.update(100).unwrap().events.any());
701        let update = button.update(105).unwrap();
702        assert!(update.events.released);
703        assert!(!update.events.click);
704        assert!(update.events.double_click);
705
706        button.into_inner().done();
707    }
708
709    #[test]
710    fn detects_configured_multi_click_count() {
711        let expectations = [
712            PinTransaction::get(PinState::Low),
713            PinTransaction::get(PinState::Low),
714            PinTransaction::get(PinState::High),
715            PinTransaction::get(PinState::High),
716            PinTransaction::get(PinState::Low),
717            PinTransaction::get(PinState::Low),
718            PinTransaction::get(PinState::High),
719            PinTransaction::get(PinState::High),
720            PinTransaction::get(PinState::Low),
721            PinTransaction::get(PinState::Low),
722            PinTransaction::get(PinState::High),
723            PinTransaction::get(PinState::High),
724            PinTransaction::get(PinState::Low),
725            PinTransaction::get(PinState::Low),
726            PinTransaction::get(PinState::High),
727            PinTransaction::get(PinState::High),
728            PinTransaction::get(PinState::Low),
729            PinTransaction::get(PinState::Low),
730            PinTransaction::get(PinState::High),
731            PinTransaction::get(PinState::High),
732        ];
733        let pin = PinMock::new(&expectations);
734        let mut button = Button::with_config(
735            pin,
736            config()
737                .with_debounce_ms(5)
738                .with_hold_ms(1000)
739                .with_multi_click(5, 3_000),
740        );
741
742        assert_eq!(button.multi_click_count(), 5);
743        assert_eq!(button.multi_click_timeout_ms(), 3_000);
744
745        assert!(!button.update(0).unwrap().events.any());
746        assert!(button.update(5).unwrap().events.pressed);
747        assert!(!button.update(20).unwrap().events.any());
748        let update = button.update(25).unwrap();
749        assert!(update.events.released);
750        assert_eq!(update.multi_click_count, 1);
751        assert!(!update.events.multi_click);
752
753        assert!(!button.update(80).unwrap().events.any());
754        assert!(button.update(85).unwrap().events.pressed);
755        assert!(!button.update(100).unwrap().events.any());
756        let update = button.update(105).unwrap();
757        assert!(update.events.released);
758        assert_eq!(update.multi_click_count, 2);
759        assert!(!update.events.multi_click);
760
761        assert!(!button.update(160).unwrap().events.any());
762        assert!(button.update(165).unwrap().events.pressed);
763        assert!(!button.update(180).unwrap().events.any());
764        let update = button.update(185).unwrap();
765        assert!(update.events.released);
766        assert_eq!(update.multi_click_count, 3);
767        assert!(!update.events.multi_click);
768
769        assert!(!button.update(240).unwrap().events.any());
770        assert!(button.update(245).unwrap().events.pressed);
771        assert!(!button.update(260).unwrap().events.any());
772        let update = button.update(265).unwrap();
773        assert!(update.events.released);
774        assert_eq!(update.multi_click_count, 4);
775        assert!(!update.events.multi_click);
776
777        assert!(!button.update(320).unwrap().events.any());
778        assert!(button.update(325).unwrap().events.pressed);
779        assert!(!button.update(340).unwrap().events.any());
780        let update = button.update(345).unwrap();
781        assert!(update.events.released);
782        assert_eq!(update.multi_click_count, 5);
783        assert!(update.events.multi_click);
784
785        button.into_inner().done();
786    }
787
788    #[test]
789    fn resets_multi_click_count_after_timeout() {
790        let expectations = [
791            PinTransaction::get(PinState::Low),
792            PinTransaction::get(PinState::Low),
793            PinTransaction::get(PinState::High),
794            PinTransaction::get(PinState::High),
795            PinTransaction::get(PinState::Low),
796            PinTransaction::get(PinState::Low),
797            PinTransaction::get(PinState::High),
798            PinTransaction::get(PinState::High),
799            PinTransaction::get(PinState::High),
800            PinTransaction::get(PinState::Low),
801            PinTransaction::get(PinState::Low),
802            PinTransaction::get(PinState::High),
803            PinTransaction::get(PinState::High),
804        ];
805        let pin = PinMock::new(&expectations);
806        let mut button = Button::with_config(
807            pin,
808            config()
809                .with_debounce_ms(5)
810                .with_hold_ms(1000)
811                .with_multi_click(3, 3_000),
812        );
813
814        assert!(!button.update(0).unwrap().events.any());
815        assert!(button.update(5).unwrap().events.pressed);
816        assert!(!button.update(20).unwrap().events.any());
817        let update = button.update(25).unwrap();
818        assert_eq!(update.multi_click_count, 1);
819        assert!(!update.events.multi_click);
820
821        assert!(!button.update(80).unwrap().events.any());
822        assert!(button.update(85).unwrap().events.pressed);
823        assert!(!button.update(100).unwrap().events.any());
824        let update = button.update(105).unwrap();
825        assert_eq!(update.multi_click_count, 2);
826        assert!(!update.events.multi_click);
827
828        let update = button.update(3106).unwrap();
829        assert!(!update.events.any());
830        assert_eq!(update.multi_click_count, 0);
831
832        assert!(!button.update(3200).unwrap().events.any());
833        assert!(button.update(3205).unwrap().events.pressed);
834        assert!(!button.update(3220).unwrap().events.any());
835        let update = button.update(3225).unwrap();
836        assert_eq!(update.multi_click_count, 1);
837        assert!(!update.events.multi_click);
838
839        button.into_inner().done();
840    }
841
842    #[test]
843    fn configurable_double_click_can_use_multi_click_timeout() {
844        let expectations = [
845            PinTransaction::get(PinState::Low),
846            PinTransaction::get(PinState::Low),
847            PinTransaction::get(PinState::High),
848            PinTransaction::get(PinState::High),
849            PinTransaction::get(PinState::Low),
850            PinTransaction::get(PinState::Low),
851            PinTransaction::get(PinState::High),
852            PinTransaction::get(PinState::High),
853        ];
854        let pin = PinMock::new(&expectations);
855        let mut button = Button::with_config(
856            pin,
857            config()
858                .with_debounce_ms(5)
859                .with_double_click_ms(200)
860                .with_hold_ms(1000)
861                .with_multi_click(2, 3_000),
862        );
863
864        assert!(!button.update(0).unwrap().events.any());
865        assert!(button.update(5).unwrap().events.pressed);
866        assert!(!button.update(20).unwrap().events.any());
867        let update = button.update(25).unwrap();
868        assert!(update.events.released);
869        assert_eq!(update.multi_click_count, 1);
870        assert!(!update.events.click);
871        assert!(!update.events.double_click);
872        assert!(!update.events.multi_click);
873
874        assert!(!button.update(1000).unwrap().events.any());
875        assert!(button.update(1005).unwrap().events.pressed);
876        assert!(!button.update(1020).unwrap().events.any());
877        let update = button.update(1025).unwrap();
878        assert!(update.events.released);
879        assert!(!update.events.click);
880        assert!(!update.events.double_click);
881        assert_eq!(update.multi_click_count, 2);
882        assert!(update.events.multi_click);
883
884        button.into_inner().done();
885    }
886
887    #[test]
888    fn reports_hold_once_and_tracks_hold_time() {
889        let expectations = [
890            PinTransaction::get(PinState::Low),
891            PinTransaction::get(PinState::Low),
892            PinTransaction::get(PinState::Low),
893            PinTransaction::get(PinState::Low),
894            PinTransaction::get(PinState::Low),
895            PinTransaction::get(PinState::High),
896            PinTransaction::get(PinState::High),
897        ];
898        let pin = PinMock::new(&expectations);
899        let mut button = Button::with_config(pin, config().with_debounce_ms(5).with_hold_ms(50));
900
901        assert!(!button.update(0).unwrap().events.any());
902        assert!(button.update(5).unwrap().events.pressed);
903
904        let update = button.update(54).unwrap();
905        assert_eq!(update.held_ms, Some(49));
906        assert!(!update.events.hold);
907
908        let update = button.update(55).unwrap();
909        assert_eq!(update.held_ms, Some(50));
910        assert!(update.events.hold);
911
912        let update = button.update(80).unwrap();
913        assert_eq!(update.held_ms, Some(75));
914        assert!(!update.events.hold);
915
916        assert!(!button.update(100).unwrap().events.any());
917        let update = button.update(105).unwrap();
918        assert!(update.events.released);
919        assert!(!update.events.click);
920        assert_eq!(update.released_after_ms, Some(100));
921
922        button.into_inner().done();
923    }
924
925    #[test]
926    fn supports_active_high_and_runtime_debounce_setting() {
927        let expectations = [
928            PinTransaction::get(PinState::Low),
929            PinTransaction::get(PinState::High),
930            PinTransaction::get(PinState::High),
931        ];
932        let pin = PinMock::new(&expectations);
933        let mut button = Button::with_config(pin, ButtonConfig::active_high());
934
935        button.set_debounce_ms(3);
936        assert_eq!(button.debounce_ms(), 3);
937
938        assert_eq!(button.update(0).unwrap().state, ButtonState::Released);
939        assert!(!button.update(1).unwrap().events.any());
940
941        let update = button.update(4).unwrap();
942        assert_eq!(update.state, ButtonState::Pressed);
943        assert!(update.events.pressed);
944
945        button.into_inner().done();
946    }
947
948    #[test]
949    fn propagates_pin_errors() {
950        let expectations =
951            [PinTransaction::get(PinState::High).with_error(MockError::Io(ErrorKind::Other))];
952        let pin = PinMock::new(&expectations);
953        let mut button = Button::new(pin);
954
955        assert!(button.update(0).is_err());
956
957        button.into_inner().done();
958    }
959}