ROS学习笔记(二)

前言

关于ROS的通信机制



一、ROS的通信机制

机器人是个高度复杂的系统性的实现,机器人上可能集成各种传感器(雷达、摄像头、GPS......)以及控制系统的实现,在ROS中每一个功能点都是一个单独的进程,每一个进程都是单独运行的。因为这些进程可能分布在不同的主机,不同主机协同工作,从而分散计算压力。随之而来就遇到了一个问题:不同的进程是如何通信的?即不同进程如何实现数据的交换。这就涉及到ROS的通信机制了。

ROS中的基本通信机制主要有以下三种实现策略

  • 话题通信(发布订阅模式)
  • 服务通信(请求响应模式)
  • 参数服务器(参数共享模式)


二、话题通信

话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,也即:一个节点发布消息,另一个节点订阅该消息。话题通信的应用场景也极其广泛,比如下面一个常见场景:

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

在上述场景中,就不止一次使用到了话题通信。

  • 以激光雷达信息的采集处理为例,在 ROS 中有一个节点需要时时的发布当前雷达采集到的数据,导航模块中也有节点会订阅并解析雷达数据。
  • 再以运动消息的发布为例,导航模块会根据传感器采集的数据时时的计算出运动控制信息并发布给底盘,底盘也可以有一个节点订阅运动信息并最终转换成控制电机的脉冲信号。

以此类推,像雷达、摄像头、GPS.... 等等一些传感器数据的采集,也都是使用了话题通信,换言之,话题通信适用于不断更新的数据传输相关的应用场景。



1.概念

以发布订阅的方式实现不同节点之间数据交互的通信模式



2.模型

话题通信实现模型是比较复杂的,该模型如下图所示,该模型中涉及到三个角色:

  • ROS Master (管理者)
  • Talker (发布者)
  • Listener (订阅者)

ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅。

​单纯就图可能不是很好理解,我们以现实为例

  • ROS Master (管理者)——媒婆
  • Talker (发布者)——男方
  • Listener (订阅者)——女方

0.男方提供自身的信息给媒婆,这个信息包含2个部分185(话题)和手机号(RPC地址)

1.女方提交自身的信息给媒婆,这个信息就是女方的要求也就是185(话题)

媒婆进行要求匹配(话题匹配),发现男方符合女方要求

2.把男方的手机号(RPC地址)给女方

3.女方通过手机号打电话给男方,希望可以加微信(TCP地址)

4.男方把自己的微信(TCP地址)给女方

5.女方加男方微信(TCP地址)

6.俩人开始通过微信交流

注意

  1. 使用的协议有RPC(电话)和TCP(微信)
  2. 步骤0和步骤1没有顺序关系
  3. Talker (发布者)——男方 和 Listener (订阅者)——女方 可以存在多个
  4. Talker (发布者)——男方 和 Listener (订阅者)——女方 建立连接后,ROS Master (管理者)——媒婆就可以关闭了
  5. 上述流程已经封装好了,调用即可

3.实现

需求:编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出

分析: 在模型实现中,ROS master是不需要我们考虑的,同时发布方与接收方之间的连接已经被封装了,需要关注的关键点有三个:

  1. 发布方
  2. 接收方
  3. 数据(此处为普通文本)

流程:编写发布者实现

           编写订阅方实现

           编写配置文件

           编译并执行

3.1发布方实现

1. 创建C++文件

2. 编程流程如下

  • 包含头文件
//ros里的文本类型——————>std_msgs/String.h
//1.包含头文件;
#include"ros/ros.h" //包含了标准ROS类的声明
#include"std_msgs/String.h" //文本被封装成单独的数据类型了std_msgs功能包里的string.h
#include<sstream> //支持字符串的流输入输出

包可以根据程序的使用一个个添加,下面这个是必备的

#include"ros/ros.h" //包含了标准ROS类的声明

  • 初始化 ROS 节点
    setlocale(LC_ALL,"");//防止乱码
//2.初始化 ROS 节点  
    ros::init(argc,argv,"talker");//看定义

因为后面有一段字符输出“发布的数据是” ,在ROS中可能会存在乱码情况,这个函数可以解决。

ros::和 std::差不多,是一个命名空间的标识符号,如果不想每次都写ros::可以在函数定义前加上如下代码:

using namespace ros;

下面类似的同理 


  • 创建节点句柄
// 3.创建节点句柄
    ros::NodeHandle nh;//相当于一个重命名 NodeHandle = nh

改个名字而已,毕竟NodeHandle太长。 


  • 创建发布者对象
//4.创建发布者对象
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);

同样改个名字,但后面的参数没有理解。 


  • 编写发布逻辑并发布数据
