Skip to main content

mecha10_controllers/mock/
imu.rs

1//! Mock IMU controller for testing
2
3use 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/// Mock IMU controller configuration
10#[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
27/// Mock IMU controller for testing
28///
29/// Generates synthetic IMU data with simulated motion.
30pub struct MockImuController {
31    config: MockImuConfig,
32    state: ControllerState,
33    calibration: CalibrationStatus,
34    sample_count: u64,
35    orientation: (f32, f32, f32), // (roll, pitch, yaw)
36}
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        // Simulate slow rotation over time
118        let time_sec = (timestamp as f64) / 1_000_000.0;
119        self.orientation.2 = (time_sec * 0.1) as f32; // Slow yaw rotation
120
121        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, // Constant slow rotation
129            linear_acceleration_x: 0.0,
130            linear_acceleration_y: 0.0,
131            linear_acceleration_z: 9.81, // Gravity
132        })
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        // Simulate calibration
141        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        // Mock save - do nothing
174        Ok(())
175    }
176
177    async fn load_calibration(&mut self, _path: &str) -> Result<(), Self::Error> {
178        // Mock load - just mark as calibrated
179        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    /// Set orientation manually for testing
204    pub fn set_orientation(&mut self, roll: f32, pitch: f32, yaw: f32) {
205        self.orientation = (roll, pitch, yaw);
206    }
207
208    /// Get sample count
209    pub fn sample_count(&self) -> u64 {
210        self.sample_count
211    }
212
213    /// Reset sample count
214    pub fn reset_sample_count(&mut self) {
215        self.sample_count = 0;
216    }
217}