Getting started
This page covers building URI, running it in the most common setups, and regenerating this documentation.
Build
mc_rtc and other controllers
URI is not meant to be tied to mc_rtc. The controller that runs in
uri manager is a plugin (see
Controller backend), and driver plugins do not
depend on mc_rtc at all. Today, though, mc_rtc is the only backend, and
the URI libraries still link mc_rtc for logging and plugin loading. So for
now, installing URI means installing it next to mc_rtc. This will change as
other controllers are supported.
There are three ways to install it:
- with mc-rtc-superbuild, the simplest if you already build mc_rtc with it;
- manually, against an existing mc_rtc install;
- in the devcontainer, for a ready-made development environment.
With mc-rtc-superbuild
mc-rtc-superbuild builds and
installs mc_rtc and its dependencies. Its WITH_URI option adds URI and
the dependencies mc_rtc doesn't provide:
| Project | Version | Notes |
|---|---|---|
| FlatBuffers | v25.12.19 | Ubuntu's package is too old |
| zenoh-c | 1.9.0 | Built with shared memory, for the zenoh/shm protocol |
| zenoh-cpp | 1.9.0 | zenoh-c backend only |
| URI | main |
Tests and Protobuf disabled |
Enable the option when you configure the superbuild. zenoh-c is written in
Rust: if cargo is not found, the configure step installs a Rust toolchain
with rustup in ~/.cargo.
cd mc-rtc-superbuild
cmake --preset relwithdebinfo -DWITH_URI=ON
cmake --build --preset relwithdebinfo
Everything is installed in the superbuild's install prefix. Build your drivers with that same prefix (see below).
Manual installation
Dependencies:
- mc_rtc
zenoh-c(built with-DZENOHC_BUILD_WITH_SHARED_MEMORY=ON, needs Rust) andzenoh-cpp(built with-DZENOHCXX_ZENOHC=ON -DZENOHCXX_ZENOHPICO=OFF)- FlatBuffers, a recent version that provides
flatbuffers_generate_headers(); Protobuf is optional (-DWITH_PROTOBUF=OFFto skip it) - GTest, only for
-DBUILD_TESTING=ON
Then configure, build and install:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_PREFIX_PATH="/path/to/mc_rtc/install;/path/to/extra/deps" \
-DCMAKE_INSTALL_PREFIX=/path/to/install
cmake --build build --parallel
cmake --install build
In the devcontainer
The devcontainer in .devcontainer/ installs all the dependencies, with
mc_rtc from its head apt repository, then builds and installs URI on first
start.
See the top-level README.md.
After installing
Whichever way you install, you also need the RobotModule of each robot you
want to control (e.g. mc_ur5e), and a driver plugin for it (see
Supported robots).
The install prefix matters. uri interface looks for driver plugins only
in <CMAKE_INSTALL_PREFIX>/lib/robot_interface. That path is compiled in and
has no environment-variable override. Drivers must be installed with the same
prefix. See Robot interface.
The build and the install both run setcap cap_sys_nice+eip on uri through
sudo, so you may be prompted for your password. uri needs this capability
to request SCHED_DEADLINE. Because of it, the dynamic loader ignores
LD_LIBRARY_PATH when running uri. See
Troubleshooting if the manager crashes on exit with
CycloneDDS.
This installs one executable, uri, with a subcommand per tool (uri manager,
uri interface, ...). See Command line for all of them
and their options.
Before running uri manager
uri manager calls mlockall() at startup and exits with code -2 if that fails.
Raise the memlock limit for your user in /etc/security/limits.conf, for
example:
youruser - memlock unlimited
Log out and back in after changing it. Also source your mc_rtc environment
(e.g. setup_mc_rtc.sh) so that uri finds mc_rtc's RobotModules and
controllers.
Example 1: one robot on the control PC (autostart)
This is the simplest setup: the robot, or its simulator, is reachable from the
control PC, so no separate robot-side PC is needed. uri manager starts
uri interface itself.
robot_manager/etc/mc_rtc_ur5e_sim.yaml drives a Gazebo UR5e through
ros2_control:
MainRobot: UR5e
Enabled: Posture
Timestep: 0.005
Robots:
ur5e:
module: UR5e # mc_rtc RobotModule
network_interface:
protocol: zenoh
robot_interface:
driver: RobotDriverROS2Control
ip: 127.0.0.1 # unused by this driver, but required
port: 0
config_path: /path/to/ros2_control_driver/etc/ur5e.yaml
autostart: true # uri manager spawns uri interface itself
Prerequisites:
- the
ros2_control_driverplugin, installed with the same prefix as URI - the UR5e simulation running, with its
controller_managerup config_pathedited to point to your checkout
Run:
uri manager -c robot_manager/etc/mc_rtc_ur5e_sim.yaml
What happens, in order:
uristarts a local Zenoh router and spawnsuri interface -c /tmp/robot_manager_ur5e_interface.yaml -n ur5e.- It sends the robot's configuration on the
ur5e/initquery. The interface loadsRobotDriverROS2Controland repliesOK. - The first
ur5e/statemessage initializes the mc_rtc robot from the real joint positions. mc_rtc then starts running thePosturecontroller.
Stop with Ctrl+C. uri manager sends SIGTERM to the spawned uri interface, and
SIGKILL if it has not exited within 5 s.
Example 2: robot on its own PC
Use this when the robot is connected to a different machine, for example a real-time PC next to the arm.
On the robot PC, write a uri interface config file. It only needs the
robot's name and how to reach the manager:
# ur5e_robot.yaml
name: ur5e
network_interface:
protocol: zenoh
Then start it before uri manager. It waits for the manager's init query:
uri interface -c ur5e_robot.yaml
# [RobotInterface] 'ur5e' waiting for init query on 'ur5e/init'
On the control PC, reference the same robot name in mc_rtc.yaml, without
autostart:
MainRobot: UR5e
Enabled: Posture
Timestep: 0.005
Robots:
ur5e:
module: UR5e
controller:
mode: position
time_step: 0.001 # the robot's own control period
network_interface:
protocol: zenoh
robot_interface:
driver: RobotDriverRTDE # loaded on the robot PC, not here
ip: 192.168.1.6 # passed to the driver on the robot PC
port: 0
uri manager -c mc_rtc.yaml
The driver name, IP, port and config_path all come from the manager's
config. They are sent in the init query, so switching drivers never requires
touching the robot PC's config. The driver .so must be installed on the
robot PC, though.
Both Zenoh peers must be able to discover each other. On a single LAN,
Zenoh's default multicast scouting finds them. Otherwise, point both sides at
a Zenoh config file that lists explicit endpoints (network_interface.configuration).
See Communication.
Example 3: several robots, one controller
Add more entries under Robots:. Each one gets its own uri interface and
its own topics. base: copies another robot's entry and overrides keys:
Robots:
ur5e_left:
module: UR5e
network_interface: { protocol: zenoh }
robot_interface: { driver: RobotDriverRTDE, ip: 192.168.1.2, port: 0 }
ur5e_right:
base: ur5e_left # everything from ur5e_left...
robot_interface: # ...except the robot address
driver: RobotDriverRTDE
ip: 192.168.1.3
port: 0
Start one uri interface -c <file> -n ur5e_left and one with
-n ur5e_right, or set autostart: true on both. Then run uri manager.
Example 4: test a driver without the manager
uri viewer loads a driver plugin directly and prints its joint state
in the terminal. It needs neither mc_rtc's controller nor Zenoh, so it is the
quickest way to check that a new driver connects and reads sensible values:
# viewer.yaml
robot_interface:
driver: RobotDriverRTDE
ip: 192.168.1.6
port: 0
# config_path: /path/to/driver_config.yaml
uri viewer -c viewer.yaml --rate 10
It also accepts the manager's mc_rtc.yaml directly and uses that robot's
robot_interface section. Pick the robot with -n when there are several:
uri viewer -c robot_manager/etc/mc_rtc_ur5e_sim.yaml
uri viewer -c robot_manager/etc/mc_rtc_callm.yaml -n <robot>
Robot State Viewer — RobotDriverRTDE @ 192.168.1.6
------------------------------------------------------------
Joint │ Position (rad) │ Velocity (rad/s) │ Torque (Nm)
──────┼──────────────────┼────────────────────┼─────────────
0 │ -0.0012 │ 0.0000 │ 0.1523
...
It only reads state. It never calls servoJ/speedJ/tauJ.
Generate this documentation
The documentation is generated by hdoc from
build/compile_commands.json and the Markdown pages in docs/, as configured
in .hdoc.toml. On every push to main, the Documentation workflow
(.github/workflows/docs.yaml) regenerates it and publishes it to the
gh-pages branch. To generate it locally:
cmake -S . -B build # compile_commands.json must exist
scripts/gen-hdoc-docs.sh
xdg-open hdoc-output/index.html
On the first run, the script builds hdoc 1.4.1 against LLVM/Clang 14 and
caches it in ~/.cache/hdoc-build, which takes a few minutes. Install the
toolchain with:
sudo apt-get install llvm-14-dev libclang-14-dev clang-14
To add a page, create docs/<Name>.md and add it to [pages] paths in
.hdoc.toml. It is published as doc<Name>.html, and its file name is the
sidebar title. Images go in docs/images/; the script copies that folder
into the output.
Copyright © CNRS-AIST JRL 2026


