话题消息通信是指发送信息的发布者和接收信息的订阅者以话题消息的形式发送和接收信息。希望接收话题的订阅者节点接收的是与在主节点中注册的话题名称对应的发布者节点的信息。基于这个信息,订阅者节点直接连接到发布者节点来发送和接收消息。
话题是单向的,适用于需要连续发送消息的传感器数据,因为它们通过一次的连接连续发送和接收消息。另外,单个发布者可以与多个订阅者进行通信,相反,一个订阅者可以在单个话题上与多个发布者进行通信。
命令 | 详细说明 |
---|---|
rostopic list | 显示活动的话题目录 |
rostopic echo [话题名称] | 实时显示指定话题的消息内容 |
rostopic find [类型名称] | 显示使用指定类型的消息的话题 |
rostopic type [话题名称] | 显示指定话题的消息类型 |
rostopic bw [话题名称] | 显示指定话题的消息带宽(bandwidth) |
rostopic hz [话题名称] | 显示指定话题的消息数据发布周期 |
rostopic info [话题名称] | 显示指定话题的信息 |
rostopic pub [话题名称] [消息类型] [参数] | 用指定的话题名称发布消息 |
PS:参考原文
ROS Master(管理者)
Talker (发布者)
Listener (订阅者)
PS: XMLRPC
(XML-Remote Procedure Call)是一种RPC协议,其编码形式采用XML编码格式,而传输方式采用既不保持连接状态、也不检查连接状态的请求和响应方式的HTTP协议。XMLRPC是一个非常简单的约定,仅用于定义小数据类型或命令,所以它比较简单。有了这个特点,XMLRPC非常轻便,支持多种编程语言,因此非常适合支持各种硬件和语言的ROS。
运行主节点
运行发布者节点
运行订阅者节点
主节点向订阅者节点发送发布者节点信息
订阅者节点向发布者节点发送连接请求
发布者节点进行连接响应
发布者节点和订阅者节点建立连接
发送消息
需求描述:编写发布者和订阅者文件,发布者以10HZ(每秒10次)的频率发布文本消息,订阅者订阅到消息后打印出来。
首先,这里创建了一个topic_test的包,然后分别创建talker.cpp(发布者)和listener(订阅者)两个文件。
#include "ros/ros.h" #include "std_msgs/UInt32.h" int main(int argc, char **argv) { // 设置编码 setlocale(LC_ALL, ""); // 1.初始化ROS节点 // 参数3为节点名称,全局唯一 ros::init(argc, argv, "talker"); // 2.实例化ROS句柄 ros::NodeHandle nh; // 3.实例化发布者对象 // 参数1为数据要发布到的话题名称,参数2为缓冲区大小 ros::Publisher pub = nh.advertise<std_msgs::UInt32>("chatter", 1000); // 4.定义要发布的数据 int count = 0; std_msgs::UInt32 msg; // 5.定义数据发布的频率为每秒10次 ros::Rate loop_rate(10); // ros::ok()表示只要节点还在运行即满足循环条件 while (ros::ok()) { msg.data = count; // 6.发布消息 pub.publish(msg); // 打印发送的信息 ROS_INFO("发送的消息是:%d", msg.data); // 处理ROS消息回调,只循环一次 // 这里并没有接收到任何回调,所以这一句可以不写 ros::spinOnce(); count++; loop_rate.sleep(); } return 0; }
#include "ros/ros.h" #include "std_msgs/UInt32.h" void chatterCallback(const std_msgs::UInt32::ConstPtr& msg) { // 4.处理订阅的消息 ROS_INFO("订阅到了消息: [%d]", msg->data); } int main(int argc, char **argv) { // 设置编码 setlocale(LC_ALL, ""); // 1.初始化ROS节点 // 参数3为节点名称,全局唯一 ros::init(argc, argv, "listener"); // 2.实例化ROS句柄 ros::NodeHandle nh; // 3.实例化订阅者者对象 // 参数1为要订阅的话题名称,参数2为缓冲区大小,参数3为处理订阅消息的回调函数 ros::Subscriber sub = nh.subscribe<std_msgs::UInt32>("chatter", 1000, chatterCallback); // 处理ROS消息回调,一直循环,直到ROS节点退出(执行了Ctrl+C或者ros::shutdown()被调用) ros::spin(); return 0; }
注意:这里的CMakeLists.txt指的是topic_test包下面的,而非整个工程下面的那个,在CMakeLists.txt文件中添加下面的内容。
# 节点构建选项,配置可执行文件 add_executable(talker src/talker.cpp) add_executable(listener src/listener.cpp) # 节点构建选项,配置目标链接库 target_link_libraries(talker ${catkin_LIBRARIES} ) target_link_libraries(listener ${catkin_LIBRARIES} )
使用Ctrl+Shift+B进行编译,然后使用roscore命令启动主节点,然后source下环境变量,分别运行发布者和订阅者节点即可看到通信数据的打印输出。
上面先运行了发布者节点,从0开始打印信息,然后运行订阅者节点,信息直接从196开始打印了,订阅者运行之前发布者发布的信息就接收不到了。
尝试先运行订阅者节点,然后运行发布者节点,仍然存在信息丢失的情况。原因是在发布者开始发布消息时,其还未在主节点注册完成。
可以在talker.cpp中添加延时来延迟第一条数据的发送时间。
ros::Duration(1.0).sleep();
在延时等待注册完成后再发送消息,就可以收到完整的消息了。
ROS节点通过消息相互通信,ROS提供了一些标准的数据类型,也提供了一种基于标准消息类型开发自定义消息类型的机制。上面的示例中我们使用的是ROS标准的消息,实际使用中常常需要自定义消息来满足需求。
ROS的消息类型可以在这里查看。
需求描述:创建一个关于学生信息的msg文件,其中包含姓名、年龄和成绩,发布者发送学生信息,订阅者打印学生信息。
首先,在topic_test包中创建msg文件夹,然后,在文件夹中创建消息文件(Student.msg)。
string name uint8 age float32 score
<!-- 编译时依赖 --> <build_depend>message_generation</build_depend> <!-- 运行时依赖 --> <exec_depend>message_runtime</exec_depend>
# catkin构建时依赖的组件包,前3个创建ROS包时已经自动生成了,这里添加了message_generation find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation ) # 配置msg源文件,FILES将引用当前功能包目录的msg目录中的*.msg文件,自动生成一个头文件(*.h) add_message_files( FILES Student.msg ) # 生成消息时依赖于std_msgs generate_messages( DEPENDENCIES std_msgs ) # 运行时依赖,描述了库、catkin构建依赖项和系统依赖的功能包 catkin_package( # INCLUDE_DIRS include # LIBRARIES topic_test CATKIN_DEPENDS roscpp rospy std_msgs message_runtime # DEPENDS system_lib )
使用Ctrl+Shift+B进行编译,然后可以在/devel/topic_test目录下看到自动生成的头文件Student.h,此时便可以代后面的代码中进行引用了。
PS: 在修改代码之前,先在c_cpp_properties.json文件中添加下头文件路径,否则在代码中引用头文件时会出现找不到的情况。
下面我们在代码中使用自定义的消息来进行通信。
#include "ros/ros.h" #include "topic_test/Student.h" int main(int argc, char **argv) { // 设置编码 setlocale(LC_ALL, ""); // 1.初始化ROS节点 // 参数3为节点名称,全局唯一 ros::init(argc, argv, "talker"); // 2.实例化ROS句柄 ros::NodeHandle nh; // 3.实例化发布者对象 // 参数1为数据要发布到的话题名称,参数2为缓冲区大小 ros::Publisher pub = nh.advertise<topic_test::Student>("chatter", 1000); // 4.定义要发布的数据 topic_test::Student msg; msg.name = "张三"; msg.age = 18; msg.score = 85.6; // 5.定义数据发布的频率为每秒1次 ros::Rate loop_rate(1); // ros::ok()表示只要节点还在运行即满足循环条件 while (ros::ok()) { // 6.发布消息 pub.publish(msg); // 打印发送的信息 ROS_INFO("发送的消息是:姓名-%s, 年龄-%d, 成绩-%.2f", msg.name.c_str(), msg.age, msg.score); // 处理ROS消息回调,只循环一次 // 这里并没有接收到任何回调,所以这一句可以不写 ros::spinOnce(); loop_rate.sleep(); } return 0; }
#include "ros/ros.h" #include "topic_test/Student.h" void chatterCallback(const topic_test::Student::ConstPtr& msg) { // 4.处理订阅的消息 ROS_INFO("订阅到了消息: 姓名【%s】, 年龄【%d】, 成绩【%.2f】", msg->name.c_str(), msg->age, msg->score); } int main(int argc, char **argv) { // 设置编码 setlocale(LC_ALL, ""); // 1.初始化ROS节点 // 参数3为节点名称,全局唯一 ros::init(argc, argv, "listener"); // 2.实例化ROS句柄 ros::NodeHandle nh; // 3.实例化订阅者者对象 // 参数1为要订阅的话题名称,参数2为缓冲区大小,参数3为处理订阅消息的回调函数 ros::Subscriber sub = nh.subscribe<topic_test::Student>("chatter", 1000, chatterCallback); // 处理ROS消息回调,一直循环,直到ROS节点退出(执行了Ctrl+C或者ros::shutdown()被调用) ros::spin(); return 0; }
使用Ctrl+Shift+B进行编译,然后使用roscore命令启动主节点,然后source下环境变量,分别运行发布者和订阅者节点即可看到通信数据的打印输出。
☝ ★★★ — 返回 《ROS机器人开发笔记汇总》总目录 — ★★★ ☝