ROS教程(下) - RLi43/Baxter-Experimental-Guide GitHub Wiki

理解ROS服务和参数

本部分介绍了ROS 服务和参数的知识,以及命令行工具rosservice 和 rosparam的使用方法。

1 ROS Services

服务(services)是节点之间通讯的另一种方式。服务允许节点发送请求(request) 并获得一个响应(response)。

2 使用rosservice

rosservice 可以很轻松的使用 ROS 客户端/服务器框架提供的服务。rosservice 提供了很多可以在 topic 上使用的命令,如下所示:

使用方法:

rosservice list         输出可用服务的信息
rosservice call         调用带参数的服务
rosservice type         输出服务类型
rosservice find         依据类型寻找服务find services by service type
rosservice uri          输出服务的ROSRPC uri

2.1 rosservice list

$ rosservice list

list 命令显示turtlesim节点提供了9个服务:重置(reset), 清除(clear), 再生(spawn), 终止(kill), turtle1/set_pen, /turtle1/teleport_absolute, /turtle1/teleport_relative, turtlesim/get_loggers, and turtlesim/set_logger_level. 同时还有另外两个rosout节点提供的服务: /rosout/get_loggers and /rosout/set_logger_level.

/clear
/kill
/reset
/rosout/get_loggers
/rosout/set_logger_level
/spawn
/teleop_turtle/get_loggers
/teleop_turtle/set_logger_level
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/get_loggers
/turtlesim/set_logger_level

我们使用rosservice type命令更进一步查看clear服务:

2.2 rosservice type

使用方法:

rosservice type [service]

我们来看看clear服务的类型:

$ rosservice type clear
std_srvs/Empty

服务的类型为空(empty),这表明在调用这个服务是不需要参数(比如,请求不需要发送数据,响应也没有数据)。下面我们使用rosservice call命令调用服务:

2.3 rosservice call

使用方法:

rosservice call [service] [args]

因为服务类型是空,所以进行无参数调用:

$ rosservice call clear

正如我们所期待的,服务清除了turtlesim_node的背景上的轨迹。

1561650141741

通过查看再生(spawn)服务的信息,我们来了解有参数的服务:

$ rosservice type spawn| rossrv show
float32 x
float32 y
float32 theta
string name
---
string name

这个服务使得我们可以在给定的位置和角度生成一只新的乌龟。名字参数是可选的,这里我们不设具体的名字,让turtlesim自动创建一个。

$ rosservice call spawn 2 2 0.2 ""

服务返回了新产生的乌龟的名字:

name: turtle2

现在我们的乌龟看起来应该是像这样的:

1561650215735

3 Using rosparam

rosparam使得我们能够存储并操作ROS 参数服务器(Parameter Server)上的数据。参数服务器能够存储整型、浮点、布尔、字符串、字典和列表等数据类型。rosparam使用YAML标记语言的语法。一般而言,YAML的表述很自然:1 是整型, 1.0 是浮点型, one是字符串, true是布尔, [1, 2, 3]是整型列表, {a: b, c: d}是字典. rosparam有很多指令可以用来操作参数,如下所示:

使用方法:

rosparam set            设置参数
rosparam get            获取参数
rosparam load           从文件读取参数
rosparam dump           向文件中写入参数
rosparam delete         删除参数
rosparam list           列出参数名

我们来看看现在参数服务器上都有哪些参数:

3.1 rosparam list

$ rosparam list

我们可以看到turtlesim节点在参数服务器上有3个参数用于设定背景颜色:

/background_b
/background_g
/background_r
/roslaunch/uris/aqy:51932
/run_id

3.2 rosparam 设置 and rosparam 获取

使用:

$ rosparam set [param_name]
$ rosparam get [param_name]

现在我们修改背景颜色的红色通道:

$ rosparam set background_r 150

上述指令修改了参数的值,现在我们调用清除服务使得修改后的参数生效:

$ rosservice call clear

现在 我们的小乌龟看起来应该是像这样:

1561650243828

现在我们来查看参数服务器上的参数值——获取背景的绿色通道的值:

$ rosparam get background_g 
86

我们可以使用rosparam get /来显示参数服务器上的所有内容:

$ rosparam get /
background_b: 255
background_g: 86
background_r: 150
roslaunch:
  uris: {'aqy:51932': 'http://aqy:51932/'}
run_id: e07ea71e-98df-11de-8875-001b21201aa8

你可能希望存储这些信息以备今后重新读取。这通过rosparam很容易就可以实现:

3.3 rosparam dump and rosparam load

使用方法:

$ rosparam dump [file_name]
$ rosparam load [file_name] [namespace]

现在我们将所有的参数写入params.yaml文件:

$ rosparam dump params.yaml

你甚至可以将yaml文件重载入新的命名空间,比如说copy空间:

$ rosparam load params.yaml copy
$ rosparam get copy/background_b 255

1561650277531

复习及扩展

ros-07

ros-08

ros-09

ros-10

ros-11

ros-12

ros-13

ros-14

ros-15

ros-16

ros-17

ros-18

使用 rqt_console 和 roslaunch

本部分介绍如何使用rqt_console和rqt_logger_level进行调试,以及如何使用roslaunch同时运行多个节点。

1 预先安装rqt和turtlesim程序包

为提高效率,请先下载在Github上的ros turorials功能包,https://github.com/ros/ros_tutorials/tree/indigo-devel 。 本部分会用到rqt 和 turtlesim这两个程序包,如果你没有安装,请先安装:

$ sudo apt-get install ros-<distro>-rqt ros-<distro>-rqt-common-plugins ros-<distro>-turtlesim

请使用ROS发行版名称(比如 electric、fuerte、groovy、hydro或indigo)替换掉<distro>

注意: 你可能已经在之前的某篇教程中编译过rqt和turtlesim,如果你不确定的话重新编译一次也没事。

2 使用rqt_console和rqt_logger_level

rqt_console 属于ROS日志框架(logging framework)的一部分,用来显示节点的输出信息。 rqt_logger_level 允许我们修改节点运行时输出信息的日志等级(logger levels)(包括 DEBUG、WARN、INFO和ERROR)。

现在来看一下 turtlesim 在 rqt_console 中的输出信息,同时在 rqt_logger_level 中修改日志等级。在启动 turtlesim 之前先在另外两个新终端中运行 rqt_console 和 rqt_logger_level:

$ rosrun rqt_console rqt_console
$ rosrun rqt_logger_level rqt_logger_level

你会看到弹出两个窗口:

1561650472914

1561650513256

现在让我们在一个新终端中启动turtlesim:

$ rosrun turtlesim turtlesim_node

因为默认日志等级是INFO,所以你会看到turtlesim启动后输出的所有信息,如下图所示:

1561650539672

现在让我们刷新一下rqt_logger_level窗口并选择Warn将日志等级修改为WARN,如下图所示:

