device_envoy_esp/button.rs
1//! A device abstraction for buttons with debouncing and press duration detection.
2//!
3//! This module provides two ways to monitor button presses:
4//!
5//! - [`ButtonEsp`] - Simple button monitoring. Each call to `wait_for_press()`, etc. starts fresh
6//! button monitoring.
7//! - [`button_watch!`](crate::button_watch!) - Monitors a button in a background task
8//! so that it works even in a fast loop/select.
9
10#[cfg(target_os = "none")]
11mod button_watch;
12pub mod button_watch_generated;
13
14#[cfg(target_os = "none")]
15// `button_watch!` expands in downstream crates and references these symbols, so
16// they must remain public even though they are macro implementation details.
17#[doc(hidden)]
18pub use button_watch::{ButtonWatchEsp, ButtonWatchStaticEsp};
19pub use device_envoy_core::button::Button;
20#[doc(hidden)]
21pub use device_envoy_core::button::__ButtonMonitor;
22pub use device_envoy_core::button::{PressDuration, PressedTo};
23// Public for compatibility; hidden from end-user docs.
24#[doc(hidden)]
25pub use device_envoy_core::button::{
26 BUTTON_DEBOUNCE_DELAY, BUTTON_POLL_INTERVAL, LONG_PRESS_DURATION,
27};
28
29#[cfg(target_os = "none")]
30use esp_hal::gpio::{Input, InputConfig, InputPin, Pull};
31
32/// A device abstraction for a button with debouncing and press duration detection.
33///
34/// # Hardware Requirements
35///
36/// The button can be wired in two ways:
37/// - [`PressedTo::Voltage`]: Button connects pin to 3.3V when pressed (uses pull-down)
38/// - [`PressedTo::Ground`]: Button connects pin to GND when pressed (uses pull-up)
39///
40/// # Usage
41///
42/// Use [`Button::wait_for_press`] when you only need a debounced
43/// press event. It returns on the down edge and does not wait for release.
44///
45/// Use [`Button::wait_for_press_duration`] when you need to
46/// distinguish short vs. long presses. It returns as soon as it can decide, so long
47/// presses are reported before the button is released.
48///
49/// # Example
50///
51/// ```rust,no_run
52/// # #![no_std]
53/// # #![no_main]
54///
55/// use device_envoy_esp::{
56/// button::{Button as _, ButtonEsp, PressDuration, PressedTo},
57/// init_and_start, Result,
58/// };
59/// # use core::convert::Infallible;
60/// # use esp_backtrace as _;
61/// # #[panic_handler]
62/// # fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
63///
64/// async fn example() -> Result<Infallible> {
65/// init_and_start!(p);
66/// let mut button = ButtonEsp::new(p.GPIO6, PressedTo::Ground);
67///
68/// // Wait for a press without measuring duration.
69/// button.wait_for_press().await;
70///
71/// // Measure press durations in a loop
72/// loop {
73/// match button.wait_for_press_duration().await {
74/// PressDuration::Short => {
75/// // Handle short press
76/// }
77/// PressDuration::Long => {
78/// // Handle long press (fires before button is released)
79/// }
80/// }
81/// }
82/// }
83/// ```
84#[cfg(target_os = "none")]
85pub struct ButtonEsp<'d> {
86 input: Input<'d>,
87 pressed_to: PressedTo,
88}
89
90#[cfg(target_os = "none")]
91impl<'d> ButtonEsp<'d> {
92 /// Creates a new `ButtonEsp` instance from a pin.
93 ///
94 /// The pin is configured based on the connection type:
95 /// - [`PressedTo::Voltage`]: Uses internal pull-down (button to 3.3V)
96 /// - [`PressedTo::Ground`]: Uses internal pull-up (button to GND)
97 #[must_use]
98 pub fn new(button_pin: impl InputPin + 'd, pressed_to: PressedTo) -> Self {
99 let pull = match pressed_to {
100 PressedTo::Voltage => Pull::Down,
101 PressedTo::Ground => Pull::Up,
102 };
103 let input = Input::new(button_pin, InputConfig::default().with_pull(pull));
104 Self { input, pressed_to }
105 }
106}
107
108#[cfg(target_os = "none")]
109impl device_envoy_core::button::__ButtonMonitor for ButtonEsp<'_> {
110 fn is_pressed_raw(&self) -> bool {
111 self.pressed_to.is_pressed(self.input.is_high())
112 }
113
114 async fn wait_until_pressed_state(&mut self, pressed: bool) {
115 match (pressed, self.pressed_to) {
116 (true, PressedTo::Voltage) | (false, PressedTo::Ground) => {
117 self.input.wait_for_high().await;
118 }
119 (true, PressedTo::Ground) | (false, PressedTo::Voltage) => {
120 self.input.wait_for_low().await;
121 }
122 }
123 }
124}
125
126#[cfg(target_os = "none")]
127impl device_envoy_core::button::Button for ButtonEsp<'_> {}
128
129#[cfg(target_os = "none")]
130#[doc(inline)]
131pub use crate::button_watch;