mc_rtc::Configuration general purpose configurationAn interface is the piece of software that connects mc_rtc to a robot (or a simulator). On every iteration it:
This tutorial shows how to write one for a new robot. There are two ways to do it:
mc_control::MCGlobalController. This is how most of the interfaces listed on the interfaces page are written.
URI splits the traditional interface into three parts:
uri manager: one process that runs a solver (mc_rtc) for all your robots. It is configured with the
usual mc_rtc.yaml plus a Robots section.uri interface: one generic process per robot. It runs on the robot's PC or on the control PC and
loads your driver at startup.The only part you write is the driver: a shared library that wraps your robot's SDK a templated driver. It does not depend on mc_rtc at all. As a bonus, a controller can drive several different robots at once, each at its own control rate, and robots can live on different computers.
URI is installed next to mc_rtc. If you build mc_rtc with mc-rtc-superbuild, enable the WITH_URI option:
cd mc-rtc-superbuild
cmake --preset relwithdebinfo -DWITH_URI=ON
cmake --build --preset relwithdebinfoOther installation methods (manual, devcontainer) are described in the URI documentation.
uri interface only looks for drivers
in <prefix>/lib/robot_interface, so your driver must be installed with the same prefix.Drivers already exist for Universal Robots (RTDE), Kinova Gen3, xArm, any robot with a ros2_control
controller manager, and more. See the list of supported robots. If
your robot is there, skip to Use the driver with mc_rtc.
URI generates a ready-to-build project for you:
uri create_new_driver MyRobot <new_driver_path>This creates a standalone CMake project, my_robot_driver, with a class named
RobotDriverMyRobot. It already compiles: it is a skeleton that reports an empty state and ignores
commands.
Open src/RobotDriverMyRobot.cpp and fill in the methods. The ones you will implement in most cases are:
| Method | What it must do |
|---|---|
| constructor | Connect to the robot using the ip, port and optional config_path you
receive. Throw if the connection fails. |
sync() |
Block until the robot publishes a new state. This sets the control rate: use a blocking read
from the SDK, not a sleep(). |
getActualQ() |
Joint positions [rad]. Return an empty vector if no state is available yet. |
getActualQd() |
Joint velocities [rad/s] (optional) |
getJointTorques() |
Joint torques [Nm] |
servoJ(q) |
Send joint positions [rad] to the robot |
speedJ(qd) |
Send joint velocities [rad/s] to the robot |
tauJ(tau) |
Send joint torques [Nm] to the robot |
getIMUs(), getForceSensors() |
Sensor readings (optional) |
You only need to implement the command methods for the control mode your robot supports (servoJ for
position control, and so on). The others can throw or do nothing.
Three rules matter more than everything else:
ref_joint_order of
your RobotModule. If your SDK uses a different order, reorder in the driver.(w, x, y, z).getIMUs() and getForceSensors() must
match the body sensors and force sensors of your RobotModule.If your SDK needs extra libraries, add them to the generated CMakeLists.txt files with the usual
find_package and target_link_libraries. Settings that do not fit in ip and
port (topic names, gains...) can go in a YAML file of your own, whose path is passed to the constructor
as config_path.
Build the driver with the same prefix as URI (and mc_rtc):
cd my_robot_driver
cmake -S . -B build \
-DCMAKE_PREFIX_PATH=/path/to/install \
-DCMAKE_INSTALL_PREFIX=/path/to/install
cmake --build build
cmake --install buildThe generated source keeps the line ROBOT_DRIVER_EXPORT_ABI_VERSION(). Do not remove it: URI refuses to
load a driver without it. When URI changes the driver interface, drivers built against an older version fail to load
with a clear message and must be rebuilt.
Before involving mc_rtc, check that the driver connects and that the state looks right. Write a small file with the robot's address:
robot_interface:
driver: RobotDriverMyRobot
ip: 192.168.1.10
port: 0Then run the viewer:
uri viewer -c viewer.yamlIt loads your driver exactly as uri interface does and prints joint positions, velocities and torques,
and the time spent in sync(). Move the robot by hand (or in simulation) and check the joint order, signs
and units. The viewer never sends any command, so it is safe to use on a real robot.
Add your robot to the Robots section of an mc_rtc.yaml. Everything else
(MainRobot, Enabled, observers, plugins...) keeps its usual meaning:
MainRobot: MyRobot
Enabled: Posture
Timestep: 0.005 # mc_rtc runs at 200 Hz
Robots:
my_robot: # robot name
module: MyRobot # mc_rtc RobotModule
controller:
mode: position # position | velocity | torque
time_step: 0.001 # the robot's own control period (1 kHz)
network_interface:
protocol: zenoh
robot_interface:
driver: RobotDriverMyRobot
ip: 192.168.1.10
port: 0
autostart: trueSome entries deserve an explanation:
module is the mc_rtc RobotModule of your robot.controller.mode is the type of command sent to the robot: position,
velocity or torque. It selects which of servoJ, speedJ and
tauJ is called.controller.time_step is the control period of the robot. Timestep (mc_rtc) must be an
exact multiple of it. In the example, the robot runs at 1 kHz and mc_rtc at 200 Hz; the last command is sent again
in between.robot_interface.driver is the class name of your driver. ip and port are
always required, even if your driver ignores them.autostart: true makes uri manager start uri interface itself. Use it when
the robot is reachable from the control PC.Now start the controller with uri manager instead of an interface executable:
uri manager -c mc_rtc.yamlYou should see the driver being loaded, the robot initialized from its real joint positions, and mc_rtc starting your controller. As usual, you can interact with the controller using any mc_rtc GUI client.
If the robot is connected to its own PC (e.g. a real-time PC next to the arm), remove autostart and
install the driver on that PC. Start uri interface there before uri manager. It
only needs to know the robot's name:
# my_robot_interface.yaml, on the robot PC
name: my_robot # same as the key under Robots:
network_interface:
protocol: zenoh# On the robot PC
uri interface -c my_robot_interface.yaml
# On the control PC
uri manager -c mc_rtc.yamlThe driver name, IP address and control mode are sent by the manager, so you can change drivers without touching the robot's PC. If the connection is lost, the manager stops the controller and the interface holds the robot in place.
Add more entries under Robots. Each robot gets its own driver and its own control rate, and all of them
are controlled by the same mc_rtc controller. The base key copies another entry so you only write what
differs.
This tutorial only covers the essentials. The URI documentation has more details on:
mc_rtc.yaml reference, timing rules, connection loss handlinguri subcommandsIn this approach you write an executable that links with mc_rtc and embeds a mc_control::MCGlobalController. It owns the whole real-time loop: communication with the robot, timing, and calling
mc_rtc. Each iteration looks like this:
// Sketch of a dedicated interface
mc_control::MCGlobalController controller("/path/to/mc_rtc.yaml");
auto & robot = controller.robot();
// 1. Initialize the controller from the real robot configuration
controller.init(readJointPositions());
// 2. Real-time loop, paced by the robot
while(running)
{
waitForNextRobotState();
// 3. Feed mc_rtc with the sensors
controller.setEncoderValues(readJointPositions());
controller.setJointTorques(readJointTorques());
controller.setSensorOrientations(Accelerometer);
// 4. Run one iteration of the controller
if(controller.run())
{
// 5. Send the result to the robot
sendJointPositions(robot.mbc().q);
}
}The functions you will use most are:
mc_control::MCGlobalController::init() initializes the controller from the initial robot
configurationmc_control::MCGlobalController::setEncoderValues(), mc_control::MCGlobalController::setEncoderVelocities() and mc_control::MCGlobalController::setJointTorques() provide the joint statemc_control::MCGlobalController::setSensorOrientations(), mc_control::MCGlobalController::setSensorAngularVelocities(), mc_control::MCGlobalController::setSensorLinearAccelerations() and mc_control::MCGlobalController::setWrenches() provide the other sensorsmc_control::MCGlobalController::run() runs one iteration of the controllerCompared to a URI driver, a dedicated interface has several drawbacks:
Existing interfaces (see the interfaces page) are good examples to read if you need to maintain or port one. If you do need a new standalone interface, for example to integrate a simulator with its own main loop, you can start from one of them.