1561650563737

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

1561650589336

2.1 日志等级说明

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

Fatal
Error
Warn
Info
Debug

Fatal是最高优先级,Debug 是最低优先级。通过设置日志等级你可以获取该等级及其以上优先等级的所有日志消息。比如,将日志等级设为Warn时,你会得到 Warn、Error和 Fatal 这三个等级的所有日志消息。

现在让我们按Ctrl-C退出turtlesim节点,接下来我们将使用 roslaunch 来启动多个 turtlesim 节点和一个模仿节点以让一个 turtlesim 节点来模仿另一个 turtlesim 节点。

2.2 使用roslaunch

roslaunch可以用来启动定义在launch文件中的多个节点。

用法:

$ roslaunch [package] [filename.launch]

先切换到beginner_tutorials程序包目录下:

$ roscd beginner_tutorials

如果roscd执行失败了,记得设置你当前终端下的ROS_PACKAGE_PATH环境变量。

$ roscd beginner_tutorials

如果你仍然无法找到beginner_tutorials程序包,说明该程序包还没有创建,那么请返回到ROS/Tutorials/CreatingPackage教程,并按照创建程序包的操作方法创建一个beginner_tutorials程序包。

然后创建一个launch文件夹:

$ mkdir launch
$ cd launch

2.3 Launch 文件

现在我们来创建一个名为 turtlemimic.launch 的 launch 文件并复制粘贴以下内容到该文件里面:

    <launch>

      <group ns="turtlesim1">
        <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
      </group>

      <group ns="turtlesim2">
        <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
      </group>

      <node pkg="turtlesim" name="mimic" type="mimic">
        <remap from="input" to="turtlesim1/turtle1"/>
        <remap from="output" to="turtlesim2/turtle1"/>
      </node>

    </launch>

2.4 Launch 文件解析

现在我们开始逐句解析launch xml文件。

  <launch>

在这里我们以launch标签开头以表明这是一个launch文件。

      <group ns="turtlesim1">
        <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
      </group>

      <group ns="turtlesim2">
        <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
      </group>

在这里我们创建了两个节点分组并以'命名空间(namespace)'标签来区分,其中一个名为turtulesim1,另一个名为turtlesim2,两个组里面都使用相同的turtlesim节点并命名为'sim'。这样可以让我们同时启动两个turtlesim模拟器而不会产生命名冲突。

     <node pkg="turtlesim" name="mimic" type="mimic">
       <remap from="input" to="turtlesim1/turtle1"/>
       <remap from="output" to="turtlesim2/turtle1"/>
     </node>

在这里我们启动模仿节点,并将所有话题的输入和输出分别重命名为turtlesim1和turtlesim2,这样就会使turtlesim2模仿turtlesim1。

   </launch>

这个是launch文件的结束标签。

2.5 使用roslaunch

现在让我们通过roslaunch命令来启动launch文件: 如果在此处遇到问题,可以参考学员更正:无法启动launch

$ roslaunch beginner_tutorials turtlemimic.launch

现在将会有两个turtlesims被启动,然后我们在一个新终端中使用rostopic命令发送速度设定消息:

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

你会看到两个turtlesims会同时开始移动,虽然发布命令只是给turtlesim1发送了速度设定消息。

1561650626200

我们也可以通过rqt_graph来更好的理解在launch文件中所做的事情。运行rqt并在主窗口中选择rqt_graph:

$ rqt

或者直接运行:

$ rqt_graph

1561650648477

1561650695611

补充复习及提升

24

25

26

27

使用rosed编辑ROS中的文件

本部分将展示如何使用rosed来简化编辑过程。

1 使用 rosed

rosed是rosbash的一部分。利用它可以直接通过package名来获取到待编辑的文件而无需指定该文件的存储路径了。

使用方法:

$ rosed [package_name] [filename]

例子:

$ rosed roscpp Logger.msg

这个实例展示了如何编辑roscpp package里的Logger.msg文件。

如果该实例没有运行成功,那么很有可能是你没有安装vim编辑器。请参考编辑器部分进行设置。

如果文件名在package里不是唯一的,那么会呈现出一个列表,让你选择编辑哪一个文件。

2 使用Tab键补全文件名

使用这个方法,在不知道准确文件名的情况下,你也可以看到并选择你所要编辑的文件。

使用方法:

$ rosed [package_name] <tab>

3 编辑器

rosed默认的编辑器是vim。如果想要将其他的编辑器设置成默认的,你需要修改你的 ~/.zshrc 文件,增加如下语句:

export EDITOR='emacs -nw'

这将emacs设置成为默认编辑器。

注意: .zshrc文件的改变,只会在新的终端才有效。已经打开的终端不受环境变量的影响。

打开一个新的终端,看看那是否定义了EDITOR:

$ echo $EDITOR
emacs -nw

这里修改为gedit,如下:

1561650810859

1561650837188

补充学习

46

47

48

49

50

创建ROS消息和ROS服务

本部分详细介绍如何创建并编译ROS消息和服务,以及rosmsg, rossrv和roscp命令行工具的使用。

本示例的所有源码都在/opt/ros_ws/src/ros_tutorials/

1 消息(msg)和服务(srv)介绍

  • 消息(msg): msg文件就是一个描述ROS中所使用消息类型的简单文本。它们会被用来生成不同语言的源代码。
  • 服务(srv): 一个srv文件描述一项服务。它包含两个部分:请求和响应。 msg文件存放在package的msg目录下,srv文件则存放在srv目录下。 msg文件实际上就是每行声明一个数据类型和变量名。可以使用的数据类型如下:
int8, int16, int32, int64 (plus uint*)
float32, float64
string
time, duration
other msg files
variable-length array[] and fixed-length array[C]

在ROS中有一个特殊的数据类型:Header,它含有时间戳和坐标系信息。在msg文件的第一行经常可以看到Header header的声明. 下面是一个msg文件的样例,它使用了Header,string,和其他另外两个消息类型。

  Header header
  string child_frame_id
  geometry_msgs/PoseWithCovariance pose
  geometry_msgs/TwistWithCovariance twist

srv文件分为请求响应两部分,由'---'分隔。下面是srv的一个样例:

int64 A
int64 B
---
int64 Sum

参考如下: /opt/ros_ws/src/ros_tutorials/roscpp_tutorials/srv/TwoInts.srv /opt/ros_ws/src/ros_tutorials/rospy_tutorials/srv/AddTwoInts.srv

$ rossrv show rospy_tutorials/AddTwoInts

1561650939104

其中 A 和 B 是请求, 而Sum 是响应。下面我们就来一步一步学习如何定义消息和服务。

2 使用 msg

2.1 创建一个 msg

下面,我们将在之前创建的package里定义新的消息。

