feat: add a finite state machine for debouncing

This commit is contained in:
Jet Pham 2025-06-07 12:41:57 -07:00
parent 93f5d8e904
commit 3f519376b2
No known key found for this signature in database
2 changed files with 57 additions and 11 deletions

View file

@ -1,4 +1,4 @@
use std::time::Duration;
use std::time::{Duration, Instant};
use std::fmt;
use serde::{Serialize, Deserialize};
@ -11,6 +11,14 @@ pub enum CircuitEvent {
Closed,
}
#[derive(Debug, PartialEq)]
enum FsmState {
Idle,
DebouncingHigh,
High,
DebouncingLow,
}
impl fmt::Display for CircuitEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@ -23,32 +31,68 @@ impl fmt::Display for CircuitEvent {
pub struct GpioMonitor {
pin: InputPin,
poll_interval: Duration,
debounce_delay: Duration,
state: FsmState,
last_potential_transition_time: Instant,
}
impl GpioMonitor {
pub fn new(pin_number: u8, poll_interval: Duration) -> Result<Self> {
pub fn new(pin_number: u8, poll_interval: Duration, debounce_delay: Duration) -> Result<Self> {
let gpio = Gpio::new()
.context("Failed to initialize GPIO")?;
let pin = gpio.get(pin_number)
.context(format!("Failed to get GPIO pin {}", pin_number))?
.into_input_pullup();
Ok(Self { pin, poll_interval })
Ok(Self {
pin,
poll_interval,
debounce_delay,
state: FsmState::Idle,
last_potential_transition_time: Instant::now(),
})
}
pub async fn monitor<F>(&mut self, mut callback: F) -> Result<()>
where
F: FnMut(CircuitEvent) + Send + 'static,
{
let mut previous_state = self.get_current_state();
callback(previous_state); // Send initial state
loop {
let current_state = self.get_current_state();
let current_switch_reading = self.get_current_state() == CircuitEvent::Closed;
let time_since_last_change = self.last_potential_transition_time.elapsed();
if current_state != previous_state {
callback(current_state);
previous_state = current_state;
match self.state {
FsmState::Idle => {
if current_switch_reading {
self.state = FsmState::DebouncingHigh;
self.last_potential_transition_time = Instant::now();
}
}
FsmState::DebouncingHigh => {
if !current_switch_reading {
self.state = FsmState::Idle;
} else if time_since_last_change >= self.debounce_delay {
self.state = FsmState::High;
callback(CircuitEvent::Closed);
}
}
FsmState::High => {
if !current_switch_reading {
self.state = FsmState::DebouncingLow;
self.last_potential_transition_time = Instant::now();
}
}
FsmState::DebouncingLow => {
if current_switch_reading {
self.state = FsmState::High;
} else if time_since_last_change >= self.debounce_delay {
self.state = FsmState::Idle;
callback(CircuitEvent::Open);
}
}
}
tokio::time::sleep(self.poll_interval).await;

View file

@ -13,6 +13,7 @@ use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
const DEFAULT_GPIO_PIN: u8 = 17;
const DEFAULT_POLL_INTERVAL_MS: u64 = 100;
const DEFAULT_DEBOUNCE_DELAY_SECS: u64 = 5;
const LOG_DIR: &str = "logs";
const LOG_PREFIX: &str = "noisebell";
const LOG_SUFFIX: &str = "log";
@ -54,7 +55,8 @@ async fn main() -> Result<()> {
info!("initializing gpio monitor");
let mut gpio_monitor = gpio::GpioMonitor::new(
DEFAULT_GPIO_PIN,
Duration::from_millis(DEFAULT_POLL_INTERVAL_MS)
Duration::from_millis(DEFAULT_POLL_INTERVAL_MS),
Duration::from_secs(DEFAULT_DEBOUNCE_DELAY_SECS)
)?;
// Set up the callback for state changes