Robot Operating System/Tutorial 따라하기

[Dashing][Client Libraries] 4. Writing a simple service and client (Python)

jstar0525 2021. 6. 17. 17:44
반응형

이글은 아래의 자료를 참고로 만들어졌습니다.

 

https://docs.ros.org/en/dashing/Tutorials/Writing-A-Simple-Py-Service-And-Client.html

 

Writing a simple service and client (Python) — ROS 2 Documentation: Dashing documentation

Inside the dev_ws/src/py_srvcli/py_srvcli directory, create a new file called service_member_function.py and paste the following code within: 2.1 Examine the code The first import statement imports the AddTwoInts service type from the example_interfaces pa

docs.ros.org


 

Writing a simple service and client (Python)

 

Goal: Create and run service and client nodes using Python.

 

Tutorial level: Beginner

 

Background

When nodes communicate using services, the node that sends a request for data is called the client node, and the one that responds to the request is the service node. The structure of the request and response is determined by a .srv file.

 

The example used here is a simple integer addition system; one node requests the sum of two integers, and the other responds with the result.

 

Prerequisites

In previous tutorials, you learned how to create a workspace and create a package.

 

Tasks

1 Create a package

Open a new terminal and source your ROS 2 installation so that ros2 commands will work.

 

Navigate into the dev_ws directory created in a previous tutorial.

 

Recall that packages should be created in the src directory, not the root of the workspace.
Navigate into dev_ws/src and create a new package:

 

$ cd ~/dev_ws/src/
$ ros2 pkg create --build-type ament_python py_srvcli --dependencies rclpy example_interfaces

going to create a new package
package name: py_srvcli
destination directory: /home/ros/dev_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ros <jstar0525@gmail.com>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: ['rclpy', 'example_interfaces']
creating folder ./py_srvcli
creating ./py_srvcli/package.xml
creating source folder
creating folder ./py_srvcli/py_srvcli
creating ./py_srvcli/setup.py
creating ./py_srvcli/setup.cfg
creating folder ./py_srvcli/resource
creating ./py_srvcli/resource/py_srvcli
creating ./py_srvcli/py_srvcli/__init__.py
creating folder ./py_srvcli/test
creating ./py_srvcli/test/test_copyright.py
creating ./py_srvcli/test/test_flake8.py
creating ./py_srvcli/test/test_pep257.py

 

Your terminal will return a message verifying the creation of your package py_srvcli and all its necessary files and folders.

 

The --dependencies argument will automatically add the necessary dependency lines to package.xml.
example_interfaces is the package that includes the .srv file you will need to structure your requests and responses:

 

int64 a
int64 b
---
int64 sum

 

The first two lines are the parameters of the request, and below the dashes is the response.

 

1.1 Update package.xml

Because you used the --dependencies option during package creation, you don’t have to manually add dependencies to package.xml.

 

As always, though, make sure to add the description, maintainer email and name, and license information to package.xml.

 

<description>Python client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

 

 

1.2 Update setup.py

Add the same information to the setup.py file for the maintainer, maintainer_email, description and license fields:

 

maintainer='Your Name',
maintainer_email='you@email.com',
description='Python client server tutorial',
license='Apache License 2.0',

 

2 Write the service node

Inside the dev_ws/src/py_srvcli/py_srvcli directory, create a new file called service_member_function.py and paste the following code within:

 

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(args=None):
    rclpy.init(args=args)

    minimal_service = MinimalService()

    rclpy.spin(minimal_service)

    rclpy.shutdown()


if __name__ == '__main__':
    main()

 

2.1 Examine the code

The first import statement imports the AddTwoInts service type from the example_interfaces package.
The following import statement imports the ROS 2 Python client library, and specifically the Node class.

 

from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node

 

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

 

def __init__(self):
    super().__init__('minimal_service')
    self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

 

The definition of the service callback receives the request data, sums it, and returns the sum as a response.

 

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

 