$ cd ~/catkin_ws/src/beginner_tutorials
$ mkdir msg
$ echo "int64 num" > msg/Num.msg

上面是最简单的例子——在.msg文件中只有一行数据。当然,你可以仿造上面的形式多增加几行以得到更为复杂的消息:

string first_name
string last_name
uint8 age
uint32 score

接下来,还有关键的一步:我们要确保msg文件被转换成为C++,Python和其他语言的源代码: 查看package.xml, 确保它包含以下两条语句:

  <build_depend>message_generation</build_depend>
  <run_depend>message_runtime</run_depend>

如果没有,添加进去。 注意,在构建的时候,我们只需要"message_generation"。然而,在运行的时候,我们只需要"message_runtime"。 在你最喜爱的编辑器中打开CMakeLists.txt文件(可以参考前边的教程rosed)。 在 CMakeLists.txt文件中,利用find_packag函数,增加对message_generation的依赖,这样就可以生成消息了。 你可以直接在COMPONENTS的列表里增加message_generation,就像这样:

# Do not just add this line to your CMakeLists.txt, modify the existing line
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)

有时候你会发现,即使你没有调用find_package,你也可以编译通过。这是因为catkin把你所有的package都整合在一起,因此,如果其他的package调用了find_package,你的package的依赖就会是同样的配置。但是,在你单独编译时,忘记调用find_package会很容易出错。

同样,你需要确保你设置了运行依赖:

catkin_package(
  ...
  CATKIN_DEPENDS message_runtime ...
  ...)

找到如下代码块:

# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

去掉注释符号#,用你的.msg文件替代Message*.msg,就像下边这样:

add_message_files(
  FILES
  Num.msg
)

手动添加.msg文件后,我们要确保CMake知道在什么时候重新配置我们的project。 确保添加了如下代码:

generate_messages()

现在,你可以生成自己的消息源代码了。如果你想立即实现,那么就跳过以下部分,到Common step for msg and srv。 请务必在使用前正确使用catkin_make和source,否则可能出错。

2.2 使用 rosmsg

以上就是你创建消息的所有步骤。下面通过rosmsg show命令,检查ROS是否能够识消息。

首先,要先使用catkin_make编译,然后再source /home/shiyanlou/catkin_ws/devel/setup.zsh

使用方法:

$ rosmsg show [message type]

样例:

$ rosmsg show beginner_tutorials/Num

你将会看到:

int64 num
如果功能包没有编译,并正确配置有提示无法找到对应文件

在上边的样例中,消息类型包含两部分:

  • beginner_tutorials -- 消息所在的package
  • Num -- 消息名Num. 如果你忘记了消息所在的package,你也可以省略掉package名。输入:
$ rosmsg show Num

你将会看到:

[beginner_tutorials/Num]:
int64 num

如果没有正确配置,但依然想看该指令的用途,可以使用ROS已经配置好的包如下:

$ rosmsg show roscpp/Logger
$ rosmsg show std_msgs/Int64

1561650991739

3 使用 srv

3.1 创建一个srv

在刚刚那个package中创建一个服务:

$ roscd beginner_tutorials
$ mkdir srv

这次我们不再手动创建服务,而是从其他的package中复制一个服务。 roscp是一个很实用的命令行工具,它实现了将文件从一个package复制到另外一个package的功能。

使用方法:

$ roscp [package_name] [file_to_copy_path] [copy_path]

现在我们可以从rospy_tutorials package中复制一个服务文件了:

$ roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

还有很关键的一步:我们要确保srv文件被转换成C++,Python和其他语言的源代码。

现在认为,你已经如前边所介绍的,在CMakeLists.txt文件中增加了对message_generation的依赖。:

# Do not just add this line to your CMakeLists.txt, modify the existing line
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)

(注意, message_generation 对msg和srv都起作用)

同样,跟msg文件类似,你也需要在package.xml文件中做一些修改。查看上边的说明,增加额外的依赖项。

删掉#,去除对下边语句的注释:

# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

用你自己的srv文件名替换掉那些Service*.srv文件:

add_service_files(
  FILES
  AddTwoInts.srv
)

现在,你可以生成自己的服务源代码了。如果你想立即实现,那么就跳过以下部分,到Common step for msg and srv.

3.2 使用 rossrv

以上就是创建一个服务所需的所有步骤。下面通过rosmsg show命令,检查ROS是否能够识该服务。

使用方法:

$ rossrv show <service type>

例子:

$ rossrv show beginner_tutorials/AddTwoInts

如果出错,说明配置有误,请认真检查。

你将会看到:

int64 a
int64 b
---
int64 sum

跟rosmsg类似, 你也可以不指定具体的package名来查找服务文件:

