Skip to main content

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}