Samxander's home

You shall see the difference now that we are back again!

0%

ROS1初步学习

ROS 1

ROS-Wiki 上,我们可以得知ROS到底是个啥:

简介:ROS 是一个适用于机器人的开源的元操作系统。它提供了操作系统应有的服务,包括硬件抽象,底层设备控制,常用函数的实现,进程间消息传递,以及包管理。它也提供用于获取、编译、编写、和跨计算机运行代码所需的工具和库函数。在某些方面ROS相当于一种”机器人框架“。(复制粘贴的

ROS1的安装

我已经在虚拟机上准备了一个已经换好源的 Ubuntu 20.04 ,我们将在这个系统上运行 ROS。

根据csdn上的教程,我根据我的乌班图版本选择了 ROS Noetic Ninjemys 这个ROS版本

安装完 ros 之后,这篇博客还推荐配置 rosdep ,我也顺手搞定了。(不知道为什么,博主推荐的一键安装失效了,还是我手动配置的)

ROS1 初级教程学习

配置ROS环境

由于我安装的是 ROS Noetic Ninjemys 这个版本,得先输入如下指令才能访问ROS相关的命令:

1
source /opt/ros/noetic/setup.bash

每次都输太麻烦,所以直接使用 vim ~/.bashrc 这条指令,在文件末尾插入上述命令(我用的小鱼一键安装,搞半天像傻子一样打开才发现人家已经在文件末尾写好了)

创建ROS工作空间

我们要使用wiki里面给出的指令创建和构建一个 catkin工作空间 。wiki还告诉我们,catkin_make命令在 catkin工作空间 中是一个非常方便的工具。在工作空间中第一次运行它时,它会在src目录下创建一个 CMakeLists.txt 的链接。

然后继续根据要求,source一下新生成的 setup.*sh 文件,再确定一下那个环境变量包括我当前的工作目录就行了。


ROS 文件系统导览

  • 软件包(Packages):wiki告诉我们,包是ROS代码的软件组织单元,每个软件包都可以包含程序库、可执行文件、脚本或者其他构件。
  • 清单(Manifests):清单是对软件包的描述。他用于定义软件包之间的依赖关系,并记录有关软件包的元信息,比如版本、维护者、许可证之类的。

程序的代码散落在许多ROS包里头,如果使用ls cd 这些来进行查找和导航比较繁琐,因此ROS提供了专门的命令工具来简化这些操作。

rospack

这个命令允许我们获取软件包的有关信息,wiki给我们讲了 find 这个参数:

使用如下代码:

1
$ rospack find roscpp

它输出的是:

1
/opt/ros/noetic/share/roscpp

所以显然,这个选项能够返回软件包的所在路径。

roscd

roscdroscd 组成,所以显然,这个命令可以让我们直接切换目录到某个软件包或者软件包集。

依次输入如下代码:

1
2
$ roscd roscpp
$ pwd

它输出的是:

1
/opt/ros/noetic/share/roscpp
  • 子目录

roscd 也可以切换到一个软件包或软件包集的子目录中。

​ 执行如下代码:

1
2
$ roscd roscpp/cmake
$ pwd

​ 结果如下:

1
/opt/ros/noetic/share/roscpp/cmake
  • roscd log

​ 这个命令会进入存储 ROS 日志文件的目录。

​ 如果没有执行过任何 ROS 程序,系统会报错说该目录不存在。即如下:

1
2
No active roscore
bash: cd: /home/wjz2024302124/.ros/log: No such file or directory

rosls

这个命令允许直接按软件包的名称执行 ls 命令,而不需要繁琐地输入绝对路径。

执行如下代码:

1
$ rosls roscpp_tutorials

输出结果:

1
cmake launch package.xml  srv

Tab 补全

总是输入完整的软件包名称比较繁琐,而一些ROS工具支持Tab补全这个功能

输入如下内容:

1
$ roscd roscpp_tut<<<按TAB键>>>

它会自动补全如下:

1
$ roscd roscpp_tutorials/

然而,当输入的东西不完整,并且有许多软件包以输入的东西作为开头,再次按下TAB键会列出所有以其开头的ROS软件包。


创建ROS软件包

一个catkin软件包的组成

  • 一个符合 catkin 规范的 package.xml 文件,用于提供该软件包的元信息
  • 一个 catkin 版本的 CMakeLists.txt 文件或文件的相关样板
  • 目录(同一个目录下不能有嵌套的或者多个软件包存在)

最简单的软件包看起来像这样:

1
2
3
my_package/
CMakeLists.txt
package.xml

catkin工作空间中的软件包

开发 catkin 软件包的推荐方法是使用 catkin工作空间,但也可以单独开发 catkin 软件包。

创建catkin软件包

先切换到创建的空白catkin工作空间中的源文件空间目录:

1
$ cd ~/catkin_ws/src

然后使用 catkin_create_pkg 命令创建一个名为 beginner_tutorials 的新软件包,这个软件包依赖于 std_msgsroscpprospy

1
$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp

这样搞的话就会创建一个名为 beginner_tutorials 的文件夹,这个文件夹里面包含一个 package.xml 文件和一个CMakeLists.txt 文件,这两个文件都已经部分填写了在执行 catkin_create_pkg 命令时提供的信息。

catkin_create_pkg 命令会要求输入 package_name ,如有需要还可以在后面添加一些需要依赖的其它软件包。

构建一个catkin工作区并生效配置文件

现在咱们在catkin工作区中构建软件包:

1
$ catkin_make

工作空间构建完成后,在 devel 子目录下创建了一个与通常在 /opt/ros/$ROSDISTRO_NAME 下看到的目录结构类似的结构。

要将这个工作空间添加到ROS环境中,需要source一下生成的配置文件:

1
$ . ~/catkin_ws/devel/setup.bash

软件包依赖关系

一级依赖

之前在使用 catkin_create_pkg 命令时提供了几个软件包作为依赖关系,现在我们可以使用 rospack 命令工具来查看这些一级依赖

1
$ rospack depends1 beginner_tutorials

结果是:

1
2
3
roscpp
rospy
std_msgs

诶,这不就是前面咱们加的东西吗?

rospack列出了在运行 catkin_create_pkg 命令时作为参数的依赖包,这些依赖关系存储在 package.xml 文件中。

间接依赖

一个依赖还会有自己的依赖关系,譬如 rospy

1
2
3
4
5
6
7
8
$ rospack depends1 rospy

genpy
roscpp
rosgraph
rosgraph_msgs
roslib
std_msgs

一个软件包可以有相当多间接的依赖关系,使用 rospack 就可以递归检测出所有嵌套的依赖包。

自定义软件包

自定义package.xml

  • 描述标签

    注意到 description 标签 <description>The beginner_tutorials package</description> ,可以将描述信息修改为任何内容。

  • 维护者标签

    1
    2
    3
    4
    <!-- One maintainer tag required, multiple allowed, one person per tag -->
    <!-- Example: -->
    <!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> -->
    <maintainer email="wjz2024302124@todo.todo">wjz2024302124</maintainer>

    从wiki中我们可以知道,这是 package.xml 中的一个重要标签,因为它能够让其他人联系到软件包的相关人员。这里至少需要填写一个维护者,但如果想的话可以添加多个。除了在标签里面填写维护者的名字外,还应该在标签的email属性中填写电子邮件地址。

  • 许可证标签

    1
    2
    3
    4
    <!-- 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>

    在wiki中提到,我们应使用BSD。

  • 依赖项标签

​ 接下来的标签描述了软件包的依赖关系,这些依赖项分为build_dependbuildtool_dependrun_dependtest_depend

    
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
<!-- 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>
  • 最终的 package.xml

    去掉注释和未使用标签即可。


构建ROS软件包

只要安装了这个包的所有系统依赖项,就可以开始编译软件包了。

catkin_make 的用法:

1
$ catkin_make [make_targets] [-DCMAKE_VARIABLES=...]

开始构建软件包

输入以下命令:

1
2
3
4
$ cd ~/catkin_ws/
$ ls src

beginner_tutorials CMakeLists.txt

现在我们可以使用catkin_make来构建

1
$ catkin_make

这样的话就成功构建了一个ROS软件包。


理解ROS节点

图概念

(概念来自wiki)计算图是一个由ROS进程组成的点对点网络,它们能够共同处理数据。

ROS的基本计算图概念:

  • 节点:一个可执行文件,可通过ROS来与其他节点进行通信
  • 消息:订阅或发布话题时所使用的ROS数据类型
  • 话题:节点可以将消息发布到话题,或通过订阅话题来接收消息
  • 主节点:ROS的命名服务,例如帮助节点发现彼此
  • rosout:在ROS中相当于 stdout/stderr
  • roscore:主节点 + rosout + 参数服务器(wiki提到后面会进行介绍)

节点

正如上面提到的,它是一个可执行文件。ROS节点使用ROS客户端库与其他节点通信。节点可以发布或订阅话题,也可以提供或使用服务。

(wiki里面的例子,是把遥控器和机器人当做两个节点来理解)

客户端库

ROS客户端库可以让用不同编程语言编写的节点进行相互通信:

  • rospy=Python数据库
  • roscpp=C++数据库

roscore

wiki提到,这个命令是在运行所有ROS程序前首先要运行的命令

1
$ roscore

我这边并没有按照预期显示,有可能网络配置出问题了,所以我输入了如下代码,保证其能够完成单机配置:

1
2
$ export ROS_HOSTNAME=localhost
$ export ROS_MASTER_URI=http://localhost:11311

最终成功。

rosnode

在保持原来终端开着的情况下,打开一个新终端。 rosnode显示当前正在运行的ROS节点信息。rosnode list命令会列出这些活动的节点:

1
2
3
$ rosnode list

/rosout

表示当前只有一个节点在运行:rosout 。这个节点用于收集和记录节点的调试输出,所以总是运行着的。

rosnode info命令返回的是某个指定节点的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ rosnode info /rosout

--------------------------------------------------------------------------------
Node [/rosout]
Publications:
* /rosout_agg [rosgraph_msgs/Log]

Subscriptions:
* /rosout [unknown type]

Services:
* /rosout/get_loggers
* /rosout/set_logger_level


contacting node http://localhost:35183/ ...
Pid: 11688

wiki表示,这给了我们更多关于rosout的信息,比如说实际上它是发布了一个/rosout_agg话题。

rosrun

这个指令可以让咱们用包名直接运行软件包内的节点,不需要知道包的路径。

它的用法如下:

1
$ rosrun [package_name] [node_name]

wiki指引我们运行一个运行 turtlesim 包中的 turtlesim_node ,跟随wiki的指引,我们能够看到一个蓝底的窗口上有一只乌龟。

咱们还可以通过命令行重新分配名称,例如输入以下代码:

1
$ rosrun turtlesim turtlesim_node __name:=my_turtle

节点的名称就被更改了。


理解ROS话题

先把roscore运行着,然后打开一个新终端就行,输入如下代码:

1
$ rosrun turtlesim turtlesim_node

通过键盘遥控turtle

这时候已经有一个乌龟窗口被打开了,打开一个新终端

1
$ rosrun turtlesim turtle_teleop_key

这时候就可以用方向键来控制乌龟了 (↑是向头指向的方向移动,←→是调整方向)

ROS话题

wiki告诉我们,刚才提到的 turtlesim_node 节点和 turtle_teleop_key 节点之间是通过一个ROS话题来相互通信的。turtle_teleop_key在话题上发布键盘按下的消息,turtlesim订阅该话题以接收消息。

rqt_graph

rqt_graph 用动态的图显示了系统中正在发生的事情。

安装完 rqt_graph 之后,再打开一个终端:

1
$ rosrun rqt_graph rqt_graph

此时会弹出一个新的窗口,把鼠标放在 /turtle1/command_velocity 上方,ROS节点会变为蓝and绿色,话题会变成红色。

wiki告诉我们,turtlesim_nodeturtle_teleop_key 节点正通过一个名为 /turtle1/command_velocity 的话题来相互通信。

rostopic

这个命令可以让我们来获取ROS话题的信息,可以使用 $ rostopic -h 来查看可用的子命令。

rostopic echo

这个命令可以显示在某个话题上发布的数据,用法如下:

1
$ rostopic echo [topic]

现在让我们看看由 turtle_teleop_key 节点发布的“指令、速度”数据。再打开一个终端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ rostopic echo /turtlel/cmd_vel

linear:
x: 2.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0
---
linear:
x: 0.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 2.0

我随便点了两下,就有上述的输出。

再看一下 rqt_graph ,按下左上角的刷新按钮显示新的节点:现在 rostopic echo(红色)现在也订阅turtle1/command_velocity这个话题。

rostopic list

这个命令能够列出当前已经被订阅和发布的所有话题。

咱们继续打开一个终端:(向上面一样,可以通过 $ rostopic list -h 命令打开帮助页面)

1
2
3
4
5
6
7
8
9
10
11
12
13
$ rostopic list -v

Published topics:
* /rosout_agg [rosgraph_msgs/Log] 1 publisher
* /rosout [rosgraph_msgs/Log] 5 publishers
* /turtle1/pose [turtlesim/Pose] 1 publisher
* /turtle1/color_sensor [turtlesim/Color] 1 publisher
* /turtle1/cmd_vel [geometry_msgs/Twist] 1 publisher

Subscribed topics:
* /rosout [rosgraph_msgs/Log] 1 subscriber
* /turtle1/cmd_vel [geometry_msgs/Twist] 2 subscribers
* /statistics [rosgraph_msgs/TopicStatistics] 1 subscriber

如输出所示,这样会列出所有发布和订阅的主题及其类型的详细信息。

ROS 消息

wiki告诉我们,话题的通信是通过节点间发送ROS消息实现的。

为了使发布者(turtle_teleop_key)和订阅者(turtulesim_node)进行通信,发布者和订阅者必须发送和接收相同类型的消息。这意味着话题的类型是由发布在它上面消息的类型决定的。使用rostopic type命令可以查看发布在话题上的消息的类型

rostopic type

这个命令是用来查看所发布话题的消息类型。

用法:

1
$ rostopic type [topic]

根据wiki,我们运行:

1
2
3
$ rostopic type /turtle1/cmd_vel

geometry_msgs/Twist

继续用rosmsg查看消息的详细信息:

1
2
3
4
5
6
7
8
9
10
$ rosmsg show geometry_msgs/Twist

geometry_msgs/Vector3 linear
float64 x
float64 y
float64 z
geometry_msgs/Vector3 angular
float64 x
float64 y
float64 z

所以,现在我们已经知道了 turtlesim 节点想要的消息类型,然后就可以发布命令给 turtle 了。

rostopic pub

这个命令可以把数据发布到当前某个正在广播的话题上。

用法:

1
$ rostopic pub [topic] [msg_type] [args]

继续跟着wiki的命令:

1
$ rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'

根据wiki上的解释,我们得知这个命令会发送一条消息给 turtlesim ,告诉它要以2.0的线速度和1.8的角速度移动。

逐步解析:

  • rostopic pub 将消息发布到指定的话题;
  • -1 这个选项会让 rostopic 只发布一条消息,然后退出;
  • /turtle1/cmd_vel 是要发布到的话题的名称;
  • geometry_mags/Twist 是发布到话题时要使用的消息的类型;
  • -- 用来告诉选项解析器,表明之后的参数都不是选项。如果参数前有破折号(-)比如负数,那么这是必需的;
  • '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]' :如前所述,一个 turtlesim/Velocity 消息有两个浮点型元素:linearangular。在本例中,'[2.0, 0.0, 0.0]'表示 linear 的值为 x=2.0, y=0.0, z=0.0,而'[0.0, 0.0, 1.8]' 是说 angular 的值为 x=0.0, y=0.0, z=1.8。而且根据wiki我们得知,这些参数使用的是YAML语法。

我们发现,小乌龟动了一小下就停止移动了。wiki告诉我们,turtle 需要一个稳定的频率为1Hz的指令流才能保持移动状态。我们可以使用 rostopic pub -r命令来发布源源不断的命令:

1
$ rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'

诶,这样的话小乌龟就一直在动了!

rostopic hz

这个命令呢,是用来报告数据发布的速率的~

用法:

1
$ rostopic hz [topic]

比如,咱们看一下 turtlesim_node 发布 /turtle/pose 的速率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ rostopic hz /turtle1/pose

average rate: 62.477
min: 0.015s max: 0.017s std dev: 0.00050s window: 63
average rate: 62.480
min: 0.015s max: 0.017s std dev: 0.00053s window: 125
average rate: 62.485
min: 0.015s max: 0.017s std dev: 0.00054s window: 188
average rate: 62.493
min: 0.015s max: 0.017s std dev: 0.00054s window: 251
average rate: 62.485
min: 0.014s max: 0.017s std dev: 0.00057s window: 313
average rate: 62.487
min: 0.014s max: 0.017s std dev: 0.00057s window: 376

由上可知,turtlesim 正以大约 60Hz 的频率发布有关乌龟的数据。

rqt_plot

这个命令可以在滚动时间图上显示发布到某个话题上的数据。这里我们将使用 rqt_plot 命令来绘制正被发布到 /turtle1/pose 话题上的数据。

再再再再次打开一个终端:

1
$ rosrun rqt_plot rqt_plot

然后就弹出了一个新窗口,咱们在左上角这个文本框里头添加任何想要绘制的话题

ROS_pic1

咱们输入 /turtle1/pose/x 之后,按一下变亮的加号按钮;对 turtle1/pose/y 重复这样的过程,咱们就能看到龟龟的 x-y 位置力~

按下减号按钮会显示一组菜单,可以在图中隐藏指定的话题。

到此为止,咱们已经理解了ROS话题是如何工作的了~


理解ROS服务和参数

ROS服务

wiki告诉我们,服务 是节点之间通讯的另一种方式,服务允许节点发送一个请求并获得一个响应

rosservice

rosservice 可以通过服务附加到ROS客户端/服务器框架上。

用法:(摘自wiki)

1
2
3
4
5
rosservice list         输出活跃服务的信息
rosservice call 用给定的参数调用服务
rosservice type 输出服务的类型
rosservice find 按服务的类型查找服务
rosservice uri 输出服务的ROSRPC uri

rosservice list

在终端输入这个命令 (注意要打开小乌龟的窗口),它会显示 turtlesim 节点提供了哪些服务,比如 clear 就不一一列举了

rosservice type

clear 服务为例,我们来查看它的类型:

1
2
3
$ rosservice type /clear

std_srvs/Empty

wiki告诉我们,服务的类型为 empty(空),这表明调用这个服务时不需要参数(即,它在发出请求时不发送数据,在接收响应时也不接收数据)。

rosservice call

这个命令就是用来调用服务的,例如:

1
$ rosservice call /clear

在终端输入后,我们不难发现,小乌龟窗口中背景的轨迹被清除了

我们再来试试有参数的服务 spawn

1
2
3
4
5
6
7
8
$ rosservice type /spawn | rossrv show

float32 x
float32 y
float32 theta
string name
---
string name

咱们来观察一下返回的东西:显然,这个服务能让我们可以在给定的位置和角度生成一只新的乌龟,还可以给它起名字。

rosparam

这个命令可以让我们在 ROS参数服务器存储操作数据。

参数服务器能够存储整型、浮点型、布尔、字典和列表这些数据类型。rosparam 使用YAML标记语言的语法。一般而言,YAML的表述很自然,1 是整型,1.0 是浮点型,one 是字符串,true 是布尔型,[1, 2, 3] 是整型组成的列表…

rosparam 有很多命令可以用来操作参数,wiki告诉了我们这些命令的用法:

1
2
3
4
5
6
rosparam set            设置参数
rosparam get 获取参数
rosparam load 从文件中加载参数
rosparam dump 向文件中转储参数
rosparam delete 删除参数
rosparam list 列出参数名

rosparam list

我们使用这个命令之后,可以看到参数服务器上有3个参数用于设定背景颜色:

1
2
3
4
5
6
7
/rosdistro
/roslaunch/uris/host_nxt__43407
/rosversion
/run_id
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r

rosparam set & rosparam get

这俩的用法都是后头加上 [param_name]

比如使用如下命令修改背景颜色:

1
2
$ rosparam set /turtlesim/background_r 150
$ rosservice call /clear #需要调用clear才能让参数的修改得以生效

我们就会发现背景颜色变紫了~

然后我们可以使用 get 来获取其它参数的值,比如获取背景绿色通道的值:

1
2
3
$ rosparam get /turtlesim/background_g

86

我们甚至还能用 rosparam get / 来显示参数服务器上的所有内容

rosparam dump & rosparam load

这俩的用法都是在后头加上 [file_name] [namespace]

比如,我们将所有的参数写入 params.yaml 文件:

1
$ rosparam dump params.yaml

甚至可以将 yaml 文件重载入新的命令空间,比如 copy_turtle:

1
2
3
4
$ rosparam load params.yaml copy_turtle
$ rosparam get /copy_turtle/turtlesim/background_b

255

使用rqt_console和roslaunch

rqt_console & rqt_logger_level

wiki告诉我们,rqt_console 连接到了ROS的日志框架,以显示节点的输出信息。rqt_logger_level 允许我们在节点运行时改变输出信息的详细级别。

rqt_logger_level允许我们在节点运行时改变输出信息的详细级别,包括DebugInfoWarnError

日志记录器级别

日志级别的优先级按以下顺序排列:

1
Fatal  Error  Warn  Info  Debug

wiki告诉我们,通过设置日志级别,咱们可以获得所有优先级级别,或只是更高级别的消息。比如,将日志级别设为Warn时,咱会得到WarnErrorFatal这三个等级的日志消息。

roslaunch

这个命令可以用来启动定义在 launch 文件中的节点,用法:

1
$ roslaunch [package] [filename.launch]

wiki引导我们切换到我们之前的 beginner_tutorials 软件包目录下:

1
$ roscd beginner_tutorials

继续创建一个 launch 目录:

1
2
$ mkdir launch
$ cd launch

launch文件

wiki让我们现在创建一个名为 turtlemimic.launch 的launch文件并复制粘贴这些东西进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 1 <launch>
2 # 首先用launch标签开头,以表明这是一个launch文件

3 <group ns="turtlesim1">
4 <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
5 </group>
6
7 <group ns="turtlesim2">
8 <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
9 </group>
# 此处我们创建了两个分组,并以命名空间(namespace)标签来区分,其中一个名为turtulesim1,另一个名为turtlesim2,两个分组中都有相同的名为sim的turtlesim节点。这样可以让我们同时启动两个turtlesim模拟器,而不会产生命名冲突。

10
11 <node pkg="turtlesim" name="mimic" type="mimic">
12 <remap from="input" to="turtlesim1/turtle1"/>
13 <remap from="output" to="turtlesim2/turtle1"/>
14 </node>
#在这里我们启动模仿节点,话题的输入和输出分别重命名为turtlesim1和turtlesim2,这样就可以让turtlesim2模仿turtlesim1了。

15
16 </launch>
# 这一行使得launch文件的XML标签闭合。

使用roslaunch

咱们通过 roslaunch 命令来运行launch文件:

1
$ roslaunch beginner_tutorials turtlemimic.launch

现在将会有两个turtlesim被启动,然后我们在一个终端中使用rostopic命令发送:

1
$ rostopic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'

诶,咱们就会惊奇地发现,咱们只让 turtle1 运动了,但是 turtle2 也跟随它一起运动了!


使用rosed在ROS中编辑文件

rosed

利用这个命令可以直接通过软件包名编辑包中文件,无需键入完整路径。

用法:

1
$ rosed [package_name] [filename]

Tab补全

使用这个方法,在不知道准确文件名的情况下,也可以轻松地查看和编辑包中的所有文件,用法:

1
$ rosed [package_name] <tab><tab>

示例:

1
2
3
4
5
6
7
8
9
$ rosed roscpp <tab><tab>

Empty.srv roscpp.cmake
genmsg_cpp.py roscppConfig.cmake
gensrv_cpp.py roscppConfig-version.cmake
GetLoggers.srv roscpp-msg-extras.cmake
Logger.msg roscpp-msg-paths.cmake
msg_gen.py SetLoggerLevel.srv
package.xml

编辑器

rosed默认的编译器是vim~


创建ROS消息和服务

msg 和 srv

  • msgmsg 文件是文本文件,用于描述ROS消息的字段。它们用于为不同编程语言编写的消息生成源代码。
  • srv:一个 srv 文件描述一个服务。它由两部分组成:请求和响应。

编写简单的发布者和订阅者 (Python)

编写发布者节点

wiki告诉我们,节点 是连接到ROS网络的可执行文件。我们要创建 talker (发布者) 节点,该节点将不断广播消息。

跟着wiki上的步骤,先创建一个 scripts 目录来存放 Python 脚本,然后下载示例脚本 talker.py 放到 scripts 目录中并给执行权限;最后把相关内容添加到 CMakeLists.txt 里头,就能确保正确安装 Python脚本,并使用合适的 Python 解释器了~

解释

跟着wiki分解一下代码:

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
#!/usr/bin/env python
# (每个Python ROS节点的最开头都有这个声明。第一行确保脚本作为Python脚本执行。)

import rospy
from std_msgs.msg import String
# 如果要编写ROS节点,则需要导入rospy。std_msgs.msg的导入则是为了使我们能够重用std_msgs/String消息类型(即一个简单的字符串容器)来发布。

def talker():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('talker', anonymous=True)
# 这部分代码定义了talker与其他ROS部分的接口。pub = rospy.Publisher("chatter", String, queue_size=10)声明该节点正在使用String消息类型发布到chatter话题。这里的String实际上是std_msgs.msg.String类。queue_size参数是在ROS Hydro及更新版本中新增的,用于在订阅者接收消息的速度不够快的情况下,限制排队的消息数量。对于ROS Groovy及早期版本来说,只需省略即可。下一行的rospy.init_node(NAME, ...)非常重要,因为它把该节点的名称告诉了rospy——只有rospy掌握了这一信息后,才会开始与ROS主节点进行通信。在本例中,节点将使用talker名称。

rate = rospy.Rate(10) # 10hz
# 此行创建一个Rate对象rate。借助其方法sleep(),它提供了一种方便的方法,来以想要的速率循环。它的参数是10,即表示希望它每秒循环10次(只要我们的处理时间不超过十分之一秒)!

while not rospy.is_shutdown():
hello_str = "hello world %s" % rospy.get_time()
rospy.loginfo(hello_str)
pub.publish(hello_str)
rate.sleep()
# 这个循环是一个相当标准的rospy结构:检查rospy.is_shutdown()标志,然后执行代码逻辑。必须查看is_shutdown()以检查程序是否应该退出(例如有Ctrl+C或其他)。在本例中,代码逻辑即对public .publish(hello_str)的调用,它将一个字符串发布到chatter话题。循环的部分还调用了rate.sleep(),它在循环中可以用刚刚好的睡眠时间维持期望的速率。

if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
# 除了标准的Python __main__检查,它还会捕获一个rospy.ROSInterruptException异常,当按下Ctrl+C或节点因其他原因关闭时,这一异常就会被rospy.sleep()和rospy.Rate.sleep()抛出。引发此异常的原因是不会意外地在sleep()之后继续执行代码。

编写订阅者节点

继续跟着wiki,下载示例脚本 listener.py 放到 scripts 目录中并给执行权限,然后编辑CMakeLists.txt 中的 catkin_install_python() 调用。(此处省略命令和代码)

1
2
3
4
5
6
rospy.init_node('listener', anonymous=True)

rospy.Subscriber('chatter', String, callback)

# spin() simply keeps python from exiting until this node is stopped
rospy.spin()

wiki对这段代码的解释是:这声明节点订阅了 chatter 话题,类型是 std_msgs.msgs.String。当接收到新消息时,callback 函数被调用,消息作为第一个参数。

我们还稍微更改了对rospy.init_node()的调用。我们添加了 anonymous=True 关键字参数。ROS要求每个节点都有一个唯一的名称,如果出现具有相同名称的节点,则会与前一个节点发生冲突,这样一来,出现故障的节点很容易地被踢出网络。anonymous=True 标志会告诉 rospy 为节点生成唯一的名称,这样就很容易可以有多个 listener.py 一起运行。

最后再补充一下,rospy.spin() 只是不让节点退出,直到节点被明确关闭。与 roscpp 不同,rospy.spin() 不影响订阅者回调函数,因为它们有自己的线程。

构建节点

咱们使用CMake作为构建系统,这是为了确保能为创建的消息服务自动生成Python代码。

回到catkin工作空间,然后运行 catkin_make

1
2
$ cd ~/catkin_ws
$ catkin_make

检验简单的发布者和订阅者

运行发布者

咱们运行刚才制作的叫做 talker 的发布者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rosrun beginner_tutorials talker.py

[INFO] [1734609185.705253]: hello world 1734609185.7051988
[INFO] [1734609185.806596]: hello world 1734609185.8064554
[INFO] [1734609185.906493]: hello world 1734609185.9063175
[INFO] [1734609186.005546]: hello world 1734609186.0053947
[INFO] [1734609186.106381]: hello world 1734609186.1062553
[INFO] [1734609186.206608]: hello world 1734609186.2064044
[INFO] [1734609186.305868]: hello world 1734609186.3057268
[INFO] [1734609186.406142]: hello world 1734609186.4059968
[INFO] [1734609186.505583]: hello world 1734609186.5054214
[INFO] [1734609186.606951]: hello world 1734609186.6068125
[INFO] [1734609186.705764]: hello world 1734609186.7056243
[INFO] [1734609186.805870]: hello world 1734609186.8057344
[INFO] [1734609186.905890]: hello world 1734609186.9057145
[INFO] [1734609187.006627]: hello world 1734609187.0064893

wiki告诉我们,发布者节点已启动并运行。现在我们需要一个订阅者以接收来自发布者的消息。

运行订阅者

咱们运行刚才制作的叫做 listener 的订阅者:

1
2
3
4
5
6
7
8
9
$ rosrun beginner_tutorials listener

[INFO] [1734609563.673116]: /listener_14824_1734609563066I heard hello world 1734609563.671003
[INFO] [1734609563.773131]: /listener_14824_1734609563066I heard hello world 1734609563.7711043
[INFO] [1734609563.873401]: /listener_14824_1734609563066I heard hello world 1734609563.8712664
[INFO] [1734609563.973288]: /listener_14824_1734609563066I heard hello world 1734609563.9714963
[INFO] [1734609564.073304]: /listener_14824_1734609563066I heard hello world 1734609564.0710852
[INFO] [1734609564.173103]: /listener_14824_1734609563066I heard hello world 1734609564.1708713
[INFO] [1734609564.273282]: /listener_14824_1734609563066I heard hello world 1734609564.2709982

编写简单的服务器和客户端 (Python)

编写服务节点

跟着wiki的指引,我们将创建简单的服务节点 add_two_ints_server,该节点将接收两个整数,并返回它们的和。

代码

在beginner_tutorials包中创建 scripts/add_two_ints_server.py 文件并粘贴以下内容进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python

from __future__ import print_function

from beginner_tutorials.srv import AddTwoInts,AddTwoIntsResponse
import rospy

def handle_add_two_ints(req):
print("Returning [%s + %s = %s]"%(req.a, req.b, (req.a + req.b)))
return AddTwoIntsResponse(req.a + req.b)

def add_two_ints_server():
rospy.init_node('add_two_ints_server')
s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
print("Ready to add two ints.")
rospy.spin()

if __name__ == "__main__":
add_two_ints_server()

解释

1
s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)

