feat: add a finite state machine for debouncing
This commit is contained in:
parent
93f5d8e904
commit
3f519376b2
2 changed files with 57 additions and 11 deletions
64
src/gpio.rs
64
src/gpio.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
|
@ -11,6 +11,14 @@ pub enum CircuitEvent {
|
||||||
Closed,
|
Closed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum FsmState {
|
||||||
|
Idle,
|
||||||
|
DebouncingHigh,
|
||||||
|
High,
|
||||||
|
DebouncingLow,
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for CircuitEvent {
|
impl fmt::Display for CircuitEvent {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -23,32 +31,68 @@ impl fmt::Display for CircuitEvent {
|
||||||
pub struct GpioMonitor {
|
pub struct GpioMonitor {
|
||||||
pin: InputPin,
|
pin: InputPin,
|
||||||
poll_interval: Duration,
|
poll_interval: Duration,
|
||||||
|
debounce_delay: Duration,
|
||||||
|
state: FsmState,
|
||||||
|
last_potential_transition_time: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpioMonitor {
|
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()
|
let gpio = Gpio::new()
|
||||||
.context("Failed to initialize GPIO")?;
|
.context("Failed to initialize GPIO")?;
|
||||||
let pin = gpio.get(pin_number)
|
let pin = gpio.get(pin_number)
|
||||||
.context(format!("Failed to get GPIO pin {}", pin_number))?
|
.context(format!("Failed to get GPIO pin {}", pin_number))?
|
||||||
.into_input_pullup();
|
.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<()>
|
pub async fn monitor<F>(&mut self, mut callback: F) -> Result<()>
|
||||||
where
|
where
|
||||||
F: FnMut(CircuitEvent) + Send + 'static,
|
F: FnMut(CircuitEvent) + Send + 'static,
|
||||||
{
|
{
|
||||||
let mut previous_state = self.get_current_state();
|
|
||||||
callback(previous_state); // Send initial state
|
|
||||||
|
|
||||||
loop {
|
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 {
|
match self.state {
|
||||||
callback(current_state);
|
FsmState::Idle => {
|
||||||
previous_state = current_state;
|
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;
|
tokio::time::sleep(self.poll_interval).await;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
const DEFAULT_GPIO_PIN: u8 = 17;
|
const DEFAULT_GPIO_PIN: u8 = 17;
|
||||||
const DEFAULT_POLL_INTERVAL_MS: u64 = 100;
|
const DEFAULT_POLL_INTERVAL_MS: u64 = 100;
|
||||||
|
const DEFAULT_DEBOUNCE_DELAY_SECS: u64 = 5;
|
||||||
const LOG_DIR: &str = "logs";
|
const LOG_DIR: &str = "logs";
|
||||||
const LOG_PREFIX: &str = "noisebell";
|
const LOG_PREFIX: &str = "noisebell";
|
||||||
const LOG_SUFFIX: &str = "log";
|
const LOG_SUFFIX: &str = "log";
|
||||||
|
|
@ -54,7 +55,8 @@ async fn main() -> Result<()> {
|
||||||
info!("initializing gpio monitor");
|
info!("initializing gpio monitor");
|
||||||
let mut gpio_monitor = gpio::GpioMonitor::new(
|
let mut gpio_monitor = gpio::GpioMonitor::new(
|
||||||
DEFAULT_GPIO_PIN,
|
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
|
// Set up the callback for state changes
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue