ROS2 Service Communication Implementation

Service Communication Overview

Unlike topic-based communication where data flows unidirectionally between multiple ROS nodes with out confirmation of receipt, services enable synchronous request-response communication patterns.

Communication Architecture

Consider a scenario where one node controls a camera publishing image topics, while another subscribes to these images for red object detection. With topic communication, position data would be published continuously at the detection frequency. However, for applications like robot navigation that require target following or positioning, it's more efficient to query the current target location on-demand rather than receiving constant updates. This request-response pattern differs from unidirectional topic transmistion.

Client-Server Architecture

Synchronous Communication Characteristics

Service communication is typically time-sensitive. If a server doesn't respond promptly, clients can detect potential issues like server downtime or network problems by monitoring response timing. This ability to assess server status distinguishes service communication from topic-based messaging.

One-to-Many Relationships

In service communication, one server can handle multiple clients simultaneously.

Service Interfcae Definition

Similar to topics, services transmit structured data through standardized definitions. Services require two data components: request parameters (like a position query command) and response results (such as coordinate data). While topics use .msg files, services utilize .srv files for interface definition.

Example 1: Arithmetic Calculator

When computing the sum of two integers, the client node encapsulates the operands into a request message for the "add_two_ints" service. The server node processes this request, performs the calculation, and returns the result in a response message.

Execution Commands

Launch two terminals running separate nodes. The first acts as the server awaiting calculation requests, while the second functions as the client sending two integer arguments and waiting for the sum.

$ ros2 run learning_service service_adder_server
$ ros2 run learning_service service_adder_client 2 3

Client Implementation Details

learning_service/service_adder_client.py

import sys
import rclpy
from rclpy.node import Node
from learning_interface.srv import AddTwoInts

class CalculationClient(Node):
    def __init__(self, node_name):
        super().__init__(node_name)
        self.calculator = self.create_client(AddTwoInts, 'add_two_ints')
        
        while not self.calculator.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Calculator service unavailable, retrying...')
        
        self.calculation_request = AddTwoInts.Request()

    def initiate_calculation(self):
        self.calculation_request.first_operand = int(sys.argv[1])
        self.calculation_request.second_operand = int(sys.argv[2])
        self.pending_result = self.calculator.call_async(self.calculation_request)

def primary_execution(args=None):
    rclpy.init(args=args)
    node_instance = CalculationClient("service_adder_client")
    node_instance.initiate_calculation()

    while rclpy.ok():
        rclpy.spin_once(node_instance)

        if node_instance.pending_result.done():
            try:
                computation_outcome = node_instance.pending_result.result()
            except Exception as error:
                node_instance.get_logger().info(f'Calculation failed: {error}')
            else:
                node_instance.get_logger().info(
                    f'Addition result: {node_instance.calculation_request.first_operand} + '
                    f'{node_instance.calculation_request.second_operand} = '
                    f'{computation_outcome.total_sum}')
            break

    node_instance.destroy_node()
    rclpy.shutdown()

After implementing the code, configure the package's setup.py file to register the Python entry point:

entry_points={
    'console_scripts': [
     'service_adder_client = learning_service.service_adder_client:primary_execution',
    ],
}

Implementation Workflow

  1. Initialize programming interfaces
  2. Create and initialize node
  3. Instantiate client object
  4. Construct and dispatch request data
  5. Await server response
  6. Terminate node and close interfaces

Server Implementation Details

learning_service/service_adder_server.py

import rclpy
from rclpy.node import Node
from learning_interface.srv import AddTwoInts

class CalculationServer(Node):
    def __init__(self, node_name):
        super().__init__(node_name)
        self.service_endpoint = self.create_service(
            AddTwoInts, 
            'add_two_ints', 
            self.handle_calculation_request
        )

    def handle_calculation_request(self, incoming_request, outgoing_response):
        outgoing_response.total_sum = (
            incoming_request.first_operand + 
            incoming_request.second_operand
        )
        
        self.get_logger().info(
            f'Processing request\nFirst: {incoming_request.first_operand} '
            f'Second: {incoming_request.second_operand}'
        )
        return outgoing_response

def server_main(args=None):
    rclpy.init(args=args)
    server_node = CalculationServer("service_adder_server")
    rclpy.spin(server_node)
    server_node.destroy_node()
    rclpy.shutdown()

Update setup.py with the server entry point:

entry_points={
    'console_scripts': [
     'service_adder_client = learning_service.service_adder_client:primary_execution',
     'service_adder_server = learning_service.service_adder_server:server_main',
    ],
}

Server Workflow

  1. Initialize programming interfaces
  2. Create and initialize node
  3. Instantiate server object
  4. Process requests via callback function
  5. Return response results
  6. Terminate node and close interfaces

Example 2: Computer Vision Detection