wiki告诉我们:这声明了一个名为 add_two_ints 的新服务,其服务类型为 AddTwoInts。所有的请求(request)都传递给了 handle_add_two_ints 函数。handle_add_two_intsAddTwoIntsRequest 的实例调用,返回 AddTwoIntsResponse 实例。

就像订阅者中的例子一样,rospy.spin() 可以防止代码在服务关闭之前退出。

编写客户端节点

代码

继续跟随wiki指引,在 beginner_tutorials 包中创建 scripts/add_two_ints_client.py文件并粘贴以下内容进去:

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
#!/usr/bin/env python

from __future__ import print_function

import sys
import rospy
from beginner_tutorials.srv import *

def add_two_ints_client(x, y):
rospy.wait_for_service('add_two_ints')
try:
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
resp1 = add_two_ints(x, y)
return resp1.sum
except rospy.ServiceException as e:
print("Service call failed: %s"%e)

def usage():
return "%s [x y]"%sys.argv[0]

if __name__ == "__main__":
if len(sys.argv) == 3:
x = int(sys.argv[1])
y = int(sys.argv[2])
else:
print(usage())
sys.exit(1)
print("Requesting %s+%s"%(x, y))
print("%s + %s = %s"%(x, y, add_two_ints_client(x, y)))

解释

