async_button/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(any(test, feature = "std")), no_std)]
3#![warn(missing_docs)]
4
5pub use config::{ButtonConfig, Mode};
6
7mod config;
8
9#[cfg(test)]
10mod tests;
11
12cfg_if::cfg_if! {
13    if #[cfg(any(test, feature = "std"))] {
14        use std::time::Duration;
15        use tokio::time::timeout as with_timeout;
16    } else {
17        use embassy_time::{with_timeout, Duration, Timer};
18    }
19}
20
21/// A generic button that asynchronously detects [`ButtonEvent`]s.
22#[derive(Debug, Clone, Copy)]
23pub struct Button<P> {
24    pin: P,
25    state: State,
26    count: usize,
27    config: ButtonConfig,
28}
29
30#[derive(Debug, Clone, Copy)]
31enum State {
32    /// Initial state.
33    Unknown,
34    /// Debounced press.
35    Pressed,
36    /// The button was just released, waiting for more presses in the same sequence, or for the
37    /// sequence to end.
38    Released,
39    /// Fully released state, idle.
40    Idle,
41    /// Waiting for the button to be released.
42    PendingRelease,
43}
44
45/// Detected button events
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48pub enum ButtonEvent {
49    /// A sequence of 1 or more short presses.
50    ShortPress {
51        /// The number of short presses in the sequence.
52        count: usize,
53    },
54    /// A long press. This event is returned directly when the button is held for more than
55    /// [`ButtonConfig::long_press`].
56    LongPress,
57}
58
59impl<P> Button<P>
60where
61    P: embedded_hal_async::digital::Wait + embedded_hal::digital::InputPin,
62{
63    /// Creates a new button with the provided config.
64    pub const fn new(pin: P, config: ButtonConfig) -> Self {
65        Self {
66            pin,
67            state: State::Unknown,
68            count: 0,
69            config,
70        }
71    }
72
73    /// Updates the button and returns the detected event.
74    ///
75    /// Awaiting this blocks execution of the task until a [`ButtonEvent`] is detected so it should
76    /// **not** be called from tasks where blocking for long periods of time is not desireable.
77    pub async fn update(&mut self) -> ButtonEvent {
78        loop {
79            if let Some(event) = self.update_step().await {
80                return event;
81            }
82        }
83    }
84
85    async fn update_step(&mut self) -> Option<ButtonEvent> {
86        match self.state {
87            State::Unknown => {
88                if self.is_pin_pressed() {
89                    self.state = State::Pressed;
90                } else {
91                    self.state = State::Idle;
92                }
93                None
94            }
95
96            State::Pressed => {
97                match with_timeout(self.config.long_press, self.wait_for_release()).await {
98                    Ok(_) => {
99                        // Short press
100                        self.debounce_delay().await;
101                        if self.is_pin_released() {
102                            self.state = State::Released;
103                        }
104                        None
105                    }
106                    Err(_) => {
107                        // Long press detected
108                        self.count = 0;
109                        self.state = State::PendingRelease;
110                        Some(ButtonEvent::LongPress)
111                    }
112                }
113            }
114
115            State::Released => {
116                match with_timeout(self.config.double_click, self.wait_for_press()).await {
117                    Ok(_) => {
118                        // Continue sequence
119                        self.debounce_delay().await;
120                        if self.is_pin_pressed() {
121                            self.count += 1;
122                            self.state = State::Pressed;
123                        }
124                        None
125                    }
126                    Err(_) => {
127                        // Sequence ended
128                        let count = self.count;
129                        self.count = 0;
130                        self.state = State::Idle;
131                        Some(ButtonEvent::ShortPress { count })
132                    }
133                }
134            }
135
136            State::Idle => {
137                self.wait_for_press().await;
138                self.debounce_delay().await;
139                if self.is_pin_pressed() {
140                    self.count = 1;
141                    self.state = State::Pressed;
142                }
143                None
144            }
145
146            State::PendingRelease => {
147                self.wait_for_release().await;
148                self.debounce_delay().await;
149                if self.is_pin_released() {
150                    self.state = State::Idle;
151                }
152                None
153            }
154        }
155    }
156
157    fn is_pin_pressed(&mut self) -> bool {
158        self.pin.is_low().unwrap_or(self.config.mode.is_pulldown()) == self.config.mode.is_pullup()
159    }
160
161    fn is_pin_released(&mut self) -> bool {
162        !self.is_pin_pressed()
163    }
164
165    async fn wait_for_release(&mut self) {
166        match self.config.mode {
167            Mode::PullUp => self.pin.wait_for_high().await.unwrap_or_default(),
168            Mode::PullDown => self.pin.wait_for_low().await.unwrap_or_default(),
169        }
170    }
171
172    async fn wait_for_press(&mut self) {
173        match self.config.mode {
174            Mode::PullUp => self.pin.wait_for_low().await.unwrap_or_default(),
175            Mode::PullDown => self.pin.wait_for_high().await.unwrap_or_default(),
176        }
177    }
178
179    async fn debounce_delay(&self) {
180        delay(self.config.debounce).await;
181    }
182}
183
184async fn delay(duration: Duration) {
185    cfg_if::cfg_if! {
186        if #[cfg(any(test, feature = "std"))] {
187            tokio::time::sleep(duration).await;
188        } else {
189            Timer::after(duration).await;
190        }
191    }
192}