2.2 对ROS文件系统的深入解析
ROS文件系统的主要目标是在实现项目构建过程集中化的同时提供足够的灵活性来分散其依赖关系。ROS文件系统(见图2-1)是在ROS中开发项目时最为重要的概念之一,当你付出了一些时间和耐心之后,就会发现它其实很容易掌握,而且也会很快认识到它在管理复杂项目及其依赖性时的作用。
图2-1 ROS文件系统
2.2.1 准备工作
和我们计算机上所使用的操作系统相类似,ROS文件系统也被划分成许多个文件夹(见图2-2)。而且在这些文件夹中包含了描述其功能的文件。
图2-2 ROS文件系统中的文件夹
•ROS功能包(package):ROS中的软件是以功能包的形式进行组织的,这也是ROS操作系统中的基本单元。一个功能包可以包含ROS节点(进程)、ROS独立库、配置文件等部分。这些内容可以在逻辑上被定义为一个完整的软件模块。例如,chapter2_tutorials就是一个简单的功能包。
•ROS功能包清单(package manifest):功能包清单是一个名为package.xml的XML文件,它必须位于功能包文件夹中。该文件中包含了关于功能包的各种信息,例如功能包的名字、版本、作者、维护者、许可证、编译标志以及对其他功能包的依赖项。另外,系统包的依赖关系也应该在package.xml中声明,当你需要在设备上通过源代码来构建包时,这一点是非常必要的。
•ROS消息(message):在ROS框架中,节点(node)之间通过将消息发布到话题(topic)中来实现彼此的异步通信,这里提到的话题是一种简单的数据结构。消息文件的扩展名为.msg,它位于所在功能包的msg文件夹中,其中的内容是一些类型字段。
•ROS服务(service):ROS节点还可以通过系统服务的调用来同步交换请求(request)和响应(response)消息。这些请求和响应消息位于SRV文件夹中,扩展名为.srv。
•ROS元功能包清单(metapackage manifest):虽然元功能包清单package.xml与功能包清单十分类似,但它们是ROS catkin构建系统中的专用包,除了package.xml清单之外,它不包含任何代码、文件或其他项。元功能包定义了Debian打包系统中所使用的虚拟包,提供了一个或者多个相关包的引用。
•ROS元功能包(metapackage):它将多个包组合在一个组中,以实现特定的目的和功能(例如导航任务)。不过在诸如Electric和Fuerte等旧版本的ROS中,它被称为栈(stack),但是后来它被元功能包所取代了。元功能包可以看作一个简单并且易于表示的包栈,例如ROS导航栈(navigation stack)就是一个典型的元功能包实例。
2.2.2 如何完成
现在是时候将所学的知识应用到实践中去了。在这一节中,我们将学习用于在ROS文件系统中实现导航功能的ROS工具,并通过实例来掌握ROS工作空间的设置、功能包和元功能包的创建和构建等操作。我们首先执行以下步骤来创建一个ROS工作空间。
(1)首先我们来建立自己的工作空间。本书中所有使用到的示例代码都会集中放置在这个工作空间中。
(2)使用下面的命令可以获得ROS正在使用的工作空间:
$ echo $ROS_PACKAGE_PATH
这条命令执行的输出结果为:
/opt/ros/kinetic/share:/opt/ros/kinetic/stacks
(3)创建和初始化ROS工作空间的命令为:
$ mkdir -p ~/catkin_ws/src $ cd ~/catkin_ws/src $ catkin_init_workspace
我们已经通过上面的命令创建了一个空的ROS工作空间,其中只包含一个CMakeLists.txt文件。
(4)如果你想要编译这个工作空间的话,可以使用如下命令:
$ cd ~/catkin_ws $ catkin_make
现在我们就可以看到前面make命令创建好的build和devel文件夹了。
(5)使用以下命令来完成配置:
$ source devel/setup.bash
这条命令将从ROS工作空间重新加载setup.bash文件,并覆盖默认配置。
(6)如果希望在打开或者关闭每个命令行(shell)时都拥有相同的效果,我们应该在~/.bashrc脚本末尾添加以下命令:
$ echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc $ echo "source /home/usrname/catkin_ws/devel/setup.bash" >>~/.bashrc
ROS提供了多个用于在文件系统中导航的的命令行工具,我们先来了解其中一些最为常用的。
如果想要获得关于ROS环境中的功能包和栈的信息,例如它们的路径、依赖关系等,我们就可以使用rospack和rosstack工具。类似的,要浏览包和堆栈,并列出它们的内容,可以使用roscd和rosls指令。
例如,我们可以使用以下命令查找到turtlesim功能包的位置:
$ rospack find turtlesim
执行之后的输出结果为:
/opt/ros/kinetic/share/turtlesim
使用如下命令来处理已经安装的元功能包,也会得到类似的结果:
$ rosstack find ros_comm
(7)使用如下的命令可以获得ros_comm元功能包的路径:
/opt/ros/kinetic/share/ros_comm
下面的命令列出了功能包或元功能包中的文件:
$ rosls turtlesim
这条命令执行完之后将会输出如下结果:
cmake images srv package.xml msg
(8)当前工作目录可以使用roscd命令修改:
$ roscd turtlesim $ pwd
下面给出新的工作目录:
/opt/ros/kinetic/share/turtlesim
虽然我们可以手动完成ROS中包创建的操作,但是为了避免这些复杂和烦琐的工作,还是建议你使用catkin_create_pkg这个命令行工具。
例如我们使用以下命令在之前建立的工作空间中创建一个新的包:
$ cd ~/catkin_ws/src $ catkin_create_pkg chapter2_tutorials std_msgs roscpp
这条命令中包括功能包的名称和依赖项,在这个例子中,功能包的依赖项包括std_msgs和roscpp。该命令的语法如下所示:
catkin_create_pkg [package_name] [dependency1] ... [dependencyN]
std_msgs功能包中包含了表示原语数据类型的公共消息类型和其他基本消息结构,例如多维数组(multiarray)。roscpp功能包是ROS的一个C++实现,它提供了一个客户端库,C++程序员可以通过这个库来快速和ROS的话题、服务和参数的接口进行交互。
前面已经提到过了,我们可以使用rospack、roscd和rosls命令来检索ROS功能包的信息。
•rospack profile:它将向我们提供新添加到ROS中功能包的信息,这一点在我们安装新功能包时非常有用。
•rospack find chapter2_tutorials:这条命令可以帮助我们找到chapter2_tutorials功能包的路径。
•rospack depends chapter2_tutorials:这条命令用来检索chapter2_tutorials功能包的依赖项。
•rosls chapter2_tutorials:这条命令用来显示chapter2_tutorials功能包的内容。
•roscd chapter2_tutorials:这条命令用来更改chapter2_tutorials功能包的工作目录。
创建元功能包和创建功能包的步骤十分类似,不过在进行操作时会有一些限制,尤其是像前面提到的特殊情况时应当谨慎处理。当我们完成功能包的创建并编写了一部分代码之后,就需要对这个功能包进行编译(build)了。这个编译过程中不仅需要对用户编写的代码进行编译,还需要对从消息和服务中生成的代码进行编译。
(9)我们接下来要使用工具catkin_make完成功能包的编译操作:
$ cd ~/catkin_ws/ $ catkin_make
我们首先要在工作空间目录中运行catkin_make命令。如果你试图在其他目录中运行这个命令,可能会失败。不过当你在catkin_ws目录中执行它时,却可以获得预期的效果。如下所示,我们使用的catkin_make命令实现了对功能包的编译:
$ catkin_make --pkg <package name>
2.2.3 扩展学习
在本节中,我们将会对ROS文件系统的一些术语进行更深入的了解,这些术语包括ROS工作空间、功能包和元功能包、消息、服务。如果你已经对这部分内容很了解,可以跳过本节直接开始后面部分的学习,不过我还是建议大家通过阅读本节来建立一个更完整的学习体系。
ROS工作空间:工作空间在实质上就是一个含有功能包的文件夹,其中有源文件和配置信息。此外,工作空间中还提供了集中开发的机制。
图2-3显示了一个典型的工作空间。
图2-3 工作空间示例
下面给出了工作空间各个部分的作用。
•build:这个目录为编译空间,在使用cmake和catkin等工具进行编译时存储缓存信息、配置和其他中间文件。
•devel:这个目录为开发空间用来保存编译完成的功能包,这些功能包可以在无须安装的情况下进行测试。
•src:这个目录作为源代码空间,其中包含功能包、工程、克隆功能包等。这个目录中包含的一个最重要文件是CMakeLists.txt,它是我们使用命令“catkin_init_workspace”初始化工作空间时创建的。另外,当我们在工作空间中使用cmake对功能包进行初始化时就会调用到它。
ROS工作空间的另一个有趣功能就是覆盖(overlay)。当我们在使用一个ROS功能包,例如turtlesim时,我们既可以使用预编译的安装版本,也可以使用自己下载源文件再编译生成的开发版本。
ROS允许我们使用任何功能包的开发版本来代替预安装版本。当我们需要使用某个预安装功能包的升级版时,这个功能就非常有用。
目前我们可能对覆盖这个功能还有一些陌生,但是在接下来的章节中,我们很快就要使用到它来创建自定义的插件。
通常我们所研究的ROS系统功能包都具有相同的文件结构。这些功能包的结构如图2-4所示。
图2-4 功能包的结构
•config:这个文件夹中保存了当前ROS包中使用的所有配置文件。它是由用户所创建的,将这个保持配置文件的文件夹命名为config是一个约定俗成的做法。
•include:这个文件夹中包含了在包中需要使用库的头文件。
•scripts:这里面包含的是一些可执行的脚本。这些脚本通常是由BASH、Python或一些其他脚本语言所编写的。
•src:这个文件夹用来存放程序源文件的位置。你可以专门为节点和nodelet创建一个文件夹,也可以根据具体情况给出适合的方案。
•launch:这个文件夹中保存了一个或者多个ROS节点的启动文件。
•msg:这个文件夹中包含了自定义的消息类型文件。
•srv:这个文件夹中存储了服务类型文件。
•action:这个文件夹存储了动作类型文件。
•package.xml:这是功能包清单文件。
•CMakeLists.txt:这是CMake的生成文件。
元功能包是ROS中的一种特殊包,它只包含一个package.xml文件。它将一系列功能包简单地组合成一个逻辑包。在package.xml文件中,元功能包中含有一个export标记。而ROS导航栈(navigation stack)就是一个元功能包的最好实例。我们可以使用如下命令来找到这个导航元功能包(navigation metapackage)。
$ rosstack find navigation $ roscd navigation $ gedit package.xml
图2-5给出了导航元功能包中package.xml文件的内容。我们在这里面可以看到<export>和<run_depend>标签。它们都是包清单中的必要组成部分。
图2-5 package.xml文件截图
ROS使用了一种简化的类型描述语言来描述ros节点发布的数据。通过这种描述语言,ros能够使用多种编程语言(C++、Python、Java或者MATLAB)编写不同节点,并实现节点间的通信。
ROS信息的数据类型描述被保存在ROS包中MSG子目录的.msg文件中。消息定义由两个部分,也就是字段(field)和常量(constant)构成。其中的字段又被分成字段类型和字段名称。字段类型是发送消息的数据类型,字段名称则是发送消息的名称,而常量定义了消息文件中的数值。
下面给出了一个.msg文件的示例:
int32 id float32 speed string name
在ROS中提供了一组可以在消息中使用的标准类型。表2-1给出了这些类型的详细信息。
表2-1 ROS中数据类型的详细信息
消息头(header)是ROS消息中的一种特殊类型,它可以携带诸如时间或戳记、坐标系信息、frame_id、序列号、seq等信息。通过这些信息,消息对于处理它们的ROS节点就是透明的了。
rosmsg命令行工具可以用来获取消息头中的信息:
$ rosmsg show std_msgs/Header
这条命令执行的结果如下所示:
uint32 seq time stamp string frame_id
在接下来的章节中,我们将讨论如何用合适的工具创建消息。
ROS使用简化的服务描述语言描述ROS服务类型。它是直接构建在ROS MSG格式上的,以便在节点之间实现请求—响应通信。服务描述存储在包的SRV子目录中的.SRV文件中。
下面给出了一个服务描述格式的实例:
#Request message type string req --- #Response message type string res
其中被“---”分开的前一部分是请求消息的类型,后一部分是响应消息的类型。在这个实例中,请求和响应都是字符串。
在接下来的章节中,我们将讨论如何使用ROS服务。