stepper_motion/config/
loader.rs

1//! Configuration loading from files (std only).
2
3use std::fs;
4use std::path::Path;
5
6use crate::error::{ConfigError, Error, Result};
7
8use super::SystemConfig;
9
10/// Load configuration from a TOML file.
11///
12/// # Errors
13///
14/// Returns an error if the file cannot be read or parsed.
15///
16/// # Example
17///
18/// ```rust,ignore
19/// use stepper_motion::load_config;
20///
21/// let config = load_config("motion.toml")?;
22/// ```
23pub fn load_config<P: AsRef<Path>>(path: P) -> Result<SystemConfig> {
24    let content = fs::read_to_string(path.as_ref()).map_err(|e| {
25        let msg = heapless::String::try_from(e.to_string().as_str()).unwrap_or_default();
26        Error::Config(ConfigError::IoError(msg))
27    })?;
28
29    parse_config(&content)
30}
31
32/// Parse configuration from a TOML string.
33///
34/// # Errors
35///
36/// Returns an error if the TOML is invalid or fails validation.
37pub fn parse_config(content: &str) -> Result<SystemConfig> {
38    let config: SystemConfig = toml::from_str(content).map_err(|e| {
39        let msg = heapless::String::try_from(e.message()).unwrap_or_default();
40        Error::Config(ConfigError::ParseError(msg))
41    })?;
42
43    // Validate the configuration
44    super::validation::validate_config(&config)?;
45
46    Ok(config)
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_parse_minimal_config() {
55        let toml = r#"
56[motors.x_axis]
57name = "X-Axis"
58steps_per_revolution = 200
59microsteps = 16
60max_velocity_deg_per_sec = 360.0
61max_acceleration_deg_per_sec2 = 720.0
62"#;
63
64        let config = parse_config(toml).unwrap();
65        assert!(config.motor("x_axis").is_some());
66    }
67
68    #[test]
69    fn test_parse_with_trajectory() {
70        let toml = r#"
71[motors.x_axis]
72name = "X-Axis"
73steps_per_revolution = 200
74microsteps = 16
75max_velocity_deg_per_sec = 360.0
76max_acceleration_deg_per_sec2 = 720.0
77
78[trajectories.home]
79motor = "x_axis"
80target_degrees = 0.0
81velocity_percent = 50
82"#;
83
84        let config = parse_config(toml).unwrap();
85        assert!(config.trajectory("home").is_some());
86    }
87
88    #[test]
89    fn test_parse_asymmetric_trajectory() {
90        let toml = r#"
91[motors.x_axis]
92name = "X-Axis"
93steps_per_revolution = 200
94microsteps = 16
95max_velocity_deg_per_sec = 360.0
96max_acceleration_deg_per_sec2 = 720.0
97
98[trajectories.gentle_stop]
99motor = "x_axis"
100target_degrees = 90.0
101velocity_percent = 100
102acceleration_deg_per_sec2 = 500.0
103deceleration_deg_per_sec2 = 200.0
104"#;
105
106        let config = parse_config(toml).unwrap();
107        let traj = config.trajectory("gentle_stop").unwrap();
108        assert!(traj.is_asymmetric());
109    }
110}