ROS学习第二章

为了解耦合,ROS中每一个功能点都是一个单独的进程,每个进程独立运行,ROS是进程的分布式框架

  1. 话题通信(发布订阅模式) 比如抖音上关注一个话题,会给话题关注者推送
  2. 服务通信(请求响应模式) 客户端先请求服务器,服务器再响应客户端,如访问网址
  3. 参数服务器(参数共享模式) 一个公共容器DATA,所有节点都可以在里面自由拿或放数据

话题通信:

引入:比如机器人在执行导航功能,使用的传感器是激光雷达,机器人会采集激光雷达感知到的信息并计算,然后生成控制信息驱动底盘

导航模块中有节点订阅并解析雷达数据;底盘中也有一个信号订阅运动信息并转为脉冲

导航相对于雷达是订阅方,相对于底盘是发布方

用于不断更新,少逻辑处理的应用场景

角色

ROS Master:管理者 Talker:发布者 Listener:订阅者

流程图上比较清楚,用自己的理解说下,Talker首先把话题和RPC远程地址给Master,Listener把自己关注的话题给Master,比对如果两者一样,Master把Talker的远程访问地址给Listener,之后Listener通过这个远程地址要到发布者的TCP地址,建立数据连接

注意:

使用的协议有RPC与TCP

步骤0与步骤1没有顺序关系

建立TCP连接后,就没Master什么事了,可以关闭

实现的话调用就行,已经被封装

首先还是新建ws,之后改下CMake编译文件的内容,创建新的功能包,在包下面新建scripts文件夹,新建.py文件

看发布的内容用 rostopic echo che

之后学习用固定的HZ数发送:现在初始化部分初始化一个频率rate=rospy.Rate(1) 1就是1HZ 之后在循环里调用rate.sleep( )实现定时发送

设置计数器,计数器初始化部分count=0 循环里面count+=1 ;msg.data = “hello”+str(count)

再来rospy.loginfo(“发布的数据是:%s”,msg.data)

#! usr/bin/envs python

import rospy

from std_msgs.msg import String

if __name__ == "__main__":

    rospy.init_node("ergou")

    pub = rospy.Publisher("che",String,queue_size=10)

    msg=String()

    rata=rospy.Rate(1)

    count=0

    while not rospy.is_shutdown():

        count+=1

        msg.data="caole"+str(count)

        pub.publish(msg)

        rospy.loginfo("输出为%s",msg.data)

        rata.sleep()

这个是发送节点

定义接收节点,要在定义sub的时候就定义好回调函数

#! /usr/bin/env python

import rospy

from std_msgs.msg import String

def doMsg(msg):

    rospy.loginfo("我订阅的数据是:%s",msg.data)

if __name__ =="__main__":

    rospy.init_node("huahua")

    sub=rospy.Subscriber("che",String,doMsg,queue_size=10)

    rospy.spin()

关键是不要忘记.spin( )没有这个回调函数就没法工作

细节上的处理,有一种情况,当订阅方早早启动,发布方后启动,此时依然会漏掉一些信息,这是因为发布方要先在Master那里注册,解决办法是在发之前加一个小休眠,rospy.sleep(3)

rqt_gragh输出计算图

二狗节点和花花节点,车是两人的话题

解耦合特点:C++发送发与python接受方可以实现数据交换

下面的例子试一下复杂信息,比如姓名;年龄;身高复合信息,每种信息的格式也是不一样

一般我们要自定义msg,类似C语言中的结构体,可以使用的数据类型有整型,浮点型,字符串,时间相关的,其他的消息文件,数组,header标头等

编辑自定义的msg

  1. 创建msg目录与文件

  1. 编辑配置文件

package.xml中

60多行的位置,message_generation与message_runtime

Cmakelist.txt里要配置的比较多

  1. 编译生成可被python和C++调用的中间文件

编译完之后在devel文件夹下include生成头文件person.h,C++调用

_person.py是python用的

首先要配置vscode,将前面生成的python文件路径配置进setting.json

pwd查看路径,添加到setting里面

repub.py

#! /usr/bin/env python

import rospy

from pub_sub.msg import person

if __name__ == "__main__":

    rospy.init_node("ergou")

    pub=rospy.Publisher("che",person,queue_size=10)

    rata=rospy.Rate(1)

    count=0

    p=person()

    p.name="奥特曼"

    p.height=173

    while not rospy.is_shutdown():

        count+=1

        p.age=count

        pub.publish(p)

        rata.sleep()

        rospy.loginfo("%s",p.name)

