use std::{sync::Arc, time::Duration};
#[allow(unused_imports)]
use log::debug;
use snafu::Snafu;
use vexide::{
adi::range_finder::AdiRangeFinder,
smart::{
PortError,
distance::{DistanceObjectError, DistanceSensor},
},
sync::Mutex,
time::user_uptime,
};
use crate::utils::{
error::Report,
units::{Length, Speed},
};
pub enum RangeSensor {
SmartDistSensor(Arc<Mutex<DistanceSensor>>),
AdiDistanceSensor(Arc<Mutex<AdiRangeFinder>>),
#[cfg(any(test, debug_assertions))]
Mock {
distance: Option<Length>,
velocity: Option<Speed>,
},
}
#[derive(Debug, Snafu)]
pub enum RangeSensorError {
#[snafu(display("Failed to access port: {port_error}"))]
PortError { port_error: PortError },
#[snafu(display("Failed to get object's distance : {distance_object_error}"))]
DistanceObjectError {
distance_object_error: DistanceObjectError,
},
#[snafu(display("Sensor did not detect a distance"))]
NoDistance,
#[snafu(display("Sensor did not detect a velocity"))]
NoVelocity,
}
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) -> Report<Length, RangeSensorError> {
match self {
RangeSensor::AdiDistanceSensor(sensor) => match sensor.lock().await.distance() {
Ok(dist) => match dist {
Some(d) => Report::new(Length::from_centimeters(d as f64)),
None => Report::from_parts(
Length::from_centimeters(0.0),
RangeSensorError::NoDistance,
),
},
Err(e) => Report::from_parts(
Length::from_centimeters(0.0),
RangeSensorError::PortError { port_error: e },
),
},
RangeSensor::SmartDistSensor(sensor) => {
let object = sensor.lock().await.object();
match object {
Ok(obj) => match obj {
Some(obj) => Report::new(Length::from_centimetres(obj.distance as f64)),
None => Report::from_parts(
Length::from_centimeters(0.0),
RangeSensorError::NoDistance,
),
},
Err(e) => Report::from_parts(
Length::from_centimeters(0.0),
RangeSensorError::DistanceObjectError {
distance_object_error: e,
},
),
}
}
RangeSensor::Mock {
distance,
velocity: _,
} => match distance {
Some(dist) => Report::new(dist.clone()),
None => {
Report::from_parts(Length::from_centimeters(0.0), RangeSensorError::NoDistance)
}
},
}
}
pub async fn velocity(&self) -> Report<Speed, RangeSensorError> {
match self {
Self::SmartDistSensor(sensor) => {
let obj = sensor.lock().await.object();
match obj {
Ok(Some(obj)) => Report::new(Speed::from_metres_per_second(obj.velocity)),
Ok(None) => Report::from_parts(
Speed::from_meters_per_second(0.0),
RangeSensorError::NoVelocity,
),
Err(e) => Report::from_parts(
Speed::from_meters_per_second(0.0),
RangeSensorError::DistanceObjectError {
distance_object_error: e,
},
),
}
}
Self::AdiDistanceSensor(_) => {
Report::from_parts(Speed::from_meters_per_second(0.0), RangeSensorError::NoVelocity)
}
Self::Mock {
distance: _,
velocity,
} => match velocity {
Some(vel) => Report::new(vel.clone()),
None => Report::from_parts(
Speed::from_meters_per_second(0.0),
RangeSensorError::NoVelocity,
),
},
}
}
}
pub struct KalmanRangeSensor {
sensor: RangeSensor,
process_var: f64,
measurement_var: f64,
last_update: Duration, prev_m: Length, prev_vel: Speed, prev_var: f64,
est_m: Length,
est_var: f64,
new_m: Length,
new_var: f64,
}
impl KalmanRangeSensor {
pub fn new(
sensor: RangeSensor,
process_var: f64,
measurement_var: f64,
initial_distance: Length,
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 {
Report::Ok(d) => d,
Report::Warn { value: d, error: e } => {
log::warn!("{}", e);
d
}
};
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 Report::Ok(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) -> Length { self.new_m }
pub fn variance(&self) -> f64 { self.new_var }
pub fn velocity(&self) -> Speed { self.prev_vel }
pub fn predicted_measurement(&self) -> Length { self.est_m }
pub fn predicted_variance(&self) -> f64 { self.est_var }
pub fn reset(&mut self, initial_distance: Length, 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: Length, 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: Length,
velocity: Speed,
dt: Duration,
}
#[vexide::test]
async fn test_kalman_filter_predict_update(_p: Peripherals) {
let test_data_clean = vec![
TestDataPoint {
distance: Length::from_metres(1.0),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Length::from_metres(1.1),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Length::from_metres(1.2),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Length::from_metres(1.3),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
TestDataPoint {
distance: Length::from_metres(1.4),
velocity: Speed::from_metres_per_second(1.0),
dt: Duration::from_millis(100),
},
];
let initial_dist = Length::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 {}: Length 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: Length::from_metres(2.0),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Length::from_metres(2.15),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Length::from_metres(2.05),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Length::from_metres(2.25),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
TestDataPoint {
distance: Length::from_metres(2.20),
velocity: Speed::from_metres_per_second(2.0),
dt: Duration::from_millis(50),
},
];
let initial_dist = Length::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 = Length::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 = Length::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);
}
}