go to index

ROS中的通信架构

read time 20 min read
ROS

ROS中的通信架构

ROS的通信架构是ROS的灵魂,也是整个ROS正常运行的关键所在。ROS通信架构包括各种数据的处理,进程的运行,消息的传递等等。本节主要介绍了通信架构的基础通信方式和相关概念。其中首先介绍了最小的进程单元节点Node,和节点管理器Node master。了解了ROS中的进程都是由很多的Node组成,并且由Node master来管理这些节点。

Node & Master

Node

在ROS中,最小的进程单元就是Node,通常我们将它翻译为节点。一个软件包中可以有多个可执行文件,可执行文件在运行后就成为了一个进程, 在ROS中这个进程就叫做Node(节点)。 从程序的角度来说, Node就是一个可执行文件(通常是由catkin从cpp源代码编译而来,或者是python文件)被执行,加载到了内存之中。 从功能的角度来说,由于机器人的模块非常复杂,通常开发者不会将所有的功能集合在一个节点上(就像鸡蛋往往不会装在一个篮子里),而是采用分布式的方法,用一个Node负责一个单独的功能,这样以模块化的方式组织代码,既可以提高系统的稳定性,也让Debug变得容易。

Master

就像上文提到的那样,由于机器人的模块非常复杂,对应的节点也非常的多,在机器人工作时,往往会运行众多的node,负责感知世界、控制运动、决策和计算等功能。 那么如何进行合理的调配,管理这些Node呢,ROS提出了Master-节点管理器。 Master在整个网络通信架构中相当于是管理中心,管理者所有的节点。如果将Node比作是摆摊的小贩,那么Master就是城管。 Node首先需要在Master处进行注册,之后Master会将Node纳入到整个ROS程序之中,各个Node之间的通信也是由Master进行牵线,才能两两进行点对点的通信。因此在ROS程序启动时,需要先启动Master,才能启动其他的Node。

launch文件

launch文件的作用

在上一小节中我们提到了ROS是一个系统级别的工程,在一个机器人正常运行时,他的Node是非常多的,那么这么多的节点要如何开启呢?这就是我们本节要讨论的问题。 首先我们要给出答案,对于一个复杂机器人的启动过程,并不需要对每个节点执行rosrun命令去依次启动,ROS为我们提供了一个命令可以一次性启动master和多个node

bash
roslaunch pkg_name file_name.launch

roslaunch命令首先会检测系统的roscore有没有运行,也就是确认master有没有正常启动,如果没有的话,roslaunch就会先启动master,然后再执行file_name.launch中规定好的启动流程。 我们可以认为roslaunch是一个一键启动工具,能够一次性的将多个节点按我们的配置启动起来。

launch的语法

launch文件也是一个xml标签文件,遵循xml的语法格式。 最简单的launch文件如下

xml
<launch>
<node name="talker" pkg="rospy_tutorials" type="talker" />
</launch>

这个launch文件只有一个作用,就是启动rospy_tutorials包中的talker节点。 然而实际工程项目中的launch文件比这要复杂得多,以Ros-Academy-for-Beginners中的robot_sim_demo为例

xml
<launch>
<!--arg是launch标签中的变量声明,arg的name为变量名,default或者value为值-->
<arg name="robot" default="xbot2"/>
<arg name="debug" default="false"/>
<arg name="gui" default="true"/>
<arg name="headless" default="false"/>

<!-- Start Gazebo with a blank world -->
<include file="$(find gazebo_ros)/launch/empty_world.launch"> <!--include用来嵌套仿真场景的launch文件-->
<arg name="world_name" value="$(find robot_sim_demo)/worlds/ROS-Academy.world"/>
<arg name="debug" value="$(arg debug)" />
<arg name="gui" value="$(arg gui)" />
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="headless" value="$(arg headless)"/>
</include>

<!-- Oh, you wanted a robot? --> <!--嵌套了机器人的launch文件-->
<include file="$(find robot_sim_demo)/launch/include/$(arg robot).launch.xml" />

<!--如果你想连同RViz一起启动,可以按照以下方式加入RViz这个node-->
<!--node name="rviz" pkg="rviz" type="rviz" args="-d $(find robot_sim_demo)/urdf_gazebo.rviz" /-->
</launch>

