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