wiki的解释如下:

客户端(用来调用服务)的代码也很简单。对于客户端来说不需要调用init_node()。我们首先调用:

1
rospy.wait_for_service('add_two_ints')

这是一种很方便的方法,可以让在add_two_ints服务可用之前一直阻塞。

1
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)

这里我们为服务的调用创建了一个句柄(handle)。

1
2
resp1 = add_two_ints(x, y)
return resp1.sum

然后我们可以使用这个句柄,就像普通的函数一样调用它。

因为我们已经将服务的类型声明为 AddTwoInts,它会生成 AddTwoIntsRequest 对象 (you’re free to pass in your own instead)。如果调用失败,rospy.ServiceException将会抛出,所以应该弄一个合适的 try/except 部分。

构建节点

继续使用CMake作为构建系统,跟前面一模一样,不再赘述~


检验简单的服务和客户端

运行服务

1
2
3
$ rosrun beginner_tutorials add_two_ints_server,py

Ready to add two ints.

运行客户端

1
2
3
4
$ rosrun beginner_tutorials add_two_ints_client.py 1 3

Requesting 1+3
1 + 3 = 4

至此,成功地运行了第一个服务和客户端~


录制和回放数据

录制数据 (创建bag文件)

wiki告诉我们,这一部分将指导咱如何从正在运行的ROS系统中记录话题数据,话题数据将被积累到一个 bag 文件中。

