ROS话题和服务(topic就像UDP,service就像TCP)

看这张图怎么感觉服务有种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包

 
  1. cd catkin_ws

  2. 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文件:

 
  1. cd ~/catkin_ws/src/h_h //创建的包的目录下

  2. mkdir msg //新建msg文件夹

  3. touch abc.msg //建立abc.msg的文件

双击打开abc.msg(记住这个名字) 定义数据类型,举例:

字符串的s,浮点型的x,浮点型的y。

修改package.xml:

我们打开h_h文件夹下的package.xml文件,将下面两句话取消注释:

 
  1. <build_depend>message_generation</build_depend>

  2. <exec_depend>message_runtime</exec_depend>

第一句用于build(构建)、第二句用于exec(执行)。

有效的package.xml文件:

 
  1. <?xml version="1.0"?>

  2. <package format="2">

  3. <name>h_h</name>

  4. <version>0.0.0</version>

  5. <description>The h_h package</description>

  6.  
  7. <maintainer email="[email protected]">jtl</maintainer>

  8.  
  9. <license>TODO</license>

  10.  
  11. <build_depend>message_generation</build_depend>

  12. <exec_depend>message_runtime</exec_depend>

  13. <buildtool_depend>catkin</buildtool_depend>

  14. <build_depend>roscpp</build_depend>

  15. <build_depend>rospy</build_depend>

  16. <build_depend>std_msgs</build_depend>

  17. <build_export_depend>roscpp</build_export_depend>

  18. <build_export_depend>rospy</build_export_depend>

  19. <build_export_depend>std_msgs</build_export_depend>

  20. <exec_depend>roscpp</exec_depend>

  21. <exec_depend>rospy</exec_depend>

  22. <exec_depend>std_msgs</exec_depend>

  23.  
  24. <export>

  25.  
  26. </export>

  27. </package>

修改CMakeLists.txt:

同样在h_h文件夹下,打开CMakeLists.txt,取消一些注释并添加:

 
  1. find_package(catkin REQUIRED COMPONENTS

  2. roscpp

  3. rospy

  4. std_msgs

  5. message_generation ##新添加

  6. )

 
  1. add_message_files( ##取消注释

  2. FILES ##取消注释

  3. abc.msg ##新添加,就是我们新建的.msg文件

  4. )

 
  1. generate_messages( ##取消注释

  2. DEPENDENCIES ##取消注释

  3. std_msgs ##取消注释

  4. )

 
  1. catkin_package(

  2. CATKIN_DEPENDS message_runtime roscpp rospy std_msgs #取消注释,添加message_runtime

  3. )

编译:修改完package.xml和CMakeLists.txt,我们回到catkin_ws 目录下进行编译(catkin_make)

 
  1. cd ~/catkin_ws

  2. catkin_make

话题发布和订阅实现:

我们在h_h文件夹下,新建test文件夹

 
  1. roscd h_h/

  2. mkdir test

  3. cd test

在test文件夹下新建talker.py

 
  1. #!/usr/bin/env python

  2. #coding=utf-8

  3.  
  4. import rospy #添加rospy的库

  5. from std_msgs.msg import String #系统的std_msgs文件夹下有已经定义的String类,没用到

  6. from h_h.msg import * #h_h文件夹下msg文件夹下,abc.msg,这句话就是把abc加载进来

  7.  
  8. def talker():

  9. #节点发布‘detection’话题,使用了abc类型,queue_size 是缓存的长度

  10. pub = rospy.Publisher('detection',abc,queue_size=10)

  11. #初始化节点 'talker' ,anonymous=True用于保证节点名称唯一

  12. rospy.init_node('talker', anonymous=True)

  13. rate = rospy.Rate(1) # 1hz

  14. while not rospy.is_shutdown(): #循环发送

  15. #rospy.get_time() 获得时间

  16. hello_str = "hello world %s" % rospy.get_time()

  17. #打印日志信息表

  18. rospy.loginfo(hello_str)

  19. x=1.0

  20. y=2.0

  21. #话题发布了三个参数

  22. pub.publish(hello_str,x,y)

  23. #速率控制

  24. rate.sleep()

  25.  
  26. if __name__ == '__main__':

  27. try:

  28. talker()

  29. except rospy.ROSInterruptException:

  30. 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

 
  1. #!/usr/bin/env python

  2. #coding=utf-8

  3.  
  4. import rospy #添加rospy的库

  5. from std_msgs.msg import String #系统的std_msgs文件夹下有已经定义的String类,没用到

  6. from h_h.msg import * #h_h文件夹下msg文件夹下,abc.msg,这句话就是把abc加载进来

  7.  
  8. def callback(data):

  9. #参数传递,data中就是abc.msg中的参数

  10. rospy.loginfo('I heard %s', data.s)

  11. print(data.x,data.y)

  12.  
  13. def listener():

  14. #初始化节点'listener',保证节点唯一,因此可以有多个listener.py可以同时运行

  15. rospy.init_node('listener', anonymous=True)

  16. #节点订阅detection’话题,使用了abc类型,调用callback函数

  17. rospy.Subscriber('detection', abc, callback)

  18. #rospy.spin()简单保持你的节点一直运行,直到程序关闭

  19. rospy.spin()

  20.  
  21.  
  22. if __name__ == '__main__':

  23. 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文件:

 
  1. cd ~/catkin_ws/src/h_h //创建的包的目录下

  2. mkdir srv //新建srv文件夹

  3. 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

 
  1. add_service_files( #取消注释

  2. FILES #取消注释

  3. qq.srv #添加,我们新建的.srv文件

  4. )

