看这张图怎么感觉服务有种TCP的味道,可靠连接,三次握手,话题像UDP,发出去我就不管了。看来服务不单单是我请求你响应那么简单,还得保证我收到。
摘自:https://blog.csdn.net/moshuilangting/article/details/86484042
ROS话题和服务
墨水兰亭 2019-01-16 15:22:36 3858 收藏 25
分类专栏: 人工智障
版权
基本概念:
ROS中有四种通信方式:Topic(话题)、Service(服务)、Parameter Serivce(参数服务器)、Actionlib(动作库)。
所谓通信,ros中每个程序包是一个node(节点),通信就是节点与节点间的数据传输,在这里简要介绍一下话题与服务
Topic话题 | Service服务 |
---|---|
节点A——>话题T (节点A发布话题T) 话题T——>节点B (节点B订阅话题T) | 节点A——>服务节点S (节点A请求服务S) 服务节点S<——节点A (服务S响应节点A) |
异步通讯 A发了朋友圈,B刷朋友圈的时候看到了 A发布话题,B订阅话题,朋友圈是话题T | 同步通讯 A发了朋友圈,并让S去给A点赞,S点赞完告诉A。 A请求S点赞,B响应A的请求。A是客户端节点,S是服务节点 |
话题内容的数据格式——msg | 服务的通信数据格式——srv |
用于连续高频的数据发布和接收:雷达测障碍物、里程计等 | 用于偶尔调用的功能或执行某一项功能:拍照、语言识别等 |
实现:
在catkin_ws下我们创建一个新的ros包
-
cd catkin_ws
-
catkin_create_pkg h_h std_msgs rospy roscpp
这条命令的意思是,切换到catkin_ws目录下,创建ROS包(roscreate-pkg),包的名称是h_h,带有std_msgs rospy roscpp三个依赖项,会在src下生成h_h的文件夹,h_h文件夹下有include、src两个文件夹和CMakeLists.txt、package.xml两个文件
在表格中提到了数据格式,msg和srv。在实现话题通信和服务通信之前,先来看一看msg数据格式。
使用msg:
步骤:建立.msg文件——修改package.xml——修改CMakeLists.txt——编译
建立.msg文件:
-
cd ~/catkin_ws/src/h_h //创建的包的目录下
-
mkdir msg //新建msg文件夹
-
touch abc.msg //建立abc.msg的文件
双击打开abc.msg(记住这个名字) 定义数据类型,举例:
字符串的s,浮点型的x,浮点型的y。
修改package.xml:
我们打开h_h文件夹下的package.xml文件,将下面两句话取消注释:
-
<build_depend>message_generation</build_depend>
-
<exec_depend>message_runtime</exec_depend>
第一句用于build(构建)、第二句用于exec(执行)。
有效的package.xml文件:
-
<?xml version="1.0"?>
-
<package format="2">
-
<name>h_h</name>
-
<version>0.0.0</version>
-
<description>The h_h package</description>
-
<maintainer email="jtl@todo.todo">jtl</maintainer>
-
<license>TODO</license>
-
<build_depend>message_generation</build_depend>
-
<exec_depend>message_runtime</exec_depend>
-
<buildtool_depend>catkin</buildtool_depend>
-
<build_depend>roscpp</build_depend>
-
<build_depend>rospy</build_depend>
-
<build_depend>std_msgs</build_depend>
-
<build_export_depend>roscpp</build_export_depend>
-
<build_export_depend>rospy</build_export_depend>
-
<build_export_depend>std_msgs</build_export_depend>
-
<exec_depend>roscpp</exec_depend>
-
<exec_depend>rospy</exec_depend>
-
<exec_depend>std_msgs</exec_depend>
-
<export>
-
</export>
-
</package>
修改CMakeLists.txt:
同样在h_h文件夹下,打开CMakeLists.txt,取消一些注释并添加:
-
find_package(catkin REQUIRED COMPONENTS
-
roscpp
-
rospy
-
std_msgs
-
message_generation ##新添加
-
)
-
add_message_files( ##取消注释
-
FILES ##取消注释
-
abc.msg ##新添加,就是我们新建的.msg文件
-
)
-
generate_messages( ##取消注释
-
DEPENDENCIES ##取消注释
-
std_msgs ##取消注释
-
)
-
catkin_package(
-
CATKIN_DEPENDS message_runtime roscpp rospy std_msgs #取消注释,添加message_runtime
-
)
编译:修改完package.xml和CMakeLists.txt,我们回到catkin_ws 目录下进行编译(catkin_make)
-
cd ~/catkin_ws
-
catkin_make
话题发布和订阅实现:
我们在h_h文件夹下,新建test文件夹
-
roscd h_h/
-
mkdir test
-
cd test
在test文件夹下新建talker.py:
-
#!/usr/bin/env python
-
#coding=utf-8
-
import rospy #添加rospy的库
-
from std_msgs.msg import String #系统的std_msgs文件夹下有已经定义的String类,没用到
-
from h_h.msg import * #h_h文件夹下msg文件夹下,abc.msg,这句话就是把abc加载进来
-
def talker():
-
#节点发布‘detection’话题,使用了abc类型,queue_size 是缓存的长度
-
pub = rospy.Publisher('detection',abc,queue_size=10)
-
#初始化节点 'talker' ,anonymous=True用于保证节点名称唯一
-
rospy.init_node('talker', anonymous=True)
-
rate = rospy.Rate(1) # 1hz
-
while not rospy.is_shutdown(): #循环发送
-
#rospy.get_time() 获得时间
-
hello_str = "hello world %s" % rospy.get_time()
-
#打印日志信息表
-
rospy.loginfo(hello_str)
-
x=1.0
-
y=2.0
-
#话题发布了三个参数
-
pub.publish(hello_str,x,y)
-
#速率控制
-
rate.sleep()
-
if __name__ == '__main__':
-
try:
-
talker()
-
except rospy.ROSInterruptException:
-
pass
代码中有注释,这里详细说一下参数的传递,我们之前在h_h/msg/文件夹下建立了abc.msg,在头文件中加载abc,然后在pub=rospy.Publisher('detection',abc,queue_size=10)中第一次提到了abc,我们发布话题需要有abc类型的参数传递。
最后在pub.publish(hello_str,x,y)中向外发布了三个参数hello_str,x,y,记得我们在abc.msg中填入了什么?string s,float32 x,float32 y。所以publish发布的三个参数正好对应abc.msg中的参数。
在test文件夹下新建listener.py:
-
#!/usr/bin/env python
-
#coding=utf-8
-
import rospy #添加rospy的库
-
from std_msgs.msg import String #系统的std_msgs文件夹下有已经定义的String类,没用到
-
from h_h.msg import * #h_h文件夹下msg文件夹下,abc.msg,这句话就是把abc加载进来
-
def callback(data):
-
#参数传递,data中就是abc.msg中的参数
-
rospy.loginfo('I heard %s', data.s)
-
print(data.x,data.y)
-
def listener():
-
#初始化节点'listener',保证节点唯一,因此可以有多个listener.py可以同时运行
-
rospy.init_node('listener', anonymous=True)
-
#节点订阅detection’话题,使用了abc类型,调用callback函数
-
rospy.Subscriber('detection', abc, callback)
-
#rospy.spin()简单保持你的节点一直运行,直到程序关闭
-
rospy.spin()
-
if __name__ == '__main__':
-
listener()
我们来看一下运行结果:
分别在三个终端中输入
roscore
rosrun h_h talker.py
rosrun h_h listener.py
结果如图:
我们可以看到在talker中 ,打印了日志信息,在listener中打印了日志信息,也输出了data.x和data.y。
使用srv:
这一步与msg的方法极其相似:
步骤:建立.srv文件——修改package.xml——修改CMakeLists.txt——编译
建立.srv文件:
-
cd ~/catkin_ws/src/h_h //创建的包的目录下
-
mkdir srv //新建srv文件夹
-
touch qq.srv //建立qq.srv的文件
双击打开qq.srv(同样记住这个名字) 定义数据类型,举例:
注意有float32 a 和float32 b,中间‘---’,下面是float32 Sum和string qq。简单说明一下:
srv类型的数据传递是双向的,上面部分a和b是客户端传递给服务器的数据,‘---’ 的下面部分是服务器回传给客户端的数据。
修改xml文件同msg类型的一模一样;
修改CMakeLists.txt同msg类型的类似,唯一的不同点是不需要add_message_files而需要add_service_files:
-
add_service_files( #取消注释
-
FILES #取消注释
-
qq.srv #添加,我们新建的.srv文件
-
)
当然如果msg类型和srv类型都有,则这两个都需要,CMakeLists.txt 如下。
-
cmake_minimum_required(VERSION 2.8.3)
-
project(h_h)
-
find_package(catkin REQUIRED COMPONENTS
-
roscpp
-
rospy
-
std_msgs
-
message_generation
-
)
-
## Generate messages in the 'msg' folder
-
add_message_files(
-
FILES
-
abc.msg
-
)
-
## Generate services in the 'srv' folder
-
add_service_files(
-
FILES
-
qq.srv
-
)
-
## Generate added messages and services with any dependencies listed here
-
generate_messages(
-
DEPENDENCIES
-
std_msgs
-
)
-
catkin_package(
-
CATKIN_DEPENDS message_runtime roscpp rospy std_msgs
-
)
-
include_directories(
-
${catkin_INCLUDE_DIRS}
-
)
编译:修改完package.xml和CMakeLists.txt,我们回到catkin_ws 目录下进行编译(catkin_make)
-
cd ~/catkin_ws
-
catkin_make
服务请求和响应的实现:
同样在test文件夹下,新建h_h_server.py:
-
#!/usr/bin/env python
-
#coding=utf-8
-
#加载h_h.srv文件夹下的内容,即qq.srv
-
from h_h.srv import *
-
import rospy
-
NAME = 'add_two_ints_server'
-
def add_two_ints(req):
-
#从客户端读入req,req.a和req.b对应了qq.srv中的a和b
-
print("Returning [%s + %s = %s]" % (req.a, req.b, (req.a + req.b)))
-
sum = req.a + req.b
-
q='1234567'
-
#从服务器返回两个参数给客户端,sum和q是qq.srv中的Sum和qq
-
return qqResponse(sum,q)
-
def add_two_ints_server():
-
rospy.init_node(NAME) #初始化一个节点
-
#构建一个服务,add_two_ints 是服务的名称,qq是参数(qq.srv),add_two_ints是调用函数
-
s = rospy.Service('add_two_ints', qq, add_two_ints)
-
print "Ready to add Two Ints"
-
# 保持运行
-
rospy.spin()
-
if __name__ == "__main__":
-
add_two_ints_server()
新建h_h_clinet.py:
-
#!/usr/bin/env python
-
#coding=utf-8
-
import sys
-
import os
-
from h_h.srv import *
-
import rospy
-
## add two numbers using the add_two_ints service
-
## @param x int: first number to add
-
## @param y int: second number to add
-
def add_two_ints_client(x, y):
-
#等待服务
-
rospy.wait_for_service('add_two_ints')
-
try:
-
#创建服务的处理句柄,即客户端调用'add_two_ints'服务,qq是参数数据格式
-
add_two_ints = rospy.ServiceProxy('add_two_ints', qq)
-
print("Requesting %s+%s" % (x, y))
-
# simplified style,简单方式,将两个参数传给服务器,返回resp1是服务器的返回值
-
resp1 = add_two_ints(x, y)
-
# formal style 正式的方式,resp2是服务器的返回值
-
resp2 = add_two_ints.call(qqRequest(x, y))
-
#校验
-
if not resp1.Sum == (x + y):
-
raise Exception("test failure, returned sum was %s" % resp1.Sum)
-
if not resp2.Sum == (x + y):
-
raise Exception("test failure, returned sum was %s" % resp2.Sum)
-
#返回值,resp1.Sum 对应qq.srv中的Sum,并没有返回resp1.qq
-
return resp1.Sum
-
except rospy.ServiceException, e:
-
print("Service call failed: %s" % e)
-
def usage():
-
return "%s [x y]" % sys.argv[0]
-
if __name__ == "__main__":
-
#rosrun时候的输入附带参数
-
argv = rospy.myargv()
-
if len(argv) == 1:
-
import random
-
x = random.randint(-50000, 50000)
-
y = random.randint(-50000, 50000)
-
elif len(argv) == 3:
-
try:
-
x = int(argv[1])
-
y = int(argv[2])
-
except:
-
print usage()
-
sys.exit(1)
-
else:
-
print usage()
-
sys.exit(1)
-
print ("%s + %s = %s" % (x, y, add_two_ints_client(x, y)))
我们来看一下运行结果:
分别在三个终端中输入
roscore
rosrun h_h h_h_server.py
rosrun h_h h_h_clinet.py
结果如图:
在clinet中请求了两次服务器的响应(简单方法和正式方法),服务器的程序每运行一个clinet要运算两次。