咱打开三个新终端,分别输入以下内容:

1
2
3
4
5
$ roscore

$ rosrun turtlesim turtlesim_node

$ rosrun turtlesim turtle_teleop_key

这三个命令就是咱之前用来控制小乌龟运动的命令。

录制所有发布的话题

咱们打开一个新终端:

1
2
3
$ mkdir ~/bagfiles
$ cd ~/bagfiles
$ rosbag record -a

wiki告诉我们,这里我们只是创建了一个临时目录来记录数据,然后运行 rosbag record 带选项 -a,表明所有发布的话题都应该积累在一个 bag 文件中。

咱们回到 turtle_teleop 节点所在的终端窗口,控制乌龟移动10秒左右。然后咱在运行 rosbag record 的窗口中按 Ctrl + C 退出,然后查看 ~/bagfiles 里的内容,咱们就能发现一个以年份、日期和时间开头,并且扩展名是 .bag 的文件,这就是咱们所说的袋文件,它包含 rosbag record 运行期间由任何节点发布的所有话题。

检查并回放bag文件

刚才咱已经使用 rosbag record 命令录制了一个 bag 文件,接下来我们可以使用 rosbag info 查看它的内容,或者使用 rosbag play 命令回放。

命令:

1
$ rosbag info <your bagfile>

