一、ROS2 服务Service 运行示意图
服务Service是一种通信方式,与话题Topic不同,服务是call-and-response模型,不同于话题的publisher-subscriber模型。因此,话题Topic用于订阅数据流并获得持续更新,而服务Service仅在被客户端Client请求调用Request时,从服务端Server取得数据,并将客户端Client的反馈Response传回给服务端Server。
话题Topic用于单向通信,而服务Service用于双向通信,在需要持续发布内容时,通常采用话题Topic方式
ROS2中的每个节点基本都包含这六项Service
/teleop_turtle/describe_parameters
/teleop_turtle/get_parameter_types
/teleop_turtle/get_parameters
/teleop_turtle/list_parameters
/teleop_turtle/set_parameters
/teleop_turtle/set_parameters_atomically
二、ros2 service
ROS2 service下的命令有四条
call Call a service
find Output a list of available services of a given type
list Output a list of available services
type Output a service's type
可以看到和ros1的service没有什么区别
ROS1
Commands: rosservice args print service arguments rosservice call
提供所需参数并请求某服务 call the service with the provided args rosservice find
find services by service type rosservice info print information about
service rosservice list list active services rosservice type print
service type rosservice uri print service ROSRPC uri
① ros2 service list
输出当前存在的话题Service列表
ros2 service list
OUTPUT:
/clear
/kill
/reset
/spawn
/teleop_turtle/describe_parameters
/teleop_turtle/get_parameter_types
/teleop_turtle/get_parameters
/teleop_turtle/list_parameters
/teleop_turtle/set_parameters
/teleop_turtle/set_parameters_atomically
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/describe_parameters
/turtlesim/get_parameter_types
/turtlesim/get_parameters
/turtlesim/list_parameters
/turtlesim/set_parameters
/turtlesim/set_parameters_atomically
同理,当添加 -t 后缀时将同时输出服务列表与各个服务Service的消息类型Message Type
② ros2 service type
输出服务Service的类型,type含有两部分,其中一个消息是用于请求,另一个用于反馈处理结果,他们之间用三个短横线分开
ros2 service type /spawn
OUTPUT:
turtlesim/srv/Spawn
查看消息类型MessageType具体的结构可以通过如下命令
ros2 service type /clear
OUTPUT:
float32 x
float32 y
float32 theta
string name # Optional. A unique name will be created and returned if this is empty
---
string name
③ ros2 service find
根据消息类型查找属于该消息类型的服务
ros2 service find std_srvs/srv/Empty
OUTPUT:
/clear
/reset
④ ros2 service call
直接在命令行中请求服务call service
ros2 service call <service_name> <service_type> <arguments>
以召唤海龟为例
ros2 service call /spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0.2, name: 'new_turtle'}"
OUTPUT:
requester: making request: turtlesim.srv.Spawn_Request(x=2.0, y=2.0, theta=0.2, name='new_turtle')
response:
turtlesim.srv.Spawn_Response(name='new_turtle')
三、服务端 Server
1.新建功能包
ros2 pkg create --build-type ament_python py_srv_cli --dependencies rclpy example_interfaces
此处在新建功能包时为其添加了依赖 rclpy和example_interfaces
example_interfaces是ros2的数据类型接口,里面有用于ROS2教学的一些样例数据类型,比如example_interfaces/srv/AddTwoInts就是本示例要使用的数据类型
2.新建服务端 Server
编写流程
1.编程接口初始化
2.创建节点并初始化
3.创建服务器端对象
4.通过回调函数处进行服务
5.向客户端反馈应答结果
6.销毁节点并关闭接口
源码示例
from example_interfaces.srv import AddTwoInts # 导入需要使用的srv数据类型
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
# 创建server
# 数据类型,服务名称,回调函数
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()
3.添加依赖
在功能包目录下打开package.xml,修改以下内容[可选,不影响使用]
例子:
<description>Examples of minimal publisher/subscriber using rclpy</description>
<maintainer email="hermanye233@icloud.com">Herman Ye</maintainer>
<license>Apache License 2.0</license>
继续添加内容[必须]
当功能包里的代码被执行时,这些语句声明了功能包的依赖
因为在创建功能包时已经添加了依赖,因此此处无需重复添加
<depend>rclpy</depend>
<depend>example_interfaces</depend>
4.添加入口点
打开setup.py,添加入口点
maintainer='Herman Ye',
maintainer_email='hermanye233@icloud.com',
description='Examples of minimal publisher/subscriber using rclpy',
license='Apache License 2.0',
在entry_points下的这个位置添加以下命令
'service = py_srv_cli.service_member_function:main',
最终效果:
entry_points={
'console_scripts': [
'service = py_srv_cli.service_member_function:main',
],
},
四、客户端 Client
1.新建客户端 Client
在和服务端相同的目录下新建client_member_function.py
编写流程
1.编程接口初始化
2.创建节点并初始化
3.创建客户端对象
4.创建并发送请求数据
5.等待服务器端应答数据
6.销毁节点并关闭接口
源码示例
import sys #客户端节点代码使用sys.argv来访问命令行输入的请求命令的参数,这是客户端的特别之处
from example_interfaces.srv import AddTwoInts # 数据类型导入
import rclpy
from rclpy.node import Node # 节点必备的类
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async') # 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() #request部分定义
def send_request(self): # 发送函数
self.req.a = int(sys.argv[1]) # 命令行输入的数1
self.req.b = int(sys.argv[2]) # 命令行输入的数2 sys.argv[x]获取了命令行输入的参数
self.future = self.cli.call_async(self.req) #future包含反馈的response
def main(args=None):
rclpy.init(args=args) #初始化rclpy
minimal_client = MinimalClientAsync() # 客户端
minimal_client.send_request() # 客户端发送请求
while rclpy.ok(): # 当ros准备就绪时
rclpy.spin_once(minimal_client) # 循环客户端
if minimal_client.future.done(): # 当future完成时,代表服务端已经完成了服务并且反馈response了result
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()
call_async()
也可以使用call_async()来执行异步呼叫服务,作用和上文中的send_request类似但更简单,上文的send_request有输入参数,因此独立成发送函数
call_async(request)
Make a service request and asyncronously get the result.
Parameters
request (~SrvTypeRequest) – The service request.
Return type
Future
Returns
A future that completes when the request does.
Raises
TypeError if the type of the passed request isn’t an instance of the Request type of the provided service when the client was constructed.
要获取更多信息请查阅rclpy文档
2.添加入口点
打开setup.py,添加入口点
在entry_points下的这个位置添加以下命令
'client = py_srv_cli.client_member_function:main',
最终效果:
entry_points={
'console_scripts': [
'service = py_srv_cli.service_member_function:main',
'client = py_srv_cli.client_member_function:main',
],
},
五、测试 客户端-服务-服务端
1.编译
colcon build --packages-select py_srv_cli
2.运行
先运行服务端
ros2 run py_srvcli service
随后运行客户端并键入参数
ros2 run py_srvcli client 2 3