use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct DualChannelComparator<S: ControlScalar> {
pub tolerance: S,
pub trip_count: u32,
consecutive_faults: u32,
tripped: bool,
suspect_channel: u8,
}
impl<S: ControlScalar> DualChannelComparator<S> {
pub fn new(tolerance: S, trip_count: u32) -> Self {
Self {
tolerance,
trip_count,
consecutive_faults: 0,
tripped: false,
suspect_channel: 2,
}
}
pub fn check(&mut self, channel_a: S, channel_b: S) -> bool {
if self.tripped {
return false;
}
let diff = (channel_a - channel_b).abs();
if diff > self.tolerance {
self.consecutive_faults += 1;
if self.consecutive_faults >= self.trip_count {
self.tripped = true;
self.suspect_channel = 2;
}
return !self.tripped;
}
self.consecutive_faults = 0;
true
}
pub fn check_with_ref(&mut self, channel_a: S, channel_b: S, reference: S) -> bool {
let ok = self.check(channel_a, channel_b);
if !ok && self.tripped {
let da = (channel_a - reference).abs();
let db = (channel_b - reference).abs();
self.suspect_channel = if da > db { 0 } else { 1 };
}
ok
}
pub fn is_tripped(&self) -> bool {
self.tripped
}
pub fn suspect_channel(&self) -> u8 {
self.suspect_channel
}
pub fn reset(&mut self) {
self.consecutive_faults = 0;
self.tripped = false;
self.suspect_channel = 2;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn channels_agree_returns_true() {
let mut cmp = DualChannelComparator::new(0.1_f64, 3);
assert!(cmp.check(10.0, 10.05));
assert!(!cmp.is_tripped());
}
#[test]
fn mismatch_trips_after_count() {
let mut cmp = DualChannelComparator::new(0.1_f64, 3);
for _ in 0..3 {
cmp.check(10.0, 15.0);
}
assert!(cmp.is_tripped());
}
#[test]
fn intermittent_mismatch_resets_counter() {
let mut cmp = DualChannelComparator::new(0.1_f64, 3);
cmp.check(10.0, 15.0); cmp.check(10.0, 10.05); cmp.check(10.0, 15.0); cmp.check(10.0, 15.0); assert!(!cmp.is_tripped()); cmp.check(10.0, 15.0); assert!(cmp.is_tripped());
}
#[test]
fn channel_id_from_reference() {
let mut cmp = DualChannelComparator::new(0.1_f64, 1);
cmp.check_with_ref(10.0_f64, 20.0, 10.0);
assert!(cmp.is_tripped());
assert_eq!(cmp.suspect_channel(), 1);
}
#[test]
fn reset_clears_trip() {
let mut cmp = DualChannelComparator::new(0.1_f64, 1);
cmp.check(0.0_f64, 10.0);
assert!(cmp.is_tripped());
cmp.reset();
assert!(!cmp.is_tripped());
assert!(cmp.check(1.0_f64, 1.05));
}
}