ROS通信机制一---话题通信
Posted loongembedded
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ROS通信机制一---话题通信相关的知识,希望对你有一定的参考价值。
文章目录
总述
ROS的核心——分布式通信机制
ROS是一个分布式框架,为用户提供多节点(进程)之间的通信服务,所有软件功能和工具都建立在这种分布式通信机制上,所以ROS的通信机制是最底层也是最核心的技术。
1. 话题通信模型
话题通信实现模型是比较复杂的,该模型如下图所示,该模型中涉及到三个角色:
●ROS Master (管理者)
●Talker (发布者)
●Listener (订阅者)
ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅。
(1) Talker注册
Talker启动,通过1234端口使用RPC向ROS Master注册发布者的信息,包含所发布消息的话题名;ROS Master会将节点的注册信息加入注册列表中。
(2) Listener注册
Listener启动,同样通过RPC向ROS Master注册订阅者的信息,包含需要订阅的话题名。
(3) ROS Master进行信息匹配
Master根据Listener的订阅信息从注册列表中进行查找,如果没有找到匹配的发布者,则等待发布者的加入:如果找到匹配的发布者信息,则通过RPC向Listener发送Talker的RPC地址信息。
(4) Listener发送连接请求
Listener接收到Master发回的Talker地址信息,尝试通过RPC向Talker发送连接请求,传输订阅的话题名、消息类型以及通信协议(TCP/UDP)
(5) Talker确认连接请求
Talker接收到Listener发送的连接请求后,继续通过RPC向Listener确认连接信息,其中包含自身的TCP地址信息。
(6) Listener尝试与Talker建立网络连接
Listener接收到确认信息后,使用TCP尝试与Talker建立网络连接。
(7) Talker向Listener发布数据
成功建立连接后,Talker开始向Listener发送话题消息数据。
ROS Master在节点建立连接的过程中起到了重要作用,但是并不参与节点之间最终的数据传输。
注意1:上述实现流程中,前五步使用的 RPC协议,最后两步使用的是 TCP 协议
注意2: Talker 与 Listener 的启动无先后顺序要求
注意3: Talker 与 Listener 都可以有多个
注意4: Talker 与 Listener 连接建立后,不再需要 ROS Master。也即,即便关闭ROS Master,Talker 与 Listern 照常通信。
2. 话题通信基本实现示例
示例需求:
编写发布订阅实现,要求发布方以1HZ(每秒1次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。
2.1 发布者
2.1.1 创建发布者topic_pub.cpp文件
代码如下:
//包含核心ROS库的头文件
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>
int main(int argc, char *argv[])
//设置编码
setlocale(LC_ALL,"");
//初始化ROS节点,节点名为topic_pub
ros::init(argc,argv,"topic_pub");
//创建节点句柄
ros::NodeHandle nh;//该类封装了ROS中的一些常用功能
//实例化发布者对象
//泛型:std_msgs::String发布的消息类型
//参数1:chatter要发布到的话题;参数2:10,队列中最大保存的消息数,超出此阈值时,先进的先销毁
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);
//组织被发布的数据,并编写逻辑发布数据
//数据(动态组织)
std_msgs::String msg;
std::string msg_prefix = "hello!";//消息前缀
int count = 0;//消息计数器
//逻辑,1s一次
ros::Rate r(1);
while(ros::ok())//节点不死
//使用stringstream拼接字符串与编号
std::stringstream ss;
ss << msg_prefix << count;
msg.data = ss.str();
//发布消息
pub.publish(msg);
//加入调试,打印发送的消息
ROS_INFO("发送的消息:%s",msg.data.c_str());
//根据前面制定的发送频率自动休眠,休眠时间=1/频率;
r.sleep();
count++;//循环结束前,让count自增
//此节点只发布话题,没有订阅话题,此函数这里主要时方便后面如果要订阅话题可调用话题绑定的回调函数而已
ros::spinOnce();
return 0;
2.1.2 修改CMakeLists.txt文件
修改后内容入下
2.1.3 编译执行验证
用ctrl+shift+b编译后,编译生成的文件
执行验证:
2.2 订阅者
2.2.1 创建发布者topic_sub.cpp文件
代码如下:
//包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
void subscribe_topic(const std_msgs::String::ConstPtr& msg_p)
ROS_INFO("订阅的话题:%s",msg_p->data.c_str());
int main(int argc, char *argv[])
setlocale(LC_ALL,"");
//初始化ROS节点,节点名为topic_sub
ros::init(argc,argv,"topic_sub");
//创建节点句柄
ros::NodeHandle nh;//该类封装了ROS中的一些常用功能
//实例化订阅者对象,subscribe_topic是订阅者回调函数
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,subscribe_topic);
//设置循环调用回调函数
ros::spin();//循环接收订阅的话题数据,并调用回调函数处理
return 0;
2.2.2 修改CMakeLists.txt文件
2.2.3 运行订阅者
rosrun topic_com topic_sub
3.基于自定义msg的话题通信示例
示例需求:
编写发布订阅实现,要求发布方以1HZ(每秒1次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出。自定义小时包含人的信息有姓名、身高、年龄等。
3.1 自定义消息
3.1.1 创建自定义消息文件person.msg
先创建msg文件夹,并在其中创建person.msg。
string name
int32 age
float32 height
3.1.2 配置package.xml
添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
3.1.3 配置CMakeLists.txt
#加入message_generation,必须有 std_msgs
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
#打开注释,并配置msg源文件person.msg
## Generate messages in the 'msg' folder
add_message_files(
FILES
person.msg
)
#打开注释,生成消息和服务时依赖std_msgs
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
#打开注释,执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES topic_com
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
3.1.4 编译
编译后生成的中间文件person.h位于devel/include目录:
后续调用相关msg时,是从中间文件person.h文件调用的
3.1.5 配置c_cpp_properties.json文件
为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性,增加的内容是"/home/kandi/catkin_ws/devel/include/**",
"includePath": [
"/opt/ros/noetic/include/**",
"/home/kandi/catkin_ws/src/helloworld/include/**",
"/home/kandi/catkin_ws/src/topic_pub/include/**",
"/home/kandi/catkin_ws/devel/include/**",
"/usr/include/**"
],
3.2 发布者实现
//包含核心ROS库的头文件
#include "ros/ros.h"
//#include "std_msgs/String.h" //普通文本类型的消息
//#include <sstream>
#include "topic_com/person.h"
int main(int argc, char *argv[])
//设置编码
setlocale(LC_ALL,"");
//初始化ROS节点,节点名为topic_pub
ros::init(argc,argv,"pub_person");
//创建节点句柄
ros::NodeHandle nh;//该类封装了ROS中的一些常用功能
//实例化发布者对象
//泛型:std_msgs::String发布的消息类型
//参数1:chatter要发布到的话题;参数2:10,队列中最大保存的消息数,超出此阈值时,先进的先销毁
ros::Publisher pub = nh.advertise<topic_com::person>("chatter_person",10);
//组织被发布的数据,并编写逻辑发布数据
topic_com::person p;
p.name = "kandi";
p.age = 20;
p.height = 1.75;
//逻辑,1s一次
ros::Rate r(1);
while(ros::ok())//节点不死
//发布消息
pub.publish(p);
p.age += 1;
//加入调试,打印发送的消息
ROS_INFO("我叫:%s,今年%d岁,高%.2f米",p.name.c_str(), p.age, p.height);
//根据前面制定的发送频率自动休眠,休眠时间=1/频率;
r.sleep();
//此节点只发布话题,没有订阅话题,此函数这里主要时方便后面如果要订阅话题可调用话题绑定的回调函数而已
ros::spinOnce();
return 0;
3.3 订阅者实现
//包含头文件
#include "ros/ros.h"
//#include "std_msgs/String.h"
#include "topic_com/person.h"
void receive_person(const topic_com::person::ConstPtr& person_p)
ROS_INFO("订阅的人信息:%s,%d,%.2f",person_p->name.c_str(),person_p->age,person_p->height);
int main(int argc, char *argv[])
setlocale(LC_ALL,"");
//初始化ROS节点,节点名为topic_sub
ros::init(argc,argv,"sub_person");
//创建节点句柄
ros::NodeHandle nh;//该类封装了ROS中的一些常用功能
//实例化订阅者对象,subscribe_topic是订阅者回调函数
ros::Subscriber sub = nh.subscribe<topic_com::person>("chatter_person",10,receive_person);
//设置循环调用回调函数
ros::spin();//循环接收订阅的话题数据,并调用回调函数处理
return 0;
以上是关于ROS通信机制一---话题通信的主要内容,如果未能解决你的问题,请参考以下文章