IN THIS LESSON

You will learn the basic concepts of how to write a service and client.

A node can communicate using services. The node sends a request for data that is called the client node and the service node responds to the request. The structure of the request and response is determined by a .srv file (a simplified service description language used for describing ROS service types).

This is how you can create a service and client.

In MiROS

Drag a Service from the Side Bar to Nodes Canvas.

Drag a Service from the Side Bar to Nodes Canvas

In Ubuntu (Linux)

This example will demonstrate a simple integer addition system. One node requests the sum of two integers and the other responds with the result. Ensure you are in the src directory of the ros2_ws to create a new package using the following command:

 

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

 

two packages will be created with the dependencies rclpy being added to the package.xml file of the py_srvcli package. Example_interfaces is the package that includes the .srv file needed to structure your requests and responses and the code should be as follows:

 

int64 a

int64 b

---

int64 sum

 

The first two lines are the parameters of the request, below the dashed line is the response.

 

Remember to update the correct fields in package.xml and setup.py.

 

Inside the directory ros2_ws/src/py_srvcli/py_srvcli create a new file called service_member_function.py and copy the following code into it:

 

from example_interfaces.srv import AddTwoInts

 

import rclpy

from rclpy.node import Node

 

 

class MinimalService(Node):

 

    def __init__(self):

        super().__init__('minimal_service')

        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

 

    def add_two_ints_callback(self, request, response):

        response.sum = request.a + request.b

        self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

 

        return response

 

 

def main():

    rclpy.init()

 

    minimal_service = MinimalService()

 

    rclpy.spin(minimal_service)

 

    rclpy.shutdown()

 

 

if __name__ == '__main__':

    main()

 

The code runs in a similar structure to the publisher subscriber code in 1.5. The AddTwoInts service type is imported from example_interfaces package.

 

The MinimalService class constructor initializes the node with the name minimal_service and then creates a service and defines the type, name and callback.

 

The definition of the service callback receives the request data, sums it and returns the sum as a response. Finally the main class initialises the ROS 2 Python client library, instantiates the MinimalService class to create the service node and spins the node to handle callbacks.

 

Again similarly and entry point must be added in setup.py of the ros2_ws/src/py_srvcli to allow the ‘ros2 run’ command to run your node. Add the following line of code between the ‘console_scripts’: brackets:

 

'service = py_srvcli.service_member_function:main’,

 

Now the client node must be written, in the same directory as the service node in a file we will call client_member_function.py.

 

import sys

 

from example_interfaces.srv import AddTwoInts

import rclpy

from rclpy.node import Node

 

 

class MinimalClientAsync(Node):

 

    def __init__(self):

        super().__init__('minimal_client_async')

        self.cli = self.create_client(AddTwoInts, 'add_two_ints')

        while not self.cli.wait_for_service(timeout_sec=1.0):

            self.get_logger().info('service not available, waiting again...')

        self.req = AddTwoInts.Request()

 

    def send_request(self, a, b):

        self.req.a = a

        self.req.b = b

        self.future = self.cli.call_async(self.req)

        rclpy.spin_until_future_complete(self, self.future)

        return self.future.result()

 

 

def main():

    rclpy.init()

 

    minimal_client = MinimalClientAsync()

    response = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))

    minimal_client.get_logger().info(

        'Result of add_two_ints: for %d + %d = %d' %

        (int(sys.argv[1]), int(sys.argv[2]), response.sum))

 

    minimal_client.destroy_node()

    rclpy.shutdown()

 

 

if __name__ == '__main__':

    main()

 

 

The new import ‘sys’ is used to get command line input arguments for the request through sys.argv

The constructor definition creates a client with the same type and name as the service node. The type and name must match for the client and service to be able to communicate.

 

The while loop checks if a service matching the type and name of the client is available once a second. Below the constructor is the request definition followed by ‘main’. The while loop in main checks the future to see if there is a response from the service (as long as the system is running). If the service has sent a response, the result will be written in a log message.

 

An entry point for the client must also be added in setup.py. The file should look like the following:

 

entry_points={

    'console_scripts': [

        'service = py_srvcli.service_member_function:main',

        'client = py_srvcli.client_member_function:main',

    ],

},

 

To build the package, ensure you are in the root of the workspace (ros2_ws) and run the command:

 

colcon build --packages-select py_srvcli

 

Open a new terminal (ensure you are in ros2_ws) and source the setup files:

 

. install/setup.bash

 

Now run the service node:

 

ros2 run py_srvcli service

 

open another terminal and source the setup files from inside ros2_ws again. Start the client node, followed by any two integers separated by a space, for example:

 

ros2 run py_srvcli client 4 7

 

and in the client terminal the response should be received as follows:

 

[INFO] [minimal_client_async]: Result of add_two_ints: for 4 + 7 = 11

 

If you return to the terminal where your service node is running you can see the published log messages from when it received the request:

 

[INFO] [minimal_service]: Incoming request

a: 4 b: 7

 

As always, press Ctrl+C in the service terminal to stop the node from spinning.