use crate::common::{FilterError, FloatOps};
#[derive(Debug, Clone, Copy)]
pub struct SensorSource {
pub id: u8,
pub variance: f64,
pub gate_threshold: f64,
pub anomaly_count: u32,
pub max_anomalies: u32,
pub is_quarantined: bool,
pub health_score: f64,
}
impl SensorSource {
pub fn new(id: u8, variance: f64, max_anomalies: u32) -> Result<Self, FilterError> {
if !variance.is_valid_float() || variance <= 0.0 {
return Err(FilterError::InvalidParameter);
}
Ok(Self {
id,
variance,
gate_threshold: 3.0,
anomaly_count: 0,
max_anomalies,
is_quarantined: false,
health_score: 1.0,
})
}
pub fn with_gate_threshold(mut self, threshold: f64) -> Result<Self, FilterError> {
if !threshold.is_valid_float() || threshold <= 0.0 {
return Err(FilterError::InvalidParameter);
}
self.gate_threshold = threshold;
Ok(self)
}
pub fn reset_quarantine(&mut self) {
self.is_quarantined = false;
self.anomaly_count = 0;
self.health_score = 1.0;
}
}
#[derive(Debug, Clone, Copy)]
pub struct SensorMeasurement {
pub sensor_id: u8,
pub value: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FusionStatus {
Fused,
Gated,
Quarantined,
IgnoredQuarantined,
UnregisteredSensor,
}
#[derive(Debug, Clone)]
pub struct MultiSensorFusion1D<const N: usize> {
pub x: f64,
pub p: f64,
pub q: f64,
pub sensors: [Option<SensorSource>; N],
}
impl<const N: usize> MultiSensorFusion1D<N> {
pub fn new(x0: f64, p0: f64, q: f64) -> Result<Self, FilterError> {
if !x0.is_valid_float() || !p0.is_valid_float() || !q.is_valid_float() {
return Err(FilterError::NonFiniteInput);
}
if p0 <= 0.0 || q < 0.0 {
return Err(FilterError::InvalidParameter);
}
Ok(Self {
x: x0,
p: p0,
q,
sensors: [None; N],
})
}
pub fn register_sensor(&mut self, source: SensorSource) -> Result<(), FilterError> {
for slot in self.sensors.iter_mut() {
if let Some(existing) = slot {
if existing.id == source.id {
*slot = Some(source);
return Ok(());
}
} else {
*slot = Some(source);
return Ok(());
}
}
Err(FilterError::CapacityExceeded)
}
pub fn predict(&mut self, control_input: f64) -> Result<(), FilterError> {
if !control_input.is_valid_float() {
return Err(FilterError::NonFiniteInput);
}
self.x += control_input;
self.p += self.q;
Ok(())
}
pub fn fuse(&mut self, measurement: SensorMeasurement) -> Result<FusionStatus, FilterError> {
if !measurement.value.is_valid_float() {
return Err(FilterError::NonFiniteInput);
}
let mut target_sensor_idx = None;
for (i, slot) in self.sensors.iter().enumerate() {
if let Some(ref s) = slot {
if s.id == measurement.sensor_id {
target_sensor_idx = Some(i);
break;
}
}
}
let idx = match target_sensor_idx {
Some(i) => i,
None => return Ok(FusionStatus::UnregisteredSensor),
};
let sensor = match self.sensors[idx].as_mut() {
Some(s) => s,
None => return Ok(FusionStatus::UnregisteredSensor),
};
if sensor.is_quarantined {
return Ok(FusionStatus::IgnoredQuarantined);
}
let innovation = measurement.value - self.x;
let s = self.p + sensor.variance;
let sigma_bound = sensor.gate_threshold * s.sqrt_val();
if innovation.abs_val() > sigma_bound {
sensor.anomaly_count += 1;
sensor.health_score = (sensor.health_score * 0.5).max(0.0);
if sensor.anomaly_count >= sensor.max_anomalies {
sensor.is_quarantined = true;
sensor.health_score = 0.0;
return Ok(FusionStatus::Quarantined);
}
return Ok(FusionStatus::Gated);
}
sensor.anomaly_count = sensor.anomaly_count.saturating_sub(1);
sensor.health_score = (sensor.health_score + 0.1).min(1.0);
let k = self.p / s;
self.x += k * innovation;
let one_minus_k = 1.0 - k;
self.p = (one_minus_k * self.p * one_minus_k) + (k * sensor.variance * k);
Ok(FusionStatus::Fused)
}
pub fn state(&self) -> f64 {
self.x
}
pub fn variance(&self) -> f64 {
self.p
}
pub fn sensor_health(&self, sensor_id: u8) -> Option<f64> {
for s in self.sensors.iter().flatten() {
if s.id == sensor_id {
return Some(s.health_score);
}
}
None
}
}