use core::time::Duration;
use crate::error::{KernelError, NavigationError, Result};
use crate::event::{EventList, NavigationEvent, NavigationIntegrity, SensorHealth, SensorId};
use crate::inline::Inline;
use crate::state::NavigationState;
use crate::time::{Instant, Utc};
use crate::units::Distance;
pub const MAX_SENSORS: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Record {
sensor: SensorId,
health: SensorHealth,
run: u8,
}
#[derive(Debug, Clone)]
pub(super) struct Sensors {
records: Inline<Record, MAX_SENSORS>,
}
impl Sensors {
pub(super) fn new() -> Self {
Self {
records: Inline::new(Record {
sensor: SensorId::named(""),
health: SensorHealth::Healthy,
run: 0,
}),
}
}
pub(super) fn health(&self, sensor: SensorId) -> Option<SensorHealth> {
self.records
.iter()
.find(|record| record.sensor == sensor)
.map(|record| record.health)
}
pub(super) fn note(
&mut self,
sensor: SensorId,
accepted: bool,
suspect_after: u8,
at: Instant<Utc>,
events: &mut EventList,
) -> Result<()> {
let known = self
.records
.iter()
.position(|record| record.sensor == sensor);
let index = if let Some(index) = known {
index
} else {
self.records
.push(Record {
sensor,
health: SensorHealth::Healthy,
run: 0,
})
.map_err(|_| {
NavigationError::Kernel(KernelError::CapacityExceeded {
context: "the estimator's sources",
needed: MAX_SENSORS.saturating_add(1),
capacity: MAX_SENSORS,
})
})?;
self.records.len().saturating_sub(1)
};
let Some(record) = self.records.as_mut_slice().get_mut(index) else {
return Ok(());
};
let (against, towards) = match record.health {
SensorHealth::Healthy => (accepted, SensorHealth::Suspect),
_ => (!accepted, SensorHealth::Healthy),
};
record.run = if against {
0
} else {
record.run.saturating_add(1)
};
if suspect_after > 0 && record.run >= suspect_after {
events.push(NavigationEvent::SensorHealthChanged {
sensor: record.sensor,
from: record.health,
to: towards,
at,
});
record.health = towards;
record.run = 0;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct IntegrityLimits {
pub(super) alert_limit: Distance,
pub(super) max_unaided: Duration,
}
pub(super) fn assess(
state: &NavigationState,
last_fix: Option<Instant<Utc>>,
now: Instant<Utc>,
limits: &IntegrityLimits,
) -> NavigationIntegrity {
if state.horizontal_error().semi_major() > limits.alert_limit {
return NavigationIntegrity::Exceeded;
}
let aided = last_fix.is_some_and(|fix| {
now.checked_duration_since(fix)
.is_some_and(|since| since <= limits.max_unaided)
});
if aided {
NavigationIntegrity::Nominal
} else {
NavigationIntegrity::DeadReckoning
}
}