Skip to main content

autocore_std/motion/
axis_config.rs

1//! Axis configuration: unit conversion, jog defaults, limits.
2//!
3//! [`AxisConfig`] stores the encoder resolution and user-unit scaling,
4//! plus jog parameters, home position, and software position limits.
5//! It is used by [`Axis`](super::Axis) for all unit conversions.
6
7/// Configuration for a single motion axis.
8///
9/// Stores encoder resolution, user-unit scaling, jog defaults,
10/// and position limits. Used by [`Axis`](super::Axis) internally
11/// and also available for direct use (e.g. logging, HMI display).
12///
13/// # Examples
14///
15/// ```
16/// use autocore_std::motion::AxisConfig;
17///
18/// // ClearPath with 12,800 counts/rev, user units = degrees
19/// let cfg = AxisConfig::new(12_800).with_user_scale(360.0);
20/// assert!((cfg.to_counts(45.0) - 1600.0).abs() < 0.01);
21/// assert!((cfg.to_user(1600.0) - 45.0).abs() < 0.01);
22///
23/// // mm on a 5 mm/rev ballscrew
24/// let mm = AxisConfig::new(12_800).with_user_scale(5.0);
25/// assert!((mm.to_counts(5.0) - 12800.0).abs() < 0.01);
26/// ```
27#[derive(Debug, Clone)]
28pub struct AxisConfig {
29    // ── Unit conversion (private — use to_counts/to_user) ──
30    counts_per_rev: f64,
31    user_per_rev: f64,
32
33    // ── Jog defaults ──
34
35    /// Jog speed in user units/s.
36    pub jog_speed: f64,
37    /// Jog acceleration in user units/s².
38    pub jog_accel: f64,
39    /// Jog deceleration in user units/s².
40    pub jog_decel: f64,
41
42    // ── Home ──
43
44    /// User-unit position assigned at the home reference point.
45    pub home_position: f64,
46    /// Homing search speed in user units/s.
47    pub homing_speed: f64,
48    /// Homing acceleration in user units/s².
49    pub homing_accel: f64,
50    /// Homing deceleration in user units/s².
51    pub homing_decel: f64,
52
53    // ── Software position limits ──
54
55    /// Enable the maximum (positive) software position limit.
56    pub enable_max_position_limit: bool,
57    /// Enable the minimum (negative) software position limit.
58    pub enable_min_position_limit: bool,
59    /// Maximum position limit in user units.
60    pub max_position_limit: f64,
61    /// Minimum position limit in user units.
62    pub min_position_limit: f64,
63}
64
65impl AxisConfig {
66    /// Create a new configuration from encoder resolution.
67    ///
68    /// Default user units are revolutions (1.0 user unit = 1 revolution).
69    /// Call [`with_user_scale`](Self::with_user_scale) to change.
70    pub fn new(counts_per_rev: u32) -> Self {
71        Self {
72            counts_per_rev: counts_per_rev as f64,
73            user_per_rev: 1.0,
74            jog_speed: 0.0,
75            jog_accel: 0.0,
76            jog_decel: 0.0,
77            home_position: 0.0,
78            homing_speed: 0.0,
79            homing_accel: 0.0,
80            homing_decel: 0.0,
81            enable_max_position_limit: false,
82            enable_min_position_limit: false,
83            max_position_limit: 0.0,
84            min_position_limit: 0.0,
85        }
86    }
87
88    /// Set user-units-per-revolution.
89    ///
90    /// - Degrees: `.with_user_scale(360.0)`
91    /// - mm on 5 mm/rev ballscrew: `.with_user_scale(5.0)`
92    pub fn with_user_scale(mut self, user_per_rev: f64) -> Self {
93        self.user_per_rev = user_per_rev;
94        self
95    }
96
97    // ── Conversion methods ──
98
99    /// Convert user units to encoder counts (f64).
100    ///
101    /// `to_counts(45.0)` on a 12800 cpr / 360° config → 1600.0
102    pub fn to_counts(&self, user_units: f64) -> f64 {
103        user_units * self.counts_per_user()
104    }
105
106    /// Convert encoder counts to user units (f64).
107    ///
108    /// `to_user(1600.0)` on a 12800 cpr / 360° config → 45.0
109    pub fn to_user(&self, counts: f64) -> f64 {
110        counts / self.counts_per_user()
111    }
112
113    /// Encoder counts per user unit (scale factor).
114    ///
115    /// For 12800 cpr / 360°: `counts_per_user() ≈ 35.556`
116    pub fn counts_per_user(&self) -> f64 {
117        self.counts_per_rev / self.user_per_rev
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn basic_conversion_degrees() {
127        let cfg = AxisConfig::new(12_800).with_user_scale(360.0);
128        assert!((cfg.to_counts(45.0) - 1600.0).abs() < 0.01);
129        assert!((cfg.to_counts(360.0) - 12800.0).abs() < 0.01);
130        assert!((cfg.to_user(1600.0) - 45.0).abs() < 0.01);
131        assert!((cfg.to_user(12800.0) - 360.0).abs() < 0.01);
132    }
133
134    #[test]
135    fn basic_conversion_mm() {
136        let cfg = AxisConfig::new(12_800).with_user_scale(5.0);
137        assert!((cfg.to_counts(5.0) - 12800.0).abs() < 0.01);
138        assert!((cfg.to_counts(2.5) - 6400.0).abs() < 0.01);
139    }
140
141    #[test]
142    fn counts_per_user() {
143        let cfg = AxisConfig::new(12_800).with_user_scale(360.0);
144        let cpu = cfg.counts_per_user();
145        assert!((cpu - 12800.0 / 360.0).abs() < 1e-9);
146    }
147
148    #[test]
149    fn default_revolutions() {
150        let cfg = AxisConfig::new(12_800);
151        // 1 rev = 12800 counts
152        assert!((cfg.to_counts(1.0) - 12800.0).abs() < 0.01);
153        assert!((cfg.to_counts(0.125) - 1600.0).abs() < 0.01);
154    }
155
156    #[test]
157    fn round_trip() {
158        let cfg = AxisConfig::new(12_800).with_user_scale(360.0);
159        let original = 45.0;
160        let counts = cfg.to_counts(original);
161        let back = cfg.to_user(counts);
162        assert!((back - original).abs() < 1e-9);
163    }
164
165    #[test]
166    fn defaults() {
167        let cfg = AxisConfig::new(12_800);
168        assert_eq!(cfg.jog_speed, 0.0);
169        assert_eq!(cfg.home_position, 0.0);
170        assert!(!cfg.enable_max_position_limit);
171        assert!(!cfg.enable_min_position_limit);
172    }
173}