ROS 2 Iron 教程 第二章 Client libraries 第五节 编写一个简单的服务端和客户端(Python))

前言

本系列文章是由笔者翻译自ROS 2 官方教程。笔者水平有限,如有错误,还请读者指正。

背景

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

本节教程使用的示例是一个简单的整数相加系统;一个节点请求两个整数的和,而另一个节点以结果响应。

先决条件

在先前的教程中,我们已经学会如何创建一个工作空间,以及创建一个包。

任务

Create a package (创建一个包)

打开一个新终端,并添加 ROS 2 安装,使得ros2命令能运作。

cd进入先前教程创建的ros2_ws目录。

回忆一下,包应被创建在src目录中,而不是工作空间根目录。cd进入ros2_ws/src目录,随后创建一个新包:

ros2 pkg create --build-type ament_python --license Apache-2.0 py_srvcli --dependencies rclpy example_interfaces

你的终端将会返回信息以证明py_srvcli包的创建,以及必要文件和文件夹创建成功。

--dependencies参数将会自动在package.xmlCMakeLists.txt中添加必要的依赖行。

example_interfaces包含了.srv的包,你将会需要这个文件来编写你的请求和响应的数据结构:

int64 a
int64 b
---
int64 sum

短横线上方两行是请求的参数,短横线下方是响应的参数。

1.1 Update package.xml (更新 package.xml)

因为我们在创建包的时候使用了--dependencies选项,所以我们不必在package.xml中手动添加依赖。

一如既往,在package.xmldescriptionmaintaineremail等标签中写入信息。

<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 (更新 setup.py)

setup.py文件中的maintainer_emaildescriptionlicense字段中写入相同的信息:

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

2 Write the service node (编写服务节点)

ros2_ws/src/py_srvcli/py_srvcli目录中,创建service_member_function.py文件并在文件中黏贴如下代码:

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

2.1 Examine the code (分析代码)

代码开头的第一行import语句从example _interfaces包中导入AddTwoInts服务。随后的import语句到了 ROS 2 Python 库,以及Node类。

from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node

MinimalService类的构造函数以minimal_service名字初始化节点。随后,它创建了一个服务并定义了类型名以及回调函数。

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

最后,main 函数初始化 ROS 2 Python 库,将MinimalService类实例化来创建服务节点,启动节点来处理调用。

2.2 Add an entry point (添加启动点)

为了让ros2 run命令运行你的代码,你必须在setup.py中添加启动点(位于ros2_ws/src/py_srvcli目录)。

console_scripts括弧中添加如下代码:

'service = py_srvcli.service_member_function:main',

3 Write the client node (编写客户端节点)

ros2_ws/src/py_srvcli/py_srvcli目录中,创建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()

3.1 Examine the code (分析代码)

同服务节点类似的是,我们先import必要的库。

import sys

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

MinimalClientAsync类的构造函数以minimal_client_async名字初始化节点。构造函数创建了与服务端节点相同类型以及名字的客户端节点。客户端和服务端的类型与名字必须匹配,它们之间才能进行通信。在构建函数中的while循环每秒检查一次与客户端相对应的服务是否可用。最后它创建了一个AddTwoInts请求对象。

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

在构造函数的下方是send_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()

最后是main函数,其中构建了MinimalClientAsync对象,使用传入的命令行参数发送请求并记录结果。

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

3.2 Add an entry point (添加启动点)

与服务端节点相似,客户端代买需要添加一个启动点来运行。

最终,setup.py中的entry_points字段应该像如下这样:

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

3 Build and run (构建并运行)

构建之前在你的工作空间的根目录(ros2_ws)运行rosdep来检查是否有缺失的依赖是一个好习惯(Liunx):

rosdep install -i --from-path src --rosdistro iron -y

在你工作空间的根目录中(ros2_ws)构建你的新包:

colcon build --packages-select py_srvcli

打开一个新终端,cd进入ros2_ws并添加启动文件:

source install/setup.bash

现在运行服务端节点:

ros2 run py_srvcli service

服务端节点将会等待客户端节点的请求。

打开另一个终端并添加在ros2_ws中的启动文件。启动客户端节点,在命令后填入两个以空格分隔的整数:

ros2 run py_srvcli client 2 3

如果你选择23,客户端将会收到像这样的相应:

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

回到你正在运行服务端节点的终端,你将会看到它在收到请求的时候显示了日志消息:

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

在这些终端中按下Ctrl+C来停止节点运行。

总结

我们创建了两个节点来通过服务请求和响应数据。而且我们已将其依赖项和可执行文件添加到包配置文件中,以便可以构建和运行它们,并查看正在工作的服务/客户端系统。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值