The Eclipse Zenoh: Zero Overhead Pub/sub, Store/Query and Compute.
Zenoh (pronounce /zeno/) unifies data in motion, data at rest and computations. It carefully blends traditional pub/sub with geo-distributed storages, queries and computations, while retaining a level of time and space efficiency that is well beyond any of the mainstream stacks.
Check the website zenoh.io and the roadmap for more detailed information.
ROS (the Robot Operating System) is a set of software libraries and tools allowing to build robotic applications. In its version 2, ROS 2 relies mostly on O.M.G. DDS as a middleware for communications. This plugin bridges all ROS 2 communications using DDS over Zenoh.
While a Zenoh bridge for DDS already exists and helped lot of robotic use cases to overcome some wireless connectivity, bandwidth and integration issues, using a bridge dedicated to ROS 2 brings the following advantages:
- A better integration of the ROS graph (all ROS topics/services/actions can be seen across bridges)
- A better support of ROS toolings (
ros2
,rviz2
...) - Configuration of a ROS namespace on the bridge, instead of on each ROS Nodes
- Easier integration with Zenoh native applications (services and actions are mapped to Zenoh Queryables)
- More compact exchanges of discovery information between the bridges
This software is built in 2 ways to choose from:
zenoh-plugin-ros2dds
: a Zenoh plugin - a dynamic library that can be loaded by a Zenoh routerzenoh-bridge-ros2dds
: a standalone executable
The features and configurations described in this document applies to both. Meaning the "plugin" and "bridge" words are interchangeables in the rest of this document.
To install the latest release of either the DDS plugin for the Zenoh router, either the zenoh-bridge-ros2dds
standalone executable, you can do as follows:
All release packages can be downloaded from:
Each subdirectory has the name of the Rust target. See the platforms each target corresponds to on https://doc.rust-lang.org/stable/rustc/platform-support.html
Choose your platform and download:
- the
zenoh-plugin-ros2dds-<version>-<platform>.zip
file for the plugin.
Then unzip it in the same directory thanzenohd
or to any directory where it can find the plugin library (e.g. /usr/lib) - the
zenoh-bridge-ros2dds-<version>-<platform>.zip
file for the standalone executable.
Then unzip it where you want, and run the extractedzenoh-bridge-ros2dds
binary.
Add Eclipse Zenoh private repository to the sources list:
echo "deb [trusted=yes] https://download.eclipse.org/zenoh/debian-repo/ /" | sudo tee -a /etc/apt/sources.list > /dev/null
sudo apt update
Then either:
- install the plugin with:
sudo apt install zenoh-plugin-ros2dds
. - install the standalone executable with:
sudo apt install zenoh-bridge-ros2dds
.
The zenoh-bridge-ros2dds
standalone executable is also available as a Docker images for both amd64 and arm64. To get it, do:
docker pull eclipse/zenoh-bridge-ros2dds:latest
for the latest releasedocker pull eclipse/zenoh-bridge-ros2dds:nightly
for the main branch version (nightly build)
The "Release" action builds packages for most most of OSes. You can download those from the "Artifacts" section in each build.
Just download the package for your OS and unzip it. You'll get 3 zips: 1 for the plugin, 1 for the plugin as debian package and 1 for the bridge.
Unzip the zenoh-bridge-ros2dds-<platform>.zip
file, and you can run ./zenoh-bridge-ros2dds
⚠️ WARNING⚠️ : Zenoh and its ecosystem are under active development. When you build from git, make sure you also build from git any other Zenoh repository you plan to use (e.g. binding, plugin, backend, etc.). It may happen that some changes in git are not compatible with the most recent packaged Zenoh release (e.g. deb, docker, pip). We put particular effort in maintaining compatibility between the various git repositories in the Zenoh project.
⚠️ WARNING⚠️ : As Rust doesn't have a stable ABI, the plugins should be built with the exact same Rust version thanzenohd
, and using forzenoh
dependency the same version (or commit number) than 'zenohd'. Otherwise, incompatibilities in memory mapping of shared types betweenzenohd
and the library can lead to a"SIGSEV"
crash.
In order to build the zenoh bridge for DDS you need first to install the following dependencies:
-
Rust. If you already have the Rust toolchain installed, make sure it is up-to-date with:
rustup update
-
On Linux, make sure the
llvm
andclang
development packages are installed:- on Debians do:
sudo apt install llvm-dev libclang-dev
- on CentOS or RHEL do:
sudo yum install llvm-devel clang-devel
- on Alpine do:
apk install llvm11-dev clang-dev
- on Debians do:
-
CMake (to build CycloneDDS which is a native dependency)
Once these dependencies are in place, you may clone the repository on your machine:
git clone https://github.com/eclipse-zenoh/zenoh-plugin-ros2dds.git
cd zenoh-plugin-ros2dds
cargo build --release
The standalone executable binary zenoh-bridge-ros2dds
and a plugin shared library (*.so
on Linux, *.dylib
on Mac OS, *.dll
on Windows)
to be dynamically loaded by the zenoh router zenohd
will be generated in the target/release
subdirectory.
DDS support is provided by the cyclors crate. As this crate contains C code, symbol clashes may occur when loading the plugin statically with other plugins which use a different version of the cyclors
crate (e.g. the zenoh-plugin-dds
plugin).
To allow multiple versions of the cyclors
crate to be loaded at the same time the symbols within the crate can be prefixed with the crate version. The optional prefix_symbols
feature can be used to build the ROS2 plugin with prefixed DDS library symbols. e.g.
cargo build --features prefix_symbols
Note: The prefix_symbols
feature cannot be used at the same time as the dds_shm
feature.
You can also build zenoh-bridge-ros2dds
as a ROS package running:
rosdep install --from-paths . --ignore-src -r -y
colcon build --packages-select zenoh_bridge_ros2dds --cmake-args -DCMAKE_BUILD_TYPE=Release
The rosdep
command will automatically install Rust and clang as build dependencies.
If you want to cross-compile the package on x86 device for any target, you can use the following command:
rosdep install --from-paths . --ignore-src -r -y
colcon build --packages-select zenoh_bridge_ros2dds --cmake-args -DCMAKE_BUILD_TYPE=Release --cmake-args -DCROSS_ARCH=<target>
where <target>
is the target architecture (e.g. aarch64-unknown-linux-gnu
). The architecture list can be found here.
The cross-compilation uses zig
as a linker. You can install it with instructions in here. Also, the zigbuild
package is required to be installed on the target device. You can install it with instructions in here.
A typical usage is to run 1 bridge in a robot, and 1 bridge in another host monitoring and operating the robot.
⚠️ The bridge relies on CycloneDDS and has been tested withRMW_IMPLEMENTATION=rmw_cyclonedds_cpp
. While the DDS implementations are interoperable over UDP multicast and unicast, some specific and non-standard features of other DDS implementations (e.g. shared memory) might cause some issues.
It's important to make sure that NO DDS communication can occur between 2 hosts that are bridged by zenoh-bridge-ros2dds
. Otherwise, some duplicate or looping traffic can occur.
To make sure of this, you can either:
-
use ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST after Iron and ROS_LOCALHOST_ONLY=1 before Iron.
Preferably, enable MULTICAST on the loopback interface with this command (on Linux):sudo ip l set lo multicast on
-
use different
ROS_DOMAIN_ID
on each hosts -
use a
CYCLONEDDS_URI
that configures CycloneDDS to only use internal interfaces to the robot. This configuration has to be used for all ROS Nodes as well as for the bridge.
For instance for Turtlebot4 which embeds 2 hosts interconnected via USB:<CycloneDDS> <Domain> <General> <Interfaces> <NetworkInterface name="usb0"/> <!-- For less traffic, force multicast usage on loopback even if not configured. --> <!-- All ROS Nodes and bridges must have this same config, otherwise they won't discover --> <NetworkInterface address="127.0.0.1" multicast="true"/> </Interfaces> <DontRoute>true</DontRoute> </General> </Domain> </CycloneDDS>
On the robot, run:
zenoh-bridge-ros2dds
On the operating host run:
zenoh-bridge-ros2dds -e tcp/<robot-ip>:7447
- check if the robot's ROS interfaces are accessible via:
ros2 topic list
ros2 service list
ros2 action list
Other interconnectivity between the 2 bridges can be configured (e.g. automatic discovery via UDP multicast, interconnection via 1 or more Zenoh routers...). See the Zenoh documentation to learn more about the possible deployments allowed by Zenoh.
zenoh-bridge-ros2dds
can be configured via a JSON5 file passed via the -c
argument. You can see a commented and exhaustive example of such configuration file: DEFAULT_CONFIG.json5
.
The "ros2dds"
part of this same configuration file can also be used in the configuration file for the zenoh router (within its "plugins"
part). The router will automatically try to load the plugin library (zenoh-plugin_dds
) at startup and apply its configuration.
zenoh-bridge-ros2dds
also allows some of those configuration values to be configured via command line arguments. Run this command to see which ones:
zenoh-bridge-ros2dds -h
The command line arguments overwrite the equivalent keys configured in a configuration file.
The bridge discovers all ROS 2 Nodes and their topics/services/actions running on the same Domain ID (set via ROS_DOMAIN_ID
or 0
by default) via UDP multicast, as per DDS specification.
As the bridge relies on CycloneDDS, it's DDS communications can be configured via a CycloneDDS XML configuration file as explained here.
Starting from v0.11.0, the zenoh-bridge-ros2dds
is by default started in router
mode (See the difference between modes in Zenoh documentation).
This means it's listening for incoming TCP connections by remote bridges or any Zenoh application on port 7447
via any network interface. It does perform discovery via scouting over UDP multicast or gossip protocol, but doesn't auto-connect to anything.
As a consequence the connectivity between bridges has to be statically configured in one bridge connecting to the other (or several other bridges) via the -e
command line option, or via the connect
section in configuration file.
If required, the automatic connection to other discovered bridges (also running in router
mode) can be enabled adding such configuration:
scouting: {
multicast: {
autoconnect: { router: "router" }
},
gossip: {
autoconnect: { router: "router" }
}
},
Prior to v0.11.0, the zenoh-bridge-ros2dds
was by default started in peer
mode.
It was listening for incoming TCP connections on a random port (chosen by the OS), and was automatically connecting to any discovered bridge, router or peer.
Deploying a zenoh-bridge-ros2dds
in each robot and configuring each with its own namespace brings several benefits:
- No need to configure each ROS Node with a namespace. As the DDS traffic between all Nodes of a single robot remains internal to the robot, no namespace needs to be configured
- Configuring each
zenoh-bridge-ros2dds
withnamespace: "/botX"
(where'X'
is a unique id), each topic/service/action name routed to Zenoh is prefixed with"/botX"
. Robots messages are not conflicting with each other. - On a monitoring/controlling host, you have 2 options:
- Run a
zenoh-bridge-ros2dds
withnamespace: "/botX"
corresponding to 1 robot. Then to monitor/operate that specific robot, just any ROS Node without namespace.
E.g.:rviz2
- Run a
zenoh-bridge-ros2dds
without namespace. Then you can monitor/operate any robot remapping the namespace to the robot's one, or each topic/service/action name you want to use adding the robot's namespace as a prefix.
E.g.:rviz2 --ros-args -r /tf:=/botX2/tf -r /tf_static:=/botX/tf_static
- Run a
NOTE: the bridge prefixes ALL topics/services/actions names with the configured namespace, including /rosout
, /parameter_events
, /tf
and /tf_static
.
The bridge exposes some internal states via a Zenoh admin space under @/<id>/ros2/**
, where <id>
is the unique Zenoh ID of the bridge (configurable).
This admin space can be queried via Zenoh get()
operation. If the REST plugin is configured for the bridge for instance via --rest-http-port 8000
argument, such URLs can be queried:
http://\<bridge-IP\>:8000/@/local/ros2/node/**
: to get all ROS nodes with their interfaces discovered by the bridgehttp://\<bridge-IP\>:8000/@/local/ros2/dds/**
: to get all the DDS Readers/Writers discovered by the bridgehttp://\<bridge-IP\>:8000/@/local/ros2/route/**
: to get all routes between ROS interfaces and Zenoh established by the bridgehttp://\<bridge-IP\>:8000/@/*/ros2/node/**
: to get all ROS nodes discovered by all bridgeshttp://\<bridge-IP\>:8000/@/*/ros2/route/**/cmd_vel
: to get all routes between established by all bridges oncmd_vel
topic