用观察大法知,这些信息告诉了咱 bag 文件中所包含话题的名称、类型和消息数量。

下一步咱们回放 bag 文件以再现系统运行过程。首先用Ctrl+C杀死之前运行的 turtle_teleop_key ,但让 turtlesim 继续运行。在终端中 bag 文件所在目录下运行以下命令:

1
$ rosbag play <your bagfile>

wiki上写到:默认模式下,rosbag play命令在公告每条消息后会等待一小段时间(0.2秒)才真正开始发布bag文件中的内容。等待一段时间是为了可以通知订阅者,消息已经公告且数据可能会马上到来。如果rosbag play在公告消息后立即发布,订阅者可能会接收不到几条最先发布的消息。等待时间可以通过 -d 选项来指定。

最终 /turtle1/cmd_vel 话题将会被发布,同时在turtuelsim中乌龟应该会像之前用turtle_teleop_key控制它那样开始移动。从运行rosbag play到乌龟开始移动时所经历时间应该近似等于之前在本教程开始部分运行rosbag record后到开始按下键盘发出控制命令时所经历时间。

录制数据子集

wiki告诉我们,当运行一个复杂的系统时,比如 PR2 软件套装,会有几百个话题被发布,有些话题亦会发布大量数据(比如包含摄像头图像流的话题)。在这种系统中,将包含所有话题的日志写入一个bag文件到磁盘通常是不切实际的。rosbag record命令支持只录制特定的话题到bag文件中,这样就可以只录制用户感兴趣的话题。

