use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct EventTrigger<S: ControlScalar> {
pub threshold: S,
pub hysteresis: S,
pub min_inter_event: S,
time_since_event: S,
debounce_count: u32,
pub required_debounce: u32,
triggered_state: bool,
}
impl<S: ControlScalar> EventTrigger<S> {
pub fn new(threshold: S, hysteresis: S, min_inter_event: S) -> Self {
Self {
threshold,
hysteresis,
min_inter_event,
time_since_event: min_inter_event, debounce_count: 0,
required_debounce: 0,
triggered_state: false,
}
}
pub fn with_debounce(mut self, count: u32) -> Self {
self.required_debounce = count;
self
}
pub fn should_fire(&mut self, error: S, dt: S) -> bool {
self.time_since_event += dt;
let abs_err = error.abs();
let clear_threshold = (self.threshold - self.hysteresis).max(S::ZERO);
if !self.triggered_state {
if abs_err >= self.threshold {
self.debounce_count += 1;
} else {
self.debounce_count = 0;
}
if self.debounce_count > self.required_debounce
&& self.time_since_event >= self.min_inter_event
{
self.triggered_state = true;
self.time_since_event = S::ZERO;
self.debounce_count = 0;
return true;
}
} else {
if abs_err < clear_threshold {
self.triggered_state = false;
self.debounce_count = 0;
}
}
false
}
pub fn advance_time(&mut self, dt: S) {
self.time_since_event += dt;
}
pub fn time_since_last_event(&self) -> S {
self.time_since_event
}
pub fn is_in_triggered_state(&self) -> bool {
self.triggered_state
}
pub fn reset(&mut self) {
self.time_since_event = self.min_inter_event;
self.debounce_count = 0;
self.triggered_state = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fires_when_error_exceeds_threshold() {
let mut trig = EventTrigger::new(0.1_f64, 0.02, 0.0);
assert!(!trig.should_fire(0.05, 0.01));
assert!(trig.should_fire(0.2, 0.01));
}
#[test]
fn hysteresis_prevents_immediate_re_fire() {
let mut trig = EventTrigger::new(0.1_f64, 0.05, 0.0);
assert!(trig.should_fire(0.15, 0.0));
assert!(trig.is_in_triggered_state());
assert!(!trig.should_fire(0.07, 0.0));
assert!(trig.is_in_triggered_state());
assert!(!trig.should_fire(0.02, 0.0));
assert!(!trig.is_in_triggered_state());
}
#[test]
fn min_inter_event_enforced() {
let mut trig = EventTrigger::new(0.1_f64, 0.0, 0.5);
assert!(trig.should_fire(0.2, 0.0));
trig.should_fire(0.0, 0.0); assert!(!trig.should_fire(0.2, 0.1)); assert!(trig.should_fire(0.2, 0.4));
}
#[test]
fn debounce_requires_consecutive_samples() {
let mut trig = EventTrigger::new(0.1_f64, 0.0, 0.0).with_debounce(2);
assert!(!trig.should_fire(0.5, 0.0));
assert!(!trig.should_fire(0.5, 0.0));
assert!(trig.should_fire(0.5, 0.0));
}
}