这个launch相较于前一个基础的示例,内容就丰富了很多,他启动了gazebo模拟器,导入了参数内容并加入了机器人模型。

ROS中的通信

ROS的通信方式是ROS最为核心的概念,ROS系统的精髓就在于它提供的通信架构。ROS的通信方式有以下四种:

  • Topic 话题
  • Service 服务
  • Paramteter Service 参数服务器
  • Actionlib 动作库

Topic

在ROS的通信中,Topic是常用的一种,对于实时性,周期性的消息,利用Topic来传递是最佳的选择。 Topic是一种点对点的通信方式,这里的点指得就是Node,也就是说Node和Node通过Topic的方式来传递信息。 Topic注册到ROS系统中要经历以下几个初始化的过程:首先,Publisher节点(也就是发布者)和Subscriber节点(也就是订阅者)都要到节点管理器(也就是master)进行注册,然后Publisher节点会发布Topic,Subscriber节点在包管理器master的指挥下订阅该Topic,从而建立起Subscriber和Publisher的通信,但要值得注意的是,这个通信是单向的。信息只能由Publisher节点流向Subscriber节点。 其示意结构图如下: 图片 Subscriber节点在接受到消息时会进行处理,一般这个处理的过程叫做回调(Callback),所谓回调提前写好一个处理得函数,每当有消息到来的时候就会触发这个处理函数对消息进行处理。比如单片机中的串口中断函数,会对接收到的消息进行处理。

异步通信

图片 参考上图,我们以摄像头画面的发布、处理、显示为例讲讲Topic通信的流程,在这个图中,调用机器人上的摄像头进行拍照的程序是一个Node(记作Node1),当然他也是一个Publisher发布者,在程序启动后,他开始发布Topic,比如它发布了一个Topic叫做/camera_rgb,是rgb颜色信息,即采集到的彩色图像。同时Node2是图像的处理程序,他订阅了/camera_rgb这个Topic,经过包管理器Master的介绍,他就能建立起和Node1的链接。 在整个通信的过程中,Node1每次发布完图像的数据之后就执行其他的动作去了,而并不会关心Node2是否接受到了图片,接受过程中是否出现了问题。Node2每次接受到了图片就去进行图像处理相关的工作,也不会关心Node1是以怎样的方式将图片数据发布了出来。因此我们可以说Node1和Node2对彼此通信的流程并不关心,不存在协同工作。我们称这样的通信方式是异步的。 ROS是一种分布式的架构,一个Topic可以被多个Node同时发布,也可以被多个Node同时订阅。

Topic bash Tool

在rosbash中提供了一些关于ros topic的工具,他们都以rostopic作为开头,具体如下

命令作用
rostopic lsit列出当前所有的topic
rostopic info topic_name显示某个topic的属性信息
rostopic echo topic_name显示某个topic的内容
rostopic pub topic_name …向某个topic发布内容
rostopic bw topic_namerostopic bw topic_name
rostopic hz topic_name查看某个topic的频率
rostopic find topic_type查找某个类型的topic
rostopic type topic_name查看某个topic的类型(msg)

如果你忘了命令的写法,不要担心,rostopic -help可以查看具体的用法。如果你忘记了命令怎么拼写,那就使用tab补全。

Topic Message

topic有很严格的格式要求,比如上一小节的/camera_rgb,它就必须要遵循ROS中定义好的rgb图像格式,这种格式就是Message。Message按照定义解释来说就是Topic内容的数据类型,也称之为Topic的格式标准。

结构与类型

基本的msg包括有bool、int8、int16、int32、int64(以及uint)、float、float64、string、time、duration、header、可变长数组array[]、固定长度数组array[]。 我们用一个具体的msg来了解,例如上一小节中的/camera_rgb话题所使用的sensor_msg/image

bash
# 使用rosmsg 展示msg的内容
user@bad76c4f6f0a:/opt/ros/noetic/share/sensor_msgs/msg$ rosmsg show sensor_msgs/Image 
std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
uint32 height
uint32 width
string encoding
uint8 is_bigendian
uint32 step
uint8[] data
常见的Message
Vector3.msg
bash
#文件位置:geometry_msgs/Vector3.msg

