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::StateMachine;
72use super::axis::Axis;
73use super::axis_view::{AxisView, AxisHandle};
74
75/// Blanket implementation to allow old-style (Axis, AxisView) calls.
76impl<'a, V: AxisView> AxisHandle for (&'a mut Axis, &'a mut V) {
77    fn position(&self) -> f64 { self.0.position }
78    fn config(&self) -> &super::axis_config::AxisConfig { self.0.config() }
79    fn move_relative(&mut self, distance: f64, vel: f64, accel: f64, decel: f64) {
80        self.0.move_relative(self.1, distance, vel, accel, decel);
81    }
82    fn move_absolute(&mut self, position: f64, vel: f64, accel: f64, decel: f64) {
83        self.0.move_absolute(self.1, position, vel, accel, decel);
84    }
85    fn halt(&mut self) { self.0.halt(self.1); }
86    fn is_busy(&self) -> bool { self.0.is_busy }
87    fn is_error(&self) -> bool { self.0.is_error }
88    fn error_message(&self) -> String { self.0.error_message.clone() }
89    fn motor_on(&self) -> bool { self.0.motor_on }
90}
91
92/// Seek Probe function block.
93///
94/// Jogs an axis in the negative direction until a sensor triggers,
95/// then halts and reports completion.
96#[derive(Debug, Clone)]
97pub struct SeekProbe {
98    /// Output: operation completed successfully.
99    pub done: bool,
100    /// Output: operation failed — check `state.error_code`.
101    pub error: bool,
102    /// State machine with index, error_code, timers, and messages.
103    pub state: StateMachine,
104}
105
106impl SeekProbe {
107    /// Create a new SeekProbe in the idle state.
108    pub fn new() -> Self {
109        Self {
110            done: false,
111            error: false,
112            state: StateMachine::new(),
113        }
114    }
115
116    /// The FB is busy running.
117    pub fn is_busy(&self) -> bool {
118        self.state.index > 10
119    }
120
121    /// The last requested command resulted in an error.
122    pub fn is_error(&self) -> bool {
123        self.error || self.state.is_error()
124    }
125
126    /// Return the current error code.
127    pub fn error_code(&self) -> i32 {
128        self.state.error_code
129    }
130
131    /// Start the FB processing a seek cycle.
132    pub fn start(&mut self) {
133        self.state.clear_error();
134        self.done = false;
135        self.error = false;
136        self.state.index = 100;
137    }
138
139    /// Stop the FB from processing and send back to idle.
140    /// Safely halts the axis if it was currently moving.
141    pub fn reset(&mut self, handle: &mut impl AxisHandle) {
142        if self.state.index > 10 {
143            handle.halt();
144        }
145        self.done = false;
146        self.error = false;
147        self.state.index = 10;
148    }
149
150    /// Execute one scan cycle of the seek-probe state machine.
151    ///
152    /// # Arguments
153    ///
154    /// * `handle` — The axis handle (e.g. `AxisLift` or `(&mut axis, &mut view)`).
155    /// * `sensor` — When this goes high during jogging, the axis halts.
156    pub fn tick(
157        &mut self,
158        handle: &mut impl AxisHandle,
159        sensor: bool,
160    ) {
161        match self.state.index {
162            0 => {
163                // Reset / initialize
164                self.done = false;
165                self.state.index = 10;
166            }
167
168            10 => {
169                // Idle — wait for start() to be called
170            }
171
172            100 => {
173                // Start motion — verify position > 0, then jog negative
174                if handle.position() > 0.0 {
175                    let speed = handle.config().jog_speed;
176                    let accel = handle.config().jog_accel;
177                    let decel = handle.config().jog_decel;
178                    // Jog negative: move toward 0 at jog speed.
179                    // The sensor trigger will halt before reaching the target.
180                    handle.move_relative(-handle.position(), speed, accel, decel);
181                    self.state.index = 120;
182                } else {
183                    self.done = false;
184                    self.error = true;
185                    self.state.set_error(100, "Axis position must be > 0 to start seek");
186                    self.state.index = 10;
187                }
188            }
189
190            120 => {
191                // Jogging negative — wait for sensor or error
192                if sensor {
193                    handle.halt();
194                    self.state.index = 200;
195                } else if handle.is_error() || !handle.motor_on() {
196                    self.state.set_error(120, "Axis error or control disabled during seek");
197                    self.state.index = 250;
198                }
199            }
200
201            200 => {
202                // Wait for axis to come to a complete stop
203                if !handle.is_busy() {
204                    if handle.is_error() {
205                        self.done = false;
206                        self.error = true;
207                        self.state.set_error(200, "Axis error while stopping");
208                    } else {
209                        self.done = true;
210                        self.error = false;
211                    }
212                    self.state.index = 10;
213                }
214            }
215
216            250 => {
217                // Motion error during jog — halt and return to idle
218                handle.halt();
219                self.done = false;
220                self.error = true;
221                self.state.index = 10;
222            }
223
224            _ => {
225                self.state.index = 0;
226            }
227        }
228
229        self.state.call();
230    }
231
232    /// Abort the current operation immediately.
233    ///
234    /// If motion is active (states > 10), the axis is halted and
235    /// an error is set with code 1.
236    pub fn abort(&mut self, handle: &mut impl AxisHandle) {
237        self.reset(handle);
238        self.error = true;
239        self.state.set_error(1, "Seek aborted");
240    }
241}
242
243impl Default for SeekProbe {
244    fn default() -> Self {
245        Self::new()
246    }
247}