机器人系统——ROS2文档—编写简单的服务以及客户端节点(Python)
目标:使用 Python 创建并运行服务和客户端节点。教程级别:入门级时间: 20 分钟
目录
- 背景
- 前置条件
- 任务
- 1 创建包
- 2 编写服务节点
- 3 编写客户端节点
- 4 构建并运行
- 总结
- 后续步骤
- 相关资料
背景
当节点使用服务进行通信时,发送数据请求的节点称为客户端节点,响应请求的节点称为服务节点。请求和响应的结构由.srv文件决定。
这里使用的示例是一个简单的整数加法系统;一个节点请求两个整数的和,另一个节点返回结果。
前置条件
在之前的教程中,您学习了如何创建工作空间和创建包。
任务
1创建包
打开一个新终端并加载您的 ROS 2 安装,以便ros2命令能够工作。
导航到在上一个教程中创建的ros2_ws目录。
回想一下,包应该创建在src目录中,而不是工作空间的根目录。导航到ros2_ws/src并创建一个新包:
$ ros2 pkg create --build-type ament_python --license Apache-2.0 py_srvcli --dependencies rclpy example_interfaces
您的终端将返回一条消息,确认您的包py_srvcli及其所有必要的文件和文件夹已创建。
--dependencies参数会自动将必要的依赖行添加到package.xml。example_interfaces是包含.srv 文件的包,您将需要该文件来构建请求和响应的结构:
int64 a
int64 b
---
int64 sum
前两行是请求的参数,横线下面是响应。
1.1 更新 package.xml
由于您在包创建期间使用了--dependencies选项,因此不必手动向package.xml添加依赖项。
不过,像往常一样,确保将描述、维护者电子邮件和名称以及许可证信息添加到package.xml中。
<description>Python client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>
1.2 更新 setup.py
将相同的信息添加到setup.py文件的maintainer、maintainer_email、description和license字段:
maintainer='Your Name',
maintainer_email='you@email.com',
description='Python client server tutorial',
license='Apache-2.0',
2编写服务节点
在ros2_ws/src/py_srvcli/py_srvcli目录中,创建一个名为service_member_function.py的新文件,并粘贴以下代码:
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.executors import ExternalShutdownException
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():
try:
with rclpy.init():
minimal_service = MinimalService()
rclpy.spin(minimal_service)
except (KeyboardInterrupt, ExternalShutdownException):
pass
if __name__ == '__main__':
main()
2.1 检查代码
第一个import语句从example_interfaces包导入AddTwoInts服务类型。接下来的import语句导入必要的 ROS 2 Python 客户端库接口。
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.executors import ExternalShutdownException
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 添加入口点
为了允许ros2 run命令运行您的节点,您必须将入口点添加到位于ros2_ws/src/py_srvcli目录中的setup.py。
在'console_scripts':括号之间添加以下行:
'service = py_srvcli.service_member_function:main',
3编写客户端节点
在ros2_ws/src/py_srvcli/py_srvcli目录中,创建一个名为client_member_function.py的新文件,并粘贴以下代码:
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.executors import ExternalShutdownException
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 = 41
self.req.b = 1
return self.cli.call_async(self.req)
def main(args=None):
try:
with rclpy.init(args=args):
minimal_client = MinimalClientAsync()
future = minimal_client.send_request()
rclpy.spin_until_future_complete(minimal_client, future)
response = future.result()
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
except (KeyboardInterrupt, ExternalShutdownException):
pass
if __name__ == '__main__':
main()
3.1 检查代码
与服务代码一样,我们首先导入必要的库。
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.executors import ExternalShutdownException
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):
self.req.a = 41
self.req.b = 1
return self.cli.call_async(self.req)
最后我们有main方法,它构造一个MinimalClientAsync对象,使用传入的命令行参数发送请求,调用rclpy.spin_until_future_complete等待结果,并记录结果。
def main(args=None):
try:
with rclpy.init(args=args):
minimal_client = MinimalClientAsync()
future = minimal_client.send_request()
rclpy.spin_until_future_complete(minimal_client, future)
response = future.result()
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
except (KeyboardInterrupt, ExternalShutdownException):
pass
警告:不要在 ROS 2 回调中使用rclpy.spin_until_future_complete。有关更多详细信息,请参阅同步死锁文章。
3.2 添加入口点
与服务节点一样,您也必须添加一个入口点才能运行客户端节点。
setup.py文件的entry_points字段应该如下所示:
entry_points={
'console_scripts': [
'service = py_srvcli.service_member_function:main',
'client = py_srvcli.client_member_function:main',
],
},
4 构建并运行
最佳实践是在工作空间根目录(ros2_ws)运行rosdep来检查构建前是否缺少依赖项:
Linux:
$ rosdep install -i --from-path src --rosdistro lyrical -y
导航回工作空间根目录ros2_ws,并构建您的新包:
$ colcon build --packages-select py_srvcli
打开一个新终端,导航到ros2_ws,并加载设置文件:
Linux:
$ source install/setup.bash
现在运行服务节点:
$ ros2 run py_srvcli service
该节点将等待客户端的请求。
打开另一个终端,再次从ros2_ws内部加载设置文件。启动客户端节点。客户端向服务发送请求,服务计算和并返回结果:
$ ros2 run py_srvcli client
[INFO] [minimal_client_async]: Result of add_two_ints: for 41 + 1 = 42
返回到服务节点正在运行的终端。您将看到它在收到请求时发布了日志消息:
[INFO] [minimal_service]: Incoming request
a: 41 b: 1
在服务端终端中输入Ctrl+C以停止节点旋转。
总结
您创建了两个节点,用于通过服务请求和响应数据。您将它们的依赖项和可执行文件添加到包配置文件中,以便能够构建和运行它们,从而看到服务/客户端系统的运行。
后续步骤
在最后几个教程中,您一直在使用接口通过话题和服务传递数据。接下来,您将学习如何创建自定义接口。
相关资料
- 用 Python 编写服务和客户端有几种方法;请查看ros2/examples仓库中的
minimal_client和minimal_service包。 - 在本教程中,您在客户端节点中使用了
call_async()API 来调用服务。还有一个可用于 Python 的服务调用 API,称为同步调用。我们不建议使用同步调用,但如果您想了解更多关于它们的信息,请阅读同步与异步客户端指南。
本文档完整翻译自 ROS 2 Lyrical 官方文档,仅用于学习交流。
夜雨聆风