ROS中的通信机制
通信机制的分类
在ROS中比较常见的通信机制分为话题通信和服务通信,当然还有其它更多的通信方式,在这里不过多描述
话题通信
角色
- 发布者(Publisher)
- 订阅者(Subscriber)
- 管理者(ROS Master)
流程
- 发布者和订阅者会向管理者提交自身信息,自身信息包括话题名称,节点信息,地址信息等,管理者会将这些信息存入
- 管理者会将接收到的信息进行比对,然后把匹配到一起的发布者的地址信息发送给订阅者,订阅者可以通过此信息找到发布者进行订阅
- 此时发布者会发送一个TCP server给订阅者,订阅者通过此信息和发布者建立单独的连接,并且发布者将消息发布给订阅者。
PS:在此图中Talker为Publisher, Listener为Subscriber
ROSmaster在完成建立之后会自己关闭
简单的案例
这里我们以仿真环境里的小车为例
gazebo
然后用rostopic list指令查看当前有什么话题
我们可以看到当前存在一个smart/cmd_vel的话题,此话题就是控制小车速度的话题,我们用rostopic pub对此话题进行发布
可以看到小车以我们发布的速度行驶
这就是一个简单的话题通信模型
真实的案例
现在我们以机器人为例,机器人实现自动导航分为三步
- 雷达接收环境信息发布给进行计算的部件
- 部件对雷达发过来的信息进行计算并且发布给底盘
- 底盘通过部件发过来已经计算好的信息进行移动
在这个过程中就存在许多话题通信模型,接下来我们来讲讲服务通信
服务通信
角色
- 客户端(client)
- 服务端(server)
- 管理者(ROS Master)
流程
- 客户端和服务端会向管理者提交自身信息,自身信息包括服务名称,节点信息,地址信息等,管理者会将这些信息存入
- 管理者会将接收到的信息进行比对,然后把匹配到一起的服务端的地址信息发送给客户端,客户端可以通过此信息找到服务端进行订阅
- 此时客户端会将自己的请求/request发给服务端,服务端进行工作并反馈/response给客户端
PS:此图中Talker为Service,Listener为Client
ROSmaster在完成建立之后会自己关闭
简单的案例
以经典时尚小海龟为例,打开小海龟
rosrun turtlesim turtlesim_node
这次我们用rosserver list查看一下有什么服务端
我们发现这个列表中存在一个/spawn服务,这个服务端可以生成一个新的小海龟,现在我们自己成为一个客户端去请求一下这个服务端
hang 我们用rosserver call去请求/spawn 并且设置参数
发现屏幕上多了一只小海龟
说明我们成功请求了服务端,服务端给我们反馈了一只小海龟
两种通信机制的区别
接下来我们来讨论一下两种通信机制有什么区别
异步与同步
首先我们先来讲一讲什么是异步
异步简单来说就是发布方和订阅方做着各自的事情,在发布方发布完消息之后就会继续去做自己的事情,至于发布的消息被怎么处理不会去管,而订阅方只管去订阅,不会在乎是谁发来的信息
在此基础上我们可以得出第一条结论
话题通信为异步,服务通信为同步
反馈
通过流程图我们可以很直观的得出第二条结论
话题通信不存在反馈,而服务通信存在反馈
实时性
由于话题通信为异步通信方式,所以它的实时性并不强
由于服务通信为同步通信方式,存在反馈机制,客户端会一直等待服务端进行反馈,所以它的实时性强
在此基础上我们可以得出第三条结论
话题通信的实时性弱,而服务通信的实时性强
节点关系
通过异步与同步得出的结论我们可以顺势推导出第四条结论
发布者可以发布给多个订阅者,而订阅者也可以订阅多个发布者
服务端可以接收多个客户端的请求,但一个客户端只能给一个服务端发送请求
小表格
条目 | 话题 | 服务 |
---|---|---|
同步性 | 异步 | 同步 |
反馈机制 | 无 | 有 |
实时性 | 弱 | 强 |
节点关系 | 多对多 | 一对多 |
使用场景
综合以上多个结论,我们可以得出最终结论
话题通信的逻辑性不强,但是对数据的发送和接收效率高
服务通信的逻辑性很强,但是对数据的发送和接收效率低
自定义消息数据类型
上面的过程中我们用的是ros中的话题和服务类型
接下来我们需要定义自己的数据类型
自定义话题消息
要想自定义消息类型,我们得先看看Twist有哪些信息
我们可以看到这个消息里面包含了线速度和角速度
现在我们就可以自己创建一个msg文件,在里面写我们想发布的消息类型
uint16 num
这里相当于创建了一个整数类型的消息
然后我们需要在CMakeList.txt和package.xml文件中添加依赖
首先在package.xml文件中的最后加入
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
接下来是CMakeList.txt
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
add_message_files(
FILES
number.msg
)
generate_messages(
DEPENDENCIES
std_msgs
)
这三步是待会在catkin_make的时候编译我们自定义的数据类型并且依赖于std_msgs
接下来我们catkin_make编译一下
然后用rosmsg show查看我们自定义的数据类型有没有成功编译
接下来我们用c++编写文件发布我们自定义的消息并且用echo订阅
#include <ros/ros.h>
#include <learning_topic/number.h>
int main(int argc, char *argv[]){
ros::init(argc,argv,"num");
ros::NodeHandle n;
ros::Publisher pub = n.advertise<learning_topic::number>("num_pub", 10);
ros::Rate rate(1);
learning_topic::number nb;
nb.num = 10;
while(ros::ok()){
pub.publish(nb);
rate.sleep();
ROS_INFO("%d", nb.num);
}
return 0;
}
这样我们就成功发布并订阅了自定义的数据类型
自定义服务消息
同样的 我们用rossrv show看看服务消息里面的信息
我们可以看到相比较与话题数据类型,服务数据类型会多增加一个反馈值,并且用“—”隔开
接下来我们就可以自己定义一个服务类型
这次需要在CMakeList.txt里面增加服务类型的依赖
add_service_files(
FILES
num_add.srv
)
接下来用rossrv show查看我们是否成功编译
可以看到我们自定义的类型已经成功编译,接下来我们编写一个服务端去接收请求,并用rosserver call发布请求
#include <ros/ros.h>
#include <learning_topic/num_add.h>
bool callback(learning_topic::num_add::Request &req, learning_topic::num_add::Response &res){
int num1 = req.num1;
int num2 = req.num2;
int result = res.result;
result = num1 + num2;
ROS_INFO("%d + %d = %d",num1,num2,result);
return true;
}
int main(int argc, char *argv[]){
ros::init(argc,argv,"n_a");
ros::NodeHandle n;
ros::ServiceServer server = n.advertiseService("add_num",callback);
ROS_INFO("Wait for request");
ros::spin();
return 0;
}
可以看到我们编写的服务端一直在等待客户端去发送请求,我们用rosserver call去发送请求,客户端成功收到请求
这样我们就成功完成了自定义服务数据类型的发布
总结
到这里ROS中的通信机制就讲完了,此讲的内容包括通信模型,实用案例,两种通信机制的区别以及自定义数据类型的使用