$ rossrv show AddTwoInts
[beginner_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum

[rospy_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum

4 msg和srv都需要的步骤

接下来,在CMakeLists.txt中找到如下部分:

# generate_messages(
#   DEPENDENCIES
# #  std_msgs  # Or other packages containing msgs
# )

去掉注释并附加上所有你消息文件所依赖的那些含有.msg文件的package(这个例子是依赖std_msgs,不要添加roscpp,rospy),结果如下:

generate_messages(
  DEPENDENCIES
  std_msgs
)

由于增加了新的消息,所以我们需要重新编译我们的package:

# In your catkin workspace
$ cd ../..
$ catkin_make
$ cd -

所有在msg路径下的.msg文件都将转换为ROS所支持语言的源代码。生成的C++头文件将会放置在~/catkin_ws/devel/include/beginner_tutorials/。 Python脚本语言会在 ~/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/msg 目录下创建。 lisp文件会出现在 ~/catkin_ws/devel/share/common-lisp/ros/beginner_tutorials/msg/ 路径下。

详尽的消息格式请参考Message Description Language 页面。

5 获得帮助

我们已经接触到不少的ROS工具了。有时候很难记住他们所需要的参数。还好大多数ROS工具都提供了帮助。

输入:

$ rosmsg -h

你可以看到一系列的rosmsg子命令:

Commands:
  rosmsg show Show message description
  rosmsg users  Find files that use message
  rosmsg md5  Display message md5sum
  rosmsg package  List messages in a package
  rosmsg packages List packages that contain messages

同样你也可以获得子命令的帮助:

$ rosmsg show -h

这会现实rosmsg show 所需的参数:

Usage: rosmsg show [options] <message type>

Options:
  -h, --help  show this help message and exit
  -r, --raw   show raw message text, including comments

6 回顾

总结一下到目前为止我们接触过的一些命令:

  • rospack = ros+pack(age) : provides information related to ROS packages
  • rosstack = ros+stack : provides information related to ROS stacks
  • roscd = ros+cd : changes directory to a ROS package or stack
  • rosls = ros+ls : lists files in a ROS package
  • roscp = ros+cp : copies files from/to a ROS package
  • rosmsg = ros+msg : provides information related to ROS message definitions
  • rossrv = ros+srv : provides information related to ROS service definitions
  • rosmake = ros+make : makes (compiles) a ROS package

编写简单的消息发布器和订阅器 (C++)

本部分将介绍如何用 C++ 编写发布器节点和订阅器节点。

1 编写发布器节点

『节点』(Node) 是指 ROS 网络中可执行文件。接下来,我们将会创建一个发布器节点("talker"),它将不断的在 ROS 网络中广播消息。

切换到之前创建的 beginner_tutorials package 路径下:

$ cd ~/catkin_ws/src/beginner_tutorials

1.1 源代码

在 beginner_tutorials package 路径下创建一个src文件夹:

$ mkdir -p ~/catkin_ws/src/beginner_tutorials/src

这个文件夹将会用来放置 beginner_tutorials package 的所有源代码。

在 beginner_tutorials package 里创建 src/talker.cpp 文件,并将如下代码粘贴到文件内:

源码链接:https://raw.github.com/ros/ros_tutorials/indigo-devel/roscpp_tutorials/talker/talker.cpp

   #include "ros/ros.h"
   #include "std_msgs/String.h"

   #include <sstream>

   /**
   * This tutorial demonstrates simple sending of messages over the ROS system.
    */
   int main(int argc, char **argv)
   {
     /**
     * The ros::init() function needs to see argc and argv so that it can perform
    * any ROS arguments and name remapping that were provided at the command line. For programmatic
     * remappings you can use a different version of init() which takes remappings
    * directly, but for most command-line programs, passing argc and argv is the easiest
    * way to do it.  The third argument to init() is the name of the node.
      *
      * You must call one of the versions of ros::init() before using any other
      * part of the ROS system.
      */
     ros::init(argc, argv, "talker");

     /**
      * NodeHandle is the main access point to communications with the ROS system.
      * The first NodeHandle constructed will fully initialize this node, and the last
      * NodeHandle destructed will close down the node.
      */
     ros::NodeHandle n;

     /**
      * The advertise() function is how you tell ROS that you want to
      * publish on a given topic name. This invokes a call to the ROS
      * master node, which keeps a registry of who is publishing and who
      * is subscribing. After this advertise() call is made, the master
      * node will notify anyone who is trying to subscribe to this topic name,
      * and they will in turn negotiate a peer-to-peer connection with this
      * node.  advertise() returns a Publisher object which allows you to
      * publish messages on that topic through a call to publish().  Once
      * all copies of the returned Publisher object are destroyed, the topic
      * will be automatically unadvertised.
      *
      * The second parameter to advertise() is the size of the message queue
      * used for publishing messages.  If messages are published more quickly
      * than we can send them, the number here specifies how many messages to
      * buffer up before throwing some away.
      */
     ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

     ros::Rate loop_rate(10);

     /**
      * A count of how many messages we have sent. This is used to create
      * a unique string for each message.
      */
     int count = 0;
     while (ros::ok())
     {
       /**
        * This is a message object. You stuff it with data, and then publish it.
        */
       std_msgs::String msg;

       std::stringstream ss;
       ss << "hello world " << count;
       msg.data = ss.str();

       ROS_INFO("%s", msg.data.c_str());

       /**
        * The publish() function is how you send messages. The parameter
        * is the message object. The type of this object must agree with the type
        * given as a template parameter to the advertise<>() call, as was done
        * in the constructor above.
       */
      chatter_pub.publish(msg);

      ros::spinOnce();

      loop_rate.sleep();
      ++count;
    }


    return 0;
  }

1.2 代码说明

现在,我们来分段解释代码。

   #include "ros/ros.h"

ros/ros.h 是一个实用的头文件,它引用了 ROS 系统中大部分常用的头文件。

   #include "std_msgs/String.h"

这引用了 std_msgs/String 消息, 它存放在 std_msgs package 里,是由 String.msg 文件自动生成的头文件。需要关于消息的定义,可以参考 msg 页面。

     ros::init(argc, argv, "talker");

初始化 ROS 。它允许 ROS 通过命令行进行名称重映射——然而这并不是现在讨论的重点。在这里,我们也可以指定节点的名称——运行过程中,节点的名称必须唯一。

这里的名称必须是一个 base name ,也就是说,名称内不能包含 / 等符号。

     ros::NodeHandle n;

为这个进程的节点创建一个句柄。第一个创建的 NodeHandle 会为节点进行初始化,最后一个销毁的 NodeHandle 则会释放该节点所占用的所有资源。

     ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

告诉 master 我们将要在 chatter(话题名) 上发布 std_msgs/String 消息类型的消息。这样 master 就会告诉所有订阅了 chatter 话题的节点,将要有数据发布。第二个参数是发布序列的大小。如果我们发布的消息的频率太高,缓冲区中的消息在大于 1000 个的时候就会开始丢弃先前发布的消息。

NodeHandle::advertise() 返回一个 ros::Publisher 对象,它有两个作用: 1) 它有一个 publish() 成员函数可以让你在topic上发布消息; 2) 如果消息类型不对,它会拒绝发布。

     ros::Rate loop_rate(10);

ros::Rate 对象可以允许你指定自循环的频率。它会追踪记录自上一次调用 Rate::sleep() 后时间的流逝,并休眠直到一个频率周期的时间。

