ROS(Robot Operating System)

ros的基本框架是斯坦福大学人工智能实验室在STAIR项目与机器人技术公司Willow Garage的个人机器人项目(Personal Robots Program)之间的合作中为了提高机器人研发中的软件复用率,由吴恩达教授指导的Morgan Quigley博士于2007年主导设计与实现的。一年后交由著名的机器人实验室Willow Garage继续开发维护。后于2010年正式发布。2013年,Willow Garage的创办者也是注资人为了全身心的投入到自己创办的公司,关闭了实验室。此后ros的维护工作交给OSRF(Open Source Robotics Foundation)接管。

ros的特点

1、分布式的结构

一个进程在ros中称作一个节点(node),每个功能节点可以单独编译,节点之间形成点对点的通信,适合多机协同工作。

2、语言支持广泛

功能节点的接口与编程语言无关。支持C++、Python、Java等。

3、集成度高功能完备

集成了众多的开源项目,如OpenCV、pcl(point cloud library)等。

4、丰富的组件化工具

物理仿真环境gazebo,3d的数据可视化工具rviz,数据记录工具rosbag,Qt工具箱rqt_* 。

5、免费且开源

遵循BSD许可协议,可以随意修改并商用,功能包的数量迅速增加。

 

总结以上特点,ros以其分布式的通信机制为核心,辅以实用的开发工具、丰富的开源功能包,逐渐形成了从社区中汲取知识提升开发效率然后开源分享自己的开发成果的良性生态系统。

 


ros的系统实现

文件系统级

所谓文件系统,就是说我们的项目放在硬盘里,到底是什么目录结构,长什么样子。一个典型的ros工程如下图所示:

首先我们看到最顶层的catkin工作空间,它是整个ros工程中层次最高的概念。工作空间也就是我们管理和组织ros工程项目文件的地方。而catkin是ros定制的编译构建系统,是对CMake的扩展,对ros这样大体量的工程有更好的支持,同时也简化了操作。

简言之,catkin工作空间就是一个文件夹,用来组织和管理ros功能包。我们可以使用catkin对其进行编译。

那么工作空间如何创建呢?

首先利用mkdir命令建立一个工作空间文件夹(名字任意取),并在这个文件夹下建立一个名为src的文件夹(必须取名为src)

mkdir -p ~/catkin_ws/src

然后进入src文件夹下,调用catkin_init_workspace命令初始化工作空间。

cd ~/catkin_ws/src
catkin_init_workspace

这时我们的src文件夹下会多出一个CMakeLists.txt文件,我们的工作空间也就创建好了。

好的,现在我们回过头来解释一下刚刚建立的src目录以及初始化工作空间后自动生成的CMakeLists.txt文件是干什么的。ros工程默认src目录作为存放功能包(package)的文件夹,即源文件空间(the source space)。而这里的(即主目录下的)CMakeLists.txt文件规定了工程的编译规则,包括指定工程名称,指定工程采用的语言,设置编译类型(debug或release),设置编译器的类型(eg:C、C++),添加要编译的子目录(即功能包,顺序任意)。

我们刚刚初始化的工作空间其实就是一个空的工作空间,里面什么都没有,我们还没有往里面写入任何的程序文件,这也是结构最简单的工作空间。这时我们可以使用catkin_make命令对工作空间进行编译(事实上,我们可以不必特意对工作空间进行初始化,而直接使用catkin_make命令进行编译,catkin_make会自动为我们初始化工作空间)

cd ~/catkin_ws
catkin_make

注意:使用catkin_make编译之前一定要回到工作空间,在其他目录下进行catkin_make是会失败的。

编译完成之后,我们工作空间下会多出build和devel两个文件夹,build文件夹下存放的是CMake和catkin的缓存信息、配置信息和其他中间文件,称为编译空间(the build space)。devel文件夹中存放的是编译后生成的目标文件,包括头文件、动态&静态链接库、可执行文件等,称为开发空间(the development space)。

