1.服务是什么
1.1 概述
ROS2服务(Service)是ROS2中一种同步通信机制,采用客户端-服务器模型,允许节点之间进行请求-响应式的交互。 也可以简单理解为客户端发送web请求,服务器端给出响应。
1.2 服务通信模型
如下图所示,客户端在需要某些数据时,针对某个具体的服务,发送请求信息, 提供该服务的服务端收到请求后,会进行处理并反馈应答信息。
1.3 同步通信
同步通信是ROS2中服务通信的特点。但是客户端代码是可以异步调用的。客户端代码的“异步调用”与底层通信的同步特性不矛盾。 ROS 2 服务的同步通信有以下特点:
- 一个请求对应一个回复:客户端发送一个请求,服务器必须且只能回复一个响应。
- 时序上的配对:请求和回复是一一对应的。服务器在处理完请求之前,不会发送其他消息。
1.4 服务接口
和话题通信类似,服务通信的核心是传递数据,数据变成了两部分,即一个请求数据和一个 响应数据,这些数据和话题消息一样,在ROS 2中也需要标准定义,话题使用.msg 文件定义, 服务使用.srv 文件定义。
2.服务通信编程示例
该示例的功能:客户端发送两个整数,服务端返回它们的和。
2.1创建接口功能包
创建接口功能包(把接口统一放在该功能包下)
ros2 pkg create --build-type ament_cmake learn_interface --license Apache-2.0
这里创建了一个C++功能包,你也可以创建python功能包。官方推荐使用C++功能包存放服务接口。
2.2创建服务接口
在learn_interface目录下创建srv目录 接着在srv目录下创建AddTwoInts.srv:
int64 a
int64 b
---
int64 sum
2.3修改CMakeLists.txt和package.xml
在CMakeLists.txt添加内容:
#查找依赖的功能包rosidl_default_generators,
find_package(rosidl_default_generators REQUIRED)
#罗列需要编译的通信接口文件
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/AddTwoInts.srv"
)
package.xml需要添加代码生成的功能依赖,以便编译时快速 定位所需要的功能包 在package.xml添加内容:
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
2.4创建服务功能包
创建服务功能包(为方便起见,服务端和客户端放在同一个功能包)
#在ROS2工作空间的src目录下执行
ros2 pkg create --build-type ament_python learn_service --license Apache-2.0
2.5编写服务端节点(Python)
在learn_service/learn_service目录下创建service_adder_server.py:
import rclpy
from rclpy.node import Node
from learn_interface.srv import AddTwoInts # 自定义的服务接口
class adderServer(Node):
def __init__(self, name):
super().__init__(name)
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.adder_callback) # 创建服务器对象(接口类型、服务名、服务器回调函数)
def adder_callback(self, request, response): # 创建回调函数,执行收到请求后对数据的处理
response.sum = request.a + request.b
self.get_logger().info('请求数据\na: %d b: %d' % (request.a, request.b)) # 输出日志信息,提示已经完成加法求和计算
return response
def main(args=None):
rclpy.init(args=args)
node = adderServer("service_adder_server") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node()
rclpy.shutdown()
2.6编写客户端节点(Python)
在learn_service/learn_service目录下创建service_adder_client.py:
import random
import rclpy
from rclpy.node import Node
from learn_interface.srv import AddTwoInts # 自定义的服务接口
class adderClient(Node):
def __init__(self, name):
super().__init__(name)
self.client = self.create_client(AddTwoInts, 'add_two_ints') # 创建服务客户端对象(服务接口类型,服务名)
while not self.client.wait_for_service(timeout_sec=1.0): # 循环等待服务器端成功启动
self.get_logger().info('服务不可用, 请等待...')
self.request = AddTwoInts.Request() # 创建服务请求的数据对象
def send_request(self):
self.request.a = random.randint(0, 100)
self.request.b = random.randint(0, 100)
self.future = self.client.call_async(self.request) # 异步方式发送服务请求
def main(args=None):
rclpy.init(args=args) # ROS2 Python接口初始化
node = adderClient("service_adder_client")
node.send_request() # 发送服务请求
while rclpy.ok(): # ROS2系统正常运行
rclpy.spin_once(node) # 循环执行一次节点
if node.future.done(): # 数据是否处理完成
try:
response = node.future.result() # 接收服务器端的反馈数据
except Exception as e:
node.get_logger().info(
'服务调用失败 %r' % (e,))
else:
node.get_logger().info( # 将收到的反馈信息打印输出
'调用结果 %d + %d = %d' %
(node.request.a, node.request.b, response.sum))
break
node.destroy_node()
rclpy.shutdown()
2.7 修改setup.py和package.xml
setup.py文件:
entry_points={
'console_scripts': [
'service_adder_client = learn_service.service_adder_client:main',
'service_adder_server = learn_service.service_adder_server:main',
],
},
不同功能包相互隔离,要使用服务接口,需要在package.xml文件添加依赖:
<depend>learn_interface</depend>
2.8 构建并运行节点
2.8.1构建功能包
#构建服务接口功能包
colcon build --packages-select learn_interface
#构建服务功能包
colcon build --packages-select learn_service
或者直接运行
colcon build
2.8.2运行节点
#运行客户端节点
source install/setup.bash
ros2 run learn_service service_adder_client
#运行服务器端节点
source install/setup.bash
ros2 run learn_service service_adder_server
2.9 输出示例
服务器端:
ros2 run learn_service service_adder_server
[INFO] [1775789969.935082632] [service_adder_server]: 请求数据
a: 90 b: 47
[INFO] [1775789985.368041400] [service_adder_server]: 请求数据
a: 92 b: 90
客户端:
ros2 run learn_service service_adder_client
[INFO] [1775789969.939193882] [service_adder_client]: 调用结果 90 + 47 = 137
ros2 run learn_service service_adder_client
[INFO] [1775789985.372322300] [service_adder_client]: 调用结果 92 + 90 = 182
3.服务的命令行操作
3.1 查看服务列表
输入:
ros2 service list
显示结果:
/add_two_ints
/service_adder_server/describe_parameters
/service_adder_server/get_parameter_types
/service_adder_server/get_parameters
/service_adder_server/list_parameters
/service_adder_server/set_parameters
/service_adder_server/set_parameters_atomically
/add_two_ints就是之前我们运行的服务名称。
3.2 查看服务数据类型
ros2 service type [service_name]
输入:
ros2 service type /add_two_ints
显示结果:
learn_interface/srv/AddTwoInts
3.3 发送服务请求
ros2 service call [service_name] [service_type] [service_data]
输入:
ros2 service call /add_two_ints learn_interface/srv/AddTwoInts "{a: 16, b: 25}"
显示结果:
requester: making request: learn_interface.srv.AddTwoInts_Request(a=16, b=25)
response:
learn_interface.srv.AddTwoInts_Response(sum=41)