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

Trait Definitions

Each reading is its own trait; all but GetRobotState have a single method. Lifecycle is shown in the lifecycle chapter.