当前位置: 首页 > news >正文

【ROS1】06-ROS通信机制——话题通信

目录

一、话题通信的四大要素

二、工作流程

三、案例

3.1 C++实现

3.1.1 发布方

3.1.2 订阅方

3.1.3 计算图展示

3.2 Python实现

3.2.1 发布方

3.2.2 订阅方

3.2.3 计算图展示

3.3 C++节点与Python节点通信


        ROS的基本通信机制主要有话题通信(发布订阅模式)、服务通信(请求响应模式)、参数服务器(参数共享模式),本篇文章主要介绍ROS的话题通信。

一、话题通信的四大要素

  1. 节点 (Node):执行具体任务的程序。它既可以是发布者,也可以是订阅者,或者两者都是。

  2. 话题 (Topic):数据传输的通道,有一个唯一的名字(例如 /cmd_vel)。它本身不存储数据,只是一个用于路由消息的“总线”或“标签”。

  3. 消息 (Message):在话题上传输的数据。它具有严格的、预定义的数据结构。例如,一个名为 geometry_msgs/Twist 的消息类型,专门用来描述速度,其内部包含 linear(线速度)和 angular(角速度)两个字段。

  4. ROS Master (节点管理器):整个系统的“电话总机”。它不参与实际的数据传输,但负责帮助发布者和订阅者相互发现

二、工作流程

  1. 发布者上线

    • 一个节点(我们称之为Node_A)启动,并决定要发布一个名为 /scan 的话题。

    • Node_A 向 ROS Master 发送一个注册请求:“你好Master,我是Node_A,我的网络地址是IP_A:Port_A,我要发布一个叫 /scan 的话题,消息类型是 sensor_msgs/LaserScan。”

    • ROS Master 记录下这些信息。

  2. 订阅者上线

    • 另一个节点(Node_B)启动,它需要激光雷达数据,所以它决定订阅 /scan 话题。

    • Node_B 向 ROS Master 发送一个请求:“你好Master,我是Node_B,我的网络地址是IP_B:Port_B,我想订阅 /scan 话题。”

    • ROS Master 检查它的记录,发现Node_A正在发布这个话题。

    • ROS Master 将Node_A的网络地址(IP_A:Port_A)回复给Node_B。

  3. 建立直接连接

    • Node_B(订阅者)收到Node_A(发布者)的地址后,会主动向Node_A发起一个直接的点对点(Peer-to-Peer)TCP连接请求

    • Node_A接受连接请求。

  4. 数据传输

    • 连接建立后,Node_A就可以通过这个TCP连接,将 sensor_msgs/LaserScan 类型的消息源源不断地直接发送给 Node_B。

    • 关键点:这个数据传输过程完全不经过ROS Master。Master只在最初的“牵线搭桥”阶段起作用。这保证了通信的高效率。

如果之后又有Node_C也想订阅/scan话题,上述第2、3、4步会重复一遍,Node_A会与Node_C建立一个新的、独立的TCP连接。

三、案例

实现发布方持续发送文本,订阅方接收文本并输出

3.1 C++实现

3.1.1 发布方

进入到工作空间的src目录下,输入如下指令来创建一个名为“plumbing_pub_sub”的功能包

catkin_create_pkg plumbing_pub_sub roscpp rospy std_msgs

 在该功能包中的“src”目录中创建.cpp,这里命名为“demo01_pub.cpp”,表示信息的发布方

添加如下代码:

#include "ros/ros.h"
#include "std_msgs/String.h"/*
发布方实现:1. 包含头文件2. 初始化ROS节点3. 创建节点句柄4. 创建发布者对象5. 编写发布逻辑并发布数据
*/int main(int argc, char *argv[])
{// 2.初 始化ROS节点ros::init(argc,argv,"pub");  //pub为节点名称//3. 创建节点句柄ros::NodeHandle nh;//4. 创建发布者对象ros::Publisher pub = nh.advertise<std_msgs::String>("Topic1",10);  //ros::Publisher pub:创建一个发布者句柄; nh.advertise:使用节点句柄来广播; <std_msgs::String>:指定发布数据的类型; Topic1:话题名称; 10:发布队列的大小;//5. 编写发布逻辑并发布数据std_msgs::String msg;  // 创建被发布的消息while (ros::ok()){msg.data = "hello";pub.publish(msg);}return 0;
}

