重头开始学ROS(2)---话题通讯

话题通信

理论模型

理论模型

话题通信的三个角色:

  • ROS Master管理者
    -> 负责保管 TalkerListener 注册的信息并匹配话题相同的 TalkerListener
  • Talker发布者
    -> 发布消息
  • Listener订阅者
    -> 订阅来自Talker的消息

话题通信的步骤:

1. Talker向Master注册自身信息,包括发布的话题名称
2. Listener向Master注册自身信息,包括订阅的话题名称
3. Master将Talker和Listener进行匹配
4. Master匹配完成后,将Talker的RPC地址发送给Listener
5. Listener根据RPC地址,通过RPC向Talker发送连接请求
6. Talker确认连接信息,发送自己的TCP地址信息
7. Listener根据Talker的TCP地址信息,通过TCP通讯协议与Talker建立连接
=============================================================

注意点

1. Talker与Listener的连接使用的是RPC协议,但最后两步通讯使用TCP协议
2. Talker与Listener无先后启动顺序要求
3. Talker与Listener可以有多个
4. Talker与Listener连接后,不再需要Master,因此即使关闭Master,二者仍可正常通信
=============================================================

C++的话题通讯基本操作

实际的话题通信建立过程,我们关注什么?

需要关注的:
1. Talker
2. Listener
3. 通信的数据
=============================================================
无需关注的:
1. ROS Master
2. 连接过程(RPC、TCP等)
这是因为这两部分内容已经被封装

话题通讯模型建立流程

1. 编写Talker实现
2. 编写Listener实现
3. 编辑配置文件CmakeList等
4. 编译并执行(Vscode使用crtl+shift+B编译)

Talker

代码实现

/*
    消息发布方:
        循环发布信息:HelloWorld 后缀数字编号
    实现流程:
        1.包含头文件 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄
        4.实例化 发布者 对象
        5.组织被发布的数据,并编写逻辑发布数据
*/
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char *argv[]){
    //1. 设置编码
    setlocale(LC_ALL, "");
    //2.初始化 ROS 节点:命名(唯一)
    ros::init(argc, argv, "Talker");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能
     //4.实例化 发布者 对象
    ros::Publisher pub = nh.advertise<std_msgs::String>("Chatter", 10);
    //5.组织被发布的数据,并编写逻辑发布数据
    std_msgs::String msg;
    std::string msg_front = "Hello nihao!";
    int count = 0;
    //循环(一秒10次)
    ros::Rate r(1);
    while (ros::ok())
    {
        std::stringstream ss;
        ss << msg_front << count;
        msg.data = ss.str();
        pub.publish(msg);
        ROS_INFO("masage sent:%s",msg.data.c_str());
        r.sleep();
        count ++;
        ros::spinOnce();
    }
    return 0;
}

代码中的名词:

  1. 节点:ROS系统中程序运行与通信的基本单位,一个程序只能对应一个节点,节点的命名是唯一的
  2. 句柄:可以简单理解为节点的把柄,系统可以根据句柄的使用情况,开启或关闭节点来节省内存。一个节点可以对应多个句柄,一个句柄可以对于多个TalkerListener

C++代码

  1. ros::init(argc, argv, "name")
    这行代码用于初始化一个ROS节点,
    argc argv后期传值使用
    name表示节点的命名,节点的命名是唯一的,不可重复
  2. ros::NodeHandle nh
    实例化一个句柄对象,名称为nh,该类封装了 ROS 中的一些常用功能
  3. ros::Publisher pub
    实例化一个Talker对象,名称为pub,这行代码同时实现了注册的功能
  4. nh.advertise<std_msgs::String>("Chatter", 10)
    利用句柄nh
    nh.advertise("Chatter", 10)表示当前节点要发布话题
    "Chatter"为话题的名称,该名称为Listener的订阅对象
    10表示发送信息在大于10个之后舍弃之前发布的消息
    <std_msgs::String>表示发布的话题消息类型
  5. std_msgs::String
    为ROS系统的标准消息之一String
  6. ros::ok()
    可用于检测crtl+c退出,当检测到crtl+c时,变为False循环终止
    或所有节点被关闭时,输出False
  7. pub.publish(msg)
    实例化的Talker发布消息msg
  8. ros::Rate r(1)
    用作定时器的作用,1表示1hz
    结合r.sleep()完成定时效果
  9. ros::spinOnce()
    十分重要的函数,称为消息回调处理函数
    没有这行代码,Listener就不会执行回调函数Callback,而是只仅仅进行实参传输

Listener

代码实现

#include "ros/ros.h"
#include "std_msgs/String.h"

void doMsg(const std_msgs::String::ConstPtr& msg_p){
    ROS_INFO("I Hear:%s",(*msg_p).data.c_str());
}

int main(int argc, char *argv[]){
    setlocale(LC_ALL, "");

    ros::init(argc, argv, "Listener");
    ros::NodeHandle nh;
    ros::Subscriber sub = nh.subscribe("Chatter", 10, doMsg);
    ros::spin();
    return 0;
}

代码的解读

  • 可以看到相比于TalkerListener多了一个doMsg的回调函数,少了一个publish
  • 回调函数的作用在于,Listener每次接收到消息之后,都会将接收的消息传入回调函数,而ros::spin()函数负责控制执行该回调函数,与ros::spinOnce()作用类似。
  • 缺少类似于publish的函数是因为,订阅的过程是被动的,所以被封装化了,在这个订阅的过程中,不断有参数传入回调函数,创建了多个函数内容相同但参数不同的回调函数序列。
  • ros::spin()ros::spinOnce()的区别在于:
    1. ros::spin()是一个内部循环的函数,内部循环不断执行被传入参数的回调函数队列
    2. ros::spinOnce()只执行当前回调函数队列中的第一个

CmakeList与launch文件设置

add_executable(talker_test src/talker_test.cpp)
add_executable(listener_test src/listener_test.cpp)

target_link_libraries(talker_test ${catkin_LIBRARIES})
target_link_libraries(listener_test ${catkin_LIBRARIES})
<launch>
    <node pkg="topic_test" type="talker_test" name="talker" output="screen" />
    <node pkg="topic_test" type="listener_test" name="listener" output="screen" />
</launch>

示例效果

实例效果

问题

1. 为什么订阅时第一条消息丢失了?

这是因为发布第一条消息时,Listener还没有注册完毕
publisher注册完毕之后加入ros::Duration(3.0).sleep()休眠一段时间即可

2. 关于rqt_graph

rqt

通过该图,可以很好地看出,我们定义了两个节点talkerlistenertalker发布了话题ChatterListener订阅

3. 总结 -- 在代码中我们究竟需要做什么?
  • 编写Talker节点,应包括节点初始化、句柄初始化、基于句柄实例注册发布者(包括其话题名称、队列最大保存消息数)、发布消息指令;
  • 编写Listener节点,应包括节点初始化、句柄初始化、基于句柄实例注册订阅者、编写回调函数、适当使用spin函数执行回调;
  • 填写CmakeListlaunch文件;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咖啡百怪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值