ROS学习笔记(四)—— 服务 Service 详解

1.服务 service

参考:Writing a Simple Service and Client (Python)

service

官方定义: Request / reply is done via a Service, which is defined by a pair of messages: one for the request and one for the reply.

服务是同步的跨进程函数调用。使用 client/server 模型,能够让客户端节点调用运行在服务端节点中的函数。服务端声明一个服务,并定义了一个回调函数来处理服务请求。客户端通过一个本地的代理请求调用这个服务。
同步:客户端发送请求数据,服务端完成处理后返回应答数据。client 端发送请求后会阻塞,直到 server 端返回结果才会继续执行。

既然有了topic 这种通讯机制,为什么还要有服务呢?原因就在于订阅/发布话题是不同步的,发布者只管发布消息,不管有没有或有几个订阅者,也不管订阅者能不能跟得上自己的发布速度。订阅者则只管监听消息,不会告诉发布者听没听到。这种方式交换数据的效率高,但完全不具备应答功能。

什么时候要应答呢?比如,节点A对着广场疯狂喊:2+3 等于几,2+3 等于几,2+3 等于几…,没有人理他,正确的操作应该是这样:节点A对着会做加法运算(声明了一个加法运算的服务)的节点B说:2+3 等于几。节点B算了一下,然后告诉节点 A:等于5。

这就是需要应答的场合,所以有了服务这个概念。当服务端收到服务请求后,会对请求做出响应,将数据的处理结果返回给客户端。这种模式更适用于双向同步的信息传输。服务调用非常适合那些只需要偶尔去做,并且会在有限的时间里完成的事。

ROS服务相关指令有两个:rosservicerossrv,前者是对ROS服务本身的管理,后者是对ROS 服务类型的管理,相当于话题的rostopicrosmsg

2. 创建 srv 文件

ros 已经定义了一些服务,但我们也可以定义自己的服务。服务自定义文件通常放在功能包的 srv 文件夹下,文件扩展名为 .srv 。服务包含请求(request)数据和应答(response)数据,中间用三个小短线(---)隔开。
例如,定义一个计算字符串中单词个数的服务文件 WordCount.srv

string words
---
uint32 count

定义好了 srv 文件,就需要运行catkin_make命令来编译与服务交互的时候真正会用到的代码和类定义。和自定义 message 一样,编译之前首先要修改CMakeLists.txtpackage.xml文件。
CMakeLists.txt文件添加:

find_package(catkin REQUIRED COMPONENTS
		rospy
		std_msgs
		message_generation  # add message_generation here
)
...
catkin_packages(
		CATKIN_DEPENDS rospy std_msg message_runtime # this will not be the only thing here
...
add_service_files(FILES WordCount.srv)
generate_messages(DEPENDENCIES std_msgs)

package.xml添加编译依赖和执行依赖:

<depend>rospy</depend>
<depend>std_msg</depend>

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

运行catkin_make会生成三个类:WordCount,WordCountRequest,和 WordCountResponse。可以在 devel 文件夹下找到相应的生成文件,当然你可能永远都不需要去查看这些细节。

3. 编写一个 Service Node(python)

服务和话题都基于回调函数的机制。服务器定义一个函数,等待客户端请求服务调用时,调用该函数并返回执行结果。
例程:单词计数服务。service_server.py

#!/usr/bin/env python
# -*- coding: utf-8
import rospy
from pkg_name.srv import WordCount,WordCountResponse

def count_words(request):
    # 回调函数只接受WordCountRequest 类型的的参数,并返回一个WordCountResponse类型的值
    return WordCountResponse(len(request.words.split()))

def server():
    rospy.init_node('service_server')  # 节点初始化
    # 声明服务,服务名称 'word_count',srv类型 WordCount,回调函数 count_words.
    service = rospy.Service('word_count', WordCount, count_words) 
    print("Ready to service.")
    rospy.spin()

if __name__ == "__main__":
    server()    

增加运行权限:chmod +x service_server.py。运行节点 rosrun pkg_name service_server.py
rosservice call word_count "one two three" 命令可以直接调用服务。

注:前文创建WordCount服务时已经编译过功能包,又python是脚本语言,故此处无需编译即可执行。

注意:即使 srv 没有请求数据,例如

# request params
---
# response params
uint32 count

回调函数的定义中也不能减省request参数(如def count_words(req):,不能缺省req参数)。

从服务中返回一些值的其他方法
  • 服务只有一个返回参数时可以直接 return,而无需显示创建一个WordCountResponse对象.
def count_words(request):
	return len(request.words.split())
  • 有多个返回参数时,可以返回一个元组或列表。列表中的值将会按顺序赋给服务中定义的返回参数。也可以返回字典,其中键名是参数的名字(以字符串的形式给出)。
def count_words(request):
   return [len(request.words.split())]
#或返回字典
def count_words(request):
   return {'count':len(request.words.split())}

回调函数(如例程中的 def count_words(request):函数)的定义中也不能减省 requset 参数。

4. 编写一个 Client Node (python)

service_client.py

#!/usr/bin/env python
# -*- coding: utf-8
import rospy
import sys
from pkg_name.srv import WordCount

rospy.init_node('service_client')  # 节点初始化
print("wait for service.")
rospy.wait_for_service('word_count')  # 等待服务端声明这个服务
print("Service has started")
# 声明服务的本地代理,需要指定服务的名称(‘word_count’)和类型(WordCount)
# 这允许我们像使用本地函数一样使用服务。
word_counter = rospy.ServiceProxy('word_count', WordCount)
words =''.join(sys.argv[1:])
word_count = word_counter(words)
print words, '->', word_count.count 
rospy.spin()

如果没有rospy.wait_for_service('word_count'),那么在服务声明之前调用这个服务,会抛出异常。这是话题和服务的一个主要区别。即使一个话题没有声明,我们也可以订阅它。

增加运行权限:chmod +x service_client.py。
运行 rosrun pkg_name service_client.py "these are some words"
会看到输出:these are some words -> 4

如果没有打开服务端,就运行客户端,客户端节点将会阻塞,等待服务被声明。此时启动服务端节点会使客户端节点继续工作。ros 的一大弊端是当服务端不可用时,客户端可能会一直等待下去。

也可以显式的构造一个服务请求对象来进行服务调用:

	#!/usr/bin/env python
	...
	from pkg_name.srv import WordCount WordCountRequest
	...
	request = WordCountRequest('one two three')
    word_count = word_counter(request)
    ...
  • 17
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值