当然如果msg类型和srv类型都有,则这两个都需要,CMakeLists.txt 如下。

 
  1. cmake_minimum_required(VERSION 2.8.3)

  2. project(h_h)

  3.  
  4. find_package(catkin REQUIRED COMPONENTS

  5. roscpp

  6. rospy

  7. std_msgs

  8. message_generation

  9. )

  10.  
  11. ## Generate messages in the 'msg' folder

  12. add_message_files(

  13. FILES

  14. abc.msg

  15. )

  16.  
  17. ## Generate services in the 'srv' folder

  18. add_service_files(

  19. FILES

  20. qq.srv

  21. )

  22.  
  23.  
  24.  
  25. ## Generate added messages and services with any dependencies listed here

  26. generate_messages(

  27. DEPENDENCIES

  28. std_msgs

  29. )

  30.  
  31. catkin_package(

  32. CATKIN_DEPENDS message_runtime roscpp rospy std_msgs

  33. )

  34.  
  35. include_directories(

  36. ${catkin_INCLUDE_DIRS}

  37. )

  38.  

编译:修改完package.xml和CMakeLists.txt,我们回到catkin_ws 目录下进行编译(catkin_make)

 
  1. cd ~/catkin_ws

  2. catkin_make

服务请求和响应的实现:

同样在test文件夹下,新建h_h_server.py:

 
  1. #!/usr/bin/env python

  2. #coding=utf-8

  3.  
  4. #加载h_h.srv文件夹下的内容,即qq.srv

  5. from h_h.srv import *

  6. import rospy

  7.  
  8.  
  9. NAME = 'add_two_ints_server'

  10. def add_two_ints(req):

  11. #从客户端读入req,req.a和req.b对应了qq.srv中的a和b

  12. print("Returning [%s + %s = %s]" % (req.a, req.b, (req.a + req.b)))

  13. sum = req.a + req.b

  14. q='1234567'

  15. #从服务器返回两个参数给客户端,sum和q是qq.srv中的Sum和qq

  16. return qqResponse(sum,q)

  17.  
  18. def add_two_ints_server():

  19. rospy.init_node(NAME) #初始化一个节点

  20. #构建一个服务,add_two_ints 是服务的名称,qq是参数(qq.srv),add_two_ints是调用函数

  21. s = rospy.Service('add_two_ints', qq, add_two_ints)

  22. print "Ready to add Two Ints"

  23. # 保持运行

  24. rospy.spin()

  25.  
  26. if __name__ == "__main__":

  27. add_two_ints_server()

新建h_h_clinet.py:

 
  1. #!/usr/bin/env python

  2. #coding=utf-8

  3. import sys

  4. import os

  5.  
  6. from h_h.srv import *

  7. import rospy

  8.  
  9. ## add two numbers using the add_two_ints service

  10. ## @param x int: first number to add

  11. ## @param y int: second number to add

  12.  
  13. def add_two_ints_client(x, y):

  14. #等待服务

  15. rospy.wait_for_service('add_two_ints')

  16. try:

  17. #创建服务的处理句柄,即客户端调用'add_two_ints'服务,qq是参数数据格式

  18. add_two_ints = rospy.ServiceProxy('add_two_ints', qq)

  19.  
  20. print("Requesting %s+%s" % (x, y))

  21.  
  22. # simplified style,简单方式,将两个参数传给服务器,返回resp1是服务器的返回值

  23. resp1 = add_two_ints(x, y)

  24.  
  25. # formal style 正式的方式,resp2是服务器的返回值

  26. resp2 = add_two_ints.call(qqRequest(x, y))

  27.  
  28. #校验

  29. if not resp1.Sum == (x + y):

  30. raise Exception("test failure, returned sum was %s" % resp1.Sum)

  31. if not resp2.Sum == (x + y):

  32. raise Exception("test failure, returned sum was %s" % resp2.Sum)

  33.  
  34.  
  35. #返回值,resp1.Sum 对应qq.srv中的Sum,并没有返回resp1.qq

  36. return resp1.Sum

  37. except rospy.ServiceException, e:

  38. print("Service call failed: %s" % e)

  39.  
  40.  
  41. def usage():

  42. return "%s [x y]" % sys.argv[0]

  43.  
  44.  
  45. if __name__ == "__main__":

  46. #rosrun时候的输入附带参数

  47. argv = rospy.myargv()

  48. if len(argv) == 1:

  49. import random

  50. x = random.randint(-50000, 50000)

  51. y = random.randint(-50000, 50000)

  52. elif len(argv) == 3:

  53. try:

  54. x = int(argv[1])

  55. y = int(argv[2])

  56. except:

  57. print usage()

  58. sys.exit(1)

  59. else:

  60. print usage()

  61. sys.exit(1)

  62. 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要运算两次。

猜你喜欢

转载自blog.csdn.net/sinat_16643223/article/details/114059848