打开该功能包下的“CMakeLists.txt” ,设置如下代码,通过 add_executable 命令将一个或多个.cpp 文件编译生成一个可执行文件,再通过target_link_libraries 命令将 demo01_pub 这个可执行文件与它所依赖的库链接起来。

Ctrl+Shift+B编译代码,然后打开一个窗口输入“roscore”启动ROS核心

在新终端中输入“rosrun plumbing_pub_sub demo01_pub”来启动该节点

再新建一个窗口,输入“rostopic echo Topic1”,来打印“Topic1”话题的信息

如果我们希望设置发布频率,并统计发布消息的数量,可以增加如下代码:

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>/*
发布方实现:1. 包含头文件2. 初始化ROS节点3. 创建节点句柄4. 创建发布者对象5. 编写发布逻辑并发布数据
*/int main(int argc, char *argv[])
{setlocale(LC_ALL, ""); // 解决中文乱码问题// 2.初 始化ROS节点ros::init(argc,argv,"pub");  //pub为节点名称//3. 创建节点句柄ros::NodeHandle nh;//4. 创建发布者对象ros::Publisher pub = nh.advertise<std_msgs::String>("Topic1",10);  //ros::Publisher pub:创建一个发布者句柄; nh.advertise:使用节点句柄来广播; <std_msgs::String>:指定发布数据的类型; Topic1:话题名称; 10:发布队列的大小;//5. 编写发布逻辑并发布数据std_msgs::String msg;  // 创建被发布的消息ros::Rate rate(10);  // 设置发布速率为1s10条int count = 0;while (ros::ok()){std::stringstream ss;ss<< "hello --->"<<count;  // 拼接字符串msg.data = ss.str();pub.publish(msg);count++;ROS_INFO("发布的数据是:%s", ss.str().c_str());rate.sleep();ros::spinOnce();  // 官方建议添加}return 0;
}

代码添加后,Ctrl+Shift+B编译。

此时我们再通过“rosrun plumbing_pub_sub demo01_pub”来发布信息时,效果如下:

3.1.2 订阅方

 新建一个.cpp文件,表示消息的订阅方,这里命名为“demo02_sub.cpp”

添加如下代码:

#include "ros/ros.h"
#include "std_msgs/String.h"/*
订阅方实现:1. 包含头文件2. 初始化ROS节点3. 创建节点句柄4. 创建订阅者对象5. 处理订阅到的数据
*///5. 处理订阅到的数据
void call_doMsg(const std_msgs::String::ConstPtr &msg){ROS_INFO("订阅到的数据:%s", msg->data.c_str());
}int main(int argc, char *argv[])
{setlocale(LC_ALL, ""); // 解决中文乱码问题// 2. 初始化ROS节点ros::init(argc, argv, "sub");// 3. 创建节点句柄ros::NodeHandle nh;// 4. 创建订阅者对象ros::Subscriber sub = nh.subscribe("Topic1",10,call_doMsg);  //Topic1是话题名称; 10是消息队列的大小; call_doMsg是回调函数;ros::spin();return 0;
}

 在功能包的“CMakeLists.txt”中添加如下部分

Ctrl+Shift+B编译一下

在两个终端中分别启动功能包下的两个节点

rosrun plumbing_pub_sub demo01_pub
rosrun plumbing_pub_sub demo02_sub

运行效果如下:

3.1.3 计算图展示

在新终端窗口中输入“rqt_graph”打开计算图

可以看到发布方“pub”通过话题“Topic1”给订阅方“sub”传递信息

3.2 Python实现

3.2.1 发布方

在功能包中新创建一个文件夹“scripts”,用于存放Python源码 

在“scripts”添加Python文件,这里命名为“py_pub.py”

添加如下代码:

import rospy
from std_msgs.msg import String"""
发布方实现:1. 导包2. 初始化ROS节点3. 创建发布者对象4. 编写发布逻辑并发布数据
"""if __name__ == "__main__":# 2. 初始化ROS节点rospy.init_node("pub")  # pub为节点名称# 3. 创建发布者对象pub = rospy.Publisher("Topic2", String, queue_size=10)# 4. 编写发布逻辑并发布数据msg = String()  # 创建被发布的消息rate = rospy.Rate(1)  # 设置发布速率为1s1条count = 0while not rospy.is_shutdown():  # 节点没关闭就一直循环msg.data = f"hello {count}"pub.publish(msg)rospy.loginfo("发布的数据是:%s", msg.data)  # 日志信息count+=1rate.sleep()

