This repository contains a collection of helper resources to simplify the implementation of ROS 2 applications which use the APIs provided by RTI Connext DDS Connectivity Framework.
- How to use this repository in your ROS 2 package
- DDS Node API
- CMake Helpers
- Other useful resources
Add the packages you wish to yous to your package's package.xml
, and then load
them in your CMakeLists.txt
. For example, to use rclcpp_dds
:
-
package.xml
:<package format="3"> <name>my_package</name> <!-- ... --> <depend>rclcpp_dds</depend> <!-- ... --> </package>
-
CMakeLists.txt
-
Load
rclcpp_dds
as a dependency:find_package(rclcpp_dds REQUIRED)
-
Have build targets link
rclcpp_dds
' library:ament_target_dependencies(my_build_target rclcpp_dds)
-
Export
rclcpp_dds
as a dependency:ament_export_dependencies(rclcpp_dds)
-
Package ros2dds
contains a helper API which ROS 2 applications
may use to simplify the creation of "native" DDS endpoints.
The API simplifies the integration of these endpoints in a ROS 2 system by automatically applying some of the ROS 2 naming conventions.
These APIs can also be accessed by extending rclcpp_dds::DDSNode
, which is
a subclass of rclcpp::Node
provided by package rclcpp_dds
.
In order to use the DDS Node API, a ROS 2 application must be running with
rmw_connextdds
as its underlying
RMW layer.
Typically, you will extend rclcpp_dds::DDSNode
, and use the provided methods
similarly to how you normally use the rclcpp::Node
API:
#include "rclcpp_dds/rclcpp_dds.hpp"
#include "std_msgs/msg/String.hpp"
class MyNode : public rclcpp_dds::DDSNode
{
public:
using std_msgs::msg::String;
explicit MyNode(const rclcpp::NodeOptions & options)
: DDSNode("my_node", options)
{
reader_ = this->create_datareader<String>("chatter");
this->set_data_callback<String>(
reader_,
[this](const String & msg)
{
RCLCPP_INFO(this->get_logger(), "I heard: [%s]", msg.data().c_str());
});
writer_ = this->create_datawriter<String>("chatter");
using namespace std::chrono_literals;
publish_timer_ = this->create_wall_timer(1s,
[this](){
String msg("Hello Connext!");
writer_.write(msg);
});
}
private:
dds::pub::DataWriter<String> writer_;
dds::sub::DataReader<String> reader_;
rclcpp::TimerBase::SharedPtr publish_timer_;
};
Example usage:
#include "rclcpp_dds/rclcpp_dds.hpp"
#include "std_msgs/msg/String.hpp"
class MyNode : public rclcpp_dds::DDSNode
{
public:
explicit MyNode(const rclcpp::NodeOptions & options)
: DDSNode("my_node", options)
{
using std_msgs::msg::String;
// Create a DataWriter for ROS topic "chatter" (i.e. DDS topic "rt/chatter").
auto writer = this->create_datawriter<String>("chatter");
// Create a DataWriter for ROS topic "chatter" with custom QoS.
dds::pub::qos::DataWriterQos writer_qos;
auto writer_custom_qos = this->create_datawriter<String>("chatter", writer_qos);
// Create a DataWriter for a custom DDS topic "my_custom_topic".
dds::topic::Topic topic(this->domain_participant(), "my_custom_topic");
auto writer_custom_topic = this->create_datawriter<String>(topic);
}
};
Example usage:
class MyNode : public rclcpp_dds::DDSNode
{
public:
explicit MyNode(const rclcpp::NodeOptions & options)
: DDSNode("my_node", options)
{
using std_msgs::msg::String;
// Create a DataReader for ROS topic "chatter" (i.e. DDS topic "rt/chatter").
auto reader = this->create_datareader<String>("chatter");
// Create a DataReader for ROS topic "chatter" with custom QoS.
dds::sub::qos::DataReaderQos reader_qos;
auto reader_custom_qos = this->create_datareader<String>("chatter", reader_qos);
// Create a DataReader for a custom DDS topic "my_custom_topic".
dds::topic::Topic topic(this->domain_participant(), "my_custom_topic");
auto writer_custom_topic = this->create_datawriter<String>(topic);
}
};
The Connext Node API uses the ROS 2 and Connext DDS APIs to provide easy access to the DDS DomainParticipant used by a ROS 2 node.
This DomainParticipant is created by the ROS 2 middleware layer (RMW) whenever
a ROS 2 context is initialized by a ROS 2 application (e.g. by means of
rclcpp::init()
).
The DomainParticipant is shared by all nodes associated with a context.
Applications built using the Connext Node API will rarely need to access the underlying DomainParticipant directly, since all public functions take a node reference and automatically retrieve it's associated DomainParticipant.
Return the id of the DDS domain joined by a node.
Example usage:
#include "rclcpp_dds/rclcpp_dds.hpp"
void print_domain(rclcpp_dds::DDSNode & node) {
std::cout << "Node " << node.get_name() << " is part of DDS domain " <<
node.domain_id() << "\n";
}
Access the DDS DomainParticipant associated with a node.
This function will return the first DomainParticipant created within the
application's DomainParticipantFactory on the DDS domain returned by
DDSNode::domain_id()
.
WARNING It is possible for (advanced) applications to create multiple ROS 2 contexts, and thus multiple DDS DomainParticipants.
If multiple DomainParticipants on the same DDS domain are created by a single process, Connext Node API will likely fail to return the correct DomainParticipant for two nodes on the same domain but on different context.
Example usage:
#include "rclcpp_dds/rclcpp_dds.hpp"
void print_domain(rclcpp_dds::DDSNode & node) {
std::cout << "Node " << node.get_name() << " is part of DDS domain " <<
node.domain_participant().domain_id() << "\n";
}
Example usage:
#include "rclcpp_dds/rclcpp_dds.hpp"
#include "std_msgs/msg/String.hpp"
void create_topic(rclcpp_dds::DDSNode & node)
{
// Create a topic and retain it for later use
auto topic = node.create_topic<std_msgs::msg::String>("my_topic");
topic.retain();
}
Example usage:
#include "ros2dds/ros2dds.hpp"
#include "std_msgs/msg/String.hpp"
bool has_topic(rclcpp_dds::DDSNode & node)
{
// The function will return nullptr (or dds::core::nullptr)
// if the topic doesn't exist.
auto topic = node.lookup_topic<std_msgs::msg::String>("my_topic");
return nullptr != topic;
}
Example usage:
#include "rclcpp_dds/rclcpp_dds.hpp"
#include "std_msgs/msg/String.hpp"
void create_content_filtered_topic(rclcpp_dds::DDSNode & node)
{
using std_msgs::msg::String;
auto topic = node.create_content_filtered_topic<std_msgs::msg::String>(
"my_topic", "my_custom_filter", "data LIKE 'Hello Connext!'");
topic.retain();
}
Once loaded in a CMakeList.txt
, connext_node_helpers
offers several CMake
functions that can be used to facilitate some common build task for
ROS 2/Connext applications.
Generate type support code for multiple data types and build it into a single shared library.
Function connext_generate_typesupport_library
simplifies the process of
defining custom data types for a ROS 2/Connext application using standard
OMG IDL syntax.
connext_generate_typesupport_library
takes a list of IDL files, and it
will generate type support code to use the data types they define with
Connext DDS' Modern C++ API
using rtiddsgen
.
The input IDL files can be specified in multiple ways:
-
As ROS message identifiers of the form
<message-namespace>/<message-name>
, e.g.std_msgs/String
. Use argumentMESSAGES
to specify IDL files with this syntax. -
As ROS service identifiers of the form
<service-namespace>/<service-name>
, e.g.std_srvs/Trigger
. Use argumentSERVICES
to specify IDL files with this syntax. -
As IDL file paths with an optional, slash-separated, namespace, using syntax
<idl-file-path>[@<idl-namespace>]
. Use argumentIDLS
to specify IDL files with this syntax.
When IDL files are specified as ROS messages and/or services, the function will
automatically try to load the containing package using find_package(REQUIRED)
.
Once the package has been loaded, the function will try to load the IDL files
that are automatically generated by ROS 2 for every custom interface. These
files must be installed under <package-install-prefix>/share/<package>/msg
and
<package-install-prefix>/share/<package>/srv
.
WARNING Not all of the IDL files that are automatically generated by ROS 2
can be correctly parsed by rtiddsgen
. If you plan on using the "standard" ROS
types with native Connext DDS endpoints, consider using package connext_msgs
which contains slightly modified (yet still interoperable) versions of these
types that can be successfully fed to rtiddsgen
.
The function offers a few more options. Some of these are similar to the ones
accepted by connext_generate_message_typesupport_cpp
which this function uses to actually define a code generation target for each
input IDL.
DEPENDS
(List)- List of targets which will be added as a dependency to each code generation target.
EXPORT_TYPES_LIST
(String)- Name of a variable which will be set with the list of IDL files included in the library.
INCLUDES
(List)- List of directories which will be added to the include path of the generated library.
INSTALL_PREFIX
(String)- Installation prefix where the generated header files will be installed.
Default:
include/
.
- Installation prefix where the generated header files will be installed.
Default:
NO_DEFAULT_INCLUDES
(Boolean)- By default, the function will automatically add the directory of each input
IDL to
rtiddsgen
's include path, to enable#include
directives that may be contained in the files. When a large number of files is provided, this may cause the resulting command line to be too large to be parsed byrtiddsgen
, particularly if running in server mode. - If this variable is true, no additional directory will be automatically added to the include path.
- By default, the function will automatically add the directory of each input
IDL to
SERVER
(Boolean)- Run
rtiddsgen
in server mode. This can be speed up code generation considerably, and it should always be used when processing a large number of input files.
- Run
WORKING_DIRECTORY
(String)- Directory from where to invoke
rtiddsgen
. Defaults to${CMAKE_CURRENT_SOURCE_DIR}
.
- Directory from where to invoke
ZEROCOPY
(Boolean)- Link the generated library with the Connext DDS libraries required to support Zero Copy Transfer Over Shared Memory.
Example usage:
find_package(connext_node_helpers REQUIRED)
connext_generate_typesupport_library(my_connext_types_lib
MESSAGES
std_msgs/String
std_msgs/Header
builtin_interfaces/Time
sensor_msgs/PointField
sensor_msgs/PointCloud2
IDLS
idl/my/custom/ns/MyType.idl@my/custom/ns
idl/SomeTypesWithoutNamespace.idl
ZEROCOPY
)
Generates type support code with rtiddsgen
from an input IDL file.
connext_generate_message_typesupport_cpp
is similar to
connext_generate_typesupport_library
, but it provides lower-level functionality,
only to defines a code generation target, and not to actually build the
generated files.
The function returns a list of generated files in an output variable named <type_pkg>_<type>_FILES
.
It is up to the caller to consume this list appropriately, typically by
pasing it add_executable()
or add_library()
.
The compilation target will also need to be configured with the appropriate
include directories. By default, all files will be generated in
${CMAKE_CURRENT_BINARY_DIR}/rtiddsgen/<pkg>
, and it is sufficient to add
${CMAKE_CURRENT_BINARY_DIR}/rtiddsgen
to the include path (since all files
should always be included as #include "<pkg>/<type>.idl"
).
Example usage:
find_package(connext_node_helpers REQUIRED)
# Generate type support for type `std_msgs::msg::String`.
# When using this syntax, the first argument is the "base name" of the type
# and the PACKAGE argument must always be specified to qualify the type.
# The list of generated files will be stored as `${std_msgs_String_FILES}`.
# The generated code must be included with `#include "std_msgs/msg/String.hpp"`.
find_package(std_msgs REQUIRED)
connext_generate_message_typesupport_cpp(String PACKAGE std_msgs)
# Generate type support from a typical IDL file for Connext.
# In this case, the input is the path to the input file, and the
# PACKAGE argument can be used to specify an optional "include prefix".
# Generated files will be available as `${my_custom_ns_MyType_FILES}`
# The generated code must be included with `#include "my/custom/ns/MyType.hpp"`.
connext_generate_message_typesupport_cpp(idl/my/custom/ns/MyType.idl
PACKAGE my/custom/ns
)
# Generated files will be available as `${SomeTypesWithoutNamespace_FILES}`
# The generated code must be included with `#include "SomeTypesWithoutNamespace.hpp"`.
connext_generate_message_typesupport_cpp(idl/SomeTypesWithoutNamespace.idl)
add_executable(my_app
main.c
${std_msgs_String_FILES}
${my_custom_ns_MyType_FILES}
${SomeTypesWithoutNamespace_FILES}
)
target_include_directories(my_app
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/rtiddsgen
)
Generate an executable to "spin up" a ROS 2/Connext node.
Function connext_components_register_node
is similar to
rclcpp_components_register_node
. The function takes a node from a component
library, and generates an executable to spin it in a main()
function.
This function differs from rclcpp_components_register_node
in that the
generate executable will link Connext DDS directly, and it will also be
properly linked in order to allow sharing of the DomainParticipantFactory
between the node component and the RMW layer.
Example usage:
find_package(connext_node_helpers REQUIRED)
# First create a library with node components
add_library(my_node_components SHARED
my_node.cpp)
ament_target_dependencies(my_node_components
rclcpp
rclcpp_components)
target_link_libraries(my_node_components
RTIConnextDDS::cpp2_api)
# Then register each component into its own executable
connext_components_register_node(my_node_components
PLUGIN "my_node::MyNode"
EXECUTABLE my_node)
Build a ROS 2/DDS C++ application.
This function can be used to simplify the definition of a build target for a ROS 2 C++ application that requires direct access to RTI Connext DDS. The function will take care of most "boiler plate" commands required to configure the target with the required Connext DDS dependencies and the appropriate linkage to share DDS entities with the RMW layer.
Example usage:
find_package(connext_node_helpers REQUIRED)
find_package(std_msgs REQUIRED)
# Generate type support files
connext_generate_message_typesupport_cpp(String
PACKAGE std_msgs)
# Build application and generated files
connext_add_executable(
NAME my_app
SOURCES
my_app.cpp
${std_msgs_String_FILES}
INCLUDES
${CMAKE_CURRENT_BINARY_DIR}/rtiddsgen)
rticonnextdds-ros2-msgs
- Helper library containing C++11 message type supports generated with
rtiddsgen
for almost every type include in ROS 2.
- Helper library containing C++11 message type supports generated with