编写简单的服务和客户端 (Python)
目标: 使用 Python 创建并运行服务和客户端节点。
教程级别: 初学者
时间: 20 分钟
背景
当 nodes 使用 services 进行通信时,发送数据请求的节点称为客户端节点,响应请求的节点称为服务节点。
请求和响应的结构由 .srv
文件确定。
这里使用的示例是一个简单的整数加法系统;一个节点请求两个整数之和,另一个节点响应结果。
先决条件
任务
1 创建包
打开一个新终端并 source your ROS 2 installation,以便 ros2
命令可以正常工作。
导航到在 previous tutorial 中创建的 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 文件 <https://github.com/ros2/example_interfaces/blob/rolling/srv/AddTwoInts.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 License 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 License 2.0',
2 编写服务节点
在“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 检查代码
第一个 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
最后,主类初始化 ROS 2 Python 客户端库,实例化 MinimalService
类以创建服务节点并旋转节点以处理回调。
2.2 添加入口点
要允许 ros2 run
命令运行您的节点,您必须将入口点添加到 setup.py``(位于 ``ros2_ws/src/py_srvcli
目录中)。
在 'console_scripts':
括号之间添加以下行:
'service = py_srvcli.service_member_function:main',
3 编写客户端节点
在 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
return self.cli.call_async(self.req)
def main():
rclpy.init()
minimal_client = MinimalClientAsync()
future = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
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' %
(int(sys.argv[1]), int(sys.argv[2]), response.sum))
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
3.1 检查代码
与服务代码一样,我们首先“导入”必要的库。
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”方法,它将发送请求并返回可以传递给“spin_until_future_complete”的未来:
def send_request(self, a, b):
self.req.a = a
self.req.b = b
return self.cli.call_async(self.req)
最后我们有“main”方法,它构造一个“MinimalClientAsync”对象,使用传入的命令行参数发送请求,调用“spin_until_future_complete”,并记录结果:
def main():
rclpy.init()
minimal_client = MinimalClientAsync()
future = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
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' %
(int(sys.argv[1]), int(sys.argv[2]), response.sum))
minimal_client.destroy_node()
rclpy.shutdown()
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”来检查是否缺少依赖项:
rosdep install -i --from-path src --rosdistro rolling -y
rosdep only runs on Linux, so you can skip ahead to next step.
rosdep only runs on Linux, so you can skip ahead to next step.
导航回到工作区的根目录“ros2_ws”,并构建新的包:
colcon build --packages-select py_srvcli
打开一个新终端,导航到“ros2_ws”,并获取安装文件:
source install/setup.bash
. install/setup.bash
call install/setup.bat
现在运行服务节点:
ros2 run py_srvcli service
节点将等待客户端的请求。
打开另一个终端并再次从“ros2_ws”内部获取安装文件。 启动客户端节点,后跟任意两个用空格分隔的整数:
ros2 run py_srvcli client 2 3
例如,如果您选择“2”和“3”,客户端将收到如下响应:
[INFO] [minimal_client_async]: Result of add_two_ints: for 2 + 3 = 5
返回运行服务节点的终端。 您将看到它在收到请求时发布了日志消息:
[INFO] [minimal_service]: Incoming request
a: 2 b: 3
在服务器终端输入“Ctrl+C”即可停止节点旋转。
摘要
您创建了两个节点来通过服务请求和响应数据。 您将它们的依赖项和可执行文件添加到包配置文件中,以便您可以构建和运行它们,从而让您看到服务/客户端系统在工作。
后续步骤
在过去的几个教程中,您一直在利用接口在主题和服务之间传递数据。 接下来,您将学习如何:doc:创建自定义接口。
相关内容
有几种方法可以用 Python 编写服务和客户端;查看`ros2/examples <https://github.com/ros2/examples/tree/rolling/rclpy/services>`_ repo 中的``minimal_client``和``minimal_service``包。
在本教程中,您使用客户端节点中的``call_async()`` API 来调用服务。
Python 还有另一个可用的服务调用 API,称为同步调用。 我们不建议使用同步调用,但如果您想了解更多信息,请阅读指南:doc:同步与异步客户端。