给“scripts”中的所有python文件添加可执行权限(注意路径)

chmod +x *.py

修改一下功能包中的“CMakeLists.txt”文件,如下: 

编译一下

在两个终端窗口中分别输入如下指令,一个用来启动发布者,一个用于打印话题信息

rosrun plumbing_pub_sub py_pub.pyrostopic echo Topic2

此时效果如下:

3.2.2 订阅方

在“scripts”目录下新建一个python文件,这里命名为“py_sub.py” 

添加如下代码:

#! /usr/bin/env python
import rospy
from std_msgs.msg import String"""
订阅方实现:1. 导包2. 初始化ROS节点3. 创建订阅者对象4. 处理订阅到的数据5. spin()
"""# 4. 处理订阅到的数据
def call_doMsg(msg):rospy.loginfo("我订阅的数据:%s", msg.data)if __name__ == "__main__":# 2. 初始化ROS节点rospy.init_node("sub")# 3. 创建订阅者对象sub = rospy.Subscriber("Topic2", String, call_doMsg, queue_size=10)# 5. spin()rospy.spin()

打开“CMakeLists.txt”,添加如下部分 

编译一下,在终端中分别输入如下指令来启动发布和订阅

rosrun plumbing_pub_sub py_pub.pyrosrun plumbing_pub_sub py_sub.py

启动后效果如下:

可以看到订阅到的数据少了1条,这是因为订阅方还没有来得及注册,我们可以在发布数据前加一点休眠

编译后再运行可以看到,订阅的数据就没有少了

3.2.3 计算图展示

在发布和订阅节点工作期间我们通过“rqt_graph”启动计算图

可以看到界面如下

3.3 C++节点与Python节点通信

通过话题通信机制,可以让不同编程语言实现的节点进行通信。

将Python中订阅者的话题改为“Topic1” ,修改后编译一下

由于C++发布者也是将数据发布到话题“Topic1”中,因此我们可以直接启动C++的发布方与Python的订阅方,如下

rosrun plumbing_pub_sub demo01_pubrosrun plumbing_pub_sub py_sub.py

可以看到还是能够正常工作

http://www.dtcms.com/a/286925.html

相关文章:

  • OOA、OOD 与 OOP:面向对象范式的核心支柱详解
  • 接口测试的原则、用例与流程详解
  • ModelSim 配合 Makefile 搭建 Verilog 仿真工程
  • Docker-下载和安装
  • ADVB协议内容分析
  • LeetCode Hot100【6. Z 字形变换】
  • GI6E 加密GRID電碼通信SHELLCODE載入
  • CCF编程能力等级认证GESP—C++3级—20250628
  • 操作系统-处理机调度和死锁进程同步
  • 基于Qwen2.5-3B-Instruct的LoRA微调与推理实战指南
  • 多线程-3-线程同步
  • HTTPie: 开发者友好的http客户端工具
  • 数据排序
  • 特种作业操作证(制冷空调)的考试科目有哪些?
  • Xilinx Zynq:一款适用于软件定义无线电的现代片上系统
  • 使用 C# 实现移动加权平均(Weighted Moving Average)算法
  • java基础-5 : 面向对象
  • python网络爬虫(第三章/共三章:驱动浏览器窗口界面,网页元素定位,模拟用户交互(输入操作、点击操作、文件上传),浏览器窗口切换,循环爬取存储)
  • RPG60.生成可拾取物品
  • 拓扑排序/
  • 安卓Android项目 报错:系统找不到指定文件
  • Python编程:从入门到实践
  • rpa机器人流程自动化软件公司是做什么的?如何选择RPA厂商?简要介绍RPA技术、应用场景和未来趋势
  • Shell变量操作
  • Linux内核设计与实现 - 第4章 进程的调度
  • 函数返回值问题,以及返回值的使用问题(c/c++)
  • [FDBUS4.2] watcher的使用
  • STM32-CAN
  • vs openssl编译提示无法打开文件“libssl.lib”或“libcrypto.lib”
  • 理解 VMA 与 LMA