A robot_interface driver plugin for Universal Robots controllers via the
Universal Robots Client Library.
It implements the mc_robot_interface::RobotDriver interface so it can be loaded
by robot_interface at runtime as a shared library — no mc_rtc dependency required.
The Dockerfile builds an image that runs uri interface with
this driver. It starts from the unified_robot_interface image (ROS Jazzy,
mc_rtc, zenoh and URI already installed in /opt/uri), so only this driver andur_client_library are added.
compose.yaml runs the published image
(ghcr.io/isri-aist/rtde_driver:latest) with the options it needs, reading
configs from ./etc (mounted on /config):
docker compose up # etc/robot_interface.yaml
URI_CONFIG_FILE=ursim_ur5e.yaml docker compose up # URSim
docker compose pull # update to the latest main
docker compose up --build # build locally insteadIt sets:
network_mode: host, so zenoh reaches the manager and the driver reaches the UR controller (which also connects back to this host for the external-control script);ipc: hostfor thezenoh/shmprotocol.cap_add: SYS_NICE, required:uricarries that file capability, and the kernel refuses to run it if the container can't grant it. Plusrtprioandmemlocklimits for real-time scheduling.
Environment variables, read from the shell or a .env file next to
compose.yaml:
| Variable | Default | Meaning |
|---|---|---|
URI_CONFIG_DIR |
./etc |
Host directory mounted on /config |
URI_CONFIG_FILE |
robot_interface.yaml |
File in it passed to uri interface -c |
RTDE_DRIVER_IMAGE |
ghcr.io/isri-aist/rtde_driver:latest |
Image to run (e.g. pin a :<commit sha> tag) |
BASE_IMAGE |
ghcr.io/isri-aist/unified_robot_interface:latest |
URI image to build on (with --build) |
The base image is published by the URI repository's CI. To build it locally instead, from the URI repository:
docker buildx bake uriExample configs are also in the image, in /opt/uri/share/rtde_driver.
| Dependency | Where to get it |
|---|---|
unified_robot_interface |
isri-aist/unified_robot_interface |
ur_client_library |
ROS package or built from source (see below) |
Option A — ROS package (Humble / Iron / Jazzy):
sudo apt install ros-<distro>-ur-client-libraryOption B — from source (no ROS required):
git clone https://github.com/UniversalRobots/Universal_Robots_Client_Library.git
cd Universal_Robots_Client_Library
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build .
sudo cmake --install .ur_client_library must be discoverable by CMake. Either source a ROS workspace,
install it system-wide, or pass the prefix explicitly:
# ROS install
cmake -DCMAKE_PREFIX_PATH="/path/to/unified_robot_interface/install;/opt/ros/humble" ..
# Source / system install
cmake -DCMAKE_PREFIX_PATH="/path/to/unified_robot_interface/install;/usr/local" ..cd rtde_driver
mkdir build && cd build
cmake .. \
-DCMAKE_PREFIX_PATH="/path/to/install;/opt/ros/humble" \
-DCMAKE_INSTALL_PREFIX=/path/to/install
cmake --build .
cmake --install .The plugin is installed to lib/robot_interface/libRobotDriverRTDE.so.
rtde_driver runs in headless mode: it sends the URScript program directly to
the robot over the primary interface, so no URCap installation is needed.
The URScript file (external_control.urscript) ships with ur_client_library and
is found automatically. Override the path with the UR_SCRIPT_FILE environment
variable if needed:
export UR_SCRIPT_FILE=/path/to/external_control.urscriptSearch order:
$UR_SCRIPT_FILEenvironment variable/opt/ros/<humble|iron|jazzy>/share/ur_client_library/resources/external_control.urscript/usr/share/ur_client_library/resources/external_control.urscript
uri has the cap_sys_nice file capability, so the dynamic loader ignores
LD_LIBRARY_PATH. liburcl.so is found instead through the plugin's RUNPATH,
which add_robot_driver() in src/CMakeLists.txt sets
to the directories it was linked against. Check it:
readelf -d lib/robot_interface/libRobotDriverRTDE.so | grep RUNPATH
# e.g. [/opt/ros/<distro>/lib/x86_64-linux-gnu]If ur_client_library has since moved (reinstalled under another prefix,
different ROS distro), rebuild the plugin. No ld.so.conf.d entry is needed.
The driver requests the following RTDE fields from the robot:
| Field | Used by |
|---|---|
actual_q |
getActualQ() — joint positions [rad] |
actual_qd |
getActualQd() — joint velocities [rad/s] |
target_moment |
getJointTorques() — target joint torques [Nm] |
sync() blocks until the robot delivers a new RTDE data package, pacing the
control loop at the robot's RTDE frequency (500 Hz by default).
loop:
sync() ← blocks, receives data package
getActualQ() ← reads from the cached package
getActualQd()
getJointTorques()
servoJ(q) ← sends position command (MODE_SERVOJ)
speedJ(qd) ← sends velocity command (MODE_SPEEDJ)
The shared library exports three C symbols consumed by robot_interface's plugin loader:
void MC_RTC_ROBOT_DRIVER(std::vector<std::string> & classes); // registers "RobotDriverRTDE"
mc_robot_interface::RobotDriver * create(const std::string & name,
const std::string & ip,
const uint16_t & port,
const std::string & config_path,
const std::vector<mc_robot_interface::GripperInfo> & grippers);
void destroy(mc_robot_interface::RobotDriver * ptr);Configure it in robot_manager/etc/mc_rtc_rtde.yaml under the robot_interface key of the
relevant robot entry:
Robots:
ur5e:
module: ur5e_module
robot_interface:
driver: RobotDriverRTDE
ip: 192.168.1.100
# port: 0 # optional, unused (RTDE uses a fixed port)