1use crate::client::types as client_types;
6use anyhow::{format_err, Error};
7use log::error;
8
9fn calculate_ewma_update(current: f64, next: f64, weighting_factor: f64) -> f64 {
11 let weight = 2.0 / (1.0 + weighting_factor);
12 weight * next + (1.0 - weight) * current
13}
14
15#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct EwmaPseudoDecibel {
19 current: f64,
20 weighting_factor: f64,
21}
22
23impl EwmaPseudoDecibel {
24 pub fn new(n: usize, initial_signal: impl Into<f64>) -> Self {
25 Self { current: initial_signal.into(), weighting_factor: n as f64 }
26 }
27
28 pub fn get(&self) -> f64 {
30 self.current
31 }
32
33 pub fn update_average(&mut self, next: impl Into<f64>) {
34 self.current = calculate_ewma_update(self.current, next.into(), self.weighting_factor);
35 }
36}
37
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub struct EwmaSignalData {
40 pub ewma_rssi: EwmaPseudoDecibel,
41 pub ewma_snr: EwmaPseudoDecibel,
42}
43
44impl EwmaSignalData {
45 pub fn new(
46 initial_rssi: impl Into<f64>,
47 initial_snr: impl Into<f64>,
48 ewma_weight: usize,
49 ) -> Self {
50 Self {
51 ewma_rssi: EwmaPseudoDecibel::new(ewma_weight, initial_rssi),
52 ewma_snr: EwmaPseudoDecibel::new(ewma_weight, initial_snr),
53 }
54 }
55
56 #[allow(clippy::ptr_arg, reason = "mass allow for https://fxbug.dev/381896734")]
57 pub fn new_from_list(
58 signals: &Vec<client_types::Signal>,
59 ewma_weight: usize,
60 ) -> Result<Self, anyhow::Error> {
61 let first_signal =
62 signals.first().ok_or_else(|| format_err!("At least one signal must be provided"))?;
63
64 let mut ewma = Self::new(first_signal.rssi_dbm, first_signal.snr_db, ewma_weight);
65 for signal in signals.iter().skip(1) {
66 ewma.update_with_new_measurement(signal.rssi_dbm, signal.snr_db);
67 }
68 Ok(ewma)
69 }
70
71 pub fn update_with_new_measurement(&mut self, rssi: impl Into<f64>, snr: impl Into<f64>) {
72 self.ewma_rssi.update_average(rssi);
73 self.ewma_snr.update_average(snr);
74 }
75}
76
77fn calculate_raw_velocity(samples: Vec<f64>) -> Result<f64, Error> {
86 let n = i32::try_from(samples.len())?;
87 if n < 2 {
88 return Err(format_err!("At least two data points required to calculate velocity"));
89 }
90 let mut sum_x: i32 = 0;
92 let mut sum_y: i32 = 0;
93 let mut sum_xy: i32 = 0;
94 let mut sum_x2: i32 = 0;
95
96 for (i, y) in samples.iter().enumerate() {
98 let x = i32::try_from(i).map_err(|_| format_err!("failed to convert index to i32"))?;
99 sum_x = sum_x.checked_add(x).ok_or_else(|| format_err!("overflow of X summation"))?;
100 sum_y =
101 sum_y.checked_add(*y as i32).ok_or_else(|| format_err!("overflow of Y summation"))?;
102 sum_xy = sum_xy
103 .checked_add(x.checked_mul(*y as i32).ok_or_else(|| format_err!("overflow of X * Y"))?)
104 .ok_or_else(|| format_err!("overflow of XY summation"))?;
105 sum_x2 = sum_x2
106 .checked_add(x.checked_mul(x).ok_or_else(|| format_err!("overflow of X**2"))?)
107 .ok_or_else(|| format_err!("overflow of X2 summation"))?;
108 }
109
110 let velocity = (n.checked_mul(sum_xy).ok_or_else(|| format_err!("overflow in n * sum_xy"))?
113 - sum_x.checked_mul(sum_y).ok_or_else(|| format_err!("overflow in sum_x * sum_y"))?)
114 / (n.checked_mul(sum_x2).ok_or_else(|| format_err!("overflow in n * sum_x2"))?
115 - sum_x.checked_mul(sum_x).ok_or_else(|| format_err!("overflow in sum_x**2"))?);
116 Ok(velocity.into())
117}
118
119#[derive(Clone, Copy, Debug, PartialEq)]
121pub struct RssiVelocity {
122 curr_velocity: f64,
123 prev_rssi: f64,
124}
125impl RssiVelocity {
126 pub fn new(initial_rssi: impl Into<f64>) -> Self {
127 Self { curr_velocity: 0.0, prev_rssi: initial_rssi.into() }
128 }
129
130 #[allow(clippy::ptr_arg, reason = "mass allow for https://fxbug.dev/381896734")]
131 pub fn new_from_list(rssi_samples: &Vec<f64>) -> Result<Self, anyhow::Error> {
132 let last = rssi_samples.last().ok_or_else(|| format_err!("empty list"))?;
133 match calculate_raw_velocity(rssi_samples.to_vec()) {
134 Ok(velocity) => Ok(Self { curr_velocity: velocity, prev_rssi: *last }),
135 Err(e) => Err(e),
136 }
137 }
138
139 pub fn get(&mut self) -> f64 {
140 self.curr_velocity
141 }
142
143 pub fn update(&mut self, rssi: impl Into<f64>) {
144 let rssi: f64 = rssi.into();
145 match calculate_raw_velocity(vec![self.prev_rssi, rssi]) {
146 Ok(velocity) => self.curr_velocity = velocity,
147 Err(e) => {
148 error!("Failed to update velocity: {:?}", e);
149 }
150 }
151 self.prev_rssi = rssi;
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use test_util::{assert_gt, assert_lt};
159
160 #[fuchsia::test]
161 fn test_ewma_pseudo_decibel_simple_averaging_calculations() {
162 let mut ewma_signal = EwmaPseudoDecibel::new(10, -50);
163 assert_eq!(ewma_signal.get(), -50.0);
164
165 ewma_signal.update_average(-60);
167 assert_lt!(ewma_signal.get(), -50.0);
168 assert_gt!(ewma_signal.get(), -60.0);
169
170 for _ in 0..20 {
172 ewma_signal.update_average(-60)
173 }
174 assert_eq!(ewma_signal.get().round(), -60.0);
175 }
176
177 #[fuchsia::test]
178 fn test_ewma_pseudo_decibel_small_variation_averaging() {
179 let mut ewma_signal = EwmaPseudoDecibel::new(5, -90);
180 assert_eq!(ewma_signal.get(), -90.0);
181
182 ewma_signal.update_average(-91);
185 assert_eq!(ewma_signal.get().round(), -90.0);
186 assert_lt!(ewma_signal.current, -90.0);
187
188 for _ in 0..5 {
190 ewma_signal.update_average(-91);
191 }
192 assert_lt!(ewma_signal.get(), -90.9);
193 }
194
195 #[fuchsia::test]
196 fn test_ewma_signal_data_new_from_list() {
197 let signals = vec![
198 client_types::Signal { rssi_dbm: -40, snr_db: 30 },
199 client_types::Signal { rssi_dbm: -60, snr_db: 15 },
200 ];
201 let ewma = EwmaSignalData::new_from_list(&signals, 10).unwrap();
202 assert_lt!(ewma.ewma_rssi.get(), -40.0);
203 assert_gt!(ewma.ewma_rssi.get(), -60.0);
204 assert_lt!(ewma.ewma_snr.get(), 30.0);
205 assert_gt!(ewma.ewma_snr.get(), 15.0);
206 }
207
208 #[fuchsia::test]
209 fn test_ewma_signal_data_new_from_list_empty() {
210 assert!(EwmaSignalData::new_from_list(&vec![], 10).is_err());
211 }
212
213 #[fuchsia::test]
214 fn test_ewma_signal_data_update_with_new_measurements() {
215 let mut signal_data = EwmaSignalData::new(-40, 30, 10);
216 signal_data.update_with_new_measurement(-60, 15);
217 assert_lt!(signal_data.ewma_rssi.get(), -40.0);
218 assert_gt!(signal_data.ewma_rssi.get(), -60.0);
219 assert_lt!(signal_data.ewma_snr.get(), 30.0);
220 assert_gt!(signal_data.ewma_snr.get(), 15.0);
221 }
222
223 #[fuchsia::test]
225 fn test_calculate_raw_velocity_insufficient_args() {
226 assert!(calculate_raw_velocity(vec![]).is_err());
227 assert!(calculate_raw_velocity(vec![-60.0]).is_err());
228 }
229
230 #[fuchsia::test]
231 fn test_calculate_raw_velocity_negative() {
232 assert_eq!(calculate_raw_velocity(vec![-60.0, -75.0]).expect("failed to calculate"), -15.0);
233 assert_eq!(
234 calculate_raw_velocity(vec![-40.0, -50.0, -58.0, -64.0]).expect("failed to calculate"),
235 -8.0
236 );
237 }
238
239 #[fuchsia::test]
240 fn test_calculate_raw_velocity_positive() {
241 assert_eq!(calculate_raw_velocity(vec![-48.0, -45.0]).expect("failed to calculate"), 3.0);
242 assert_eq!(
243 calculate_raw_velocity(vec![-70.0, -55.0, -45.0, -30.0]).expect("failed to calculate"),
244 13.0
245 );
246 }
247
248 #[fuchsia::test]
249 fn test_calculate_raw_velocity_constant_zero() {
250 assert_eq!(
251 calculate_raw_velocity(vec![-25.0, -25.0, -25.0, -25.0, -25.0, -25.0])
252 .expect("failed to calculate"),
253 0.0
254 );
255 }
256
257 #[fuchsia::test]
258 fn test_calculate_raw_velocity_oscillating_zero() {
259 assert_eq!(
260 calculate_raw_velocity(vec![-35.0, -45.0, -35.0, -25.0, -35.0, -45.0, -35.0,])
261 .expect("failed to calculate"),
262 0.0
263 );
264 }
265
266 #[fuchsia::test]
267 fn test_calculate_raw_velocity_min_max() {
268 assert_eq!(
269 calculate_raw_velocity(vec![-1.0, -128.0]).expect("failed to calculate"),
270 -127.0
271 );
272 assert_eq!(calculate_raw_velocity(vec![-128.0, -1.0]).expect("failed to calculate"), 127.0);
273 }
274
275 #[fuchsia::test]
276 fn test_rssi_velocity_update() {
277 let mut velocity = RssiVelocity::new(-40.0);
278 velocity.update(-80.0);
279 assert_lt!(velocity.get(), 0.0);
280
281 let mut velocity = RssiVelocity::new(-40.0);
282 velocity.update(-20.0);
283 assert_gt!(velocity.get(), 0.0);
284 }
285
286 #[fuchsia::test]
287 fn test_rssi_velocity_new_from_list_insufficent_args() {
288 assert!(RssiVelocity::new_from_list(&vec![]).is_err());
289 assert!(RssiVelocity::new_from_list(&vec![-40.0]).is_err());
290 }
291
292 #[fuchsia::test]
293 fn test_rssi_velocity_new_from_list_negative() {
294 assert_eq!(
295 RssiVelocity::new_from_list(&vec![-60.0, -75.0]).expect("failed to calculate").get(),
296 -15.0
297 );
298 assert_eq!(
299 RssiVelocity::new_from_list(&vec![-40.0, -50.0, -58.0, -64.0])
300 .expect("failed to calculate")
301 .get(),
302 -8.0
303 );
304 }
305
306 #[fuchsia::test]
307 fn test_rssi_velocity_new_from_list_positive() {
308 assert_eq!(
309 RssiVelocity::new_from_list(&vec![-48.0, -45.0]).expect("failed to calculate").get(),
310 3.0
311 );
312 assert_eq!(
313 RssiVelocity::new_from_list(&vec![-70.0, -55.0, -45.0, -30.0])
314 .expect("failed to calculate")
315 .get(),
316 13.0
317 );
318 }
319
320 #[fuchsia::test]
321 fn test_rssi_velocity_new_from_list_constant_zero() {
322 assert_eq!(
323 RssiVelocity::new_from_list(&vec![-25.0, -25.0, -25.0, -25.0, -25.0, -25.0])
324 .expect("failed to calculate")
325 .get(),
326 0.0
327 );
328 }
329
330 #[fuchsia::test]
331 fn test_rssi_velocity_new_from_list_oscillating_zero() {
332 assert_eq!(
333 RssiVelocity::new_from_list(&vec![-35.0, -45.0, -35.0, -25.0, -35.0, -45.0, -35.0,])
334 .expect("failed to calculate")
335 .get(),
336 0.0
337 );
338 }
339
340 #[fuchsia::test]
341 fn test_rssi_velocity_new_from_list_min_max() {
342 assert_eq!(
343 RssiVelocity::new_from_list(&vec![-1.0, -128.0]).expect("failed to calculate").get(),
344 -127.0
345 );
346 assert_eq!(
347 RssiVelocity::new_from_list(&vec![-128.0, -1.0]).expect("failed to calculate").get(),
348 127.0
349 );
350 }
351}