1use core::fmt;
19use core::ops::Deref;
20
21use crate::inline::{Inline, InlineStr};
22use crate::time::{Instant, Utc};
23use crate::units::Speed;
24
25#[non_exhaustive]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub enum PositionSource {
32 Gnss,
34 DeadReckoning,
36 Estimated,
38}
39
40#[non_exhaustive]
44#[derive(Debug, Clone, Copy, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub enum RejectionReason {
47 Invalid,
49 Stale,
51 OutOfOrder,
55 ImplausibleJump {
57 implied_speed: Speed,
59 },
60 Improbable {
62 normalised_innovation_squared: f64,
64 },
65}
66
67#[non_exhaustive]
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77pub enum NavigationIntegrity {
78 Nominal,
80 DeadReckoning,
83 Exceeded,
87}
88
89#[non_exhaustive]
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98pub enum SensorHealth {
99 Healthy,
101 Suspect,
105}
106
107pub const SENSOR_NAME_BYTES: usize = 32;
109
110#[derive(Clone, Copy, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114pub struct SensorId(InlineStr<SENSOR_NAME_BYTES>);
115
116impl SensorId {
117 #[must_use]
119 pub fn named(name: &str) -> Self {
120 Self(InlineStr::new(name))
121 }
122
123 #[must_use]
125 pub fn as_str(&self) -> &str {
126 self.0.as_str()
127 }
128}
129
130impl fmt::Debug for SensorId {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 fmt::Debug::fmt(&self.0, f)
133 }
134}
135
136impl fmt::Display for SensorId {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 fmt::Display::fmt(&self.0, f)
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149pub struct TargetId(u32);
150
151impl TargetId {
152 #[must_use]
154 pub const fn new(number: u32) -> Self {
155 Self(number)
156 }
157
158 #[must_use]
160 pub const fn number(self) -> u32 {
161 self.0
162 }
163}
164
165impl fmt::Display for TargetId {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 write!(f, "#{}", self.0)
169 }
170}
171
172impl core::hash::Hash for SensorId {
173 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
174 self.as_str().hash(state);
175 }
176}
177
178impl PartialEq<str> for SensorId {
179 fn eq(&self, other: &str) -> bool {
180 self.as_str() == other
181 }
182}
183
184impl PartialEq<&str> for SensorId {
185 fn eq(&self, other: &&str) -> bool {
186 self.as_str() == *other
187 }
188}
189
190#[non_exhaustive]
194#[derive(Debug, Clone, Copy, PartialEq)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196pub enum NavigationEvent {
197 FixAcquired {
199 source: PositionSource,
201 at: Instant<Utc>,
203 },
204 FixLost {
206 source: PositionSource,
208 at: Instant<Utc>,
210 last_good: Instant<Utc>,
212 },
213 ObservationRejected {
215 reason: RejectionReason,
217 at: Instant<Utc>,
219 },
220 IntegrityChanged {
222 from: NavigationIntegrity,
224 to: NavigationIntegrity,
226 at: Instant<Utc>,
228 },
229 SensorHealthChanged {
231 sensor: SensorId,
233 from: SensorHealth,
235 to: SensorHealth,
237 at: Instant<Utc>,
239 },
240}
241
242pub trait Event: Copy + PartialEq + fmt::Debug {
248 const PLACEHOLDER: Self;
250
251 fn at(&self) -> Instant<Utc>;
253}
254
255impl Event for NavigationEvent {
256 const PLACEHOLDER: Self = Self::FixAcquired {
257 source: PositionSource::Gnss,
258 at: Instant::UNIX_EPOCH,
259 };
260
261 fn at(&self) -> Instant<Utc> {
262 match self {
263 Self::FixAcquired { at, .. }
264 | Self::FixLost { at, .. }
265 | Self::ObservationRejected { at, .. }
266 | Self::IntegrityChanged { at, .. }
267 | Self::SensorHealthChanged { at, .. } => *at,
268 }
269 }
270}
271
272pub const MAX_EVENTS: usize = 8;
279
280#[must_use = "an unread event list is a navigation event nobody acted on"]
285#[derive(Clone, Copy)]
286pub struct EventList<E: Event = NavigationEvent, const N: usize = MAX_EVENTS> {
287 events: Inline<E, N>,
288 overflowed: bool,
289}
290
291impl<E: Event> EventList<E, MAX_EVENTS> {
292 pub const fn new() -> Self {
294 Self::with_capacity()
295 }
296}
297
298impl<E: Event, const N: usize> EventList<E, N> {
299 pub const fn with_capacity() -> Self {
302 Self {
303 events: Inline::new(E::PLACEHOLDER),
304 overflowed: false,
305 }
306 }
307
308 pub fn push(&mut self, event: E) {
314 if self.events.push(event).is_err() {
315 self.overflowed = true;
316 }
317 }
318
319 #[must_use]
324 pub const fn overflowed(&self) -> bool {
325 self.overflowed
326 }
327
328 #[must_use]
330 pub fn as_slice(&self) -> &[E] {
331 self.events.as_slice()
332 }
333}
334
335impl<E: Event> Default for EventList<E, MAX_EVENTS> {
336 fn default() -> Self {
337 Self::new()
338 }
339}
340
341impl<E: Event, const N: usize> Deref for EventList<E, N> {
342 type Target = [E];
343
344 fn deref(&self) -> &[E] {
345 self.as_slice()
346 }
347}
348
349impl<'a, E: Event, const N: usize> IntoIterator for &'a EventList<E, N> {
350 type Item = &'a E;
351 type IntoIter = core::slice::Iter<'a, E>;
352
353 fn into_iter(self) -> Self::IntoIter {
354 self.as_slice().iter()
355 }
356}
357
358impl<E: Event, const N: usize> fmt::Debug for EventList<E, N> {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 f.debug_struct("EventList")
361 .field("events", &self.events)
362 .field("overflowed", &self.overflowed)
363 .finish()
364 }
365}
366
367impl<E: Event, const N: usize, const M: usize> PartialEq<EventList<E, M>> for EventList<E, N> {
368 fn eq(&self, other: &EventList<E, M>) -> bool {
371 self.overflowed == other.overflowed && self.as_slice() == other.as_slice()
372 }
373}
374
375#[cfg(test)]
376#[allow(clippy::cast_possible_wrap)]
377mod tests {
378 use super::*;
379
380 const CAPACITY: i64 = MAX_EVENTS as i64;
382
383 fn acquired(seconds: i64) -> NavigationEvent {
384 NavigationEvent::FixAcquired {
385 source: PositionSource::Gnss,
386 at: Instant::from_unix_seconds(seconds),
387 }
388 }
389
390 #[test]
391 fn events_come_back_in_order() {
392 let mut list = EventList::new();
393 assert!(list.is_empty());
394 list.push(acquired(1));
395 list.push(NavigationEvent::ObservationRejected {
396 reason: RejectionReason::Stale,
397 at: Instant::from_unix_seconds(2),
398 });
399 assert_eq!(list.len(), 2);
400 assert_eq!(list.first(), Some(&acquired(1)));
401 assert!(matches!(
402 list.last(),
403 Some(NavigationEvent::ObservationRejected {
404 reason: RejectionReason::Stale,
405 ..
406 })
407 ));
408 assert_eq!((&list).into_iter().count(), 2);
409 assert!(!list.overflowed());
410 }
411
412 #[test]
413 fn a_full_list_keeps_the_earliest_and_says_it_lost_the_rest() {
414 let mut list = EventList::new();
415 for second in 0..CAPACITY {
416 list.push(acquired(second));
417 }
418 assert!(!list.overflowed());
419 list.push(acquired(99));
420 assert!(list.overflowed());
421 assert_eq!(list.len(), MAX_EVENTS);
422 assert_eq!(list.last(), Some(&acquired(CAPACITY - 1)));
423 }
424
425 #[test]
426 fn lists_compare_by_events_and_by_loss() {
427 let mut first = EventList::new();
428 let mut second = EventList::default();
429 first.push(acquired(1));
430 second.push(acquired(1));
431 assert_eq!(first, second);
432 for second_number in 0..=CAPACITY {
433 second.push(acquired(second_number));
434 }
435 assert_ne!(first, second);
436 }
437}