在这个例子中,我们让它以 10Hz 的频率运行。

     int count = 0;
     while (ros::ok())
     {

roscpp 会默认生成一个 SIGINT 句柄,它负责处理 Ctrl-C 键盘操作——使得 ros::ok() 返回 false。

如果下列条件之一发生,ros::ok() 返回false:

  • 列表项SIGINT 被触发 (Ctrl-C)
  • 列表项被另一同名节点踢出 ROS 网络
  • 列表项ros::shutdown() 被程序的另一部分调用
  • 节点中的所有 ros::NodeHandles 都已经被销毁 一旦 ros::ok() 返回 false, 所有的 ROS 调用都会失效。
       std_msgs::String msg;

       std::stringstream ss;
       ss << "hello world " << count;
       msg.data = ss.str();

我们使用一个由 msg file 文件产生的『消息自适应』类在 ROS 网络中广播消息。现在我们使用标准的String消息,它只有一个数据成员 "data"。当然,你也可以发布更复杂的消息类型。

      chatter_pub.publish(msg);

这里,我们向所有订阅 chatter 话题的节点发送消息。

       ROS_INFO("%s", msg.data.c_str());

ROS_INFO 和其他类似的函数可以用来代替 printf/cout 等函数。具体可以参考 rosconsole documentation,以获得更多信息。

      ros::spinOnce();

在这个例子中并不是一定要调用 ros::spinOnce(),因为我们不接受回调。然而,如果你的程序里包含其他回调函数,最好在这里加上 ros::spinOnce()这一语句,否则你的回调函数就永远也不会被调用了。

      loop_rate.sleep();

这条语句是调用 ros::Rate 对象来休眠一段时间以使得发布频率为 10Hz。

对上边的内容进行一下总结:

  • 列表项初始化 ROS 系统
  • 列表项在 ROS 网络内广播我们将要在 chatter 话题上发布 std_msgs/String 类型的消息
  • 列表项以每秒 10 次的频率在 chatter 上发布消息

接下来我们要编写一个节点来接收这个消息。

2 编写订阅器节点

2.1 源代码

在 beginner_tutorials package 目录下创建 src/listener.cpp 文件,并粘贴如下代码:

源码链接:https://raw.github.com/ros/ros_tutorials/indigo-devel/roscpp_tutorials/listener/listener.cpp

   #include "ros/ros.h"
   #include "std_msgs/String.h"

   /**
    * This tutorial demonstrates simple receipt of messages over the ROS system.
    */
   void chatterCallback(const std_msgs::String::ConstPtr& msg)
   {
     ROS_INFO("I heard: [%s]", msg->data.c_str());
   }

   int main(int argc, char **argv)
   {
     /**
      * The ros::init() function needs to see argc and argv so that it can perform
      * any ROS arguments and name remapping that were provided at the command line. For programmatic
      * remappings you can use a different version of init() which takes remappings
      * directly, but for most command-line programs, passing argc and argv is the easiest
      * way to do it.  The third argument to init() is the name of the node.
      *
      * You must call one of the versions of ros::init() before using any other
      * part of the ROS system.
      */
     ros::init(argc, argv, "listener");

     /**
      * NodeHandle is the main access point to communications with the ROS system.
     * The first NodeHandle constructed will fully initialize this node, and the last
      * NodeHandle destructed will close down the node.
      */
     ros::NodeHandle n;

     /**
      * The subscribe() call is how you tell ROS that you want to receive messages
      * on a given topic.  This invokes a call to the ROS
      * master node, which keeps a registry of who is publishing and who
      * is subscribing.  Messages are passed to a callback function, here
      * called chatterCallback.  subscribe() returns a Subscriber object that you
      * must hold on to until you want to unsubscribe.  When all copies of the Subscriber
      * object go out of scope, this callback will automatically be unsubscribed from
      * this topic.
      *
      * The second parameter to the subscribe() function is the size of the message
      * queue.  If messages are arriving faster than they are being processed, this
      * is the number of messages that will be buffered up before beginning to throw
      * away the oldest ones.
      */
     ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

     /**
      * ros::spin() will enter a loop, pumping callbacks.  With this version, all
      * callbacks will be called from within this thread (the main one).  ros::spin()
      * will exit when Ctrl-C is pressed, or the node is shutdown by the master.
      */
     ros::spin();

     return 0;
   }

2.2 代码说明

下面我们将逐条解释代码,当然,之前解释过的代码就不再赘述了。

   void chatterCallback(const std_msgs::String::ConstPtr& msg)
   {
     ROS_INFO("I heard: [%s]", msg->data.c_str());
   }

这是一个回调函数,当接收到 chatter 话题的时候就会被调用。消息是以 boost shared_ptr 指针的形式传输,这就意味着你可以存储它而又不需要复制数据。

     ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

告诉 master 我们要订阅 chatter 话题上的消息。当有消息发布到这个话题时,ROS 就会调用 chatterCallback() 函数。第二个参数是队列大小,以防我们处理消息的速度不够快,当缓存达到 1000 条消息后,再有新的消息到来就将开始丢弃先前接收的消息。

NodeHandle::subscribe() 返回 ros::Subscriber 对象,你必须让它处于活动状态直到你不再想订阅该消息。当这个对象销毁时,它将自动退订 chatter 话题的消息。

有各种不同的 NodeHandle::subscribe() 函数,允许你指定类的成员函数,甚至是 Boost.Function 对象可以调用的任何数据类型。roscpp overview 提供了更为详尽的信息。

     ros::spin();

ros::spin() 进入自循环,可以尽可能快的调用消息回调函数。如果没有消息到达,它不会占用很多 CPU,所以不用担心。一旦 ros::ok() 返回 false,ros::spin() 就会立刻跳出自循环。这有可能是 ros::shutdown() 被调用,或者是用户按下了 Ctrl-C,使得 master 告诉节点要终止运行。也有可能是节点被人为关闭的。

还有其他的方法进行回调,但在这里我们不涉及。想要了解,可以参考 roscpp_tutorials package 里的一些 demo 应用。需要更为详尽的信息,可以参考 roscpp overview。

下边,我们来总结一下:

  • 列表项初始化ROS系统
  • 列表项订阅 chatter 话题
  • 列表项进入自循环,等待消息的到达
  • 列表项当消息到达,调用 chatterCallback() 函数

3 编译节点

之前教程中使用 catkin_create_pkg 创建了 package.xml 和 CMakeLists.txt 文件。

生成的 CMakeLists.txt 看起来应该是这样(在 Creating Msgs and Srvs 教程中的修改和未被使用的注释和例子都被移除了):

https://raw.github.com/ros/catkin_tutorials/master/create_package_modified/catkin_ws/src/beginner_tutorials/CMakeLists.txt

    cmake_minimum_required(VERSION 2.8.3)
    project(beginner_tutorials)

    ## Find catkin and any catkin packages
    find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)

    ## Declare ROS messages and services
    add_message_files(DIRECTORY msg FILES Num.msg)
    add_service_files(DIRECTORY srv FILES AddTwoInts.srv)

    ## Generate added messages and services
    generate_messages(DEPENDENCIES std_msgs)

    ## Declare a catkin package
    catkin_package()

在 CMakeLists.txt 文件末尾加入几条语句:

include_directories(include ${catkin_INCLUDE_DIRS})

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})

结果,CMakeLists.txt 文件看起来大概是这样:

https://raw.github.com/ros/catkin_tutorials/master/create_package_pubsub/catkin_ws/src/beginner_tutorials/CMakeLists.txt

    cmake_minimum_required(VERSION 2.8.3)
    project(beginner_tutorials)

    ## Find catkin and any catkin packages
    find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)

    ## Declare ROS messages and services
    add_message_files(FILES Num.msg)
    add_service_files(FILES AddTwoInts.srv)

    ## Generate added messages and services
    generate_messages(DEPENDENCIES std_msgs)

    ## Declare a catkin package
    catkin_package()

    ## Build talker and listener
    include_directories(include ${catkin_INCLUDE_DIRS})

    add_executable(talker src/talker.cpp)
    target_link_libraries(talker ${catkin_LIBRARIES})
    add_dependencies(talker beginner_tutorials_generate_messages_cpp)

    add_executable(listener src/listener.cpp)
    target_link_libraries(listener ${catkin_LIBRARIES})
    add_dependencies(listener beginner_tutorials_generate_messages_cpp)