resub.py

#! /usr/bin/env python

import rospy

from pub_sub.msg import person

def doperson(da):

    rospy.loginfo("收到的是:%s,%d,%.2f",da.nmae,da.age,da.height)

if __name__ =="__main__":

    rospy.init_node("huahua")

    sub=rospy.Subscriber("che",person,doperson)

    rospy.spin()

服务通信:基于请求响应模式,是一种应答机制,比如识别到疑似指针表,需要拍照,一个节点需要向相机节点发送拍照请求,相机节点处理请求并返回结果

用于偶然的,对实时性有要求,有一定逻辑处理需求的场景

Mast er还是会根据话题建立服务端与客户端的连接,之后就可以TCP通信了

Master这里类比114,匹配需求与服务,左边是公司注册,要提供通信地址,右边是用户,只给出需求的话题

注意:

  1. 顺序需要注意,客户端发起请求时,服务端需要已经启动
  2. 客户端与服务端都可以存在多个

接下来开始实操,首先是创建srv文件,在功能包下新建srv文件夹,Addint.srv文件,与之前不同,他要包含两个数据,发送部分和响应部分之间用三个杠分割---

package.xml

之后Cmakelist.txt

生成了对应头文件

开发时还是要先配置下setting.json文件,将生成的中间文件添加进来

首先编写服务端,解析客户请求,产生响应

  1. 导包
  2. 初始化ROS节点
  3. 创建服务端对象
  4. 处理逻辑(回调函数)
  5. Spin( )

