提示:本文基于Ubuntu20.04和ROS Noetic版本,版本不同可能会导致部分差异,敬请谅解。
文章目录
前言
本期学习ROS中最常用的节点通信方式——Topic话题与Message消息
节点不能脱离软件包而独立存在;
建立两个软件包,使两个软件包的两个节点之间建立通信;
ssr_pkg (sensor package)中有个Ultrasound_node节点;
atr_pkg (actual package)中有个Motor_node节点;
节点间的通讯双方的定义:
发送信息的一方称Publisher——发布者;
发布者发布的话题称Topic——主题;
发布的消息称Message——消息;
接收信息的一方称Subsciber——定阅者;
一、Topic
1.话题Topic是节点间进行持续通信(消息不停地发送与接收)的一种形式;
2.话题通讯的两个节点是通过话题的名称建立起话题通讯连接;
3.消息Message通常会按照一定的频率持续不断地发送以保证消息数据的实时性;
4.对于多节点间通讯:
假如在ssr_pkg (sensor package)中额外添加IMU_node节点;
方式一:
每个Publisher节点会发布各自的消息到各自不同的Topic里,而不是在同一个Topic里发送所有Message,Subsciber会根据自己的需要订阅不同的话题;
方式二:
不同的Publisher节点在同一个话题里发布消息,Subsciber节点订阅这个话题;
注:在这种通讯方式中,同一时刻只能有一个发送信息的Publisher节点
5.一个ROS节点网络中可以存在多个话题(对应上述4中的方式一);一个话题可以有多个发布者(Publisher)和多个订阅者;任何一个节点可以对多个话题进行订阅,也可以发布多个话题;
6.不同的传感器消息通常只会拥有各自独立的话题名称,每个话题只有一个发布者;
二、Message
消息的作用是携带数据,数据从发布者传输到订阅者;
在生成消息包的时候通常要指定消息的类型;
前面用到的catkin_create_pkg ssr_pkg rospy roscpp std_msgs
其中std_msgs的消息类型是如何查看的?
index.ros.org
搜索栏输入std_msgs,可以查询到不同版本ROS对应的std_msgs的定义;
可以查到Bool、Byte、ByteMUltiArray(字节数组)
三、使用C++实现Publisher发布者节点实例
1.创建Publisher发布者节点
(不喜欢原理解释部分可以直接跳转到最终代码)
发布者Publisher节点
ssr_pkg (sensor package)中有个Ultrasound_node节点;
atr_pkg (actual package)中有个Motor_node节点;
订阅者Subsciber节点
本节目标:在Ultrasound_node节点的框架里添加代码,将其打造成一个话题发布者;
确定发布的话题名称;
确定发布的消息的类型;
确认消息类型:
打开index.ros.org搜索std_msgs,点击查看可以找到String字符串类型;
在节点初始化完成后定义一个NodeHandle对象;
这个对象是节点和ROS通讯的关键;
ros::NodeHandle nh;//在节点初始化完成后定义一个NodeHandle对象
定义一个Publisher对象,这个Publisher对象是我们发送消息的工具;
需要命名话题的名称、缓存的长度;
缓存长度表示:
订阅者处理数据的周期比较长,如果消息没有处理完而新的消息发送过来了,数据就会按发送次序排队等待处理,当缓存队列满了,最早发送过来的未被处理的数据就会被舍弃,通俗点讲缓存长度决定了缓存中可以容纳多少个消息包排队;
缓存长度既不能太大,也不能太小,需要根据延迟来进行计算;
ros::Publisher pub =nh.advertise<std_msgs::String >(“话题名称”,缓存长度);//构建消息发送对象
在本项目中改动如下
ros::Publisher pub = nh.advertise<std_msgs::String>("sensor_topic",10);//该节点向NodeHandle发送一个请求
#由于String类型的头文件还没有包含进来,会报错;
需要加入头文件:
#include <std_msgs/String.h>
按Table键可以补全代码;
接下来生成一个消息包:
消息类型是std_msgs::String
对象名称取为: msg;
给上述msg数据包的变量赋值
msg.data = “Ultrasound_data”;//给变量赋值
将数据包发送出去
pub.publish(msg);//将msg发布出去
最终程序示例:
#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc,char *argv[])
{
ros::init(argc,argv,"Ultrasound_node");//初始化节点
printf("Hello world!\n");
ros::NodeHandle nh;//在节点初始化完成后定义一个NodeHandle对象
ros::Publisher pub = nh.advertise<std_msgs::String>("sensor_topic",10);//该节点向NodeHandle发送一个请求
while(ros::ok())
{
printf("Ultrasound_node is working!\n");
std_msgs::String msg;//生成一个消息包
msg.data = "Ultrasound_data";//给变量赋值
pub.publish(msg);//将msg发布出去
}
return 0;
}
编译CTR+SHIFT+B
CTRL+ALT+T启动终端窗口
在终端窗口运行ros,输入roscore
新建终端窗口,运行rosrun ssr_pkg Ultrasound_node,这里rossrun 包名 节点名,包和节点的名称取决于你的工程。
运行结果如下:
按CTRL+C可以终止程序运行
2.rostopic的使用
查看ros中活跃的话题有哪些:
重新运行刚刚终止的节点程序rosrun ssr_pkg Ultrasound_node
CTRL+SHIFT+O分割出新的终端(如果安装了Terminar程序);
rostopic list
运行结果描述:可以看到sensor_topic(程序中自定义的话题)
查看话题中的消息
输入rostopic echo 话题名称(输入首字母和Tab键自动补全)
rostopic echo sensor_topic
在该窗口点击一下,按CTRL+C暂停方便观察;(没显示就是因为刷新太快了,暂停即可)
可以看到topic中的信息为data: “Ultrasound_data”,其中Ultrasound_data为前文写入的信息;
如果前面写入的是中文信息打印错误,需要将data:后面的字段"中文"复制下来;
CTRL+SHIFT+O打开新终端;
输入==echo -e “中文”==即可正确解码为汉字信息;
使用rostopic查看话题里消息发送的频率;
CTRL+SHIFT+W关闭当前一个终端;
rostopic hz /话题名称 #可以得到消息发送的频率n次/秒;
修改信息发送的频率
ros::Rate Ultrasound_loop_rate(10);//生成一个频率对象,每秒执行十次;
Utrasound_loop_rate.sleep();//一段时间的阻塞,可以放到主函数的while循环中,使得循环执行频率能和上述参数对应;
#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc,char *argv[])
{
ros::init(argc,argv,"Ultrasound_node");//初始化节点
printf("Hello world!\n");
ros::NodeHandle nh;//在节点初始化完成后定义一个NodeHandle对象
ros::Publisher pub = nh.advertise<std_msgs::String>("sensor_topic",10);//该节点向NodeHandle发送一个请求
ros::Rate Ultrasound_loop_rate(10);//生成一个频率对象,每秒钟执行10次
while(ros::ok())
{
printf("Ultrasound_node is working!\n");
std_msgs::String msg;//生成一个消息包
msg.data = "Ultrasound_data";//给变量赋值
pub.publish(msg);//将msg发布出去
Ultrasound_loop_rate.sleep();//一段时间的阻塞,使得循环执行频率能和上述参数对应;
}
return 0;
}
3.复制当前节点并修改成新的节点
1.在vscode工程中复制之前创建的Ultrasound.cpp并右键当前的src目录进行粘贴,将文件名称修改为DHT11_node.cpp;
修改节点名称(必选)
ros::init(argc,argv,"DHT11_node");//初始化节点
2.修改话题名称(可选,前面讲过多个节点可以发布同一个话题)
ros::Publisher pub = nh.advertise<std_msgs::String>("sensor_topic",10);//该节点向NodeHandle发送一个请求
3.修改CMakeLists.txt文件,添加下列语句到文件末尾(和Ultrasound.cpp的创建步骤一样)
add_executable(DHT11_node src/DHT11_node.cpp)
target_link_libraries(DHT11_node
${
catkin_LIBRARIES}
)
DHT11_node.cpp文件修改结果如下:
#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc,char *argv[])
{
ros::init(argc,argv,"DHT11_node");//初始化节点
printf("This is DHT11_node\n");
ros::NodeHandle nh;//在节点初始化完成后定义一个NodeHandle对象
ros::Publisher pub = nh.advertise<std_msgs::String>("sensor_topic",10);//该节点向NodeHandle发送一个请求
ros::Rate loop_rate(10);//生成一个频率对象,每秒钟执行10次
while(ros::ok())
{
printf("DHT11_node is working!\n");
std_msgs::String msg;//生成一个消息包
msg.data = "DHT11_data";//给变量赋值
pub.publish(msg);//将msg发布出去
loop_rate.sleep();//一段时间的阻塞,使得循环执行频率能和上述参数对应;
}
return 0;
}
编译CTR+SHIFT+B
CTRL+ALT+T启动终端窗口
在终端窗口运行ros,输入roscore
CTRL+SHIFT+O 新建终端窗口
rosrun ssr_pkg DHT11_node 运行DHT11节点
CTRL+SHIFT+O 新建终端窗口
rostopic list 查看消息列表,可以看到如下信息:
/rosout
/rosout_agg
/sensor_topic
查看消息内容
rostopic echo /sensor_topic
运行结果如下:
查看消息发送频率
rostopic hz /sensor_topic
4.本章小节
使用C++实现了Publisher发布者节点实例
熟练掌握如下命令:
列出当前系统中所有活跃着的话题
rostopic list
显示指定话题中发送的消息包内容
rostopic echo /话题名称
示例:rostopic echo /sensor_topic
统计指定话题中消息发送的频率
rostopic hz /话题名称
示例:
rostopic hz /sensor_topic
四、使用C++实现Subscrible订阅者节点实例
我们已经编写了两个发布者节点(Ultrasound_node和DHT11_node),现在来添加订阅者节点;
index.ros.org
搜索std_msgs,进入noetic版本
在(Noetic版本的)std_msgs界面点击Website;
翻到ROS Message Types查找类型名称;
1.创建一个包:
打开工作空间目录
cd catkin_ws/src/
创建一个包
catkin_create_pkg<包名称><依赖项列表>
catkin_create_pkg atr_pkg rospy roscpp std_msgs
前两项三对python和c++的支持,后面两项是所需的String消息类型所在的包;
2.创建一个节点(motor_node.cpp文件)
编写必要的文件内容
#include <ros/ros.h>
#include <std_msgs/String.h>
/*
函数功能描述:回调函数,对消息包到来时的一个响应;
每当一个新的消息到达订阅者节点ROS系统就会在后台调用这个回调函数;
参数即是需要接收的消息,程序中表述为最新获取的消息包对象
*/
void motor_callback(std_msgs::String msg)
{
printf(msg.data.c_str());//从strng类型转化成字符数组
printf("\n");
}
int main(int argc,char *argv[])
{
ros::init(argc,argv,"motor_node");//初始化节点
printf("This is motor_node\n");
ros::NodeHandle nh;//在节点初始化完成后定义一个NodeHandle对象
ros::Subscriber sub = nh.subscribe("sensor_topic",10,motor_callback);//设置订阅的话题、消息缓存长度、回调函数名
//ros::Rate loop_rate(10);//生成一个频率对象,每秒钟执行10次
while(ros::ok())
{
//printf("motor_node is working!\n");
ros::spinOnce();
//loop_rate.sleep();//一段时间的阻塞,使得循环执行频率能和上述参数对应;
}
return 0;
}
3.补充说明motor_node.cpp的解释
头文件声明前面已经讲过了;
回调函数的解释
/*
函数功能描述:回调函数,对消息包到来时的一个响应;
每当一个新的消息到达订阅者节点ROS系统就会在后台调用这个回调函数;
参数即是需要接收的消息,程序中表述为最新获取的消息包对象
*/
motor callback(std_msgs::String msg)
{
printf(msg.data.c_str());//从strng类型转化成字符数组
printf(“\n”);
}
while循环体中的ros::spinOnce();函数:
当节点在忙自己的事情的时候,当调用这个sinOnce()函数的时候,就会转过身去看看有没有新的消息 包接受到;
如果不调用这个函数那么节点就会在while()循环忙自己的事情,从而不响应新到 来的数据包;
4.CMakeLists.txt中添加必要的指令(生成可执行函数、将节点链接到ros库函数)
CMakeLists.txt中添加如下指令
add_executable(motor_node src/motor_node.cpp)
target_link_libraries(motor_node
${catkin_LIBRARIES}
)
5.运行ros、ssr_pkg中的Ultrasound_node发布者节点、atr_pkg中的motor_node订阅者节点
启动ros
CTRL+SHIFT+O
rocore
启动ssr_pkg中的Ultrasound_node发布者节点
CTRL+SHIFT+O
rosrun ssr_pkg Ultrasound_node
启动atr_pkg中的motor_node订阅者节点
CTRL+SHIFT+O
rosrun atr_pkg motor_node
查看消息列表
rostopic list
以下是消息列表内容
/rosout
/rosout_agg
/sensor_topic
查看sensor_topic话题中的消息
rostopic echo /sensor_topic
实验现象(四个窗口依次是ros启动界面、Ultrasound_node、motor_node、话题查看窗口)
atr_pkg中的motor_node订阅者节点接受并发送出ssr_pkg中的Ultrasound_node发布者节点中的内容;
熟练掌握快捷键的使用
6.使用rqt_graph查看节点间通信
运行ros
CTRL+SHIFT+O
rocore
启动atr_pkg中的motor_node订阅者节点
CTRL+SHIFT+O
rosrun atr_pkg motor_node
启动ssr_pkg中的Ultrasound_node发布者节点
CTRL+SHIFT+O
rosrun ssr_pkg Ultrasound_node
启动ssr_pkg中的DHT11_node发布者节点
CTRL+SHIFT+O
rosrun ssr_pkg DHT11_node
CTRL+SHIFT+O
rqt_graph
运行结果如下
这里可以看到Ultrasound_node和DHT11_node两个发布者节点各自发布了名为sensor_topic的话题,订阅者motor_node订阅了sensor_topic的话题;
话题并不独属于发布者和订阅者,只要有节点向NodeHandle提出话题发布订阅请求、话题发布请求,话题就会存在,哪怕只有单一的通信节点存在,话题就会自动产生;
7.本章小节
话题的订阅步骤
1.确定话题的名称和消息类型;
2.在代码文件中添加ros和消息类型的头文件;
3.在main函数中通过NodeHandler订阅一个话题并设置消息回调函数;
4.定义一个回调函数,对接收的消息包进行处理;
5.在main函数中执行ros::spinOnce(),让回调函数能够响应接收到的消息包;
五、拓展篇——使用launch一次性运行多个节点
上个章节中我们要运行ros和另外三个节点需要输入四条指令才能完成操作,如果是面对一个多节点组成的复杂通信网络岂不是要输入很多指令,这时我们可以用luanch指令来完成这个操作;
在任意一个工程包中新建luanch文件夹
在launch文件夹下新建contrl.launch文件
编写contrl.launch中文件内容;
<launch>
<node pkg="ssr_pkg" type="Ultrasound_node" name="Ulrasound_node"/>
<node pkg="ssr_pkg" type="DHT11_node" name="DHT11_node"/>
<node pkg="atr_pkg" type="motor_node" name="motor_node" output="screen"/>
</launch>
在终端窗口运行
roslaunch atr_pkg contrl.launch
CTRL+C终止
可以看到结束了四个节点,查看运行窗口可以发现ros也是一个节点,但是这个节点不需要手动添加到launch文件中;
使用ros_rqt查看节点间的通信方式如下
总结
简单介绍了ROS中最常用的节点通信方式——Topic话题与Message消息;
使用C++实现Publisher实现了简单的发布者节点实例;
使用C++实现Subscrible实现了简单的订阅者节点实例;
话题并不独属于发布者和订阅者,只要有节点向NodeHandle提出话题发布订阅请求、话题发布请求,话题就会存在,哪怕只有单一的通信节点存在,话题就会自动产生;