/5.编写发布逻辑并发布数据
  //要求以10hz的频率发布,文本后添加编号
  //先创建被发布的消息
    std_msgs::String msg;//创建一个名为msg,在std_msgs/String里
  //发布频率
    ros::Rate rate(10);
  //设置编号
    int count = 0;//定义一个int数据类型的count
  //编写循环,循环中发布数据
  while (ros::ok())
  {
      count++;

      //msg.data = "hello";消息内容为hello
      //实现字符串拼接数字,https://blog.csdn.net/shs1992shs/article/details/83051298
      std::stringstream ss;//因为是字符串与数字的组合,所以需要字符串流stringstream
      ss << "hello ---> " <<count;//C++中"<<“叫做插入运算符,第一个相当于把右边赋值给左边,第二紧接其后也一样。
      msg.data = ss.str(); //把流里面的变成字符串给msg.data,我们之前定义的消息是字符串string,所以要用到stringstream里的str
      pub.publish(msg);//用这个发布者对象发布消息,看定义,也和我们之前的创建发布者那边有关,他只能发布string的
      //添加日志
      ROS_INFO("发布的数据是:%s",ss.str().c_str());//%s表示字符串
      rate.sleep();
  }
    
    return 0;

逐行解析

//先创建被发布的消息
    std_msgs::String msg;//创建一个名为msg,在std_msgs/String里

创建被发布的消息,按照要求是个字符串的类型,此消息被我们命名为msg

//发布频率
    ros::Rate rate(10);

按照要求设置频率,详情可见此函数的定义。

//设置编号
    int count = 0;//定义一个int数据类型的count

 按照要求我们需要在发布的消息后面假如数字,所以我们这里定义了个int型的变量count

因为是不间断的发送,所以我们用个循环语句while

while (ros::ok())

节点不死,循环不休。

循环体里的内容如下

count++;

变量不断加1.

//实现字符串拼接数字,https://blog.csdn.net/shs1992shs/article/details/83051298
      std::stringstream ss;//因为是字符串与数字的组合,所以需要字符串流stringstream

定义一个字符串流ss,用来存储字符串和数字的组合 。

 ss << "hello ---> " <<count;//C++中"<<“叫做插入运算符,第一个相当于把右边赋值给左边,第二紧接其后也一样。

<<:在C++中称为插入运算符,通俗点的理解就是把右边的给左边。这里面给了2个:hello———> 和 count(不停改变)。

msg.data = ss.str(); //把流里面的变成字符串给msg.data,我们之前定义的消息是字符串string,所以要用到stringstream里的str

因为我们设置发布消息是字符串,所以我们需要利用str()将输出的数据变成字符串送给发布的消息。

pub.publish(msg);//用这个发布者对象发布消息,看定义,也和我们之前的创建发布者那边有关,他只能发布string的

消息发布,这个函数的参数也要求是字符串的形式。

ROS_INFO("发布的数据是:%s",ss.str().c_str());//%s表示字符串

日志输出

rate.sleep();

 关闭发布的频率。

总的程序如下:


/*
  发布方实现
     1.包含头文件;
     2.初始化 ROS 节点
     3.创建节点句柄
     4.创建发布者对象
     5.编写发布逻辑并发布数据
*/

//ros里的文本类型——————>std_msgs/String.h
//1.包含头文件;
#include"ros/ros.h" //包含了标准ROS类的声明
#include"std_msgs/String.h" //文本被封装成单独的数据类型了std_msgs功能包里的string.h
#include<sstream> //支持字符串的流输入输出

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");//防止乱码
//2.初始化 ROS 节点  
    ros::init(argc,argv,"talker");//定义了解

// 3.创建节点句柄
    ros::NodeHandle nh;//相当于一个重命名 NodeHandle = nh

//4.创建发布者对象
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);

//5.编写发布逻辑并发布数据
  //要求以10hz的频率发布,文本后添加编号
  //先创建被发布的消息
    std_msgs::String msg;//创建一个名为msg,在std_msgs/String里
  //发布频率
    ros::Rate rate(10);
  //设置编号
    int count = 0;//定义一个int数据类型的count
  //编写循环,循环中发布数据
  while (ros::ok())
  {
      count++;

      //msg.data = "hello";消息内容为hello
      //实现字符串拼接数字,https://blog.csdn.net/shs1992shs/article/details/83051298
      std::stringstream ss;//因为是字符串与数字的组合,所以需要字符串流stringstream
      ss << "hello ---> " <<count;//C++中"<<“叫做插入运算符,第一个相当于把右边赋值给左边,第二紧接其后也一样。
      msg.data = ss.str(); //把流里面的变成字符串给msg.data,我们之前定义的消息是字符串string,所以要用到stringstream里的str
      pub.publish(msg);//用这个发布者对象发布消息,看定义,也和我们之前的创建发布者那边有关,他只能发布string的
      //添加日志
      ROS_INFO("发布的数据是:%s",ss.str().c_str());//%s表示字符串
      rate.sleep();
  }
    
    return 0;
}

运行的结果如下

 发布方的代码实现结束。




 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lbuera

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值