Writing a Service Client (Python)

Create a package

First of all, you will have to create a new package, where you will place the code for your Publisher node. For this, you need to be in your ROS2 workspace src folder.

cd ~/ros2_ws/src

Now, execute the following command to create the package:

ros2 pkg create --build-type ament_python service_client_py --dependencies rclpy example_interfaces

You will get an output similar to this one:

This will automatically generate your package and all its necessary files and folders. Now, navigate to the 'ros2_ws/src/' folder in order to create your file.

Write the code

Inside the ros2_ws/src folder, create a new file named service_client.py. Into this file, you will copy the following code

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

class ServiceClient(Node):
    def __init__(self):
        super().__init__('service_client_node')
        self.service_client = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.service_client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = AddTwoInts.Request()

    def send_request(self):
        self.req.a = int(sys.argv[1])
        self.req.b = int(sys.argv[2])
        self.future = self.service_client.call_async(self.req)

def main(args=None):
    rclpy.init(args=args)
    service_client = ServiceClient()
    service_client.send_request()
    while rclpy.ok():
        rclpy.spin_once(service_client)
        if service_client.future.done():
            try:
                response = service_client.future.result()
            except Exception as e:
                service_client.get_logger().info('Service call failed %r' % (e,))
            else:
                service_client.get_logger().info('Result of add_two_ints: for %d + %d = %d' %(service_client.req.a, service_client.req.b, response.sum))
            break
    service_client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Review the code

The first thing you can notice is the sys import. This is used in order to get access to the command line input arguments for generating the request.

import sys

You can see that we are also importing the AddTwoInts message, from the example_interfaces package.

from example_interfaces.srv import AddTwoInts

Next we define our class, which inherits from the Node class.

class ServiceClient(Node):

Within the constructor, we are initializing our node by calling to the constructor of the superclass Node. Also within the constructor, we create the service client, with the same type and name as the service node .

def __init__(self):
        super().__init__('service_client_node')
        self.service_client = self.create_client(AddTwoInts, 'add_two_ints')

You can also notice a while loop in the constructor. It basically checks if the service server is available, once a second. The program will wait here until the service server is available.

while not self.service_client.wait_for_service(timeout_sec=1.0):
    self.get_logger().info('service not available, waiting again...')

Next, the request is created. Its structure is defined by the .srv file AddTwoInts.

def send_request(self):
    self.req.a = int(sys.argv[1])
    self.req.b = int(sys.argv[2])
    self.future = self.service_client.call_async(self.req)

Finally we have the main() function.

def main(args=None):
    rclpy.init(args=args)
    service_client = ServiceClient()
    service_client.send_request()
    while rclpy.ok():
        rclpy.spin_once(service_client)
        if service_client.future.done():
            try:
                response = service_client.future.result()
            except Exception as e:
                service_client.get_logger().info('Service call failed %r' % (e,))
            else:
                service_client.get_logger().info('Result of add_two_ints: for %d + %d = %d' %(service_client.req.a, service_client.req.b, response.sum))
            break
    service_client.destroy_node()
    rclpy.shutdown()

The most remarkable thing to mention here is the while loop. The loop tries to check whether there is a response from the server, as long as the program is running. If the server has sent a response, the result will be written in a log message.

Modify the setup.py file

In the entry_points section of your setup.py file, add the following lines:

entry_points={
    'console_scripts': [
        'service_client_node = service_client_py.service_client:main'
    ],
},

This will generate an executable, which points to the main function from the service_client.py file, which is inside the service_client_py folder.

Compile the package

First of all, let's go the ros2_ws folder in order to be able to compile.

cd ~/ros2_ws;

Let's now execute the colcon command in order to build our node.

colcon build --symlink-install

You will get an output like the following one:

Finally, let's source the workspace.

source ~/ros2_ws/install/setup.bash

Testing the Service Client

First of all, we need to run our service server (so that it becomes available) with the following command:

ros2 run service_server_cpp service_server_node

Now let's run our client node with the following command:

ros2 run service_client_cpp service_client_node 2 3

You will get an output like this:

From the Shell where you executed the Server, you will get the following:

And from the Shell where you called the Server, you wll get the following:

Practice Online

Also, you can test this tutorial in ROSDS, using a ROSject which already contains all the code described in it. You can get the ROSject by clicking on the button below:

Above: Get ROSDS ROSject

The link above will take yo a page like the below one:

Now, you just need to Sign In (or Sign Up if you don't have an account yet) to ROSDS in order to launch the ROSject.