在bag文件所在目录下执行以下命令:

1
$ rosbag record -O subset /turtle1/cmd_vel /turtle1/pose

上述命令中的 -O 参数告诉 rosbag record 将数据记录到名为 subset.bag 的文件中,而后面的 topic 参数告诉rosbag record只能订阅这两个指定的话题。然后通过键盘控制乌龟随意移动几秒钟,最后按 Ctrl+C 退出 rosbag record 命令。

现在看看bag文件中的内容(rosbag info subset.bag),里面就只包含指定的话题了~

rosbag 录制和回放的局限性

仔细观察发现,乌龟的路径可能并没有完全地映射到原先通过键盘控制时产生的路径上——整体形状应该是差不多的,但没有完全一样。wiki告诉我们,这是因为 turtlesim 的移动路径对系统定时精度的变化非常敏感。rosbag 受制于其本身的性能无法完全复制录制时的系统运行行为,rosplay 也一样。对于像 turtlesim 这样的节点,当处理消息的过程中系统定时发生极小变化时也会使其行为发生微妙变化,用户不应该期望能够完美地模仿系统行为。


从bag文件中读取信息

立即回放信息并在多个终端中查看输出

在终端里头使用下面这个命令,来手动检查所有已经发布的话题,以及向每个话题发布了多少信息:

