ROS 2 middleware implementations
ROS middleware implementations are sets of packages that provide the underlying communication framework for ROS 2.
These packages interact with core ROS 2 interfaces such as the rmw
, rcl
, and rosidl
APIs to integrate with external protocols like Zenoh, DDS, or others.
For example, rmw_fastrtps_cpp
adapts eProsima’s Fast DDS implementation to ROS 2’s middleware API, while rmw_zenoh_cpp
provides similar integration for the Zenoh protocol.
Common Packages for DDS Middleware Implementations
Many of the ROS 2 middleware solutions are based on full or partial DDS implementations. For example, there are middleware implementations that use RTI’s Connext DDS, eProsima’s Fast DDS and GurumNetworks’ GurumDDS. These DDS-based implementations share some common packages and patterns.
In the ros2/rosidl_dds repository on GitHub, there is the following package:
rosidl_generator_dds_idl
: provides tools to generate DDS.idl
files fromrosidl
files, e.g..msg
files,.srv
files, etc.
The rosidl_generator_dds_idl
package generates a DDS .idl
file for each ROS interface definition file (.msg
, .srv
, .action
, etc.) found in ROS packages.
These interface definition files specify the data structures used for topics, services, and actions in ROS 2.
DDS-based ROS middleware implementations then use these generated .idl
files to create vendor-specific pre-compiled type support.
Structure of DDS Middleware Implementations
A DDS-based ROS middleware implementation typically includes, but is not limited to, these packages in a single repository:
<implementation_name>_cmake_module
: contains CMake Module for discovering and exposing required dependenciesrmw_<implementation_name>_<language>
: contains the implementation of the RMW API in a particular language, typically C++rosidl_typesupport_<implementation_name>_<language>
: contains tools to generate static type support code forrosidl
files, tailored to the implementation in a particular language, typically C or C++
The <implementation_name>_cmake_module
package contains any CMake Modules and functions needed to find the supporting dependencies for the middleware implementation.
For example, rti_connext_dds_cmake_module
provides wrapper logic around the CMake Module shipped with RTI Connext DDS to make sure that all packages that depend on it will select the same installation of RTI Connext DDS.
Similarly, fastrtps_cmake_module
includes a CMake Module to find eProsima’s Fast DDS and gurumdds_cmake_module
includes a CMake Module to find GurumNetworks GurumDDS.
Not all implementations will have a package like this: for example, Eclipe’s Cyclone DDS already provides a CMake Module which is used directly by its RMW implementation without the need of additional wrappers.
The rmw_<implementation_name>_<language>
package implements the rmw
C API in a particular language.
The implementation itself can be C++, it just must expose the header’s symbols as extern "C"
so that C applications can link against it.
The rosidl_typesupport_<implementation_name>_<language>
package provides a generator which generates DDS code in a particular language.
This is done using the .idl
files generated by the rosidl_generator_dds_idl
package and the DDS IDL code generator provided by the DDS vendor.
It also generates code for converting ROS message structures to and from DDS message structures.
This generator is also responsible for creating a shared library for the message package it is being used in, which is specific to the messages in the message package and to the DDS vendor being used.
As mentioned above, the rosidl_typesupport_introspection_<language>
may be used instead of a vendor specific type support package if an RMW implementation supports runtime interpretation of messages.
This ability to programmatically send and receive types over topics without generating code beforehand is achieved by supporting the DDS X-Types Dynamic Data standard.
As such, RMW implementations may provide support for the X-Types standard, and/or provide a package for type support generated at compile time specific to their DDS implementation.
For examples of example of DDS RMW implementation repositories,
Structure of the Zenoh Middleware Implementation
For data to be sent and received over Zenoh using ROS 2, the middleware package, rmw_zenoh_cpp
, maps the ROS 2 middleware API to Zenoh’s APIs using zenoh-c.
Unlike DDS-based implementations, this middleware relies on a Zenoh router to discover peers and pass discovery information along via Zenoh’s ‘gossip scouting’.
Therefore, rmw_zenoh_cpp
requires the Zenoh router (zenohd
) to be active on the local system or reachable over the network.
In ROS 2’s Zenoh integration, each context is mapped to a single Zenoh session. This session is shared across all publishers, subscriptions, services, and clients within that context. The context maintains a local graph cache that tracks the network topology of ROS 2 entities and the presence of each entity is managed through unique liveliness tokens issued on creation and revoked during destruction.
Here is an inexhaustive list of how the Zenoh middleware API adapts ROS 2 entities over its communication protocol:
Nodes: Nodes in ROS 2 have been referred to as “units of computation” in a ROS 2 graph, whereby each node should be responsible for a single, modular purpose.
Zenoh has no direct counterpart to the node, so rmw_zenoh_cpp
creates no Zenoh entities for them.
However, when a node is created through the RMW API, a liveliness token of type NN
is declared.
Publishers: A ROS 2 publisher sends data to a specific topic.
Because Zenoh publishers function very similarly with Keys, rmw_zenoh_cpp
maps these entities directly.
When a publisher is created through the RMW API, a liveliness token of type MP
is declared.
Subscribers: Subscribers in ROS 2 listen on topics for new data.
They are conceptually equivalent to subscribers in Zenoh so rmw_zenoh_cpp
maps these entities directly.
When new data arrives, Zenoh’s middleware package invokes an internal callback that takes ownership of the data and signals availability to rmw_wait
.
When a subscriber is created through the RMW API, a liveliness token of type MS
is declared.
Service clients: rmw_zenoh_cpp
uses Zenoh queryables to implement ROS 2 services.
Clients use rmw_send_request
to make requests in ROS 2.
A request will carry metadata that will be used to correlate a response, like its sequence number and the GUID of the client that sent it.
Zenoh’s middleware package can then use z_get
to send a query out into the network.
When a client is created through the RMW API, a liveliness token of type SC
is declared.
Service server: rmw_zenoh_cpp
uses Zenoh queryables to implement ROS 2 services.
ROS 2 nodes use rmw_create_service
to advertise services to the network and the Zenoh API, z_declare_queryable
, is used to create the server-side representation of a ROS 2 service.
rmw_take_request
delivers the query to the use callback to be processed and after the computation is complete, rmw_send_reponse
returns the result to the requester.
When a server is created, a liveliness token of type SS
is declared.
The RMW implementation for Zenoh
is on GitHub at ros/rmw_zenoh.