mecha10_controllers/mock/
imu.rs1use crate::imu::*;
4use crate::{Controller, ControllerCapabilities, ControllerError, ControllerHealth, ControllerState};
5use async_trait::async_trait;
6use mecha10_core::sensor::ImuData;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Debug, Clone)]
11pub struct MockImuConfig {
12 pub update_rate_hz: f32,
13 pub has_magnetometer: bool,
14 pub auto_calibrate: bool,
15}
16
17impl Default for MockImuConfig {
18 fn default() -> Self {
19 Self {
20 update_rate_hz: 100.0,
21 has_magnetometer: true,
22 auto_calibrate: true,
23 }
24 }
25}
26
27pub struct MockImuController {
31 config: MockImuConfig,
32 state: ControllerState,
33 calibration: CalibrationStatus,
34 sample_count: u64,
35 orientation: (f32, f32, f32), }
37
38#[async_trait]
39impl Controller for MockImuController {
40 type Config = MockImuConfig;
41 type Error = ControllerError;
42
43 async fn init(config: Self::Config) -> Result<Self, Self::Error> {
44 let calibration = if config.auto_calibrate {
45 CalibrationStatus {
46 system: CalibrationLevel::FullyCalibrated,
47 accelerometer: CalibrationLevel::FullyCalibrated,
48 gyroscope: CalibrationLevel::FullyCalibrated,
49 magnetometer: if config.has_magnetometer {
50 Some(CalibrationLevel::FullyCalibrated)
51 } else {
52 None
53 },
54 }
55 } else {
56 CalibrationStatus::default()
57 };
58
59 Ok(Self {
60 config,
61 state: ControllerState::Initialized,
62 calibration,
63 sample_count: 0,
64 orientation: (0.0, 0.0, 0.0),
65 })
66 }
67
68 async fn start(&mut self) -> Result<(), Self::Error> {
69 self.state = ControllerState::Running;
70 Ok(())
71 }
72
73 async fn stop(&mut self) -> Result<(), Self::Error> {
74 self.state = ControllerState::Stopped;
75 Ok(())
76 }
77
78 async fn health_check(&self) -> ControllerHealth {
79 match self.state {
80 ControllerState::Running => {
81 if self.calibration.is_fully_calibrated() {
82 ControllerHealth::Healthy
83 } else {
84 ControllerHealth::Degraded {
85 reason: "Not fully calibrated".to_string(),
86 }
87 }
88 }
89 ControllerState::Error => ControllerHealth::Unhealthy {
90 reason: "Mock error".to_string(),
91 },
92 _ => ControllerHealth::Unknown,
93 }
94 }
95
96 fn capabilities(&self) -> ControllerCapabilities {
97 ControllerCapabilities::new("imu", "mock")
98 .with_vendor("Mecha10")
99 .with_model("Mock IMU")
100 .with_feature("magnetometer", self.config.has_magnetometer)
101 .with_feature("absolute_orientation", self.config.has_magnetometer)
102 .with_feature("temperature", true)
103 }
104}
105
106#[async_trait]
107impl ImuController for MockImuController {
108 async fn read_imu(&mut self) -> Result<ImuData, Self::Error> {
109 if self.state != ControllerState::Running {
110 return Err(ControllerError::InvalidState("IMU not running".to_string()));
111 }
112
113 self.sample_count += 1;
114
115 let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros() as u64;
116
117 let time_sec = (timestamp as f64) / 1_000_000.0;
119 self.orientation.2 = (time_sec * 0.1) as f32; Ok(ImuData {
122 timestamp,
123 roll: self.orientation.0,
124 pitch: self.orientation.1,
125 yaw: self.orientation.2,
126 angular_velocity_x: 0.0,
127 angular_velocity_y: 0.0,
128 angular_velocity_z: 0.1, linear_acceleration_x: 0.0,
130 linear_acceleration_y: 0.0,
131 linear_acceleration_z: 9.81, })
133 }
134
135 fn calibration_status(&self) -> CalibrationStatus {
136 self.calibration.clone()
137 }
138
139 async fn calibrate(&mut self) -> Result<CalibrationData, Self::Error> {
140 self.calibration = CalibrationStatus {
142 system: CalibrationLevel::FullyCalibrated,
143 accelerometer: CalibrationLevel::FullyCalibrated,
144 gyroscope: CalibrationLevel::FullyCalibrated,
145 magnetometer: if self.config.has_magnetometer {
146 Some(CalibrationLevel::FullyCalibrated)
147 } else {
148 None
149 },
150 };
151
152 Ok(CalibrationData {
153 timestamp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros() as u64,
154 model: "Mock IMU".to_string(),
155 accel_offset: [0.01, -0.02, 0.03],
156 accel_radius: Some(1000.0),
157 gyro_offset: [0.001, -0.001, 0.002],
158 mag_offset: if self.config.has_magnetometer {
159 Some([10.0, -5.0, 15.0])
160 } else {
161 None
162 },
163 mag_radius: if self.config.has_magnetometer {
164 Some(500.0)
165 } else {
166 None
167 },
168 raw_data: None,
169 })
170 }
171
172 async fn save_calibration(&self, _path: &str) -> Result<(), Self::Error> {
173 Ok(())
175 }
176
177 async fn load_calibration(&mut self, _path: &str) -> Result<(), Self::Error> {
178 self.calibration = CalibrationStatus {
180 system: CalibrationLevel::FullyCalibrated,
181 accelerometer: CalibrationLevel::FullyCalibrated,
182 gyroscope: CalibrationLevel::FullyCalibrated,
183 magnetometer: if self.config.has_magnetometer {
184 Some(CalibrationLevel::FullyCalibrated)
185 } else {
186 None
187 },
188 };
189 Ok(())
190 }
191
192 async fn reset_fusion(&mut self) -> Result<(), Self::Error> {
193 self.orientation = (0.0, 0.0, 0.0);
194 Ok(())
195 }
196
197 async fn temperature(&self) -> Option<f32> {
198 Some(25.0 + (self.sample_count % 10) as f32 * 0.1)
199 }
200}
201
202impl MockImuController {
203 pub fn set_orientation(&mut self, roll: f32, pitch: f32, yaw: f32) {
205 self.orientation = (roll, pitch, yaw);
206 }
207
208 pub fn sample_count(&self) -> u64 {
210 self.sample_count
211 }
212
213 pub fn reset_sample_count(&mut self) {
215 self.sample_count = 0;
216 }
217}