1use umsh_core::RouterHint;
13use umsh_hal::Snr;
14
15pub const MAX_TRANSMITTER_OBSERVATIONS: usize = 16;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct TransmitterObservation {
25 pub hint: RouterHint,
28 pub rssi_dbm: i16,
30 pub snr: Snr,
32 pub last_seen_ms: u64,
34}
35
36#[derive(Clone, Debug, Default)]
38pub struct TransmitterObservations {
39 entries: heapless::Vec<TransmitterObservation, MAX_TRANSMITTER_OBSERVATIONS>,
40}
41
42impl TransmitterObservations {
43 pub const fn new() -> Self {
44 Self {
45 entries: heapless::Vec::new(),
46 }
47 }
48
49 pub fn observe(&mut self, hint: RouterHint, rssi_dbm: i16, snr: Snr, now_ms: u64) {
55 if let Some(entry) = self.entries.iter_mut().find(|entry| entry.hint == hint) {
56 entry.rssi_dbm = rssi_dbm;
57 entry.snr = snr;
58 entry.last_seen_ms = now_ms;
59 return;
60 }
61 let observation = TransmitterObservation {
62 hint,
63 rssi_dbm,
64 snr,
65 last_seen_ms: now_ms,
66 };
67 if self.entries.push(observation).is_ok() {
68 return;
69 }
70 let Some(oldest) = self
73 .entries
74 .iter_mut()
75 .min_by_key(|entry| entry.last_seen_ms)
76 else {
77 return;
78 };
79 *oldest = observation;
80 }
81
82 pub fn iter(&self) -> impl Iterator<Item = &TransmitterObservation> {
83 self.entries.iter()
84 }
85
86 pub fn len(&self) -> usize {
87 self.entries.len()
88 }
89
90 pub fn is_empty(&self) -> bool {
91 self.entries.is_empty()
92 }
93
94 pub fn get(&self, hint: &RouterHint) -> Option<&TransmitterObservation> {
96 self.entries.iter().find(|entry| &entry.hint == hint)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 fn hint(seed: u8) -> RouterHint {
105 RouterHint([seed, seed])
106 }
107
108 #[test]
109 fn a_repeat_reception_replaces_what_was_known_rather_than_adding_to_it() {
110 let mut table = TransmitterObservations::new();
111 table.observe(hint(1), -100, Snr::from_decibels(-9), 1_000);
112 table.observe(hint(1), -70, Snr::from_decibels(6), 2_000);
113 assert_eq!(table.len(), 1);
114 let entry = table.get(&hint(1)).unwrap();
115 assert_eq!(entry.rssi_dbm, -70);
116 assert_eq!(entry.snr, Snr::from_decibels(6));
117 assert_eq!(entry.last_seen_ms, 2_000);
118 }
119
120 #[test]
121 fn a_full_table_drops_the_least_recently_heard_transmitter() {
122 let mut table = TransmitterObservations::new();
123 for seed in 0..MAX_TRANSMITTER_OBSERVATIONS as u8 {
124 table.observe(
125 hint(seed),
126 -90,
127 Snr::from_decibels(0),
128 1_000 + u64::from(seed),
129 );
130 }
131 table.observe(hint(0), -80, Snr::from_decibels(1), 9_000);
134 table.observe(hint(200), -95, Snr::from_decibels(-2), 10_000);
135
136 assert_eq!(table.len(), MAX_TRANSMITTER_OBSERVATIONS);
137 assert!(table.get(&hint(200)).is_some());
138 assert!(
139 table.get(&hint(0)).is_some(),
140 "refreshed, so not the oldest"
141 );
142 assert!(table.get(&hint(1)).is_none(), "least recently heard");
143 }
144}