#[cfg(not(test))]
use micromath::F32Ext;
use crate::{
altimeter::Altimeter,
ublox_device::{Device, UbloxDriver},
utils::WheelBuffer,
};
use embedded_hal::blocking::delay::DelayMs;
use fugit::{ExtU64, MillisDuration, MillisDurationU32};
const BUFFER_LENGTH: usize = 5;
#[derive(Debug, Clone, Copy)]
pub struct BaroData {
pub agl: f32,
pub pressure: f32,
pub timestamp: MillisDuration<u64>,
}
impl core::default::Default for BaroData {
fn default() -> Self {
Self {
timestamp: 0.millis(),
..BaroData::default()
}
}
}
impl core::ops::Sub<BaroData> for BaroData {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
BaroData {
agl: self.agl - rhs.agl,
pressure: self.pressure - rhs.pressure,
timestamp: self.timestamp - rhs.timestamp,
}
}
}
pub struct BaroComputer<A: Altimeter> {
altimeter: A,
buf: WheelBuffer<BaroData, BUFFER_LENGTH>,
current_phase: FlightPhase,
num_pre_checks: usize,
ground_ref: BaroData,
latest_measurement: BaroData,
}
impl<A: Altimeter> BaroComputer<A> {
#[inline]
pub fn new(altimeter: A, current_phase: FlightPhase, fill_buf: BaroData) -> Self {
Self {
altimeter,
current_phase,
buf: WheelBuffer::new(fill_buf),
num_pre_checks: 0,
ground_ref: fill_buf,
latest_measurement: fill_buf,
}
}
#[inline]
pub fn current_phase(&self) -> FlightPhase {
self.current_phase
}
#[inline]
pub fn ground_ref(&self) -> BaroData {
self.ground_ref
}
#[inline]
pub fn measure<D: DelayMs<u8>>(&mut self, delay: &mut D, timestamp: MillisDuration<u64>) {
self.latest_measurement = self.altimeter.measure(delay, self.ground_ref(), timestamp);
}
#[inline]
pub fn buf(&self) -> &WheelBuffer<BaroData, BUFFER_LENGTH> {
&self.buf
}
#[inline]
pub fn last_measurement(&self) -> BaroData {
self.latest_measurement
}
#[inline]
pub fn acquire_signal<D: UbloxDriver, const L: usize>(&mut self, gps: &mut Device<D, L>) {
gps.acquire_signal();
if self.current_phase == FlightPhase::Startup {
self.current_phase = FlightPhase::Ground;
gps.gnss_power(false);
}
}
#[inline]
pub fn check_phase_switch<F: FnMut(FlightPhase, BaroData, &mut A)>(
&mut self,
mut on_switch_phase: F,
) {
self.push_latest();
let old_phase = self.current_phase();
match old_phase {
FlightPhase::Startup | FlightPhase::Ground => {
if self.detect_climb() {
self.current_phase = FlightPhase::Climb;
}
}
FlightPhase::Climb => {
if self.detect_freefall() {
self.current_phase = FlightPhase::FlightLogging;
}
}
FlightPhase::FlightLogging => {
if self.detect_landing() {
self.current_phase = FlightPhase::Ground;
}
}
}
let new_phase = self.current_phase();
if old_phase != new_phase {
on_switch_phase(new_phase, self.buf.latest(), &mut self.altimeter);
}
}
#[inline]
fn push_latest(&mut self) {
self.buf.push(self.last_measurement());
}
#[inline]
fn diff(&self) -> BaroData {
self.buf.peek(0).unwrap() - self.buf.peek(1).unwrap()
}
#[inline]
fn detect_climb(&mut self) -> bool {
const REQD_PRE_CHECKS: usize = 2;
const TRIGGER_CLIMB_RATES: [f32; REQD_PRE_CHECKS + 1] =
[0.25 / 1000.0, 0.75 / 1000.0, 1.0 / 1000.0];
static_assertions::const_assert!(REQD_PRE_CHECKS < BUFFER_LENGTH);
let mut pre_trigger = false;
let climb_detected = self.detect_phase(REQD_PRE_CHECKS, |diff, num_pre_checks| {
pre_trigger =
diff.agl >= TRIGGER_CLIMB_RATES[num_pre_checks] * diff.timestamp.ticks() as f32;
pre_trigger
});
if !pre_trigger {
self.set_ground_ref(self.buf().peek(4).unwrap());
}
climb_detected
}
#[inline]
fn detect_landing(&mut self) -> bool {
const LANDING_RATE_MAX: f32 = 3.0 / 1000.0;
const REQD_PRE_CHECKS: usize = 19;
self.detect_phase(REQD_PRE_CHECKS, |diff, _| {
diff.agl.abs() <= LANDING_RATE_MAX * diff.timestamp.ticks() as f32
})
}
#[inline]
fn detect_freefall(&mut self) -> bool {
const REQD_PRE_CHECKS: usize = 3;
const TRIGGER_FREEFALL_RATES: [f32; REQD_PRE_CHECKS + 1] =
[-3.0 / 1000.0, -5.0 / 1000.0, -8.0 / 1000.0, -10.0 / 1000.0];
self.detect_phase(REQD_PRE_CHECKS, |diff, num_pre_checks| {
diff.agl <= TRIGGER_FREEFALL_RATES[num_pre_checks] * diff.timestamp.ticks() as f32
})
}
#[inline]
fn detect_phase<F>(&mut self, reqd_pre_checks: usize, mut func: F) -> bool
where
F: FnMut(BaroData, usize) -> bool,
{
let diff = self.diff();
if func(diff, self.num_pre_checks) {
if self.num_pre_checks >= reqd_pre_checks {
self.num_pre_checks = 0;
return true;
}
self.num_pre_checks += 1;
} else {
self.num_pre_checks = 0;
}
false
}
#[inline]
fn set_ground_ref(&mut self, ground_ref: BaroData) {
self.ground_ref = ground_ref;
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FlightPhase {
Startup,
FlightLogging,
Ground,
Climb,
}
impl FlightPhase {
#[inline]
pub const fn phase_check_wait_time(&self) -> MillisDuration<u32> {
match self {
FlightPhase::Startup | FlightPhase::Ground => MillisDurationU32::from_ticks(10_000),
FlightPhase::FlightLogging | FlightPhase::Climb => MillisDurationU32::from_ticks(1000),
}
}
pub const FLIGHT_LOGGING_MEASUREMENT_INTERVAL: MillisDuration<u32> =
MillisDurationU32::from_ticks(50);
}