Service.[y

#! /usr/bin/env python

import rospy

from server_client.srv import*

def donum(re):

    num1=re.num1

    num2=re.num2

    sum=num1+num2

    respons=AddintsResponse()

    respons.sum=sum

    rospy.loginfo("结果是%d",sum)

    return respons

if __name__ == "__main__":

    rospy.init_node("heishui")

    server=rospy.Service("add",Addints,donum)

    rospy.loginfo("开机")

    rospy.spin()

注意这些新的srv,msg都要在setting.json中配置新路径

"/home/rosenoetic/demo03_ws/devel/lib/python3/dist-packages"

Client.py

#! /usr/bin/env python

import rospy

from server_client.srv import *

if __name__ == "__main__":

    rospy.init_node("ergo")

    client=rospy.ServiceProxy("add",Addints)

    respon=client.call(12,32)

    rospy.loginfo("%d",respon.sum)

   

改进,把原来写死的数字改为命令行键入

使用sys.argv本例有三个参数 [0]是文件名字

#! /usr/bin/env python

import rospy

from server_client.srv import *

import sys

if __name__ == "__main__":

    if len(sys.argv)!=3:

        rospy.logerr("输入的个数不对")

        sys.exit(1)

    rospy.init_node("ergo")

    client=rospy.ServiceProxy("add",Addints)

    num1=int(sys.argv[1])

    num2=int(sys.argv[2])

    respon=client.call(num1,num2)

    rospy.loginfo("%d",respon.sum)

注意:这种实现下要求Service先启动,Client再启动才可以使用,但ROS节点启动是不保证顺序的,我们要改进下,如果Service没启动Client先挂起,实现就是在发送前加入

client.wait_for_service()

或者用rospy的一个函数,等待指定话题的服务

rospy.wait_for_service("add")

参数服务器用于实现不同节点之间的参数共享,相当于独立于所有节点的公共容器,可以将数据存储在该容器,被不同节点调用,当然不同节点也可以往里存放数据

全局路径规划:设计一个从出发点到目标点的大概路径,比如回龙观到故宫

本地路径规划:根据当前路况生成的实时行进路径

这两种服务都会使用到参数服务器,比如我们把小车的尺寸信息存储到参数服务器,规划时调用出来

类似于全局变量吧

Talker设置方 Listener调用方 整体通信效率一般,建议存储简单静态数据(如unit32,时间iso数据,布尔数据等)

首先是第一类操作:参数的增与改

调用rospy.set_param( ) 并在命令行调用rosparam list查看有哪些项目 调用rosparam get r查看r的值

#! usr/bin/env python

import rospy

if __name__ == "__main__":

    rospy.init_node("seet")

    rospy.set_param("type","AGV_CAR")

    rospy.set_param("r",0.15)

修改值操作跟设置一样的,就是重新set刷新下值

之后编写查询代码

import rospy

if __name__ == "__main__":

    rospy.init_node("serachh")

    rr=rospy.get_param("r",5.0)

    rr1=rospy.get_param("r1",5.0)

    rospy.loginfo("%.2f",rr)

    rospy.loginfo("%.2f",rr1)

对于可以查到的返回正常值,查不到的返回一个默认值

r=rospy.get_param_cached("r",5.0)

这个是更高效的一种查询方式,如果在缓存直接有的数据会更快查到

for name in names:

        rospy.loginfo("name=%s",name)

这个是用来查询所有名字

flag=rospy.has_param("r")

这个是用来判断是否有这个键

最后看删除,只有一个函数

#! /usr/bin/env python

import rospy

if __name__ == "__main__":

    rospy.init_node("delate")

    rospy.delete_param("r")

就不会报错了

常用命令行,当自定义节点与已有的节点通信时,需要获取话题以及消息载体的格式,可以用命令行的指令完成

Rosnode -h,什么-h都是查看帮助

rosnode info打印的信息比较全面,后期常用

rostopic echo如果消息载体是自定义的,则需要切换到目标目录,刷新下环境变量再运行,扮演接收方的角色

rostopic pub 话题之后两次自动补齐就可以充当发布方

rostopic pub -r 10就是以一秒10次的频率发送

rostopic info可以查看比较详细的信息

rostopic hz可以显示发布频率

对于客户服务通信,rosservice info充当客户,要注意还是得配置使用的环境变量才可以TABLE补齐

发布订阅模型msg查询,比较鸡肋,也得配置环境变量

客户服务模型rossrv查询,与msg语法高度神似

rosparam可以实现YAML文件与参数服务器交互,以及对参数服务器的更删改查操作

还可以加载回来

之后开始乌龟项目来加深对通信的理解

案例一,替换键盘控制(通过命令行和计算图获取话题和消息),写一个小程序控制乌龟运动

先获取格式,再看格式具体

线速度和角速度,本案例线速度只设置X,角速度只设置Z即可

X:前进或后退 Y:水平方向 Z:垂直方向 角速度单位弧度每秒

偏航:围绕Z轴转 翻滚:围绕X轴转 俯仰:围绕Y轴转

import rospy

from geometry_msgs.msg import Twist

if __name__ =="__main__":

    rospy.init_node("control1")

    pub=rospy.Publisher("/turtle1/cmd_vel",Twist,queue_size=10)

    rate=rospy.Rate(10)

    twist=Twist()

    twist.linear.x=1.0

    twist.linear.y=0.0

    twist.linear.y=0.0

    twist.angular.x=0.0

    twist.angular.y=0.0

    twist.angular.z=1.0

    while not rospy.is_shutdown():

        pub.publish(twist)

        rate.sleep()

第二个例子是订阅,为了方便打开乌龟GUI和键盘,创建一个launch文件夹以及launch文件,用来启动这两个节点

<launch>

    <node pkg="turtlesim" name="turtle1" type="turtlesim_node" output="screen"/>

    <node pkg="turtlesim" name="key" type="turtle_teleop_key" output="screen"/>

</launch>

用roslaunch直接启动

可以看到包含坐标,朝向,线速度和角速度

#! /usr/bin/env python

import rospy

from turtlesim.msg import Pose

def chuli(p):

    rospy.loginfo("x=%.2f,y=%.2f",p.x,p.y)

if __name__ == "__main__":

    rospy.init_node("retrunn")

    sub=rospy.Subscriber("/turtle1/pose",Pose,chuli,queue_size=100)

    rospy.spin()

可以接受到了,原点在GUI左下角

需要的参数有四个 坐标,朝向以及名字

#! /usr/bin/env python

import rospy

from turtlesim.srv import *

if __name__ =="__main__":

    rospy.init_node("sheng")

    cliant=rospy.ServiceProxy("/spawn",Spawn)

    sp=SpawnRequest()

    sp.x=5.0

    sp.y=5.0

    sp.theta=0.0

    sp.name="turtle4"

    cliant.wait_for_service()

    try:

        cliant.call(sp)

    except Exception as e:

        rospy.loginfo("error")

注意操作有.srv import *还有try: except Exception as e:

参数服务器案例:修改乌龟背景色

rospy.setparam

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值