Stop
Stopping the robot’s movement can be useful to react to external events during program execution. When a stop command is sent, the robot decelerates along its current path until it comes to a complete stop. All active and pending instructions are aborted. In contrast to a pause, new instructions cannot be registered until the stop signal is revoked. A stop overrides a pause: after the stop signal is reset, the robot is ready to move again, even if it was paused before.
Stopping the Robot’s Movement
To stop the robot, call stop_motion() from the Stop trait. It blocks until the robot has come to a stop,
bounded by the robot’s command timeout, and returns RobotArmError::DidNotStop
if the robot fails to settle in time. It can be called either at standstill or while the robot is moving:
robot.move_ptp(poses::home()).start()?.join()?;
robot.move_ptp(poses::vertical()).start()?;
let move_to_zero = robot.move_ptp(JointPose::default()).start()?;
// Stop all motion. Blocks until the robot has settled, bounded by the
// robot's command timeout.
info!("Stopping motion...");
robot.stop_motion()?;
info!("Robot stopped.");
Note that the motions to the vertical and zero poses are started without join(), which allows a stop during the
movement.
After stop_motion(), the registration of new motion instructions is blocked and all active and pending
instructions are aborted. A new instruction fails at start() with NotAllowed. A pending instruction resolves
with StopTriggered on its handle:
match robot.move_ptp(poses::home()).start() {
Err(RobotArmError::NotAllowed(_)) => info!("Expected: robot is stopped."),
Err(e) => info!("Unexpected error: {e}"),
Ok(_) => info!("Instruction accepted (unexpected)."),
}
// Pending handles resolve with StopTriggered.
match move_to_zero.join() {
Err(RobotArmError::StopTriggered(_)) => info!("Pending instruction aborted."),
Err(e) => info!("Unexpected error: {e}"),
Ok(()) => info!("Instruction completed (unexpected)."),
}
Before sending new motion instructions, unstop_motion() must be called. The robot stays at standstill until new
instructions arrive. If a stop is still in progress, unstop_motion() first waits until the robot control has
completed it, bounded by the command timeout (DidNotStop otherwise), because the control drops a motion it receives
during a stop. At standstill it returns at once:
robot.unstop_motion()?;
info!("Robot unstopped, ready for new instructions.");
// Now we can move again.
robot.move_ptp(JointPose::default()).start()?.join()?;
info!("Moved to zero after unstop.");
Stopping from Another Thread
stop_motion() takes &self and the robots are Sync, so a watchdog thread can stop the robot while another
thread is blocked waiting on a motion. The waiting thread sees the stop as StopTriggered as soon as the stop is
raised, while the watchdog’s stop_motion() still waits for the robot to come to a stop. The waiting thread does not
have to wait for the watchdog before it calls unstop_motion(), which waits for the stop in progress itself:
fn stop_from_watchdog(robot: &(impl MovePtp + Stop + Sync)) -> Result<()> {
info!("--- Stop from a watchdog thread ---");
scope(|scope| -> Result<()> {
scope.spawn(|| {
sleep(Duration::from_millis(100));
if let Err(e) = robot.stop_motion() {
info!("Watchdog could not stop: {e}");
}
});
match robot
.move_ptp(poses::vertical())
.move_ptp(JointPose::default())
.start()?
.join()
{
Err(RobotArmError::StopTriggered(_)) => {
info!("Block was stopped by the watchdog.");
}
other => other?,
}
Ok(())
})?;
// The watchdog may have stopped the robot after the block had already
// finished; either way the stop has to be reset before moving on.
// `unstop_motion` waits for a stop that is still in progress.
robot.unstop_motion()?;
robot.move_ptp(poses::home()).start()?.join()?;
Ok(())
}
Configuring the Stop Behavior
How the robot decelerates, and how quickly, can be configured per stop source with the SetStopBehavior trait. See
Stop Behavior.
Full Example
Stop example
//! Stop and unstop motion example.
//!
//! Demonstrates how to stop robot motion during execution, handle the
//! resulting errors, and resume with `unstop_motion`.
mod common;
use std::thread::scope;
use std::thread::sleep;
use std::time::Duration;
use common::poses;
use tracing::info;
use voraus_robot_arm::prelude::*;
/// Demonstrates stopping and unstopping robot motion.
///
/// After `stop_motion()`, the robot cannot accept new instructions until
/// `unstop_motion()` is called. Any pending handles resolve with
/// `StopTriggered`.
fn stop_example(robot: &(impl MovePtp + Stop)) -> Result<()> {
info!("--- Stop motion example ---");
// Start moving.
robot.move_ptp(poses::home()).start()?.join()?;
robot.move_ptp(poses::vertical()).start()?;
let move_to_zero = robot.move_ptp(JointPose::default()).start()?;
// Stop all motion. Blocks until the robot has settled, bounded by the
// robot's command timeout.
info!("Stopping motion...");
robot.stop_motion()?;
info!("Robot stopped.");
// New instructions are rejected while stopped; `start()` fails at once.
match robot.move_ptp(poses::home()).start() {
Err(RobotArmError::NotAllowed(_)) => info!("Expected: robot is stopped."),
Err(e) => info!("Unexpected error: {e}"),
Ok(_) => info!("Instruction accepted (unexpected)."),
}
// Pending handles resolve with StopTriggered.
match move_to_zero.join() {
Err(RobotArmError::StopTriggered(_)) => info!("Pending instruction aborted."),
Err(e) => info!("Unexpected error: {e}"),
Ok(()) => info!("Instruction completed (unexpected)."),
}
// Unstop to allow new instructions.
robot.unstop_motion()?;
info!("Robot unstopped, ready for new instructions.");
// Now we can move again.
robot.move_ptp(JointPose::default()).start()?.join()?;
info!("Moved to zero after unstop.");
Ok(())
}
/// Stop from another thread while the main thread is blocked in `run`.
///
/// Commands take `&self` and the robot is `Sync`, so a watchdog thread can
/// stop a block that another thread is waiting on.
fn stop_from_watchdog(robot: &(impl MovePtp + Stop + Sync)) -> Result<()> {
info!("--- Stop from a watchdog thread ---");
scope(|scope| -> Result<()> {
scope.spawn(|| {
sleep(Duration::from_millis(100));
if let Err(e) = robot.stop_motion() {
info!("Watchdog could not stop: {e}");
}
});
match robot
.move_ptp(poses::vertical())
.move_ptp(JointPose::default())
.start()?
.join()
{
Err(RobotArmError::StopTriggered(_)) => {
info!("Block was stopped by the watchdog.");
}
other => other?,
}
Ok(())
})?;
// The watchdog may have stopped the robot after the block had already
// finished; either way the stop has to be reset before moving on.
// `unstop_motion` waits for a stop that is still in progress.
robot.unstop_motion()?;
robot.move_ptp(poses::home()).start()?.join()?;
Ok(())
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
stop_example(&robot)?;
stop_from_watchdog(&robot)?;
info!("Done.");
Ok(())
}
Trait Definition
Stop
pub trait Stop {
/// Stop robot motion.
///
/// The robot decelerates on the planned path until standstill. All
/// pending instructions are aborted and their handles resolve with
/// [`RobotArmError::StopTriggered`](crate::error::RobotArmError::StopTriggered);
/// new instructions are rejected with
/// [`RobotArmError::NotAllowed`](crate::error::RobotArmError::NotAllowed)
/// until [`unstop_motion`](Self::unstop_motion) is called. A stop
/// overrides a pause.
///
/// Blocks until the robot has settled, bounded by the command timeout.
///
/// # Returns
///
/// `Ok(())` on success.
///
/// # Errors
///
/// Returns [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop)
/// if the robot does not settle in time, or the error the stop command
/// failed with. The stop state is in force after either error: pending
/// handles have already resolved with `StopTriggered`, and new motions
/// need [`unstop_motion`](Self::unstop_motion) first. If the stop
/// command itself failed, the robot may still be moving.
fn stop_motion(&self) -> Result<()> {
self.send_stop_motion()?.confirm()
}
/// Send the stop and return before the robot has settled.
///
/// The stop takes effect as in [`stop_motion`](Self::stop_motion): the
/// pending instructions are aborted and new ones are rejected at once.
/// Call [`confirm`](CommandHandle::confirm) on the returned handle to
/// wait until the robot has settled.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl Stop) -> Result<()> {
/// let stop = robot.send_stop_motion()?;
/// // ... other work while the robot decelerates ...
/// stop.confirm()?;
/// # Ok(())
/// # }
/// ```
///
/// # Returns
///
/// The handle that confirms the stop.
/// [`CommandHandle::confirm`] returns
/// [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop)
/// if the robot does not settle in time.
///
/// # Errors
///
/// Returns the error the stop command failed with. The stop state is in
/// force after it, and the robot may still be moving.
fn send_stop_motion(&self) -> Result<CommandHandle<'_>>;
/// Reset the stop state, re-arming instruction acceptance.
///
/// The robot stays at standstill until new instructions arrive. A stop
/// still in progress, for example one called from another thread, is
/// waited for first, bounded by the command timeout: the robot control
/// drops a motion it receives before it has completed a stop. At
/// standstill this returns at once.
///
/// # Returns
///
/// `Ok(())` on success.
///
/// # Errors
///
/// Returns [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop)
/// if a stop in progress does not complete in time; the stop state
/// stays in force. Returns another
/// [`RobotArmError`](crate::error::RobotArmError) if the state cannot be
/// read or cleared.
fn unstop_motion(&self) -> Result<()>;
}