ROS知识积累
本文最后更新于 2024年10月16日 晚上
本文主要积累了平时使用到的ROS知识。
代码编译和运行
示范
-
新建工作区
1
2
3
4
5
6
7
8
9
10
11cd youfolder/
mkdir -p catkin_ws/src # catkin_ws可改名
cd catkin_ws
catkin config --init --mkdirs --extend /opt/ros/melodic --merge-devel --cmake-args -DCMAKE_BUILD_TYPE=Release
cd src
catkin_init_workspace # 创建顶层CMakeLists.txt文件,CLion利用此文件打开项目
cd ..
catkin build # 编译生成完整工作区此时,
./catkin_ws
文件夹下有build
、devel
、logs
和src
四个文件夹。 -
编译代码
1
2
3
4
5
6
7
8cd src
# 把代码总放在src文件夹下
git clone https://github.com/HKUST-Aerial-Robotics/VINS-Fusion.git
cd ../ # 退回到catkin_ws文件夹
catkin build # 或 catkin_make。相当于cmake+make
# 编译完成后,在代码运行前,须使用source命令刷新一下工作空间的环境。否则会出现找不到“package XXX not found” 的问题。
source ./catkin_ws/devel/setup.bash加速编译:
-
catkin build
命令会自动执行多线程,使用最快的编译速度。 -
catkin_make -j2 -l2
:- -j2,j 是 job 的意思,代表允许 2 个编译命令同时进行,一般是以 CPU的核心数目的两倍为。
- -l2,l 是 load-average 的意思,代表系统加载的任务数,数目一般与-j的数目保持一致。
注意,此处不仅限于2,可以是2,4,8,这取决于你是几核CPU。
如果编译报错:
catkin clean
相当于rm -r ${build} ${devel}
,但是避免了rm -r
这种危险的操作! -
-
添加环境变量(可选)
代码编译完成,后面需要运行ros命令,为了避免每次打开新的shell都需要source,我们将其添加到bash配置文件中。
1
2
3
4
5
6
7
8
9# 方法一,使用gedit
echo “source youfolder/catkin_ws/devel/setup.bash” >> ~/.bashrc
gedit ~/.bashrc # 注:用gedit打开文件,查看是否加入
# 方法二,使用vim
sudo vi ~/.bashrc # 注:打开.bashrc文件
# 最后一行添加:
source youfolder/catkin_ws/devel/setup.bash
# 注:点击键盘“E”,进入编辑(edit)模式,用“delete”键删除添加的路径注,vim按键:
1
2
3
4
5
6
7
8按Esc。输入 :wq!,保存并退出。
注:ESC”键退出编辑模式,并输入以下命令:
:wq 保存后退出vi,若为:wq!则为强制存储后退出
:w 保存但不退出
:w! 若文件属性为【只读】,强制写入该档案
:q 离开vi
:q! 若曾经修改过档案,又不想存储,使用!为强制离开不存储档案
:e! 将档案还原到最原始状态! -
运行代码
1
2
3
4
5# 示例
# 分别新建三个终端各自按顺序执行
roslaunch vins vins_rviz.launch
rosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_mono_imu_config.yaml
rosbag play ~/SLAM/test_data/MH_04_difficult.bag -
完成。
知识
编译
命令
catkin是由ROS社区开发的元编译系统(meta-buildsystem),最初目的是用于编译ROS社区中多个相互依赖的CMake工程。主要有几个阶段:
-
catkin_make
。刚开始是以catkin_make
为代表的命令行工具,主要是封装一系列的cmake
命令。catkin_make
相当于:1
2
3
4mkdir build
cd build
cmake ../src -DCATKIN_DEVEL_SPACE=../devel -DCMAKE_INSTALL_PREFIX=../install
make -j<number of cores> -l<number of cores> [optional target, e.g. install]但是
catkin_make
自身有些缺陷。比如,没有编译时的错误隔离。 -
catkin_make_isolated
。后来catkin_make_isolated
解决了隔离的问题,但是编译速度还是慢,因为不支持并行编译。 -
catkin tools
(推荐使用)。再后来catkin tools
独立出来,推出一系列python
编写的编译命令。其中catkin build
支持隔离和并行编译。简单来说,如果ros的工作空间 (workspace) 中只有一个ros包(ros package),那么catkin_make和catkin build区别不大;如果一个caktin workspace里有多个ros包,那么catkin build显然是更好的编译工具。因为catkin_make只是几行Cmake和make指令的简写,不管里面有几个包都一起编译了。但是catkin_build会将工作空间里所有的包同时单独(isolated)编译,编译过程互不影响。在编译生成的文件夹中,
catkin_make
会生成一个与src
文件夹中各文件(夹)层级相同的ros包,而catkin build
会生成各自单独的ros包?
catkin_make
-
编译指定的包:
1
2
3catkin_make -DCATKIN_WHITELIST_PACKAGES="package1;package2" # 不会自动编译依赖包,也就是说需要手动添加依赖包。or
catkin_make --only-pkg-with-deps <target_package>
catkin_make -DCATKIN_WHITELIST_PACKAGES="" # 如果想要再回到那种catkin_make 编译所有包的状态 -
不编译指定包。在包的根目录(
CMakeLists.txt
、package.xml
的同级目录)下添加名为CATKIN_IGNORE
的空文件即可。如果有好几个包放在一个文件夹下,再在这个文件夹添加名为CATKIN_IGNORE
的文件,则这个文件夹下所有的包都不会被编译。如果您现在运行
catkin_make
(或任何其他catkin
变体),它应该忽略该目录(以及所有子目录)中的任何包。请注意,如果您在添加CATKIN_IGNORE
之前已经构建了工作区,catkin 仍可能会拾取 pkg,因为它随后会使用build
目录中的一些信息。只需删除build/
和devel/
并重建即可解决此问题。
catkin tools
-
cmake build
。-
cmake build
的输出在catkin_ws/logs/package/build.cmake.log、build.make.log
里。 -
在使用
cmake build
进行编译时,会先调用package.xml
,检测所有的依赖库都编译好了,再编译源码。如果有依赖库编译报错,源码编译就会被Abandoned。所以,当依赖库报错时,可以先不编译整个项目的源码,而是先指定cmake build
编译报错的依赖库。等依赖库的报错解决了,再编译整个项目。 -
不
catkin clean
直接cmake build
只会编译修改过的包?检查到没有修改的包就跳过。 -
跳过包。假设您按照 roslib 构建了每个包,但该包有一个构建错误。修复错误后,您可以再次运行相同的构建命令,但构建动词提供了在这种情况下节省时间的选项。如果从头重新开始,roslib 的依赖项的任何产品都不会被重新构建,但是底层构建系统仍然需要一些时间来验证每个包。但是,可以通过直接跳转到给定的包来跳过这些检查。 您可以使用
--start-with
选项在解决问题后从中断的地方继续构建。1
2cd /tmp/ros_tutorials_ws # Navigate to workspace
catkin build --start-with roslib # Build roslib and its dependents -
编译单个包。
1
2cd /tmp/ros_tutorials_ws # Navigate to workspace
catkin build roslib # Build roslib如果您只对在工作区中构建单个包感兴趣,您还可以使用
--no-deps
选项和包名称。这将跳过包的所有依赖项,构建给定的包,然后退出。1
catkin build roslib --no-deps # Build roslib only
-
不编译指定包。
-
在包的根目录(
CMakeLists.txt
、package.xml
的同级目录)下添加名为CATKIN_IGNORE
的空文件即可。如果有好几个包放在一个文件夹下,再在这个文件夹添加名为CATKIN_IGNORE
的文件,则这个文件夹下所有的包都不会被编译。如果您现在运行
catkin_make
(或任何其他catkin
变体),它应该忽略该目录(以及所有子目录)中的任何包。请注意,如果您在添加CATKIN_IGNORE
之前已经构建了工作区,catkin 仍可能会拾取 pkg,因为它随后会使用build
目录中的一些信息。只需删除build/
和devel/
并重建即可解决此问题。 -
配置参数:
1
2
3catkin config --skiplist baz # 要设置跳过列表,可以调用该命令
catkin config --no-skiplist # 要清除跳过列表,您可以使用 --no-skiplist 选项请注意,您仍然可以通过将其名称显式传递给
catkin build
来在跳过列表和构建列表上构建包。
-
-
-
catkin clean
。相当于rm -r ${build} ${devel}
,但是避免了rm -r
这种危险的操作! -
catkin init
可以初始化workspace,而且初始化后,在workspace下的任何一个子目录里使用catkin工具,都相当于在workspace文件夹下使用,它会自动感知workspace。 -
catkin config
可以查看workspace的结构和参数。 -
catkin config --merge-devel
可以使得每个包编译得到的devel产物都在同一个devel文件夹里,当然也可以用catkin config --isolate-devel
使得各个包的devel分开。 -
catkin list
可以查看工作空间中有哪些ros包。 -
可以通过运行以下命令获取参数的详尽列表:
1
source ./devel/setup.zsh && rosrun ze_vio_ceres ze_vio_ceres_node --help
-
指定使用到的gcc和g++版本:
1
catkin build --cmake-args -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10
编译第三方开源软件需要下载的问题
注意
CmakeList.txt
里有没有指定具体版本。在package.xml
里也可以看到指定的版本。
其实就类似于在系统中
cmake
、make
和make install
的步骤,只不过这里的第三方库是安装在了ROS工作区里被相互调用,catkin clean
后也就删除掉了,而没有安装在系统环境里。也方便使用指定版本的第三方库。
1 |
|
或者,将src
(这个文件是原本解压下载的第三方源码source的地方,具体名称要看CMakeLists.txt
中SOURCE_DIR的设置)中的各个第三方源码都解压好,放到src对应的文件夹中。例如catkin_ws/build/xxx/xxx_src-prefix/src/xxx.tar.gz
。
运行
运行节点
-
roscore
是运行ROS主节点的命令。也可以在另一台位于同一个网络内的计算机上运行它。但是,除了支持多roscore
的某些特殊情况,roscore
在一个网络中只能运行一个。 -
rosrun
是ROS的基本运行命令。它用于在功能包中运行一个节点。1
rosrun PACKAGE_NAME NODE_NAME
-
如果
rosrun
是执行一个节点的命令,那么roslaunch
是运行多个节点的概念。该命令允许运行多个确定的节点。其他功能还包括一些专为执行具有诸多选项的节点的ROS命令,比如包括更改功能包参数或节点名称、配置节点命名空间、设置ROS_ROOT和ROS_PACKAGE_PATH以及更改环境变量等。roslaunch
使用*.launch
文件来设置可执行节点,它基于可扩展标记语言(XML),并提供XML标记形式的多种选项。启动时会检查系统是否已经启动了roscore
,如果没有,会自动启动。1
roslaunch PACKAGE_NAME LAUNCH_NAME
-
当用
roslaunch
命令运行多个节点时,运行中的节点的输出(INFO、ERROR等)不会显示在终端屏幕上,这会使调试变得困难。如果此时添加了--screen
选项,终端上运行的所有节点的输出将显示在终端屏幕上。1
roslaunch my_first_ros_pkg union.launch --screen
-
-
运行
rosbag play
命令需要提前启动roscore
。 -
rosbag play
命令可以放在roscore
或roslaunch
命令最后运行。程序读取不到数据就等着呗。
查看
rqt
界面介绍
打开rqt:
1 |
|
工具在工具栏-Plugins里:
-
Introspection
- Node Graph:节点图
- Package Fraph
- Process Monitor
-
Logging
- Console:序号、消息内容、消息等级、节点、时间戳、主题和代码所在位置
- Logger level:设置消息显示级别
-
Topic-Topic Monitor:包含主题、数据类型、带宽、频率、值
-
Visualization
- Image View:可视化发布的主题
- Plot:根据主题绘制曲线
launch文件编写
-
可视化单个图像主题:
1
2
3
4<launch>
<!-- Display -->
<node name="image_view" pkg="rqt_image_view" type="rqt_image_view" args="/package_name/image_topic"/>
</launch>args
参数指定了rqt_image_view
界面打开时默认显示的主题,其它主题可以在界面的下拉菜单中进行切换。 -
可视化多个图像主题:
-
方法一:
1
2
3
4
5
6
7
8<launch>
<!-- Display -->
<!-- Node for first image -->
<node name="image_view_1" pkg="rqt_image_view" type="rqt_image_view" args="/package_name/image_topic_1"/>
<!-- Node for second image -->
<node name="image_view_2" pkg="rqt_image_view" type="rqt_image_view" args="/package_name/image_topic_2"/>
</launch>这样会打开两个
rqt_image_view
界面分别进行显示。 -
方法二:
首先,编写
*.lanuch
文件的内容为:1
2
3
4<launch>
<!-- Display -->
<node name="rqt_gui" pkg="rqt_gui" type="rqt_gui"/>
</launch>运行
*.launch
文件后打开的界面是rqt
主界面(默认是 工具栏-Plugins-Topic-Topic Monitor)。菜单栏-Plugins-Visualization-Image View,然后为第一个Image View
窗口选择合适的主题。同理,添加第二个Image View
窗口和选择主题。(可选)关闭``rqt主界面窗口,只留下
Image View窗口。调整
Image View窗口的位置和大小。最后,菜单栏-Perspectives-Export导出
*.perspectives`配置文件。将
*.perspectives
配置文件放置在项目文件夹中,修改*.launch
文件:1
2
3
4<launch>
<!-- Display -->
<node name="rqt_gui" pkg="rqt_gui" type="rqt_gui" args="--perspective-file $(find package_name)/launch/visualization.perspective"/>
</launch>这样,再次运行
*.launch
文件时,打开的就是修改好的界面。修改界面后,没找到可以直接保存修改的地方。需要重新导出再覆盖源
*.perspective
文件。
-
命令行
1 |
|
调试
GitHub Copilot
-
我希望能够对ROS程序进行断点调试,但是,程序读取的文件来自rosbag包。那么,如何保证在程序遇到断点暂停时,rosbag包也停止播放?
在ROS中,rosbag包的播放和程序的执行是两个独立的进程,它们之间没有直接的通信机制来同步它们的运行状态。因此,当你在程序中设置断点并使程序暂停时,rosbag包将继续播放,这可能会导致你错过一些重要的数据。
一种可能的解决方案是使用ROS的服务(Service)来控制rosbag的播放。你可以创建一个服务,当你的程序达到断点并暂停时,这个服务会发送一个请求给rosbag,告诉它暂停播放。当你的程序继续运行时,这个服务再次发送请求,告诉rosbag继续播放。
然而,这需要你修改rosbag的源代码,使其能够响应这些服务请求。这可能需要一些ROS编程的经验,并且可能不适用于所有情况。
另一种解决方案是手动控制rosbag的播放。你可以在程序达到断点并暂停时,手动暂停rosbag的播放。然后,在程序继续运行时,再手动让rosbag继续播放。这种方法的缺点是需要人工干预,但它不需要修改任何代码。
最后,你也可以考虑使用一些可视化工具,如rqt_bag,它提供了一个图形界面,可以让你更容易地控制rosbag的播放。你可以在程序达到断点并暂停时,使用这个工具来暂停rosbag的播放。然后,在程序继续运行时,再使用这个工具让rosbag继续播放。
-
对于调试需要读取rosbag包播放的数据来运行的ROS程序,你有哪些推荐的调试方法?
对于需要读取rosbag包播放数据来运行的ROS程序,以下是一些推荐的调试方法:
-
使用ROS的rqt工具:rqt提供了一种可视化的方式来查看和理解ROS系统的运行情况。你可以使用rqt_graph来查看节点和主题的连接情况,使用rqt_plot来绘制主题数据,使用rqt_console来查看节点的日志输出等。
-
使用ROS的rostopic和rosnode命令:这些命令可以帮助你查看当前运行的节点和主题,以及主题的数据。例如,你可以使用
rostopic echo /topic_name
来查看某个主题的数据,使用rosnode info /node_name
来查看某个节点的详细信息。 -
使用GDB或其他调试工具:如果你的ROS程序是用C编写的,你可以使用GDB或其他C调试工具来进行断点调试。你可以在程序的关键位置设置断点,然后逐步执行程序,查看变量的值,理解程序的运行流程。
-
使用print或ROS_INFO输出调试信息:在程序的关键位置输出一些调试信息,可以帮助你理解程序的运行情况。例如,你可以输出某个变量的值,或者输出一些提示信息来表示程序运行到了某个位置。
-
控制rosbag的播放:你可以使用rosbag的命令行工具来控制rosbag的播放。例如,你可以使用
rosbag play --pause bagfile.bag
来暂停播放,然后在需要的时候使用rosbag play --resume bagfile.bag
来恢复播放。这样,你可以在程序的关键位置暂停rosbag的播放,然后逐步执行程序,查看程序处理单个数据的情况。 -
使用rviz进行可视化:rviz是ROS的一个3D可视化工具,可以用来可视化传感器数据、机器人的状态、计划的路径等。通过rviz,你可以更直观地理解程序的运行情况。
以上就是一些推荐的调试方法,具体使用哪种方法,需要根据你的程序的具体情况和你的需求来决定。
-
ROS知识
消息通信术语
可视化:
状态图(graph)。节点、话题、发布者和订阅者之间关系可以通过状态图(graph)直观地表示。它是当前正在运行的消息通信的图形表示。但不能为一次性服务创建状态图。执行它是通过运行rqt_graph功能包的rqt_graph节点完成的。有两种执行命令:
1 |
|
以VINS-Fusion项目为例:
圆圈表示节点,正方形表示话题。
简单来说,在ROS操作系统中,节点、话题、消息、发布者和订阅者之间的关系为:
- 一个节点既可以作为发布者(节点)对外发布(多个)话题,也可以作为订阅者(节点)订阅来自其他(多个)节点的(多个)话题。
- ROS中最小的进程单元就是节点(node),节点之间通过主题(topic)传递消息数据(message)。
每个节点并行运行,有消息就订阅和发布,以互相通信。
计算图是ROS处理数据的一种点对点的网络形式。ROS会创建一个连接到所有进程的网络。在系统中的任何节点都可以访问此网络,并通过该网络与其他节点进行交互,获取其他节点发布的信息,并将自身数据发布到网络上。如下图所示:
-
节点(Node)是各自独立的可执行的文件。
- 如果需要一个可以与其他节点进行交互的进程,则需要创建一个节点,并将此节点连接到ROS网络。
- 在通常情况下,系统包含能够实现不同功能的多个节点。每个节点具备特定的单一的功能。
-
主节点/节点管理器(Master)负责节点到节点的连接和消息通信。
roscore
是它的运行命令,当运行主节点时,可以注册每个节点的名字,并根据需要获取信息。- 没有主节点,就不能在节点之间建立访问和消息交流(如话题和服务)。
- 节点只有在需要注册自己的信息或向其他节点发送请求信息时才能访问主节点并获取信息。通常情况下,不检查彼此的连接状态。
-
参数服务器(Parameter Server)能够使数据通过关键词存储在一个系统的核心位置。
- 通过使用参数,就能在运行时配置节点或改变节点的工作任务。
-
消息(Message)。
-
节点通过消息完成彼此间的通信。消息包含了一个节点发送到其他节点的信息数据。同时也可以基于标准消息开发自定义消息。话题、服务和动作都使用消息。
-
消息可以是简单的数据结构,如整数(integer)、浮点(floating point)和布尔值(boolean),或者是像“geometry_msgs/PoseStamped”一样消息包含消息的简单的数据结构,或者也可以是像 “float32[ ] ranges”或“Point32[10] points”之类的消息数组结构。另外,ROS中常用的头(header、std_msgs/Header)也可以作为消息来使用。这些消息由两种类型组成:字段类型(fieldtype)和字段名称(fieldname)。下图为ROS的基本数据类型:
-
msg文件是用于话题的消息文件,扩展名为
.msg
;srv文件是服务使用的消息文件,扩展名为.srv
;action消息文件是动作中使用的消息文件,它使用.action
扩展名。
-
-
话题(topic)是节点间用来传输数据的总线。
- 每个消息都必须有一个名称来被ROS网络路由。每一条消息都要发布到相应的话题。
- 当一个节点发送数据时,称为该节点正在向话题发布消息。节点可以通过订阅某个话题,接收来自其他节点的消息。
- 一个节点可以订阅某个话题而不需要该节点同时发布该话题。这就保证了消息的发布者和订阅者之间的解耦,发布者和订阅者间完全无需知晓对方的存在。同一个话题可以有多个发布者与订阅者。
-
服务(Service)。
- 在发布话题时,正在发送的数据能够以多对多的方式交互。但当需要从某个节点获得一个请求或答应时,就不能通过话题来实现了。在这种情况下,服务能够运行两个节点间直接进行交互。
- 此外,服务必须有唯一的名称。当一个节点提供某个服务时,所有的节点都可以通过使用ROS客户端库编写的代码与其进行通信。
- 一个服务被分成服务服务器和服务客户端,其中服务服务器只在有请求(request)的时候才响应(response),而服务客户端会在发送请求后接收响应。与话题不同,服务是一次性消息通信。因此,当服务的请求和响应完成时,两个连接的节点将被断开。
-
动作(Action)。
- 是在需要像服务那样的双向请求的情况下,使用的消息通信方式,不同于服务之处是在处理请求之后需要很长的响应,并且需要中途反馈值。
- 动作文件也非常类似于服务,目标(goal)和结果(result)对应于请求和响应。此外,还添加了对应于中途的反馈(feedback)。它由一个设置动作目标(goal)的动作客户端(action client)和一个动作服务器(action server),动作服务器根据目标执行动作,并发送反馈和结果。
- 动作客户端和动作服务器之间进行异步双向消息通信。
-
消息记录包(bag)是一种用于保存和回放ROS消息数据的文件格式。
- ROS提供了可以将bag文件可视化的图形工具:rqt_bag和PlotJuggler。
节点名称(name)
一般规则
ROS服务中使用的节点、话题、消息以及ROS中使用的参数都具有唯一的名称(name):
1 |
|
其中,节点的名称是/node1
。如果用一个没有任何字符的相对形式的bar来声明一个发布者,这个话题将和/bar
具有相同的名字。如果以如下所示使用斜杠(/)字符用作全局形式,话题名也是/bar
。所以下面两句是等价的:
1 |
|
但是,如果使用波浪号(~)字符将其声明为私有,则话题名称将变为/node1/bar
:
1 |
|
简单地两次执行相同节点将导致之前执行的节点因ROS的性质而终止,因为ROS必须具有唯一的名称。用户可以在运行时更改节点的名称,而不需要运行额外的程序或更改源代码。例如,当有前、左、右,三个摄像头,且当多次执行同名的节点时,由于节点名重复,该节点将被重复执行,因此节点会被停止。为了避免这种情况,可以采取用同一个名称运行多个不同的节点的方法。
1 |
|
其中。name选项使用了两个下划线(__
)。选项__ns
、__name
、__log
、__ip
、__hostname
和__master
是运行节点时使用的特殊选项。我们在话题名称选项中使用了一个下划线(_
),如果它是private名称,则在现有名称前加上一个下划线。
命名规则
包资源名称具有严格的命名规则,因为它们经常在自动生成的代码中使用。因此,ROS 包不能包含下划线以外的特殊字符,并且它们必须以字母字符开头。有效的名称具有以下特征:
- 第一个字符是字母字符 ([a-z|A-Z])
- 后续字符可以是字母数字 ([0-9|a-z|A-Z])、下划线 ( _ ) 或正斜杠 ( / )
- 最多有一个正斜杠 ('/')
坐标变换(TF)
ROS中的坐标转换TF在描述组成机器人的每个部分、障碍物和外部物体时是最有用的概念之一。这些可以被描述为位置(position)和方向(direction),统称为姿态(pose)。在此,位置由x、y、z这3个矢量表示,而方向是用四元数(quaternion)x、y、z、w表示。四元数并不直观,因为它们没有使用我们在日常生活中使用的三元数的角度表达方式:滚动角(roll)、俯仰角(pitch)和偏航角(yaw)。但这种四元数方式不存在滚动、俯仰和偏航矢量的欧拉(Euler)方式具有的万向节死锁(gimbal lock)问题或速度问题,因此在机器人工程中人们更喜欢用四元数(quaternion)的形式。因为同样的原因,ROS中也大量使用四元数。当然,考虑到方便,它也提供将欧拉值转换成四元数的功能。
TF中的消息采用以下格式:Header用于记录转换的时间,并使用名为child_frame_id的消息来表示下位的坐标。并且为了表达坐标的转换值,使用transformTranslation.x / transform.translation.y / transform.translation.z / transform.rotation.x / transform.rotation.y / transform.rotation.z / transform.rotation.w 的数据形式描述对方的位置和方向。
1 |
|
文件系统
如上图所示,首先是最顶层的catkin工作空间,它是整个ROS工程中层次最高的概念。工作空间也就是管理和组织ROS工程项目文件的地方。其下主要的一级目录有四个:
./src/
:源空间,存放功能包(package)。功能包是ROS文件系统中组织程序文件的基本单元,也就是catkin编译的基本单元。一个 package 下必须包含CMakeLists.txt
和package.xml
两个文件。./build/
:编译空间。存放CMake和catkin的缓存信息、配置信息和其他中间文件。./devel/
:开发空间。存放编译后生成的目标文件,包括头文件、动态&静态链接库、可执行文件(./catkin_ws/devel/lib/package_name
)等。./install/
:安装空间。开发完成后的安装包。
其中,
- 最顶层的工作空间(可以任意命名,如
catkin_ws
)和src
(必须为src
)文件夹是需要自己创建;build
和devel
文件夹由catkin build
或catkin_make
命令自动创建;install
文件夹由catkin_make install
命令自动创建。
对src
文件夹中内容的详解:
-
./include/
- 项目程序的头文件(夹)(
.h
文件)。
- 项目程序的头文件(夹)(
-
./src/
- 项目源程序文件(夹)(
.c
/.cpp
文件等)。
- 项目源程序文件(夹)(
-
./dependencies.yaml
- 该
dependencies.yaml
文件列出了一个源代码项目在构建和运行时所依赖的外部库。这些库通过catkin
构建系统进行管理,通常用于ROS(Robot Operating System)项目。每一个依赖项提供了三个关键信息:类型、URL和版本。在这个文件中,所有依赖项都是从Git仓库中获取的,且均指定为master
分支。 - 这个文件主要用于配置项目的依赖管理系统,确保所有所需的外部库均能被正确拉取并集成到项目中。这对于确保代码的一致性和可复现性是非常重要的。
- 该
-
CMakeLists.txt
- ROS 构建系统的catkin基本上使用了CMake,因此在功能包目录的
CMakeLists.txt
文件中描述着构建环境。
- ROS 构建系统的catkin基本上使用了CMake,因此在功能包目录的
-
.launch
- 这是一个ROS(Robot Operating System)的启动配置文件,用于启动ROS节点。
-
package.xml
- 此文件是一个ROS(Robot Operating System)软件包的清单文件,用于定义包的基本信息和依赖关系。包含功能包信息的XML文件,描述功能包名称、作者、许可证和依赖包。
- 这个文件主要被ROS用来理解如何构建和维护程序包,并且定义了必要的信息,使得包可以在ROS生态中被正确找到和编译。
-
./config/
- 存放配置文件(
.yaml
等)。
- 存放配置文件(
-
.yaml
- 项目参数配置文件。
-
.cfg
- 一个配置脚本。例如:这个脚本使用
dynamic_reconfigure
功能,来允许实时修改运行中ROS节点的参数。 - 在最后,脚本执行
generate
函数来创建一个用于动态配置的服务,该服务可以在ROS运行时动态调整上述参数。
- 一个配置脚本。例如:这个脚本使用
-
.perspective
- 这是一个JSON格式的配置文件,用于定义一个名为
ESVO
的软件项目中的esvo_time_surface
插件的窗口布局和行为。基于文档结构,这看起来像是为图形用户界面(GUI),如rqt
插件制定的配置。rqt
是一个基于Qt的软件框架,它使得为机器人操作系统(ROS)创建丰富的图形界面变得简单。 - 总的来说,这个配置文件为ESVO项目中的
esvo_time_surface.perspective
插件提供了窗口布局、插件状态和用户界面元素的详细信息。
- 这是一个JSON格式的配置文件,用于定义一个名为
-
.rviz
.rviz
文件是一个用于配置 ROS (Robot Operating System) 可视化工具 RViz 的视图配置文件。RViz 是一个用来对机器人传感器数据、导航状态以及其他信息进行三维可视化的工具。这个文件定义了RViz中的一系列面板、显示项、视图以及工具的属性和设置。
-
./scripts/
- 存放脚本文件(比如Python文件
.py
,shell文件.sh
)。
- 存放脚本文件(比如Python文件
-
此外,还有自定义的通信格式文件,包括消息(
.msg
)、服务(.srv
)以及动作(.action
)。
roslaunch
launch 文件
在.launch
文件中根据XML的编写方式可以实现多种功能,可使用的标签如下所示:
<launch>
指roslaunch
语句的开始和结束。<node>
这是对于节点运行的标签。您可以更改功能包、节点名称和执行名称。<machine>
可以设置运行该节点的PC的名称、address、ros-root和ros-package-path。<include>
您可以加载属于同一个功能包或不同的功能包的另一个launch,并将其作为一个launch文件来运行。<remap>
可以更改节点名称、话题名称等等,在节点中用到的ROS变量的名称。<env>
设置环境变量,如路径和P(很少使用)。<param>
设置参数名称、类型、值等。<rosparam>
可以像rosparam
命令一样,查看和修改load
、dump
和delete
等参数信息。<group>
用于分组正在运行的节点。<test>
用于测试节点。类以于<node>
,但是有可以用于测试的选项。<arg>
可以在launch文件中定义一个变量,以便在像下面这样运行时更改参数。
例如,.launch
文件的内容如下所示:
1 |
|
- 在
<launch>
标签中,描述了使用roslaunch
命令运行节点所需的标签。 <node>
描述了roslaunch运行的节点。选项包括:pkg
:功能包的名称。- 节点的包名称是在ROS的
package.xml
文件中定义的。每个ROS包都有一个package.xml
文件,它包含了包的元数据,如包的名称、版本、维护者、依赖关系等。
- 节点的包名称是在ROS的
type
:实际运行的节点的名称(节点名)。- 这是节点的类型,也就是节点的可执行文件的名称。
- 节点的可执行文件名称是在ROS包的
CMakeLists.txt
文件中定义的。在CMakeLists.txt
文件中,add_executable()
函数用于创建一个可执行文件,target_link_libraries()
函数用于链接必要的库。
name
:这是节点的名称,它在ROS系统中必须是唯一的。- 一般与在主程序中
在ros::init(argc, argv, "node_name");
初始化定义的节点名称相同。 - 与上述
type
对应的节点被运行时,起的名称(运行名)。一般情况下使用与type
相同的名称,但可以根据需要,在运行时更改名称。在上面的示例代码中,topic_publisher
节点已被重命名为topic_publisher1
和topic_publisher2
,而topic_subscriber
节点已被重命名为topic_subscriber1
和topic_subscriber2
。可以由此避免两次执行相同节点导致之前执行的节点因ROS的性质而终止。
- 一般与在主程序中
打开rqt如下图所示:
两个订阅者都在订阅两个发布者的消息。这是因为我们只是改变了节点的名字,而没有改变要订阅的消息的名字。
修改前面所创建的.launch
文件如下:
1 |
|
<group>
是对指定节点进行分组的标签。选项有ns
。这是命名空间(name space),是组的名称,属于该组的节点和消息都包含在由ns
指定的名称中。
再次打开rqt结果如下图所示:
命令行参数
1 |
|
1 |
|
CMakeLists.txt 和 package.xml
话题通信机制、消息队列与多话题回调机制
-
话题通信机制简介:
- 首先,发布节点把消息发布,消息进入Publisher的消息队列,同时通知订阅了该话题消息的Subscriber来取消息。
- 其次,Subscriber来Publisher的消息队列里取消息,但取走的也是最老的消息,因为毕竟这是先入先出的队列。
- 最后,被取走的消息存放入了Subscriber的消息队列中,等待被Callback执行。如果Callback执行很慢,消息越堆越多,最老的消息会逐渐被顶替。
-
消息队列分析:
Publisher和Subscriber设置消息队列,都可以从两点进行分析:
- 话题通信属于异步通信,队列可以存储一定量的历史数据。
- 网络传输异常的时候,队列可以预先进行缓存。
- 为什么需要设置Publisher的消息队列?
- 话题通信属于异步通信,publisher节点发布消息,有多个Subscriber节点进行订阅,因此需要有一个地方对消息进行缓存。
- 网络传输差、延时突然高的时候,可以把消息放在Publisher的队列中进行暂存。
- 为什么要设置Subscriber消息队列?
- Subscriber消息队列提供一边处理数据一边缓存新消息的机制。Publisher和Subscriber不一定在同一台主机上,但是网络的性能时好时坏,如果Subscriber没有消息队列。如果没有Subscriber消息队列,那么程序每次运行Callback函数前都要先通过网络取回消息,然后才能处理,当网络很差的时候就会造成系统的阻塞。
-
使用技巧:
- 当queue_size=0,即ROS消息队列为0时,表示为无限队列长度,内存使用量可以无限增长,因此不推荐使用。
- 当两个queue_size=1时,那么系统不会缓存数据,自然处理的就是最新的消息。
- 当queue_size设置为10或者更多时候,用户更不容易错过发布的消息,适用于与人交互的用户界面的数据展示。
-
多话题回调机制:
主要涉及到两个函数的使用:
ros::spin()
和ros::spinOnce()
。-
ros::spin()
。- 对于
spin
函数,一旦进入spin
函数,它就不会返回了,也不继续往后执行了,相当于它在自己的函数里面死循环了(直到Ctrl+C
或者程序终止的时候才退出)。 - 主要的工作,就是不断的检查回调函数队列里面是否有
callback
函数存在,如果有的话,它就会马上去执行callback
函数。如果没有的话,它就会阻塞,不会占用CPU。
ros::spin()
可以看作r.sleep()=0
的ros::spinOnce()
,即:1
2
3
4while(ros::ok())
{
ros::spinOnce();
}其他方面都可当作
spinOnce()
来理解。 - 对于
-
ros::spinOnce()
。spinOnce
函数执行一次后,接着执行主函数下面的语句。不像spin
函数,进入到自己的内部循环,不往下执行。即可以根据自己的需求设置接收频率。- 每次调用
ros::spinOnce()
都会执行与消息队列中缓存的信息数量相同次数的回调函数,只要回调函数执行够快的话,就能清空队列。 - 当多个subscriber都需要调用回调函数时,则按顺序依次执行各个subscriber的回调函数。例如,订阅的消息队列长度分别为5和3,那么以0.1hz即10s的间隔调用ros::spinOnce()时,两个Topic的消息队列都满了,这时一共会调用8次回调函数,且前6次为交替调用。就是说这些回调函数都是串行执行的。
ros::spinOnce()
可以配合其它函数,一起放在while
循环中处理。也就是说,当程序中除了响应回调函数还有其他重复性工作的时候,那就在循环中做那些工作,然后调用ros::spinOnce()
。
spinOnce()
的主要用法是:1
2
3
4
5
6ros::Rate r(rate); // 设置执行频率。
while (ros::ok())
{
ros::spinOnce();
r.sleep(); // 配合执行频率,sleep一段时间,然后进入下一个循环。
}有一点要注意,在回调函数里执行的时间也是会算在
r.sleep()
中的,如果在回调函数中执行的时间大于r.sleep()
需要延时的时间,那么回调函数就一直执行下去。 -
ros::MultiThreadedSpinner
。MultiThreadedSpinner
的用法是:1
2ros::MultiThreadedSpinner spinner(4); // 数字表示调用的线程数
spinner.spin(); // spin() will not return until the node has been shutdown通过
MultiThreadedSpinner
调用的回调函数就不再是串行执行的了,而是多线程并行执行。 -
总结:
spinOnce()
/spin()
调用时是串行执行所有的回调函数,并且是各个话题按顺序执行的,执行的次数与调用spinOnce()
当时消息队列的元素个数相同。MultiThreadedSpinner
调用的回调函数就不再是串行执行的了,而是多线程并行执行。
-
注意事项:
在单话题回调时,必然只存在一个消息队列。但若是多话题回调,又该如何呢?
- 多个话题的订阅器是否共享一个消息队列?在默认情况下,所有的回调函数都会被分配到一个全局队列中,然后由spin()或其它函数进行处理。也就是说,多话题的订阅的消息是共享一个全局消息队列的。根据上文
spinOnce()
的描述,在执行spinOnce()
时会处理当时在全局队列中所有的消息。若当时队列中存在多个话题消息,那么就会处理多个话题的回调函数。但由于是单线程的,因此处理的过程是串行的。 - 若它们共享一个消息队列,那为什么每个订阅器在初始化时都可以设置队列长度?各个订阅器设置的队列长度是指该订阅话题在整个全局队列中所占的长度。也就是说,全局队列的长度至少不会小于所有话题订阅器设置的队列之和。
- 是否可以自定义设置多个消息队列?可以自定义设置消息队列,
roscpp
中提供了相应函数和类。详情可参照[官方文档](https://wiki.ros.org/roscpp/Overview/Callbacks and Spinning)。
- 多个话题的订阅器是否共享一个消息队列?在默认情况下,所有的回调函数都会被分配到一个全局队列中,然后由spin()或其它函数进行处理。也就是说,多话题的订阅的消息是共享一个全局消息队列的。根据上文
-
-
注意事项:
- 在执行回调函数时,消息队列并不会被锁定住,会继续更新。
- 当回调函数处理时长小于数据发布的时间间隔,数据可以完整地传输。
- 同一平台上,发布与订阅节点之间存在延迟时间:大概为0.2ms。
- 先运行订阅者节点,后运行发布者节点也会存在数据丢失,说明:订阅者与发布者刚建立连接时需要大致0.5s的时间。
- 数据发布到缓存到订阅者队列的时间是很短的,大约为0.5ms,如果对数据实时性要求比较高或只想要处理当前最新的消息时,发布者和订阅者的队列长度均需要设置为1。
- 回调函数处理数据时间过长,subscriber队列数据堆积,并可能导致数据丢失。每次执行回调函数时,会从subscriber队列选取最老的数据。
- 如果我们想完整的接收所有的消息,那就要设置合适的队列长度了。比如消息发送频率为10hz,接收频率为5hz,那订阅队列就至少为2(5x2=10)。
发布者节点和订阅者节点的创建和运行
订阅:
1
2
ros::Subscriber 订阅者 = n.subscribe(订阅的主题, 信息队列大小, 回调函数); # 声明
void 回调函数() # 定义发布:
1
2
3
ros::Publisher 发布者; # 声明
发布者 = n.advertise<消息类型>(发布主题, 信息队列大小);
发布者.publish(要发布的消息); # 定义
函数语法
订阅函数:
1 |
|
其中的参数:
topic
为订阅的节点名,字符串类型。queue_size
为待处理信息队列大小。fp
当消息传入时,可以调用的函数指针,即回调函数。而其中M
是回调函数的不同类型,例如const boost::shared_ptr<M const>& or const M&
。这样的话,我们还可以使用boost::bind()
调用回调函数。
常见的用法:
1 |
|
当我们要使用一个class
里面的回调函数(而不是,例如,使用的是直接定义在main()
外的回调函数),我们需要调用第四个参数来声明使用到的类。例如我们有一个class
:
1 |
|
如果想要调用这个class
里的回调函数,可以使用第四个参数,如下:
1 |
|
如果订阅的函数也在class里面(例如,在类的构造函数里),可以直接使用出参数this
:
1 |
|
创建
topic_publisher.cpp
文件:
1 |
|
topic_subscriber.cpp
文件:
1 |
|
运行
1 |
|
创建和运行服务服务器与客户端节点
创建和运行动作服务器与客户端节点
rosbag
获取当前工作目录下bag文件的信息:
1 |
|
回放:
1 |
|
可视化bag文件:
1 |
|
查询:
1 |
|
编辑:
1 |
|
录制:
1 |
|
编写*.launch
文件:
1 |
|
rosclean
清理文件系统资源(例如日志文件)。
1 |
|
时间戳
知识
在ROS(机器人操作系统)中,时间戳(Timestamp)是一个重要的概念,用于在消息传递和数据同步中跟踪和标记事件的时间。ROS使用一种称为ROS::Time
的数据结构来表示时间戳,通常与ROS消息一起使用。以下是有关ROS时间戳的一些关键信息:
-
数据结构
ROS::Time
是ROS中表示时间戳的数据结构。它包括两个成员变量:sec
(秒)和nsec
(纳秒)。这两个成员变量的组合提供了一个精确的时间戳,精度为纳秒级。例如,ros::Time(1633895107, 123456789)
表示的时间戳为1633895107秒 + 123456789纳秒。 -
时钟来源
在roscpp(C++)和rospy(Python)中,可以使用相应的API来获取当前时间、计算时间差、比较时间戳等。例如在C++中,
ros::Time::now()
获取当前时间戳,在Python中,rospy.get_rostime()
获取当前时间戳。时间信息的来源有两种,系统时间和仿真时间,分别被称为Wall Time和ROS Time,可以通过参数
use_sim_time
来进行选择,当use_sim_time
为true
时选择使用仿真时间,反之使用系统时间。1
2
3
4
5
6
7
8
9# 设置参数use_sim_time:
# 通过节点设置:
# 使用仿真时间
rosparam set use_sim_time true
# 使用系统时间
rosparam set use_sim_time false
# launch文件设置:
<param name="use_sim_time" value="false" />系统时间直接通过访问本地时钟来获得,而仿真时间则需要订阅
/clock
话题的消息获得,每当接收到该话题的消息,时间才会更新,在没有接收到该话题的第一个消息时,当前时间为0,/clock
是通过仿真时间服务器发布的,通常为ros_bag
节点或仿真器。此外,ROS的绝对时间戳
ROS::WallTime
,它不可以被人为修改,不同话题或服务数据的ROS::Time
时间戳会和绝对时间戳ROS::WallTime
有一个差值,如果需要满足较高精度的对齐,需要进行时间补偿。 -
应用
在ROS中,每个消息类型通常一般都包含一个时间戳字段,例如
Header
消息类型包括stamp
字段,用于存储时间戳。这允许节点记录和传递消息的时间信息。使用ROS时间戳,可以实现多个节点之间的数据同步。
-
为什么有时候
ros:time::now()
输出值为0?ROS设置了一个模拟时钟的节点,使用模拟时钟的时候,
now()
返回时间0直到第一条消息在/clock
已经收到,所以当客户端不知道时钟时间时ros:time::now()
输出为0。 -
ros:time::now()
输出的值是什么?ros:time::now()
输出的值与参数use_sim_time
有关。-
use_sim_time
为false
时,ros:time::now()
输出系统时间; -
use_sim_time
为true
时,ros:time::now()
输出输出仿真时间,如果回放bag
则是bag
的时间。
-
-
如何直接使用系统时间?
使用
ros::WallTime::now()
。播放rosbag
时,若参数/use_sim_time
为true
,则此时:ros::WallTime::now()
为当前的真实时间,也就是墙上的挂钟时间,一直在走。ros::Time::now()
为rosbag
当时的时间,是由bag
中/clock
获取的。是仿真时间。
使用仿真时间
你可以通过设置/use_sim_time
参数为true
并在播放rosbag时使用--clock
选项来让ROS使用rosbag的时间,而不是真实时间。这种方法通常用于回放rosbag时的仿真。
首先,你需要在启动你的节点之前,设置/use_sim_time
参数为true
:
1 |
|
或者,在*.launch
文件中指定:
1 |
|
不执行上述操作,时间戳就还是现实时间,不是 rosbag 的时间戳。
然后,当你播放rosbag时,需要添加--clock
选项:
1 |
|
这样,ROS的时间就会同步到rosbag的时间。然后,你可以在你的代码中使用ros::Time::now()
来获取当前的rosbag时间,而不是真实时间。
1 |
|
消息打印
基本用法
ROS的Log等级跟平常的一样:DEBUG、INFO、WARN、ERROR、FATAL。
引入头文件:
1 |
|
-
类似C语言风格(类比
printf
)1
ROS_INFO("position=(%0.2f,%0.2f) direction=%0.2f", msg.x, msg.y, msg.theta);
-
类似C++语言风格(类比
std::cout
)(推荐使用)-
输出多个变量和设置小数点位数
1
ROS_INFO_STREAM( std::setprecision (2) << std::fixed<< " position=(" << msg.x << " ," << msg.y << ")" << "direction=" << msg.theta );
-
为调试信息命名
1
ROS_INFO_STREAM_NAMED( “named_msg”, “INFO named message.” ); // 为这段信息命名,可以更容易知道这段信息来自哪段代码
-
打印一次与定时打印
1
2
3
4ROS_INFO_STREAM_ONCE(recv head1 : -> << std::hex << static_cast<int>((int)buffer_data[0])); // 放在回调函数中确认数据有接收到,但是又不想一直刷屏可以用这个
// ROS_[_STREAM]_ONCE[_NAMED]
ROS_INFO_STREAM_THROTTLE(0.5, "Message print every 0.5s"); // 定时刷屏有时候回调的频率比较高,可以设置慢一点的打印。每0.5s打印一次。
-
示例:
1 |
|
时间戳
1 |
|
输出:
当use_sim_time false
时,输出时间戳为系统时间:
1 |
|
当设置use_sim_time true
时执行程序:
1 |
|
输出信息:第一个为系统时间戳,第二个为消息的输出的时候时钟源的时间,当ros系统设置了use_sim_time
为true
的时候,这个为播放的bag包的时间,没有bag数据包播放时仿真时间为0,会卡住。
如果设置了ros时钟仿真参数,发布一个时间的clock
话题,就可以控制ros系统的时间。找个bag数据包,播放一下,使用仿真时间加 —clock
:
1 |
|
程序继续运行输出如下,时间戳多了仿真时间一项:
1 |
|
但是当发布clock
话题的节点没有运行,会出现什么反应?其他所有节点线程遇到延时,执行rospy.sleep
、rate.sleep
等函数时,会一直卡住,while
循环不跳出。一直等待 ,直到时钟初始化。
另外,你无法直接分辨是哪天,这都是从1970-01-01 00:00:00 UTC开始算起的时间,单位为秒。国内北京时间UTC+8.0所以时间戳是从1970-01-01 08:00:00开始。
要想看懂时间戳,下面就轮到rqt_console
登场了。
使用rosconsole和设置显示级别
ROS的调试信息可以用rosconsole
查看和设置,用法如下:
-
查看节点:
1
rosnode list
-
查看节点的log(例如,节点名为
rosdebugtest
):1
2
3
4
5
6
7
8rosconsole list rosdebugtest
# 输出为:
ros
ros.roscpp
ros.roscpp.roscpp_internal
ros.roscpp.superdebug
ros.rosdebugtest
ros.rosdebugtest.named_msg -
查看log显示级别:
1
2# Usage: rosconsole get
rosconsole get rosdebugtest ros.rosdebugtest.named_msg -
动态设置log的显示级别:
1
2# Usage:rosconsole set <node> <logger> <level>
rosconsole set rosdebugtest ros.rosdebugtest debug或者用图形化界面设置,启动rqt_logger_level
1
rosrun rqt_logger_level rqt_logger_level
找到相应的
node
名,选择loggers
,等级调整,默认为Inf
。 -
在编译时设置源代码的调试级别:
由于编译时默认显示的是INFO级别,因此调试时为了看到Debug信息,需要在编译是设置调试级别。
在
ros::init( argc, argv, “rosdebugtest” );
后加入这一句就行了:1
ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME,ros::console::levels::Debug);
-
设置ros默认输出级别:
更改文件
$ROS_ROOT/config/rosconsole.config
,文件内容如下:1
2
3
4# Set the default ros output to warning and higher
log4j.logger.ros=WARN
# Override my package to output everything
log4j.logger.ros.my_package_name=DEBUG
打开rosconsole界面:
1 |
|
- 中间的
Stamp
列显示的就是转换过后的时间戳。 - 第一行从左到右如图,很清晰,最后一列
Location
可以定位到来自于哪个文件多少行,双击每条消息任意位置可以查看消息的所有详细信息。 - 第二行是配置过滤哪些消息,可根据等级,消息内容,时间范围,节点以及话题来滤除。
- 第三行是配置高亮哪些消息,可根据等级,消息内容,时间范围,节点以及话题来设置。
对于大型项目日志查看和分析很有用了。
RViz官方用户手册
小技巧
.sh
脚本一次性启动多个程序(对应多个终端)
经常开发ros环境的应该知道,当需要运行的节点太多太乱的时候,不如写个脚本直接启动多个节点(对应多个终端)。
基础知识
先介绍一下gnome-terminal
命令的使用:
1 |
|
基本使用
了解上面后我们可以创建属于自己的脚本了:
-
在ROS工作空间(例如,
catkin_ws
文件夹)下创建一个脚本文件,例如runROS.sh
。 -
写入内容,例如:
1
2
3
4
5
6#!/bin/bash
gnome-terminal --window -- bash -c "source ./devel/setup.bash && roslaunch PACKAGE_NAME *.launch; exec bash"
sleep 3s
gnome-terminal --window -- bash -c "source ./devel/setup.bash && rosrun PACKAGE_NAME NODE_NAME *.yaml; exec bash"
sleep 3s
gnome-terminal --window -- bash -c "rosbag play /yourfolder/*.bag; exec bash"需要什么节点按照这个模块添加即可。
-
给脚本文件授予权限:
1
sudo chmod 755 runROS.sh
chmod 755
设置用户的权限为:- 文件所有者可读可写可执行。
- 与文件所有者同属一个用户组的其他用户可读可执行。
- 其它用户组可读可执行。
-
运行脚本文件:
1
2cd catkin_ws
./runROS.sh -
完成。
进阶使用
打开多标签页而不是多终端
修改runROS.sh
文件内容为:
1 |
|
--
经测试好像不支持--tab
,脚本执行没有按照预期来,这里还是要使用-x
或-e
。
命令行使用
\
实现换行,方便排版。
Terminator终端和zsh shell
修改runROS.sh
文件内容为:
1 |
|
这里播放
rosbag
前面需要加上source ./devel/setup.zsh
,否则会报错zsh:1: command not found: rosbag
。
如果
exec zsh
命令未能在命令运行完成后保持终端窗口打开,可以尝试修改terminal的配置,在terminal点右键,选择Profiles->Profile Preferences,然后找到Title and Command,里面有一项When command exits,后面选择为Hold the terminal open。
因为
roscore
和roslanuch
打开ROS节点后不会自行关闭,需要使用快捷键Ctrl+C
手动关闭,所以不会运行到exec zsh
这一步。而且,使用快捷键
Ctrl+C
手动关闭ROS节点后,终端会直接退出,也就是关闭窗口(这个BUG?反而能自动关闭终端窗口,减少了自己的操作?)。如果
Ctrl+C
后不想关闭终端,可以在terminal点右键,选择Profiles->Profile Preferences,然后找到Title and Command,里面有一项When command exits,后面选择为Hold the terminal open。不过即使这样,当前终端也不再能输入命令,只能使用Terminator的功能:水平分割/数值分割来另起一个终端。
提高脚本的扩展性
主要解决播放不同rosbag包的需求,runROS.sh
文件的内容修改如下:
1 |
|
对程序简单地说明:
- 当执行命令
./runROS.sh 1
时,会播放dynamic_6dof.bag
; - 当执行命令
./runROS.sh 2
时,会播放boxes_6dof.bag
。
以此类推,可以只编写使用一个脚本文件,通过输入不同的(简单的)参数,来实现运行不同的配置文件。
Terminator终端实现多标签页
以下是gpt-4的回答:
要在 Terminator 终端中实现您的需求,我们依旧会利用一个 shell 脚本。不过,值得注意的是,Terminator 的命令行界面(CLI)可能没有提供直接的方式来新建一个标签页并在其中运行特定命令。但我们仍然可以通过一些创造性的方法来尝试实现这一需求,比如使用 Terminator 的布局功能或发送按键事件来模拟用户操作。下面提供一个基于布局功能的基础脚本示例,并解释如果可能的话如何利用它来达到目的。
使用 Terminator 的布局功能
首先,你需要创建一个 Terminator 配置,其中预定义了一个具有两个标签页的布局。然而,Terminator 配置通常不允许直接在创建标签页时指定要运行的命令。作为替代,我们可以创建一个脚本,一开始就与预期的标签页中要运行的命令相对应,然后将这些脚本设置为在 Terminator 布局中的对应标签页自动运行。
下面是一个简化的例子,假设你已经有了一个包括两个标签页的 Terminator 布局(我们这里称之为 mylayout),以及如何编写一个 shell 脚本来间接实现你的需求:
步骤 1: 创建脚本来运行你的命令
run_first_command.sh
1 |
|
run_second_command.sh
1 |
|
为这两个脚本文件设置执行权限:
1 |
|
步骤 2: 配置 Terminator 布局
你需要手动编辑 Terminator 的配置文件,通常位于 ~/.config/terminator/config
(如果还没有此文件,首次运行 Terminator 会创建)。你要编辑这个文件,加入一个自定义布局,其配置两个终端分别运行这两个脚本。请参考 Terminator 的官方文档 来学习如何配置布局。
步骤 3: 编写 shell 脚本启动 Terminator
假设你已经按照步骤 2 创建好了名为 mylayout 的 Terminator 布局,接下来,你可以编写一个简单的 shell 脚本来启动这个布局:
1 |
|
这其中,-l
参数指定了要启动的布局名称。
总结:这个解决方案需要几个步骤和一些手动配置,而且不如在原生的 shell 脚本中那样直接和灵活。不过,它提供了一种可行的方式来实现你的需求——在 Terminator 中启动一个布局,该布局包含了运行特定命令的多个标签页。根据你的 Terminator 版本和配置情况,详细步骤可能会有所不同,因此建议查阅最新的 Terminator 文档以获得最准确的指导。
发送按键事件来模拟用户操作
要通过发送按键事件来模拟用户操作,实现在 Terminator 终端新建一个标签页并在其中运行命令的需求,你可以使用 xdotool。xdotool 是一个模拟键盘输入和鼠标活动的工具,可以用来实现自动化的交互操作。请注意,这种方法依赖于具体的窗口管理器和终端行为,可能需要根据你的系统环境微调。
首先,你需要安装 xdotool(如果尚未安装):
1 |
|
接下来,你可以编写一个脚本来完成这个任务。这个脚本将做以下几件事:
打开一个 Terminator 窗口。等待一小段时间,确保 Terminator 启动完成。使用 xdotool 发送按键事件来在 Terminator 中新建一个标签页。在新标签页中执行你的命令。
下面是这样一个脚本的示例:
1 |
|
注意: 这个示例脚本包含许多简化的操作,真实环境下的表现可能会因系统配置不同而有所差异。请务必根据自己的终端快捷键和系统行为进行相应的调整。使用 xdotool 发送命令字符串到终端可能有一些限制,特别是对于复杂的命令。在某些情况下,使用脚本文件是绕过这些限制的一种方法。
多个工作空间同时工作
ROS开发中,有时需要建多个工作空间,将不同的功能包区别开来。但是,多个工作空间会存在相互影响的情况,如何让它们能同时工作且相互不影响?单纯改环境变量是不行的。
假设有两个工作空间/home/user1/work1_ws
,/home/user1/work2_ws
,怎么做呢?
需要在同一个shell窗口下依次执行:
-
在
work1_ws
中执行catkin_make
,编译完成后,执行:1
source /home/user1/work1_ws/devel/setup.bash
-
在
work2_ws
中执行catkin_make
,编译完成后,执行:1
source /home/user1/work2_ws/devel/setup.bash
-
完成后,查看:
1
2echo $ROS_PACKAGE_PATH
# /home/user1/work2_ws /src:/home/user1/work1_ws /src:/opt/ros/kinetic/share两个工作空间可以同时工作了。
-
核心点就是在
catkin_make
时确保另一个工作空间的setup.bash
已经执行。
将ROS的功能包打包为.deb
文件并安装
查看bag包每个主题的大小
新建rosbag_size_by_topic.py
文件并写入以下内容:
1 |
|
运行:
1 |
|
示例输出:
1 |
|
查看bag包的开始和结束时间戳
1 |
|
自定义消息类型
-
新建工作区:
1
2
3
4
5
6
7
8
9
10
11cd youfolder/
mkdir -p catkin_ws/src # catkin_ws可改名
cd catkin_ws
catkin config --init --mkdirs --extend /opt/ros/melodic --merge-devel --cmake-args -DCMAKE_BUILD_TYPE=Release
cd src
catkin_init_workspace # 创建顶层CMakeLists.txt文件,CLion利用此文件打开项目
cd ..
catkin build # 编译生成完整工作区此时,
./catkin_ws
文件夹下有build
、devel
、logs
和src
四个文件夹。 -
克隆catkin_simple:
1
2cd youfolder/catkin_ws/src
git clone https://github.com/catkin/catkin_simple.git -
创建消息类型:
1
2
3
4cd youfolder/catkin_ws/src
mkdir detection_msgs/msg
cd detection_msgs/msg
touch Detection.msg在
Detection.msg
中写入:1
2
3
4
5
6float64 x
float64 y
float64 w
float64 h
int32 class_id
float64 class_confidence -
创建
package.xml
:1
2cd youfolder/catkin_ws/src/detection_msgs
touch package.xml在
package.xml
中写入:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<?xml version="1.0"?>
<package>
<name>detection_msgs</name>
<version>0.0.0</version>
<description>The detection_msgs package</description>
<maintainer email="zeyu.long0121@gmail.com">Zeyu Long</maintainer>
<license>MIT</license>
<buildtool_depend>catkin</buildtool_depend>
<buildtool_depend>catkin_simple</buildtool_depend>
<build_depend>std_msgs</build_depend>
<build_depend>message_generation</build_depend>
<build_depend>roscpp</build_depend>
<build_depend>rosmsg</build_depend>
<run_depend>roscpp</run_depend>
<run_depend>rosmsg</run_depend>
<run_depend>message_runtime</run_depend>
<run_depend>std_msgs</run_depend>
<run_depend>rosbag</run_depend>
</package> -
创建
CMakeLists.txt
:1
2cd youfolder/catkin_ws/src/detection_msgs
touch CMakeLists.txt在
CMakeLists.txt
中写入:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36cmake_minimum_required(VERSION 2.8.3)
project(detection_msgs)
# search for everything we need to build the messages, dont forget the message_generation
find_package(catkin REQUIRED COMPONENTS
message_generation
std_msgs
roscpp
rospy
)
# search for all msg files
FILE(GLOB messages_to_build RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/msg"
"${CMAKE_CURRENT_SOURCE_DIR}/msg/*.msg")
# notify catkin to look at the previously found msg files
add_message_files(
FILES
${messages_to_build}
)
# build the header files from the msg files, and notify catkin about the dependencies
generate_messages(
DEPENDENCIES
std_msgs
)
# export the dependencis of this package for who ever depends on us
catkin_package(
CATKIN_DEPENDS message_runtime std_msgs roscpp rosmsg
)
include_directories(
${catkin_INCLUDE_DIRS}
) -
编译:
1
2cd youfolder/catkin_ws
catkin build -
编译完成后,
-
在
youfolder/catkin_ws/devel/include
中生成了detection_msgs
文件夹,把该文件夹放入你的C++项目中,引用头文件即可使用。1
2#include <ros/ros.h>
#include "detection_msgs/Detection.h" -
在
youfolder/catkin_ws/devel/lib/python2.7/dist-packages
中生成了detection_msgs
文件夹,把该文件夹放入你的Python项目中,导入包,即可引用:1
2
3
4
5import rosbag
import rospy
from std_msgs.msg import String
from std_msgs.msg import Header
from detection_msgs.msg import Detection
-
-
完成。
附:
DetectionArray.msg
:
1
2
3
4
5# This message contains an array of detections
Header header
# an array of detections
Detection[] detections修改生成的
DetectionArray.h
:
1
2
// #include <detection_msgs/Detection.h>
#include "Detection.h"
同时播放多个rosbag
怎样同时播放多个rosbag?它们的播放顺序是怎么样的?
你可以使用rosbag play
命令来播放多个rosbag。如果你在命令行中列出多个rosbag文件,它们将按照你列出的顺序播放。例如:
1 |
|
这将首先播放bag1.bag
,然后是bag2.bag
,最后是bag3.bag
。
然而,这并不会按照时间戳对齐这些rosbag。每个rosbag都会从它自己的开始时间开始播放,这可能会导致时间戳的不连续。如果你需要按照时间戳对齐这些rosbag,你可能需要先将它们合并为一个rosbag,然后再播放。
如果你需要同时播放多个rosbag,你可以在不同的终端窗口中运行rosbag play
命令,或者使用&
在后台运行它们。例如:
1 |
|
这将同时开始播放bag1.bag
和bag2.bag
。但是请注意,这并不会按照时间戳对齐这些rosbag。
按照时间戳对齐,合并rosbag
1 |
|
按照时间戳对齐,同时播放多个rosbag
在ROS中,你可以使用rosbag play
命令的--clock
选项来发布一个/clock
主题,这个主题可以用来同步多个rosbag。然而,rosbag play
命令本身并不支持同时播放多个rosbag并按照时间戳对齐它们。
为了实现这个功能,你可能需要写一个脚本来读取多个rosbag,然后按照时间戳排序所有的消息,最后将排序后的消息发布到相应的主题。这可能需要使用到rosbag
的Python API,以及rospy
库来发布消息。
以下是一个简单的示例,展示了如何实现这个功能:
1 |
|
这段代码首先定义了一个函数play_rosbags
,它接受一个输入rosbag文件列表。然后,它打开每个输入rosbag文件,读取其中的所有消息,并将它们添加到一个列表中。然后,它按照时间戳对所有的消息进行排序,并将排序后的消息发布到相应的主题。
注意,这个函数假设所有输入rosbag文件的时间戳都是正确的,即它们都是相对于同一个时间原点的。如果这不是你的情况,你可能需要在发布每个消息之前调整它的时间戳。
运行多个launch文件
你可以在一个ROS launch文件中使用<include>
标签来启动另一个launch文件。这样,你只需要使用一个roslaunch
命令就可以启动两个launch文件。
以下是如何在launch文件中使用<include>
标签的示例:
1 |
|
在这个例子中,我们使用<include>
标签来包含另一个launch文件。file
属性是要包含的launch文件的路径。你可以使用$(find your_package_name)
来获取你的包的路径。
请注意,被包含的launch文件必须在同一个ROS工作空间中,或者在ROS_PACKAGE_PATH环境变量指定的路径中。
问题与解决
新建的工作空间不在当前路径下/删除工作空间
问题描述:使用catkin config --init
初始化工作空间时,显示的Workspace
的路径不在当前目录下,而是其上级(或以上)路径,
原因:工作空间不能嵌套。当前路径的上级(或以上)路径已经被设置为了工作空间。
解决:
1 |
|
参考链接
catkin build和catkin_make的区别和使用
复习笔记之——ROS(robot operating system)
ros::spin()、ros::spinOnce():使用细节、区别
机器人操作系统ROS(十一):ROS时间戳及其与年月日时分秒毫秒格式的转换