1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! # Button Module
//!
//! This module provides a simple interface for button handling with debouncing
//! algorythm. It can be used to detect press and release events for a button
//! connected to an input pin.
//! ## Example
/// ```no_run
/// use esp_hal::delay::Delay;
/// use esp_ward::peripherals::button::{Button, Event};
///
/// // Suppose the button is connected to GPIO23
/// let peripherals = take_periph!();
/// let system = take_system!(peripherals);
/// let (clocks, pins) = init_chip!(peripherals, system);
/// let mut button = Button::new(pins.gpio23.into_pull_up_input());
/// let mut delay = Delay::new(&clocks);
///
/// loop {
///     // With `match`
///     match button.poll(&mut delay) {
///         Event::Pressed => println!("Button pressed!"),
///         Event::Released => println!("Button released!"),
///         Event::Nothing => (),
///     }
///
///     // Or `if let...`
///     if let crate::peripherals::button::Event::Pressed = self.select.poll(&mut delay) {
///         // your callback if button was pressed
///     } else {
///         // your callback if not
///     }
/// }
/// ```
use embedded_hal::blocking::delay::DelayMs;
use esp_hal::delay::Delay;

use super::{PeripheralError, UnifiedData};

/// Represents possible events from a button press.
pub enum Event {
    Pressed,
    Released,
    Nothing,
}
/// A generic button that can report press and release events.
pub struct Button<T> {
    /// The input pin connected to the button.
    button: T,
    /// Tracks the current debounced state of the button.
    pressed: bool,
}

/// Creates a new `Button` instance associated with a specific input pin.
///
/// # Arguments
/// * `button` - The input pin the button is connected to.
///
/// # Returns
/// A new `Button` instance that can be used to detect button events.
impl<T: embedded_hal::digital::v2::InputPin<Error = core::convert::Infallible>> Button<T> {
    pub fn create_on_pins(button: T) -> Self {
        Button {
            button,
            pressed: true,
        }
    }
    /// Updates the internal state of the button by reading its current state.
    fn check(&mut self) {
        self.pressed = !self.button.is_low().unwrap();
    }

    /// Polls the button to determine its current state and debounce it.
    ///
    /// This method should be called repeatedly to ensure accurate event
    /// detection.
    ///
    /// # Arguments
    /// * `delay` - A delay provider used for debouncing.
    ///
    /// # Returns
    /// An `Event` indicating the debounced state change of the button.
    pub(crate) fn poll(&mut self, delay: &mut Delay) -> Event {
        let pressed_now = !self.button.is_low().unwrap();
        if !self.pressed && pressed_now {
            delay.delay_ms(30 as u32);
            self.check();
            if !self.button.is_low().unwrap() {
                Event::Pressed
            } else {
                Event::Nothing
            }
        } else if self.pressed && !pressed_now {
            delay.delay_ms(30 as u32);
            self.check();
            if self.button.is_low().unwrap() {
                Event::Released
            } else {
                Event::Nothing
            }
        } else {
            Event::Nothing
        }
    }
}

impl<T: embedded_hal::digital::v2::InputPin<Error = core::convert::Infallible>> UnifiedData
    for Button<T>
{
    type Output = bool;
    /// Reads the current state of a Button
    ///
    /// # Returns
    /// Returns an `Ok(true)' if Button is pressed, `Ok(false)` otherwise
    fn read(&mut self, mut delay: Delay) -> Result<Self::Output, PeripheralError> {
        if let crate::peripherals::button::Event::Pressed = self.poll(&mut delay) {
            return Ok(true);
        } else {
            return Ok(false);
        }
    }
}