For on-demand object positioning queries, service communication proves more suitable than continuous topic streaming.

System Components

  • Camera Driver Node: Publishes raw image data
  • Vision Processing Node: Subscribes to images and provides position detection service
  • Control Client Node: Requests current target coordinates when needed for navigation

Execution Commands

$ ros2 run usb_cam usb_cam_node_exe
$ ros2 run learning_service service_object_server
$ ros2 run learning_service service_object_client

Client Implementation

learning_service/service_object_client.py

import rclpy
from rclpy.node import Node
from learning_interface.srv import GetObjectPosition

class VisionClient(Node):
    def __init__(self, node_identifier):
        super().__init__(node_identifier)
        self.vision_service = self.create_client(
            GetObjectPosition, 
            'get_target_position'
        )
        
        while not self.vision_service.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Vision service unavailable, waiting...')
        
        self.position_request = GetObjectPosition.Request()

    def request_coordinates(self):
        self.position_request.trigger_detection = True
        self.async_response = self.vision_service.call_async(self.position_request)

def client_execution(args=None):
    rclpy.init(args=args)
    vision_client = VisionClient("service_object_client")
    vision_client.request_coordinates()

    while rclpy.ok():
        rclpy.spin_once(vision_client)

        if vision_client.async_response.done():
            try:
                detection_result = vision_client.async_response.result()
            except Exception as error:
                vision_client.get_logger().info(f'Detection failed: {error}')
            else:
                vision_client.get_logger().info(
                    f'Target coordinates:\n X: {detection_result.coordinate_x} '
                    f'Y: {detection_result.coordinate_y}'
                )
            break
    
    vision_client.destroy_node()
    rclpy.shutdown()

Server Implementation

learning_service/service_object_server.py

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
import numpy as np
from cv_bridge import CvBridge
import cv2
from learning_interface.srv import GetObjectPosition

red_threshold_min = np.array([0, 90, 128])
red_threshold_max = np.array([180, 255, 255])

class ImageProcessor(Node):
    def __init__(self, identifier):
        super().__init__(identifier)
        
        self.image_subscriber = self.create_subscription(
            Image, 
            'image_raw', 
            self.process_image_frame, 
            10
        )
        
        self.bridge_converter = CvBridge()
        
        self.position_service = self.create_service(
            GetObjectPosition,
            'get_target_position',
            self.provide_coordinates
        )
        
        self.target_x = 0
        self.target_y = 0

    def detect_red_objects(self, input_image):
        hsv_converted = cv2.cvtColor(input_image, cv2.COLOR_BGR2HSV)
        red_mask = cv2.inRange(hsv_converted, red_threshold_min, red_threshold_max)
        detected_contours, _ = cv2.findContours(
            red_mask, 
            cv2.RETR_LIST, 
            cv2.CHAIN_APPROX_NONE
        )

        for contour in detected_contours:
            if contour.shape[0] < 150:
                continue

            x_coord, y_coord, width, height = cv2.boundingRect(contour)
            cv2.drawContours(input_image, [contour], -1, (0, 255, 0), 2)
            center_x = int(x_coord + width/2)
            center_y = int(y_coord + height/2)
            cv2.circle(input_image, (center_x, center_y), 5, (0, 255, 0), -1)

            self.target_x = center_x
            self.target_y = center_y

        cv2.imshow("detection", input_image)
        cv2.waitKey(50)

    def process_image_frame(self, image_message):
        self.get_logger().info('Processing new image frame')
        converted_image = self.bridge_converter.imgmsg_to_cv2(image_message, 'bgr8')
        self.detect_red_objects(converted_image)

    def provide_coordinates(self, request_data, response_data):
        if request_data.trigger_detection:
            response_data.coordinate_x = self.target_x
            response_data.coordinate_y = self.target_y
            self.get_logger().info(
                f'Target located\nX: {response_data.coordinate_x} '
                f'Y: {response_data.coordinate_y}'
            )
        else:
            response_data.coordinate_x = 0
            response_data.coordinate_y = 0
            self.get_logger().info('Invalid detection command')
        return response_data

def processor_main(args=None):
    rclpy.init(args=args)
    processing_node = ImageProcessor("service_object_server")
    rclpy.spin(processing_node)
    processing_node.destroy_node()
    rclpy.shutdown()

Command Line Operations

$ ros2 service list
$ ros2 service type <service_identifier>
$ ros2 service call <service_identifier> <service_datatype> <service_parameters>

Topics and services represent ROS's primary communication mechanisms. Topics suit periodic, unidirectional data like sensor readings and control commands, while services excel at synchronous request-response interactions requiring immediate feedback, such as retrieving computer vision detection results.

Tags: ROS2 Service Communication client-server synchronous communication Computer Vision

Posted on Wed, 14 Oct 2026 16:27:22 +0000 by mrbill501