Understanding ROS2 Services


Concept and Functionality

What are ROS2 Services?

ROS2 (Robot Operating System 2) is an open-source framework for building robotic applications. Within this framework, services provide a means of communication between nodes that allows for request-response interactions. Unlike topics, which are used for streaming data, services are designed for discrete transactions.

A ROS2 service consists of two parts:

  • Service Server: This node offers a service.
  • Service Client: This node calls the service and waits for a response.

Services are synchronous by nature. When a client sends a request, it waits for the server to process the request and send back a response before continuing.

How do they differ from Topics?

  • Nature of Communication:

    • Topics: Used for continuous data streams, supporting many-to-many communication. Nodes can publish to or subscribe from a topic independently.
    • Services: Used for request-response interactions, supporting one-to-one communication. A client sends a request to a server, which processes it and returns a response.
  • Use Cases:

    • Topics: Suitable for sensor data streaming, telemetry, and other continuous data flows.
    • Services: Suitable for commands, configuration changes, or any operation that requires a response after processing.
  • Behavior:

    • Topics: Asynchronous communication where publishers and subscribers do not wait for each other.
    • Services: Synchronous communication where the client waits for the server’s response.

Practical Example

Implementing a Service Server and Client

Let’s implement a simple example where a service server will add two integers sent by a client and return the result.

Service Definition

First, define a service in an IDL (Interface Definition Language) file. Create a file named AddTwoInts.srv:

int64 a
int64 b
---
int64 sum
Implementing the Service Server

Create a Python script for the service server.


# Import necessary modules
import rclpy  # ROS2 Python client library
from rclpy.node import Node  # Node class from ROS2
from example_interfaces.srv import AddTwoInts  # Service type to add two integers (request-response)

# Define the server node class
class AddTwoIntsServer(Node):
    def __init__(self):
        # Initialize the node with the name 'add_two_ints_server'
        super().__init__('add_two_ints_server')
        
        # Create a service named 'add_two_ints' that listens for requests of type 'AddTwoInts'
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
    
    # Callback function that gets executed when a request is received
    def add_two_ints_callback(self, request, response):
        # Performs the addition of the two integers from the request (request.a and request.b)
        response.sum = request.a + request.b
        
        # Logs the incoming request details (values of a and b)
        self.get_logger().info(f'Incoming request\na: {request.a} b: {request.b}')
        
        # Logs the response 
        self.get_logger().info(f'Sending back response: {response.sum}')
        
        return response

# The main function initializes the node and keeps it running
def main(args=None):
    # Initialize the ROS2 Python library
    rclpy.init(args=args)
    
    # Creates an instance of the AddTwoIntsServer node
    node = AddTwoIntsServer()
    
    # Keeps the node alive and responsive to incoming service requests
    rclpy.spin(node)
    
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Implementing the Service Client

Create a Python script for the service client.


import sys
import rclpy
from rclpy.node import Node
# Import the custom AddTwoInts service type
from example_interfaces.srv import AddTwoInts

class AddTwoIntsClient(Node):
    def __init__(self):
        super().__init__('add_two_ints_client')
        
        # Create a client for the 'add_two_ints' service
        # specifying the service type AddTwoInts
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')
        
        # Waits until the service is available (checking every second)
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        
        # Creates a request object for the AddTwoInts service
        self.req = AddTwoInts.Request()

    def send_request(self):
        # Extracts command-line arguments for the two integers to be added
        self.req.a = int(sys.argv[1])
        self.req.b = int(sys.argv[2])
        # Calls the service asynchronously and store the future result
        self.future = self.cli.call_async(self.req)

def main(args=None):
    rclpy.init(args=args)
    client = AddTwoIntsClient()
    client.send_request()
    
    # Spin the node until the response is received or the program is interrupted
    while rclpy.ok():
        rclpy.spin_once(client)
        if client.future.done():
            try:
                response = client.future.result()
            except Exception as e:
                client.get_logger().info('Service call failed %r' % (e,))
            else:
                client.get_logger().info(f'Result: {response.sum}')
            break

    client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

To run these scripts, save them in your workspace,make sure to update your setup.py, and then execute the following commands in separate terminals:

# Terminal 1: Run the server
ros2 run your_package_name add_two_ints_server

# Terminal 2: Run the client with arguments
ros2 run your_package_name add_two_ints_client 3 5

ROS CLI Commands

ROS2 provides a rich set of command-line tools to interact with services. Here are some common commands to list, call, and find services.

Listing Services

To list all available services, use the following command:

ros2 service list

Calling a Service

To call a service and pass parameters, use the following command:

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 5}"

Finding Service Types

To find the type of a specific service, use:

ros2 service type /add_two_ints

Describing a Service

To get details about a service type, including the request and response fields, use:

ros2 interface show example_interfaces/srv/AddTwoInts

Conclusion

ROS2 services provide a structured way to implement request-response communication between nodes, complementing the asynchronous data flow provided by topics. By understanding the differences between services and topics, and leveraging the provided ROS2 CLI commands, developers can effectively build and debug ROS2 applications. The example provided demonstrates the basic implementation of a service server and client, serving as a foundation for more complex service interactions in your robotics projects.