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
roscd
由 ros
和 cd
组成,所以显然,这个命令可以让我们直接切换目录到某个软件包或者软件包集。
依次输入如下代码:
1 | $ roscd roscpp |
它输出的是:
1 | /opt/ros/noetic/share/roscpp |
- 子目录
roscd
也可以切换到一个软件包或软件包集的子目录中。
执行如下代码:
1 | $ roscd roscpp/cmake |
结果如下:
1 | /opt/ros/noetic/share/roscpp/cmake |
- roscd log
这个命令会进入存储 ROS
日志文件的目录。
如果没有执行过任何 ROS
程序,系统会报错说该目录不存在。即如下:
1 | No active roscore |
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 | my_package/ |
catkin工作空间中的软件包
开发 catkin
软件包的推荐方法是使用 catkin工作空间
,但也可以单独开发 catkin
软件包。
创建catkin软件包
先切换到创建的空白catkin工作空间中的源文件空间目录:
1 | $ cd ~/catkin_ws/src |
然后使用 catkin_create_pkg
命令创建一个名为 beginner_tutorials
的新软件包,这个软件包依赖于 std_msgs
、roscpp
和 rospy
:
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 | roscpp |
诶,这不就是前面咱们加的东西吗?
rospack
列出了在运行 catkin_create_pkg
命令时作为参数的依赖包,这些依赖关系存储在 package.xml
文件中。
间接依赖
一个依赖还会有自己的依赖关系,譬如 rospy
1 | $ rospack depends1 rospy |
一个软件包可以有相当多间接的依赖关系,使用 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_depend
、buildtool_depend
、run_depend
、test_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 | $ cd ~/catkin_ws/ |
现在我们可以使用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 | $ export ROS_HOSTNAME=localhost |
最终成功。
rosnode
在保持原来终端开着的情况下,打开一个新终端。 rosnode
显示当前正在运行的ROS节点信息。rosnode list
命令会列出这些活动的节点:
1 | $ rosnode list |
表示当前只有一个节点在运行:rosout
。这个节点用于收集和记录节点的调试输出,所以总是运行着的。
rosnode info
命令返回的是某个指定节点的信息。
1 | $ rosnode info /rosout |
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_node
和 turtle_teleop_key
节点正通过一个名为 /turtle1/command_velocity
的话题来相互通信。
rostopic
这个命令可以让我们来获取ROS话题的信息,可以使用 $ rostopic -h
来查看可用的子命令。
rostopic echo
这个命令可以显示在某个话题上发布的数据,用法如下:
1 | $ rostopic echo [topic] |
现在让我们看看由 turtle_teleop_key
节点发布的“指令、速度”数据。再打开一个新终端:
1 | $ rostopic echo /turtlel/cmd_vel |
我随便点了两下,就有上述的输出。
再看一下 rqt_graph
,按下左上角的刷新按钮显示新的节点:现在 rostopic echo
(红色)现在也订阅了 turtle1/command_velocity
这个话题。
rostopic list
这个命令能够列出当前已经被订阅和发布的所有话题。
咱们继续打开一个新终端:(向上面一样,可以通过 $ rostopic list -h
命令打开帮助页面)
1 | $ rostopic list -v |
如输出所示,这样会列出所有发布和订阅的主题及其类型的详细信息。
ROS 消息
wiki告诉我们,话题的通信是通过节点间发送ROS消息实现的。
为了使发布者(turtle_teleop_key
)和订阅者(turtulesim_node
)进行通信,发布者和订阅者必须发送和接收相同类型的消息。这意味着话题的类型是由发布在它上面消息的类型决定的。使用rostopic type
命令可以查看发布在话题上的消息的类型。
rostopic type
这个命令是用来查看所发布话题的消息类型。
用法:
1 | $ rostopic type [topic] |
根据wiki,我们运行:
1 | $ rostopic type /turtle1/cmd_vel |
继续用rosmsg查看消息的详细信息:
1 | $ rosmsg show geometry_msgs/Twist |
所以,现在我们已经知道了 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
消息有两个浮点型元素:linear
和angular
。在本例中,'[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 | $ rostopic hz /turtle1/pose |
由上可知,turtlesim
正以大约 60Hz
的频率发布有关乌龟的数据。
rqt_plot
这个命令可以在滚动时间图上显示发布到某个话题上的数据。这里我们将使用 rqt_plot
命令来绘制正被发布到 /turtle1/pose
话题上的数据。
再再再再次打开一个新终端:
1 | $ rosrun rqt_plot rqt_plot |
然后就弹出了一个新窗口,咱们在左上角这个文本框里头添加任何想要绘制的话题
咱们输入 /turtle1/pose/x
之后,按一下变亮的加号按钮;对 turtle1/pose/y
重复这样的过程,咱们就能看到龟龟的 x-y
位置力~
按下减号按钮会显示一组菜单,可以在图中隐藏指定的话题。
到此为止,咱们已经理解了ROS话题是如何工作的了~
理解ROS服务和参数
ROS服务
wiki告诉我们,服务 是节点之间通讯的另一种方式,服务允许节点发送一个请求并获得一个响应。
rosservice
rosservice
可以通过服务附加到ROS客户端/服务器框架上。
用法:(摘自wiki)
1 | rosservice list 输出活跃服务的信息 |
rosservice list
在终端输入这个命令 (注意要打开小乌龟的窗口),它会显示 turtlesim
节点提供了哪些服务,比如 clear
就不一一列举了
rosservice type
以 clear
服务为例,我们来查看它的类型:
1 | $ rosservice type /clear |
wiki告诉我们,服务的类型为 empty
(空),这表明调用这个服务时不需要参数(即,它在发出请求时不发送数据,在接收响应时也不接收数据)。
rosservice call
这个命令就是用来调用服务的,例如:
1 | $ rosservice call /clear |
在终端输入后,我们不难发现,小乌龟窗口中背景的轨迹被清除了
我们再来试试有参数的服务 spawn
:
1 | $ rosservice type /spawn | rossrv show |
咱们来观察一下返回的东西:显然,这个服务能让我们可以在给定的位置和角度生成一只新的乌龟,还可以给它起名字。
rosparam
这个命令可以让我们在 ROS参数服务器
上存储和操作数据。
参数服务器能够存储整型、浮点型、布尔、字典和列表这些数据类型。rosparam
使用YAML标记语言的语法。一般而言,YAML的表述很自然,1
是整型,1.0
是浮点型,one
是字符串,true
是布尔型,[1, 2, 3]
是整型组成的列表…
rosparam
有很多命令可以用来操作参数,wiki告诉了我们这些命令的用法:
1 | rosparam set 设置参数 |
rosparam list
我们使用这个命令之后,可以看到参数服务器上有3个参数用于设定背景颜色:
1 | /rosdistro |
rosparam set & rosparam get
这俩的用法都是后头加上 [param_name]
比如使用如下命令修改背景颜色:
1 | $ rosparam set /turtlesim/background_r 150 |
我们就会发现背景颜色变紫了~
然后我们可以使用 get
来获取其它参数的值,比如获取背景绿色通道的值:
1 | $ rosparam get /turtlesim/background_g |
我们甚至还能用 rosparam get /
来显示参数服务器上的所有内容
rosparam dump & rosparam load
这俩的用法都是在后头加上 [file_name] [namespace]
比如,我们将所有的参数写入 params.yaml
文件:
1 | $ rosparam dump params.yaml |
甚至可以将 yaml
文件重载入新的命令空间,比如 copy_turtle
:
1 | $ rosparam load params.yaml copy_turtle |
使用rqt_console和roslaunch
rqt_console & rqt_logger_level
wiki告诉我们,rqt_console
连接到了ROS的日志框架,以显示节点的输出信息。rqt_logger_level
允许我们在节点运行时改变输出信息的详细级别。
rqt_logger_level
允许我们在节点运行时改变输出信息的详细级别,包括Debug
、Info
、Warn
和Error
。
日志记录器级别
日志级别的优先级按以下顺序排列:
1 | Fatal Error Warn Info Debug |
wiki告诉我们,通过设置日志级别,咱们可以获得所有优先级级别,或只是更高级别的消息。比如,将日志级别设为Warn
时,咱会得到Warn
、Error
和Fatal
这三个等级的日志消息。
roslaunch
这个命令可以用来启动定义在 launch
文件中的节点,用法:
1 | $ roslaunch [package] [filename.launch] |
wiki引导我们切换到我们之前的 beginner_tutorials
软件包目录下:
1 | $ roscd beginner_tutorials |
继续创建一个 launch
目录:
1 | $ mkdir launch |
launch文件
wiki让我们现在创建一个名为 turtlemimic.launch
的launch文件并复制粘贴这些东西进去:
1 | 1 <launch> |
使用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 | $ rosed roscpp <tab><tab> |
编辑器
rosed默认的编译器是vim~
创建ROS消息和服务
msg 和 srv
msg
:msg
文件是文本文件,用于描述ROS消息的字段。它们用于为不同编程语言编写的消息生成源代码。srv
:一个srv
文件描述一个服务。它由两部分组成:请求和响应。
编写简单的发布者和订阅者 (Python)
编写发布者节点
wiki告诉我们,节点
是连接到ROS网络的可执行文件。我们要创建 talker
(发布者) 节点,该节点将不断广播消息。
跟着wiki上的步骤,先创建一个 scripts
目录来存放 Python
脚本,然后下载示例脚本 talker.py
放到 scripts
目录中并给执行权限;最后把相关内容添加到 CMakeLists.txt
里头,就能确保正确安装 Python
脚本,并使用合适的 Python
解释器了~
解释
跟着wiki分解一下代码:
1 | #!/usr/bin/env python |
编写订阅者节点
继续跟着wiki,下载示例脚本 listener.py
放到 scripts
目录中并给执行权限,然后编辑CMakeLists.txt
中的 catkin_install_python()
调用。(此处省略命令和代码)
1 | rospy.init_node('listener', anonymous=True) |
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 | $ cd ~/catkin_ws |
检验简单的发布者和订阅者
运行发布者
咱们运行刚才制作的叫做 talker
的发布者:
1 | rosrun beginner_tutorials talker.py |
wiki告诉我们,发布者节点已启动并运行。现在我们需要一个订阅者以接收来自发布者的消息。
运行订阅者
咱们运行刚才制作的叫做 listener
的订阅者:
1 | $ rosrun beginner_tutorials listener |
编写简单的服务器和客户端 (Python)
编写服务节点
跟着wiki的指引,我们将创建简单的服务节点 add_two_ints_server
,该节点将接收两个整数,并返回它们的和。
代码
在beginner_tutorials包中创建 scripts/add_two_ints_server.py
文件并粘贴以下内容进去:
1 | #!/usr/bin/env python |
解释
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_ints
被 AddTwoIntsRequest
的实例调用,返回 AddTwoIntsResponse
实例。
就像订阅者中的例子一样,rospy.spin()
可以防止代码在服务关闭之前退出。
编写客户端节点
代码
继续跟随wiki指引,在 beginner_tutorials
包中创建 scripts/add_two_ints_client.py
文件并粘贴以下内容进去:
1 | #!/usr/bin/env python |
解释
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 | resp1 = add_two_ints(x, y) |
然后我们可以使用这个句柄,就像普通的函数一样调用它。
因为我们已经将服务的类型声明为 AddTwoInts
,它会生成 AddTwoIntsRequest
对象 (you’re free to pass in your own instead)。如果调用失败,rospy.ServiceException
将会抛出,所以应该弄一个合适的 try/except
部分。
构建节点
继续使用CMake作为构建系统,跟前面一模一样,不再赘述~
检验简单的服务和客户端
运行服务
1 | $ rosrun beginner_tutorials add_two_ints_server,py |
运行客户端
1 | $ rosrun beginner_tutorials add_two_ints_client.py 1 3 |
至此,成功地运行了第一个服务和客户端~
录制和回放数据
录制数据 (创建bag文件)
wiki告诉我们,这一部分将指导咱如何从正在运行的ROS系统中记录话题数据,话题数据将被积累到一个 bag
文件中。
咱打开三个新终端,分别输入以下内容:
1 | $ roscore |
这三个命令就是咱之前用来控制小乌龟运动的命令。
录制所有发布的话题
咱们打开一个新终端:
1 | $ mkdir ~/bagfiles |
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 | $ time rosbag info demo.bag |
咱们就能看到有多少条消息发布在什么话题上了~
有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 | # Download the file |
先通过 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 | $ roscd rosmaster |
在线检查
依照wiki,接下来咱们要启动一个 Master
,在新终端启动 roscore
,然后继续
现在咱们按照相同的顺序运行如下命令:
1 | $ roscd |
显然,在输出末尾, roswtf
发出了警告,rosout
节点订阅了一个没有节点向其发布的话题。wiki告诉我们,在本例中,这正是所期望看到的,因为除了 roscore
没有任何其它节点在运行,所以我们就可以忽略这个警告了~
错误
wiki告诉我们, roswtf
会对一些系统中看起来异常但可能是正常的运行情况发出警告。也会对确实有问题的情况报告错误。
接下来按照wiki的步骤,给 ROS_PACKAGE_PATH
环境变量设置一个 bad
值,并退出 roscore
以简化检查输出信息。
1 | $ roscd |
显然,roswtf
发现了一个有关 ROS_PACKAGE_PATH
设置的错误。
roswtf
还可以发现很多其它类型的问题。如果发现自己被构建或通信的问题难住了,可以尝试运行roswtf
看能否指明正确的方向。
至此,ROS 1学习结束~