How to Write a ROS 2 Service in C++ – ROS 2 Jazzy

ros2-service

In this tutorial, we’ll create a ROS 2 service using C++

A ROS 2 service is a way for different parts of a robot system to communicate with each other by sending a request and receiving a response, similar to making a phone call and waiting for an answer. This form of communication is different from the publish-subscribe communication method, where one part of the system continuously broadcasts information, and any other parts that are interested can listen in, like a radio station broadcasting to many listeners. 

With a service, the communication is direct and specific, where the caller sends a request to a particular service provider and waits for a response before proceeding. In contrast, publish-subscribe allows for more flexible and asynchronous communication between multiple parts of the system without waiting for specific responses.

Services are useful when you need a quick but specific task done, and you need to wait for it to finish before moving on. You generally do not want to use a service if you need to make continuous calls for information. If you need to make continuous calls for information, topics are better suited.

Real-World Applications

Here are some real-world examples of how ROS 2 service and client nodes can be used in robotic applications:

  • Starting a Robot Vacuum Cleaner
    • Service: A service node called start_cleaning is created. When this service is called, it initiates the cleaning process of the robot vacuum cleaner.
    • Client: A client node calls the start_cleaning service to begin the cleaning process. It waits for a response from the service confirming that the cleaning process has started.
  • Localization on a Map
    • Service: A service node determines the current position and orientation of a robot on a map based on a provided map and LIDAR scans.
    • Client: A client node requests the current position and orientation of a robot on a map.
  • Gripper Control:
    • Service: A service node controls the robotic arm’s gripper to open or close.
    • Client: A client node sends requests to the gripper service to open the gripper to release an object into a bin.

Prerequisites

All my code for this project is located here on GitHub.

Creating a Cleaning Service for a Robotic Vacuum Cleaner  

Let’s imagine we have a fictitious robotic vacuum cleaner:

  • If this service is called with a value of True, the robot will start cleaning.
  • If this service is called with a value of False, the robot will stop cleaning.

The name of the service is /set_cleaning_state.

We will call the service using the following syntax:

ros2 service call <service_name> <service_type> <arguments>

To start cleaning, we will run this command:

ros2 service call /set_cleaning_state yahboom_rosmaster_msgs/srv/SetCleaningState "{desired_cleaning_state: true}"

To stop cleaning, we will run this command:

ros2 service call /set_cleaning_state yahboom_rosmaster_msgs/srv/SetCleaningState "{desired_cleaning_state: false}"

Create a Custom Service Definition

Create the srv folder inside the yahboom_rosmaster_msgs package:

cd ~/ros2_ws/src/yahboom_rosmaster/yahboom_rosmaster_msgs
mkdir srv

Create the service definition file SetCleaningState.srv inside the srv folder:

cd srv

Open the SetCleaningState.srv file and add the following content:

# Request
bool desired_cleaning_state  # Request: true to start cleaning, false to stop
---
bool success                 # Response: whether the request was successful
string message               # Response: information about the result

Update CMakeLists.txt

Add these lines to yahboom_rosmaster_msgs/CMakeLists.txt:

# Find required packages
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)

# Generate interfaces
rosidl_generate_interfaces(${PROJECT_NAME}
  "action/TimedRotation.action"
  "srv/SetCleaningState.srv"
)

ament_package()

Build the Service Messages

cd ~/ros2_ws
colcon build
source ~/.bashrc

Confirm the service creation:

ros2 interface show yahboom_rosmaster_msgs/srv/SetCleaningState
1-create-a-service

Write the Service Node

Let’s write the service node.

Create the set_cleaning_state_service.cpp file inside the src directory of yahboom_rosmaster_system_tests:

cd ~/ros2_ws/src/yahboom_rosmaster/yahboom_rosmaster_system_tests/src

Add the following content:

/**
 * @file set_cleaning_state_service.cpp
 * @brief ROS 2 node that implements a service to set the cleaning state of a robot
 *
 * This program implements a ROS 2 node that provides a service to set the cleaning state
 * of a robot. It demonstrates the use of service servers in ROS 2.
 *
 * Subscription Topics:
 *   None
 *
 * Publishing Topics:
 *   None
 *
 * Services:
 *   /set_cleaning_state (yahboom_rosmaster_msgs/srv/SetCleaningState):
 *     Sets the cleaning state of the robot
 *
 * @author Addison Sears-Collins
 * @date November 26, 2024
 */
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "yahboom_rosmaster_msgs/srv/set_cleaning_state.hpp"

