UP | HOME

Synchronous Service Calls

Overview

This document explains the simplest way to call services synchronously from a ROS 2 python node.

Constructing the Node

The node's __init__ function is where

  1. The service client callback group is created
  2. The services are created
  3. We wait for the services to become available
    • We initially assume that once available, the services will always be available, otherwise there is an error.
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup

class AwaitClient(node):
    def __init__(self):
        super().__init__('await_client')
        # Create the callback group
        self._mux_client = MutuallyExclusiveCallbackGroup()

        # Create the services
        self._client = self.create_client(ServiceType, 'service_name', callback_group = self._mux_client)

        # Other clients with callbacks get the default callback group, for example, a timer
        self._tmr = self.create_timer(5.0, self.timer_callback)

        # Wait for the servies to become available.
        if not self._client.wait_for_service(timeout_sec = 1.0):
            raise RuntimeError(f"Timeout waiting for service {self._client.service_name}")

Calling the Service From A Callback

  1. To call the service from a callback, the callback should be defined as an async function
  2. At the site of the call, use await, which will suspend the callback until the result is available.
  3. This mode can be dangerous in real code since it will wait forever (e.g., if the node providing the service crashes)
async def timer_callback(self):
    # Call the service, waiting indefinitely for the response
    # If you are not using the response, omit the result =
    result = await self._client.call_async(Request())

Timeouts

  1. Adding a timeout to the await is good practice, because it is possible that the node providing the service server can crash or become unreachable
    • Without a timeout, await will block the client node forever.
  2. The Python asyncio library provides a few methods for adding timeouts to await calls
  3. For a single call, result = await asyncio.wait_for(client.call_async(Request()), timeout = 0.5)
  4. For several service calls that should all be governed by the same (cumulative) timeout:

    async with asyncio.timeout(4.5):
        await client.call_async(Request())
        await client.call_async(Request())
    
  5. If the async call does not complete before the timeout elapses, a TimeoutError is raised

Calling a Service during Setup

  1. If you need to call services during the setup of your node, you can use spin_until_future_complete to wait for services to return.
  2. Any use of spin_until_future_complete is restricted to the code prior to calling rclpy.spin()

Author: Matthew Elwin.