use core::{cell::Cell, marker::PhantomData, num::NonZeroU8};
use crate::{
active::Active,
strategy::{self, Strategy},
Status,
};
pub struct Debounced<A, S, F> {
is_input_high: F,
strategy: S,
hysteresis: Cell<Status>,
_a: PhantomData<*const A>, }
pub type DebouncedIntegrator<A, F> = Debounced<A, strategy::Integrator, F>;
pub type DebouncedShifter<A, T, F> = Debounced<A, strategy::Shifter<T>, F>;
impl<A, S, F> Debounced<A, S, F>
where
A: Active,
F: Fn() -> bool,
{
pub fn new(strategy: S, is_input_high: F) -> Self {
Self {
is_input_high,
strategy,
hysteresis: Cell::new(!A::ACTIVE_VALUE),
_a: PhantomData,
}
}
}
impl<A, F> DebouncedIntegrator<A, F>
where
A: Active,
F: Fn() -> bool,
{
pub fn with_integrator(max: NonZeroU8, is_input_high: F) -> Self {
Self::new(strategy::Integrator::new::<A>(max), is_input_high)
}
}
impl<A, F> DebouncedShifter<A, u8, F>
where
A: Active,
F: Fn() -> bool,
{
pub fn with_shifter<T>(is_input_high: F) -> DebouncedShifter<A, T, F>
where
T: strategy::NumericType,
{
Debounced::new(strategy::Shifter::new::<A>(), is_input_high)
}
}
impl<A, S, F> Debounced<A, S, F>
where
S: Strategy,
F: Fn() -> bool,
{
#[inline]
fn input_status(&self) -> Status {
if (self.is_input_high)() {
Status::High
} else {
Status::Low
}
}
pub fn try_get(&self) -> Option<Status> {
let s = self.strategy.update(self.input_status());
if let Some(s) = s {
self.hysteresis.set(s);
}
s
}
pub fn get_latest(&self) -> Status {
self.try_get().unwrap_or_else(|| self.hysteresis.get())
}
pub fn get_blocking(&self) -> Status {
let mut status;
loop {
status = self.try_get();
if let Some(s) = status {
return s;
}
}
}
}
impl<A, S, F> Debounced<A, S, F>
where
A: Active,
S: Strategy,
F: Fn() -> bool,
{
pub fn with_current_strategy(strategy: S, is_input_high: F) -> Option<Self> {
let status = strategy.status()?;
Some(Self {
is_input_high,
strategy,
hysteresis: Cell::new(status),
_a: PhantomData,
})
}
pub fn get_or_unset(&self) -> Status {
self.try_get().unwrap_or(!A::ACTIVE_VALUE)
}
pub fn try_is_triggered(&self) -> Option<bool> {
self.try_get().map(|s| s == A::ACTIVE_VALUE)
}
pub fn is_triggered_latest(&self) -> bool {
self.get_latest() == A::ACTIVE_VALUE
}
pub fn is_triggered_blocking(&self) -> bool {
self.get_blocking() == A::ACTIVE_VALUE
}
pub fn is_triggered_or_unset(&self) -> bool {
self.get_or_unset() == A::ACTIVE_VALUE
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::active::{High, Low};
type DbInt<F> = DebouncedIntegrator<Low, F>;
type DbShf<T, F> = DebouncedShifter<High, T, F>;
#[test]
fn low_is_triggered() {
let d = DbInt::with_integrator(NonZeroU8::new(6).unwrap(), || false);
assert_eq!(d.try_is_triggered(), None);
assert_eq!(d.is_triggered_or_unset(), false);
assert_eq!(d.is_triggered_latest(), false);
assert_eq!(d.try_is_triggered(), None);
assert_eq!(d.is_triggered_latest(), false);
assert_eq!(d.is_triggered_latest(), true);
assert_eq!(d.is_triggered_latest(), true);
assert_eq!(d.is_triggered_or_unset(), true);
assert_eq!(d.try_is_triggered(), Some(true));
}
#[test]
fn blocking() {
let d = DbInt::with_integrator(NonZeroU8::new(6).unwrap(), || false);
assert_eq!(d.get_blocking(), Status::Low);
let d = DbInt::with_integrator(NonZeroU8::new(6).unwrap(), || true);
assert_eq!(d.get_blocking(), Status::High);
let e = DbInt::with_integrator(NonZeroU8::new(6).unwrap(), || false);
assert_eq!(e.is_triggered_blocking(), true);
let e = DbInt::with_integrator(NonZeroU8::new(6).unwrap(), || true);
assert_eq!(e.is_triggered_blocking(), false);
let f = Debounced::<High, _, _>::with_integrator(NonZeroU8::new(6).unwrap(), || true);
assert_eq!(f.get_blocking(), Status::High);
let f = Debounced::<High, _, _>::with_integrator(NonZeroU8::new(6).unwrap(), || false);
assert_eq!(f.get_blocking(), Status::Low);
let g = Debounced::<High, _, _>::with_integrator(NonZeroU8::new(6).unwrap(), || true);
assert_eq!(g.is_triggered_blocking(), true);
let g = Debounced::<High, _, _>::with_integrator(NonZeroU8::new(6).unwrap(), || false);
assert_eq!(g.is_triggered_blocking(), false);
}
#[test]
fn low_status() {
let bit = Cell::new(false);
let d = DbInt::with_integrator(NonZeroU8::new(6).unwrap(), || bit.get());
assert_eq!(d.try_get(), None);
assert_eq!(d.get_or_unset(), Status::High);
assert_eq!(d.get_latest(), Status::High);
assert_eq!(d.try_get(), None);
assert_eq!(d.get_latest(), Status::High);
assert_eq!(d.get_latest(), Status::Low);
assert_eq!(d.get_latest(), Status::Low);
assert_eq!(d.get_or_unset(), Status::Low);
assert_eq!(d.try_get(), Some(Status::Low));
bit.set(true);
assert_eq!(d.try_get(), None);
assert_eq!(d.get_or_unset(), Status::High);
assert_eq!(d.get_latest(), Status::Low);
assert_eq!(d.try_get(), None);
assert_eq!(d.get_latest(), Status::Low);
assert_eq!(d.get_latest(), Status::High);
assert_eq!(d.get_latest(), Status::High);
assert_eq!(d.get_or_unset(), Status::High);
assert_eq!(d.try_get(), Some(Status::High));
}
#[test]
fn high_is_triggered() {
let d = DbShf::with_shifter::<u8>(|| true);
assert_eq!(d.try_is_triggered(), None);
assert_eq!(d.is_triggered_or_unset(), false);
assert_eq!(d.is_triggered_latest(), false);
assert_eq!(d.try_is_triggered(), None);
assert_eq!(d.is_triggered_latest(), false);
assert_eq!(d.is_triggered_latest(), false);
assert_eq!(d.is_triggered_latest(), true);
assert_eq!(d.is_triggered_latest(), true);
assert_eq!(d.is_triggered_or_unset(), true);
assert_eq!(d.try_is_triggered(), Some(true));
}
}