In this tutorial, we will learn how to create a service and a client in ROS 2 Foxy Fitzroy using C++. The client-service relationship in ROS 2 is a request-reply relationship. A client node sends a request for data to the service node. The service node then sends a reply to the client node.
A real-world example of this is a client node requesting sensor data from a service node. The service node then responds to the client node with the requested sensor data.
A file called a .srv file defines the structure of the service-client node interaction.
The example we will use here is an addition system. We will create a client node that requests the sum of two integers. We will then create a service node that will respond to the client node with the sum of those two integers.
The official tutorial is located in the ROS 2 Foxy documentation, but we’ll run through the entire process step-by-step below.
You Will Need
In order to complete this tutorial, you will need:
Prerequisites
You have already created a workspace.
Create a Package
Open a new terminal window, and navigate to the src directory of your workspace:
cd ~/dev_ws/src
Now let’s create a package named cpp_srvcli.
Type this command (this is a single command):
ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces
Your package named cpp_srvcli has now been created.
You will note that we added the –dependencies command after the package creation command. Doing this automatically adds the dependencies to your package.xml and CMakeLists.txt files.
Modify Package.xml
Go to the dev_ws/src/cpp_srvcli folder.
cd ~/dev_ws/src/cpp_srvcli
Make sure you have a text editor installed. I like to use gedit.
sudo apt-get install gedit
Open the package.xml file.
gedit package.xml
Fill in the description of the cpp_srvcli package, your email address and name on the maintainer line, and the license you desire (e.g. Apache License 2.0).
<description>A minimal C++ client and service node</description>
<maintainer email="automaticaddison@todo.todo">automaticaddison</maintainer>
<license>Apache License 2.0</license>
Save and close the file.
Write a Service Node
Move to the dev_ws/src/cpp_srvcli/src folder.
cd ~/dev_ws/src/cpp_srvcli/src
Create a new C++ file named add_two_ints_server.cpp
gedit add_two_ints_server.cpp
Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.
This service node adds two integers together.
#include "rclcpp/rclcpp.hpp" // ROS 2 C++ Client Library
#include "example_interfaces/srv/add_two_ints.hpp" // Package dependency
#include <memory>
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
// Adds two integers from the request and gives the sum to the response.
response->sum = request->a + request->b;
// Notifies the console of its status using logs.
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
int main(int argc, char **argv)
{
// Initialize the ROS 2 C++ Client Library
rclcpp::init(argc, argv);
// Create a node named add_two_ints_server
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
// Create a service named add_two_ints and advertise it over the network (i.e. &add method)
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
// Display a log message when the service is ready
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
// Make the service available.
rclcpp::spin(node);
// Call shutdown procedure when we are done
rclcpp::shutdown();
}
Save the file, and close it.
Modify CMakeLists.txt
Go to the following directory.
cd ~/dev_ws/src/cpp_srvcli/
Open CMakeLists.txt.
gedit CMakeLists.txt
Add the following lines above the # find dependencies block:
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)
Right before the ament_package() line, add the following lines.
install(TARGETS
server
DESTINATION lib/${PROJECT_NAME})
Save the file, and close it.
Write a Client Node
Move to the dev_ws/src/cpp_srvcli/src folder.
cd ~/dev_ws/src/cpp_srvcli/src
Create a new C++ file named add_two_ints_client.cpp
gedit add_two_ints_client.cpp
Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <chrono>
#include <cstdlib>
#include <memory>
using namespace std::chrono_literals;
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
if (argc != 3) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
return 1;
}
// Create the node
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
// Create the client for the node
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
// Make the request
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
// Show an error if the user types CTRL + C
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
// Search for service nodes in the network
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
// Send a request
auto result = client->async_send_request(request);
// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
}
rclcpp::shutdown();
return 0;
}
Save the file, and close it.
Modify CMakeLists.txt
Go to the following directory.
cd ~/dev_ws/src/cpp_srvcli/
Open CMakeLists.txt.
gedit CMakeLists.txt
Add the following lines after the server’s add_executable block:
add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
rclcpp example_interfaces)
Add client inside the install target.
install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})
Here is what your CMakeLists.txt file should look like:
cmake_minimum_required(VERSION 3.5)
project(cpp_srvcli)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)
add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
rclcpp example_interfaces)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})
ament_package()
Save the file, and close it.
Build the Package
Return to the root of your workspace:
cd ~/dev_ws/
We need to double check that all the dependencies needed are already installed.
rosdep install -i --from-path src --rosdistro foxy -y
Build all packages in the workspace.
colcon build
Run the Nodes
To run the nodes, open a new terminal window.
Make sure you are in the root of your workspace:
cd ~/dev_ws/
Run the service node.
ros2 run cpp_srvcli server
Open a new terminal, and run the client node. At the end of the command, put the two integers you would like to add.
ros2 run cpp_srvcli client 5 3
Go back to the service node terminal. Here is what I see:
When you’re done, press CTRL + C in all terminal windows to shut everything down.
That’s it. Keep building!