1use crate::{Controller, ControllerError};
4use async_trait::async_trait;
5use mecha10_core::sensor::ImuData;
6use serde::{Deserialize, Serialize};
7
8#[async_trait]
15pub trait ImuController: Controller {
16 async fn read_imu(&mut self) -> Result<ImuData, Self::Error>;
21
22 fn calibration_status(&self) -> CalibrationStatus;
26
27 async fn calibrate(&mut self) -> Result<CalibrationData, Self::Error>;
32
33 async fn save_calibration(&self, path: &str) -> Result<(), Self::Error>;
35
36 async fn load_calibration(&mut self, path: &str) -> Result<(), Self::Error>;
38
39 async fn reset_fusion(&mut self) -> Result<(), Self::Error>
43 where
44 Self::Error: From<ControllerError>,
45 {
46 Err(Self::Error::from(ControllerError::NotSupported(
47 "Sensor fusion reset not supported".to_string(),
48 )))
49 }
50
51 async fn temperature(&self) -> Option<f32> {
53 None
54 }
55
56 fn has_magnetometer(&self) -> bool {
58 self.capabilities()
59 .features
60 .get("magnetometer")
61 .copied()
62 .unwrap_or(false)
63 }
64
65 fn has_absolute_orientation(&self) -> bool {
67 self.capabilities()
68 .features
69 .get("absolute_orientation")
70 .copied()
71 .unwrap_or(false)
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct CalibrationStatus {
82 pub system: CalibrationLevel,
84
85 pub accelerometer: CalibrationLevel,
87
88 pub gyroscope: CalibrationLevel,
90
91 pub magnetometer: Option<CalibrationLevel>,
93}
94
95impl Default for CalibrationStatus {
96 fn default() -> Self {
97 Self {
98 system: CalibrationLevel::Uncalibrated,
99 accelerometer: CalibrationLevel::Uncalibrated,
100 gyroscope: CalibrationLevel::Uncalibrated,
101 magnetometer: None,
102 }
103 }
104}
105
106impl CalibrationStatus {
107 pub fn is_fully_calibrated(&self) -> bool {
109 self.system == CalibrationLevel::FullyCalibrated
110 && self.accelerometer == CalibrationLevel::FullyCalibrated
111 && self.gyroscope == CalibrationLevel::FullyCalibrated
112 && self
113 .magnetometer
114 .as_ref()
115 .map(|m| *m == CalibrationLevel::FullyCalibrated)
116 .unwrap_or(true)
117 }
118
119 pub fn is_minimally_calibrated(&self) -> bool {
121 self.system != CalibrationLevel::Uncalibrated
122 && self.accelerometer != CalibrationLevel::Uncalibrated
123 && self.gyroscope != CalibrationLevel::Uncalibrated
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
129pub enum CalibrationLevel {
130 Uncalibrated = 0,
132
133 Minimal = 1,
135
136 Partial = 2,
138
139 FullyCalibrated = 3,
141}
142
143impl From<u8> for CalibrationLevel {
144 fn from(value: u8) -> Self {
145 match value {
146 0 => Self::Uncalibrated,
147 1 => Self::Minimal,
148 2 => Self::Partial,
149 _ => Self::FullyCalibrated,
150 }
151 }
152}
153
154impl From<CalibrationLevel> for u8 {
155 fn from(level: CalibrationLevel) -> Self {
156 level as u8
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct CalibrationData {
163 pub timestamp: u64,
165
166 pub model: String,
168
169 pub accel_offset: [f32; 3],
171
172 pub accel_radius: Option<f32>,
174
175 pub gyro_offset: [f32; 3],
177
178 pub mag_offset: Option<[f32; 3]>,
180
181 pub mag_radius: Option<f32>,
183
184 pub raw_data: Option<Vec<u8>>,
186}
187
188impl Default for CalibrationData {
189 fn default() -> Self {
190 Self {
191 timestamp: 0,
192 model: String::new(),
193 accel_offset: [0.0; 3],
194 accel_radius: None,
195 gyro_offset: [0.0; 3],
196 mag_offset: None,
197 mag_radius: None,
198 raw_data: None,
199 }
200 }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct ImuConfig {
210 pub update_rate_hz: f32,
212
213 pub enable_accel: bool,
215
216 pub enable_gyro: bool,
218
219 pub enable_mag: bool,
221
222 pub accel_range_g: Option<u8>,
224
225 pub gyro_range_dps: Option<u16>,
227
228 pub filter_cutoff_hz: Option<f32>,
230
231 pub calibration_file: Option<String>,
233}
234
235impl Default for ImuConfig {
236 fn default() -> Self {
237 Self {
238 update_rate_hz: 100.0,
239 enable_accel: true,
240 enable_gyro: true,
241 enable_mag: true,
242 accel_range_g: Some(4),
243 gyro_range_dps: Some(500),
244 filter_cutoff_hz: Some(10.0),
245 calibration_file: None,
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
256pub enum FusionMode {
257 AccelOnly,
259
260 GyroOnly,
262
263 MagOnly,
265
266 AccelGyro,
268
269 AccelMag,
271
272 GyroMag,
274
275 FullFusion,
277
278 ImuMode,
280
281 CompassMode,
283
284 M4G,
286
287 NDOF,
289
290 NDOFFastMagCal,
292}
293
294#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
300pub struct Quaternion {
301 pub w: f32,
302 pub x: f32,
303 pub y: f32,
304 pub z: f32,
305}
306
307impl Default for Quaternion {
308 fn default() -> Self {
309 Self {
310 w: 1.0,
311 x: 0.0,
312 y: 0.0,
313 z: 0.0,
314 }
315 }
316}
317
318impl Quaternion {
319 pub fn identity() -> Self {
321 Self::default()
322 }
323
324 pub fn to_euler(&self) -> (f32, f32, f32) {
326 let sinr_cosp = 2.0 * (self.w * self.x + self.y * self.z);
328 let cosr_cosp = 1.0 - 2.0 * (self.x * self.x + self.y * self.y);
329 let roll = sinr_cosp.atan2(cosr_cosp);
330
331 let sinp = 2.0 * (self.w * self.y - self.z * self.x);
333 let pitch = if sinp.abs() >= 1.0 {
334 std::f32::consts::FRAC_PI_2.copysign(sinp)
335 } else {
336 sinp.asin()
337 };
338
339 let siny_cosp = 2.0 * (self.w * self.z + self.x * self.y);
341 let cosy_cosp = 1.0 - 2.0 * (self.y * self.y + self.z * self.z);
342 let yaw = siny_cosp.atan2(cosy_cosp);
343
344 (roll, pitch, yaw)
345 }
346}
347
348#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
350pub struct EulerAngles {
351 pub roll: f32,
352 pub pitch: f32,
353 pub yaw: f32,
354}
355
356impl Default for EulerAngles {
357 fn default() -> Self {
358 Self {
359 roll: 0.0,
360 pitch: 0.0,
361 yaw: 0.0,
362 }
363 }
364}
365
366impl From<Quaternion> for EulerAngles {
367 fn from(q: Quaternion) -> Self {
368 let (roll, pitch, yaw) = q.to_euler();
369 Self { roll, pitch, yaw }
370 }
371}