Reading the Robot State
The current robot state is defined by various metrics: the main state of the robot control, the joint pose, the tool center point (TCP) pose, and the velocities and accelerations. Each metric is its own trait, so a function can require exactly the readings it needs.
Note
Every robot and view implements GetRobotState, GetJointPose, GetTcpPose, GetJointVelocities,
GetJointAccelerations and GetTcpVelocity.
Every reader can be called from any thread at any time, also while a motion runs, for example through an Arc of the
robot. Each call is a separate blocking read from the robot control, bounded by the request timeout of the connection
(see the lifecycle page), so values from two readers are not sampled at the same instant.
Reading the Main State of the Robot
The main state of the robot control is one of the states of the RobotState enum: Init, Standby, Ready,
Stopping, Error, Active, Reset, GravComp, Shutdown, and CollisionReaction; the
lifecycle page describes them. The state is read with robot_state() from the
GetRobotState trait, which every robot and every view implements. is_enabled() tells whether the regulators are
on, and accepts_motions() whether the robot control takes motion instructions:
let state = robot.robot_state()?;
info!("Robot state: {state:?}");
info!("Regulators on: {}", state.is_enabled());
info!("Accepts motions: {}", state.accepts_motions());
Active means that the interpolator produces set values, not that the robot moves. The robot control leaves Active
once all set values of a motion are calculated, while the axes may still decelerate, and it reports Ready for a
moment between two motions of a block. Whether one instruction is the one the robot control executes is a question
for its handle, InstructionHandle::is_active(), see instructions.
Waiting for Standstill
Whether the robot has come to rest is answered by is_in_standstill(), and wait_for_standstill(timeout) waits
for it; wait_for_motion(timeout) waits for the opposite, the axes in motion, which they are a little after
the control takes an instruction. The robot stands still when the main state is not Active, no motion started
through this robot is still queued or running, and the robot control reports the axes at rest. The motions of a
block count as running from the start until the last one has completed, so the wait does not end before the first
one moves or in the short Ready
between two of them:
fn wait_for_standstill(robot: &(impl MovePtp + GetRobotState)) -> Result<()> {
info!("--- Waiting for motion and standstill ---");
let _home = robot.move_ptp(poses::home()).start()?;
info!("In standstill: {}", robot.is_in_standstill()?); // false
robot.wait_for_motion(Duration::from_secs(3))?;
// Returns once the motion is done and the axes are at rest.
robot.wait_for_standstill(Duration::from_secs(20))?;
info!("In standstill: {}", robot.is_in_standstill()?); // true
Ok(())
}
The timeout is an argument rather than the command timeout, since a robot that runs a long program may take longer
to come to rest. If the robot does not come to rest in time, the wait returns RobotArmError::DidNotStop;
wait_for_motion() returns RobotArmError::Timeout if the axes still rest when its timeout has passed. A
read-only view sends no motions, so it does not know the motions another client queued: its wait_for_standstill()
can return in the short Ready between two of them.
Reading the Joint Pose
The joint pose is read with joint_pose() from the GetJointPose trait. It returns the most recent joint pose
reported by the robot control, in radians for rotary axes and meters for linear axes:
let joint_pose = robot.joint_pose()?;
info!("Current joint pose (rad): {joint_pose:.3}");
The robot control reports the values. Depending on the robot type, it measures them or calculates them.
Reading the Joint Velocities
The joint velocities are read with joint_velocities() from the GetJointVelocities trait. It returns the most
recent joint velocities reported by the robot control as a JointVelocities value, in radians per second for
rotary axes and meters per second for linear axes:
let joint_velocities = robot.joint_velocities()?;
info!("Current joint velocities (rad/s): {joint_velocities:.3}");
The robot control reports the values. Depending on the robot type, it measures them or calculates them.
Reading the Joint Accelerations
The joint accelerations are read with joint_accelerations() from the GetJointAccelerations trait. It returns
the most recent joint accelerations reported by the robot control, in radians per second squared for rotary axes
and meters per second squared for linear axes, as a
JointAccelerations value:
let joint_accelerations = robot.joint_accelerations()?;
info!("Current joint accelerations (rad/s²): {joint_accelerations:.3}");
The robot control reports the values. Depending on the robot type, it measures them or calculates them.
Reading the TCP Pose
The TCP pose is read with tcp_pose() from the GetTcpPose trait. It returns the current TCP pose reported by the
robot control in meters for the translational part and in radians for the rotational part:
let tcp_pose = robot.tcp_pose()?;
info!("Current TCP pose: {tcp_pose:.3}");
The pose is the one the robot control reports, in the robot coordinate system, the frame of a plain CartesianPose
motion target, and not in a user coordinate system a motion target may name. It is the pose of the TCP of the selected
tool, or of the flange when no tool is selected.
Reading the TCP Velocity
The TCP velocity is read with tcp_velocity() from the GetTcpVelocity trait. It returns the current TCP velocity
in meters per second for the translational part and in radians per second for the rotational part as a
CartesianVelocity value:
let tcp_velocity = robot.tcp_velocity()?;
info!("Current TCP velocity: {tcp_velocity:.3}");
Like the TCP pose, the velocity is the one the robot control reports, in the robot coordinate system, for the TCP of the selected tool, or for the flange when no tool is selected.
Working with the Values
Velocities and accelerations are distinct types, so that the unit is visible at the call site and you can implement
your own traits on them. They hold one value per axis and can be read by Joint with get(), iterated, or viewed
as a slice:
let fastest = joint_velocities
.iter()
.fold(0.0_f64, |acc, v| acc.max(v.abs()));
info!("Fastest joint: {fastest:.3} rad/s");
Velocities and accelerations are only non-zero while the robot moves. To observe them, start a motion without waiting for it, read the state, and join the handle afterwards:
fn read_velocities_and_accelerations(
robot: &(impl MovePtp + GetJointVelocities + GetJointAccelerations + GetTcpVelocity),
) -> Result<()> {
info!("--- Reading velocities and accelerations during motion ---");
// Start a motion (non-blocking).
let vertical = robot.move_ptp(poses::vertical()).start()?;
// Wait briefly so the robot is actually moving.
sleep(Duration::from_millis(50));
// Read joint velocities (rad/s for each axis).
let joint_velocities = robot.joint_velocities()?;
info!("Current joint velocities (rad/s): {joint_velocities:.3}");
// The aggregates iterate over one value per axis.
let fastest = joint_velocities
.iter()
.fold(0.0_f64, |acc, v| acc.max(v.abs()));
info!("Fastest joint: {fastest:.3} rad/s");
// Read TCP velocity (translational m/s + rotational rad/s).
let tcp_velocity = robot.tcp_velocity()?;
info!("Current TCP velocity: {tcp_velocity:.3}");
// Read joint accelerations (rad/s² for each axis).
let joint_accelerations = robot.joint_accelerations()?;
info!("Current joint accelerations (rad/s²): {joint_accelerations:.3}");
// Wait for the motion to finish.
vertical.join()?;
info!("Motion complete.");
Ok(())
}
Read-only View
A program that only observes a robot, for example to monitor or visualize it while another program commands it, connects a view instead of a robot. Every robot type has its view, which implements every trait of its robot that only reads and has every getter of its robot. A view never moves the robot, never enables or disables it and writes no configuration. Connecting a view leaves the robot in its current state, so the view can join a robot that is already moving. The example connects a view:
let mut view = VorausIndustrialRobotArmView::new();
view.connect(&host, port)?;
// Read what another client may be changing meanwhile.
info!("Robot state: {:?}", view.robot_state()?);
info!("Joint pose: {}", view.joint_pose()?);
info!("TCP pose: {}", view.tcp_pose()?);
// Disconnect; dropping the view would disconnect it as well.
view.disconnect()?;
A view has no lifecycle beyond connect(), disconnect() and is_connected(), and dropping a connected view
disconnects it. Since
it implements the same reading traits as its robot, a function bound only by them accepts a view and a robot alike.
The getters of a trait that also writes live in a Get* trait of their own, which the trait extends and the view
implements: GetTimeOverride, GetTool, GetCsOrigin, GetLimitSets, GetStopBehavior
and GetPayload. A function that only reads the time override is bound by GetTimeOverride and accepts a
view; one bound by SetTimeOverride accepts only a robot.
Full Example
Reading state example
//! Reading robot state: joint pose, TCP pose, robot state, standstill and motion, velocities and accelerations.
//!
//! Demonstrates how to query the robot's current position, velocity, and
//! acceleration during and after motion.
mod common;
use std::thread::sleep;
use std::time::Duration;
use common::poses;
use tracing::info;
use voraus_robot_arm::prelude::*;
/// Read joint and TCP poses while the robot is stationary.
fn read_poses(robot: &(impl GetJointPose + GetTcpPose + MovePtp)) -> Result<()> {
info!("--- Reading poses ---");
// Move to a known position first.
robot.move_ptp(poses::home()).start()?.join()?;
// Read the current joint pose.
let joint_pose = robot.joint_pose()?;
info!("Current joint pose (rad): {joint_pose:.3}");
// Read the current TCP (tool center point) pose.
let tcp_pose = robot.tcp_pose()?;
info!("Current TCP pose: {tcp_pose:.3}");
Ok(())
}
/// Read the main state of the robot control.
fn read_state(robot: &impl GetRobotState) -> Result<()> {
info!("--- Reading robot state ---");
let state = robot.robot_state()?;
info!("Robot state: {state:?}");
info!("Regulators on: {}", state.is_enabled());
info!("Accepts motions: {}", state.accepts_motions());
Ok(())
}
/// Start a motion, wait until the axes move, then until the robot has
/// come to rest.
///
/// A handle follows the interpolator of the robot control. The axes start a
/// little later and can still decelerate after `join` returns.
fn wait_for_standstill(robot: &(impl MovePtp + GetRobotState)) -> Result<()> {
info!("--- Waiting for motion and standstill ---");
let _home = robot.move_ptp(poses::home()).start()?;
info!("In standstill: {}", robot.is_in_standstill()?); // false
robot.wait_for_motion(Duration::from_secs(3))?;
// Returns once the motion is done and the axes are at rest.
robot.wait_for_standstill(Duration::from_secs(20))?;
info!("In standstill: {}", robot.is_in_standstill()?); // true
Ok(())
}
/// Read velocities and accelerations while the robot is in motion.
///
/// Starts a motion, waits briefly for the robot to be moving, then
/// reads joint velocities, TCP velocity, and joint accelerations.
fn read_velocities_and_accelerations(
robot: &(impl MovePtp + GetJointVelocities + GetJointAccelerations + GetTcpVelocity),
) -> Result<()> {
info!("--- Reading velocities and accelerations during motion ---");
// Start a motion (non-blocking).
let vertical = robot.move_ptp(poses::vertical()).start()?;
// Wait briefly so the robot is actually moving.
sleep(Duration::from_millis(50));
// Read joint velocities (rad/s for each axis).
let joint_velocities = robot.joint_velocities()?;
info!("Current joint velocities (rad/s): {joint_velocities:.3}");
// The aggregates iterate over one value per axis.
let fastest = joint_velocities
.iter()
.fold(0.0_f64, |acc, v| acc.max(v.abs()));
info!("Fastest joint: {fastest:.3} rad/s");
// Read TCP velocity (translational m/s + rotational rad/s).
let tcp_velocity = robot.tcp_velocity()?;
info!("Current TCP velocity: {tcp_velocity:.3}");
// Read joint accelerations (rad/s² for each axis).
let joint_accelerations = robot.joint_accelerations()?;
info!("Current joint accelerations (rad/s²): {joint_accelerations:.3}");
// Wait for the motion to finish.
vertical.join()?;
info!("Motion complete.");
Ok(())
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
read_poses(&robot)?;
read_state(&robot)?;
read_velocities_and_accelerations(&robot)?;
wait_for_standstill(&robot)?;
info!("Done.");
Ok(())
}
Trait Definitions
Each reading is its own trait; all but GetRobotState have a single method. Lifecycle is shown in the
lifecycle chapter.
GetRobotState
pub trait GetRobotState {
/// Get the current main state of the robot control.
///
/// # Returns
///
/// The current [`RobotState`].
///
/// # Errors
///
/// Returns [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the
/// robot is not connected,
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the read fails and
/// [`RobotArmError::InvalidValue`](crate::error::RobotArmError::InvalidValue) if the control
/// reports a state this crate does not know.
fn robot_state(&self) -> Result<RobotState>;
/// Whether the robot stands still.
///
/// The robot stands still when the main state is not
/// [`Active`](RobotState::Active), no motion started through this
/// handle is still queued or running, and the robot control reports
/// the axes at rest.
///
/// # Returns
///
/// `true` if the robot stands still.
///
/// # Errors
///
/// Returns [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the
/// robot is not connected, or
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if a read fails.
fn is_in_standstill(&self) -> Result<bool>;
/// Wait until the robot stands still, as
/// [`is_in_standstill`](Self::is_in_standstill) defines it.
///
/// Motions started through this handle, a whole block included, count
/// as running from the start until they complete, so the wait does not end in the short
/// [`Ready`](RobotState::Ready) between two of them. A handle that sends
/// no motions, such as a read-only view, does not know the motions of
/// another client and can return in such a gap.
///
/// # Arguments
///
/// * `timeout` - How long to wait at most. The command timeout does not
/// apply, since the robot may legitimately run for longer.
///
/// # Returns
///
/// `Ok(())` once the robot stands still.
///
/// # Errors
///
/// Returns [`RobotArmError::DidNotStop`](crate::error::RobotArmError::DidNotStop) if the
/// robot does not come to rest within `timeout`,
/// [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if it is not
/// connected, or
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if a read fails.
fn wait_for_standstill(&self, timeout: Duration) -> Result<()>;
/// Wait until the robot control reports the axes in motion.
///
/// The axes start a little after the control takes an instruction, so
/// a reading that needs the robot in motion, such as a velocity, waits
/// here first.
///
/// # Arguments
///
/// * `timeout` - How long to wait at most.
///
/// # Returns
///
/// `Ok(())` once the axes move.
///
/// # Errors
///
/// Returns [`RobotArmError::Timeout`](crate::error::RobotArmError::Timeout) with
/// [`TimedOutOperation::Motion`](crate::error::TimedOutOperation::Motion) if the axes
/// still rest after `timeout`,
/// [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the robot
/// is not connected, or
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if a read fails.
fn wait_for_motion(&self, timeout: Duration) -> Result<()>;
}
GetJointPose
pub trait GetJointPose {
/// Get the current joint pose of the robot.
///
/// # Returns
///
/// The current [`JointPose`] of all robot axes.
///
/// # Errors
///
/// Returns [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the
/// robot is not connected,
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the read fails and
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument) if the
/// control reports a different number of axes.
fn joint_pose(&self) -> Result<JointPose>;
}
GetJointVelocities
pub trait GetJointVelocities {
/// Get the current joint velocities of the robot (rad/s or m/s).
///
/// # Returns
///
/// The [`JointVelocities`] of all robot axes.
///
/// # Errors
///
/// Returns [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the
/// robot is not connected,
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the read fails and
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument) if the
/// control reports a different number of axes.
fn joint_velocities(&self) -> Result<JointVelocities>;
}
GetJointAccelerations
pub trait GetJointAccelerations {
/// Get the current joint accelerations of the robot (rad/s² or m/s²).
///
/// # Returns
///
/// The [`JointAccelerations`] of all robot axes.
///
/// # Errors
///
/// Returns [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the
/// robot is not connected,
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the read fails and
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument) if the
/// control reports a different number of axes.
fn joint_accelerations(&self) -> Result<JointAccelerations>;
}
GetTcpPose
pub trait GetTcpPose {
/// Get the current TCP pose of the robot.
///
/// The pose is the one the robot control reports, in the robot
/// coordinate system, the frame of a plain [`CartesianPose`] motion
/// target, and not in a user coordinate system a motion target may
/// name. It is the pose of the selected tool's TCP, or of the flange
/// when no tool is selected.
///
/// # Returns
///
/// The reported [`CartesianPose`] of the tool center point.
///
/// # Errors
///
/// Returns [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the
/// robot is not connected and
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the read fails.
fn tcp_pose(&self) -> Result<CartesianPose>;
}
GetTcpVelocity
pub trait GetTcpVelocity {
/// Get the current TCP velocity of the robot.
///
/// The velocity is the one the robot control reports, in the robot
/// coordinate system, for the TCP of the selected tool, or for the
/// flange when no tool is selected.
///
/// # Returns
///
/// A [`CartesianVelocity`] with translational velocity in m/s
/// and rotational velocity in rad/s.
///
/// # Errors
///
/// Returns [`RobotArmError::NotConnected`](crate::error::RobotArmError::NotConnected) if the
/// robot is not connected and
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the read fails.
fn tcp_velocity(&self) -> Result<CartesianVelocity>;
}