use crate::config::Config;
use crate::eskf::Eskf;
use crate::frames::{Body, Ned};
use crate::geodetic::Geodetic;
use crate::health::Propagation;
use crate::init::tests::{gravity_at, spaced, still, turning};
use crate::init::{Alignment, InitError, StaticSample, StaticWindow};
use crate::propagate::ImuSample;
use crate::state::{Covariance, ErrorState, STATES, State};
use crate::units::{
Altitude, AltitudeNoise, AngularRate, Attitude, MagField, Position, PositionNoise, Seconds,
Timestamp, Velocity, VelocityNoise,
};
pub(super) const DT: Seconds = Seconds::from_secs(0.01);
pub(super) trait Clocked {
fn step(&mut self, imu: ImuSample, dt: Seconds) -> Propagation;
fn initialize_over(
&mut self,
window: &[StaticSample],
dt: Seconds,
) -> Result<Alignment, InitError>;
fn seed(&mut self, state: State, covariance: Covariance) -> Result<Alignment, InitError>;
fn now(&self) -> Timestamp;
}
impl Clocked for Eskf {
fn step(&mut self, imu: ImuSample, dt: Seconds) -> Propagation {
let time = self.time().unwrap_or_default().after(dt);
self.predict(imu.timed(time, dt))
}
fn initialize_over(
&mut self,
window: &[StaticSample],
dt: Seconds,
) -> Result<Alignment, InitError> {
self.initialize(&StaticWindow::try_from(spaced(window, dt).as_slice())?)
}
fn seed(&mut self, state: State, covariance: Covariance) -> Result<Alignment, InitError> {
self.initialize_from(state, covariance, Timestamp::ZERO)
}
fn now(&self) -> Timestamp {
self.time().unwrap_or_default()
}
}
pub(super) fn initialized() -> Eskf {
let mut filter = Eskf::default();
let alignment = filter
.initialize_over(&[still(); 8], Seconds::from_secs(0.25))
.expect("a 2 s window of stillness");
assert_eq!(alignment, Alignment::Static);
filter
}
pub(super) fn unheld() -> Eskf {
let mut filter = Eskf::new(Config {
hold: None,
..Config::default()
})
.unwrap();
let alignment = filter
.initialize_over(&[still(); 8], Seconds::from_secs(0.25))
.expect("a 2 s window of stillness");
assert_eq!(alignment, Alignment::Static);
filter
}
pub(super) fn window_at(altitude: f32) -> [StaticSample; 8] {
window_with_baro([-0.5, 0.5, 0.0, -0.25, 0.25, 0.0, -0.125, 0.125].map(|d| altitude + d))
}
pub(super) fn window_with_baro(altitudes: [f32; 8]) -> [StaticSample; 8] {
altitudes.map(|altitude| StaticSample {
baro: Some(Altitude::from_meters(altitude)),
..still()
})
}
pub(super) fn window_with_mag() -> [StaticSample; 8] {
[StaticSample {
mag: Some(MagField::body(0.22, 0.0, 0.44)),
..still()
}; 8]
}
pub(super) fn aided() -> Eskf {
aided_with(Config::default())
}
pub(super) fn aided_with(config: Config) -> Eskf {
let mut filter = Eskf::new(config).unwrap();
let _ = filter
.initialize_over(&window_at(100.0), Seconds::from_secs(0.25))
.expect("a 2 s window of stillness");
assert!(
filter
.fuse_baro_altitude(
filter.now(),
Altitude::from_meters(100.0),
AltitudeNoise::from_sigma(2.0)
)
.is_accepted()
);
filter
}
pub(super) fn flying(config: Config) -> Eskf {
let mut filter = aided_with(config);
assert!(filter.reset_position_to(
Position::ned(0.0, 0.0, -100.0),
PositionNoise::horizontal_vertical(0.5, 0.5)
));
assert!(filter.reset_velocity_to(
Velocity::ned(20.0, 0.0, 0.0),
VelocityNoise::from_speed_accuracy(0.1)
));
filter
}
pub(super) const SITE_GRAVITY: f32 = 9.79;
pub(super) fn at_site() -> Config {
Config {
gravity: SITE_GRAVITY,
..Config::default()
}
}
pub(super) fn moving_window_at(altitude: f32) -> [StaticSample; 8] {
let mut window = window_at(altitude);
window[3].imu = window[3].imu.with_gyro(AngularRate::body(0.0, 0.4, 0.0));
window
}
pub(super) fn seed() -> (State, Covariance) {
let state = State {
velocity: Velocity::ned(18.0, 0.0, 0.0),
gyro_bias: AngularRate::body(0.001, -0.002, 0.0005),
..State::default()
};
let mut sigmas = [0.5f32; STATES];
sigmas[ErrorState::AttitudeZ.index()] = 1.0; (state, Covariance::from_sigmas(sigmas))
}
pub(super) fn window_tilted(roll: f32, pitch: f32) -> [StaticSample; 8] {
[StaticSample {
imu: still().imu.with_accel(gravity_at(roll, pitch, 0.0)),
..still()
}; 8]
}
pub(super) fn seeded(attitude: Attitude, bias: AngularRate<Body>) -> Eskf {
let mut filter = Eskf::default();
let (state, covariance) = seed();
let state = State {
attitude,
gyro_bias: bias,
..state
};
let _ = filter.seed(state, covariance).expect("a sane seed");
filter
}
pub(super) fn coarse() -> Eskf {
let mut filter = Eskf::default();
let mut window = turning(0.0, 0.4, 0.0);
for sample in &mut window {
sample.mag = None;
}
let alignment = filter
.initialize_over(&window, Seconds::from_secs(0.25))
.expect("moving, not unusable");
assert!(matches!(alignment, Alignment::Coarse(..)));
filter
}
pub(super) fn zurich() -> Geodetic {
Geodetic::from_degrees(47.3977, 8.5456, 488.0)
}
pub(super) fn near(a: Position<Ned>, b: Position<Ned>) -> bool {
(a.vector() - b.vector()).norm() < 1e-2
}
pub(super) fn mast() -> Position<Body> {
Position::body(1.0, 0.0, -0.5)
}
pub(super) fn on_its_tail() -> Eskf {
let mut filter = Eskf::default();
let alignment = filter
.initialize_over(
&window_tilted(0.0, core::f32::consts::FRAC_PI_2),
Seconds::from_secs(0.25),
)
.expect("a parked vehicle reads exactly g, however it is standing");
assert_eq!(alignment, Alignment::Static);
filter
}
pub(super) fn hold(
filter: &mut Eskf,
seconds: f32,
every: usize,
mut offer: impl FnMut(&mut Eskf),
) {
let steps = (seconds / DT.as_secs()).round() as usize;
for step in 1..=steps {
assert_eq!(filter.step(still().imu, DT), Propagation::Propagated);
if step % every == 0 {
offer(filter);
}
}
}
pub(super) fn one_metre() -> PositionNoise<Ned> {
PositionNoise::from_sigma(1.0, 1.0, 1.0)
}
pub(super) fn cruising(velocity: Velocity<Ned>) -> Eskf {
let mut filter = initialized();
assert!(filter.reset_velocity_to(velocity, VelocityNoise::from_speed_accuracy(0.3)));
hold_velocity(&mut filter, velocity);
filter
}
pub(super) fn hold_velocity(filter: &mut Eskf, velocity: Velocity<Ned>) {
let noise = VelocityNoise::from_speed_accuracy(0.3);
let outcome = filter.fuse_gnss_velocity(filter.now(), velocity, noise, Position::zero());
assert!(outcome.is_accepted(), "{outcome:?}");
}