欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

3_ROS创建工作空间和功能包

程序员文章站 2022-07-14 22:13:00
...

3_ROS创建工作空间和功能包

前面我们讲解了ROS中的核心概念和使用ROS进行开发时候必须用到的命令行工具

下面我们就正式开始ROS中的开发,我们首先从创建工作空间和功能包开始

1.工作空间WorkSpace

工作空间是ROS中非常重要的概念,我们进行开发首先就需要明白什么是工作空间,然后才能进行开发

什么是工作空间?

我们通常称当前开发的项目为一个工程
举例来说,加入我们现在要制作一个APP,那么这个APP的制作就是一个工程

工作空间指的是存放工程开发相关文件的文件夹,其中包含了工程的所有内容,例如源代码,图标文件等等
同样,在ROS中,我们所有的源码,所有的可执行文件都放在工作空间中

编译系统

通常来说,一个可执行文件的生成需要两步: 编译和链接

编译指的是将本质是文本的源代码编译成可以重定向的二进制文件,想到与最终可执行文件的部分
链接指的是将不同的可重定向二进制文件按照顺序链接在一起,最终生成可执行的二进制文件

编译并不是生成的难点,链接才是生成的难点

我们最初在学编程时候编写的代码由于很少,因此很容易就能进行链接,而且往往只有几个可重定向的二进制文件

但是随着能力的提升,我们开发的项目也越来越大,这个时候想要编译完整的项目可能需要链接高达几百甚至上千的可重定向二进制文件
而且所有的可重定向二进制文件之间的逻辑关系不能乱,必须按照顺序依次链接
例如A依赖于B,那么就要先链接B再链接A

这个时候我们如果不对源代码进行规划的话在编译的时候就会很难受,因此我们在工程开始之前就规定好项目的文件夹划分,使得内容不乱套.

make编译系统

前面讲到大工程的链接问题,实际上为了解决链接时候困难,Linux中提供了make命令

在开发一个系统时,一般是将一个系统分成几个模块,这样做提高了系统的可维护性,但由于各个模块间不可避免存在关联,所以当一个模块改动后,其他模块也许会有所更新,当然对小系统来说,手工编译连接是没问题,但是如果是一个大系统,存在很多个模块,那么手工编译的方法就不适用了。为此,在Linux系统中,专门提供了一个make命令来自动维护目标文件,与手工编译和连接相比,make命令的优点在于他只更新修改过的文件(在Linux中,一个文件被创建或更新后有一个最后修改时间,make命令就是通过这个最后修改时间来判断此文件是否被修改),而对没修改的文件则置之不理,并且make命令不会漏掉一个需要更新的文件。

文件和文件间或模块或模块间有可能存在倚赖关系,make命令也是依据这种依赖关系来进行维护的,所以我们有必要了解什么是依赖关系;make命令当然不会自己知道这些依赖关系,而需要程序员将这些依赖关系写入一个叫Makefile的文件中。Makefile文件中包含着一些目标,通常目标就是文件名,对每一个目标,提供了实现这个目标的一组命令以及和这个目标有依赖关系的其他目标或文件名。

Cmake编译系统

尽管make命令很好用,但是随着项目的增大,我们编写makefile的难度也会越来越大,因此要不断地编写模块之间的依赖关系,为此产生了Cmake工具. Cmake可以帮助我们输出makefile文件,然后我们再使用make命令编译.我们只需要简单的语句就能够描述编译过程

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为 CMakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是 CMake 和 SCons 等其他类似系统的区别之处

所以我们在Linux平台下使用cmake的流程如下:

  1. 编写Cmake配置文件CmakeList.txt
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile。其中, PATH 是 CMakeLists.txt 所在的目录
  3. 使用make命令进行编译

Catkin编译系统

ROS中使用Catkin编译系统,Catkin编译系统实际上集成了Cmake和make的功能,只需要一条命令就能够生成可执行文件

在进行机器人开发的时候通常一个功能包会有很多依赖,使用Catkin编译系统就能够很轻松的进行编译

Catkin的工作流程

Catkin的工作流程如下

3_ROS创建工作空间和功能包

Catkin的工作条件

Catkin在编译的时候必须具备两个条件

  1. 描述包的xml文件 : package.xml
  2. 构建package的Camke文件 : Cmakelist.txt

Catkin的工作原理

  1. 首先在当前工作空间下 (具体是catkin_ws/src) 递归查找所有的ROS Package
  2. 由于每个Package中都会有package.xml和CamkeLists.txt, Catkin依据这两个文件生成Makefile.txt文件,放在catkin_ws/build文件夹下
  3. 根据生成的Makefile文件生成可执行文件,放在catkin_ws/devel

