Limits
Limits are used to configure motion and monitoring constraints of the robot such as axis velocities, axis
accelerations, TCP velocity, and many more. The robot control provides default limit sets like MACHINE and
SAFETY. Users can define additional limit sets on the robot control to constrain the robot behavior for specific
applications.
Note
It is expected that limit sets are preconfigured on the robot control. Creating or modifying limit sets is currently not supported by this library.
Reading the Currently Active Limits
The currently active limits are read with active_limits() from the GetLimits trait. It returns a
Limits value with all currently active limit values, reflecting the combined effect of all active limit
sets:
let active = robot.active_limits()?;
info!("Active limits: {active:?}");
Every field of Limits is an Option: None means the limit is not configured. The axis limits are vectors
with one value per axis in the axis’s SI unit, which depends on the axis type: for example rad/s for rotational and m/s
for prismatic axes in axes_velocity, N·m in the torque limits (see the field docs of Limits). The scalar
limits carry their unit, for example tcp_velocity_translational as MetersPerSecond.
Reading the Limits of a Limit Set
The limits of a specific limit set are read with limits(). It returns a Limits value with only the
limits that set configures; the others are None:
let limits = robot.limits(limit_set)?;
info!("Axes acceleration limits: {:?}", limits.axes_acceleration);
Selecting Limit Sets
Listing the limit sets is part of the GetLimitSets trait, which a read-only view implements as well. Activating and
deactivating them is part of the SelectLimitSets trait, which extends it.
Listing Limit Sets
default_limit_sets() returns the names of the default limit sets. They are always active and cannot be
deactivated. optional_limit_sets() returns the
user-configurable limit sets; the default sets are not included. active_limit_sets() returns all currently
active sets, the default ones as well as any activated optional ones:
info!("Default limit sets: {:?}", robot.default_limit_sets()?);
info!("Optional limit sets: {:?}", robot.optional_limit_sets()?);
info!("Active limit sets: {:?}", robot.active_limit_sets()?);
Activating and Deactivating a Limit Set
An optional limit set is activated with activate_limit_set() and deactivated with
deactivate_limit_set(). The set must exist on the robot control, default limit sets cannot be switched, and no
set can be switched while the robot is in gravity compensation or in its collision reaction. These three failures are
reported as LimitSetFailed, with LimitSetError::Unknown, LimitSetError::DefaultSet or
LimitSetError::RobotMoving. The robot control switches a set only while the robot stands still, so both calls first
wait until the robot has come to rest, bounded by the robot’s command timeout, and
return RobotArmError::DidNotStop if it does not; join pending motions first. A switch the robot control refuses
returns the robot control’s error as RobotArmError::RobotInErrorState, and RobotArmError::NotConfirmed means that
the robot control did not confirm the switch within the command timeout:
robot.activate_limit_set(limit_set)?;
info!(
"Active after activation: {:?}",
robot.active_limit_sets()?
);
robot.deactivate_limit_set(limit_set)?;
Limit Sets for a Section
A limit set that should constrain one section of the program is expressed with with_limit_set(). It
activates the set, runs the closure, and deactivates the set afterwards, also when the closure returns an error.
The closure’s result is returned; if the set could not be deactivated after a successful closure, that error is
returned instead. Deactivating needs the robot to be idle, so join every motion the closure starts before it returns:
if the closure fails while one of its motions is still executing, the deactivation is refused, the refusal is only
logged, the closure’s error is returned, and the set stays active:
fn scoped_limit_set_example(
robot: &(impl SelectLimitSets + MovePtp),
limit_set: &str,
) -> Result<()> {
info!("--- Scoped limit set ---");
robot.with_limit_set(limit_set, |robot| {
robot
.move_ptp(poses::home())
.move_ptp(poses::vertical())
.start()?
.join()?;
info!("Moved under {limit_set}.");
Ok(())
})?;
info!("Active after the section: {:?}", robot.active_limit_sets()?);
Ok(())
}
The section restores the state it found rather than switching the set off unconditionally: if the set was already active when the section started, for example because an enclosing section activated it, the section does not switch it at all and it stays active afterwards.
Full Example
The example picks the first optional limit set the control offers and skips the limit set sections if there is none.
Limits example
//! Limits examples.
//!
//! Demonstrates reading active limits, reading a limit set, and activating
//! optional limit sets, including a limit set for one section.
mod common;
use common::poses;
use tracing::info;
use voraus_robot_arm::prelude::*;
// The optional limit set used below is picked from what the control has
// configured; a control without optional limit sets skips those sections.
/// Read the limits that are currently in effect.
///
/// The values reflect the combined effect of all active limit sets.
fn active_limits_example(robot: &impl GetLimits) -> Result<()> {
info!("--- Active limits ---");
let active = robot.active_limits()?;
info!("Active limits: {active:?}");
Ok(())
}
/// Read the limits configured in one specific limit set.
fn limits_of_a_set_example(robot: &impl GetLimits, limit_set: &str) -> Result<()> {
info!("--- Limits of a limit set ---");
let limits = robot.limits(limit_set)?;
info!("Axes acceleration limits: {:?}", limits.axes_acceleration);
Ok(())
}
/// Activate and deactivate an optional limit set by hand.
fn select_limit_set_example(robot: &impl SelectLimitSets, limit_set: &str) -> Result<()> {
info!("--- Selecting limit sets ---");
// Default limit sets are constants: always active, never deactivatable.
info!("Default limit sets: {:?}", robot.default_limit_sets()?);
info!("Optional limit sets: {:?}", robot.optional_limit_sets()?);
info!("Active limit sets: {:?}", robot.active_limit_sets()?);
robot.activate_limit_set(limit_set)?;
info!(
"Active after activation: {:?}",
robot.active_limit_sets()?
);
robot.deactivate_limit_set(limit_set)?;
info!(
"Active after deactivation: {:?}",
robot.active_limit_sets()?
);
Ok(())
}
/// Constrain one section of the application with a limit set.
///
/// The set is activated before the closure and deactivated after it, also
/// when the closure fails.
fn scoped_limit_set_example(
robot: &(impl SelectLimitSets + MovePtp),
limit_set: &str,
) -> Result<()> {
info!("--- Scoped limit set ---");
robot.with_limit_set(limit_set, |robot| {
robot
.move_ptp(poses::home())
.move_ptp(poses::vertical())
.start()?
.join()?;
info!("Moved under {limit_set}.");
Ok(())
})?;
info!("Active after the section: {:?}", robot.active_limit_sets()?);
Ok(())
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
active_limits_example(&robot)?;
match robot.optional_limit_sets()?.first() {
Some(limit_set) => {
limits_of_a_set_example(&robot, limit_set)?;
select_limit_set_example(&robot, limit_set)?;
scoped_limit_set_example(&robot, limit_set)?;
}
None => {
info!("No optional limit sets configured; skipping the limit set sections.");
}
}
info!("Done.");
Ok(())
}
Trait Definitions
GetLimits
pub trait GetLimits {
/// Get the currently active limits.
///
/// The values reflect the combined effect of all active limit sets;
/// unconfigured limits are `None`.
///
/// # Returns
///
/// All currently active limit values.
///
/// # Errors
///
/// Returns [`RobotArmError::Backend`](crate::error::RobotArmError::Backend)
/// with [`BackendError::NotConnected`](crate::error::BackendError::NotConnected)
/// if the robot is not connected, or with the backend error if the read
/// from the robot control fails.
fn active_limits(&self) -> Result<Limits>;
/// Get the limits for a specific limit set.
///
/// Limits that are not actively monitored in the set are `None`.
///
/// # Arguments
///
/// * `limit_set` - The name of a limit set configured on the robot control.
///
/// # Returns
///
/// The limits of the given set.
///
/// # Errors
///
/// Returns [`RobotArmError::LimitSetFailed`](crate::error::RobotArmError::LimitSetFailed)
/// with [`LimitSetError::Unknown`](crate::error::LimitSetError::Unknown) if the limit set does not
/// exist on the robot control,
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) with
/// [`BackendError::NotConnected`](crate::error::BackendError::NotConnected)
/// if the robot is not connected, or with the backend error if a read from
/// the robot control fails.
fn limits(&self, limit_set: &str) -> Result<Limits>;
}
GetLimitSets
pub trait GetLimitSets {
/// The names of the default limit sets, which are always active.
///
/// # Returns
///
/// The names of the default limit sets for this robot type.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the sets
/// cannot be read.
fn default_limit_sets(&self) -> Result<Vec<String>>;
/// The names of the optional limit sets that can be activated and
/// deactivated.
///
/// # Returns
///
/// The names of all optional limit sets configured on the control.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the sets
/// cannot be read.
fn optional_limit_sets(&self) -> Result<Vec<String>>;
/// The names of all currently active limit sets, default and optional.
///
/// # Returns
///
/// The names of the limit sets that are active right now.
///
/// # Errors
///
/// Returns [`RobotArmError`](crate::error::RobotArmError) if the sets
/// cannot be read.
fn active_limit_sets(&self) -> Result<Vec<String>>;
}
SelectLimitSets
pub trait SelectLimitSets: GetLimitSets {
/// Activate a limit set.
///
/// The call waits until the robot stands still, bounded by the command
/// timeout, because the control refuses the change while the robot moves.
///
/// # Arguments
///
/// * `limit_set` - The name of the limit set to activate.
///
/// # Returns
///
/// `Ok(())` on success.
///
/// # Errors
///
/// Returns [`RobotArmError::LimitSetFailed`](crate::error::RobotArmError::LimitSetFailed)
/// before anything is written if the set does not exist
/// ([`LimitSetError::Unknown`](crate::error::LimitSetError::Unknown)), is
/// a default set
/// ([`LimitSetError::DefaultSet`](crate::error::LimitSetError::DefaultSet)),
/// or the robot is in gravity compensation or in its collision reaction,
/// also while the call waits for standstill
/// ([`LimitSetError::RobotMoving`](crate::error::LimitSetError::RobotMoving)),
/// [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop)
/// if the robot does not come to rest within the command
/// timeout (join pending motions first),
/// [`RobotArmError::RobotInErrorState`](crate::error::RobotArmError::RobotInErrorState)
/// with the control's error if the control refuses the write,
/// [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not confirm the activation within the command
/// timeout, or the error the write failed with.
fn activate_limit_set(&self, limit_set: &str) -> Result<()> {
self.send_activate_limit_set(limit_set)?.confirm()
}
/// Send the activation of a limit set.
///
/// The call returns before the control confirms the activation. Call
/// [`confirm`](CommandHandle::confirm) on the returned handle to wait
/// for it.
///
/// The checks and the wait for standstill happen before the call
/// returns.
///
/// # Arguments
///
/// * `limit_set` - The name of the limit set to activate.
///
/// # Returns
///
/// The handle that confirms the activation. [`CommandHandle::confirm`]
/// returns
/// [`RobotArmError::RobotInErrorState`](crate::error::RobotArmError::RobotInErrorState)
/// with the control's error if the control refuses the write, or
/// [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not confirm the activation within the command
/// timeout.
///
/// # Errors
///
/// Returns [`RobotArmError::LimitSetFailed`](crate::error::RobotArmError::LimitSetFailed)
/// before anything is written, as for
/// [`activate_limit_set`](Self::activate_limit_set),
/// [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop)
/// if the robot does not come to rest within the command timeout, or the
/// error the write failed with.
fn send_activate_limit_set(&self, limit_set: &str) -> Result<CommandHandle<'_>>;
/// Deactivate an optional limit set.
///
/// The call waits until the robot stands still, bounded by the command
/// timeout, because the control refuses the change while the robot moves.
///
/// # Arguments
///
/// * `limit_set` - The name of the optional limit set to deactivate.
///
/// # Returns
///
/// `Ok(())` on success.
///
/// # Errors
///
/// Returns [`RobotArmError::LimitSetFailed`](crate::error::RobotArmError::LimitSetFailed)
/// before anything is written if the set does not exist
/// ([`LimitSetError::Unknown`](crate::error::LimitSetError::Unknown)), is
/// a default set
/// ([`LimitSetError::DefaultSet`](crate::error::LimitSetError::DefaultSet)),
/// or the robot is in gravity compensation or in its collision reaction,
/// also while the call waits for standstill
/// ([`LimitSetError::RobotMoving`](crate::error::LimitSetError::RobotMoving)),
/// [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop)
/// if the robot does not come to rest within the command
/// timeout (join pending motions first),
/// [`RobotArmError::RobotInErrorState`](crate::error::RobotArmError::RobotInErrorState)
/// with the control's error if the control refuses the write,
/// [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not confirm the deactivation within the command
/// timeout, or the error the write failed with.
fn deactivate_limit_set(&self, limit_set: &str) -> Result<()> {
self.send_deactivate_limit_set(limit_set)?.confirm()
}
/// Send the deactivation of a limit set.
///
/// The call returns before the control confirms the deactivation. Call
/// [`confirm`](CommandHandle::confirm) on the returned handle to wait
/// for it.
///
/// The checks and the wait for standstill happen before the call
/// returns.
///
/// # Arguments
///
/// * `limit_set` - The name of the optional limit set to deactivate.
///
/// # Returns
///
/// The handle that confirms the deactivation. [`CommandHandle::confirm`]
/// returns
/// [`RobotArmError::RobotInErrorState`](crate::error::RobotArmError::RobotInErrorState)
/// with the control's error if the control refuses the write, or
/// [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not confirm the deactivation within the command
/// timeout.
///
/// # Errors
///
/// Returns [`RobotArmError::LimitSetFailed`](crate::error::RobotArmError::LimitSetFailed)
/// before anything is written, as for
/// [`deactivate_limit_set`](Self::deactivate_limit_set),
/// [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop)
/// if the robot does not come to rest within the command timeout, or the
/// error the write failed with.
fn send_deactivate_limit_set(&self, limit_set: &str) -> Result<CommandHandle<'_>>;
/// Run `body` with a limit set active, then deactivate the set.
///
/// The set is deactivated after `body` returns, whether `body` succeeded
/// or failed. Deactivating needs the robot to be idle, so join the
/// motions `body` starts before it returns: if a failed `body` leaves a
/// motion executing, the deactivation is refused, the refusal is only
/// logged, and the set stays active.
///
/// A set that is already active when the section starts is not switched
/// at all and stays active afterwards: the section restores the state it
/// found rather than unconditionally deactivating, so a nested section
/// cannot switch off a set the surrounding code relies on. This is a
/// snapshot, not a reference count: sections on the same set that run
/// concurrently on different threads are not coordinated, and the one
/// that activated the set deactivates it when its `body` ends, even while
/// the other is still running. A panic in `body` restores too and then
/// continues to unwind.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &(impl SelectLimitSets + MoveLinear), target: CartesianPose) -> Result<()> {
/// robot.with_limit_set("NearHuman", |robot| {
/// robot.move_linear(target, MetersPerSecond(0.05)).start()?.join()
/// })?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `limit_set` - The name of the limit set to activate while `body` runs.
/// * `body` - The section of the program that runs under the set.
///
/// # Returns
///
/// The value `body` returned.
///
/// # Errors
///
/// Returns the error of reading the active sets; nothing is switched in
/// this case. Returns the error of activating the set, see
/// [`SelectLimitSets::activate_limit_set`]; `body` does not
/// run, but the set is deactivated. Otherwise the error of `body`, or the
/// error of deactivating the set once `body` succeeded. A deactivation
/// failure after a failed activation or a failed `body` is logged.
fn with_limit_set<T>(&self, limit_set: &str, body: impl FnOnce(&Self) -> Result<T>) -> Result<T>
where
Self: Sized,
{
let was_active = self
.active_limit_sets()?
.iter()
.any(|active| active == limit_set);
// The activation runs inside the guarded section: a failed
// confirmation can follow a write the control already applied, so
// the set is deactivated whenever the activation fails too.
restore_after(
|| {
if !was_active {
self.activate_limit_set(limit_set)?;
}
body(self)
},
|| {
if was_active {
Ok(())
} else {
self.deactivate_limit_set(limit_set)
}
},
format_args!("deactivate the limit set '{limit_set}'"),
)
}
}