一般在catkin_make编译之后要记得source一下devel目录下的setup.bash文件,将编译生成的文件刷新到系统环境中,否则我们调用生成的可执行文件时系统会找不到。

source ~/catkin_ws/devel/setup.bash  #编译之后要用source刷新环境

每次重新打开一个新的终端shell,都需要运行上述命令。我们可以将该命令脚本加入~/.bashrc文件中,这样每次打开shell,~/.bashrc文件会自动运行,而不必手动刷新环境

echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc 
source ~/.bashrc

对我们来说,在编写ros工程以及调试程序过程中,只有src目录是我们直接写代码的地方。所以这里我们主要介绍src目录。

src目录中可以平行放置多个功能包

也可以创建多级目录,把多个功能包放在一个文件夹下

功能包是ros文件系统中组织程序文件的基本单元,也就是catkin编译的基本单元。功能包中不同类型的文件分别放置在不同的文件夹中,同时,一个package可以包含多个可执行文件。

那么如何判断一个文件夹是不是package呢?如下图:

一个文件夹下包含CMakeLists.txt和package.xml两个文件,即定义了一个软件包,也就可以被编译系统编译了。这也是package最精简的结构。每个package中都必须包含这两个文件,这里的(即子目录中的)CMakeLists.txt文件中规定了功能包的编译规则,包括指定功能包名称,指定编译依赖项,指定要编译的源文件,指定要添加的消息格式文件/服务格式文件/动作格式文件,指定生成的消息/服务/动作,指定头文件搜索目录,指定链接库搜索目录,指定生成的静态链接库文件,指定需要链接的库文件,指定编译生成的可执行文件以及路径等等。而package.xml文件定义了功能包的属性信息,包括包名,版本号,作者,编译依赖和运行依赖等。

除了这两个文件,package中还有代码文件,代码文件又分为脚本文件(比如Python文件 *.py,Linux下的shell文件 *.sh)、头文件(*.h)和源程序文件(*.cpp以及其他编程语言格式),分别存放在scripts目录、include目录和src目录中。

package中还会存放我们自定义的通信格式文件,包括消息(*.msg)、服务(*.srv)以及动作(*.action),分别存放在msg目录、srv目录和action目录中。

最后,package中还有launch文件(*.launch)以及配置文件(*.yaml以及其他的标签类格式),launch文件的作用是批量运行多个可执行文件,存放于launch目录。配置文件当中主要包含的是相关的参数配置,比如机器人的尺寸参数,关节坐标系的tf变换参数等,存放在config目录中。

接下来大家可能会问,如何创建一个package呢?我们可以使用catkin_create_pkg命令创建功能包,同时指定依赖:

catkin_create_pkg package_name dep1 dep2 dep3...

创建完成之后,功能包中会产生CMakeLists.txt和package.xml两个文件。

既然功能包都有各自的依赖项,那么当我们克隆别人的功能包来使用的时候,我们的系统环境中可能并没有那个功能包的依赖项,这时该怎么办呢?我们可以利用rosdep命令为功能包安装所需要的依赖(根据package.xml中指定的依赖):

rosdep install package_name

最后介绍几个有关功能包的命令行工具:rospack、rosls、roscd、roscp、rosed

  • rospack list 列出所有功能包
  • rospack find package_name 查找功能包路径
  • rospack depends package_name 查看功能包依赖的包
  • rosls package_name 列出功能包中的内容
  • roscd package_name 进入功能包所在路径
  • rosed package_name file_name 编辑功能包中的文件
  • roscp  package_name  file_name  target_path 从功能包中复制文件到其他目录

到此为止,关于package的内容就介绍完了。下面我们介绍一下有关package的另一个概念。