catkin_ws, catkin_ws/src, catkin_ws/build, catkin_ws/devel具体干什么用的文件夹,将在后面给出

ROS工作空间的结构

ROS中的工作空间下存在四个文件夹,作用如下

  • src : 代码空间, Source Space. 其中存放着源代码,配置文件,lauch文件等文件
  • build : 编译空间, Build Space. 其中存放着编译过程中产生的中间文件,例如动态链接库和静态链接库等等
  • devel : 开发空间, Development Space. 其中存放着编译完成之后生成的可执行文件,包括一些库和脚本
  • install : 安装空间, Install Space. 其中存放着我们安装的一些第三方命令

注意,devel和intall其实有很多内容重复了,因此在ROS2中对这两个文件进行了修改,去掉了devel,只剩下install

ROS整个工作空间的结果如下: (我们一般称当前的工作空间名字为catkin_ws,实际上名字无所谓,可以随便换)

3_ROS创建工作空间和功能包

创建工作空间

ROS中创建工作空间通常包含如下几步: 1.初始化工作空间 2.编译工作空间 3.设置环境变量 4.检查环境变量

初始化工作空间

初始化工作空间我们首先需要创建工程项目文件夹,然后在src中运行catkin_init_workspace命令

mkdir -p 工作空间名/src
cd 工作空间名/src
catkin_init_workspace

3_ROS创建工作空间和功能包

编译工作空间

我们在src目录下编写完代码之后想要进行编译,我们首先需要回到主文件夹,然后执行catkin_make命令

cd ~/catkin_ws/
catkin_make

3_ROS创建工作空间和功能包

编译成功之后,就会生成build和devel文件夹,并且在src下会出现一个CmakeLists.txt文件

3_ROS创建工作空间和功能包

由于我们的Src里什么都没有,因此我们编译之后得到的实际上是空的文件夹

我们这里只得到了三个文件夹,如果我们想要得到install文件夹,需要运行catkin_make intall命令

3_ROS创建工作空间和功能包

设置环境变量

这点在后面功能包讲

检查环境变量

这点也在后面功能包将

2.功能包Package

ROS中我们的开发代码不能直接放在src文件夹下,而是必须以功能包的形式存在,因此我们还需要掌握创建功能包的方法

ROS功能包的结构

一个创建好的ROS功能包的结构如下

3_ROS创建工作空间和功能包

创建功能包

我们需要在src目录下使用catkin_create命令来创建功能包,命令的一般格式如下,其中的depend是指定依赖的其他功能包,通常我们编写功能包的时候都会依赖ROS自带的C++/Python接口,因此我们都会加上ROS的C++和Python功能包, 此外我们自己的功能包通常需要发布message,因此我们也需要依赖ROS自带的message接口

catkin_create_pkg <package_name> [depend1] [depend2] [depend3]

下面我们创建一个功能包试试

aaa@qq.com:~/myFirstRosProject$ cd src/
aaa@qq.com:~/myFirstRosProject/src$ catkin_create_pkg firstpackage rospy roscpp

3_ROS创建工作空间和功能包

我们能看到,成功创建之后src文件夹下就多出来我们创建的功能包,我们进入这个Package

编译功能包

我们上面成功创建了一个功能包,接下来我们要去编译这个功能包,我们其实就是在主文件下调用catkin_make命令来进行编译

我们上面讲过,catkin集成了camke和make,因此可以只对这一个修改过了的功能包进行编译

具体步骤又分为编译和重新初始化环境变量两步

cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash

我们对上面的例子进行编译,结果如下

3_ROS创建工作空间和功能包

第二步重新初始化环境变量的目的是使我们刚编译的功能包能够被查找到,即在当前功能包的路径在环境变量中
我们可以使用echo命令获取下ROS_PACKAGE_PATH这个环境变量的值

echo $ROS_PACKAGE_PATH

3_ROS创建工作空间和功能包

查询功能包的依赖

我们实际上具有两种方式进行查询,第一种通过命令,第二种通过Package.xml文件,这里只讲第一种方式,第二种后面会讲

我们通常是用rospack depends和rospackdepends1命令来进行查询,后面接要查询的包名

rospack depends

rospack depends用于查询某个功能的所有依赖

我们这里查询下我们刚才创建的功能包

rospack depends firstpackage

3_ROS创建工作空间和功能包

我们能看到,实际上在创建一个功能包的时候ROS自动会为其添加一些默认依赖

rospack depends1

rospack depends1用于查询我们在创建的时候指定的依赖