这会生成两个可执行文件, talker 和 listener, 默认存储到 devel space 目录下,具体是在~/catkin_ws/devel/lib/ 中。

现在要为可执行文件添加对生成的消息文件的依赖:

add_dependencies(talker beginner_tutorials_generate_messages_cpp)

这样就可以确保自定义消息的头文件在被使用之前已经被生成。因为 catkin 把所有的 package 并行的编译,所以如果你要使用其他 catkin 工作空间中其他 package 的消息,你同样也需要添加对他们各自生成的消息文件的依赖。当然,如果在 Groovy 版本下,你可以使用下边的这个变量来添加对所有必须的文件依赖:

add_dependencies(talker ${catkin_EXPORTED_TARGETS})

你可以直接调用可执行文件,也可以使用 rosrun 来调用他们。他们不会被安装到 /bin 路径下,因为那样会改变系统的 PATH 环境变量。如果你确定要将可执行文件安装到该路径下,你需要设置安装位置,请参考 catkin/CMakeLists.txt

如果需要关于 CMakeLists.txt 更详细的信息,请参考 catkin/CMakeLists.txt

现在运行 catkin_make:

# In your catkin workspace
$ catkin_make  

注意:如果你是添加了新的 package,你需要通过 --force-cmake 选项告诉 catkin 进行强制编译。参考 catkin/Tutorials/using_a_workspace#With_catkin_make。

既然已经编写好了发布器和订阅器,下面让我们来测试消息发布器和订阅器。

说明

全部案例示例如下,有兴趣自主学习(目录有变化,/opt/ros_ws):

1561651095959

测试消息发布器和订阅器

本部分将测试上一教程所写的消息发布器和订阅器。

1 启动发布器

确保roscore可用,并运行:

$ roscore

catkin specific 如果使用catkin,确保你在调用catkin_make后,在运行你自己的程序前,已经source了catkin工作空间下的setup.sh文件:

# In your catkin workspace
$ cd ~/catkin_ws
$ source /home/shiyanlou/catkin_ws/devel/setup.zsh

在上一节中,我们编写了一个发布器"talker",现在运行它:

$ rosrun beginner_tutorials talker      (C++)

如果出错,请仔细检查错误,直接查看示例使用如下命令:

$ rosrun roscpp_tutorials talker  (C++)

1561651167554

你将看到如下的输出信息:

[INFO] [WallTime: 1314931831.774057] hello world 1314931831.77
[INFO] [WallTime: 1314931832.775497] hello world 1314931832.77
[INFO] [WallTime: 1314931833.778937] hello world 1314931833.78
[INFO] [WallTime: 1314931834.782059] hello world 1314931834.78
[INFO] [WallTime: 1314931835.784853] hello world 1314931835.78
[INFO] [WallTime: 1314931836.788106] hello world 1314931836.79

1561651192262

发布器节点已经启动运行。现在需要一个订阅器节点来接受发布的消息。

2 启动订阅器

上一教程,我们编写了一个名为"listener"的订阅器节点。现在运行它:

$ rosrun beginner_tutorials listener     (C++)

你将会看到如下的输出信息:

[INFO] [WallTime: 1314931969.258941] /listener_17657_1314931968795I heard hello world 1314931969.26
[INFO] [WallTime: 1314931970.262246] /listener_17657_1314931968795I heard hello world 1314931970.26
[INFO] [WallTime: 1314931971.266348] /listener_17657_1314931968795I heard hello world 1314931971.26
[INFO] [WallTime: 1314931972.270429] /listener_17657_1314931968795I heard hello world 1314931972.27
[INFO] [WallTime: 1314931973.274382] /listener_17657_1314931968795I heard hello world 1314931973.27
[INFO] [WallTime: 1314931974.277694] /listener_17657_1314931968795I heard hello world 1314931974.28
[INFO] [WallTime: 1314931975.283708] /listener_17657_1314931968795I heard hello world 1314931975.28

1561651262945

补充学习

40

41

42

43

44

45

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

本部分介绍如何用 C++ 编写服务器 Service 和客户端 Client 节点。

1 编写Service节点

这里,我们将创建一个简单的service节点(add_two_ints_server),该节点将接收到两个整形数字,并返回它们的和。

进入先前你在catkin workspace教程中所创建的beginner_tutorials包所在的目录:

$ cd ~/catkin_ws/src/beginner_tutorials

请确保已经按照 新建AddTwoInts.srv 教程的步骤创建了本教程所需要的srv(确保选择了对应的编译系统catkin)。

1.1 代码

在 beginner_tutorials 包中创建src/add_two_ints_server.cpp文件,并复制粘贴下面的代码:

    #include "ros/ros.h"
    #include "beginner_tutorials/AddTwoInts.h"

    bool add(beginner_tutorials::AddTwoInts::Request  &req,
             beginner_tutorials::AddTwoInts::Response &res)
    {
      res.sum = req.a + req.b;
      ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
      ROS_INFO("sending back response: [%ld]", (long int)res.sum);
      return true;
    }

    int main(int argc, char **argv)
    {
      ros::init(argc, argv, "add_two_ints_server");
      ros::NodeHandle n;

      ros::ServiceServer service = n.advertiseService("add_two_ints", add);
      ROS_INFO("Ready to add two ints.");
      ros::spin();

      return 0;
    }

1.2 代码解释

现在,让我们来逐步分析代码。

    #include "ros/ros.h"
    #include "beginner_tutorials/AddTwoInts.h"

beginner_tutorials/AddTwoInts.h是由编译系统自动根据我们先前创建的 srv 文件生成的对应该 srv 文件的头文件。

    bool add(beginner_tutorials::AddTwoInts::Request  &req,
             beginner_tutorials::AddTwoInts::Response &res)

这个函数提供两个int值求和的服务,int值从request里面获取,而返回数据装入response内,这些数据类型都定义在srv文件内部,函数返回一个boolean值。

    {
      res.sum = req.a + req.b;
      ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
      ROS_INFO("sending back response: [%ld]", (long int)res.sum);
      return true;
    }

现在,两个int值已经相加,并存入了response。然后一些关于request和response的信息被记录下来。最后,service完成计算后返回true值。

      ros::ServiceServer service = n.advertiseService("add_two_ints", add);

这里,service已经建立起来,并在ROS内发布出来。

2 编写Client节点

2.1 代码