float64 x
float64 y
float64 z
Accel.msg
bash
#定义加速度项,包括线性加速度和角加速度
#文件位置:geometry_msgs/Accel.msg
Vector3 linear
Vector3 angular
Header.msg
bash
#定义数据的参考时间和参考坐标
#文件位置:std_msgs/Header.msg
uint32 seq      #数据ID
time stamp      #数据时间戳
string frame_id #数据的参考坐标系
Echos.msg
bash
#定义超声传感器
#文件位置:自定义msg文件
Header header
uint16 front_left
uint16 front_center
uint16 front_right
uint16 rear_left
uint16 rear_center
uint16 rear_right
Quaternion.msg
bash
#消息代表空间中旋转的四元数
#文件位置:geometry_msgs/Quaternion.msg

float64 x
float64 y
float64 z
float64 w
Imu.msg
bash
#消息包含了从惯性原件中得到的数据,加速度为m/^2,角速度为rad/s
#如果所有的测量协方差已知,则需要全部填充进来如果只知道方差,则
#只填充协方差矩阵的对角数据即可
#位置:sensor_msgs/Imu.msg

Header header
Quaternion orientation
float64[9] orientation_covariance
Vector3 angular_velocity
float64[9] angular_velocity_covariance
Vector3 linear_acceleration
float64[] linear_acceleration_covariance
LaserScan.msg
bash
#平面内的激光测距扫描数据,注意此消息类型仅仅适配激光测距设备
#如果有其他类型的测距设备(如声呐),需要另外创建不同类型的消息
#位置:sensor_msgs/LaserScan.msg

Header header            #时间戳为接收到第一束激光的时间
float32 angle_min        #扫描开始时的角度(单位为rad)
float32 angle_max        #扫描结束时的角度(单位为rad)
float32 angle_increment    #两次测量之间的角度增量(单位为rad)
float32 time_increment    #两次测量之间的时间增量(单位为s)
float32 scan_time        #两次扫描之间的时间间隔(单位为s)
float32 range_min        #距离最小值(m)
float32 range_max        #距离最大值(m)
float32[] ranges        #测距数据(m,如果数据不在最小数据和最大数据之间,则抛弃)
float32[] intensities    #强度,具体单位由测量设备确定,如果仪器没有强度测量,则数组为空即可
Point.msg
bash
#空间中的点的位置
#文件位置:geometry_msgs/Point.msg

float64 x
float64 y
float64 z
Pose.msg
bash
#消息定义自由空间中的位姿信息,包括位置和指向信息
#文件位置:geometry_msgs/Pose.msg

Point position
Quaternion orientation
PoseStamped.msg
bash
#定义有时空基准的位姿
#文件位置:geometry_msgs/PoseStamped.msg

Header header
Pose pose
PoseWithCovariance.msg
bash
#表示空间中含有不确定性的位姿信息
#文件位置:geometry_msgs/PoseWithCovariance.msg

Pose pose
float64[36] covariance
Power.msg
bash
#表示电源状态,是否开启
#文件位置:自定义msg文件
Header header
bool power
######################
bool ON  = 1
bool OFF = 0
Twist.msg
bash
#定义空间中物体运动的线速度和角速度
#文件位置:geometry_msgs/Twist.msg

Vector3 linear
Vector3 angular
TwistWithCovariance.msg
bash
#消息定义了包含不确定性的速度量,协方差矩阵按行分别表示:
#沿x方向速度的不确定性,沿y方向速度的不确定性,沿z方向速度的不确定性
#绕x转动角速度的不确定性,绕y轴转动的角速度的不确定性,绕z轴转动的
#角速度的不确定性
#文件位置:geometry_msgs/TwistWithCovariance.msg

Twist twist
float64[36] covariance  #分别表示[x; y; z; Rx; Ry; Rz]
Odometry.msg
bash
#消息描述了自由空间中位置和速度的估计值
#文件位置:nav_msgs/Odometry.msg

Header header
string child_frame_id
PoseWithCovariance pose
TwistWithCovariance twist

