一、引言
机器人在执行导航功能,使用的传感器是激光雷达,机器人会采集激光雷达感知到的信息并计算,然后生成运动控制信息驱动机器人底盘运动。
一共有两次使用话题通信:
- 以激光雷达信息的采集处理为例,在 ROS 中有一个节点需要时时的发布当前雷达采集到的数据,导航模块中也有节点会订阅并解析雷达数据。
- 再以运动消息的发布为例,导航模块会根据传感器采集的数据时时的计算出运动控制信息并发布给底盘,底盘也可以有一个节点订阅运动信息并最终转换成控制电机的脉冲信号。
话题通信适用于不断更新的数据传输相关的应用场景
概念
以发布订阅的方式实现不同节点之间数据交互的通信模式。
作用
用于不断更新的、少逻辑处理的数据传输场景。
核心要素
发布方、订阅方、话题
二、理论模型
话题通信有三个角色:
- ROS Master (管理者)
- Talker (发布者)
- Listener (订阅者)
管理者其实有点像媒婆的角色。下面就是大型ROS相亲现场:
RPC是远程调用,相当于手机号,TCP相当于微信,联系比较方便。
基本流程(相亲流程):
- 0> Talker注册(又叫发布者,说话一方)(男方向媒婆提供自己的个人信息)
- 1> Listener注册(又叫订阅者,听话的一方)(女方向媒婆提供自己的个人信息)
- 2> ROS Master进行信息匹配(媒婆根据男方、女方的信息进行匹配,把男方的地址(foo,可以理解为电话号码)给女方)
- 3> Listener发送连接请求(女方看得到男方的地址(电话号码),打电话过去)
- 4> Talker确认连接请求(男方给了女方一个新的地址foo,可以理解为给个微信号)
- 5> 建立网络连接(女方通过TCP访问新的地址,可以理解为加上微信)
- 6> Talkerl向 Listener发布数据(发信息)
无论是Listener还是Talker,都不能直接进行通信,需由ROS Master代发。Listener向ROS Master询问(专业点称作订阅 subscribe)叫bar的话题(topic),若存在发布该话题的Talker,ROS Master响应给Listener,Listener与Talker建立TCP连接并通信。
注意事项:
- 注意1:上述实现流程中,前五步使用的 RPC协议,最后两步使用的是 TCP 协议(先通过手机号之类的联系,有点意思了再加微信)
- 注意2: Talker 与 Listener 的启动无先后顺序要求(可以女追男,也可以男追女)
- 注意3: Talker 与 Listener 都可以有多个(人人都是海王的时代)
- 注意4: Talker 与 Listener 连接建立后,不再需要 ROS Master。也即,即便关闭ROS Master,Talker 与 Listern 照常通信。(两个人的事,媒婆只启介绍作用,工具人实锤)
- 上述流程已经封装了,不需要完全自己写,开发者只需要做一些个性化工作。
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有四个:
- 发布方实现
- 接收方实现
- 数据(话题设置)
- 消息载体
三、话题通信的实现(C++)
- 编写发布方实现;
- 编写订阅方实现;
- 编辑配置文件;
- 编译并执行。
3.1 发布方的实现
编写代码(测试版):
#include<ros/ros.h>
#include<std_msgs/String.h>
/*
该文件是发布方实现:
1.包含头文件
ROS的文本类型----->std_msgs/String.h
2.初始化ros节点
3.创建节点句柄
4.创建发布者对象
5.编写发布逻辑并发布数据
*/
int main(int argc,char *argv[])
{
//2.初始化ROS节点
ros::init(argc,argv,"ErGouZi");
// 3.创建节点句柄
ros::NodeHandle nh;
//4.创建发布者对象
ros::Publisher pub= nh.advertise<std_msgs::String>("FangZi",10);
// 5.编写发布逻辑并发布数据
//先创建发布的消息
std_msgs::String msg;
//再编写一个循环
while(ros::ok()) //ros::ok()要是这个节点活着就为真
{
msg.data = "hello";
pub.publish(msg);
}
return 0;
}
配置编译文件(CMakeLists.txt文件中):
注意:这里面后面是没有分号的。
add_executable(demo01_pub src/demo01_pub.cpp)
target_link_libraries(demo01_pub
${catkin_LIBRARIES}
)
编译运行后发现没有显示什么,那就对了。只发布了,并没有接受。
完整版代码:
#include<ros/ros.h>
#include<std_msgs/String.h>
#include<sstream>
/*
该文件是发布方实现:
1.包含头文件
ROS的文本类型----->std_msgs/String.h
2.初始化ros节点
3.创建节点句柄
4.创建发布者对象
5.编写发布逻辑并发布数据
*/
int main(int argc,char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化ROS节点
ros::init(argc,argv,"ErGouZi");
// 3.创建节点句柄
ros::NodeHandle nh;
//4.创建发布者对象
ros::Publisher pub= nh.advertise<std_msgs::String>("FangZi",10);
// 5.编写发布逻辑并发布数据
//要求以10Hz的频率发布数据,并且,文本后添加编号
//先创建发布的消息
std_msgs::String msg;
//发布频率
ros::Rate rate(10);
//设置编号
int count = 0;
//再编写一个循环
while(ros::ok()) //ros::ok()要是这个节点活着就为真
{
count++;
//msg.data = "hello";
//实现字符串拼接
std::stringstream ss;
ss<<"hello --->"<<count;
msg.data = ss.str();
pub.publish(msg);
//添加日志
ROS_INFO("发布的数据是:%s",ss.str().c_str() );
rate.sleep();
}
return 0;
}
3.2 订阅方的实现
订阅者的代码:
#include<ros/ros.h>
#include<std_msgs/String.h>
/*
该文件是订阅方实现:
1.包含头文件
ROS的文本类型----->std_msgs/String.h
2.初始化ros节点
3.创建节点句柄
4.创建订阅者对象
5.处理订阅的数据
6.spin()函数
*/
void doMsg(const std_msgs::String::ConstPtr &msg)
{
//通过msg获取并操作订阅到的数据
ROS_INFO("翠花订阅到的数据为:%s",msg->data.c_str() );
}
int main(int argc,char *argv[])
{
// 2.初始化ros节点
ros::init(argc,argv,"CuiHua");
// 3.创建节点句柄
ros::NodeHandle nh;
// 4.创建订阅者对象
ros::Subscriber sub = nh.subscribe("fang",10,doMsg);
//subscribe(话题,存储的数,回调函数);
// 5.处理订阅的数据
ros::spin();
return 0;
}
CMakeLists.txt文件:
add_executable(demo02_sub src/demo02_sub.cpp)
target_link_libraries(demo02_sub
${catkin_LIBRARIES}
)
3.3 同时运行发布者和订阅者
4.查看计算图
在新的终端运行:
rqt_graph 查看计算图:
参考文章:
https://blog.csdn.net/ck784101777/article/details/106183819/