Skip to main content

butt_head/
butt_head.rs

1use crate::TimeDuration;
2use crate::config::Config;
3use crate::event::Event;
4use crate::service_timing::ServiceTiming;
5use crate::state_machine::{Edge, StateMachine};
6use crate::time::TimeInstant;
7
8/// The result of a single `update()` call.
9#[derive(Debug, Clone, Copy)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub struct UpdateResult<D: TimeDuration> {
12    /// The event produced by this update, if any.
13    pub event: Option<Event<D>>,
14    /// When to call `update()` again. See [`ServiceTiming`].
15    pub next_service: ServiceTiming<D>,
16}
17
18/// Button input processor.
19///
20/// Expects clean, debounced input. If your button is subject to mechanical
21/// bounce, debounce the signal before passing it to `update()`.
22pub struct ButtHead<I: TimeInstant> {
23    prev_input: bool,
24    state_machine: StateMachine<I>,
25    config: &'static Config<I::Duration>,
26}
27
28impl<I: TimeInstant> ButtHead<I> {
29    /// Creates a new `ButtHead` instance with the given configuration.
30    pub fn new(config: &'static Config<I::Duration>) -> Self {
31        Self {
32            prev_input: false,
33            state_machine: StateMachine::new(config),
34            config,
35        }
36    }
37
38    /// Returns `true` if the button is currently physically pressed.
39    pub fn is_pressed(&self) -> bool {
40        self.prev_input
41    }
42
43    /// Returns how long the button has been continuously held, or `None` if it
44    /// is not currently pressed.
45    pub fn pressed_duration(&self, now: I) -> Option<I::Duration> {
46        self.state_machine
47            .pressed_at()
48            .map(|at| now.duration_since(at))
49    }
50
51    /// Advances the state machine.
52    ///
53    /// `is_pressed` is the raw pin state (before active-low inversion).
54    /// `now` is the current timestamp. Returns the resulting event and the
55    /// recommended time for the next call.
56    pub fn update(&mut self, is_pressed: bool, now: I) -> UpdateResult<I::Duration> {
57        let input = if self.config.active_low {
58            !is_pressed
59        } else {
60            is_pressed
61        };
62
63        let edge = if input != self.prev_input {
64            self.prev_input = input;
65            Some(if input { Edge::Press } else { Edge::Release })
66        } else {
67            None
68        };
69
70        let (event, next_service) = self.state_machine.update(edge, now);
71
72        UpdateResult {
73            event,
74            next_service,
75        }
76    }
77}