#![cfg_attr(target_os = "none", no_std, no_main)]
#![cfg_attr(not(target_os = "none"), allow(dead_code))]
use fusion_nav::prelude::*;
const IMU_HZ: u32 = 400;
const REPORT_EVERY: u32 = IMU_HZ;
#[cfg(not(feature = "defmt"))]
macro_rules! log {
($board:expr, $($arg:tt)*) => {{
let _ = writeln!($board, $($arg)*);
}};
}
#[cfg(feature = "defmt")]
macro_rules! log {
($board:expr, $($arg:tt)*) => {{
let _ = &$board;
defmt::info!($($arg)*)
}};
}
struct Fix {
time: Timestamp,
position: Geodetic,
position_noise: PositionNoise<Ned>,
velocity: Velocity<Ned>,
velocity_noise: VelocityNoise<Ned>,
}
trait Board: core::fmt::Write {
fn imu(&mut self) -> Option<ImuSample>;
fn gnss(&mut self) -> Option<Fix>;
fn baro(&mut self) -> Option<(Timestamp, Altitude)>;
fn mag(&mut self) -> Option<(Timestamp, MagField<Body>)>;
fn state_stepped(&mut self);
fn failsafe(&mut self);
}
fn run(board: &mut impl Board) -> ! {
let mut filter = Eskf::default();
align(&mut filter, board);
let mut ticks: u32 = 0;
loop {
let Some(imu) = board.imu() else {
continue;
};
match filter.predict(imu) {
Propagation::Propagated => {}
coasted @ Propagation::Coasted { .. } => log!(board, "imu {}", coasted),
refused @ (Propagation::StepTooLong { .. }
| Propagation::InvalidStep { .. }
| Propagation::NotFinite
| Propagation::InvalidInterval { .. }
| Propagation::StateNotFinite) => log!(board, "imu {}", refused),
Propagation::NotInitialized => align(&mut filter, board),
}
let mut stepped = false;
if let Some(fix) = board.gnss() {
let position = filter.fuse_gnss_geodetic(
fix.time,
fix.position,
fix.position_noise,
Position::zero(),
);
stepped |= report(board, "gnss position", position.horizontal);
stepped |= report(board, "gnss height", position.height);
let velocity = filter.fuse_gnss_velocity(
fix.time,
fix.velocity,
fix.velocity_noise,
Position::zero(),
);
stepped |= report(board, "gnss velocity", velocity);
}
if let Some((time, altitude)) = board.baro() {
let outcome = filter.fuse_baro_altitude(time, altitude, AltitudeNoise::from_sigma(2.0));
stepped |= report(board, "baro", outcome);
}
if let Some((time, field)) = board.mag() {
let outcome = filter.fuse_mag_heading(time, field, HeadingNoise::from_sigma(0.05));
stepped |= report(board, "mag heading", outcome);
}
if stepped {
board.state_stepped();
}
ticks = ticks.wrapping_add(1);
if ticks.is_multiple_of(REPORT_EVERY) {
supervise(&filter, board);
}
}
}
fn align(filter: &mut Eskf, board: &mut impl Board) {
let mut window = StaticWindow::new();
let (mut baro, mut mag) = (None, None);
while !window.is_long_enough(filter.config()) {
baro = board.baro().map(|(_, altitude)| altitude).or(baro);
mag = board.mag().map(|(_, field)| field).or(mag);
let Some(imu) = board.imu() else {
continue;
};
let sample = StaticSample {
imu,
baro,
mag,
velocity: None,
};
if let Err(error) = window.push(sample) {
log!(board, "window sample refused: {}", error);
}
}
match filter.initialize(&window) {
Ok(alignment) => log!(board, "aligned {}", alignment),
Err(error) => log!(board, "initialization failed: {}", error),
}
}
fn report(board: &mut impl Board, source: &str, outcome: Fusion) -> bool {
match outcome {
Fusion::Accepted { .. } | Fusion::Rejected { .. } => {}
Fusion::Reset => log!(board, "{} {}", source, outcome),
Fusion::NotInitialized | Fusion::NoReference | Fusion::Unobservable => {}
Fusion::NotFinite
| Fusion::InvalidNoise
| Fusion::StateInvalid
| Fusion::OutOfHorizon { .. } => {
log!(board, "{} {}", source, outcome)
}
}
outcome.is_reset()
}
fn supervise(filter: &Eskf, board: &mut impl Board) {
let state = filter.state();
log!(board, "status {} valid {}", state.status, state.validity);
match state.status {
Status::Healthy | Status::Degraded | Status::Aligning => {}
Status::DeadReckoning => {
if !state.validity.horizontal_position {
board.failsafe();
}
}
}
}
struct Hal;
impl core::fmt::Write for Hal {
fn write_str(&mut self, _: &str) -> core::fmt::Result {
Ok(())
}
}
impl Board for Hal {
fn imu(&mut self) -> Option<ImuSample> {
None
}
fn gnss(&mut self) -> Option<Fix> {
None
}
fn baro(&mut self) -> Option<(Timestamp, Altitude)> {
None
}
fn mag(&mut self) -> Option<(Timestamp, MagField<Body>)> {
None
}
fn state_stepped(&mut self) {}
fn failsafe(&mut self) {}
}
#[cfg(feature = "defmt")]
mod logger {
#[defmt::global_logger]
struct Discard;
unsafe impl defmt::Logger for Discard {
fn acquire() {}
unsafe fn flush() {}
unsafe fn release() {}
unsafe fn write(_: &[u8]) {}
}
}
#[cfg(target_os = "none")]
#[panic_handler]
fn panicked(_: &core::panic::PanicInfo) -> ! {
loop {}
}
#[cfg(target_os = "none")]
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
run(&mut Hal)
}
#[cfg(not(target_os = "none"))]
fn main() {}