1use crate::errors;
2use crate::math::FlightFloat;
3use crate::packets::*;
4use crate::params::{ParamId, ParamValue, Params};
5use crate::{log_error, log_info};
6use bitflags::bitflags;
7
8fn deg_to_rad<R: FlightFloat>() -> R {
9 <R as FlightFloat>::from_f32(0.017453293)
10}
11
12bitflags! {
13 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14 pub struct CalibrationFlags: u16 {
15 const GYRO = 1 << 0;
16 const ACCEL = 1 << 1;
17 const BARO = 1 << 2;
18 const PITOT = 1 << 3;
19 const GYRO_FAILED = 1 << 4;
20 const ACCEL_FAILED = 1 << 5;
21 const BARO_FAILED = 1 << 6;
22 const PITOT_FAILED = 1 << 7;
23
24 const IMU = Self::GYRO.bits() | Self::ACCEL.bits();
26 }
27}
28
29pub trait SensorPacketProcessor<P> {
30 fn process(
31 &mut self,
32 packet: &mut Option<Result<P, errors::SensorError>>,
33 flags: &mut CalibrationFlags,
34 params: &mut Params,
35 ) -> Option<P>;
36}
37
38fn take_ok_packet<P>(packet: &mut Option<Result<P, errors::SensorError>>) -> Option<P> {
39 match packet.take() {
40 Some(Ok(packet)) => Some(packet),
41 _ => None,
42 }
43}
44
45macro_rules! impl_passthrough_sensor_packet_processor {
46 ($processor:ty, $packet:ty) => {
47 impl SensorPacketProcessor<$packet> for $processor {
48 fn process(
49 &mut self,
50 packet: &mut Option<Result<$packet, errors::SensorError>>,
51 _flags: &mut CalibrationFlags,
52 _params: &mut Params,
53 ) -> Option<$packet> {
54 take_ok_packet(packet)
55 }
56 }
57 };
58}
59
60#[derive(Default, Copy, Clone)]
65pub struct PassthroughBatteryProcessor;
66impl_passthrough_sensor_packet_processor!(PassthroughBatteryProcessor, BatteryPacket);
67
68#[derive(Copy, Clone)]
69pub struct BatteryProcessor {
70 previous_voltage: f32,
71 previous_current: f32,
72 latest_packet: Option<BatteryPacket>,
73 initialized: bool,
74}
75
76impl Default for BatteryProcessor {
77 fn default() -> Self {
78 Self {
79 previous_voltage: 0.0,
80 previous_current: 0.0,
81 latest_packet: None,
82 initialized: false,
83 }
84 }
85}
86
87impl BatteryProcessor {
88 fn process_packet(
89 &mut self,
90 packet: &mut Option<Result<BatteryPacket, errors::SensorError>>,
91 _flags: &mut CalibrationFlags,
92 params: &mut Params,
93 ) -> Option<BatteryPacket> {
94 let Some(mut packet) = take_ok_packet(packet) else {
95 return self.latest_packet;
96 };
97
98 if !self.initialized {
99 self.previous_voltage = param_float(params, ParamId::PARAM_VOLT_MAX);
100 self.previous_current = 0.0;
101 self.initialized = true;
102 }
103
104 let voltage_alpha = param_float(params, ParamId::PARAM_BATTERY_VOLTAGE_ALPHA);
105 let current_alpha = param_float(params, ParamId::PARAM_BATTERY_CURRENT_ALPHA);
106
107 packet.voltage =
108 packet.voltage * (1.0 - voltage_alpha) + self.previous_voltage * voltage_alpha;
109 packet.current =
110 packet.current * (1.0 - current_alpha) + self.previous_current * current_alpha;
111
112 self.previous_voltage = packet.voltage;
113 self.previous_current = packet.current;
114 self.latest_packet = Some(packet);
115
116 Some(packet)
117 }
118}
119
120impl SensorPacketProcessor<BatteryPacket> for BatteryProcessor {
121 fn process(
122 &mut self,
123 packet: &mut Option<Result<BatteryPacket, errors::SensorError>>,
124 flags: &mut CalibrationFlags,
125 params: &mut Params,
126 ) -> Option<BatteryPacket> {
127 self.process_packet(packet, flags, params)
128 }
129}
130
131#[derive(Default, Copy, Clone)]
136pub struct PassthroughImuProcessor;
137
138impl<R: FlightFloat> SensorPacketProcessor<ImuPacket<R>> for PassthroughImuProcessor {
139 fn process(
140 &mut self,
141 packet: &mut Option<Result<ImuPacket<R>, errors::SensorError>>,
142 _flags: &mut CalibrationFlags,
143 _params: &mut Params,
144 ) -> Option<ImuPacket<R>> {
145 take_ok_packet(packet)
146 }
147}
148
149#[derive(Default, Copy, Clone)]
150pub struct ImuCalibrationState<R: FlightFloat> {
151 gyro_sum: [R; 3],
152 gyro_calibration_count: u16,
153 accel_sum: [R; 3],
154 accel_temp_sum: R,
155 accel_calibration_count: u16,
156 max_accel: [R; 3],
157 min_accel: [R; 3],
158}
159
160#[derive(Copy, Clone)]
161pub struct ImuProcessor<R: FlightFloat> {
162 calibration_state: ImuCalibrationState<R>,
163 mount_rotation: ImuMountRotation<R>,
164}
165
166impl<R: FlightFloat> Default for ImuProcessor<R> {
167 fn default() -> Self {
168 ImuProcessor::new()
169 }
170}
171
172impl<R: FlightFloat> ImuProcessor<R> {
173 pub fn new() -> Self {
174 Self {
175 calibration_state: ImuCalibrationState {
176 max_accel: [<R as FlightFloat>::from_f32(-1000.0); 3],
177 min_accel: [<R as FlightFloat>::from_f32(1000.0); 3],
178 ..Default::default()
179 },
180 mount_rotation: ImuMountRotation::default(),
181 }
182 }
183
184 fn process_packet(
185 &mut self,
186 packet: &mut Option<Result<ImuPacket<R>, errors::SensorError>>,
187 flags: &mut CalibrationFlags,
188 params: &mut Params,
189 ) -> Option<ImuPacket<R>> {
190 if let Some(Ok(mut packet)) = packet.take() {
191 self.mount_rotation.rotate_imu_in_place(&mut packet, params);
192 let is_calibrating = flags.intersects(CalibrationFlags::IMU);
193
194 if is_calibrating {
195 if flags.contains(CalibrationFlags::GYRO) {
196 self.calibration_state.gyro_sum[0] += packet.gyro[0];
197 self.calibration_state.gyro_sum[1] += packet.gyro[1];
198 self.calibration_state.gyro_sum[2] += packet.gyro[2];
199 self.calibration_state.gyro_calibration_count += 1;
200 if self.calibration_state.gyro_calibration_count > 1000 {
201 let count = <R as FlightFloat>::from_u64(
202 self.calibration_state.gyro_calibration_count as u64,
203 );
204 let bias_x = self.calibration_state.gyro_sum[0] / count;
205 let bias_y = self.calibration_state.gyro_sum[1] / count;
206 let bias_z = self.calibration_state.gyro_sum[2] / count;
207
208 if vector_norm([bias_x, bias_y, bias_z]) < <R as FlightFloat>::from_f32(1.0)
209 {
210 params.set_by_id(
211 ParamId::PARAM_GYRO_X_BIAS,
212 ParamValue::Float(bias_x.to_f32_lossy()),
213 );
214 params.set_by_id(
215 ParamId::PARAM_GYRO_Y_BIAS,
216 ParamValue::Float(bias_y.to_f32_lossy()),
217 );
218 params.set_by_id(
219 ParamId::PARAM_GYRO_Z_BIAS,
220 ParamValue::Float(bias_z.to_f32_lossy()),
221 );
222 log_info!("Gyro Calibration complete!");
223 } else {
224 flags.insert(CalibrationFlags::GYRO_FAILED);
225 log_error!("Gyro calibration failed");
226 }
227
228 self.calibration_state.gyro_sum = [<R as FlightFloat>::from_f32(0.0); 3];
229 self.calibration_state.gyro_calibration_count = 0;
230 flags.remove(CalibrationFlags::GYRO);
231 }
232 }
233
234 if flags.contains(CalibrationFlags::ACCEL) {
235 let gravity = <R as FlightFloat>::from_f32(9.80665);
236 self.calibration_state.accel_sum[0] += packet.accel[0];
237 self.calibration_state.accel_sum[1] += packet.accel[1];
238 self.calibration_state.accel_sum[2] += packet.accel[2] + gravity;
239 self.calibration_state.accel_temp_sum +=
240 <R as FlightFloat>::from_f32(packet.temperature);
241 self.calibration_state.accel_calibration_count += 1;
242
243 self.calibration_state.max_accel[0] =
244 self.calibration_state.max_accel[0].max(packet.accel[0]);
245 self.calibration_state.min_accel[0] =
246 self.calibration_state.min_accel[0].min(packet.accel[0]);
247 self.calibration_state.max_accel[1] =
248 self.calibration_state.max_accel[1].max(packet.accel[1]);
249 self.calibration_state.min_accel[1] =
250 self.calibration_state.min_accel[1].min(packet.accel[1]);
251 self.calibration_state.max_accel[2] =
252 self.calibration_state.max_accel[2].max(packet.accel[2]);
253 self.calibration_state.min_accel[2] =
254 self.calibration_state.min_accel[2].min(packet.accel[2]);
255 if self.calibration_state.accel_calibration_count > 1000 {
256 let accel_delta_x = self.calibration_state.max_accel[0]
257 - self.calibration_state.min_accel[0];
258 let accel_delta_y = self.calibration_state.max_accel[1]
259 - self.calibration_state.min_accel[1];
260 let accel_delta_z = self.calibration_state.max_accel[2]
261 - self.calibration_state.min_accel[2];
262 let max_delta = (accel_delta_x * accel_delta_x
263 + accel_delta_y * accel_delta_y
264 + accel_delta_z * accel_delta_z)
265 .sqrt();
266
267 if max_delta < <R as FlightFloat>::from_f32(1.0) {
268 let count = <R as FlightFloat>::from_u64(
269 self.calibration_state.accel_calibration_count as u64,
270 );
271 let temp_comp_x = if let ParamValue::Float(v) =
272 params.get_by_id(ParamId::PARAM_ACC_X_TEMP_COMP)
273 {
274 <R as FlightFloat>::from_f32(v)
275 } else {
276 <R as FlightFloat>::from_f32(0.0)
277 };
278 let temp_comp_y = if let ParamValue::Float(v) =
279 params.get_by_id(ParamId::PARAM_ACC_Y_TEMP_COMP)
280 {
281 <R as FlightFloat>::from_f32(v)
282 } else {
283 <R as FlightFloat>::from_f32(0.0)
284 };
285 let temp_comp_z = if let ParamValue::Float(v) =
286 params.get_by_id(ParamId::PARAM_ACC_Z_TEMP_COMP)
287 {
288 <R as FlightFloat>::from_f32(v)
289 } else {
290 <R as FlightFloat>::from_f32(0.0)
291 };
292
293 let bias_x = (self.calibration_state.accel_sum[0]
294 - temp_comp_x * self.calibration_state.accel_temp_sum)
295 / count;
296 let bias_y = (self.calibration_state.accel_sum[1]
297 - temp_comp_y * self.calibration_state.accel_temp_sum)
298 / count;
299 let bias_z = (self.calibration_state.accel_sum[2]
300 - temp_comp_z * self.calibration_state.accel_temp_sum)
301 / count;
302
303 if vector_norm([bias_x, bias_y, bias_z])
304 < <R as FlightFloat>::from_f32(3.0)
305 {
306 params.set_by_id(
307 ParamId::PARAM_ACC_X_BIAS,
308 ParamValue::Float(bias_x.to_f32_lossy()),
309 );
310 params.set_by_id(
311 ParamId::PARAM_ACC_Y_BIAS,
312 ParamValue::Float(bias_y.to_f32_lossy()),
313 );
314 params.set_by_id(
315 ParamId::PARAM_ACC_Z_BIAS,
316 ParamValue::Float(bias_z.to_f32_lossy()),
317 );
318 log_info!("Accelerometer Calibration Complete!");
319 } else {
320 flags.insert(CalibrationFlags::ACCEL_FAILED);
321 log_error!("Accelerometer calibration failed");
322 }
323 } else {
324 flags.insert(CalibrationFlags::ACCEL_FAILED);
325 log_error!("Accelerometer calibration failed: too much movement");
326 }
327
328 self.calibration_state.accel_sum = [<R as FlightFloat>::from_f32(0.0); 3];
329 self.calibration_state.accel_calibration_count = 0;
330 self.calibration_state.accel_temp_sum = <R as FlightFloat>::from_f32(0.0);
331 self.calibration_state.max_accel =
332 [<R as FlightFloat>::from_f32(-1000.0); 3];
333 self.calibration_state.min_accel =
334 [<R as FlightFloat>::from_f32(1000.0); 3];
335 flags.remove(CalibrationFlags::ACCEL);
336 }
337 }
338 }
339 packet.gyro[0] -=
340 <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_GYRO_X_BIAS));
341 packet.gyro[1] -=
342 <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_GYRO_Y_BIAS));
343 packet.gyro[2] -=
344 <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_GYRO_Z_BIAS));
345
346 let temp = <R as FlightFloat>::from_f32(packet.temperature);
347 packet.accel[0] -=
348 <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_ACC_X_TEMP_COMP))
349 * temp
350 + <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_ACC_X_BIAS));
351 packet.accel[1] -=
352 <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_ACC_Y_TEMP_COMP))
353 * temp
354 + <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_ACC_Y_BIAS));
355 packet.accel[2] -=
356 <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_ACC_Z_TEMP_COMP))
357 * temp
358 + <R as FlightFloat>::from_f32(param_float(params, ParamId::PARAM_ACC_Z_BIAS));
359 Some(packet)
360 } else {
361 None
362 }
363 }
364}
365
366impl<R: FlightFloat> SensorPacketProcessor<ImuPacket<R>> for ImuProcessor<R> {
367 fn process(
368 &mut self,
369 packet: &mut Option<Result<ImuPacket<R>, errors::SensorError>>,
370 flags: &mut CalibrationFlags,
371 params: &mut Params,
372 ) -> Option<ImuPacket<R>> {
373 self.process_packet(packet, flags, params)
374 }
375}
376
377#[derive(Default, Copy, Clone)]
382pub struct PassthroughBaroProcessor;
383impl_passthrough_sensor_packet_processor!(PassthroughBaroProcessor, BaroPacket);
384
385const SENSOR_CAL_DELAY_CYCLES: u16 = 128;
386const SENSOR_CAL_CYCLES: u16 = 127;
387const BARO_MAX_CALIBRATION_VARIANCE: f32 = 25.0;
388
389#[derive(Default, Copy, Clone)]
390pub struct BaroCalibrationState {
391 mean: f32,
392 m2: f32, count: u16,
394 last_iter_ms: u32,
395 calibrated: bool,
396 request_active: bool,
397}
398
399#[derive(Default, Copy, Clone)]
400pub struct BaroProcessor {
401 calibration_state: BaroCalibrationState,
402}
403
404impl BaroProcessor {
405 pub fn new() -> Self {
406 Self::default()
407 }
408
409 fn process_packet(
410 &mut self,
411 packet: &mut Option<Result<BaroPacket, errors::SensorError>>,
412 flags: &mut CalibrationFlags,
413 params: &mut Params,
414 ) -> Option<BaroPacket> {
415 if let Some(Ok(mut packet)) = packet.take() {
416 if flags.contains(CalibrationFlags::BARO) && !self.calibration_state.request_active {
417 self.calibration_state = BaroCalibrationState::default();
418 self.calibration_state.request_active = true;
419 }
420 if !self.calibration_state.calibrated {
421 self.calibrate(&packet, flags, params);
422 }
423 packet.altitude = pressure_to_altitude(packet.pressure);
424 Some(packet)
425 } else {
426 None
427 }
428 }
429
430 fn calibrate(
431 &mut self,
432 packet: &BaroPacket,
433 flags: &mut CalibrationFlags,
434 params: &mut Params,
435 ) {
436 let now_ms = (packet.header.timestamp / 1000) as u32;
437 if now_ms <= self.calibration_state.last_iter_ms + 20 {
438 return;
439 }
440
441 self.calibration_state.count += 1;
442 let total_cycles = SENSOR_CAL_DELAY_CYCLES + SENSOR_CAL_CYCLES;
443
444 if self.calibration_state.count > total_cycles {
445 if self.calibration_state.m2 < BARO_MAX_CALIBRATION_VARIANCE {
446 params.set_by_id(
447 ParamId::PARAM_BARO_BIAS,
448 ParamValue::Float(self.calibration_state.mean),
449 );
450 let ground_alt = pressure_to_altitude(self.calibration_state.mean);
451 params.set_by_id(ParamId::PARAM_GROUND_LEVEL, ParamValue::Float(ground_alt));
452 self.calibration_state.calibrated = true;
453 flags.remove(CalibrationFlags::BARO);
454 self.calibration_state.request_active = false;
455 log_info!("Baro calibration complete");
456 } else {
457 flags.insert(CalibrationFlags::BARO_FAILED);
458 log_error!("Baro calibration failed");
459 }
460
461 self.calibration_state.mean = 0.0;
462 self.calibration_state.m2 = 0.0;
463 self.calibration_state.count = 0;
464 } else if self.calibration_state.count > SENSOR_CAL_DELAY_CYCLES {
465 let n = (self.calibration_state.count - SENSOR_CAL_DELAY_CYCLES) as f32;
466 let delta = packet.pressure - self.calibration_state.mean;
467 self.calibration_state.mean += delta / n;
468 let delta2 = packet.pressure - self.calibration_state.mean;
469 self.calibration_state.m2 += delta * delta2 / (SENSOR_CAL_CYCLES - 1) as f32;
470 }
471
472 self.calibration_state.last_iter_ms = now_ms;
473 }
474}
475
476fn pressure_to_altitude(pressure: f32) -> f32 {
477 pressure_to_altitude_real::<f32>(pressure)
478}
479
480fn pressure_to_altitude_real<R: FlightFloat>(pressure: R) -> R {
481 <R as FlightFloat>::from_f32(44330.0)
482 * (<R as FlightFloat>::from_f32(1.0)
483 - (pressure / <R as FlightFloat>::from_f32(101325.0))
484 .powf(<R as FlightFloat>::from_f32(0.190295)))
485}
486
487fn indicated_airspeed(dp: f32) -> f32 {
488 indicated_airspeed_real::<f32>(dp)
489}
490
491fn indicated_airspeed_real<R: FlightFloat>(dp: R) -> R {
492 const RHO: f32 = 1.225;
493 (<R as FlightFloat>::from_f32(2.0) * dp.abs() / <R as FlightFloat>::from_f32(RHO)).sqrt()
494 * dp.signum()
495}
496
497impl SensorPacketProcessor<BaroPacket> for BaroProcessor {
498 fn process(
499 &mut self,
500 packet: &mut Option<Result<BaroPacket, errors::SensorError>>,
501 flags: &mut CalibrationFlags,
502 params: &mut Params,
503 ) -> Option<BaroPacket> {
504 self.process_packet(packet, flags, params)
505 }
506}
507
508#[derive(Default, Copy, Clone)]
513pub struct PassthroughPitotProcessor;
514impl_passthrough_sensor_packet_processor!(PassthroughPitotProcessor, PitotPacket);
515
516const PITOT_MAX_CALIBRATION_VARIANCE: f32 = 100.0;
517
518#[derive(Default, Copy, Clone)]
519pub struct PitotCalibrationState {
520 mean: f32,
521 m2: f32,
522 count: u16,
523 last_iter_ms: u32,
524 calibrated: bool,
525 request_active: bool,
526}
527
528#[derive(Default, Copy, Clone)]
529pub struct PitotProcessor {
530 calibration_state: PitotCalibrationState,
531}
532
533impl PitotProcessor {
534 pub fn new() -> Self {
535 Self::default()
536 }
537
538 fn process_packet(
539 &mut self,
540 packet: &mut Option<Result<PitotPacket, errors::SensorError>>,
541 flags: &mut CalibrationFlags,
542 params: &mut Params,
543 ) -> Option<PitotPacket> {
544 if let Some(Ok(mut packet)) = packet.take() {
545 if flags.contains(CalibrationFlags::PITOT) && !self.calibration_state.request_active {
546 self.calibration_state = PitotCalibrationState::default();
547 self.calibration_state.request_active = true;
548 }
549 if !self.calibration_state.calibrated {
550 self.calibrate(&packet, flags, params);
551 }
552
553 packet.differential_pressure -= param_float(params, ParamId::PARAM_DIFF_PRESS_BIAS);
554
555 let dp = packet.differential_pressure;
556 packet.indicated_airspeed = indicated_airspeed(dp);
557
558 Some(packet)
559 } else {
560 None
561 }
562 }
563
564 fn calibrate(
565 &mut self,
566 packet: &PitotPacket,
567 flags: &mut CalibrationFlags,
568 params: &mut Params,
569 ) {
570 let now_ms = (packet.header.timestamp / 1000) as u32;
571 if now_ms <= self.calibration_state.last_iter_ms + 20 {
572 return;
573 }
574
575 self.calibration_state.count += 1;
576 let total_cycles = SENSOR_CAL_DELAY_CYCLES + SENSOR_CAL_CYCLES;
577
578 if self.calibration_state.count > total_cycles {
579 if self.calibration_state.m2 < PITOT_MAX_CALIBRATION_VARIANCE {
580 params.set_by_id(
581 ParamId::PARAM_DIFF_PRESS_BIAS,
582 ParamValue::Float(self.calibration_state.mean),
583 );
584 self.calibration_state.calibrated = true;
585 flags.remove(CalibrationFlags::PITOT);
586 self.calibration_state.request_active = false;
587 } else {
588 flags.insert(CalibrationFlags::PITOT_FAILED);
589 log_error!("Airspeed calibration failed");
590 }
591
592 self.calibration_state.mean = 0.0;
593 self.calibration_state.m2 = 0.0;
594 self.calibration_state.count = 0;
595 } else if self.calibration_state.count > SENSOR_CAL_DELAY_CYCLES {
596 let n = (self.calibration_state.count - SENSOR_CAL_DELAY_CYCLES) as f32;
597 let delta = packet.differential_pressure - self.calibration_state.mean;
598 self.calibration_state.mean += delta / n;
599 let delta2 = packet.differential_pressure - self.calibration_state.mean;
600 self.calibration_state.m2 += delta * delta2 / (SENSOR_CAL_CYCLES - 1) as f32;
601 }
602
603 self.calibration_state.last_iter_ms = now_ms;
604 }
605}
606
607impl SensorPacketProcessor<PitotPacket> for PitotProcessor {
608 fn process(
609 &mut self,
610 packet: &mut Option<Result<PitotPacket, errors::SensorError>>,
611 flags: &mut CalibrationFlags,
612 params: &mut Params,
613 ) -> Option<PitotPacket> {
614 self.process_packet(packet, flags, params)
615 }
616}
617
618#[derive(Default, Copy, Clone)]
623pub struct PassthroughMagProcessor;
624impl_passthrough_sensor_packet_processor!(PassthroughMagProcessor, MagPacket);
625
626#[derive(Default, Copy, Clone)]
627pub struct MagProcessor;
628
629impl MagProcessor {
630 fn process_packet(
631 &mut self,
632 packet: &mut Option<Result<MagPacket, errors::SensorError>>,
633 _flags: &mut CalibrationFlags,
634 params: &mut Params,
635 ) -> Option<MagPacket> {
636 if let Some(Ok(mut packet)) = packet.take() {
637 rotate_mag_in_place(&mut packet, params);
638 let mag_hard_x = packet.flux[0] - param_float(params, ParamId::PARAM_MAG_X_BIAS);
640 let mag_hard_y = packet.flux[1] - param_float(params, ParamId::PARAM_MAG_Y_BIAS);
641 let mag_hard_z = packet.flux[2] - param_float(params, ParamId::PARAM_MAG_Z_BIAS);
642
643 let a00 = param_float(params, ParamId::PARAM_MAG_A00_COMP);
645 let a01 = param_float(params, ParamId::PARAM_MAG_A01_COMP);
646 let a02 = param_float(params, ParamId::PARAM_MAG_A02_COMP);
647 let a10 = param_float(params, ParamId::PARAM_MAG_A10_COMP);
648 let a11 = param_float(params, ParamId::PARAM_MAG_A11_COMP);
649 let a12 = param_float(params, ParamId::PARAM_MAG_A12_COMP);
650 let a20 = param_float(params, ParamId::PARAM_MAG_A20_COMP);
651 let a21 = param_float(params, ParamId::PARAM_MAG_A21_COMP);
652 let a22 = param_float(params, ParamId::PARAM_MAG_A22_COMP);
653
654 packet.flux[0] = a00 * mag_hard_x + a01 * mag_hard_y + a02 * mag_hard_z;
656 packet.flux[1] = a10 * mag_hard_x + a11 * mag_hard_y + a12 * mag_hard_z;
657 packet.flux[2] = a20 * mag_hard_x + a21 * mag_hard_y + a22 * mag_hard_z;
658
659 Some(packet)
660 } else {
661 None
662 }
663 }
664}
665
666fn param_float(params: &Params, param_id: ParamId) -> f32 {
667 match params.get_by_id(param_id) {
668 ParamValue::Float(value) => value,
669 _ => 0.0,
670 }
671}
672
673fn vector_norm<R: FlightFloat>(vector: [R; 3]) -> R {
674 (vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]).sqrt()
675}
676
677fn rotate_mag_in_place(packet: &mut MagPacket, params: &Params) {
678 let rotation = rosflight_orientation_matrix(
679 param_float(params, ParamId::PARAM_MAG_ROLL) * deg_to_rad::<f32>(),
680 param_float(params, ParamId::PARAM_MAG_PITCH) * deg_to_rad::<f32>(),
681 param_float(params, ParamId::PARAM_MAG_YAW) * deg_to_rad::<f32>(),
682 );
683 let rotated = rotate_vector_real(rotation, [packet.flux[0], packet.flux[1], packet.flux[2]]);
684 packet.flux = rotated;
685}
686
687#[derive(Copy, Clone)]
688struct ImuMountRotation<R: FlightFloat> {
689 angles_deg: [f32; 3],
690 rotation: [[R; 3]; 3],
691}
692
693impl<R: FlightFloat> ImuMountRotation<R> {
694 fn rotate_imu_in_place(&mut self, packet: &mut ImuPacket<R>, params: &Params) {
695 self.update_from_params(params);
696 packet.accel = rotate_vector_real(self.rotation, packet.accel);
697 packet.gyro = rotate_vector_real(self.rotation, packet.gyro);
698 }
699
700 fn update_from_params(&mut self, params: &Params) {
701 let angles_deg = [
702 param_float(params, ParamId::PARAM_IMU_ROLL),
703 param_float(params, ParamId::PARAM_IMU_PITCH),
704 param_float(params, ParamId::PARAM_IMU_YAW),
705 ];
706
707 if angles_deg == self.angles_deg {
708 return;
709 }
710
711 self.angles_deg = angles_deg;
712 self.rotation = imu_mount_rotation_matrix(angles_deg);
713 }
714}
715
716impl<R: FlightFloat> Default for ImuMountRotation<R> {
717 fn default() -> Self {
718 let angles_deg = [0.0; 3];
719 Self {
720 angles_deg,
721 rotation: imu_mount_rotation_matrix(angles_deg),
722 }
723 }
724}
725
726fn imu_mount_rotation_matrix<R: FlightFloat>(angles_deg: [f32; 3]) -> [[R; 3]; 3] {
727 rosflight_orientation_matrix(
728 <R as FlightFloat>::from_f32(angles_deg[0]) * deg_to_rad(),
729 <R as FlightFloat>::from_f32(angles_deg[1]) * deg_to_rad(),
730 <R as FlightFloat>::from_f32(angles_deg[2]) * deg_to_rad(),
731 )
732}
733
734fn rosflight_orientation_matrix<R: FlightFloat>(roll: R, pitch: R, yaw: R) -> [[R; 3]; 3] {
735 let two = <R as FlightFloat>::from_f32(2.0);
736 let half = <R as FlightFloat>::from_f32(0.5);
737 let roll_cos = (roll * half).cos();
738 let roll_sin = (roll * half).sin();
739 let pitch_cos = (pitch * half).cos();
740 let pitch_sin = (pitch * half).sin();
741 let yaw_cos = (yaw * half).cos();
742 let yaw_sin = (yaw * half).sin();
743
744 let mut w = yaw_cos * pitch_cos * roll_cos + yaw_sin * pitch_sin * roll_sin;
747 let mut x = yaw_cos * pitch_cos * roll_sin - yaw_sin * pitch_sin * roll_cos;
748 let mut y = yaw_cos * pitch_sin * roll_cos + yaw_sin * pitch_cos * roll_sin;
749 let mut z = yaw_sin * pitch_cos * roll_cos - yaw_cos * pitch_sin * roll_sin;
750 let norm = (w * w + x * x + y * y + z * z).sqrt();
751 w /= norm;
752 x /= norm;
753 y /= norm;
754 z /= norm;
755
756 [
757 [
758 R::one() - two * y * y - two * z * z,
759 two * (x * y + w * z),
760 two * (x * z - w * y),
761 ],
762 [
763 two * (x * y - w * z),
764 R::one() - two * x * x - two * z * z,
765 two * (y * z + w * x),
766 ],
767 [
768 two * (x * z + w * y),
769 two * (y * z - w * x),
770 R::one() - two * x * x - two * y * y,
771 ],
772 ]
773}
774
775fn rotate_vector_real<R: FlightFloat>(rotation: [[R; 3]; 3], vector: [R; 3]) -> [R; 3] {
776 [
777 rotation[0][0] * vector[0] + rotation[0][1] * vector[1] + rotation[0][2] * vector[2],
778 rotation[1][0] * vector[0] + rotation[1][1] * vector[1] + rotation[1][2] * vector[2],
779 rotation[2][0] * vector[0] + rotation[2][1] * vector[1] + rotation[2][2] * vector[2],
780 ]
781}
782
783impl SensorPacketProcessor<MagPacket> for MagProcessor {
784 fn process(
785 &mut self,
786 packet: &mut Option<Result<MagPacket, errors::SensorError>>,
787 flags: &mut CalibrationFlags,
788 params: &mut Params,
789 ) -> Option<MagPacket> {
790 self.process_packet(packet, flags, params)
791 }
792}
793
794#[derive(Default, Copy, Clone)]
799pub struct PassthroughRcProcessor;
800impl_passthrough_sensor_packet_processor!(PassthroughRcProcessor, RcPacket);
801
802#[derive(Default, Copy, Clone)]
807pub struct PassthroughRangeProcessor;
808impl_passthrough_sensor_packet_processor!(PassthroughRangeProcessor, RangePacket);
809
810#[derive(Default, Copy, Clone)]
815pub struct PassthroughGNSSProcessor;
816impl_passthrough_sensor_packet_processor!(PassthroughGNSSProcessor, GNSSPacket);
817
818#[derive(Default, Copy, Clone)]
823pub struct PassthroughPpsProcessor;
824impl_passthrough_sensor_packet_processor!(PassthroughPpsProcessor, PpsPacket);
825
826#[derive(Default, Copy, Clone)]
831pub struct PassthroughAttitudeProcessor;
832impl_passthrough_sensor_packet_processor!(PassthroughAttitudeProcessor, AttitudePacket);
833
834#[cfg(test)]
835mod tests {
836 use super::*;
837
838 fn process_one<P, Proc>(processor: &mut Proc, packet: P, params: &mut Params) -> P
839 where
840 Proc: SensorPacketProcessor<P>,
841 {
842 let mut raw = Some(Ok(packet));
843 let mut flags = CalibrationFlags::empty();
844 processor.process(&mut raw, &mut flags, params).unwrap()
845 }
846
847 fn assert_vector_close(actual: [f64; 3], expected: [f64; 3]) {
848 for axis in 0..3 {
849 assert!(
850 (actual[axis] - expected[axis]).abs() < 1e-6,
851 "axis {axis}: expected {}, got {}",
852 expected[axis],
853 actual[axis]
854 );
855 }
856 }
857
858 #[test]
859 fn rosflight_mount_rotation_matches_positive_and_negative_cardinal_axes() {
860 let cases = [
861 ([90.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]),
862 ([-90.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]),
863 ([0.0, 90.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]),
864 ([0.0, -90.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0]),
865 ([0.0, 0.0, 90.0], [1.0, 0.0, 0.0], [0.0, -1.0, 0.0]),
866 ([0.0, 0.0, -90.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]),
867 ];
868
869 for (angles_deg, input, expected) in cases {
870 let rotation = imu_mount_rotation_matrix::<f64>(angles_deg);
871 assert_vector_close(rotate_vector_real(rotation, input), expected);
872 }
873 }
874
875 #[test]
876 fn rosflight_mount_rotation_matches_combined_rpy_reference() {
877 let rotation = imu_mount_rotation_matrix::<f64>([30.0, -20.0, 40.0]);
878 assert_vector_close(
879 rotate_vector_real(rotation, [0.3, -0.4, 0.5]),
880 [
881 0.14535485535869916,
882 -0.19277467629484094,
883 0.6646125865517978,
884 ],
885 );
886 }
887
888 #[test]
889 fn zero_mount_rotation_is_exact_identity() {
890 assert_eq!(
891 imu_mount_rotation_matrix::<f64>([0.0; 3]),
892 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
893 );
894 }
895
896 #[test]
897 fn imu_processor_applies_rosflight_orientation_before_correction() {
898 let mut params = Params::new();
899 params.set_by_id(ParamId::PARAM_IMU_YAW, ParamValue::Float(90.0));
900 params.set_by_id(ParamId::PARAM_GYRO_Y_BIAS, ParamValue::Float(0.5));
901 let mut processor = ImuProcessor::<f64>::new();
902
903 let processed: ImuPacket<f64> = process_one(
904 &mut processor,
905 ImuPacket {
906 accel: [1.0, 0.0, 0.0],
907 gyro: [1.0, 0.0, 0.0],
908 ..Default::default()
909 },
910 &mut params,
911 );
912
913 assert!(processed.accel[0].abs() < 1e-6);
914 assert!((processed.accel[1] + 1.0).abs() < 1e-6);
915 assert!(processed.gyro[0].abs() < 1e-6);
916 assert!((processed.gyro[1] + 1.5).abs() < 1e-6);
917 }
918
919 #[test]
920 fn imu_processor_refreshes_mount_rotation_after_param_change() {
921 let mut params = Params::new();
922 let mut processor = ImuProcessor::<f64>::new();
923
924 let unchanged: ImuPacket<f64> = process_one(
925 &mut processor,
926 ImuPacket {
927 accel: [1.0, 0.0, 0.0],
928 gyro: [1.0, 0.0, 0.0],
929 ..Default::default()
930 },
931 &mut params,
932 );
933 assert!((unchanged.accel[0] - 1.0).abs() < 1e-6);
934 assert!(unchanged.accel[1].abs() < 1e-6);
935
936 params.set_by_id(ParamId::PARAM_IMU_YAW, ParamValue::Float(90.0));
937 let rotated: ImuPacket<f64> = process_one(
938 &mut processor,
939 ImuPacket {
940 accel: [1.0, 0.0, 0.0],
941 gyro: [1.0, 0.0, 0.0],
942 ..Default::default()
943 },
944 &mut params,
945 );
946
947 assert!(rotated.accel[0].abs() < 1e-6);
948 assert!((rotated.accel[1] + 1.0).abs() < 1e-6);
949 assert!(rotated.gyro[0].abs() < 1e-6);
950 assert!((rotated.gyro[1] + 1.0).abs() < 1e-6);
951 }
952
953 #[test]
954 fn imu_processor_matches_rosflight_c_bias_and_temperature_compensation() {
955 let mut params = Params::new();
956 params.set_by_id(ParamId::PARAM_GYRO_X_BIAS, ParamValue::Float(0.1));
957 params.set_by_id(ParamId::PARAM_GYRO_Y_BIAS, ParamValue::Float(-0.2));
958 params.set_by_id(ParamId::PARAM_GYRO_Z_BIAS, ParamValue::Float(0.3));
959 params.set_by_id(ParamId::PARAM_ACC_X_BIAS, ParamValue::Float(0.4));
960 params.set_by_id(ParamId::PARAM_ACC_Y_BIAS, ParamValue::Float(-0.5));
961 params.set_by_id(ParamId::PARAM_ACC_Z_BIAS, ParamValue::Float(0.6));
962 params.set_by_id(ParamId::PARAM_ACC_X_TEMP_COMP, ParamValue::Float(0.01));
963 params.set_by_id(ParamId::PARAM_ACC_Y_TEMP_COMP, ParamValue::Float(-0.02));
964 params.set_by_id(ParamId::PARAM_ACC_Z_TEMP_COMP, ParamValue::Float(0.03));
965 let mut processor = ImuProcessor::<f64>::new();
966
967 let processed: ImuPacket<f64> = process_one(
968 &mut processor,
969 ImuPacket {
970 accel: [1.4, -2.5, -8.00665],
971 gyro: [0.6, -0.7, 1.1],
972 temperature: 20.0,
973 ..Default::default()
974 },
975 &mut params,
976 );
977
978 assert!((processed.gyro[0] - 0.5).abs() < 1e-6);
979 assert!((processed.gyro[1] + 0.5).abs() < 1e-6);
980 assert!((processed.gyro[2] - 0.8).abs() < 1e-6);
981 assert!((processed.accel[0] - 0.8).abs() < 1e-6);
982 assert!((processed.accel[1] + 1.6).abs() < 1e-6);
983 assert!((processed.accel[2] + 9.20665).abs() < 1e-5);
984 }
985
986 #[test]
987 fn mag_processor_applies_orientation_hard_iron_and_rosflight_matrix() {
988 let mut params = Params::new();
989 params.set_by_id(ParamId::PARAM_MAG_YAW, ParamValue::Float(90.0));
990 params.set_by_id(ParamId::PARAM_MAG_Y_BIAS, ParamValue::Float(1.0));
991 params.set_by_id(ParamId::PARAM_MAG_A00_COMP, ParamValue::Float(2.0));
992 params.set_by_id(ParamId::PARAM_MAG_A01_COMP, ParamValue::Float(3.0));
993 params.set_by_id(ParamId::PARAM_MAG_A02_COMP, ParamValue::Float(5.0));
994 params.set_by_id(ParamId::PARAM_MAG_A10_COMP, ParamValue::Float(7.0));
995 params.set_by_id(ParamId::PARAM_MAG_A11_COMP, ParamValue::Float(11.0));
996 params.set_by_id(ParamId::PARAM_MAG_A12_COMP, ParamValue::Float(13.0));
997 params.set_by_id(ParamId::PARAM_MAG_A20_COMP, ParamValue::Float(17.0));
998 params.set_by_id(ParamId::PARAM_MAG_A21_COMP, ParamValue::Float(19.0));
999 params.set_by_id(ParamId::PARAM_MAG_A22_COMP, ParamValue::Float(23.0));
1000 let mut processor = MagProcessor;
1001
1002 let processed: MagPacket = process_one(
1003 &mut processor,
1004 MagPacket {
1005 flux: [1.0, 0.0, 2.0],
1006 ..Default::default()
1007 },
1008 &mut params,
1009 );
1010
1011 assert!((processed.flux[0] - 4.0).abs() < 1e-5);
1014 assert!((processed.flux[1] - 4.0).abs() < 1e-5);
1015 assert!((processed.flux[2] - 8.0).abs() < 1e-5);
1016 }
1017
1018 #[test]
1019 fn baro_processor_matches_rosflight_correction_without_subtracting_bias() {
1020 let mut params = Params::new();
1021 params.set_by_id(ParamId::PARAM_BARO_BIAS, ParamValue::Float(1000.0));
1022 let mut processor = BaroProcessor::new();
1023
1024 let processed: BaroPacket = process_one(
1025 &mut processor,
1026 BaroPacket {
1027 pressure: 80_000.0,
1028 ..Default::default()
1029 },
1030 &mut params,
1031 );
1032
1033 let expected = pressure_to_altitude(80_000.0) as f32;
1034 assert_eq!(processed.pressure, 80_000.0);
1035 assert!((processed.altitude - expected).abs() < 1e-3);
1036 }
1037
1038 #[test]
1039 fn baro_processor_calibration_uses_rosflight_timing_and_mean() {
1040 let mut params = Params::new();
1041 let mut processor = BaroProcessor::new();
1042 let mut flags = CalibrationFlags::BARO;
1043
1044 for sample in 0..=SENSOR_CAL_DELAY_CYCLES + SENSOR_CAL_CYCLES {
1045 let mut raw = Some(Ok(BaroPacket {
1046 header: RosflightPacketHeader {
1047 timestamp: (sample as u64 + 1) * 21_000,
1048 status: 0,
1049 },
1050 pressure: 90_000.0,
1051 ..Default::default()
1052 }));
1053 let _ = processor.process(&mut raw, &mut flags, &mut params);
1054 }
1055
1056 assert!(!flags.contains(CalibrationFlags::BARO));
1057 assert_eq!(
1058 params.get_by_id(ParamId::PARAM_BARO_BIAS),
1059 ParamValue::Float(90_000.0)
1060 );
1061 }
1062
1063 #[test]
1064 fn pitot_processor_calibrates_then_corrects_pressure_and_airspeed() {
1065 let mut params = Params::new();
1066 let mut processor = PitotProcessor::new();
1067 let mut flags = CalibrationFlags::PITOT;
1068
1069 for sample in 0..=SENSOR_CAL_DELAY_CYCLES + SENSOR_CAL_CYCLES {
1070 let mut raw = Some(Ok(PitotPacket {
1071 header: RosflightPacketHeader {
1072 timestamp: (sample as u64 + 1) * 21_000,
1073 status: 0,
1074 },
1075 differential_pressure: 4.0,
1076 ..Default::default()
1077 }));
1078 let _ = processor.process(&mut raw, &mut flags, &mut params);
1079 }
1080
1081 assert!(!flags.contains(CalibrationFlags::PITOT));
1082 assert_eq!(
1083 params.get_by_id(ParamId::PARAM_DIFF_PRESS_BIAS),
1084 ParamValue::Float(4.0)
1085 );
1086
1087 let processed: PitotPacket = process_one(
1088 &mut processor,
1089 PitotPacket {
1090 differential_pressure: 6.0,
1091 ..Default::default()
1092 },
1093 &mut params,
1094 );
1095 assert_eq!(processed.differential_pressure, 2.0);
1096 assert!((processed.indicated_airspeed - (4.0_f32 / 1.225).sqrt()).abs() < 1e-6);
1097 }
1098
1099 #[test]
1100 fn battery_processor_matches_rosflight_lpf_initialization() {
1101 let mut params = Params::new();
1102 params.set_by_id(ParamId::PARAM_VOLT_MAX, ParamValue::Float(20.0));
1103 params.set_by_id(ParamId::PARAM_BATTERY_VOLTAGE_ALPHA, ParamValue::Float(0.5));
1104 params.set_by_id(
1105 ParamId::PARAM_BATTERY_CURRENT_ALPHA,
1106 ParamValue::Float(0.25),
1107 );
1108 let mut processor = BatteryProcessor::default();
1109
1110 let first = process_one(
1111 &mut processor,
1112 BatteryPacket {
1113 voltage: 10.0,
1114 current: 8.0,
1115 ..Default::default()
1116 },
1117 &mut params,
1118 );
1119 let second = process_one(
1120 &mut processor,
1121 BatteryPacket {
1122 voltage: 14.0,
1123 current: 4.0,
1124 ..Default::default()
1125 },
1126 &mut params,
1127 );
1128
1129 assert_eq!(first.voltage, 15.0);
1130 assert_eq!(first.current, 6.0);
1131 assert_eq!(second.voltage, 14.5);
1132 assert_eq!(second.current, 4.5);
1133 }
1134
1135 #[test]
1136 fn battery_processor_reuses_latest_sample_between_battery_updates() {
1137 let mut params = Params::new();
1138 params.set_by_id(ParamId::PARAM_VOLT_MAX, ParamValue::Float(23.5));
1139 params.set_by_id(ParamId::PARAM_BATTERY_VOLTAGE_ALPHA, ParamValue::Float(0.0));
1140 params.set_by_id(ParamId::PARAM_BATTERY_CURRENT_ALPHA, ParamValue::Float(0.0));
1141 let mut processor = BatteryProcessor::default();
1142 let mut flags = CalibrationFlags::empty();
1143 let mut raw = Some(Ok(BatteryPacket {
1144 voltage: 22.0,
1145 current: 3.0,
1146 ..Default::default()
1147 }));
1148
1149 let first = processor
1150 .process(&mut raw, &mut flags, &mut params)
1151 .unwrap();
1152 let mut no_new_packet = None;
1153 let second = processor
1154 .process(&mut no_new_packet, &mut flags, &mut params)
1155 .unwrap();
1156
1157 assert_eq!(first.voltage, 22.0);
1158 assert_eq!(second.voltage, first.voltage);
1159 assert_eq!(second.current, first.current);
1160 }
1161
1162 #[test]
1163 fn imu_calibration_uses_rosflight_gravity_sign_and_sanity_gates() {
1164 let mut params = Params::new();
1165 let mut processor = ImuProcessor::<f64>::new();
1166 let mut flags = CalibrationFlags::ACCEL | CalibrationFlags::GYRO;
1167
1168 for seq in 0..=1000 {
1169 let mut raw = Some(Ok(ImuPacket {
1170 accel: [0.1, -0.2, -9.50665],
1171 gyro: [0.2, -0.1, 0.3],
1172 seq,
1173 ..Default::default()
1174 }));
1175 let _ = processor.process(&mut raw, &mut flags, &mut params);
1176 }
1177
1178 assert!(!flags.intersects(CalibrationFlags::IMU));
1179 assert_eq!(
1180 params.get_by_id(ParamId::PARAM_ACC_X_BIAS),
1181 ParamValue::Float(0.1)
1182 );
1183 assert_eq!(
1184 params.get_by_id(ParamId::PARAM_ACC_Y_BIAS),
1185 ParamValue::Float(-0.2)
1186 );
1187 assert!((param_float(¶ms, ParamId::PARAM_ACC_Z_BIAS) - 0.3).abs() < 1e-5);
1188 assert_eq!(
1189 params.get_by_id(ParamId::PARAM_GYRO_X_BIAS),
1190 ParamValue::Float(0.2)
1191 );
1192 }
1193
1194 #[test]
1195 fn imu_calibration_uses_mount_rotation_before_gravity_sanity_gate() {
1196 let mut params = Params::new();
1197 params.set_by_id(ParamId::PARAM_IMU_ROLL, ParamValue::Float(90.0));
1198 let mut processor = ImuProcessor::<f64>::new();
1199 let mut flags = CalibrationFlags::ACCEL | CalibrationFlags::GYRO;
1200
1201 for seq in 0..=1000 {
1202 let mut raw = Some(Ok(ImuPacket {
1203 accel: [0.1, 9.50665, -0.2],
1204 gyro: [0.2, -0.3, -0.1],
1205 seq,
1206 ..Default::default()
1207 }));
1208 let _ = processor.process(&mut raw, &mut flags, &mut params);
1209 }
1210
1211 assert!(!flags.intersects(CalibrationFlags::IMU));
1212 assert!(!flags.intersects(CalibrationFlags::ACCEL_FAILED | CalibrationFlags::GYRO_FAILED));
1213 assert!((param_float(¶ms, ParamId::PARAM_ACC_X_BIAS) - 0.1).abs() < 1e-6);
1214 assert!((param_float(¶ms, ParamId::PARAM_ACC_Y_BIAS) + 0.2).abs() < 1e-6);
1215 assert!((param_float(¶ms, ParamId::PARAM_ACC_Z_BIAS) - 0.3).abs() < 1e-5);
1216 assert!((param_float(¶ms, ParamId::PARAM_GYRO_X_BIAS) - 0.2).abs() < 1e-6);
1217 assert!((param_float(¶ms, ParamId::PARAM_GYRO_Y_BIAS) + 0.1).abs() < 1e-6);
1218 assert!((param_float(¶ms, ParamId::PARAM_GYRO_Z_BIAS) - 0.3).abs() < 1e-6);
1219 }
1220}