Robot Driver¶
To execute motions on a real robot, we offer a range of drivers for common robot manufacturers such as ABB, Universal Robot, or Yaskawa. All drivers share a common interface, however some features depend on specific hardware or internal software requirements and are thus only available on selected platforms. Our list of supported robots includes information about the corresponding driver. There is also a SimulatedDriver
that can be used for any robot for testing, development, or visualization purposes.
All drivers subclass the base Driver
class that defines the primary interface. In the following, we will guide you through this API and it’s general behavior.
Installation¶
Please refer to each driver’s section for the installation procedure, as usually the robot controller needs to be prepared in a specific way. For installing the code that is running on the control PC, you can either download the Python package via pip install jacobi-[ROBOT]-driver
or the C++ Debian package from our downloads page.
Connect to a Robot¶
A driver is constructed by passing a Planner
as well as information on how to connect to the robot:
driver = Driver(planner, host='192.168.1.130')
Driver driver {planner, "192.168.1.130"};
Usually, this is the robot’s IP address and/or the port to connect to. For some robots, networking configuration needs to be set on the controller side (e.g. for ABB). Please make sure to have a direct connection to your robot, and also be careful about firewall settings on your control PC.
The planner is used to plan motions on-the-fly (e.g. a stop trajectory for braking) within the specified obstacle environment and with the defined robot limits.
On construction, the driver will first try to connect to the robot. If the connection fails, e.g. because of the default timeout of 2s, a JacobiError
is raised. Second, the driver will enable the robot for subsequent motions, e.g. by turning the motors on. Then, you can check and change the connection status via
is_connected = driver.is_connected # Should be true
driver.disconnect() # E.g. to disconnect on purpose
is_connected = driver.reconnect()
bool is_connected = driver.is_connected(); // Should be true
driver.disconnect(); // E.g. to disconnect on purpose
is_connected = driver.reconnect();
The reconnect
method needs to be called to reconnect after an external disconnect event.
Simulated Driver
The simulated driver can connect to Studio Live to visualize the robot motions easily. To connect to a SimulatedDriver
, enable the sync_with_studio
parameter in the constructor. Alternatively, you can log to the console via log_stream
.
from jacobi.drivers import SimulatedDriver
driver = SimulatedDriver(planner, sync_with_studio=True)
#include <jacobi/drivers/simulated.hpp>
drivers::SimulatedDriver driver {planner, true};
Read the Robot’s State¶
Given an established connection, you can read the robot’s current state or just the joint position by
state = driver.get_current_state()
position = driver.get_current_joint_position()
RobotState state = driver.get_current_state();
Config position = driver.get_current_joint_position();
Reading from the robot also works while executing a motion. Whether the robot is currently executing a motion, the is_running
property can be used.
Run a Trajectory¶
Given a trajectory that was planned beforehand, the easiest way to execute it on a real robot is by
# Plan offline
trajectory = planner.plan('motion-1')
result = driver.run(trajectory)
// Plan offline
auto trajectory = planner.plan("motion-1");
Result result = driver.run(trajectory);
which blocks the code and waits for the robot to finish the motion. The result type holds information about the success or possible errors of the exeuction. This method requires the trajectory to start at the current position of the robot, otherwise a ErrorStartPositionDeviation
result is returned. In general, the driver doesn’t make use of exceptions due to its real-time capability.
Asynchronous Programming
Asynchronous programming is very useful for applications interfacing with the real world. In C++, the motion library is based on std::future
and std::promise
ecosystem of the standard library. We added a small wrapper called FutureResult
to make it easier to use.
For Python, we integrated our drivers into the asyncio
standard library. This makes asynchronous execution very easy using the async
/ await
commands.
You can run a trajectory asynchronously via to supervise the motion execution, control external IO, or change the motion while the robot is moving.
future_result = driver.run_async(trajectory)
# Do something here, e.g. change the motion on-the-fly or control some external IO
await future_result
auto future_result = driver.run_async(trajectory);
// Do something here, e.g. change the motion on-the-fly or control some external IO
future_result->wait();
The await
method then blocks code execution until the motion is completed. Besides the run
method, there are a few more methods to move the robot - and all have an asynchronous counterpart with an _async
suffix.
Stop the Robot¶
To stop the current motion of the robot, simply call
driver.stop()
driver.stop();
e.g. during an asynchronous motion. We differentiate between a fast and soft stop, with the fast stop being the default. The fast stop sends - if available - a native stop signal to the controller which will then take over the braking motion. Alternativel, a soft stop is calculated and executed by our driver: It executes a time-optimal trajectory to zero velocity and zero acceleration within the specified limits of the robot. This is of course completely independent of any safety or emergency stops, which are always on the controller level. Again, there is also an asynchronous variant called stop_async()
.
Move To a Goal¶
To plan and execute motions easily starting from the current joint position of the robot, the driver includes the move_to
command:
# The goal point can be a Config, Waypoint, Frame, or CartesianWaypoint.
point = Frame(x=0.3, y=0.2, z=0.3)
driver.move_to(point)
// The goal point can be a Config, Waypoint, Frame, or CartesianWaypoint.
auto point = Frame::from_translation(0.3, 0.2, 0.3);
driver.move_to(point);
This is why we pass the Planner
to the driver - to have a unified interface for planning and executing.
Plan On-the-fly¶
The real power of our motion planner is it’s capability to plan motions on-the-fly. Then, calling
# Set a new goal
new_goal = Frame(x=0.5, y=0.2, z=0.3) # [m]
# And move there by either
await driver.move_to_async(new_goal)
// Set a new goal
const auto new_goal = Frame::from_translation(0.5, 0.2, 0.3); // [m]
// And move there by either
driver.move_to_async(new_goal).wait();
will overwrite the current motion of the robot, plan a new motion using the planner
object that we passed for construction, and run the resulting trajectory to the new goal (either synchronously or asynchronously). In particular, the new motion starts at the current state of the robot that might have a non-zero current velocity.
Real-time Capability¶
In general, the motion planning must be real-time capable within a single control cycle to allow for planning while the robot is moving. Just to clarify, the motion planning is still allowed to fail (e.g. as a valid collision-free motion might not even exist), however it must report this within the control cycle.
The planner of our Jacobi Motion library guarantees real-time capability if either:
There are no obstacles within the environment, including end-effector or item obstacles of the robot. This also holds true if the obstacles are far away from the robot’s trajectory, so that a direct motion is not blocked. To enforce this behaviour, all
move_to()
ormove_to_async()
methods take the booleanignore_collisions
parameter as input that guarantees a real-time computation if set to true. In this case, we guarantee a time-optimal solution as well.The motion was trained beforehand, and the planner has loaded the corresponding motion plan.
Pre-defined Motions¶
We can use the motion interface to run pre-defined motions on-the-fly. Then, we query a motion given its name and an optional goal point in case a region was specified:
driver.move_along('motion-1', new_goal);
driver.move_along("motion-1", new_goal);
This allows to use all the motion parameters for executing complex motions.