与功能包相区别,还有一个元功能包(metapackage)的概念。它的作用是将多个功能类似的用于同一目的的功能包组织到一个文件夹当中。元功能包其实是一个虚包,里面没有实质性的内容,但是它依赖了很多其他的软件包,通过这种方法把其他的软件包组合起来。比如导航功能包navigation,它其实是一个元功能包,元功能包当中有一个与其同名的功能包,其中除了一些log信息,readme文件,就只有CMakeLists.txt和package.xml文件。并且CMakeLists.txt文件中没有指明要生成什么可执行文件,或生成什么库,其中只多出了一句catkin_metapackage()宏命令,这句命令声明了这个包是一个元功能包。而package.xml中注明了元功能包依赖的功能包。

metapackage的最主要的功能是让我们安装的时候更方便,可以一次性下载所有相关的功能包。

OK,文件系统级系统实现的内容就是这些。下面介绍计算图级的系统实现。

 


 

计算图级

所谓计算图,就是指我们的程序运行起来,会形成什么样的网络通信结构,进程之间数据以何种路径流通。一个常见的计算图如下图所示:

这是一个比较简单的计算图,其中只包含了几个进程,以及它们之间的数据流向。

事实上,计算图级中包含了很多概念。包括节点(node)、节点管理器(master)、话题(topic)、服务(service)、动作(action)、参数服务器(parameter server)等等。

1、节点

ros中将进程称为节点,需要注意节点名不一定与对应的可执行文件名相同(一般设置为相同的名称)。节点之间各自独立,它们通过话题、服务和参数服务器进行通信。ros通过使用节点将代码以及功能解耦(ros节点之间并不直接连接,ros 节点只管发布它们有用的信息,而不需要担心是否有其他节点来订阅这些消息)。

ros提供了一个处理节点的工具:rosnode,支持的命令如下:

  • rosnode info node_name 输出此节点信息
  • rosnode list 列出所有当前运行的节点
  • rosnode kill node_name 关闭此节点
  • rosnode machine hostname 列出此主机上运行的节点
  • rosnode cleanup 清除无法访问节点的注册信息
  • rosnode ping node_name 测试节点的连通性

ros还提供了一个启动节点和修改节点名称、话题名称以及参数名称的工具:rosrun,我们无需重新编译代码即可重新配置节点。用法如下:

  • rosrun package_name executable_name 启动某功能包中的某节点
  • rosrun package_name executable_name  _name:=node_name 修改节点名称,使用 node_name 参数给出的名称覆盖节点的默认名
  • rosrun package_name executable_name topic_name:=new_topic_name 修改节点发布或订阅的话题名称
  • rosrun package_name executable_name _paramname:=9 修改节点中的参数,注意要在参数名称前添加一个下划线

还记得在文件系统级中讲过批量启动ros节点的*.launch文件吗,我们可以使用roslaunch命令启动launch文件,它还会帮我们自动启动节点管理器。

  • roslaunch  package_name  *.launch

2、节点管理器

master用于节点的名称注册和查找,也用于设置节点间的通信。因此在启动节点之前必须要先启动master:

roscore

这句命令不仅仅启动了master,还同时启动了日志输出(rosout)和参数服务器(parameter server)。rosout是一个节点,这个节点的作用是用来生成各个节点的文本日志消息。它订阅了同名话题rosout,所有的节点都向这个话题发布消息。参数服务器是通过网络访问的,共享的多变量字典,相当于全局变量库。它使用XMLRPC实现并运行在master中。

参数服务器通信的过程为:

  • 1、节点1设置参数值;
  • 2、节点2查询参数值;
  • 3、参数服务器向节点2发送参数值。

三步全部使用RPC(Remote Procedure Call)协议。

ros中关于参数服务器的工具是rosparam,支持的参数如下:

  • rosparam list 列出所有参数
  • rosparam get param_name 获取参数值
  • rosparam set param_name value 设置参数值
  • rosparam delete param_name 删除参数
  • rosparam dump filename 将参数字典保存到文件中
  • rosparam load filename 从文件中加载参数字典