1
2
3
$ time rosbag info demo.bag
# 或者已经知道话题名称的话:
$ time rosbag info mybag.bag | grep -E "(topic1|topic2|topic3)"

咱们就能看到有多少条消息发布在什么话题上了~

有30条消息发布在 /obs1/gps/fix 话题上,有40条消息发布在 /diagnostics_agg 话题上。

跟着wiki,咱们还是先运行 roscore ,然后订阅 /obs1/gps/fix 话题并复读该话题发布的所有内容,同时用tee命令转储到一个 yaml 格式的文件中以遍之后查看:

1
$ rostopic echo /obs1/gps/fix | tee topic1.yaml

再打开一个新终端,订阅另一个话题 /diagnostics_agg:

1
rostopic echo /diagnostics_agg | tee topic2.yaml

然后咱们还得再打开另一个新终端来回放 bag 文件。这一次我们将尽可能快地回放 bag 文件(使用 --immediate 选项),会发布我们感兴趣的话题。格式如下:

1
time rosbag play --immediate demo.bag --topics /obs1/gps/fix /diagnostics_agg

现在看一下咱的两个终端,每个终端都订阅了一个话题,每个话题类型的所有消息都用 YAML 格式输出,每条消息之间用 --- 分割。咱们用任意文本编辑器(我用的 vscode)就能查看文件中的消息了~

