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}