ROS话题和服务

基本概念:

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

 

 

  • 14
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值