stepper_motion/motion/
executor.rs1use super::profile::{MotionPhase, MotionProfile};
4
5#[derive(Debug, Clone)]
7pub struct MotionExecutor {
8 profile: MotionProfile,
10
11 current_step: u32,
13
14 current_interval_ns: u32,
16
17 phase: MotionPhase,
19}
20
21impl MotionExecutor {
22 pub fn new(profile: MotionProfile) -> Self {
24 let phase = if profile.is_zero() {
25 MotionPhase::Complete
26 } else {
27 MotionPhase::Accelerating
28 };
29
30 let interval = if profile.is_zero() {
31 u32::MAX
32 } else {
33 profile.initial_interval_ns
34 };
35
36 Self {
37 profile,
38 current_step: 0,
39 current_interval_ns: interval,
40 phase,
41 }
42 }
43
44 #[inline]
46 pub fn is_complete(&self) -> bool {
47 self.phase == MotionPhase::Complete
48 }
49
50 #[inline]
52 pub fn current_step(&self) -> u32 {
53 self.current_step
54 }
55
56 #[inline]
58 pub fn total_steps(&self) -> u32 {
59 self.profile.total_steps
60 }
61
62 #[inline]
64 pub fn steps_remaining(&self) -> u32 {
65 self.profile.total_steps.saturating_sub(self.current_step)
66 }
67
68 #[inline]
70 pub fn phase(&self) -> MotionPhase {
71 self.phase
72 }
73
74 #[inline]
76 pub fn current_interval_ns(&self) -> u32 {
77 self.current_interval_ns
78 }
79
80 #[inline]
82 pub fn profile(&self) -> &MotionProfile {
83 &self.profile
84 }
85
86 pub fn advance(&mut self) -> bool {
90 if self.is_complete() {
91 return false;
92 }
93
94 self.current_step += 1;
95
96 if self.current_step >= self.profile.total_steps {
97 self.phase = MotionPhase::Complete;
98 self.current_interval_ns = u32::MAX;
99 return false;
100 }
101
102 self.phase = self.profile.phase_at(self.current_step);
104 self.current_interval_ns = self.profile.interval_at(self.current_step);
105
106 true
107 }
108
109 pub fn reset(&mut self) {
111 self.current_step = 0;
112 self.phase = if self.profile.is_zero() {
113 MotionPhase::Complete
114 } else {
115 MotionPhase::Accelerating
116 };
117 self.current_interval_ns = if self.profile.is_zero() {
118 u32::MAX
119 } else {
120 self.profile.initial_interval_ns
121 };
122 }
123
124 #[inline]
126 pub fn progress(&self) -> f32 {
127 if self.profile.total_steps == 0 {
128 1.0
129 } else {
130 self.current_step as f32 / self.profile.total_steps as f32
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_executor_complete() {
141 let profile = MotionProfile::symmetric_trapezoidal(10, 1000.0, 2000.0);
142 let mut executor = MotionExecutor::new(profile);
143
144 assert!(!executor.is_complete());
145 assert_eq!(executor.current_step(), 0);
146
147 while executor.advance() {}
149
150 assert!(executor.is_complete());
151 assert_eq!(executor.current_step(), 10);
152 }
153
154 #[test]
155 fn test_zero_profile() {
156 let profile = MotionProfile::zero();
157 let executor = MotionExecutor::new(profile);
158
159 assert!(executor.is_complete());
160 assert_eq!(executor.steps_remaining(), 0);
161 }
162
163 #[test]
164 fn test_phase_transitions() {
165 let profile = MotionProfile::symmetric_trapezoidal(100, 1000.0, 2000.0);
166 let mut executor = MotionExecutor::new(profile);
167
168 let mut saw_accel = false;
169 let mut saw_decel = false;
170
171 while !executor.is_complete() {
172 match executor.phase() {
173 MotionPhase::Accelerating => saw_accel = true,
174 MotionPhase::Cruising => {} MotionPhase::Decelerating => saw_decel = true,
176 MotionPhase::Complete => {}
177 }
178 executor.advance();
179 }
180
181 assert!(saw_accel);
182 assert!(saw_decel);
183 }
184}