启动了master,接下来我们就可以启动节点了,每启动一个节点,就会向master注册该节点,然后各个节点之间才能进行通信。master的作用是使ros节点之间能够相互查找,并建立点对点通信,消息直接从发布节点传递到订阅节点,中间不经过master转交。注意,ros是一个分布式网络系统,我们可以在某一台电脑上运行master,在其他电脑上运行节点。

3、话题

话题是ros中使用最多的,单向的通信方式,相当于节点间传输数据的总线异步(发布消息的节点只管发布,不管是否有其他节点接收;订阅的节点收到消息就处理,收不到就等待,不管是否有其他节点发布消息),多对多(一个话题可以由多个节点发布,也可以被多个节点同时订阅)。适用于连续、高频的数据传输工作(如激光雷达、里程计发布数据)。ros话题的消息可以通过TCP/IP和UDP传输,基于TCP传输称为TCPROS,是默认的传输方式;基于UDP传输称为UDPROS,是一种低延迟高效率但不稳定的传输方式,可能丢失数据,适合远程操作任务。

那么,两个节点间建立话题通信的过程是什么样的呢?请看下图:

如图,话题的通信过程(假设节点1为发布节点,节点2为订阅节点,节点1先启动):

  • 1、节点1向master注册,包括发布话题名称、节点所在地址等;
  • 2、节点2向master注册,包括订阅话题名称、节点所在地址等;
  • 3、master将注册列表中的信息进行匹配,匹配完成后将节点1的地址发送给节点2;
  • 4、节点2根据master发来的地址向节点1发送连接请求,包括订阅的话题名称、以及通信协议类型(TCP);
  • 5、节点1收到连接请求后反馈一个确认信息,包括自身的TCP地址;
  • 6、节点2收到TCP地址后与节点1建立TCP网络连接;
  • 7、节点1将数据发送给节点2。

其中,前五个步骤采用XMLRPC的通信协议;后面两个步骤采用TCP通信协议。

ros中用于主题操作的工具是rostopic,支持参数如下:

  • rostopic list 列出当前存在的话题
  • rostopic info topic_name 输出话题的相关信息,包括发布者、订阅者、以及相关服务的信息
  • rostopic bw topic_name 显示话题所占带宽
  • rostopic echo topic_name 输出本话题的消息
  • rostopic find msg_type 按照消息类型查找话题
  • rostopic hz topic_name 显示话题发布的频率
  • rostopic pub topic_name msg_data 向话题发布消息
  • rostopic type topic_name 输出本话题的消息类型(即消息名称)

4、服务

服务是ros中的双向通信方式。同步(一个节点向另一个节点发送服务请求,并等待请求被处理,等待过程中阻塞停止运行。服务节点收到请求后立即处理,处理完成后向请求节点反馈处理结果。请求节点收到回复后继续运行。),一对多(一个服务只能由一个服务节点提供,可以向多个请求节点提供服务)。适用于偶尔调用的逻辑处理功能或具体的任务(如开关传感器、运动学求解等)。

服务通信过程如下图:

服务的通信过程(假设节点1为服务节点,节点2为请求节点,节点1先启动):

  • 1、节点1向master注册,包括发布话题名称、节点所在地址等;
  • 2、节点2向master注册,包括订阅话题名称、节点所在地址等;
  • 3、master将注册列表中的信息进行匹配,匹配完成后将节点1的地址发送给节点2;
  • 4、请求节点与服务节点建立TCP连接并发送请求数据;
  • 5、服务节点处理请求数据并反馈应答数据。

其中,前三个步骤采用XMLRPC的通信协议;后面两个步骤采用TCP通信协议。

ros中用于服务操作的工具是rosservice,支持参数如下:

  • rosservice list 列出当前存在的服务
  • rosservice info service_name 输出服务信息,包括服务节点名称、服务的ROSRPC uri、服务类型(即服务格式的名称)、服务的参数
  • rosservice call service_name request_param 调用服务
  • rosservice find srv_type 根据服务类型查找服务
  • rosservice args service_name 打印服务的参数
  • rosservice type service_name 输出服务的类型
  • rosservice uri service_name 输出服务的ROSRPC uri

