use core::fmt;
use core::ops::Deref;
use crate::inline::{Inline, InlineStr};
use crate::time::{Instant, Utc};
use crate::units::Speed;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PositionSource {
Gnss,
DeadReckoning,
Estimated,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RejectionReason {
Invalid,
Stale,
OutOfOrder,
ImplausibleJump {
implied_speed: Speed,
},
Improbable {
normalised_innovation_squared: f64,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NavigationIntegrity {
Nominal,
DeadReckoning,
Exceeded,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SensorHealth {
Healthy,
Suspect,
}
pub const SENSOR_NAME_BYTES: usize = 32;
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SensorId(InlineStr<SENSOR_NAME_BYTES>);
impl SensorId {
#[must_use]
pub fn named(name: &str) -> Self {
Self(InlineStr::new(name))
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Debug for SensorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for SensorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TargetId(u32);
impl TargetId {
#[must_use]
pub const fn new(number: u32) -> Self {
Self(number)
}
#[must_use]
pub const fn number(self) -> u32 {
self.0
}
}
impl fmt::Display for TargetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.0)
}
}
impl core::hash::Hash for SensorId {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl PartialEq<str> for SensorId {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for SensorId {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NavigationEvent {
FixAcquired {
source: PositionSource,
at: Instant<Utc>,
},
FixLost {
source: PositionSource,
at: Instant<Utc>,
last_good: Instant<Utc>,
},
ObservationRejected {
reason: RejectionReason,
at: Instant<Utc>,
},
IntegrityChanged {
from: NavigationIntegrity,
to: NavigationIntegrity,
at: Instant<Utc>,
},
SensorHealthChanged {
sensor: SensorId,
from: SensorHealth,
to: SensorHealth,
at: Instant<Utc>,
},
}
pub trait Event: Copy + PartialEq + fmt::Debug {
const PLACEHOLDER: Self;
fn at(&self) -> Instant<Utc>;
}
impl Event for NavigationEvent {
const PLACEHOLDER: Self = Self::FixAcquired {
source: PositionSource::Gnss,
at: Instant::UNIX_EPOCH,
};
fn at(&self) -> Instant<Utc> {
match self {
Self::FixAcquired { at, .. }
| Self::FixLost { at, .. }
| Self::ObservationRejected { at, .. }
| Self::IntegrityChanged { at, .. }
| Self::SensorHealthChanged { at, .. } => *at,
}
}
}
pub const MAX_EVENTS: usize = 8;
#[must_use = "an unread event list is a navigation event nobody acted on"]
#[derive(Clone, Copy)]
pub struct EventList<E: Event = NavigationEvent, const N: usize = MAX_EVENTS> {
events: Inline<E, N>,
overflowed: bool,
}
impl<E: Event> EventList<E, MAX_EVENTS> {
pub const fn new() -> Self {
Self::with_capacity()
}
}
impl<E: Event, const N: usize> EventList<E, N> {
pub const fn with_capacity() -> Self {
Self {
events: Inline::new(E::PLACEHOLDER),
overflowed: false,
}
}
pub fn push(&mut self, event: E) {
if self.events.push(event).is_err() {
self.overflowed = true;
}
}
#[must_use]
pub const fn overflowed(&self) -> bool {
self.overflowed
}
#[must_use]
pub fn as_slice(&self) -> &[E] {
self.events.as_slice()
}
}
impl<E: Event> Default for EventList<E, MAX_EVENTS> {
fn default() -> Self {
Self::new()
}
}
impl<E: Event, const N: usize> Deref for EventList<E, N> {
type Target = [E];
fn deref(&self) -> &[E] {
self.as_slice()
}
}
impl<'a, E: Event, const N: usize> IntoIterator for &'a EventList<E, N> {
type Item = &'a E;
type IntoIter = core::slice::Iter<'a, E>;
fn into_iter(self) -> Self::IntoIter {
self.as_slice().iter()
}
}
impl<E: Event, const N: usize> fmt::Debug for EventList<E, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventList")
.field("events", &self.events)
.field("overflowed", &self.overflowed)
.finish()
}
}
impl<E: Event, const N: usize, const M: usize> PartialEq<EventList<E, M>> for EventList<E, N> {
fn eq(&self, other: &EventList<E, M>) -> bool {
self.overflowed == other.overflowed && self.as_slice() == other.as_slice()
}
}
#[cfg(test)]
#[allow(clippy::cast_possible_wrap)]
mod tests {
use super::*;
const CAPACITY: i64 = MAX_EVENTS as i64;
fn acquired(seconds: i64) -> NavigationEvent {
NavigationEvent::FixAcquired {
source: PositionSource::Gnss,
at: Instant::from_unix_seconds(seconds),
}
}
#[test]
fn events_come_back_in_order() {
let mut list = EventList::new();
assert!(list.is_empty());
list.push(acquired(1));
list.push(NavigationEvent::ObservationRejected {
reason: RejectionReason::Stale,
at: Instant::from_unix_seconds(2),
});
assert_eq!(list.len(), 2);
assert_eq!(list.first(), Some(&acquired(1)));
assert!(matches!(
list.last(),
Some(NavigationEvent::ObservationRejected {
reason: RejectionReason::Stale,
..
})
));
assert_eq!((&list).into_iter().count(), 2);
assert!(!list.overflowed());
}
#[test]
fn a_full_list_keeps_the_earliest_and_says_it_lost_the_rest() {
let mut list = EventList::new();
for second in 0..CAPACITY {
list.push(acquired(second));
}
assert!(!list.overflowed());
list.push(acquired(99));
assert!(list.overflowed());
assert_eq!(list.len(), MAX_EVENTS);
assert_eq!(list.last(), Some(&acquired(CAPACITY - 1)));
}
#[test]
fn lists_compare_by_events_and_by_loss() {
let mut first = EventList::new();
let mut second = EventList::default();
first.push(acquired(1));
second.push(acquired(1));
assert_eq!(first, second);
for second_number in 0..=CAPACITY {
second.push(acquired(second_number));
}
assert_ne!(first, second);
}
}