veloxity_core/sensors/
health.rs1use crate::{
2 math::FlightFloat,
3 params::{ParamId, ParamValue, Params},
4 sensors::ProcessedSensors,
5 state_machine::{ErrorFlag, StateManager},
6};
7
8pub struct SensorHealthCtx<'a, R: FlightFloat> {
9 pub now_us: u64,
10 pub sensors: &'a ProcessedSensors<R>,
11 pub params: &'a Params,
12 pub state: &'a mut StateManager,
13 pub last_imu_seen: &'a mut u64,
14 pub imu_timeout_us: u64,
15}
16
17pub fn update_sensor_health<R: FlightFloat>(ctx: SensorHealthCtx<'_, R>) {
18 if ctx.sensors.imu.is_some() {
19 *ctx.last_imu_seen = ctx.now_us;
20 ctx.state
21 .set_error_flag(ErrorFlag::IMU_NOT_RESPONDING, false, ctx.params);
22 update_imu_calibration_error(ctx.state, ctx.params);
23 } else if ctx.now_us > *ctx.last_imu_seen + ctx.imu_timeout_us {
24 ctx.state
25 .set_error_flag(ErrorFlag::IMU_NOT_RESPONDING, true, ctx.params);
26 }
27}
28
29fn update_imu_calibration_error(state: &mut StateManager, params: &Params) {
30 let error = ErrorFlag::UNCALIBRATED_IMU;
31 if imu_bias_params_are_all_zero(params) {
32 state.set_error_flag(error, true, params);
33 } else {
34 state.set_error_flag(error, false, params);
35 }
36}
37
38fn imu_bias_params_are_all_zero(params: &Params) -> bool {
39 [
40 ParamId::PARAM_ACC_X_BIAS,
41 ParamId::PARAM_ACC_Y_BIAS,
42 ParamId::PARAM_ACC_Z_BIAS,
43 ParamId::PARAM_GYRO_X_BIAS,
44 ParamId::PARAM_GYRO_Y_BIAS,
45 ParamId::PARAM_GYRO_Z_BIAS,
46 ]
47 .into_iter()
48 .all(|id| matches!(params.get_by_id(id), ParamValue::Float(value) if value == 0.0))
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use crate::{
55 packets::{ImuPacket, RosflightPacketHeader},
56 state_machine::Event,
57 };
58
59 const TEST_IMU_TIMEOUT_US: u64 = 100_000;
60
61 fn imu_packet(timestamp: u64) -> ImuPacket<f64> {
62 ImuPacket {
63 header: RosflightPacketHeader {
64 timestamp,
65 status: 0,
66 },
67 accel: [0.0, 0.0, -9.80665],
68 gyro: [0.0, 0.0, 0.0],
69 temperature: 25.0,
70 seq: 1,
71 }
72 }
73
74 #[test]
75 fn imu_calibration_health_sets_uncalibrated_error_when_all_bias_params_are_zero() {
76 let params = Params::new();
77 let mut state = StateManager::new();
78 state.update(Event::INITIALIZED, ¶ms);
79 let mut last_imu_seen = 0;
80 let sensors = ProcessedSensors {
81 imu: Some(imu_packet(1)),
82 ..ProcessedSensors::<f64>::default()
83 };
84
85 update_sensor_health(SensorHealthCtx {
86 now_us: 1,
87 sensors: &sensors,
88 params: ¶ms,
89 state: &mut state,
90 last_imu_seen: &mut last_imu_seen,
91 imu_timeout_us: TEST_IMU_TIMEOUT_US,
92 });
93
94 assert!(state.get_errors().contains(ErrorFlag::UNCALIBRATED_IMU));
95 assert_eq!(last_imu_seen, 1);
96 }
97
98 #[test]
99 fn imu_calibration_health_clears_uncalibrated_error_when_any_bias_param_is_nonzero() {
100 let mut params = Params::new();
101 params.set_by_id(ParamId::PARAM_ACC_X_BIAS, ParamValue::Float(0.01));
102 let mut state = StateManager::new();
103 state.update(Event::INITIALIZED, ¶ms);
104 state.update(Event::ERROR_OCCURRED(ErrorFlag::UNCALIBRATED_IMU), ¶ms);
105 let mut last_imu_seen = 0;
106 let sensors = ProcessedSensors {
107 imu: Some(imu_packet(1)),
108 ..ProcessedSensors::<f64>::default()
109 };
110
111 update_sensor_health(SensorHealthCtx {
112 now_us: 1,
113 sensors: &sensors,
114 params: ¶ms,
115 state: &mut state,
116 last_imu_seen: &mut last_imu_seen,
117 imu_timeout_us: TEST_IMU_TIMEOUT_US,
118 });
119
120 assert!(!state.get_errors().contains(ErrorFlag::UNCALIBRATED_IMU));
121 }
122
123 #[test]
124 fn sensor_health_sets_imu_not_responding_after_timeout_without_imu_sample() {
125 let params = Params::new();
126 let mut state = StateManager::new();
127 state.update(Event::INITIALIZED, ¶ms);
128 let mut last_imu_seen = 0;
129 let sensors = ProcessedSensors::<f64>::default();
130
131 update_sensor_health(SensorHealthCtx {
132 now_us: TEST_IMU_TIMEOUT_US + 1,
133 sensors: &sensors,
134 params: ¶ms,
135 state: &mut state,
136 last_imu_seen: &mut last_imu_seen,
137 imu_timeout_us: TEST_IMU_TIMEOUT_US,
138 });
139
140 assert!(state.get_errors().contains(ErrorFlag::IMU_NOT_RESPONDING));
141 assert_eq!(last_imu_seen, 0);
142 }
143
144 #[test]
145 fn sensor_health_clears_imu_not_responding_when_imu_sample_returns() {
146 let params = Params::new();
147 let mut state = StateManager::new();
148 state.update(Event::INITIALIZED, ¶ms);
149 state.update(
150 Event::ERROR_OCCURRED(ErrorFlag::IMU_NOT_RESPONDING),
151 ¶ms,
152 );
153 let mut last_imu_seen = 0;
154 let sensors = ProcessedSensors {
155 imu: Some(imu_packet(5)),
156 ..ProcessedSensors::<f64>::default()
157 };
158
159 update_sensor_health(SensorHealthCtx {
160 now_us: 5,
161 sensors: &sensors,
162 params: ¶ms,
163 state: &mut state,
164 last_imu_seen: &mut last_imu_seen,
165 imu_timeout_us: TEST_IMU_TIMEOUT_US,
166 });
167
168 assert!(!state.get_errors().contains(ErrorFlag::IMU_NOT_RESPONDING));
169 assert_eq!(last_imu_seen, 5);
170 }
171}