rospack depends1 firstpackage

3_ROS创建工作空间和功能包

3.Package.xml文件的结构

我们上面创建了工作空间和功能包,接下来我们需要了解下Package.xml文件的具体结构

一个完整的Package.xml如下

<package format="2">
	<name>firstpackage</name>
	<version>0.0.0</version>
	<description>The firstpackage package</description>
	<!--  One maintainer tag required, multiple allowed, one person per tag  -->
	<!--  Example:   -->
	<!--  <maintainer email="aaa@qq.com">Jane Doe</maintainer>  -->
	<maintainer email="aaa@qq.com">jack</maintainer>
	<!--  One license tag required, multiple allowed, one license per tag  -->
	<!--  Commonly used license strings:  -->
	<!--    BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3  -->
	<license>TODO</license>
	<!--  Url tags are optional, but multiple are allowed, one per tag  -->
	<!--  Optional attribute type can be: website, bugtracker, or repository  -->
	<!--  Example:  -->
	<!--  <url type="website">http://wiki.ros.org/firstpackage</url>  -->
	<!--  Author tags are optional, multiple are allowed, one per tag  -->
	<!--  Authors do not have to be maintainers, but could be  -->
	<!--  Example:  -->
	<!--  <author email="aaa@qq.com">Jane Doe</author>  -->
	<!--  The *depend tags are used to specify dependencies  -->
	<!--  Dependencies can be catkin packages or system dependencies  -->
	<!--  Examples:  -->
	<!--  Use depend as a shortcut for packages that are both build and exec dependencies  -->
	<!--    <depend>roscpp</depend>  -->
	<!--    Note that this is equivalent to the following:  -->
	<!--    <build_depend>roscpp</build_depend>  -->
	<!--    <exec_depend>roscpp</exec_depend>  -->
	<!--  Use build_depend for packages you need at compile time:  -->
	<!--    <build_depend>message_generation</build_depend>  -->
	<!--  Use build_export_depend for packages you need in order to build against this package:  -->
	<!--    <build_export_depend>message_generation</build_export_depend>  -->
	<!--  Use buildtool_depend for build tool packages:  -->
	<!--    <buildtool_depend>catkin</buildtool_depend>  -->
	<!--  Use exec_depend for packages you need at runtime:  -->
	<!--    <exec_depend>message_runtime</exec_depend>  -->
	<!--  Use test_depend for packages you need only for testing:  -->
	<!--    <test_depend>gtest</test_depend>  -->
	<!--  Use doc_depend for packages you need only for building documentation:  -->
	<!--    <doc_depend>doxygen</doc_depend>  -->
	<buildtool_depend>catkin</buildtool_depend>
	<build_depend>roscpp</build_depend>
	<build_depend>rospy</build_depend>
	<build_depend>std_msgs</build_depend>
	<build_export_depend>roscpp</build_export_depend>
	<build_export_depend>rospy</build_export_depend>
	<build_export_depend>std_msgs</build_export_depend>
	<exec_depend>roscpp</exec_depend>
	<exec_depend>rospy</exec_depend>
	<exec_depend>std_msgs</exec_depend>
	<!--  The export tag contains other, unspecified, tags  -->
	<export>
		<!--  Other tools can request additional information be placed here  -->
	</export>
</package>

去掉所有注释后内容如下

<package format="2">
	<!-- 包名 -->
	<name>firstpackage</name>	
    <!-- 版本号 -->
	<version>0.0.0</version>
    <!-- 包的描述信息 -->
	<description>The firstpackage package</description>
    <!-- 作者信息 -->
	<maintainer email="aaa@qq.com">jack</maintainer>
    <!-- 包的许可证信息 -->
	<license>TODO</license>
		
    <!-- 包所有的依赖 -->
	<buildtool_depend>catkin</buildtool_depend>
	<build_depend>roscpp</build_depend>
	<build_depend>rospy</build_depend>
	<build_depend>std_msgs</build_depend>
    
	<build_export_depend>roscpp</build_export_depend>
	<build_export_depend>rospy</build_export_depend>
	<build_export_depend>std_msgs</build_export_depend>

	<exec_depend>roscpp</exec_depend>
	<exec_depend>rospy</exec_depend>
	<exec_depend>std_msgs</exec_depend>

	<export>
		<!--  Other tools can request additional information be placed here  -->
	</export>
</package>

4.CmakeLists.txt文件的结构

一个完整的CmakeList.txt文件的结构如下

其中的内容是用Cmake的语法编写的