5、动作

服务只能用于处理短时间的逻辑处理任务请求,对于有些长时间的任务不太适用。比如要让小车向前走十米,这个任务可能需要执行较长的时间,对于客户端来说就需要等待很久才能有结果。而我们希望服务器能够在执行任务的时候能够提供执行状态的反馈。甚至有的时候我们需要取消这个任务,转而前往另一个目标点。

为了满足此类任务的通信需求,action应运而生,它是一种升级版的service。action也是基于client、server模型,但这里的client和server之间是通过actionlib定义的“action protocol”进行通信。这种通信协议是基于ros的话题消息机制实现的。

action类似于service,区别是action带有持续的状态反馈,并且可随时取消。通常用于长时间、可抢占(即可以被打断,执行到一半可以去做其他的事情)的任务中。

action的运行机制为,客户端向服务器发布任务目标(goal),并随时可以取消任务(cancel)。服务器接受到任务目标后就会开始执行任务,并通知客户端当前服务器的状态(status),同时还会周期性的反馈任务执行过程中机器人实时的状态(feedback),最后当任务执行完成后返回执行结果(result)。

6、消息类型和服务类型

ros使用了一种简化的类型描述语言来描述ros节点发布的数据。通过这种描述语言,ros能够使用多种编程语言编写不同节点,并实现节点间通信。一个典型的msg文件如下:

int32 id

float32 vel

string name

ros提供了很多预定义的消息类型,我们也可以自定义消息以及服务类型。ros所使用的标准数据类型如下:

基本类型 序列化 C++ Python
bool(1) unsigned 8-bit int uint8_t(2) bool
int8 signed 8-bit int int8_t int
uint8 unsigned 8-bit int uint8_t int(3)
int16 signed 16-bit int int16_t int
uint16 unsigned 16-bit int uint16_t int
int32 signed 32-bit int int32_t int
uint32 unsigned 32-bit int uint32_t int
int64 signed 64-bit int int64_t long
uint64 unsigned 64-bit int uint64_t long
float32 32-bit IEEE float float float
float64 64-bit IEEE float double double
string ascii string(4) std::string string
time secs/nsecs signed 32-bit ints ros::Time rospy.Time
duration secs/nsecs signed 32-bit ints ros::Duration rospy.Duration

在ros的消息类型中,有一种特殊的消息类型是std_msgs/Header,叫做报文头。报文头其实是由三个字段组成,结构如下:

uint32 seq

time stamp

string frame_id

报文头主要用于添加消息序号、时间戳、坐标位置(frame_id可以是0或者1;0表示:no frame,1表示:global frame)。消息是可以嵌套的,报文头就常常嵌套在自定义消息中使用。

ros使用命令行工具rosmsg来获取有关消息的信息:

  • rosmsg list 列出所有消息名
  • rosmsg md5 msg_name 输出一条消息数据的MD5求和结果
  • rosmsg package package_name 列出功能包中所有的消息
  • rosmsg packages  列出所有具有消息文件的功能包
  • rosmsg show msg_name 显示一条该消息的数据
  • rosmsg users  msg_name 搜索使用该消息类型的代码文件
  • rosmsg info msg_name 输出消息数据格式

ros使用命令行工具rossrv来获取有关服务的信息,支持的参数和rosmsg相同。

 


 

开源社区级

主要指ros的相关资源:

1、系统发行版(Distribution)

2、软件源(Repository)

3、ROS wiki(维基手册)

4、ROS Answers(咨询ros相关问题的网站)

5、博客

6、邮件列表(Mailing list):交流ros更新的主要渠道

 


 

OK,到这里我们应该对ros有一个总体的认识了,相关的命令需要一些练习去熟练掌握,下次我们将更加深入的介绍ros的基础编程方法。