在 beginner_tutorials 包中创建src/add_two_ints_client.cpp文件,并复制粘贴下面的代码:

    #include "ros/ros.h"
    #include "beginner_tutorials/AddTwoInts.h"
    #include <cstdlib>

    int main(int argc, char **argv)
    {
      ros::init(argc, argv, "add_two_ints_client");
      if (argc != 3)
      {
        ROS_INFO("usage: add_two_ints_client X Y");
        return 1;
      }

      ros::NodeHandle n;
      ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
       beginner_tutorials::AddTwoInts srv;
       srv.request.a = atoll(argv[1]);
       srv.request.b = atoll(argv[2]);
       if (client.call(srv))
       {
         ROS_INFO("Sum: %ld", (long int)srv.response.sum);
       }
       else
       {
        ROS_ERROR("Failed to call service add_two_ints");
         return 1;
       }

      return 0;
     }

2.2 代码解释

现在,让我们来逐步分析代码。

      ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");

这段代码为add_two_ints service创建一个client。ros::ServiceClient 对象待会用来调用service。

      beginner_tutorials::AddTwoInts srv;
      srv.request.a = atoll(argv[1]);
      srv.request.b = atoll(argv[2]);

这里,我们实例化一个由ROS编译系统自动生成的service类,并给其request成员赋值。一个service类包含两个成员request和response。同时也包括两个类定义Request和Response。

       if (client.call(srv))

这段代码是在调用service。由于service的调用是模态过程(调用的时候占用进程阻止其他代码的执行),一旦调用完成,将返回调用结果。如果service调用成功,call()函数将返回true,srv.response里面的值将是合法的值。如果调用失败,call()函数将返回false,srv.response里面的值将是非法的。

3 编译节点

再来编辑一下 beginner_tutorials 里面的CMakeLists.txt,文件位于~/catkin_ws/src/beginner_tutorials/CMakeLists.txt,并将下面的代码添加在文件末尾:

源码链接:https://raw.github.com/ros/catkin_tutorials/master/create_package_srvclient/catkin_ws/src/beginner_tutorials/CMakeLists.txt

   add_executable(add_two_ints_server src/add_two_ints_server.cpp)
   target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
   add_dependencies(add_two_ints_server beginner_tutorials_gencpp)

   add_executable(add_two_ints_client src/add_two_ints_client.cpp)
   target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
   add_dependencies(add_two_ints_client beginner_tutorials_gencpp)

这段代码将生成两个可执行程序add_two_ints_serveradd_two_ints_client,这两个可执行程序默认被放在你的devel space 下的包目录下,默认为~/catkin_ws/devel/lib/share/<package name>。你可以直接调用可执行程序,或者使用rosrun命令去调用它们。它们不会被装在/bin目录下,因为当你在你的系统里安装这个包的时候,这样做会污染PATH变量。如果你希望在安装的时候你的可执行程序在PATH变量里面,你需要设置一下install target,请参考:catkin/CMakeLists.txt

关于CMakeLists.txt文件更详细的描述请参考:catkin/CMakeLists.txt

现在运行catkin_make命令:

# In your catkin workspace
cd ~/catkin_ws
catkin_make

如果你的编译过程因为某些原因而失败:

  • 确保你已经依照先前的 新建 AddTwoInts.srv 教程里的步骤完成操作。

现在你已经学会如何编写简单的服务器 Service 和客户端 Client,开始测试简单的Service和Client吧。

测试简单的服务器和客户端

本部分将测试之前所写的Service和Client。

1 运行Service

让我们从运行Service开始:

$ rosrun beginner_tutorials add_two_ints_server     (C++)

你将会看到如下类似的信息:

Ready to add two ints.

1561651395378

2 运行Client

现在,运行Client并附带一些参数:

$ rosrun beginner_tutorials add_two_ints_client 1 3     (C++)

你将会看到如下类似的信息:

request: x=1, y=3
sending back response: [4]

1561651449146

3 关于Service和Client节点的更多例子

如果你想做更深入的研究,或者是得到更多的操作示例,你可以从这个链接找到:

https://github.com/fairlight1337/ros_service_examples/

一个简单的Client与Service的组合程序演示了自定义消息类型的使用方法。 如果Service节点是用C++写的,写Client用C++,Python或者是LISP都可以。

补充学习

68

69

70

71

72

73

74

75

76

录制与回放数据

本部分将教你如何将ROS系统运行过程中的数据录制到一个.bag文件中,然后通过回放数据来重现相似的运行过程。

1 录制数据(通过创建一个bag文件)

本小节将教你如何记录ROS系统运行时的话题数据,记录的话题数据将会积累保存到bag文件中。

首先,执行以下命令:

$ roscore
$ rosrun turtlesim turtlesim_node 
$ rosrun turtlesim turtle_teleop_key

以上操作将会启动两个节点——一个turtlesim可视化节点和一个turtlesim键盘控制节点。在运行turtlesim键盘控制节点的终端窗口中你应该会看到如下类似信息:

Reading from keyboard
---------------------------
Use arrow keys to move the turtle.

这时按下键盘上的方向键应该会让turtle运动起来。需要注意的是要想控制turtle运动你必须先选中启动turtlesim键盘控制节点时所在的终端窗口而不是显示虚拟turtle所在的窗口。

1.1 录制所有发布的话题

首先让我们来检查看当前系统中发布的所有话题。要完成此操作请打开一个新终端并执行:

$ rostopic list -v

这应该会生成以下输出:

Published topics:
 * /turtle1/color_sensor [turtlesim/Color] 1 publisher
 * /turtle1/command_velocity [turtlesim/Velocity] 1 publisher
 * /rosout [roslib/Log] 2 publishers
 * /rosout_agg [roslib/Log] 1 publisher
 * /turtle1/pose [turtlesim/Pose] 1 publisher

Subscribed topics:
 * /turtle1/command_velocity [turtlesim/Velocity] 1 subscriber
 * /rosout [roslib/Log] 1 subscriber

上面所发布话题部分列出的话题消息是唯一可以被录制保存到文件中的的话题消息,因为只有消息已经发布了才可以被录制。/turtle1/command_velocity话题是 teleop_turtle 节点所发布的命令消息并作为 turtlesim 节点的输入。而/turtle1/color_sensor和/turtle1/pose是 turtlesim 节点发布出来的话题消息。

现在我们开始录制。打开一个新的终端窗口,在终端中执行以下命令:

$ mkdir ~/bagfiles
$ cd ~/bagfiles
$ rosbag record -a

在这里我们先建立一个用于录制的临时目录,然后在该目录下运行 rosbag record 命令,并附加 -a 选项,该选项表示将当前发布的所有话题数据都录制保存到一个bag文件中。

然后回到 turtle_teleop 节点所在的终端窗口并控制 turtle 随处移动10秒钟左右。

在运行 rosbag record 命令的窗口中按 Ctrl-C 退出该命令。现在检查看 ~/bagfiles 目录中的内容,你应该会看到一个以年份、日期和时间命名并以 .bag 作为后缀的文件。这个就是 bag 文件,它包含 rosbag record 运行期间所有节点发布的话题。

2 检查并回访bag文件