Finally, the main class initializes the ROS 2 Python client library, instantiates the MinimalService class to create the service node and spins the node to handle callbacks.

 

2.2 Add an entry point

To allow the ros2 run command to run your node, you must add the entry point to setup.py (located in the dev_ws/src/py_srvcli directory).

 

Add the following line between the 'console_scripts': brackets:

 

	entry_points={
        'console_scripts': [
            'service = py_srvcli.service_member_function:main',
        ],
    },

 

3 Write the client node

Inside the dev_ws/src/py_srvcli/py_srvcli directory, create a new file called client_member_function.py and paste the following code within:

 

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):
        self.req.a = int(sys.argv[1])
        self.req.b = int(sys.argv[2])
        self.future = self.cli.call_async(self.req)


def main(args=None):
    rclpy.init(args=args)

    minimal_client = MinimalClientAsync()
    minimal_client.send_request()

    while rclpy.ok():
        rclpy.spin_once(minimal_client)
        if minimal_client.future.done():
            try:
                response = minimal_client.future.result()
            except Exception as e:
                minimal_client.get_logger().info(
                    'Service call failed %r' % (e,))
            else:
                minimal_client.get_logger().info(
                    'Result of add_two_ints: for %d + %d = %d' %
                    (minimal_client.req.a, minimal_client.req.b, response.sum))
            break

    minimal_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

 

3.1 Examine the code

The only different import statement for the client is import sys.
The client node code uses sys.argv to get access to command line input arguments for the request.

 

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 in the constructor 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 only significant difference in the client’s main is the while loop.
The loop 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.

 

3.2 Add an entry point

Like the service node, you also have to add an entry point to be able to run the client node.

 

The entry_points field of your setup.py file should look like this:

 

    entry_points={
        'console_scripts': [
            'service = py_srvcli.service_member_function:main',
            'client = py_srvcli.client_member_function:main',
        ],
    },

 

4 Build and run

It's good practice to run rosdep in the root of your workspace (dev_ws) to check for missing dependencies before building:

 

$ cd ~/dev_ws/
$ rosdep install -i --from-path src --rosdistro dashing -y

#All required rosdeps installed successfully

 

Navigate back to the root of your workspace, dev_ws, and build your new package:

 

$ colcon build --packages-select py_srvcli

Starting >>> py_srvcli
Finished <<< py_srvcli [0.41s]          

Summary: 1 package finished [0.52s]

 

Open a new terminal, navigate to dev_ws, and source the setup files:

Now run the service node:

 

$ cd ~/dev_ws/
$ . install/setup.bash
$ ros2 run py_srvcli service

 

The node will wait for the client’s request.

 

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

 

If you chose 2 and 3, for example, the client would receive a response like this:

 

$ cd ~/dev_ws/
$ . install/setup.bash
$ ros2 run py_srvcli client 2 3

[INFO] [minimal_client_async]: Result of add_two_ints: for 2 + 3 = 5

 

Return to the terminal where your service node is running.
You will see that it published log messages when it received the request:

 

($ ros2 run py_srvcli service)

[INFO] [minimal_service]: Incoming request
a: 2 b: 3

 

Enter Ctrl+C in the server terminal to stop the node from spinning.

 

Summary

You created two nodes to request and respond to data over a service.
You added their dependencies and executables to the package configuration files so that you could build and run them, allowing you to see a service/client system at work.

 

Next steps

In the last few tutorials you’ve been utilizing interfaces to pass data across topics and services. Next, you’ll learn how to create custom interfaces.

 

Related content

  • There are several ways you could write a service and client in Python; check out the minimal_client and minimal_service packages in the ros2/examples repo.
  • In this tutorial, you used the call_async() API in your client node to call the service.
    There is another service call API available for Python called synchronous calls.
    We do not recommend using synchronous calls, but if you'd like to learn more about them, read the guide to Synchronous vs. asynchronous clients.
반응형