ROS中的文件系统
参考文档:
- Navigating the ROS Filesystem:主要介绍ROS的文件系统概念以及使用 roscd、rosls 和 rospack 这三个命令行工具的方法。
- Creating a ROS Package:主要使用roscreate-pkg或catkin创建新包,以及使用rospack列出包的依赖项。
- Building a ROS Package:主要介绍如何构建一个ros包,以及使用到的工具链。
- Catkin:主要介绍ROS中的包构建工具Catkin
- 中国大学MOOC---《机器人操作系统入门》课程讲义
ROS中的文件系统概念
Prerequisites-准备工作
在这一节中我们通过ROS中的一个内置包ros-noetic-ros-tutorials去了解ROS的文件系统,因此需要确定电脑中是否有这个包,打开终端执行下面的命令,如果没有这个包就会下载。
sudo apt-get install ros-noetic-ros-tutorials
值得说明的是,在这一节中所下载的ros-noetic-ros-tutorials并不是一个单独的包,而是多个教程包的集合,因此在终端中补全代码时,并不会补全出一个ros-noetic-ros-tutorials包出来。
文件系统概念快速概述
总的来说,ROS中的文件系统包括两点,一个是包-Package,一个是清单-Manifests(package.xml)
- Packgae: 包是 ROS 代码的软件组织单元。每个包可以包含库、可执行文件、脚本或其他工件。
- Manifests: 清单是对包的描述。它用于定义包之间的依赖关系,并捕获有关包的元信息,如版本、维护者、许可证等…
文件系统中的工具(rospack, roscd, rosls, Tab completion)
代码被分布在包中进行管理,而使用常规的cd 与 ls命令在终端中寻找包显得十分繁琐,因此ROS推出了一系列的工具来帮助开发者快速定位包。
rospack
详细的说明文档rospack
1.概述
rospack是ROS包管理工具。Rospack部分是dpkg,部分是pkg-config。
rospack的主要功能是遍历ROS_ROOT和ROS_PACKAGE_PATH中的包,读取和解析每个包的manifest.xml,并为所有包组装一个完整的依赖树。
使用这个依赖树,rospack可以回答许多关于包及其依赖关系的查询。常见的查询包括:
- rospack find : 返回被查询包的路径
- rospack depends : 返回被查询包的依赖列表
- rospack depends-on : 返回一个列表,列表中的包是依赖于被查询包的。
- rospack export : 返回构建和链接包所必需的标志
rospack的命令是跨平台的,无论是那个版本的ROS都具有同样的功能。
rospack中的遍历算法
rospack会爬取环境变量ROS_ROOT指定的目录。然后,它按照列出的顺序爬取ROS_PACKAGE_PATH中以冒号分隔的目录,如果目录包含一个名为manifest.xml的文件,则确定该目录为包。 如果找到这样的文件,则认为包含该文件的目录是ROS包,包名等于目录名。一旦找到manifest.xml,遍历就不会进一步下降(即包不能彼此嵌套)。 如果在给定目录中没有找到manifest.xml文件,则搜索每个子目录。如果找到名为rospack_nosubdirs的文件,则阻止此子目录搜索。目录本身仍然会搜索manifest,但不会抓取其子目录。 如果在搜索路径中存在多个同名的包,则找到的第一个包优先。强烈建议您在单独的树中保留相同名称的包,每个包在ROS_PACKAGE_PATH中都有自己的元素。这样,您就可以通过指定ROS_PACKAGE_PATH来确定地控制搜索顺序。在ROS_PACKAGE_PATH的给定元素中的搜索顺序可能会受到文件在磁盘上如何布局的细节的不可预测的影响。
rospack的性能分析
Rospack重新解析manifest.xml文件,并在每次执行时重新构建依赖树。但是,它在ROS_ROOT/.rospack_cache中维护包目录的缓存。当缓存丢失或缓存过期60秒时,此缓存就会更新。您可以通过设置环境变量ROS_CACHE_TIMEOUT来更改这个超时,以秒为单位。将其设置为0.0,以便在每次调用rospack时强制进行缓存重建。 Rospack的性能可能会受到不包含清单文件的非常广泛和/或深度目录结构的不利影响。如果这样的目录位于rospack的搜索路径中,它可能会花费大量时间来爬行它们,结果却发现没有要找到的包。您可以通过在这些目录中创建rospack_nosubdirs文件来防止这种延迟。如果rospack运行得非常慢,您可以使用profile命令,它将打印出20棵最慢的树(或者使用profile—length=N来打印最慢的N棵树)。
rospack_nosubdirs
您可以通过简单地添加一个空的rospack_nosubdirs文件来防止rospack下降到目录中。当您出于性能原因或隐藏另一个版本的代码而想要阻止包树的一部分时,这很有用。 我们建议在检出来自其他代码库的代码的包中创建一个rospack_nosubdirs文件,因为这些包通常会创建大的目录树,如果包被移动或删除,这些目录树不会被清理。 注意:rospack_nosubdirs指令只影响rospack工具。像roslaunch或rosrun这样的工具无法观察到它。
2.用法
rospack工具实现了许多打印ROS包信息的命令。所有这些命令都将它们的结果打印到标准输出。任何错误或警告都到stderr。这种分离确保错误输出不会混淆将rospack作为子进程执行的程序,例如,恢复包的构建标志。
rospack -h
在终端中执行该命令,在ros环境存在的情况下,应当得到和下面一样的结果
USAGE: rospack <command> [options] [package]
Allowed commands:
help
cflags-only-I [--deps-only] [package]
cflags-only-other [--deps-only] [package]
depends [package] (alias: deps)
depends-indent [package] (alias: deps-indent)
depends-manifests [package] (alias: deps-manifests)
depends-msgsrv [package] (alias: deps-msgsrv)
depends-on [package]
depends-on1 [package]
depends-why --target=<target> [package] (alias: deps-why)
depends1 [package] (alias: deps1)
export [--deps-only] --lang=<lang> --attrib=<attrib> [package]
find [package]
langs
libs-only-L [--deps-only] [package]
libs-only-l [--deps-only] [package]
libs-only-other [--deps-only] [package]
list
list-duplicates
list-names
plugins --attrib=<attrib> [--top=<toppkg>] [package]
profile [--length=<length>] [--zombie-only]
rosdep [package] (alias: rosdeps)
rosdep0 [package] (alias: rosdeps0)
vcs [package]
vcs0 [package]
Extra options:
-q Quiets error reports.
If [package] is omitted, the current working directory
is used (if it contains a package.xml or manifest.xml).
我打了一些文字,然后突然意识到并不该在这里写下这么多的文字,如果你想知道某个命令的用法,直接访问引用中的详细文档说明,这要来的更直接也更清晰。 总之关于rospack的说明就写到这里。
roscd
roscd属于rosbash的一部分,在介绍roscd之前我们先介绍rosbash。 rosbash实际上是ros的一个包,而并不是一个独立的bash程序,同样的,rosbash并不只对bash进行支持,他也对zsh和tcsh提供同样地支持,但坦白的来说,效果并不如bash一样明显以及好用。 rosbash包含一些有用的bash函数,并向大量基本ros实用程序添加了Tab补全功能。 roscd允许您使用包名、堆栈名或特殊位置来更改当前的目录。
用法
标准的语法格式是
roscd <package-or-stack>[/subdir]
例如
roscd roscpp
# or
roscd roscpp/include/ros
rosls
rosls同样是rosbash的一部分,rosls允许你像bash中常见的ls一样查看包的内容,而不必打出复杂的路径。 举个例子
rosls roscpp
# or
rosls roscpp/include/ros
Tab补全
最后我们来聊一下Tab补全,Tab补全实际上也是rosbash的一部分,tab补全类似bash终端的补全,用户不必打出完整的包名或者工具名,只需打出前几个即可。
创建一个ROS软件包
软件包的结构
在创建一个软件包之前我们要先了解什么是软件包,在前面我们提到过,包是 ROS 代码的软件组织单元。每个包可以包含库、可执行文件、脚本或其他工件。 首先我们使用git工具,下载一个后面会用到的仿真仓库
git clone https://github.com/6-robot/wpr_simulation.git
cd wpr_simulation
ls
CMakeLists.txt LICENSE README.md advanced config include launch maps media meshes models package.xml rviz scripts src worlds
显然这个仓库里面有很多的东西
- CMakeLists.txt : 这个文件是 CMake 构建系统的核心,用于定义如何构建和链接软件包中的 C++(以及可能的 Python 或其他语言)代码
- LICENSE: lincese是许可证,这个主要是github仓库用于选择开源方式,与ros包没有什么关系
- README.md:在github上对于这个代码仓库的说明文件,主要是介绍这个代码仓库
- advanced: 根据作者的描述,这里主要存放了规划器(用于机器人的路径规划算法),与我们本节要讨论的没有什么关系
- config:主要是作者的一些配置文件,与我们本节要讨论的没有什么关系
- launch: launch一般存放的是ros的launch文件,launch可以看作是一个脚本,通过启动launch我们可以同时启动多个节点,并通过launch文件传递一些参数
- maps: 作者存放的仿真地图
- media: 地图中的一些媒体文件,或者仿真中所使用到的一些图片
- meshes: 作者用来存放一些机器人的三维网格文件,用于仿真
- models: 作者用来存放一些机器人的模型
- package.xml: 用来说明包与包之间的依赖关系
- rviz : rviz可视化数据
- scripts: 作者用来存放Python文件的文件夹
- src: 包的代码文件,主要用于存放节点的源文件,主要是存放Cpp源文件。
- worlds:作者用来存放仿真环境的文件夹
综上,对于一个通用的ros软件包,或者是我们新创建的软件包,所需要的文件分别是
- CMakeLists.txt
- package.xml
- src 而其余的文件或者文件夹,都是因为后面的工作而添加的。
软件包的构建工具——Catkin编译系统
在介绍如何构建ros包之前,我想先介绍一下ros软件包的构建工具— catkin编译系统。 有c语言基础的同学都知道,c代码想要顺利的执行,编译是必不可少的一步。机器所能识别的是二进制数据or机器码。一个C语言程序想要被CPU顺利执行,我们需要使用编译器(比如GCC)将我们的C语言源代码转换(更加具体的描述是编译)为可执行文件。 随着源文件的增加,多个文件之间的协同变得不可避免,而直接使用GCC命令的方式进行编译显得有些效率低下了,于是人们开发了Make帮助编译,然而随着工程量的进一步增大,人们发现Make好像也有点满足不了人们的需求了,这时候Cmak应运而生。 Cmake是make工具的生成器,他简化了编译构建的过程,使得人们可以更加方便的管理大型的工程。而ROS这种体量更大的工程项目,对Cmake进行了一定的扩展,形成了Catkin编译系统,catkin继承了Cmake的优点,同时与ROS紧密相连。 我们可以用下面这张图来理解这几个构建工具之间的关系
Catkin的特点
- Catkin是基于Cmake的编译构建系统,具有以下特点:
- Catkin沿用了包管理的传统结构,像rospack中的find_package(),pkg-config
- 扩展了Cmake:
- 软件包生成后无需安装即可使用
- 自动生成find_package()代码,pkg-config文件
- 解决了多个软件包之间的构建顺序问题
- 一个catkin的软件包必须要包含两个文件:
- package.xml: 包括了package的描述信息
- CMakeLists.txt: 构建package所需的CMake文件
Catkin工作空间
Catkin工作空间是创建、修改、编译catkin软件包的目录。catkin的工作空间,直观的形容就是一个仓库,里面装载着ROS的各种项目工程,便于系统组织管理调用。在可视化图形界面里是一个文件夹。我们自己写的ROS代码通常就放在工作空间中,本节就来介绍catkin工作空间的结构。
初始化catkin工作空间
catkin的工作空间是建立在已有的文件夹上的,因此需要先创建一个文件夹,然后使用catkin_make
初始化工作空间,比如:
# 如果使用的不是容器环境,可以直接在根目录下创建,当然也可以自己选择一个喜欢的路径
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/
# 初始化工作空间
catkin_make
soure devel/setup.bash
你可能会在某些地方看到使用
catkin_init_workspace
指令来实现初始化工作空间的教程bashwhich catkin_init_workspace
返回的路径是 /opt/ros/noetic/bin/catkin_init_workspace, 进入到该目录下,我们会发现catkin_make也在这里,同时不难发现catkin_make和catkin_init_workspace都是一个python文件 catkin_init_workspace中,当确定该目录不是catkin的工作空间时,会调用init_workspace()函数对该路径下的文件夹进行初始化 catkin_make 同样包含了这一步骤,并且调用的函数也是init_workspace(),因此我们可以说,catkin_make同样可以用来初始化catkin工作空间
使用Catkin进行编译
要使用catkin编译一个工程或者是软件包,只需要使用catkin_make
指令,不过值得注意的是,执行catkin_make
时需要位于工作空间目录下,在软件包的子目录下是会失败的。 现在我们来讨论在catkin_make
时, catkin是如何工作的:
- 首先catkin会在工作空间目录下(这里假定为~/catkin_ws)的src文件夹中递归的查找每一个ROS的package
- 我们已经知道一个ROS的package中都会有package.xml和CMakeLists.txt文件, catkin依据CMakeLists.txt文件生成makefiles文件, 存放在catkin_ws/build文件夹下
- 执行make流程,从makefiles文件进行编译,链接生成可执行文件,并存放在catkin_ws/devel文件夹下。
也就是说,catkin将cmake与make指令进行了二次的封装, 是一个可以一键(catkin_make)完成整个编译流程的工具。
CMakeLists.txt
CMakeLists.txt作用
CMakeLists.txt原本是Cmake编译系统的配置文件,用于指定如何构建一个项目。通常来说, CMakeLists.txt是项目构建过程的核心, 它告诉 CMake 如何构建项目中的每个目标,以及如何配置编译器和链接器。 由于catkin编译系统基本沿用了Cmake的编程风格, 并只是针对ROS工程添加了一些宏定义,因此catkin的CMakeLists.txt与Cmake的基本一致。
CMakeLists.txt语法
如果在此之前你从未接触过Cmake的语法,可以参考Cmake实践 catkin中CMakeLists.txt的基本语法都是按照Cmake的, 但也有少量的额外的宏, 总体的结构如下:
cmake_minimum_required() #CMake的最低版本号
project() #项目名称
find_package() #找到编译需要的其他CMake/Catkin package
catkin_python_setup() #catkin新加宏,打开catkin的Python Module的支持
add_message_files() #catkin新加宏,添加自定义Message/Service/Action文件
add_service_files()
add_action_files()
generate_message() #catkin新加宏,生成不同语言版本的msg/srv/action接口
catkin_package() #catkin新加宏,生成当前package的cmake配置,供依赖本包的其他软件包调用
add_library() #生成库
add_executable() #生成可执行二进制文件
add_dependencies() #定义目标文件依赖于其他目标文件,确保其他目标已被构建
target_link_libraries() #链接
catkin_add_gtest() #catkin新加宏,生成测试
install() #安装至本机
CMakeLists.txt实例
我们以ros自带的turtlesim小海龟为例,分析以下这个package中的CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
#CMake至少为2.8.3版
project(turtlesim)
#项目(package)名称为turtlesim,在后续文件中可使用变量${PROJECT_NAME}来引用项目名称turltesim
find_package(catkin REQUIRED COMPONENTS geometry_msgs message_generation rosconsole roscpp roscpp_serialization roslib rostime std_msgs std_srvs)
#cmake宏,指定依赖的其他pacakge,实际是生成了一些环境变量,如<NAME>_FOUND, <NAME>_INCLUDE_DIRS, <NAME>_LIBRARYIS
#此处catkin是必备依赖 其余的geometry_msgs...为组件
find_package(Qt5Widgets REQUIRED)
find_package(Boost REQUIRED COMPONENTS thread)
include_directories(include ${catkin_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
#指定C++的头文件路径
link_directories(${catkin_LIBRARY_DIRS})
#指定链接库的路径
add_message_files(DIRECTORY msg FILES
Color.msg Pose.msg)
#自定义msg文件
add_service_files(DIRECTORY srv FILES
Kill.srv
SetPen.srv
Spawn.srv
TeleportAbsolute.srv
TeleportRelative.srv)
#自定义srv文件
generate_messages(DEPENDENCIES geometry_msgs std_msgs std_srvs)
#在add_message_files、add_service_files宏之后必须加上这句话,用于生成srv msg头文件/module,生成的文件位于devel/include中
catkin_package(CATKIN_DEPENDS geometry_msgs message_runtime std_msgs std_srvs)
# catkin宏命令,用于配置ROS的package配置文件和CMake文件
# 这个命令必须在add_library()或者add_executable()之前调用,该函数有5个可选参数:
# (1) INCLUDE_DIRS - 导出包的include路径
# (2) LIBRARIES - 导出项目中的库
# (3) CATKIN_DEPENDS - 该项目依赖的其他catkin项目
# (4) DEPENDS - 该项目所依赖的非catkin CMake项目。
# (5) CFG_EXTRAS - 其他配置选项
set(turtlesim_node_SRCS
src/turtlesim.cpp
src/turtle.cpp
src/turtle_frame.cpp
)
set(turtlesim_node_HDRS
include/turtlesim/turtle_frame.h
)
#指定turtlesim_node_SRCS、turtlesim_node_HDRS变量
qt5_wrap_cpp(turtlesim_node_MOCS ${turtlesim_node_HDRS})
add_executable(turtlesim_node ${turtlesim_node_SRCS} ${turtlesim_node_MOCS})
# 指定可执行文件目标turtlesim_node
target_link_libraries(turtlesim_node Qt5::Widgets ${catkin_LIBRARIES} ${Boost_LIBRARIES})
# 指定链接可执行文件
add_dependencies(turtlesim_node turtlesim_gencpp)
add_executable(turtle_teleop_key tutorials/teleop_turtle_key.cpp)
target_link_libraries(turtle_teleop_key ${catkin_LIBRARIES})
add_dependencies(turtle_teleop_key turtlesim_gencpp)
add_executable(draw_square tutorials/draw_square.cpp)
target_link_libraries(draw_square ${catkin_LIBRARIES} ${Boost_LIBRARIES})
add_dependencies(draw_square turtlesim_gencpp)
add_executable(mimic tutorials/mimic.cpp)
target_link_libraries(mimic ${catkin_LIBRARIES})
add_dependencies(mimic turtlesim_gencpp)
# 同样指定可执行目标、链接、依赖
install(TARGETS turtlesim_node turtle_teleop_key draw_square mimic
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
# 安装目标文件到本地系统
install(DIRECTORY images
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
FILES_MATCHING PATTERN "*.png" PATTERN "*.svg")
package.xml
package.xml也是一个catkin的package必备文件,它是这个软件包的描述文件,在较早的ROS版本(rosbuild编译系统)中,这个文件叫做manifest.xml,用于描述pacakge的基本信息。如果你在网上看到一些ROS项目里包含着manifest.xml,那么它多半是hydro版本之前的项目了。
package.xml的作用
pacakge.xml包含了package的名称、版本号、内容描述、维护人员、软件许可、编译构建工具、编译依赖、运行依赖等信息。 实际上rospack find、rosdep等命令之所以能快速定位和分析出package的依赖项信息,就是直接读取了每一个pacakge中的package.xml文件。它为用户提供了快速了解一个pacakge的渠道。
package.xml的写法
pacakge.xml遵循xml标签文本的写法,由于版本更迭原因,现在有两种格式并存(format1与format2) 老版本(format1)通常包含以下标签:
<pacakge> 根标记文件
<name> 包名
<version> 版本号
<description> 内容描述
<maintainer> 维护者
<license> 软件许可证
<buildtool_depend> 编译构建工具,通常为catkin
<build_depend> 编译依赖项,与Catkin中的一致
<run_depend> 运行依赖项
新版本(format2)通常包含以下标签:
<pacakge> 根标记文件
<name> 包名
<version> 版本号
<description> 内容描述
<maintainer> 维护者
<license> 软件许可证
<buildtool_depend> 编译构建工具,通常为catkin
<depend> 指定依赖项为编译、导出、运行需要的依赖,是最常用的一个标签
<build_depend> 编译依赖项
<build_export_depend> 导出依赖项
<exec_depend> 运行依赖项
<test_depend> 测试用例依赖项
<doc_depend> 文档依赖项
package.xml的实例
我们还是以ros自带的小海龟turtlesim包中的package.xml为例来分析
<?xml version="1.0"?> <!--本示例为老版本的pacakge.xml-->
<package> <!--pacakge为根标签,写在最外面-->
<name>turtlesim</name>
<version>0.10.2</version>
<description>
turtlesim is a tool made for teaching ROS and ROS packages.
</description>
<maintainer email="dthomas@osrfoundation.org">Dirk Thomas</maintainer>
<license>BSD</license>
<url type="website">http://www.ros.org/wiki/turtlesim</url>
<url type="bugtracker">https://github.com/ros/ros_tutorials/issues</url>
<url type="repository">https://github.com/ros/ros_tutorials</url>
<author>Josh Faust</author>
<!--编译工具为catkin-->
<buildtool_depend>catkin</buildtool_depend>
<!--编译时需要依赖以下包-->
<build_depend>geometry_msgs</build_depend>
<build_depend>libboost-thread-dev</build_depend>
<build_depend>qtbase5-dev</build_depend>
<build_depend>message_generation</build_depend>
<build_depend>qt5-qmake</build_depend>
<build_depend>rosconsole</build_depend>
<build_depend>roscpp</build_depend>
<build_depend>roscpp_serialization</build_depend>
<build_depend>roslib</build_depend>
<build_depend>rostime</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>std_srvs</build_depend>
<!--运行时需要依赖以下包-->
<run_depend>geometry_msgs</run_depend>
<run_depend>libboost-thread-dev</run_depend>
<run_depend>libqt5-core</run_depend>
<run_depend>libqt5-gui</run_depend>
<run_depend>message_runtime</run_depend>
<run_depend>rosconsole</run_depend>
<run_depend>roscpp</run_depend>
<run_depend>roscpp_serialization</run_depend>
<run_depend>roslib</run_depend>
<run_depend>rostime</run_depend>
<run_depend>std_msgs</run_depend>
<run_depend>std_srvs</run_depend>
</package>
使用catkin创建一个ROS的软件包
这里我们依旧假定你的工作空间是catkin_ws 创建一个ROS的package需要在catkin_ws/src路径下, 用到
catkin_create_pkg
命令, 语法如下:
catkin_create_pkg package_name depends
其中package_name是包名, depends是包的依赖, 一个ROS的软件包只能有一个包名, 但是可以有多个依赖。 通常一个ROS包常用的依赖有roscpp
,rospy
,std_msgs
,以此为例, 我们可以在catkin_ws/src下新建一个软件包, 名字就叫做beginner_tutorials
catkin_create_pkg beginner_tutorials roscpp rospy std_msgs
接着我们可以观察一下这个新建的包的结构
user@bad76c4f6f0a:/home/taolin/Documents/Docker-files/ros-noetic/catkin_ws/src/beginner_tutorials$ ls
CMakeLists.txt include package.xml src
其中include文件夹与src文件夹,都是空的,但是catkin_create_pkg
帮你完成了软件包的初始化,填充好了CMakeLists.txt和package.xml,并且将依赖项填进了这两个文件中。
更新包路径
新建完包以后,需要回到工作空间下,执行catkin_make
编译整个工作空间,然后
source /devel/setup.bash
这样更新完包路径后,可以使用roscd, rosls等工具,通过包名进行索引。