现在我们已经使用 rosbag record 命令录制了一个 bag 文件,接下来我们可以使用 rosbag info 检查看它的内容,使用 rosbag play 命令回放出来。接下来我们首先会看到在 bag 文件中都录制了哪些东西。我们可以使用 info 命令,该命令可以检查看 bag 文件中的内容而无需回放出来。在 bag 文件所在的目录下执行以下命令:

$ rosbag info <your bagfile>

你应该会看到如下类似信息:

bag: 2009-12-04-15-02-56.bag
version: 1.2
start_time: 1259967777871383000
end_time: 1259967797238692999
length: 19367309999
topics:
  - name: /rosout
    count: 2
    datatype: roslib/Log
    md5sum: acffd30cd6b6de30f120938c17c593fb
  - name: /turtle1/color_sensor
    count: 1122
    datatype: turtlesim/Color
    md5sum: 353891e354491c51aabe32df673fb446
  - name: /turtle1/command_velocity
    count: 23
    datatype: turtlesim/Velocity
    md5sum: 9d5c2dcd348ac8f76ce2a4307bd63a13
  - name: /turtle1/pose
    count: 1121
    datatype: turtlesim/Pose
    md5sum: 863b248d5016ca62ea2e895ae5265cf9

这些信息告诉你 bag 文件中所包含话题的名称、类型和消息数量。我们可以看到,在之前使用 rostopic 命令查看到的五个已公告的话题中,其实只有其中的四个在我们录制过程中发布了消息。因为我们带 -a 参数选项运行 rosbag record 命令时系统会录制下所有节点发布的所有消息。

下一步是回放 bag 文件以再现系统运行过程。首先在 turtle_teleop_key 节点运行时所在的终端窗口中按 Ctrl+C 退出该节点。让 turtlesim 节点继续运行。在终端中 bag 文件所在目录下运行以下命令:

$ rosbag play <your bagfile>

在这个窗口中你应该会立即看到如下类似信息:

Hit space to pause.
[ INFO] 1260210510.566003000: Sleeping 0.200 seconds after advertising /rosout...
[ INFO] 1260210510.766582000: Done sleeping.

[ INFO] 1260210510.872197000: Sleeping 0.200 seconds after advertising /turtle1/pose...
[ INFO] 1260210511.072384000: Done sleeping.

[ INFO] 1260210511.277391000: Sleeping 0.200 seconds after advertising /turtle1/color_sensor...
[ INFO] 1260210511.477525000: Done sleeping.

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

最终/turtle1/command_velocity话题将会被发布,同时在turtuelsim虚拟画面中turtle应该会像之前你通过turtle_teleop_key节点控制它一样开始移动。从运行rosbag play到turtle开始移动时所经历时间应该近似等于之前在本教程开始部分运行rosbag record后到开始按下键盘发出控制命令时所经历时间。你可以通过-s参数选项让rosbag play命令等待一段时间跳过bag文件初始部分后再真正开始回放。最后一个可能比较有趣的参数选项是-r选项,它允许你通过设定一个参数来改变消息发布速率。如果你执行:

$ rosbag play -r 2 <your bagfile>

你应该会看到turtle的运动轨迹有点不同了,这时的轨迹应该是相当于当你以两倍的速度通过按键发布控制命令时产生的轨迹。

3 录制数据子集

当运行一个复杂的系统时,比如PR2软件系统,会有几百个话题被发布,有些话题会发布大量数据(比如包含摄像头图像流的话题)。在这种系统中,要想把所有话题都录制保存到硬盘上的单个bag文件中是不切实际的。rosbag record命令支持只录制某些特别指定的话题到单个bag文件中,这样就允许用户只录制他们感兴趣的话题。

如果还有turtlesim节点在运行,先退出他们,然后重新启动(relaunch)键盘控制节点相关的启动文件(launch file):

$ rosrun turtlesim turtlesim_node 
$ rosrun turtlesim turtle_teleop_key

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

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

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

现在检查看bag文件中的内容(rosbag info subset.bag)。你应该会看到如下类似信息,里面只包含录制时指定的话题:

bag: subset.bag
version: 1.2
start_time: 3196900000000
end_time: 3215400000000
length: 18500000000
topics:
  - name: /turtle1/command_velocity
    count: 8
    datatype: turtlesim/Velocity
    md5sum: 9d5c2dcd348ac8f76ce2a4307bd63a13
  - name: /turtle1/pose
    count: 1068
    datatype: turtlesim/Pose
    md5sum: 863b248d5016ca62ea2e895ae5265cf9

4 rosbag record/play 命令的局限性

在前述部分中你可能已经注意到了turtle的路径可能并没有完全地映射到原先通过键盘控制时产生的路径上——整体形状应该是差不多的,但没有完全一样。造成该问题的原因是turtlesim的移动路径对系统定时精度的变化非常敏感。rosbag受制于其本身的性能无法完全复制录制时的系统运行行为,rosplay也一样。对于像turtlesim这样的节点,当处理消息的过程中系统定时发生极小变化时也会使其行为发生微妙变化,用户不应该期望能够完美的模仿系统行为。

现在你已经学会了如何录制和回放数据,接下来我们开始学习如何使用 roswtf来检查系统故障。

补充学习

77

78

79

roswtf入门

**简介:**本教程介绍了roswtf工具的基本使用方法。

**重点:**roswtf

**难度:**初级

在开始本教程之前请确保roscore没在运行。

1 安装检查

roswtf可以检查你的ROS系统并尝试发现问题,我们来试看:

$ roscd
$ roswtf

你应该会看到(各种详细的输出信息):

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

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

Cannot communicate with master, ignoring graph checks

1561651563043

如果你的 ROS 安装没问题,你应该会看到类似上面的输出信息,它的含义是: Stack: ros: roswtf根据你当前目录来确定需要做的检查,这里表示你是在rosstack中启动roswtf。 Static checks summary:这是有关文件系统问题的检查报告,现在的检查结果表示文件系统没问题。 Cannot communicate with master, ignoring graph checks(无法与master连接,忽略图(graph)检查):roscore没有运行,所以roswtf没有做运行时检查。

2 运行时检查(在有ROS节点运行时)

在这一步中,我们需要让Master运行起来,所以得先启动roscore。

$ roscore

1561651593727

现在按照相同的顺序再次运行以下命令:

$ roscd
$ roswtf

你应该会看到:

Stack: ros
================================================================================
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

1561651613982

既然roscore已经运行了所以roswtf做了一些运行时检查。检查过程的长短取决于正在运行的ROS节点数量,可能会花费很长时间才能完成。正如你看到的,这一次出现了警告:

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

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

3 错误报告

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

接下来我们在ROS_PACKAGE_PATH 环境变量中设置一个 bad值,并退出roscore以简化检查输出信息。

$ 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看能否帮你解决。

⚠️ **GitHub.com Fallback** ⚠️