use std::{sync::Arc, time::Duration};
#[allow(unused_imports)]
use log::debug;
use log::warn;
use measurements::{Distance, Speed};
use vexide::{
adi::range_finder::AdiRangeFinder,
smart::distance::DistanceSensor,
sync::Mutex,
time::user_uptime,
};
pub enum RangeSensor {
SmartDistSensor(Arc<Mutex<DistanceSensor>>),
AdiDistanceSensor(Arc<Mutex<AdiRangeFinder>>),
#[cfg(any(test, debug_assertions))]
Mock {
distance: Option<Distance>,
velocity: Option<Speed>,
},
}
impl RangeSensor {
pub fn from_distance(sensor: DistanceSensor) -> Self {
Self::SmartDistSensor(Arc::new(Mutex::new(sensor)))
}
pub fn from_adi(sensor: AdiRangeFinder) -> Self {
Self::AdiDistanceSensor(Arc::new(Mutex::new(sensor)))
}
pub async fn distance(&self) -> Option<Distance> {
match self {
RangeSensor::AdiDistanceSensor(sensor) => match sensor.lock().await.distance() {
Ok(dist) => match dist {
Some(d) => Some(Distance::from_centimeters(d as f64)),
None => None,
},
Err(e) => {
warn!("Error getting distance: {}", e);
None
}
},
RangeSensor::SmartDistSensor(sensor) => {
let object = sensor.lock().await.object();
match object {
Ok(obj) => match obj {
Some(obj) => Some(Distance::from_micrometres(obj.distance as f64)),
None => None,
},
Err(e) => {
warn!("Error getting object from distance sensor: {}", e);
None
}
}
}
RangeSensor::Mock {
distance,
velocity: _,
} => distance.clone(),
}
}
pub async fn velocity(&self) -> Option<Speed> {
match self {
Self::SmartDistSensor(sensor) => {
let obj = sensor.lock().await.object();
match obj {
Ok(Some(obj)) => Some(Speed::from_metres_per_second(obj.velocity)),
Ok(None) => None,
Err(e) => {
warn!("Error getting velocity {}", e);
None
}
}
}
Self::AdiDistanceSensor(_) => None,
Self::Mock {
distance: _,
velocity,
} => velocity.clone(),
}
}
}
pub struct KalmanRangeSensor {
sensor: RangeSensor,
process_var: f64,
measurement_var: f64,
last_update: Duration, prev_m: Distance, prev_vel: Speed, prev_var: f64,
est_m: Distance,
est_var: f64,
new_m: Distance,
new_var: f64,
}
impl KalmanRangeSensor {
pub fn new(
sensor: RangeSensor,
process_var: f64,
measurement_var: f64,
initial_distance: Distance,
initial_velocity: Speed,
) -> Self {
Self {
sensor,
process_var,
measurement_var,
last_update: user_uptime(),
prev_m: initial_distance,
prev_vel: initial_velocity,
prev_var: measurement_var, est_m: initial_distance,
est_var: measurement_var,
new_m: initial_distance,
new_var: measurement_var,
}
}
pub async fn predict(&mut self) {
let elapsed = user_uptime() - self.last_update;
let dx = self.prev_vel * elapsed;
self.est_m = self.prev_m + dx;
self.est_var = self.prev_var + self.process_var;
self.last_update = user_uptime();
}
#[cfg(any(test, debug_assertions))]
pub async fn predict_with_dt(&mut self, dt: Duration) {
let dx = self.prev_vel * dt;
self.est_m = self.prev_m + dx;
self.est_var = self.prev_var + self.process_var;
self.last_update = self.last_update + dt;
}
pub async fn update(&mut self) {
let m = match self.sensor.distance().await {
Some(val) => val,
None => {
warn!("Error getting distance");
self.new_m = self.est_m;
self.new_var = self.est_var;
return; }
};
let residual = m - self.est_m;
let kalman_gain = self.est_var / (self.est_var + self.measurement_var);
self.new_m = self.est_m + (kalman_gain * residual);
self.new_var = (1.0 - kalman_gain) * self.est_var;
self.prev_m = self.new_m;
self.prev_var = self.new_var;
if let Some(vel) = self.sensor.velocity().await {
self.prev_vel = vel;
} else {
let distance_change = self.new_m - self.prev_m;
let elapsed = user_uptime() - self.last_update;
self.prev_vel = distance_change / elapsed;
}
}
pub fn measurement(&self) -> Distance { self.new_m }
pub fn variance(&self) -> f64 { self.new_var }
pub fn velocity(&self) -> Speed { self.prev_vel }
pub fn predicted_measurement(&self) -> Distance { self.est_m }
pub fn predicted_variance(&self) -> f64 { self.est_var }
pub fn reset(&mut self, initial_distance: Distance, initial_velocity: Speed) {
self.prev_m = initial_distance;
self.prev_vel = initial_velocity;
self.prev_var = self.measurement_var;
self.est_m = initial_distance;
self.est_var = self.measurement_var;
self.new_m = initial_distance;
self.new_var = self.measurement_var;
self.last_update = user_uptime();
}
#[cfg(any(test, debug_assertions))]
pub fn set_sensor_mock(&mut self, distance: Distance, velocity: Speed) {
self.sensor = RangeSensor::Mock {
distance: Some(distance),
velocity: Some(velocity),
};
}
}
#[cfg(test)]
mod tests {
use vexide::prelude::Peripherals;
use super::*;
struct TestDataPoint {
distance: Distance,
velocity: Speed,
dt: Duration,
}
#[vexide::test]
async fn test_kalman_filter_predict_update(_p: Peripherals) {
let test_data_clean = vec![
TestDataPoint {
distance: Distance::from_metres(1.0),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Distance::from_metres(1.1),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Distance::from_metres(1.2),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Distance::from_metres(1.3),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Distance::from_metres(1.4),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
];
let initial_dist = Distance::from_metres(1.0);
let initial_vel = Speed::from_metres_per_second(1.0);
let mut kalman = KalmanRangeSensor::new(
RangeSensor::Mock {
distance: Some(initial_dist),
velocity: Some(initial_vel),
},
0.01, 0.05, initial_dist,
initial_vel,
);
debug!("=== Kalman Filter Clean Data Test ===");
for (i, data_point) in test_data_clean.iter().enumerate() {
kalman.predict_with_dt(data_point.dt).await;
let predicted_dist = kalman.predicted_measurement().as_metres();
let predicted_var = kalman.predicted_variance();
kalman.set_sensor_mock(data_point.distance, data_point.velocity);
kalman.update().await;
let updated_dist = kalman.measurement().as_metres();
let updated_var = kalman.variance();
assert!(updated_dist > 0.0, "Step {}: Distance should be positive", i + 1);
assert!(
updated_var < predicted_var,
"Step {}: Variance should decrease after update",
i + 1
);
assert!(updated_var > 0.0, "Step {}: Variance should be positive", i + 1);
let min_dist = predicted_dist.min(data_point.distance.as_metres());
let max_dist = predicted_dist.max(data_point.distance.as_metres());
assert!(
updated_dist >= min_dist - 0.01 && updated_dist <= max_dist + 0.01,
"Step {}: Updated distance should be between prediction and measurement",
i + 1
);
}
let test_data_noisy = vec![
TestDataPoint {
distance: Distance::from_metres(2.0),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Distance::from_metres(2.15),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Distance::from_metres(2.05),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Distance::from_metres(2.25),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Distance::from_metres(2.20),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
];
let initial_dist = Distance::from_metres(2.0);
let initial_vel = Speed::from_metres_per_second(2.0);
let mut kalman = KalmanRangeSensor::new(
RangeSensor::Mock {
distance: Some(initial_dist),
velocity: Some(initial_vel),
},
0.02, 0.1, initial_dist,
initial_vel,
);
debug!("=== Kalman Filter Noisy Data Test ===");
let mut prev_var = kalman.variance();
for (i, data_point) in test_data_noisy.iter().enumerate() {
kalman.predict_with_dt(data_point.dt).await;
kalman.set_sensor_mock(data_point.distance, data_point.velocity);
kalman.update().await;
let current_var = kalman.variance();
assert!(current_var > 0.0, "Step {}: Variance should be positive", i + 1);
assert!(
current_var <= prev_var + 0.01, "Step {}: Variance should not increase significantly",
i + 1
);
prev_var = current_var;
}
debug!("=== Kalman Filter Tests Complete ===");
}
#[test]
fn test_reset_functionality() {
let initial_dist = Distance::from_metres(1.0);
let initial_vel = Speed::from_metres_per_second(0.0);
let mut kalman = KalmanRangeSensor::new(
RangeSensor::Mock {
distance: Some(initial_dist),
velocity: Some(initial_vel),
},
0.01,
0.1,
initial_dist,
initial_vel,
);
assert_eq!(kalman.measurement().as_metres(), 1.0);
let new_dist = Distance::from_metres(5.0);
let new_vel = Speed::from_metres_per_second(0.5);
kalman.reset(new_dist, new_vel);
assert_eq!(kalman.measurement().as_metres(), 5.0);
assert_eq!(kalman.velocity().as_metres_per_second(), 0.5);
}
}