引言
ROS node 之间的通讯形式主要包括两种:topic 和 service。
通过 topic 通讯时,不同的 node 可以向同一个 topic 上发送、接收数据,发送数据的 node 不知道数据是从哪个 node 发送过来的,同样地,发送数据的 node 也不知道是哪个 node 接收了数据。因此,每个 node 都是相对独立的,只需要负责自己的功能实现以及外部接口,不需要关心其他 node 的行为。这是一种开放式的收、发数据的方式,也是 node 之间通讯的主要形式,有利于构造分布式大系统。
service 则是一种请求+反馈的通信机制。消息的传输只涉及两个 node:发送请求的一方称为 client,提供服务的一方叫做 server。在通过 service 形式进行通讯时,client 首先向 server 请求服务, 收到消息之后 server 运行事先设置好的服务功能,并返回消息给 client。service 通讯一般用在事件触发情景中,例如满足某个条件就令 node 开启某项功能,并希望确认功能确实顺利开启。
本文通过实例介绍如何创建 srv 文件,这个很像 msg 文件,只不过格式中包括 request 和 response 两部分内容。然后介绍如何通过 roscpp 和 rospy 构造 client 和 server 节点。
具体的,我们希望创建一个 server 节点,可以接受外界请求,请求内容包括 name 和 age,然后 server 提供的服务就是显示问候信息。
创建 Greeting.srv
我们知道 node 通过 topic 进行通讯时需要借助 msg 作为数据传输的载体,同样的,service 通讯时也需要借助某种数据载体,这里就是 srv 。ROS 自带了很多 srv 类型,基本可以满足常见的 service 通讯需求。不过,这里为了比较全面地介绍 service 通讯过程,我们自己创建一个 srv,并在 service 通讯中使用它。
首先创建一个 srv 名为 Greeting.srv
。一般将自定义的 msg 文件存放在 <package>/msg 文件夹中,srv 文件存放在 <package>/srv 文件夹中。这里我们利用之前创建的一个 package,名字是 agitr
。在 agitr/srv
中创建Greeting.srv
文件,内容如下:
string name
int32 age
---
string feedback
其中,短线上边是 request 内容,就是 client 传递给 server 的消息,下边是 response 内容,就是 server 反馈给 client 的消息。
创建完 srv 文件之后,需要进行编译。实际上,编译 srv 与编译 msg 时的设置几乎是完全相同的,原本修改 msg 的地方,现在就修改 srv。
在 CmakeLists.txt
文件中修改如下
find_package(catkin REQUIRED COMPONENTS message_generation)
add_service_files(FILES Greeting.srv)
在 package.xml
中的修改
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
修改完成后回到工作空间,进行编译。编译成功后就可以像 ROS 自带的 srv 类型一样使用了。
利用 roscpp 编写 server node
在 agitr/src 目录下创建 c++ 源文件 server.cpp,内容如下:
#include <ros/ros.h>
#include <agitr/Greeting.h>
bool handle_function(agitr::Greeting::Request &req, agitr::Greeting::Response &res){
ROS_INFO("Request from %s with age %d", req.name.c_str(), req.age);
res.feedback = "Hi" + req.name + ". I’m server!";
return true;
}
int main(int argc, char** argv){
ros::init(argc,argv, "greetings_server");
ros::NodeHandle nh;
ros::ServiceServer service = nh.advertiseService("greetings", handle_function);
ros::spin();
return 0;
}
在以上代码中,服务的处理操作都在 handle_function()
中,它的输入参数就是 Greeting.srv
中的Request
和 Response
两部分。通常在处理函数中,我们对 Request
部分的数据进行相应的服务操作,然后将结果写入到 Response
中。处理函数返回值是 bool
类型,表征服务是否成功执行。
编写完 c++ 源文件之后还要对 CmakeLists.txt
文件和 package.xml
文件做相应修改,然后编译。
下面介绍如何用 rospy 实现上述同样的 server node.
利用 rospy 编写 server node
将创建的 python 文件存放在路径为 agitr/scripts
目录下,命名为 server.py
,内容如下:
#!/usr/bin/env python
#coding=utf-8
import rospy
from agitr.srv import *
def server_srv():
# 初始化节点,命名为"greetings_server"
rospy.init_node("greetings_server")
#定义service的server端,service名称为"greetings",service类型为Greeting
#收到的request请求信息将作为参数传递给handle_function进行处理
s = rospy.Service("greetings", Greeting, handle_function)
rospy.loginfo("Ready to handle the request:")
rospy.spin()
def handle_function(req):
rospy.loginfo( 'Request from %s with age %d', req.name,,req.age)
return GreetingResponse("Hi %s. I' server!"%req.name)
if __name__=="__main__":
server_srv()
这里 server
端的处理函数有区别: C++ 的handle_function()
传入的参数是整个 srv
对象的 request
和 response
两部分,返回值是 bool
型,显示这次服务是否成功的处理,即:
bool handle_function(agitr::Greeting::Request &req,agitr::Greeting::Response &res){
...
return true;
}
而 Python 的 handle_function()
传入的只有 request
,返回值是 response
,即:
def handle_function(req):
...
return GreetingResponse("Hi %s. I'am server!"%req.name)
利用 rospy 编写 client node
将创建的文件存放在 agitr/scripts
目录下,命名为 client.py
,内容如下:
#!/usr/bin/env python
#coding:utf-8
import rospy
from agitr.srv import *
def client_srv():
rospy.init_node('greetings_client')
# 等待有可用的服务"greetings"
rospy.wait_for_service("greetings")
try:
# 定义service客户端,service 名称为 “greetings”,service 类型为 Greeting
greetings_client = rospy.ServiceProxy("greetings",Greeting)
# 向server端发送请求,发送的request内容为 name 和 age,其值分别为 "HAN", 20
# 此处发送的 request 内容与 srv 文件中定义的 request 部分的属性是一致的
#resp = greetings_client("HAN",20)
resp = greetings_client.call("HAN",20)
rospy.loginfo("Message From server:%s"%resp.feedback)
except rospy.ServiceException, e:
rospy.logwarn("Service call failed: %s"%e)
if __name__=="__main__":
client_srv()
以上代码中 greetings_client.call("HAN",20)
等同于 greetings_client("HAN",20)
。
至此就创建了 server.py
和 client.py
两个 python 文件,python 文件不需要编译,但是要将它们设置为可执行文件,操作如下:
chmod +x server.py
chmod +x client.py
最后测试一下效果,启动 server 和 client 两个 ROS node,显示结果如下:
Written by SH
Revised by QP