autocore_std/motion/seek_probe.rs
1/// Seek Probe — Jog an axis in the negative direction until a sensor triggers.
2///
3/// This function block implements a simple seek-to-probe state machine:
4/// 1. On a rising edge of `execute`, the axis begins jogging in the negative direction
5/// using the jog speed/accel/decel from [`AxisConfig`](super::AxisConfig).
6/// 2. When the sensor input goes high, the axis is halted.
7/// 3. Once the axis has fully stopped, the operation is complete.
8///
9/// The axis must be enabled and at a position > 0 before executing.
10/// Jog velocity, acceleration, and deceleration are taken from
11/// [`AxisConfig::jog_speed`](super::AxisConfig::jog_speed),
12/// [`AxisConfig::jog_accel`](super::AxisConfig::jog_accel), and
13/// [`AxisConfig::jog_decel`](super::AxisConfig::jog_decel).
14///
15/// # Example
16///
17/// ```ignore
18/// use autocore_std::motion::{Axis, AxisConfig, SeekProbe};
19/// use autocore_std::motion::axis_view::AxisView;
20///
21/// struct MyProgram {
22/// axis: Axis,
23/// seek: SeekProbe,
24/// }
25///
26/// fn process_tick(&mut self, ctx: &mut TickContext<Self::Memory>) {
27/// let mut view = ctx.gm.axis_view();
28/// self.axis.tick(&mut view, ctx.client);
29///
30/// self.seek.call(
31/// &mut self.axis,
32/// &mut view,
33/// ctx.gm.start_seek, // execute: bool
34/// ctx.gm.ball_sensor, // sensor: bool
35/// );
36///
37/// if self.seek.done {
38/// log::info!("Probe found at position {:.3}", self.axis.position);
39/// }
40/// if self.seek.state.is_error() {
41/// log::error!("Seek failed: error_code={}", self.seek.state.error_code);
42/// }
43/// }
44/// ```
45///
46/// # Error Codes
47///
48/// | Code | Meaning |
49/// |------|---------|
50/// | 1 | Panic/abort while motion was active |
51/// | 100 | Axis position is not > 0 at start |
52/// | 120 | Axis error or control disabled during motion |
53/// | 200 | Axis reported error when stopping |
54///
55/// # State Diagram
56///
57/// ```text
58/// ┌──────────┐ execute ↑ ┌────────────┐ position>0 ┌────────────────┐
59/// │ 10: Idle │──────────►│ 100: Start │────────────►│ 120: Jogging │
60/// └──────────┘ └────────────┘ │ (negative) │
61/// ▲ │ pos<=0 └───────┬────────┘
62/// │ ▼ sensor │ │ axis error
63/// │ error_code=100 ▼ ▼
64/// │ ┌────────────────┐
65/// │◄──── done=true ◄─────────────────────────│ 200: Stopping │
66/// │◄──── error=true ◄────────────────────────│ │
67/// │ └────────────────┘
68/// │◄──── error=true ◄── 250: Motion Error
69/// ```
70
71use crate::fb::{RTrig, StateMachine};
72use super::axis::Axis;
73use super::axis_view::AxisView;
74
75/// Seek Probe function block.
76///
77/// Jogs an axis in the negative direction until a sensor triggers,
78/// then halts and reports completion.
79#[derive(Debug, Clone)]
80pub struct SeekProbe {
81 /// Output: operation completed successfully.
82 pub done: bool,
83 /// Output: operation failed — check `state.error_code`.
84 pub error: bool,
85 /// State machine with index, error_code, timers, and messages.
86 pub state: StateMachine,
87
88 rt_execute: RTrig,
89}
90
91impl SeekProbe {
92 /// Create a new SeekProbe in the idle state.
93 pub fn new() -> Self {
94 Self {
95 done: false,
96 error: false,
97 state: StateMachine::new(),
98 rt_execute: RTrig::new(),
99 }
100 }
101
102 /// Execute one scan cycle of the seek-probe state machine.
103 ///
104 /// # Arguments
105 ///
106 /// * `axis` — The axis to jog. Must have been ticked already this cycle.
107 /// * `view` — The axis PDO view for issuing motion commands.
108 /// * `execute` — Rising edge triggers the seek operation.
109 /// * `sensor` — When this goes high during jogging, the axis halts.
110 pub fn call(
111 &mut self,
112 axis: &mut Axis,
113 view: &mut impl AxisView,
114 execute: bool,
115 sensor: bool,
116 ) {
117 let execute_edge = self.rt_execute.call(execute);
118
119 match self.state.index {
120 0 => {
121 // Reset / initialize
122 self.done = false;
123 self.state.index = 10;
124 }
125
126 10 => {
127 // Idle — wait for rising edge of execute
128 if execute_edge {
129 self.state.clear_error();
130 self.done = false;
131 self.error = false;
132 self.state.index = 100;
133 }
134 }
135
136 100 => {
137 // Start motion — verify position > 0, then jog negative
138 if axis.position > 0.0 {
139 let speed = axis.config().jog_speed;
140 let accel = axis.config().jog_accel;
141 let decel = axis.config().jog_decel;
142 // Jog negative: move toward 0 at jog speed.
143 // The sensor trigger will halt before reaching the target.
144 axis.move_relative(view, -axis.position, speed, accel, decel);
145 self.state.index = 120;
146 } else {
147 self.done = false;
148 self.error = true;
149 self.state.set_error(100, "Axis position must be > 0 to start seek");
150 self.state.index = 10;
151 }
152 }
153
154 120 => {
155 // Jogging negative — wait for sensor or error
156 if sensor {
157 axis.halt(view);
158 self.state.index = 200;
159 } else if axis.is_error || !axis.motor_on {
160 self.state.set_error(120, "Axis error or control disabled during seek");
161 self.state.index = 250;
162 }
163 }
164
165 200 => {
166 // Wait for axis to come to a complete stop
167 if !axis.is_busy {
168 if axis.is_error {
169 self.done = false;
170 self.error = true;
171 self.state.set_error(200, "Axis error while stopping");
172 } else {
173 self.done = true;
174 self.error = false;
175 }
176 self.state.index = 10;
177 }
178 }
179
180 250 => {
181 // Motion error during jog — halt and return to idle
182 axis.halt(view);
183 self.done = false;
184 self.error = true;
185 self.state.index = 10;
186 }
187
188 _ => {
189 self.state.index = 0;
190 }
191 }
192
193 self.state.call();
194 }
195
196 /// Abort the current operation immediately.
197 ///
198 /// If motion is active (states > 10), the axis is halted and
199 /// an error is set with code 1.
200 pub fn abort(&mut self, axis: &mut Axis, view: &mut impl AxisView) {
201 if self.state.index > 10 {
202 axis.halt(view);
203 self.error = true;
204 self.state.set_error(1, "Seek aborted");
205 }
206 self.state.index = 10;
207 self.done = false;
208 }
209
210 /// Returns `true` while the seek operation is in progress.
211 pub fn is_busy(&self) -> bool {
212 self.state.index > 10
213 }
214}
215
216impl Default for SeekProbe {
217 fn default() -> Self {
218 Self::new()
219 }
220}