use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct PlausibilityMonitor<S: ControlScalar> {
pub max_diff: S,
pub trip_count: u32,
consecutive_fail: u32,
tripped: bool,
}
impl<S: ControlScalar> PlausibilityMonitor<S> {
pub fn new(max_diff: S, trip_count: u32) -> Self {
Self {
max_diff,
trip_count,
consecutive_fail: 0,
tripped: false,
}
}
pub fn check(&mut self, a: S, b: S) -> bool {
if self.tripped {
return false;
}
let diff = (a - b).abs();
if diff > self.max_diff {
self.consecutive_fail += 1;
if self.consecutive_fail >= self.trip_count {
self.tripped = true;
}
return !self.tripped;
}
self.consecutive_fail = 0;
true
}
pub fn is_tripped(&self) -> bool {
self.tripped
}
pub fn reset(&mut self) {
self.consecutive_fail = 0;
self.tripped = false;
}
}
#[derive(Debug, Clone, Copy)]
pub struct TripleSensorPlausibility<S: ControlScalar> {
pub max_diff: S,
}
impl<S: ControlScalar> TripleSensorPlausibility<S> {
pub fn new(max_diff: S) -> Self {
Self { max_diff }
}
pub fn check(&self, a: S, b: S, c: S) -> Option<S> {
let ab = (a - b).abs() <= self.max_diff;
let ac = (a - c).abs() <= self.max_diff;
let bc = (b - c).abs() <= self.max_diff;
match (ab, ac, bc) {
(true, true, true) => {
Some(median3(a, b, c))
}
(true, _, _) => Some((a + b) / S::TWO), (_, true, _) => Some((a + c) / S::TWO), (_, _, true) => Some((b + c) / S::TWO), _ => None, }
}
}
fn median3<S: ControlScalar>(a: S, b: S, c: S) -> S {
if (a >= b && a <= c) || (a >= c && a <= b) {
a
} else if (b >= a && b <= c) || (b >= c && b <= a) {
b
} else {
c
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sensors_agree_returns_true() {
let mut mon = PlausibilityMonitor::new(0.5_f64, 3);
assert!(mon.check(10.0, 10.2));
assert!(!mon.is_tripped());
}
#[test]
fn large_discrepancy_trips_after_count() {
let mut mon = PlausibilityMonitor::new(0.5_f64, 3);
for _ in 0..3 {
mon.check(10.0, 15.0);
}
assert!(mon.is_tripped());
}
#[test]
fn intermittent_fault_does_not_trip() {
let mut mon = PlausibilityMonitor::new(0.5_f64, 3);
mon.check(10.0, 15.0); mon.check(10.0, 10.1); mon.check(10.0, 15.0); mon.check(10.0, 10.1); assert!(!mon.is_tripped());
}
#[test]
fn triple_sensor_median() {
let mon = TripleSensorPlausibility::new(0.5_f64);
let result = mon.check(10.0, 10.1, 10.2);
assert!(result.is_some());
let v = result.unwrap();
assert!((v - 10.1).abs() < 0.01, "v={v:.4}");
}
#[test]
fn triple_sensor_faulty_one() {
let mon = TripleSensorPlausibility::new(0.5_f64);
let result = mon.check(10.0, 10.1, 20.0);
assert!(result.is_some());
let v = result.unwrap();
assert!((v - 10.05).abs() < 0.01, "v={v:.4}");
}
#[test]
fn triple_sensor_all_disagree_returns_none() {
let mon = TripleSensorPlausibility::new(0.5_f64);
let result = mon.check(10.0, 15.0, 20.0);
assert!(result.is_none());
}
}