stepper_motion/motor/
system.rs1use embedded_hal::delay::DelayNs;
6use embedded_hal::digital::OutputPin;
7use heapless::{FnvIndexMap, String};
8
9use crate::config::{MechanicalConstraints, MotorConfig, SystemConfig};
10use crate::error::{ConfigError, Error, Result};
11use crate::motor::state::Idle;
12use crate::motor::{StepperMotor, StepperMotorBuilder};
13use crate::trajectory::TrajectoryRegistry;
14
15pub struct MotorSystem {
40 config: SystemConfig,
42 registry: TrajectoryRegistry,
44 registered_motors: FnvIndexMap<String<32>, MechanicalConstraints, 8>,
46}
47
48impl MotorSystem {
49 pub fn from_config(config: SystemConfig) -> Self {
55 let registry = TrajectoryRegistry::from_config(&config);
56 Self {
57 config,
58 registry,
59 registered_motors: FnvIndexMap::new(),
60 }
61 }
62
63 pub fn config(&self) -> &SystemConfig {
65 &self.config
66 }
67
68 pub fn trajectories(&self) -> &TrajectoryRegistry {
70 &self.registry
71 }
72
73 pub fn motor_config(&self, name: &str) -> Option<&MotorConfig> {
77 self.config.motor(name)
78 }
79
80 pub fn constraints(&self, name: &str) -> Option<MechanicalConstraints> {
84 self.config
85 .motor(name)
86 .map(MechanicalConstraints::from_config)
87 }
88
89 pub fn has_motor(&self, name: &str) -> bool {
91 self.config.motor(name).is_some()
92 }
93
94 pub fn motor_names(&self) -> impl Iterator<Item = &str> {
96 self.config.motor_names()
97 }
98
99 pub fn register_motor<STEP, DIR, DELAY>(
108 &mut self,
109 name: &str,
110 step_pin: STEP,
111 dir_pin: DIR,
112 delay: DELAY,
113 ) -> Result<StepperMotor<STEP, DIR, DELAY, Idle>>
114 where
115 STEP: OutputPin,
116 DIR: OutputPin,
117 DELAY: DelayNs,
118 {
119 let motor_config = self.config.motor(name).ok_or_else(|| {
120 Error::Config(ConfigError::MotorNotFound(
121 String::try_from(name).unwrap_or_default(),
122 ))
123 })?;
124
125 let constraints = MechanicalConstraints::from_config(motor_config);
126
127 let motor_name: String<32> = String::try_from(name).unwrap_or_default();
129 let _ = self.registered_motors.insert(motor_name, constraints);
130
131 StepperMotorBuilder::new()
133 .step_pin(step_pin)
134 .dir_pin(dir_pin)
135 .delay(delay)
136 .from_motor_config(motor_config)
137 .build()
138 }
139
140 pub fn build_motor<STEP, DIR, DELAY>(
148 &self,
149 name: &str,
150 step_pin: STEP,
151 dir_pin: DIR,
152 delay: DELAY,
153 ) -> Result<StepperMotor<STEP, DIR, DELAY, Idle>>
154 where
155 STEP: OutputPin,
156 DIR: OutputPin,
157 DELAY: DelayNs,
158 {
159 let motor_config = self.config.motor(name).ok_or_else(|| {
160 Error::Config(ConfigError::MotorNotFound(
161 String::try_from(name).unwrap_or_default(),
162 ))
163 })?;
164
165 StepperMotorBuilder::new()
166 .step_pin(step_pin)
167 .dir_pin(dir_pin)
168 .delay(delay)
169 .from_motor_config(motor_config)
170 .build()
171 }
172
173 pub fn is_registered(&self, name: &str) -> bool {
175 self.registered_motors
176 .iter()
177 .any(|(k, _)| k.as_str() == name)
178 }
179
180 pub fn registered_count(&self) -> usize {
182 self.registered_motors.len()
183 }
184
185 pub fn registered_constraints(&self, name: &str) -> Option<&MechanicalConstraints> {
189 self.registered_motors
190 .iter()
191 .find(|(k, _)| k.as_str() == name)
192 .map(|(_, v)| v)
193 }
194
195 pub fn trajectory(&self, name: &str) -> Result<&crate::config::TrajectoryConfig> {
199 self.registry.get_or_error(name)
200 }
201
202 pub fn trajectories_for_motor<'a>(
204 &'a self,
205 motor_name: &'a str,
206 ) -> impl Iterator<Item = &'a str> + 'a {
207 self.registry
208 .iter()
209 .filter(move |(_, traj)| traj.motor.as_str() == motor_name)
210 .map(|(name, _)| name)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 fn test_config() -> SystemConfig {
219 let toml = r#"
220[motors.x_axis]
221name = "X Axis"
222steps_per_revolution = 200
223microsteps = 16
224max_velocity_deg_per_sec = 360.0
225max_acceleration_deg_per_sec2 = 720.0
226
227[motors.y_axis]
228name = "Y Axis"
229steps_per_revolution = 400
230microsteps = 8
231max_velocity_deg_per_sec = 180.0
232max_acceleration_deg_per_sec2 = 360.0
233
234[trajectories.home_x]
235motor = "x_axis"
236target_degrees = 0.0
237velocity_percent = 50
238
239[trajectories.home_y]
240motor = "y_axis"
241target_degrees = 0.0
242velocity_percent = 50
243"#;
244 toml::from_str(toml).unwrap()
245 }
246
247 #[test]
248 fn test_motor_system_creation() {
249 let config = test_config();
250 let system = MotorSystem::from_config(config);
251
252 assert!(system.has_motor("x_axis"));
253 assert!(system.has_motor("y_axis"));
254 assert!(!system.has_motor("z_axis"));
255 }
256
257 #[test]
258 fn test_motor_names() {
259 let config = test_config();
260 let system = MotorSystem::from_config(config);
261
262 let names: Vec<_> = system.motor_names().collect();
263 assert!(names.contains(&"x_axis"));
264 assert!(names.contains(&"y_axis"));
265 }
266
267 #[test]
268 fn test_constraints_lookup() {
269 let config = test_config();
270 let system = MotorSystem::from_config(config);
271
272 let constraints = system.constraints("x_axis").unwrap();
273 assert_eq!(constraints.steps_per_revolution, 3200);
275
276 let constraints = system.constraints("y_axis").unwrap();
277 assert_eq!(constraints.steps_per_revolution, 3200);
279 }
280
281 #[test]
282 fn test_trajectories_for_motor() {
283 let config = test_config();
284 let system = MotorSystem::from_config(config);
285
286 let x_trajectories: Vec<_> = system.trajectories_for_motor("x_axis").collect();
287 assert!(x_trajectories.contains(&"home_x"));
288 assert!(!x_trajectories.contains(&"home_y"));
289
290 let y_trajectories: Vec<_> = system.trajectories_for_motor("y_axis").collect();
291 assert!(y_trajectories.contains(&"home_y"));
292 assert!(!y_trajectories.contains(&"home_x"));
293 }
294
295 #[test]
296 fn test_trajectory_lookup() {
297 let config = test_config();
298 let system = MotorSystem::from_config(config);
299
300 let traj = system.trajectory("home_x");
301 assert!(traj.is_ok());
302 assert_eq!(traj.unwrap().motor.as_str(), "x_axis");
303
304 let traj = system.trajectory("nonexistent");
305 assert!(traj.is_err());
306 }
307}