ROS 2 Tutorial¶
This section serves as a comprehensive guide to assist in creating and understanding the key points of a basic Safe DDS application that successfully communicates with a ROS2 application.
Note
It is recommended to first read the Getting Started section to gain a better understanding of the basic concepts and terminology used in this section.
Download Code
Try this example by downloading the code from this CMake project.
Create a CMake project¶
Create a CMake project with a folder structure as shown below:
ros2_example
├── CMakeLists.txt
└── src
└── main.cpp
After the project structure folder has been created, a minimal version of the CMakeLists.txt file can be generated as follows:
mkdir ros2_example
cd ros2_example
touch CMakeLists.txt
The CMakeLists.txt file is a crucial component, as it contains the build instructions for the project. The following example CMakeLists.txt file is provided to assist in properly configuring the project’s build infrastructure:
cmake_minimum_required(VERSION 3.5)
project(tutorial_ros2)
find_package(safedds REQUIRED)
file(GLOB SRCS ./src/*.cpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fno-exceptions -fno-rtti -Wall -Werror -Wextra -Wpedantic")
add_executable(${CMAKE_PROJECT_NAME} ${SRCS})
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE include)
target_link_libraries(${CMAKE_PROJECT_NAME} safedds)
Safe DDS application¶
After the project folder and CMakeLists.txt file have been set up, the next step is to create a main.cpp file that will contain the application’s source code:
mkdir src
cd src
touch main.cpp
To further illustrate a successful communication between Safe DDS and ROS 2, a simple application that publishes a ROS 2 std_msgs/msg/String message on HelloWorldTopicROS2 topic is presented.
To achieve this, the application requires both the DDS headers and a type support.
This tutorial will provide a detailed focus on the essential considerations when establishing connections with ROS 2. For additional information, please refer to the Getting Started section.
TypeSupport¶
To successfully integrate with ROS 2, it is necessary to register a Type Support that aligns with a compatible message format in Safe DDS. Detailed instructions on managing Type Supports in Safe DDS can be found in the Typesupport section.
Type Name¶
To ensure compatibility between DDS types and ROS 2 types, it is necessary to adhere to a specific convention.
For instance, when using a DDS type that matches the ROS 2 type std_msgs/String, the corresponding DDS type name would be std_msgs::msg::dds_::String_.
This convention requires using the DDS type name version of the ROS 2 type.
In this case, the DDS type name is derived from the ROS 2 type by appending ::msg::dds_:: before the actual type name.
This convention is fully documented in the official ROS 2 Design Documents.
// Type name
memory::container::StaticString<100> type_name("std_msgs::msg::dds_::String_");
Note
The ros2_utils folder, located within the extra_support_packages directory, provides a suite of utilities designed to facilitate ROS 2 integration.
This package includes the ROS2Mangler class, which streamlines the conversion process between ROS 2 and DDS type names.
The example below demonstrates a basic usage scenario:
std::string msg_type_name = "std_msgs/msg/msg_type_name";
std::string ros2_msg_type_name = ROS2Mangler::get_ros_type(msg_type_name);
// ros2_msg_type_name >> std_msgs::msg::dds_::msg_type_name_
std::string srv_type_name = "example_interfaces/srv/srv_type_name";
std::string ros2_srv_type_name = ROS2Mangler::get_ros_type(srv_type_name);
// ros2_srv_type_name >> example_interfaces::srv::dds_::srv_type_name_
Topic Name¶
In ROS 2, topic names are specified using a specific convention, as detailed in the official ROS 2 Design Documents.
To properly define a ROS 2 topic name, it is necessary to append rt/ before the DDS topic name.
// Topic name
memory::container::StaticString<100> topic_name("rt/HelloWorldTopicROS2");
Note
The ros2_utils folder, located within the extra_support_packages directory, provides a suite of utilities designed to facilitate ROS 2 integration.
This package includes the ROS2Mangler class, which streamlines the conversion process between ROS 2 and DDS topic_and_service_names names.
The example below demonstrates a basic usage scenario:
std::string topic_name = "topic_name";
std::string ros2_topic_name = ROS2Mangler::get_ros_topic(topic_name);
// ros2_topic_name >> rt/topic_name
Running the application¶
To run the application, first navigate to the project directory, create a build folder (TUTORIAL_ROS2_FOLDER on the example) and generate the build files with CMake.
cmake "$TUTORIAL_ROS2_FOLDER" -Bbuild -DCMAKE_PREFIX_PATH="$INSTALL_SAFEDDS_FOLDER"
cmake --build build
This command will configure the project with the default settings, including the path to the Safe DDS library, and it will then build the application. Once the build is complete, the application can be run by executing the binary file from the build directory:
./ros2_example
If the application is successfully connected to the network and running, the console output should show messages similar to the following:
[DW: 0] Message: HelloWorld with index: 1
[DW: 0] Message: HelloWorld with index: 2
[DW: 0] Message: HelloWorld with index: 3
[DW: 0] Message: HelloWorld with index: 4
[DW: 0] Message: HelloWorld with index: 5
[DW: 0] Message: HelloWorld with index: 6
[DW: 0] Message: HelloWorld with index: 7
[DW: 0] Message: HelloWorld with index: 8
[DW: 0] Message: HelloWorld with index: 9
[DW: 0] Message: HelloWorld with index: 10
[DW: 0] Message: HelloWorld with index: 11
[DW: 0] Message: HelloWorld with index: 12
[DW: 0] Message: HelloWorld with index: 13
The program will continue publishing messages every second until it is manually stopped.
At this point, it can be checked at the ROS 2 CLI if the topic is available:
root@machine:/# ros2 topic list
/HelloWorldTopicROS2
/parameter_events
/rosout
In order to receive the publisher messages at ROS 2 CLI, the following command can be executed:
root@machine:/# ros2 topic echo /HelloWorldTopicROS2
data: HelloWorld with index: 1
---
data: HelloWorld with index: 2
---
data: HelloWorld with index: 3
---
Finally, ROS 2 side can publish messages that will be received by Safe DDS with the following command:
root@machine:/# ros2 topic pub /HelloWorldTopicROS2 std_msgs/String 'data: Hi Safe DDS, I am ROS 2'
publisher: beginning loop
publishing #1: std_msgs.msg.String(data='Hi Safe DDS, I am ROS 2')
publishing #2: std_msgs.msg.String(data='Hi Safe DDS, I am ROS 2')
publishing #3: std_msgs.msg.String(data='Hi Safe DDS, I am ROS 2')
publishing #4: std_msgs.msg.String(data='Hi Safe DDS, I am ROS 2')
The console output should show messages similar to the following:
[DW: 0] Message: HelloWorld with index: 22
[DW: 0] Message: HelloWorld with index: 23
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 24
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 25
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 26
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 27
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 28
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 29
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 30
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 31
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 32
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 33
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 34
[DW: 16777229] Message: Hi Safe DDS, I am ROS 2
[DW: 0] Message: HelloWorld with index: 35
ROS 2 Compatibility¶
This document describes the integration of a Safe DDS application with the ROS 2 ecosystem using the utilities provided in the extra_support_packages/ros2_utils package. It covers three main areas:
ROS 2 Node
ROS 2 Services
ROS 2 Parameter Server
ROS 2 Node¶
Up to this point, a Safe DDS application that communicates with a ROS 2 has been described.
However, this application is not creating ROS 2 Nodes, just DDS Domain Participants, DataWriters and DataReaders that are visible from ROS 2.
To make the ROS 2 ecosystem aware of the Safe DDS application, a ROS 2 Node that participates in the ros_discovery_info topic must be created.
The ros2_utils folder, located within the extra_support_packages directory, provides a suite of utilities designed to facilitate ROS 2 integration.
This package includes the ROS2Node class, which streamlines the creation of DDS entities that handle the ROS 2 discovery process.
The only steps required to create a ROS 2 node are:
Instantiate a ROS 2 Node.
// ROS 2 Node ROS2Node ros2_node(*participant, "ROS2SafeNode");
Enable the Node and all the remaining entities.
// Enable entities bool enabled = true; enabled = enabled && (dds::ReturnCode::OK == ros2_node.enable()); // enabled = enabled && ...
Run the Node once before entering the application’s main loop.
if (enabled && (dds::ReturnCode::OK != ros2_node.run())) { return 1; } // Main Loop while (true) { // ... }
Note
The participant must be the last entity to be enabled.
ROS 2 Services¶
The utilities in ros2_utils also provide a simple way to create ROS 2 Services in the Safe DDS application.
It includes the ROS2ServiceClient and ROS2ServiceServer classes, which streamline the creation of DDS entities that handle ROS 2 services.
Note
ROS 2 Services require defining the Service Request and Response types. These types can be generated from IDL files using Safe DDS Gen (Check Safe DDS Gen for more information).
Under ros2_utils/idl, there is a sample IDL file demonstrating how to create the Request and Response types for the well-known AddTwoInts service.
ROS 2 Service Client
To create a ROS 2 Service Client, follow these steps:
Instantiate a ROS 2 Node as explained in section ROS 2 Node.
Instantiate the ROS 2 Service Client. It does not require an enabling step.
// Create service client, where the first template parameter is the typesupport for Request types and the second one is the typesupport for Response types using AddTwoIntsClient = ServiceClient<AddTwoIntsReqTypeSupport, AddTwoIntsResTypeSupport>; AddTwoIntsClient service_client( // DDS Domain Participant *participant, // Service name "add_two_ints", // Service type ROS2Mangler::get_ros_type("example_interfaces/srv/AddTwoInts").c_str(), // Service Client callback for handling responses [](const AddTwoIntsClient::RequestId& request_id, const typename AddTwoIntsResTypeSupport::DataType& response) -> void { std::cout << "Received response (" << request_id.sequence_number.to_int64() << ") : " << response.sum << std::endl; }, // QoS for requester ROS2RequesterQos{});
Enable and run the Node once before entering the application’s main loop as explained in section ROS 2 Node.
Make a ROS 2 Service Request.
// Main Loop while (true) { // ... // Call service client AddTwoIntsReqTypeSupport::DataType request = {}; request.a = 2; request.b = 3; AddTwoIntsClient::RequestId id = service_client.send_request(request); request_ids.push_back(id); // ... }
ROS 2 Service Server
To create a ROS 2 Service Server, follow these steps:
Instantiate a ROS 2 Node as explained in section ROS 2 Node.
Instantiate the ROS 2 Service Server. It does not require an enabling step. The callback function will be called whenever a ROS 2 Service Request is received.
// Create service server ServiceServer<AddTwoIntsReqTypeSupport, AddTwoIntsResTypeSupport> service_server( // DDS Domain Participant *participant, // Service name "add_two_ints", // Service type ROS2Mangler::get_ros_type("example_interfaces/srv/AddTwoInts").c_str(), // Service Server callback for handling requests [](const AddTwoIntsReq& req) -> AddTwoIntsRes { AddTwoIntsRes res; res.sum = req.a + req.b; return res; }, // QoS for replier ROS2ReplierQos{});
Enable and run the Node once before entering the application’s main loop as explained in section ROS 2 Node.
ROS 2 Parameter Server¶
The utilities in ros2_utils also provide a simple way to create a ROS 2 Parameter Server in the Safe DDS application.
It includes the ROS2ParameterServer class, which streamlines the creation of DDS entities that handle ROS 2 Parameters.
To create a ROS 2 Parameter Server, follow these steps:
Instantiate a ROS 2 Node as explained in section ROS 2 Node.
Instantiate the ROS2 Parameter Server.
// ROS 2 Parameter server observer class ParametersHandler : public ParameterServerObserver { // Callback to be called when a new parameter is being added. Shall return true if the parameter should be added, false otherwise. bool on_parameter_being_added( const Parameter& parameter) override { std::cout << "Parameter " << parameter.name << " is being added" << std::endl; return true; } // Callback to be called when a parameter is being updated. Shall return true if the parameter should be updated, false otherwise. bool on_parameter_being_updated( const Parameter& parameter) override { std::cout << "Parameter " << parameter.name << " is being updated" << std::endl; return true; } // Callback to be called when a parameter is being removed. Shall return true if the parameter should be removed, false otherwise. bool on_parameter_being_removed( const Parameter& parameter) override { std::cout << "Parameter " << parameter.name << " is being removed" << std::endl; return true; } }; // ROS 2 Parameter server ParametersHandler parameters_handler; ParameterServer parameter_server(ros2_node, ROS2ReplierQos{}, ¶meters_handler);
Add ROS 2 Parameters.
// String parameter parameter_server.add_parameter("string_parameter", "Hello World!"); parameter_server.set_parameter_description("string_parameter", "This is a string test parameter"); parameter_server.set_parameter_additional_constraints("string_parameter", "The string must start with 'Hello'"); // Integer parameter int64_t integer_parameter_initial_value = 42; int64_t integer_parameter_from = 20; int64_t integer_parameter_to = 50; parameter_server.add_parameter("integer_parameter", integer_parameter_initial_value, integer_parameter_from, integer_parameter_to); parameter_server.set_parameter_description("integer_parameter", "This is an integer test parameter");
Enable and run the Node once before entering the application’s main loop as explained in section ROS 2 Node.