Home

Awesome

<img src="https://raw.githubusercontent.com/eclipse-zenoh/zenoh/main/zenoh-dragon.png" height="150"> <!--- [![CI](https://github.com/eclipse-zenoh/zenoh-plugin-dds/workflows/Rust/badge.svg)](https://github.com/eclipse-zenoh/zenoh-plugin-dds/actions?query=workflow%3ARust) --->

Discussion Discord License License

Eclipse Zenoh

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.


DDS plugin and standalone zenoh-bridge-dds

:point_right: Install latest release: see below

:point_right: Docker image: see below

:point_right: Build "main" branch: see below

Background

The Data Distribution Service (DDS) is a standard for data-centric publish subscribe. Whilst DDS has been around for quite some time and has a long history of deployments in various industries, it has recently gained quite a bit of attentions thanks to its adoption by the Robotic Operating System (ROS 2) -- where it is used for communication between ROS 2 nodes.

⚠️ On usage with ROS 2 ⚠️

This plugin is based on the DDS standard, and thus can work with ROS 2 to some extent.

However we strongly advise ROS 2 users to rather try the new zenoh-plugin-ros2dds which is dedicated to the support of ROS 2 with DDS. Thanks to a better integration with ROS 2 concepts, this new plugin comes with those benefits:

This Zenoh plugin for DDS will eventually be deprecated for ROS 2 usage.

Plugin or bridge ?

This software is built in 2 ways to choose from:

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.

How to install it

To install the latest release of either the DDS plugin for the Zenoh router, either the zenoh-bridge-dds standalone executable, you can do as follows:

Manual installation (all platforms)

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:

Linux Debian

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:

How to build it

:warning: WARNING :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: WARNING :warning: : As Rust doesn't have a stable ABI, the plugins should be built with the exact same Rust version than zenohd, and using for zenoh dependency the same version (or commit number) than 'zenohd'. Otherwise, incompatibilities in memory mapping of shared types between zenohd 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:

rustup update

Once these dependencies are in place, you may clone the repository on your machine:

git clone https://github.com/eclipse-zenoh/zenoh-plugin-dds.git
cd zenoh-plugin-dds
cargo build --release

The standalone executable binary zenoh-bridge-dds 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.

Enabling Cyclone DDS Shared Memory Support

Cyclone DDS Shared memory support is provided by the Iceoryx PSMX plugin based on the Iceoryx library. Iceoryx introduces additional system requirements which are documented here.

Note: To ensure successful communication the entire system should be built to use the same version of the Iceoryx Library. The Zenoh DDS Plugin currently uses Iceoryx v2.0.5.

To build the zenoh bridge for DDS with support for shared memory the dds_shm optional feature must be enabled during the build process as follows:

cargo build --release -p zenoh-plugin-dds --features dds_shm
cargo build --release -p zenoh-bridge-dds --features dds_shm

Note: Iceoryx does not need to be installed to build the bridge when the dds_shm feature is enabled. Iceoryx will be automatically downloaded, compiled, and statically linked into the zenoh bridge as part of the cargo build process.

When the zenoh bridge is configured to use DDS shared memory (see Configuration) the Iceoryx RouDi daemon (iox-roudi) must be running in order for the bridge to start successfully. If not started the bridge will wait for a period of time for the daemon to become available before timing out and terminating.

When building the zenoh bridge with the dds_shm feature enabled the iox-roudi daemon is also built for convenience. The daemon can be found under target/debug|release/build/cyclors-<hash>/out/iceoryx-build/bin/iox-roudi.

See here for more details of shared memory support in Cyclone DDS.

Shared Memory Limitations

The following limitations apply to Cyclone DDS shared memory support in the plugin:

DDS Library Symbol Prefixing

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-ros2dds 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 DDS plugin with prefixed DDS library symbols. e.g.

cargo build --release -p zenoh-plugin-dds --features prefix_symbols
cargo build --release -p zenoh-bridge-dds --features prefix_symbols

Note: The prefix_symbols feature cannot be used at the same time as the dds_shm feature.

ROS 2 package

:warning: Please consider using zenoh-bridge-ros2dds which is dedicated to ROS 2.

If you're a ROS 2 user, you can also build zenoh-bridge-dds as a ROS package running:

rosdep install --from-paths . --ignore-src -r -y
colcon build --packages-select zenoh_bridge_dds --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_dds --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.

Docker image

The zenoh-bridge-dds standalone executable is also available as a Docker images for both amd64 and arm64. To get it, do:

:warning: However, notice that it's usage is limited to Docker on Linux and using the --net host option.
The cause being that DDS uses UDP multicast and Docker doesn't support UDP multicast between a container and its host (see cases moby/moby#23659, moby/libnetwork#2397 or moby/libnetwork#552). The only known way to make it work is to use the --net host option that is only supported on Linux hosts.

Usage: docker run --init --net host eclipse/zenoh-bridge-dds
It supports the same command line arguments than the zenoh-bridge-dds (see below or check with -h argument).


Usage

The use cases of this Zenoh plugin for DDS are various:

Configuration

zenoh-bridge-dds can be configured via a JSON5 file passed via the -cargument. You can see a commented example of such configuration file: DEFAULT_CONFIG.json5.

The "dds" 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-dds also accepts the following arguments. If set, each argument will override the similar setting from the configuration file:

Admin space

The zenoh bridge for DDS exposes an administration space allowing to browse the DDS entities that have been discovered (with their QoS), and the routes that have been established between DDS and zenoh. This administration space is accessible via any zenoh API, including the REST API that you can activate at zenoh-bridge-dds startup using the --rest-http-port argument.

Starting from version 0.11.0-rc.2, the zenoh-bridge-dds exposes this administration space with paths prefixed by @/<uuid>/dds (where <uuid> is the unique identifier of the bridge instance). The information is then organized with such paths:

For previous versions, see the corresponding version of README.md: 0.10.1-rc.

Example of queries on administration space using the REST API with the curl command line tool (don't forget to activate the REST API with --rest-http-port 8000 argument):

Pro tip: pipe the result into jq command for JSON pretty print or transformation.

Architecture details

The zenoh bridge for DDS discovers all DDS Writers and Readers in a DDS system and routes each DDS publication on a topic T as a Zenoh publication on key expression T. In the other way, assuming a DDS Reader on topic T is discovered, it routes each Zenoh publication on key expression T as a DDS publication on topic T.

The bridge doesn't deserialize the DDS data which are received from DDS Writer as a SerializedPayload with the representation defined in §10 of the DDS-RTPS specification. Therefore, the payload published from any Zenoh application for a DDS Reader served by the bridge must have such format:

 0...2...........8...............16..............24..............32
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |   representation_identifier   |    representation_options     |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 ~                                                               ~
 ~ ... Bytes of data representation using a format that ...      ~
 ~ ... depends on the RepresentationIdentifier and options ...   ~
 ~                                                               ~
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Where the first 4 bytes (representation_identifier and representation_options) are usually {0x00, 0x0} for Big Endian encoding or {0x00, 0x01} for Little Endian encoding, and the remaining bytes are the data encoded in CDR.

In details, whether it's built as a library or as a standalone executable, it does the same things:

Mapping of DDS topics to zenoh keys

The mapping between DDS and zenoh is rather straightforward: given a DDS Reader/Writer for topic A without the partition QoS set, then the equivalent zenoh key will have the same name: A. If a partition QoS P is defined, the equivalent zenoh key will be named as P/A.

Optionally, the bridge can be configured with a scope that will be used as a prefix to each zenoh key. That is, for scope S the equivalent zenoh key will be:

Mapping ROS 2 names to zenoh keys

The mapping from ROS 2 topics and services name to DDS topics is specified here. Notice that ROS 2 does not use the DDS partitions.
As a consequence of this mapping and of the DDS to zenoh mapping specified above, here are some examples of mapping from ROS 2 names to zenoh keys:

ROS2 namesDDS Topics nameszenoh keys (no scope)zenoh keys (if scope="myscope")
topic: /rosoutrt/rosoutrt/rosoutmyscope/rt/rosout
topic: /turtle1/cmd_velrt/turtle1/cmd_velrt/turtle1/cmd_velmyscope/rt/turtle1/cmd_vel
service: /turtle1/set_penrq/turtle1/set_penRequest<br>rr/turtle1/set_penReplyrq/turtle1/set_penRequest<br>rr/turtle1/set_penReplymyscope/rq/turtle1/set_penRequest<br>myscope/rr/turtle1/set_penReply
action: /turtle1/rotate_absoluterq/turtle1/rotate_absolute/_action/send_goalRequest<br>rr/turtle1/rotate_absolute/_action/send_goalReply<br>rq/turtle1/rotate_absolute/_action/cancel_goalRequest<br>rr/turtle1/rotate_absolute/_action/cancel_goalReply<br>rq/turtle1/rotate_absolute/_action/get_resultRequest<br>rr/turtle1/rotate_absolute/_action/get_resultReply<br>rt/turtle1/rotate_absolute/_action/status<br>rt/turtle1/rotate_absolute/_action/feedbackrq/turtle1/rotate_absolute/_action/send_goalRequest<br>rr/turtle1/rotate_absolute/_action/send_goalReply<br>rq/turtle1/rotate_absolute/_action/cancel_goalRequest<br>rr/turtle1/rotate_absolute/_action/cancel_goalReply<br>rq/turtle1/rotate_absolute/_action/get_resultRequest<br>rr/turtle1/rotate_absolute/_action/get_resultReply<br>rt/turtle1/rotate_absolute/_action/status<br>rt/turtle1/rotate_absolute/_action/feedbackmyscope/rq/turtle1/rotate_absolute/_action/send_goalRequest<br>myscope/rr/turtle1/rotate_absolute/_action/send_goalReply<br>myscope/rq/turtle1/rotate_absolute/_action/cancel_goalRequest<br>myscope/rr/turtle1/rotate_absolute/_action/cancel_goalReply<br>myscope/rq/turtle1/rotate_absolute/_action/get_resultRequest<br>myscope/rr/turtle1/rotate_absolute/_action/get_resultReply<br>myscope/rt/turtle1/rotate_absolute/_action/status<br>myscope/rt/turtle1/rotate_absolute/_action/feedback
all parameters for node turtlesimrq/turtlesim/list_parametersRequest<br>rr/turtlesim/list_parametersReply<br>rq/turtlesim/describe_parametersRequest<br>rr/turtlesim/describe_parametersReply<br>rq/turtlesim/get_parametersRequest<br>rr/turtlesim/get_parametersReply<br>rr/turtlesim/get_parameter_typesReply<br>rq/turtlesim/get_parameter_typesRequest<br>rq/turtlesim/set_parametersRequest<br>rr/turtlesim/set_parametersReply<br>rq/turtlesim/set_parameters_atomicallyRequest<br>rr/turtlesim/set_parameters_atomicallyReplyrq/turtlesim/list_parametersRequest<br>rr/turtlesim/list_parametersReply<br>rq/turtlesim/describe_parametersRequest<br>rr/turtlesim/describe_parametersReply<br>rq/turtlesim/get_parametersRequest<br>rr/turtlesim/get_parametersReply<br>rr/turtlesim/get_parameter_typesReply<br>rq/turtlesim/get_parameter_typesRequest<br>rq/turtlesim/set_parametersRequest<br>rr/turtlesim/set_parametersReply<br>rq/turtlesim/set_parameters_atomicallyRequest<br>rr/turtlesim/set_parameters_atomicallyReplymyscope/rq/turtlesim/list_parametersRequest<br>myscope/rr/turtlesim/list_parametersReply<br>myscope/rq/turtlesim/describe_parametersRequest<br>myscope/rr/turtlesim/describe_parametersReply<br>myscope/rq/turtlesim/get_parametersRequest<br>myscope/rr/turtlesim/get_parametersReply<br>myscope/rr/turtlesim/get_parameter_typesReply<br>myscope/rq/turtlesim/get_parameter_typesRequest<br>myscope/rq/turtlesim/set_parametersRequest<br>myscope/rr/turtlesim/set_parametersReply<br>myscope/rq/turtlesim/set_parameters_atomicallyRequest<br>myscope/rr/turtlesim/set_parameters_atomicallyReply
specific ROS discovery topicros_discovery_inforos_discovery_infomyscope/ros_discovery_info