Understanding ROS 2 services
目标: 使用命令行工具学习关于ROS 2 中的服务(services)的知识。
背景知识
服务(services)是ROS中用于节点之间沟通的另外一种方式。之所以说另外一种,之前发布的教程中就讲了一种,是通过话题(topic)进行沟通的,有兴趣的话,可以戳:理解ROS 2话题(topics)。
服务是基于一种call-and-response(呼叫-回应)模型,而话题是publisher-subscriber(发布-订阅)模型。使用话题的时候,节点通过订阅,不断的接收数据流,持续更新。但是服务就不一样,是需要节点呼叫了一个提供服务的节点,才会得到一次数据。类似于,在烤肉店里面,有一个服务员站在旁边一直给你烤肉,是持续的;但是你突然需要加一双筷子,喊了一下:“服务员,麻烦给我拿一双筷子。”服务员就会给你送过来一双筷子。这种就是喊一次,递一双,不会一直给你递筷子。大家可以看到两种不同的通讯方式,其实会对应着系统中两种不同的需求,实际使用的时候,可以根据实际情况选择。
前提条件
这个教程中提及的一些概念,在我之前的ROS 2系列教程中已经讲解过,如果还不熟悉,可以跳过去看看,比如节点和和话题。这里,你还需要已经安装过turtlesim的包了,如果你还没安装或者不确定是否已经安装过,也可以跳过去扫一眼:turtlesim的安装。最后,记得在每个新开启的终端都先source一下ROS 2 的设置文件,如果不清楚如何source的,可以看之前的:设置ROS 2环境。
任务
1. 设置
开启两个turtlesim的节点,/turtlesim
和/teleop_turtle
。操作方法如下。
打开一个新的终端,并运行:
ros2 run turtlesim turtlesim_node
打开另外一个终端,并运行:
ros2 run turtlesim turtle_teleop_key
2. 查看ros2 service
在一个新的终端运行 ros2 service list
可以得到当前系统中活跃的所有ROS 2的服务(services):
/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
你可以看到,两个节点都有相同的六个名字中带有parameter
的服务。几乎每一个ROS 2的节点都有这些基础的服务。在下一个教程中,我们才会更加详细的介绍参数。在这个教程,先不讨论参数服务。
现在,让我们先集中注意力在turtlesim独有的服务上, /clear
, /kill
, /reset
, /spawn
, /turtle1/set_pen
, /turtle1/teleport_absolute
, and /turtle1/teleport_relative
。你可能会记起来,我们曾经在介绍turtlesim和rqt的教程中,使用rqt跟这些服务做了一些交互。
3. 查看ros2 服务(service)的类型
服务(service)是有类型的,类型描述了 一个服务请求以及回应的数据是如何组织的。 服务的类型被定义成类似于话题的类型,不同的是, 服务的类型包含两个部分:一个是用来请求服务的消息,另一个是用与回复的数据的结构。可以通过下面的指令查看一个服务的类型:
ros2 service type <service_name>
让我们看一下turtlesim的/clear
这个服务。在一个新的终端,输入下面的命令:
ros2 service type /clear
将会有下面的返回:
std_srvs/srv/Empty
Empty
类型意味着调用这个服务不需要输入数据,并且收到的回复也没有数据。
3.1 ros2 service list -t
为了能够同时查看所有活跃的服务的类型, 你可以在后面添加--show-type
这个选项,缩写是t
,具体如下面的命令所示:
ros2 service list -t
这将会返回:
/clear [std_srvs/srv/Empty]
/kill [turtlesim/srv/Kill]
/reset [std_srvs/srv/Empty]
/spawn [turtlesim/srv/Spawn]
...
/turtle1/set_pen [turtlesim/srv/SetPen]
/turtle1/teleport_absolute [turtlesim/srv/TeleportAbsolute]
/turtle1/teleport_relative [turtlesim/srv/TeleportRelative]
...
4. 查找ros2 service
如果你想找到某一个特殊类型的所有服务,你可以使用下面的指令。
ros2 service find <type_name>
比如你可以像这样找到所有empty
类型的服务。
ros2 service find std_srvs/srv/Empty
上面的指令你将会得到这样的输出。
/clear
/reset
5. 显示ros2 interface(接口)
你可以通过命令行调用服务,但是你需要首先知道输入的结构。 可以通过下面的指令来获取。
ros2 interface show <type_name>.srv
比如,我们对/clear
这个服务的类型使用上面的这个指令:
ros2 interface show std_srvs/srv/Empty.srv
将会得到这样的输出:
---
---
这个符号会分隔开服务的请求数据结构(上方),以及回复数据结构(下方)。 但是,正如你之前所学到的一样,这个类型不会发送或者接收任何数据,因此,很自然的,它的结构是空白的。
让我们再来看一个带有发送和接收数据的服务的类型,比如 /spawn
。从 ros2 service list -t
的结果, /spawn
的类型是 turtlesim/srv/Spawn
。
让我们看一下/spawn
这个服务的调用和回复的参数,运行下面的指令:
ros2 interface show turtlesim/srv/Spawn
将会返回下面的结果:
float32 x
float32 y
float32 theta
string name # Optional. A unique name will be created and returned if this is empty
---
string name
---
上方的信息告诉我们,调用需要的参数包括:x
,y
和theta
,以便确定海龟生成的地方,而name
显然就是可选的。
在这个例子中,下方的信息是你不需要知道的,但是它可以帮助你理解你调用了服务之后得到的数据类型。
6. 调用ros2 service
现在你已经知道了,什么是一个服务的类型,应该如何找到服务的类型以及如何找到这个类型所对应的参数的数据结构。因此, 你现在可以使用下面这个命令来调用一个服务。
ros2 service call <service_name> <service_type> <arguments>
参数的部分是可选的,比如你知道了empty
这个服务的类型其实并不需要任何的参数,所以你可以向下面这个命令一样调用这个服务。
ros2 service call /clear std_srvs/srv/Empty
这一句命令将会清除海龟在窗口中划下的所有线。
现在让我们通过输入参数来调用这个服务。在命令行中输入参数来调用一个服务的时候, 参数需要被写成YAML语法。
比如输入下面的指令。
ros2 service call /spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0.2, name: ''}"
你将会看到服务的响应:
waiting for service to become available...
requester: making request: turtlesim.srv.Spawn_Request(x=2.0, y=2.0, theta=0.2, name='None')
response:
turtlesim.srv.Spawn_Response(name='None')
你的海龟窗口立刻会产生一个新的小海龟。
总结
节点之间可以通过服务来进行沟通。 但是一般不推荐通过服务来实现持续的调用,如果需要持续的获取数据,话题(topic),甚至是动作(action)都可能是更合适的选择。