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#[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 Unknown,
34 Pressed,
36 Released,
39 Idle,
41 PendingRelease,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48pub enum ButtonEvent {
49 ShortPress {
51 count: usize,
53 },
54 LongPress,
57}
58
59impl<P> Button<P>
60where
61 P: embedded_hal_async::digital::Wait + embedded_hal::digital::InputPin,
62{
63 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 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 self.debounce_delay().await;
101 if self.is_pin_released() {
102 self.state = State::Released;
103 }
104 None
105 }
106 Err(_) => {
107 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 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 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}