/**
 * @class SetCleaningStateService
 * @brief A ROS 2 node that provides a service to set the cleaning state of a robot
 */
class SetCleaningStateService : public rclcpp::Node
{
public:
  /**
   * @brief Constructor for the SetCleaningStateService class.
   *
   * Initializes the node with the name "set_cleaning_state_service"
   * and creates the service server for the "/set_cleaning_state" service.
   */
  SetCleaningStateService()
  : Node("set_cleaning_state_service")
  {
    service_ = this->create_service<yahboom_rosmaster_msgs::srv::SetCleaningState>(
      "/set_cleaning_state",
      std::bind(&SetCleaningStateService::set_cleaning_state_callback, this,
                std::placeholders::_1, std::placeholders::_2));

    RCLCPP_INFO(this->get_logger(), "Cleaning Service Server is ready.");
  }

private:
  /**
   * @brief Callback function for the set_cleaning_state service.
   *
   * This function is called when a request is received for the "/set_cleaning_state" service.
   * It checks the desired cleaning state in the request and sets the appropriate response.
   *
   * @param request The request shared pointer for the set_cleaning_state service.
   * @param response The response shared pointer for the set_cleaning_state service.
   */
  void set_cleaning_state_callback(
    const std::shared_ptr<yahboom_rosmaster_msgs::srv::SetCleaningState::Request> request,
    std::shared_ptr<yahboom_rosmaster_msgs::srv::SetCleaningState::Response> response)
  {
    if (request->desired_cleaning_state) {
      RCLCPP_INFO(this->get_logger(), "Starting cleaning...");
      response->success = true;
      response->message = "Robot started cleaning";
    } else {
      RCLCPP_INFO(this->get_logger(), "Stopping cleaning...");
      response->success = true;
      response->message = "Robot stopped cleaning";
    }

    RCLCPP_INFO(this->get_logger(), "Response sent: %s", response->message.c_str());
  }

  /// Service server for setting cleaning state
  rclcpp::Service<yahboom_rosmaster_msgs::srv::SetCleaningState>::SharedPtr service_;
};

/**
 * @brief Main function to run the SetCleaningStateService node.
 *
 * @param argc Number of command-line arguments.
 * @param argv Array of command-line arguments.
 * @return int Exit status of the program.
 */
int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<SetCleaningStateService>();
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

Update CMakeLists.txt

Edit the CMakeLists.txt file:

add_executable(set_cleaning_state_service 
  src/set_cleaning_state_service.cpp
)

ament_target_dependencies(set_cleaning_state_service
  rclcpp
  yahboom_rosmaster_msgs
)

install(TARGETS
  set_cleaning_state_service
  DESTINATION lib/${PROJECT_NAME}
)

Build the Package

cd ~/ros2_ws
colcon build
source ~/.bashrc

Run the Service Node

Open a new terminal window, and type:

ros2 run yahboom_rosmaster_system_tests set_cleaning_state_service
2-cleaning-service-server-is-ready
ros2 service list -t

The ros2 service list -t command lists all the available services along with their corresponding service types. It shows that the /set_cleaning_state service uses the yahboom_rosmaster_msgs/srv/SetCleaningState service type.

3-ros2-service-list

Open a new terminal window, and call the service to start cleaning:

ros2 service call /set_cleaning_state yahboom_rosmaster_msgs/srv/SetCleaningState "{desired_cleaning_state: true}"
4-ros2-service-call

Now call the service to stop cleaning:

ros2 service call /set_cleaning_state yahboom_rosmaster_msgs/srv/SetCleaningState "{desired_cleaning_state: false}"
5-ros2-service-call-false

You see how we called the service via the terminal. You can also create an actual program that is the service client that calls the service just like we did in the terminal.

This tutorial showed you a simple service that:

  • Listens for requests
  • Processes the requests
  • Returns appropriate responses

All services are structured this way, so you can use this as a template for any service you create.

That’s it! Keep building!