使用 ros_readbagfile 脚本提取感兴趣的话题

下载并安装 ros_readbag.py :

1
2
3
4
5
6
7
8
9
10
11
12
# Download the file
wget https://raw.githubusercontent.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/master/useful_scripts/ros_readbagfile.py
# Make it executable
chmod +x ros_readbagfile.py
# Ensure you have the ~/bin directory for personal binaries
mkdir -p ~/bin
# Move this executable script into that directory as `ros_readbagfile`, so that it will
# be available as that command
mv ros_readbagfile.py ~/bin/ros_readbagfile
# Re-source your ~/.bashrc file to ensure ~/bin is in your PATH, so you can use this
# new `ros_readbagfile` command you just installed
. ~/.bashrc

先通过 rosbag info 命令确定要从bag文件中读取的准确话题名,然后使用 ros_readbagfile ,格式如下:

1
$ ros_readbagfile <mybagfile.bag> [topic1] [topic2] [topic3] [...]

要阅读信息,就输入以下内容:

1
$ time ros_readbagfile demo.bag /obs1/gps/fix /diagnostics_agg | tee topics.yaml

这样之后,咱们就能看到它快速打印出来了所有信息~

咱们用任意文本编辑器 (我用的vim) 来打开 topics.yaml ,就能看到它从 bag 文件中提取的所有信息了~


roswtf入门

首先咱们要确保 roscore 没有运行

安装检查

这个命令可以检查我的系统,并且尝试发现问题,咱们输入以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ roscd rosmaster
$ roswtf

Package: rosmaster # roswtf使用当前目录中的任何内容来确定其执行的检查。这个输出告诉我们是在包rosmaster的目录中启动了roswtf。
================================================================================
Static checks summary: # 它会报告任何关于文件系统或非运行时(比如无需roscore的场景)的问题。本例显示我们没有错误。

No errors or warnings
================================================================================

ROS Master does not appear to be running. # roscore未在运行。roswtf不会做任何ROS在线检查。
Online graph checks will not be run.
ROS_MASTER_URI is [http://localhost:11311]

在线检查

依照wiki,接下来咱们要启动一个 Master ,在新终端启动 roscore ,然后继续

现在咱们按照相同的顺序运行如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ roscd
$ roswtf

No package or stack in context
======================================================
Static checks summary:

No errors or warnings
======================================================
Beginning tests of your ROS graph. These may take awhile...
analyzing graph...
... done analyzing graph
running graph rules...
... done running graph rules

Online checks summary:

Found 1 warning(s).
Warnings are things that may be just fine, but are sometimes at fault

WARNING The following node subscriptions are unconnected:
* /rosout:
* /rosout

显然,在输出末尾, roswtf 发出了警告,rosout 节点订阅了一个没有节点向其发布的话题。wiki告诉我们,在本例中,这正是所期望看到的,因为除了 roscore 没有任何其它节点在运行,所以我们就可以忽略这个警告了~

错误

wiki告诉我们, roswtf 会对一些系统中看起来异常但可能是正常的运行情况发出警告。也会对确实有问题的情况报告错误。

接下来按照wiki的步骤,给 ROS_PACKAGE_PATH 环境变量设置一个 bad 值,并退出 roscore 以简化检查输出信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ roscd
$ ROS_PACKAGE_PATH=bad:$ROS_PACKAGE_PATH roswtf

Stack: ros
================================================================================
Static checks summary:

Found 1 error(s).

ERROR Not all paths in ROS_PACKAGE_PATH [bad] point to an existing directory:
* bad

================================================================================

Cannot communicate with master, ignoring graph checks

显然,roswtf 发现了一个有关 ROS_PACKAGE_PATH 设置的错误。

roswtf 还可以发现很多其它类型的问题。如果发现自己被构建或通信的问题难住了,可以尝试运行roswtf看能否指明正确的方向。


至此,ROS 1学习结束~

Insist on writing original high-quality articles. Your support is my biggest motivation.