目录
- 一、通信机制简介
- 二、话题通信机制
- 2.1话题通信简介
- 2.2话题通信实操(C++)
- 2.2.1分析
- 2.2.2发布方代码
- 2.2.3订阅方代码
- 2.2.4配置CMakeLists.txt
- 2.2.5执行
- 2.2.6注意
- 2.3话题通信实操(python)
- 2.3.1分析
- 2.3.2发布方代码
- 2.3.3订阅方代码
- 2.3.4添加可执行权限
- 2.3.5配置 CMakeLists.txt
- 2.3.6执行
- 三、服务通信机制
- 四、参数管理机制
- 五、话题与服务的区别
一、通信机制简介
机器人是一种高度复杂的系统性实现,在机器人上可能集成各种传感器(雷达、摄像头、GPS等)以及运动控制实现,为了解耦合,在ROS中每一个功能点都是一个单独的进程,每一个进程都是独立运行的。更确切的讲,ROS是进程(也称为Nodes)的分布式框架。 因为这些进程甚至还可分布于不同主机,不同主机协同工作,从而分散计算压力。不过随之也有一个问题: 不同的进程是如何通信的?不同进程间如何实现数据交换的?这里就涉及到ROS中的通信机制了。
ROS 中的基本通信机制主要有如下三种实现策略:
话题通信(发布订阅模式)
服务通信(请求响应模式)
参数服务器(参数共享模式)
二、话题通信机制
2.1话题通信简介
话题在ROS中使用最为频繁,其通信模型也较为复杂。在ROS中有两个节点:一个是发布者Talker,另一个是订阅着Listener。两个节点分别发布、订阅同一个话题,启动顺序没有强制要求,此处假设Talker首先启动,可分为如下七步分析建立通信的详细过程。
0、Talker注册
Talker启动,通过1234端口使用RPC向ROS Master注册发布者的信息,包含所发布消息的话题名;ROS Master会将节点的注册信息加入注册列表中。
1、Listener注册
Listener启动,同样通过RPC向ROS Master注册订阅者的信息,包含需要订阅的话题名。
2、ROS Master进行信息匹配
Master根据Listener的订阅信息从注册列表中进行查找,如果没有找到匹配的发布者,则等待发布者的加入:如果找到匹配的发布者信息,则通过RPC向Listener发送Talker的RPC地址信息。
3、Listener发送连接请求
Listener接收到Master发回的Talker地址信息,尝试通过RPC向Talker发送连接请求,传输订阅的话题名、消息类型以及通信协议(TCP/UDP)
4、Talker确认连接请求
Talker接收到Listener发送的连接请求后,继续通过RPC向Listener确认连接信息,其中包含自身的TCP地址信息。
5、Listener尝试与Talker建立网络连接
Listener接收到确认信息后,使用TCP尝试与Talker建立网络连接。
6、Talker向Listener发布数据
成功建立连接后,Talker开始向Listener发送话题消息数据。
从上面的分析中可以发现,前五个步骤使用的通信协议都是RPC,最后发布数据的过程才使用到TCP。ROS Master在节点建立连接的过程中起到了重要作用,但是并不参与节点之间最终的数据传输。
2.2话题通信实操(C++)
2.2.1分析
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
发布方
接收方
数据(此处为普通文本)
流程:
编写发布方实现;
编写订阅方实现;
编辑配置文件;
编译并执行。
2.2.2发布方代码
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "sstream"
/*
发布方实现:
1.包含头文件
ROS中文本类型--->td_msgs/String.h
2.初始化ros节点
3.创建节点句柄
4.创建发布者对象
5.编写发布逻辑并发布数据
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化ros节点
ros::init(argc,argv,"erGouZi");
//3.创建节点句柄
ros::NodeHandle nh;
//4.创建发布者对象
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10);
//5.编写发布逻辑并发布数据
//要求以10HZ的频率发布数据,并且文本后添加编号
//先创建被发布的消息
std_msgs::String msg;
//发布频率
ros::Rate rate(10);
//设置编号
int count =0;
//编写循环,循环中发布数据
while (ros::ok())
{
count++;
//msg.data = "hello";
//实现字符串拼接数字
std::stringstream ss;
ss << "hello ---> " <<count;
msg.data = ss.str();
pub.publish(msg);
//添加日志
ROS_INFO("发布的数据是:%s",ss.str().c_str());
rate.sleep();根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
ros::spinOnce();//官方建议添加回调函数
}
return 0;
}
2.2.3订阅方代码
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
订阅方实现:
1.包含头文件
ROS中文本类型--->td_msgs/String.h
2.初始化ros节点
3.创建节点句柄
4.创建订阅者对象
5.处理订阅数据
6.sain()函数
*/
void doMsg(const std_msgs::String::ConstPtr &msg)
{
//通过msg获取并操作订阅到的数据
ROS_INFO("翠花订阅的数据:%s",msg->data.c_str());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化ros节点
ros::init(argc,argv,"cuihua");
//3.创建节点句柄
ros::NodeHandle nh;
//4.创建订阅者对象
ros::Subscriber sub = nh.subscribe("fang",10,doMsg);
//5.处理订阅数据
ros::spin();//循环读取接收的数据,并调用回调函数处理
return 0;
}
2.2.4配置CMakeLists.txt
add_executable(demo01_pub src/demo01_pub.cpp)
add_executable(demo02_sub src/demo02_sub.cpp)
target_link_libraries(demo01_pub
${catkin_LIBRARIES}
)
target_link_libraries(demo02_sub
${catkin_LIBRARIES}
)
2.2.5执行
1.启动 roscore;
2.启动发布节点;
3.启动订阅节点。
2.2.6注意
补充0:
vscode 中的 main 函数 声明 int main(int argc, char const *argv[]){}
,默认生成 argv 被 const 修饰,需要去除该修饰符
补充1:
ros/ros.h No such file or directory …
检查 CMakeList.txt find_package 出现重复,删除内容少的即可
参考资料:https://answers.ros.org/question/237494/fatal-error-rosrosh-no-such-file-or-directory/
补充2:
find_package 不添加一些包,也可以运行啊, ros.wiki 答案如下
You may notice that sometimes your project builds fine even if you did not call find_package with all dependencies. This is because catkin combines all your projects into one, so if an earlier project calls find_package, yours is configured with the same values. But forgetting the call means your project can easily break when built in isolation.
补充3:
订阅时,第一条数据丢失
原因: 发送第一条数据时, publisher 还未在 roscore 注册完毕
解决: 注册后,加入休眠 ros::Duration(3.0).sleep();
延迟第一条数据的发送
2.3话题通信实操(python)
2.3.1分析
分析:
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
发布方
接收方
数据(此处为普通文本)
流程:
编写发布方实现;
编写订阅方实现;
为python文件添加可执行权限;
编辑配置文件;
编译并执行。
2.3.2发布方代码
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "sstream"
/*
发布方实现:
1.包含头文件
ROS中文本类型--->td_msgs/String.h
2.初始化ros节点
3.创建节点句柄
4.创建发布者对象
5.编写发布逻辑并发布数据
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化ros节点
ros::init(argc,argv,"erGouZi");
//3.创建节点句柄
ros::NodeHandle nh;
//4.创建发布者对象
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10);
//5.编写发布逻辑并发布数据
//要求以10HZ的频率发布数据,并且文本后添加编号
//先创建被发布的消息
std_msgs::String msg;
//发布频率
ros::Rate rate(10);
//设置编号
int count =0;
//编写循环,循环中发布数据
while (ros::ok())
{
count++;
//msg.data = "hello";
//实现字符串拼接数字
std::stringstream ss;
ss << "hello ---> " <<count;
msg.data = ss.str();
pub.publish(msg);
//添加日志
ROS_INFO("发布的数据是:%s",ss.str().c_str());
rate.sleep();//根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
ros::spinOnce();//官方建议添加回调函数
}
return 0;
}
2.3.3订阅方代码
#! /usr/bin/env python
import rospy
from std_msgs.msg import String #发布消息的类型
"""
使用py 实现消息订阅
1.导包
2.初始化ROS节点
3.创建订阅者对象
4.回调函数处理数据
spin
"""
def doMsg(msg):
rospy.loginfo("我订阅的数据:%s",msg.data)
if __name__ == "__main__":
# 2.初始化ROS节点
rospy.init_node("huahua")#传入节点名称
# 3.创建订阅者对象
sub = rospy.Subscriber("che",String,doMsg,queue_size=10)
# 4.回调函数处理数据
#5.spin
rospy.spin()
2.3.4添加可执行权限
终端下进入 scripts 执行:chmod +x *.py
2.3.5配置 CMakeLists.txt
catkin_install_python(PROGRAMS
scripts/demo01_pub_p.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
catkin_install_python(PROGRAMS
scripts/demo02_sub_p.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
2.3.6执行
1.启动 roscore;
2.启动发布节点;
3.启动订阅节点。
注:可以使用 rqt_graph 查看节点关系。
三、服务通信机制
服务是一种带有应答的通信机制,与话题的通信相比,其减少了Listener与Talker之间的RPC通信。
0、Talker注册
Talker启动,通过1234端口使用RPC向ROS Master注册发布者的信息,包含所提供的服务名;ROS Master会将节点的注册信息加入注册列表中。
1、Listener注册
Listener启动,同样通过RPC向ROS Master注册订阅者的信息,包含需要订阅的服务名。
2、ROS Master进行信息匹配
Master根据Listener的订阅信息从注册列表中进行查找,如果没有找到匹配的服务提供者,则等待该服务的提供者加入:如果找到匹配的服务提供者信息,则通过RPC向Listener发送Talker的TCP地址信息。
4、Listener与Talker建立网络连接
Listener接收到确认信息后,使用TCP尝试与Talker建立网络连接,并且发送服务的请求数据。
5、Talker向Listener发布服务应答数据
Talker接收到服务请求和参数后,开始执行服务功能,执行完成后,向Listener发送应答数据。
四、参数管理机制
参数类似于ROS中的全局变量,由ROS Master进行管理,其通信机制较为简单,不涉及TCP/UDP的通信。
1、Talker设置变量
Talker使用RPC向ROS Master发送参数设置数据,包含参数名和参数值;ROS Master会将参数名和参数值保存到参数列表中。
2、Listener查询参数值
Listener通过RPC向ROS Master发送参数查找请求,包含所要查找的参数名。
3、ROS Master向Listener发送参数值
Master根据Listener的查找请求从参数列表中进行查找,查找到参数后,使用RPC将参数值发送给Listener。
五、话题与服务的区别
话题和服务是ROS中最基础也是使用最多的通信方法。具体总结如表所示。
话题与服务的区别
总之,话题是ROS中基于发布/订阅模型的异步通信模式,这种方式将信息的产生和使用双方解耦,常用于不断更新的、含有较少逻辑处理的数据通信;而服务多用于处理ROS中的同步通信,采用客户端/服务器模型,常用于数据量较少但有强逻辑处理的数据交换。