flight_computer/
flight_phase.rs1#[cfg(not(test))]
7use micromath::F32Ext;
8
9use crate::{
10 altimeter::Altimeter,
11 ublox_device::{Device, UbloxDriver},
12 utils::WheelBuffer,
13};
14use embedded_hal::blocking::delay::DelayMs;
15use fugit::{ExtU64, MillisDuration, MillisDurationU32};
16
17const BUFFER_LENGTH: usize = 5;
18#[derive(Debug, Clone, Copy)]
21pub struct BaroData {
22 pub agl: f32,
24 pub pressure: f32,
26 pub timestamp: MillisDuration<u64>,
28}
29
30impl core::default::Default for BaroData {
31 fn default() -> Self {
32 Self {
33 timestamp: 0.millis(),
34 ..BaroData::default()
35 }
36 }
37}
38
39impl core::ops::Sub<BaroData> for BaroData {
40 type Output = Self;
41
42 #[inline]
43 fn sub(self, rhs: Self) -> Self::Output {
44 BaroData {
45 agl: self.agl - rhs.agl,
46 pressure: self.pressure - rhs.pressure,
47 timestamp: self.timestamp - rhs.timestamp,
48 }
49 }
50}
51
52pub struct BaroComputer<A: Altimeter> {
54 altimeter: A,
56 buf: WheelBuffer<BaroData, BUFFER_LENGTH>,
58 current_phase: FlightPhase,
60 num_pre_checks: usize,
62 ground_ref: BaroData,
64 latest_measurement: BaroData,
67}
68
69impl<A: Altimeter> BaroComputer<A> {
70 #[inline]
72 pub fn new(altimeter: A, current_phase: FlightPhase, fill_buf: BaroData) -> Self {
73 Self {
74 altimeter,
75 current_phase,
76 buf: WheelBuffer::new(fill_buf),
77 num_pre_checks: 0,
78 ground_ref: fill_buf,
79 latest_measurement: fill_buf,
80 }
81 }
82
83 #[inline]
85 pub fn current_phase(&self) -> FlightPhase {
86 self.current_phase
87 }
88
89 #[inline]
91 pub fn ground_ref(&self) -> BaroData {
92 self.ground_ref
93 }
94
95 #[inline]
98 pub fn measure<D: DelayMs<u8>>(&mut self, delay: &mut D, timestamp: MillisDuration<u64>) {
99 self.latest_measurement = self.altimeter.measure(delay, self.ground_ref(), timestamp);
100 }
101
102 #[inline]
104 pub fn buf(&self) -> &WheelBuffer<BaroData, BUFFER_LENGTH> {
105 &self.buf
106 }
107
108 #[inline]
110 pub fn last_measurement(&self) -> BaroData {
111 self.latest_measurement
112 }
113
114 #[inline]
117 pub fn acquire_signal<D: UbloxDriver, const L: usize>(&mut self, gps: &mut Device<D, L>) {
118 gps.acquire_signal();
119
120 if self.current_phase == FlightPhase::Startup {
121 self.current_phase = FlightPhase::Ground;
122 gps.gnss_power(false);
123 }
124 }
125
126 #[inline]
131 pub fn check_phase_switch<F: FnMut(FlightPhase, BaroData, &mut A)>(
132 &mut self,
133 mut on_switch_phase: F,
134 ) {
135 self.push_latest();
136
137 let old_phase = self.current_phase();
138
139 match old_phase {
140 FlightPhase::Startup | FlightPhase::Ground => {
141 if self.detect_climb() {
142 self.current_phase = FlightPhase::Climb;
143 }
144 }
145
146 FlightPhase::Climb => {
147 if self.detect_freefall() {
148 self.current_phase = FlightPhase::FlightLogging;
149 }
150 }
151
152 FlightPhase::FlightLogging => {
153 if self.detect_landing() {
154 self.current_phase = FlightPhase::Ground;
155 }
156 }
157 }
158
159 let new_phase = self.current_phase();
160
161 if old_phase != new_phase {
162 on_switch_phase(new_phase, self.buf.latest(), &mut self.altimeter);
163 }
164 }
165
166 #[inline]
168 fn push_latest(&mut self) {
169 self.buf.push(self.last_measurement());
170 }
171
172 #[inline]
174 fn diff(&self) -> BaroData {
175 self.buf.peek(0).unwrap() - self.buf.peek(1).unwrap()
176 }
177
178 #[inline]
180 fn detect_climb(&mut self) -> bool {
181 const REQD_PRE_CHECKS: usize = 2;
182 const TRIGGER_CLIMB_RATES: [f32; REQD_PRE_CHECKS + 1] =
185 [0.25 / 1000.0, 0.75 / 1000.0, 1.0 / 1000.0];
186 static_assertions::const_assert!(REQD_PRE_CHECKS < BUFFER_LENGTH);
187
188 let mut pre_trigger = false;
189
190 let climb_detected = self.detect_phase(REQD_PRE_CHECKS, |diff, num_pre_checks| {
191 pre_trigger =
192 diff.agl >= TRIGGER_CLIMB_RATES[num_pre_checks] * diff.timestamp.ticks() as f32;
193
194 pre_trigger
195 });
196
197 if !pre_trigger {
204 self.set_ground_ref(self.buf().peek(4).unwrap());
205 }
206
207 climb_detected
208 }
209
210 #[inline]
212 fn detect_landing(&mut self) -> bool {
213 const LANDING_RATE_MAX: f32 = 3.0 / 1000.0;
216 const REQD_PRE_CHECKS: usize = 19;
217
218 self.detect_phase(REQD_PRE_CHECKS, |diff, _| {
219 diff.agl.abs() <= LANDING_RATE_MAX * diff.timestamp.ticks() as f32
220 })
221 }
222
223 #[inline]
225 fn detect_freefall(&mut self) -> bool {
226 const REQD_PRE_CHECKS: usize = 3;
227 const TRIGGER_FREEFALL_RATES: [f32; REQD_PRE_CHECKS + 1] =
230 [-3.0 / 1000.0, -5.0 / 1000.0, -8.0 / 1000.0, -10.0 / 1000.0];
231
232 self.detect_phase(REQD_PRE_CHECKS, |diff, num_pre_checks| {
233 diff.agl <= TRIGGER_FREEFALL_RATES[num_pre_checks] * diff.timestamp.ticks() as f32
234 })
235 }
236
237 #[inline]
243 fn detect_phase<F>(&mut self, reqd_pre_checks: usize, mut func: F) -> bool
244 where
245 F: FnMut(BaroData, usize) -> bool,
246 {
247 let diff = self.diff();
250
251 if func(diff, self.num_pre_checks) {
253 if self.num_pre_checks >= reqd_pre_checks {
255 self.num_pre_checks = 0;
256 return true;
257 }
259
260 self.num_pre_checks += 1;
261
262 } else {
264 self.num_pre_checks = 0;
265 }
266
267 false
268 }
269
270 #[inline]
272 fn set_ground_ref(&mut self, ground_ref: BaroData) {
273 self.ground_ref = ground_ref;
274 }
275}
276
277#[derive(Clone, Copy, Debug, PartialEq)]
279pub enum FlightPhase {
280 Startup,
282 FlightLogging,
284 Ground,
286 Climb,
288}
289
290impl FlightPhase {
291 #[inline]
293 pub const fn phase_check_wait_time(&self) -> MillisDuration<u32> {
294 match self {
295 FlightPhase::Startup | FlightPhase::Ground => MillisDurationU32::from_ticks(10_000),
296 FlightPhase::FlightLogging | FlightPhase::Climb => MillisDurationU32::from_ticks(1000),
297 }
298 }
299
300 pub const FLIGHT_LOGGING_MEASUREMENT_INTERVAL: MillisDuration<u32> =
301 MillisDurationU32::from_ticks(50);
302}