貌似****的markdown不支持makefile的语法高亮,所以我下面用的是c语言的高亮,首先要指明的是在cmake中#后面的都是注释,大概浏览下之后就跳到后面去看具体的解释

cmake_minimum_required(VERSION 3.0.2)
project(firstpackage)

## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
)

## System dependencies are found with CMake's conventions
# find_package(Boost REQUIRED COMPONENTS system)


## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
# catkin_python_setup()

################################################
## Declare ROS messages, services and actions ##
################################################

## To declare and build messages, services or actions from within this
## package, follow these steps:
## * Let MSG_DEP_SET be the set of packages whose message types you use in
##   your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
## * In the file package.xml:
##   * add a build_depend tag for "message_generation"
##   * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
##   * If MSG_DEP_SET isn't empty the following dependency has been pulled in
##     but can be declared for certainty nonetheless:
##     * add a exec_depend tag for "message_runtime"
## * In this file (CMakeLists.txt):
##   * add "message_generation" and every package in MSG_DEP_SET to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * add "message_runtime" and every package in MSG_DEP_SET to
##     catkin_package(CATKIN_DEPENDS ...)
##   * uncomment the add_*_files sections below as needed
##     and list every .msg/.srv/.action file to be processed
##   * uncomment the generate_messages entry below
##   * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)

## Generate messages in the 'msg' folder
# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

## Generate services in the 'srv' folder
# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

## Generate actions in the 'action' folder
# add_action_files(
#   FILES
#   Action1.action
#   Action2.action
# )

## Generate added messages and services with any dependencies listed here
# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )

################################################
## Declare ROS dynamic reconfigure parameters ##
################################################

## To declare and build dynamic reconfigure parameters within this
## package, follow these steps:
## * In the file package.xml:
##   * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
## * In this file (CMakeLists.txt):
##   * add "dynamic_reconfigure" to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * uncomment the "generate_dynamic_reconfigure_options" section below
##     and list every .cfg file to be processed

## Generate dynamic reconfigure parameters in the 'cfg' folder
# generate_dynamic_reconfigure_options(
#   cfg/DynReconf1.cfg
#   cfg/DynReconf2.cfg
# )

###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if your package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES firstpackage
#  CATKIN_DEPENDS roscpp rospy std_msgs
#  DEPENDS system_lib
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

## Declare a C++ library
# add_library(${PROJECT_NAME}
#   src/${PROJECT_NAME}/firstpackage.cpp
# )

## Add cmake target dependencies of the library
## as an example, code may need to be generated before libraries
## either from message generation or dynamic reconfigure
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
# add_executable(${PROJECT_NAME}_node src/firstpackage_node.cpp)

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
#   ${catkin_LIBRARIES}
# )

#############
## Install ##
#############

# all install targets should use catkin DESTINATION variables
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html

## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
# catkin_install_python(PROGRAMS
#   scripts/my_python_script
#   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark executables for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
# install(TARGETS ${PROJECT_NAME}_node
#   RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark libraries for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
# install(TARGETS ${PROJECT_NAME}
#   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )

## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/
#   DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
#   FILES_MATCHING PATTERN "*.h"
#   PATTERN ".svn" EXCLUDE
# )

## Mark other files for installation (e.g. launch and bag files, etc.)
# install(FILES
#   # myfile1
#   # myfile2
#   DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )

#############
## Testing ##
#############

## Add gtest based cpp test target and link libraries
# catkin_add_gtest(${PROJECT_NAME}-test test/test_firstpackage.cpp)
# if(TARGET ${PROJECT_NAME}-test)
#   target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()

## Add folders to be run by python nosetests
# catkin_add_nosetests(test)

我们下面说明其中的部分内容的作用

#说明:Cmake语法中,命令名字不区分大小写,但参数和变量有区分。

cmake_minimum_required(VERSION 2.6)	#声明需求的cmake最低版本
project(ProjName)	#项目名称:非强制性,但最好加上
set(SRC_LIST main.c hello.c)	#用来设置变量:把main.c和hello.c路径传递给变量

add_executable(ProjName FileName.cxx)	#告诉工程生成一个可执行文件,目标文件
add_library(LibName STATIC FileName.cxx)	#告诉工程生成一个静态[STATIC]库文件,目标文件;动态库的话[SHARED]即可

find_package(VTK REQUIRED)	#可以找到库文件的绝对路径

include_directories()	#指明头文件所在的路径
add_subdirectory()	#去子目录寻找新的CMakeList.txt子文件
link_directories()	#指定要链接的库文件的路径
target_link_libraries()	#将目标文件与库文件进行链接,链接静态库所依赖的库