use fusion_nav::prelude::*;
const IMU_HZ: u32 = 400;
const GNSS_HZ: u32 = 5;
const BARO_HZ: u32 = 20;
const MAG_HZ: u32 = 50;
fn main() -> Result<(), InitError> {
let mut filter = Eskf::default();
assert!(filter.set_magnetic_declination(Radians::from_radians(-0.06)));
println!("fusion-nav basic example\n");
let mut window = StaticWindow::new();
let mut samples = 0;
while !window.is_long_enough(filter.config()) {
if let Err(refusal) = window.push(stationary_sample(samples)) {
println!("window sample refused: {refusal}");
}
samples += 1;
}
let alignment = filter.initialize(&window)?;
println!("alignment {alignment:?}\n");
for tick in 0..(5 * IMU_HZ) {
let time = sample_time(samples as u32 + tick + 1);
assert!(filter.predict(imu_sample(time)).is_propagated());
if tick % (IMU_HZ / GNSS_HZ) == 0 {
let outcome = filter.fuse_gnss_geodetic(
time,
Geodetic::from_degrees(47.397_742, 8.545_594, 488.0),
PositionNoise::horizontal_vertical(1.5, 3.0),
Position::zero(),
);
for half in [outcome.horizontal, outcome.height] {
if let Some(test_ratio) = half.test_ratio() {
assert!(test_ratio <= 1.0, "gnss position rejected");
}
}
filter.fuse_gnss_velocity(
time,
Velocity::ned(14.0, 0.5, -0.2),
VelocityNoise::from_speed_accuracy(0.3),
Position::zero(),
);
}
if tick % (IMU_HZ / BARO_HZ) == 0 {
filter.fuse_baro_altitude(
time,
Altitude::from_meters(60.0),
AltitudeNoise::from_sigma(2.0),
);
}
if tick % (IMU_HZ / MAG_HZ) == 0 {
filter.fuse_mag_heading(
time,
MagField::body(0.21, 0.03, 0.44),
HeadingNoise::from_sigma(0.3),
);
}
}
let state = filter.state();
let (roll, pitch, yaw) = state.attitude.euler_angles();
println!("status {:?}", state.status);
println!("position {:?}", state.position);
if let (Some(origin), Some(here)) = (filter.origin(), filter.geodetic_position()) {
let origin = origin.geodetic();
println!(
"origin {:.6}, {:.6}; here {:.6}, {:.6}",
origin.latitude_deg(),
origin.longitude_deg(),
here.latitude_deg(),
here.longitude_deg()
);
}
println!("velocity {:?}", state.velocity);
println!("attitude roll {roll:.3} pitch {pitch:.3} yaw {yaw:.3} rad");
println!("biases {:?}", state.gyro_bias);
match state.status {
Status::Healthy => println!("\nevery source that has been fused is still accepted"),
Status::Aligning => println!("\nattitude has not converged yet; do not fly on it"),
Status::Degraded => {
println!("\na source has timed out; horizontal position is still aided")
}
Status::DeadReckoning => {
println!("\nno GNSS is holding horizontal position; drift is unbounded")
}
}
Ok(())
}
fn sample_time(n: u32) -> Timestamp {
Timestamp::from_micros(u64::from(n) * 1_000_000 / u64::from(IMU_HZ))
}
fn imu_sample(time: Timestamp) -> ImuSample {
ImuSample::from_rates(
time,
AngularRate::body(0.01, -0.002, 0.03),
Acceleration::body(0.2, 0.1, -GRAVITY),
Seconds::from_secs(1.0 / IMU_HZ as f32),
)
}
fn stationary_sample(i: usize) -> StaticSample {
StaticSample {
imu: ImuSample::from_rates(
sample_time(i as u32 + 1),
AngularRate::body(0.0, 0.0, 0.0),
Acceleration::body(0.0, 0.0, -GRAVITY),
Seconds::from_secs(1.0 / IMU_HZ as f32),
),
mag: Some(MagField::body(0.22, 0.0, 0.44)),
baro: Some(Altitude::from_meters(if i.is_multiple_of(2) {
52.25
} else {
51.75
})),
..StaticSample::default()
}
}