Lifecycle of a Robot

A robot transitions through multiple states during its operation, often for safety and convenience purposes. The robot’s state is also essential for error handling within the automation system it is part of. Decisions must be based on the robot’s current state, so it is crucial to both read and manipulate this state.

The state of a robot is the main state of its robot control, a RobotState. The Lifecycle trait moves the robot between these states: it connects to the robot control, switches the regulators on and off, and disconnects again. Reading the state is part of the GetRobotState trait, which every robot implements; see Reading the Robot State.

Note

To interact with a robot successfully, this library assumes a stable network connection to the robot control. Ensure that the robot control is reachable from the machine running this library.

The States of a Robot

The robot control reports one of the states of the RobotState enum:

State

Description

Init

The robot control starts and waits for its peripheral hardware.

Standby

Initialized, regulators off: the robot cannot move.

Ready

Regulators on, the robot is not moving and waits for instructions.

Active

The interpolator produces set values for a motion.

Stopping

A critical stop: the robot decelerates to a standstill.

GravComp

Gravitation compensation mode.

CollisionReaction

A collision was detected and the configured reaction runs.

Error

An error occurred; the robot stays in this state until it is reset.

Reset

The robot control clears its errors.

Shutdown

The robot control stops the robot, switches the regulators off and terminates.

RobotState::is_enabled() tells whether the regulators are on (Ready, Active, Stopping, GravComp and CollisionReaction), and RobotState::accepts_motions() whether the robot control takes motion instructions (Ready and Active). The enum is #[non_exhaustive], so a match on it needs a wildcard arm; the two helpers cover the common questions without one.

The typical transitions are:

not connected --connect()--> Standby --enable()--> Ready
                                ^                    |
                                +-----disable()------+

connect() returns once the robot control has booted to Standby, waiting at most the command timeout, so a control that boots for longer needs a longer timeout; a robot control that is enabled when the robot connects is disabled first. enable() returns once the robot control takes motion instructions, so a motion can be started right after it. Error is entered by the robot control itself and left through the error handler, see Resetting Errors. A robot in the error state still connects, but enable() rejects it with RobotArmError::WrongState until it is reset. The errors name the state the robot control was in, for example WrongState { operation: "enable", state: CollisionReaction }.

A robot that enters the error state during enable() or disable() makes the call return RobotArmError::StateNotReached at once rather than after the command timeout.

Whether the robot is connected is not a state of the robot control but of the robot instance, and is_connected() tells it. A connected robot counts as not connected once its connection is lost: the robot control closed it, or it dropped and could not be restored. A request that fails or times out does not count as a lost connection by itself; it fails with its own error while the robot stays connected. Once the connection is lost, pending motions end with RobotArmError::CommunicationEnded, the lifecycle calls and the state readers fail with RobotArmError::NotConnected, the other calls with the backend’s error, and connect() connects the robot again, no disconnect() needed first.

Creating a Robot

Create a robot instance of the desired type with VorausIndustrialRobotArm::new(); it talks OPC UA to the voraus.core.

Every lifecycle transition waits for the robot control to reach the target state. The wait is bounded by the robot’s command timeout, which also bounds every other command (stop, pause, time override, and the configuration calls). It defaults to DEFAULT_COMMAND_TIMEOUT and can be changed with set_command_timeout() from the CommandTimeout trait. The command timeout bounds only these waits, not the requests to the control themselves. Those are bounded by the limits of the connection, a ConnectionTimeouts value the robot’s backend takes before it connects. They default to DEFAULT_CONNECT_TIMEOUT for establishing the connection, DEFAULT_REQUEST_TIMEOUT for each read or call and DEFAULT_DISCONNECT_TIMEOUT for closing the connection, all in the constants module. To change them for one robot, build it before connecting with a backend that takes them, for the voraus.core as VorausIndustrialRobotArm::with_backend(OpcUaBackend::with_timeouts(timeouts)); VorausErrorHandler::with_timeouts() does the same for the error handler.

robot.set_command_timeout(Duration::from_secs(10));

Checking the Connection

is_connected() tells whether the robot is connected. After creation, it is not:

// Initially not connected.
info!("Connected: {}", robot.is_connected()); // false

Connecting to a Robot

To establish a connection, the host of the robot control (an IP address or a hostname) and its port must be provided. The example reads them from the environment variables VORAUS_CORE_HOST and VORAUS_CORE_PORT:

robot.connect(&common::robot_host(), common::robot_port())?;
info!("Current state: {:?}", robot.robot_state()?); // Standby

Enabling the Robot

Most actions require the robot to be in a moveable state. This is done with enable(), which returns once the robot control takes motion instructions:

robot.enable()?;
info!("Current state: {:?}", robot.robot_state()?); // Ready

Afterwards, the main application logic can be implemented using the functionality provided by this library, together with any other Rust code that helps to solve the given problem.

Once the robot is no longer required to be moveable, switch back with disable(). Since switching the regulators off while the axes still move could harm the mechanics, disable() first waits until the robot stands still, bounded by the command timeout, and returns DidNotStop with the robot still enabled if it does not come to rest in time:

robot.disable()?;
info!("Current state: {:?}", robot.robot_state()?); // Standby

Disconnecting

Finally, disconnect() closes the connection:

robot.disconnect()?;
info!("Connected: {}", robot.is_connected()); // false

disable() and disconnect() contain cleanup, so they should always run. disconnect() disables a robot that is still enabled and stops a running heartbeat session, in that order, before it closes the connection, so the heartbeat stays armed while the robot comes to rest; a pending stop or pause does not carry over to the next connection. A failed disable or heartbeat stop, such as a robot that does not come to rest in time and therefore stays enabled, is logged and the connection is closed anyway. A robot that is still enabled keeps its heartbeat tracker on the control, and so does one behind a dead link, where the tracker cannot be removed; the tracker then times out and puts the robot into the error state until it is reset (see resetting errors). The robot structs implement Drop to guarantee the cleanup: dropping a connected robot disconnects it. Failures are logged. In most applications the robot is simply left to go out of scope. Call the methods explicitly when the outcome matters. Note that the cleanup does blocking network I/O, which can take longer than the command timeout if the control stopped answering (see the connection limits above), and that std::process::exit skips it, since it ends the process without running destructors.

A Note on Receivers

The lifecycle methods take &mut self: connect, enable, disable, and disconnect are state transitions of the robot handle, and exclusive access prevents two threads from racing them. Motion and query methods take &self, because issuing instructions or reading state does not change what the handle is, and applications routinely do both from several threads at once.

Full Example

Trait Definitions