Skip to main content

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;