① 背景
当节点使用服务进行通信时,发送数据请求的节点称为客户机节点,响应请求的节点称为服务节点。请求和响应的结构由.srv文件决定。

这里使用的示例是一个简单的整数加法系统;一个节点请求两个整数的和,另一个节点用结果响应。这儿用python写的

其实简单得功能函数就可以解决了,这个解决了数据交互的问题,比如服务端返回客户端需要的,但是不用在客户端管理的数据或者状态,还是模块化

② 前提
学会创建工作空间和包

③ 任务
Ⅰ 创建一个包
打开终端,准备好环境,切换到dev_ws/src 路径下来创建包

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

然后出一个文件夹

utry@utry:~/dev_ws/src$ tree py_srvcli/
py_srvcli/
├── package.xml
├── py_srvcli
│   └── __init__.py
├── resource
│   └── py_srvcli
├── setup.cfg
├── setup.py
└── test
    ├── test_copyright.py
    ├── test_flake8.py
    └── test_pep257.py

3 directories, 8 files

加了--dependencies rclcpp example_interfaces之后,创建包的时候,会自动把package.xml 和CMakeLists.txt依赖加上,省事,example_interfaces包含例子srv文件

utry@utry:~/dev_ws/src/cpp_srvcli$ ros2 interface show example_interfaces/
example_interfaces/action/Fibonacci  example_interfaces/srv/AddTwoInts
utry@utry:~/dev_ws/src/cpp_srvcli$ ros2 interface show example_interfaces/srv/AddTwoInts 
int64 a
int64 b
---
int64 sum

修改 package.xml

这三个部分还是要改一下的,依赖已经在创建的时候解决了,所以,不用关心依赖了

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

修改 setup.py

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

Ⅱ 写服务节点

 dev_ws/src/py_srvcli/py_srvcli 下新建文件service_member_function.py

demo 代码:

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()

解析代码导入消息类型,python 模块

from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node

初始化节点名字minimal_service,创建服务add_two_ints 绑定回调 add_two_ints_callback,并发布

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

修改setup.py在console_scripts list里加上一行

'service = py_srvcli.service_member_function:main',

Ⅲ 写客户端节点

 dev_ws/src/py_srvcli/py_srvcli 下新建文件client_member_function.py

demo 代码:

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()

代码解析import sys 为了获取输入的参数初始化节点,创建客户端

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...')

发送请求

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

修改setup.py在console_scripts list里加上一行

'client = py_srvcli.client_member_function:main',

最后这个样子:

from setuptools import setup

package_name = 'py_srvcli'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='utry',
    maintainer_email='jinmenglei',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'service = py_srvcli.service_member_function:main',
            'client = py_srvcli.client_member_function:main',
        ],
    },
)

Ⅳ 编译和运行

养成好习惯,编译前先解决依赖,切换到dev_ws下

sudo rosdep install -i --from-path src --rosdistro eloquent -y

只编译新增的包

colcon build --packages-select py_srvcli

安装环境

. install/setup.bash

运行两个节点

ros2 run py_srvcli server
ros2 run py_srvcli client 2 3

在这里插入图片描述

④ 总结
您创建了两个节点来通过服务请求和响应数据。您将它们的依赖项和可执行文件添加到程序包配置文件中,可以构建和运行它们,并看到的服务/客户端系统运行起来。

这里面有个注意点就是客户端的调用方式,有两种,一个是同步call(self.req)一个是异步call_async(self.req),同步就是一直阻塞到有结果,很容易就死锁了,推荐异步