Service

什么是Service

在Topic一节中我们介绍了,Topic是一种单向异步,用于实时性,周期性消息的通信方式。然而有些时候单向的通信满足不了通信要求,比如当一些节点只是临时而非周期性的需要某些数据,如果用topic通信方式时就会消耗大量不必要的系统资源,造成系统的低效率高功耗。 这种情况下,就需要有另外一种请求-查询式的通信模型。这节我们来介绍ROS通信中的另一种通信方式——Service(服务)。

Service的工作原理

为了补充Topic通信的不足,Service在通信模型上与Topic做了区别。 Service是一种双向同步通信,它不仅可以发送消息,同时还会有反馈。所以Service分为请求方(Clinet)和应答方/服务提供方(Server)。请求方(Client)发送一个Request,然后等待服务提供方(Server)处理,反馈一个Reply。通过这样一个类似”请求-应答”机制完成整个服务的通信。 这种通信的示意图如下: 图像 Node B是Server方,提供了一个服务接口/Service,通常我们会用/String类型来指定Service的名称。Node A向Node B发起了请求(Request),经过处理后的到了反馈(Reply) Service是一种同步通信方式,也就是说,Node A 在向Node B提交请求后会在原地等待Node B的反馈,直到得到了这个反馈才回去执行其他的流程。Node A在等待的过程是一种阻塞性的状态,这样的通信不会有频繁的消息传递,也没有冲突于高资源的占用,简单且高效。

Coding for Node

Client Library

在正式开始ROS的编程训练之前,我想先介绍一下ROS中的Client Library,ROS为机器人开发者们提供了不同语言的编程接口,比如C++接口roscpp,Python接口rospy,Java接口rosjava。 尽管编程语言不同,但这些接口都可以用来实现ros中的通信功能 目前ros支持的Client Library包括

Client LibraryIntroduction
roscppROS的C++库,是目前最广泛应用的ROS客户端库,执行效率高
rospyROS的Python库,开发效率高,通常用在对运行时间没有太大要求的场合,例如配置、初始化等操作
roslispROS的LISP库
roscsMono/.NET.库,可用任何Mono/.NET语言,包括C#,Iron Python, Iron Ruby等
rosgoROS Go语言库
rosjavaROS Java语言库
rosnodejsJavascript客户端库
也许未来还有对其他语言的支持

但坦白的来说,目前只有roscpp和rospy最常用也最能正常使用,其他的语言接口基本上都是处于测试版本。 从开发者的角度来看,一个客户端库,至少需要做到Master注册,名称注册,消息收发功能,才能给开发者提供ROS通信架构进行配置的方法。

roscpp

roscpp位于/opt/ros/noetic下(noetic是ros的版本号),用C++实现了ROS通信。 roscpp的主要部分包括

  • ros::init(): 解析传入的ROS节点参数, 是创建Node时最先用到的函数
  • ros::NodeHandle: Node与topic,service,param等交互的公共接口
  • ros::master: 包含从master处查询信息的函数
  • ros::this_node: 包含查询这个进程的函数
  • ros::service: 包含查询服务的函数
  • ros::param: 包含查询参数服务器的函数
  • ros::names: 包含处理计算图资源的函数

从功能上,roscpp中包含的函数可以分为以下几类:

  • Initialization and Shutdown 初始化与关闭
  • Topics 话题
  • Services 服务
  • Parameter Server 参数服务器
  • Timers 定时器
  • NodeHandle 节点句柄
  • Callbacks and Spinning 回调和自旋(或者翻译叫轮询?)
  • Logging 日志
  • Names and Node Information 名称管理
  • Time 时钟
  • Exception 异常

在本小节中,我们会为在上一节[ROS中的文件系统]中提到的beginner_tutorials创建一个节点,从而熟悉roscpp的部分函数。

创建节点源文件

假设你已经完成了上一节中新建包以及编译工作空间和更新包路径等相关工作。

bash
# 使用roscd进入到beginner_tutorials包中
roscd beginner_tutorials/src
# 使用vim创建一个cpp源文件
vim demo.cpp 

如果没有安装vim,可以使用下面的命令安装

bash
sudo apt install vim