# ROS 基础教程

## ROS 基础教程

![](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MedHKydXkEP1HG53Q2Q%2F-MgFdRwJbmjMEwhVCILi%2F-MgFjhw0m_5oYyRVr7mh%2Fimage.png?alt=media\&token=18c1f79a-ac78-4105-b944-cefc22bee397)

### 关于本课程

​ **本课程为ROS基础课程：**&#x7B80;要介绍ROS 系统框架，ROS 节点、话题、消息；如何创建 ROS 工作空间；如何新建 ROS 功能包；如何编译 ROS 功能包；如何使用编程语言（c++ 和 python）来编写简单的节点和服务；如何发布和订阅话题；如何启动和调用服务；如何自定义消息类型；rosbag 消息的录制和回放；以及如何使用 ROS 的常用工具等。

​ ROS 基础课程主要针对的是 ROS 初学习者，若你已熟悉ROS 基础，可直接跳过，进入 路威实验指导书 学习。此课程可作为基础工具使用，有问题时查阅。

## 第一章 安装并配置 ROS 环境

### 1.1 安装 ROS

**注意事项:** 如果你是使用类似 apt 这样的软件管理器来安装 ROS 的，那么安装后这些软件包将不具备写入权限，当前系统用户比如你自己也无法对这些软件包进行修改编辑。当你的开发涉及到 ROS 软件包源码层面的操作或者在创建一个新的 ROS 软件包时，你应该是在一个具备读写权限的目录下工作，就像在你当前系统用户的 home 目录下一样。

​ 按照官方 [ROS 安装说明 ](http://wiki.ros.org/cn/ROS/Installation)完成安装，具体步骤如下：

#### 1.1.1 选择相应的 ROS 版本

​ 本文以 Melodic 为例，可根据自己的需求选择相应的版本，安装过程一样

​ <http://wiki.ros.org/cn/ROS/Installation>

#### 1.1.2 ROS 安装指南

​ 点击上图中的 “**ROS Melodic Morenia**"，进入到 [ROS Melodic 安装指南](http://wiki.ros.org/cn/melodic/Installation)

![image-20210730173254534](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuW57CSBMx5tRR2%2Fimage-20210730173254534.png?generation=1628073370739908\&alt=media)

#### 1.1.3 选择要安装的平台

​ 下面以 Ubuntu 为例，点击上图红色框中的”Ubuntu"，即可进入教程在 Ubuntu 中安装ROS Melodic。

**注意事项**：

​ ROS Melodic Morenia 发行版可以安装在 Ubuntu Artful (17.10)，Bionic (18.04 LTS)，本例将其安装在 Ubuntu 18.04 上。

#### 1.1.4 在 Ubuntu 中安装 ROS Melodic

**参考**： [在 Ubuntu 中安装 ROS Melodic](http://wiki.ros.org/cn/melodic/Installation/Ubuntu)

1. **配置 Ubuntu 软件仓库**

   配置你的 Ubuntu 软件仓库(repositories) 以允许 "restricted"、"universe" 和

   "multiverse"这三种安装模式。 你可以[按照 Ubuntu 中的配置指南](https://help.ubuntu.com/community/Repositories/Ubuntu)来完成配置。
2. **添加 sources.list**

   ​ 配置软件下载源，设置你的电脑以从 packages.ros.org 接收软件，命令如下：

   ```bash
   sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
   ```

   ​ 若下载速度缓慢，推荐就近选择一个镜像源替换上面的命令。例如，Tsinghua University为：

   ```bash
   sudo sh -c '. /etc/lsb-release && echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/ros-latest.list'
   ```
3. **添加公钥**

   ```bash
   sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
   ```

   ​ 若无法连接到密钥服务器，可以尝试替换上面命令中的 `hkp://keyserver.ubuntu.com:80` 为 `hkp://pgp.mit.edu:80` 。若使用代理，可尝试使用curl命令替换apt-key方式。

   ```bash
   curl -sSL 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xC1CF6E31E6BADE8868B172B4F42ED6FBAB17C654' | sudo apt-key add -
   ```
4. **开始安装**

   首先，确保你的 Debian 包索引是最新的：

   ```bash
   sudo apt update
   ```

   在 ROS 中有很多不同的库和工具。我们提供了四种默认选项供你开始。你也可以单独安装 ROS 的软件包。如果下面的步骤出现问题，你可以用这个源来替代上面提到的源：[ros-shadow-fixed](http://wiki.ros.org/ShadowRepository)

   **桌面完整版（推荐）：**&#x5305;含 ROS、[rqt](http://wiki.ros.org/rqt)、[rviz](http://wiki.ros.org/rviz)、机器人通用库、2D/3D 模拟器、导航以及 2D/3D 感知包。

   ```bash
   sudo apt install ros-melodic-desktop-full
   ```

   **桌面版：** 包含 ROS，[rqt](http://wiki.ros.org/rqt)，[rviz](http://wiki.ros.org/rviz) 和机器人通用库

   ```bash
   sudo apt install ros-melodic-desktop
   ```

   **ROS-基础包：** 包含 ROS 包，构建和通信库。没有图形界面工具。

   ```bash
   sudo apt install ros-melodic-ros-base
   ```

   **单独的包：** 你也可以安装某个指定的ROS软件包（使用软件包名称替换掉下面的PACKAGE）：

   ```bash
   sudo apt install ros-melodic-PACKAGE
   ```

   如：

   ```bash
   sudo apt install ros-melodic-slam-gmapping
   ```

   要查找可用软件包，请运行：

   ```
   apt search ros-melodic
   ```

   **建议安装的 ROS 配置：** 我们可以根据不同的需求来选择不同的配置来安装ROS 软件包，由于我们只需要学习 ROS 的基础，所以只需要安装 ROS 桌面版即可：

   ```bash
   sudo apt install ros-melodic-desktop
   ```
5. **初始化rosdep**

   在你使用 ROS 之前，需要初始化 `rosdep`。`rosdep` 让你能够轻松地安装被想要编译的源代码，或被某些 ROS 核心组件需要的系统依赖。

   ```bash
   sudo rosdep init
   rosdep update
   ```
6. **配置环境**

   用户在使用 ROS 的功能包前，需要添加ROS 环境变量，这样系统才能找到相应的ROS 功能包。 下面的命令就是在当前 bash 会话中添加 ROS 环境变量，但其只在当前会话中有效， 每次打开新的终端时，都要重新设置，所以不建议使用此法。

   ```bash
   source /opt/ros/melodic/setup.bash
   ```

   可以将配置命令写入到用户的.bashrc 文件中，然后使用 source 命令将该配置文件生效， 这样每次新开bash 会话（如打开终端）ROS 环境变量自动添加，很方便：

   ```bash
   echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc 
   source ~/.bashrc
   ```

   如果你不只安装了一个 ROS 发行版，可在\~/.bashrc 文件中进行修改，使用你需要的ROS版本的setup.bash。

   到目前为止，你已经安装了运行核心 ROS 包所需的内容。
7. **构建工厂依赖**

   到目前为止，你已经安装了运行核心 ROS 包所需的内容。为了创建和管理自己的 ROS 工作区，有各种各样的工具和需求分别分布。例如：[rosinstall](http://wiki.ros.org/rosinstall) 是一个经常使用的命令行工具，它使你能够轻松地从一个命令下载许多 ROS 包的源树。

   要安装这个工具和其他构建ROS包的依赖项，请运行:

   ```bash
   sudo apt-get install python-rosinstall python-rosinstall-generator python-wstool build-essential
   ```

   为了创建和管理自己的 ROS 工作区，需要有各种各样的工具。例如：rosinstall 是一个经常使用的命令行工具，它使你能够轻松地从一个命令下载许多 ROS 包的源树。
8. **测试 ROS 是否安装成功**

   打开终端，输入roscore 命令

   ```bash
   roscore
   ```

   ![image-20210802173642228](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuYsDLcb25DNyrt%2Fimage-20210802173642228.png?generation=1628073370709663\&alt=media)

   然后再打开一个终端输入rostopic list，若出现 话题 /rosout 和 /rosout\_agg 则说明 ROS 安装成功。

```bash
rostopic list
```

![image-20210802173713320](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuZm9reu72r8EjG%2Fimage-20210802173713320.png?generation=1628073370528095\&alt=media)

### 1.2 查看 ROS 环境

​ 在安装 ROS 期间，你会看到提示说需要 source 多个 setup.\*sh 文件中的某一个，或者甚至提示添加这条'source'命令到你的启动脚本里面。这些操作是必须的，因为 ROS 是依赖于某种组合空间的概念，而这种概念就是通过配置脚本环境来实现的。这可以让针对不同版本或者不同软件包集的开发更加容易。如果你在查找和使用ROS 软件包方面遇到了问题，首先要检查脚本环境是否配置正确。一个检查像ROS\_ROOT 和ROS\_PACKAGE\_PATH 这样的环境变量的方法是，通过以下命令查看：

```bash
$ printenv | grep ROS
```

![image-20210802173901507](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyu_jqbxnvsjhhlv%2Fimage-20210802173901507.png?generation=1628073370606294\&alt=media)

​ 由上图所示，可以看到我们的ROS 环境变量，ROS 的功能包路径，ROS 安装的根目录，ROS 版本等，如果没发现我们之前的配置 ROS 环境变量，说明之前的配置失败。则需重新按上述配置过程重新执行。 到此为止ROS 的安装和环境配置已完成。

### 1.3 创建 ROS 工作空间

​ 在安装并配置好ROS 环境后，我们需要创建自己的 ROS 工作空间来存放我们开发的功能包，下面我们开始创建一个 [catkin 工作空间](http://wiki.ros.org/catkin/workspaces)：

```bash
$ mkdir -p ~/catkin_ws/src
$ cd ~/catkin_ws/
$ catkin_make
```

​ 执行上述代码即可在当前用户的主目录下创建工作空间 catkin\_ws。上述代码的作用分别是 在当前用户主目录下创建两级目录 catkin\_ws/src，而 src 目录将存储我们开发的功能包源码，然后进入目录 catkin\_ws 下，执行 ROS 编译命令 catkin\_make，则完成初始化 ROS工作空间 catkin\_ws。[catkin\_make ](http://wiki.ros.org/catkin/commands/catkin_make)命令在 [catkin 工作空间 ](http://wiki.ros.org/catkin/workspaces)中是一个非常方便的工具。第一次在你的工作空间中运行它时，它会在你的 'src' 文件夹里创建一个 CMakeLists.txt 的链接。

![image-20210802174046769](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuaXQM8tvKSSfiS%2Fimage-20210802174046769.png?generation=1628073370552936\&alt=media)

​ 此时输入 ls，则看到 catkin\_ws 下多了两个目录 build 和 devel，则说明 ROS 工作空间 catkin\_ws 创建成功

![image-20210802174104269](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyubi7b_CYMQiaiS%2Fimage-20210802174104269.png?generation=1628073370531109\&alt=media)

​ **对于** **Python 3** **用户**，在一个空的 catkin 工作空间中第一次运行 [catkin\_make ](http://wiki.ros.org/catkin/commands/catkin_make)的命令应为

```bash
$ catkin_make -DPYTHON_EXECUTABLE=/usr/bin/python3
```

​ 这将会配置 [catkin\_make ](http://wiki.ros.org/catkin/commands/catkin_make)使用 Python 3.你可以在随后的构建中只使用 catkin\_make。

**配置工作空间：**

​ 在创建好ROS 工作空间 catkin\_ws 后 ，我们需要将该工作空间添加 ROS 环境变量中， 以便后面在此开发的ROS 功能包能被系统找 到。配置的方法就是使能 工作空间 catkin\_ws 下的 devel 目录下的 setup.bash，具体操作如下：

​ 在当前系统的 .bashrc 文件末尾加入下面一行命令：

```bash
source /home/ubuntu/catkin_ws/devel/setup.bash
```

​ **注意：**&#x5C06;上述 setup.bash 文件的目录换成你的工作空间中的路径，且必须使用绝对路径。

![image-20210802174614875](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyucHY8XUIK-9BYE%2Fimage-20210802174614875.png?generation=1628073370556184\&alt=media)

​ 然后 source .bashrc 文件，使当前的配置生效：

```bash
source ~/.bashrc
```

​ 最后查看工作空间是否已配置正确，ROS\_PACKAGE\_PATH 环境变量是否包含你的工作空间目录，命令如下：

```bash
echo $ROS_PACKAGE_PATH
```

​ 若输出的ROS 包路径中含有刚才创建的工作空间，说明配置成功。

## 第二章 ROS 文件系统介绍

​ 本课将介绍 ROS 文件系统概念，包括命令行工具 roscd、rosls 和 [rospack ](http://wiki.ros.org/rospack)的使用。

### 2.1 准备工作

​ 首先安装 ros-tutorials 程序包，命令如下：

```bash
$ sudo apt-get install ros-<distro>-ros-tutorials
```

​ 其中，将命令中的换成对应的 ROS 版本。

### 2.2 文件系统概念

**Packages:** 软件包，是 ROS 应用程序代码的组织单元，每个软件包都可以包含程序库、可执行文件、脚本或者其它手动创建的东西。

**Manifest** **(**[**package.xml**](http://wiki.ros.org/catkin/package.xml)**):** 清单，是对于'软件包'相关信息的描述，用于定义软件包相关元信息之间的依赖关系，这些信息包括版本、维护者和许可协议等

### 2.3 文件系统工具

​ 程序代码是分布在众多 ROS 软件包当中，当使用命令行工具（比如 ls 和 cd ）来浏览时会非常繁琐，因此 ROS 提供了专门的命令工具来简化这些操作。

#### 2.3.1 rospack

​ [rospack ](http://wiki.ros.org/rospack)允许你获取软件包的有关信息，可使用 rospack help 来查看其所有的参数选项。其中常用的是 rospack 中 find 参数选项，该选项可以返回软件包的路径信息。顾名思义，就是当我们要快速查找 ROS 功能包路径时使用。

​ **用法：**

```bash
rospack find [包名称]
```

​ **示例：**

```bash
rospack find jubot_driver
```

![image-20210802175228570](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyudrsPbqAjbxxep%2Fimage-20210802175228570.png?generation=1628073369892817\&alt=media)

#### 2.3.2 roscd

​ roscd 是 [rosbash ](http://wiki.ros.org/rosbash)命令集中的一部分，它允许你直接切换（[cd](http://ss64.com/bash/cd.html)）工作目录到某个软件包或者软件包集当中。相比于 linux 的 cd 命令，我们不需知道该 ROS 功能包的具体路径，也不需一步一步切换到软件包中，只需知道功能包的名称，就可以直接切换到该目录下，非常方便。

​ **用法：**

```bash
roscd [本地包名称[/子目录]]
```

​ **示例：**

```bash
roscd jubot_driver
```

​ 为了验证我们已经切换到了 roscpp 软件包目录下，可以使用 Unix 命令 [pwd ](http://ss64.com/bash/pwd.html)来输出当前工作目录：

```bash
pwd
```

![image-20210802175411257](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyue3xn0bKSIUKSG%2Fimage-20210802175411257.png?generation=1628073369884749\&alt=media)

​ 你可以看到该功能包的路径和之前使用 rospack find 得到的路径名称是一样的。

和ROS 中的其它工具一样，[roscd ](http://wiki.ros.org/roscd) 只能切换到那些路径已经包含在 [ROS\_PACKAGE\_PATH ](http://wiki.ros.org/ROS/EnvironmentVariables#ROS_PACKAGE_PATH) 环境变量中的软件包，要查看[ROS\_PACKAGE\_PATH ](http://wiki.ros.org/ROS/EnvironmentVariables#ROS_PACKAGE_PATH) 中包含的路径可以输入：

```bash
echo $ROS_PACKAGE_PATH
```

​ [ROS\_PACKAGE\_PATH ](http://wiki.ros.org/ROS/EnvironmentVariables#ROS_PACKAGE_PATH)环境变量应该包含那些保存有 ROS 软件包的路径，并且每个路径之间用冒号分隔开来。若要在 [ROS\_PACKAGE\_PATH ](http://wiki.ros.org/ROS/EnvironmentVariables#ROS_PACKAGE_PATH)中添加更多其它路径，每条路径要使用冒号':'分隔。

​ 使用 [roscd ](http://wiki.ros.org/roscd)也可以切换到一个软件包或软件包集的子目录中，命令如下。

```bash
$ roscd jubotdriver/src
$ pwd
```

![image-20210802175613225](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyufgO95oOoicmbE%2Fimage-20210802175613225.png?generation=1628073369885789\&alt=media)

#### 2.3.3 rosls

​ [rosls ](http://wiki.ros.org/rosbash#rosls)是[rosbash ](http://wiki.ros.org/rosbash)命令集中的一部分，它允许你直接按软件包的名称而不是绝对路径执行 [ls ](http://ss64.com/bash/ls.html)命令。也就是说，使用 rosls 可直接查看当前功能包中包含的文件和文件夹信息，不需要切换到该功能包目录下。

**用法：**

```bash
$ rosls [本地包名称[/子目录]]
```

**示例：**

```bash
$ rosls jubot_driver
```

可看到该功能包中包含的文件和文件夹，如下图所示

![image-20210802180611935](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuhbIHkM32Y0a4y%2Fimage-20210802180611935.png?generation=1628073369881683\&alt=media)

#### 2.3.4 Tab 自动补全

​ 当要输入一个完整的软件包名称时会变得比较繁琐，甚至很容易出错，幸运的是，一些ROS 工具支持 [TAB 补全 ](http://en.wikipedia.org/wiki/Command_line_completion)的功能。也就是说，我们只需输入功能包名的前面一部分，然后按 Tab 键，就会自动补全，就跟我们编写代码时代码自动补全一样。

**示例：**

```bash
$ roscd roscpp_tut<<< 现在请按 TAB 键 >>>
```

​ 当按 **TAB** 键后，命令行中应该会自动补充剩余部分：

```bash
$ roscd roscpp_tutorials/
```

​ 这应该有用，因为 roscpp\_tutorials 是当前唯一一个名称以 roscpp\_tut 作为开头的 ROS 软件包。

​ 但若输入的部分软件包名不能唯一指定想要的软件包，此时按 TAB 键，则会列出所有以 输入内容开头的 ROS 软件包，然后我们继续输入更多的软件包名称，直到可以唯一指定该软件包。

### 2.4 ROS功能包目录结构

​ ROS 功能包都遵循同样的文件目录命名格式，然后将相应的程序文件分别放于指定的目录下，以自己创建的 jubot\_driver 功能包为例：

![image-20210802180611935](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuhbIHkM32Y0a4y%2Fimage-20210802180611935.png?generation=1628073369881683\&alt=media)

​ WorkSpace --- 自定义的工作空间

```bash
|--- build:编译空间，用于存放CMake和catkin的缓存信息、配置信息和其他中间文件。

|--- devel:开发空间，用于存放编译后生成的目标文件，包括头文件、动态&静态链接库、可执行文件等。

|--- src: 源码 下可有多个package

    |-- package：功能包(ROS基本单元)包含多个节点、库与配置文件，包名所有字母小写，只能由字母、数字与下划线组成

        |-- CMakeLists.txt 配置编译规则，比如源文件、依赖项、目标文件

        |-- package.xml 包信息，比如:包名、版本、作者、依赖项...

        |-- scripts 存储python文件

        |-- src 存储C++源文件

        |-- include 头文件

        |-- msg 消息通信格式文件

        |-- srv 服务通信格式文件

        |-- action 动作格式文件

        |-- launch 可一次性运行多个节点的启动文件

        |-- config 包的配置信息

    |-- CMakeLists.txt: 编译的基本配置
```

​ 功能包中的 src 目录用于存放功能包的源代码，launch 目录用于存放功能包的启动文件，script 目录用于存放脚本文件,param 目录用于存放参数加载文件，map 目录用于存放地图文件，include 用于存放 头文件，config 目录用于存储相差配置文件。

## 第三章 创建 ROS 功能包

### 3.1 catkin 程序包的组成

​ 一个程序包要想称为 catkin 程序包必须符合以下要求：

​ 1） 该程序包必须包含[ catkin compliant package.xml ](http://wiki.ros.org/catkin/package.xml)文件,这个 package.xml 文件提供有关程序包的元信息，也就是其依赖信息。

​ 2） 程序包必须包含一个 [catkin 版本的CMakeLists.txt ](http://wiki.ros.org/catkin/CMakeLists.txt)文件（此为功能包的编译文件），而[ Catkin metapackages ](http://wiki.ros.org/catkin/package.xml#Metapackages)中必须包含一个 对CMakeList.txt 文件的引用。

​ 3） 每个目录下只能有一个程序包。（程序包中不能包含其他程序包） 这意味着在同一个目录下不能有嵌套的或者多个程序包存在。

​ 最简单的程序包看起来就像这样，最少要有一个 CMakeLists.txt 和package.xml：

```bash
my_package/ 
CMakeLists.txt 
package.xml
```

### 3.2 catkin 工作空间中的程序包

​ 推荐使用[catkin 工作空间](http://wiki.ros.org/catkin/workspaces)开发catkin 程序包，但是你也可以[单独开发(standalone)](http://wiki.ros.org/catkin/build_standalone)catkin 软件包，只不过相对较复杂，你需要自己将功能包的信息配置到ROS 环境变量中，也就是说让ROS 能找到这个功能包。

​ **一个简单的工作空间如下：**

```bash
workspace_folder/         -- 工作空间名
  src/                    -- SOURCE SPACE 存放功能包
    CMakeLists.txt        -- 'Toplevel' CMake file, provided by catkin
    package_1/              -- 功能包 1
      CMakeLists.txt      -- CMakeLists.txt file for package_1
      package.xml          -- Package manifest for package_1
      ...
    package_n/              -- 功能包 n
      CATKIN_IGNORE       -- Optional empty file to exclude package_n from being processed
      CMakeLists.txt      -- CMakeLists.txt file for package_n
      package.xml          -- Package manifest for package_n
      ...
  build/                  -- BUILD 空间
    CATKIN_IGNORE         -- Keeps catkin from walking this directory
  devel/                  -- DEVELOPMENT SPACE (set by CATKIN_DEVEL_PREFIX)
    bin/
    etc/
    include/
    lib/
    share/
    .catkin
    env.bash
    setup.bash
    setup.sh
    ...
  install/                -- INSTALL SPACE (set by CMAKE_INSTALL_PREFIX)
    bin/
    etc/
    include/
    lib/
    share/
    .catkin             
    env.bash
    setup.bash
    setup.sh
    ...
```

### 3.3 创建一个 catkin 程序包

​ 下面将演示如何使用 [catkin\_create\_pkg ](http://wiki.ros.org/catkin/commands/catkin_create_pkg)命令来创建一个新的ROS 功能包以及创建之后都能做些什么。

​ 首先 参考第 01 课中的《**1.3** **创建** **ROS** **工作空间**》的创建 catkin 工作空间教程，创建一个 catkin 工作空间 catkin\_ws，

​ 然后进入到其 src 目录下，其中\~ 代表的是当前用户的主目录：

```bash
$ cd ~/catkin_ws/src
```

​ 然后使用 catkin\_create\_pkg 命令来创建一个名为'beginner\_tutorials'的新程序包，并且添加了其所需要的依赖包 std\_msgs、roscpp 和 rospy，它们分别是标准的输入输出信息流，编写 c++代码的依赖和编写 python 代码的依赖：

```bash
$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
```

​ 这将会在 catkin\_ws 中的 src 目录下创建一个名为 beginner\_tutorials 的文件夹， 这个文件夹里面包含一个 [package.xml ](http://wiki.ros.org/catkin/package.xml)文件和一个[CMakeLists.txt ](http://wiki.ros.org/catkin/CMakeLists.txt)文件，这两个文件都已经自动包含了部分你在执行 catkin\_create\_pkg 命令时提供的信息。

​ catkin\_create\_pkg 命令格式如下：

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

​ 其中，package\_name 为你要创建的功能包名，不能为空，depend1、depend2、depend3 等为该功能包所需的依赖包，根据实际需要添加：

​ catkin\_create\_pkg 命令也有更多的高级功能，在 [catkin/commands/catkin\_create\_pkg](http://wiki.ros.org/catkin/commands/catkin_create_pkg)中有描述。

### 3.4 程序包依赖关系

​ 一个ROS 程序包的运行需要其它程序包，程序包之间的依赖关系分为一级依赖和间接依赖。

​ **一级依赖**就是批这个程序包直接依赖的程序包，如前面创建的功能包 beginner\_tutorials 的一级依赖包 std\_msgs、roscpp 和 rospy。

​ **间接依赖**包就是指该功能包直接依赖的程序包的依赖包，如程序包 rospy 的依赖包就是功能 包 beginner\_tutorials 的间接依赖包。

​ 下面就以功能包 beginner\_tutorials 为例，使用 rospack 命令工具来查看功能包的依赖包。

#### 3.4.1 一级依赖

​ 使用 rospack 命令工具的 depends1 命令参数来查看功能包 beginner\_tutorials 的一级依赖包。

```bash
$ rospack depends1 beginner_tutorials
```

![image-20210803142318565](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuiRd94pEQ-2Q8g%2Fimage-20210803142318565.png?generation=1628073370522801\&alt=media)

​ 可见 rospack 列出了在运行创建功能包命令 catkin\_create\_pkg 时作为参数的依赖包，而这些依赖包在创建功能包时保存到 **package.xml** 文件中。

​ 执行下面代码，进入到 功能包 beginner\_tutorials 目录下，使用cat 命令将该目录下的功能包信息文件 package.xml 内容输出到终端，可见其依赖包 std\_msgs、roscpp 和 rospy 已保存到 **package.xml** 文件中。

```bash
$ roscd beginner_tutorials
$ cat package.xml
```

```markup
<package>
...
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
...
</package>
```

#### 3.4.2 间接依赖

​ 现在使用 rospack 命令工具的 depends1 命令参数来查看功能包 beginner\_tutorials 的**间接**依赖包。以其的依赖包 rospy 为例，查看 rospy 的依赖包。

```bash
$ rospack depends1 rospy
```

![image-20210803142600421](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyujRkm-upss6Enl%2Fimage-20210803142600421.png?generation=1628073370541036\&alt=media)

​ 一个程序包还可以有好几个间接的依赖包，我们有时需要同时输出其所有的依赖包，这时可以使用 rospack 工具的 depends 命令参数 来递归检测出该功能包所有的依赖包。

```bash
$ rospack depends beginner_tutorials
```

```bash
cpp_common 
rostime 
roscpp_traits 
roscpp_serialization 
genmsg
genpy 
message_runtime 
rosconsole 
std_msgs 
rosgraph_msgs 
xmlrpcpp
roscpp 
rosgraph 
catkin 
rospack 
roslib 
rospy
```

### 3.5 自定义你的程序包

​ 下面将剖析创建功能包时生成的每个文件并详细描述这些文件的组成部分以及如何自定义这些文件。

#### 3.5.1 自定义 package.xml

​ 创建功能包时会在功能包目录下自动生成一个 [package.xml ](http://wiki.ros.org/catkin/package.xml)文件，它里面存储该功能包的依赖信息和一些标签信息。下面让我们一起来看看新生成的 [package.xml ](http://wiki.ros.org/catkin/package.xml)文件以及每一个需要注意的标签元素。

​ 1）描述标签

​ 首先更新描述标签：

```markup
<description>The beginner_tutorials package</description>
```

​ 将描述信息修改为任何你喜欢的内容，但是按照约定第一句话应该简短一些，因为它覆盖了程序包的范围。如果用一句话难以描述完全那就需要换行了。

​ 2）作者标签

```markup
<maintainer email="user@todo.todo">user</maintainer>
```

​ 这是 [package.xml ](http://wiki.ros.org/catkin/package.xml)中要求填写的一个重要标签，因为它能够让其他人联系到程序包的相关人员。至少需要填写一个维护者名称，但如果有需要的话你可以添加多个。除了在标签里面填写维护者的名称外，还应该在标签的 email 属性中填写邮箱地址。

​ 3）开源协议标签

```markup
<license>TODO</license>
```

​ 你可以选择一种许可协议并将它填写到这里。一些常见的开源许可协议有 BSD、MIT、Boost Software License、GPLv2、GPLv3、LGPLv2.1 和 LGPLv3。你可以在 [Open Source](http://opensource.org/licenses/alphabetical) [Initiative ](http://opensource.org/licenses/alphabetical)中阅读其中的若干个许可协议的相关信息。对于本教程我们将使用BSD 协议，因为 ROS 核心组件的剩余部分已经使用了该协议：

```markup
<license>BSD</license>
```

​ 4）依赖项标签

​ 接下来的标签用来描述程序包的各种依赖项，这些依赖项分为 build\_depend、buildtool\_depend、exec\_depend、test\_depend。关于这些标签的更详细介绍请参考 [Catkin](http://wiki.ros.org/catkin/package.xml#Build.2C_Run.2C_and_Test_Dependencies) [Dependencies ](http://wiki.ros.org/catkin/package.xml#Build.2C_Run.2C_and_Test_Dependencies)相关的文档。在之前的操作中，因为我们将 std\_msgs、roscpp、和 rospy 作为 [catkin\_create\_pkg ](http://wiki.ros.org/catkin/commands/catkin_create_pkg)命令的参数，所以生成的依赖项看起来如下：

```markup
<!-- The *_depend tags are used to specify dependencies -->
<!-- Dependencies can be catkin packages or system dependencies -->
<!-- Examples: -->
<!-- Use build_depend for packages you need at compile time: -->
<!--    <build_depend>genmsg</build_depend> -->
<!-- Use buildtool_depend for build tool packages: -->
<!--    <buildtool_depend>catkin</buildtool_depend> -->
<!-- Use exec_depend for packages you need at runtime: -->
<!--    <exec_depend>python-yaml</exec_depend> -->
<!-- Use test_depend for packages you need only for testing: -->
<!--    <test_depend>gtest</test_depend> -->
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
```

​ 除了 catkin 中默认提供的 buildtool\_depend，所有我们列出的依赖包都已经被添加到build\_depend 标签中。在本例中，因为在编译和运行时我们需要用到所有指定的依赖包， 因此还需要将每一个依赖包分别添加到 exec\_depend 标签中：

```markup
<buildtool_depend>catkin</buildtool_depend>

<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
```

​ 5）包名和版本标签

​ 我们可以根据需要修改功能包名，但建议不要随意修改，同时可以修改功能包版本号， 比如当我们要更新发布不同版本该功能包时，或上传到 git 中时。

```markup
<name>beginner_tutorials</name>
<version>0.1.0</version>
```

​ 6）最后完成的 package.xml

​ 现在看下面最后去掉了注释和未使用标签后的 [package.xml ](http://wiki.ros.org/catkin/package.xml)文件就显得更加简洁了：

```markup
<?xml version="1.0"?>
<package format="2">
    <name>beginner_tutorials</name>
    <version>0.1.0</version>
    <description>The beginner_tutorials package</description>

    <maintainer email="you@yourdomain.tld">Your Name</maintainer>
    <license>BSD</license>
    <url type="website">http://wiki.ros.org/beginner_tutorials</url>
    <author email="you@yourdomain.tld">Jane Doe</author>

    <buildtool_depend>catkin</buildtool_depend>

    <build_depend>roscpp</build_depend>
    <build_depend>rospy</build_depend>
    <build_depend>std_msgs</build_depend>

    <exec_depend>roscpp</exec_depend>
    <exec_depend>rospy</exec_depend>
    <exec_depend>std_msgs</exec_depend>    
</package>
```

#### 3.5.2 自定义 CMakeLists.txt

​ 到此，已详细介绍了这个包含程序包元信息的 [package.xml ](http://wiki.ros.org/catkin/package.xml)文件，现在可以继续下面的教程了。[catkin\_create\_pkg ](http://wiki.ros.org/catkin/commands/catkin_create_pkg)命令创建功能包时生成的[CMakeLists.txt ](http://wiki.ros.org/catkin/CMakeLists.txt)文件将在后续关于编译ROS 程序代码的教程中讲述。

​ 现在已经创建了一个新的ROS 程序包，接下来我们开始编译这个程序包。

## 第四章 编译 ROS 程序包

​ **描述:** 本节将介绍ROS 程序包的编译方法

​ 在开始编译上一节课创建的程序包前，要安装所需的系统依赖项。而通过 *apt* 或者其它软件包管理工具来安装 ROS 的，系统已经默认安装好所有依赖项。而通过下载 ROS 源码自己编译的则需要安装。

​ 而我们之前 是通过 apt 安装的ROS 系统，因此无需再另行安装依赖项。

​ 在编译功能包之前 ，首先在终端 source 你的ROS 环境配置(setup)文件，操作指令如下：

```bash
$ source /opt/ros/melodic/setup.bash
```

​ 如果你已经在当前用户的.bashrc 文件中配置过 ROS 环境 source 操作，则无需要执行上面操作，每次打开bash 会话后，会自动生效。若没有在.bashrc 文件中配置过，则每次打开bash 会话时都必须执行上述操作，使用ROS 环境变量生效，否则找不到ROS 系统程序。

### 4.1 使用 catkin\_make

​ [catkin\_make ](http://wiki.ros.org/catkin/commands/catkin_make)是一个命令行工具，它简化了 catkin 的标准工作流程。可以认为[catkin\_make ](http://wiki.ros.org/catkin/commands/catkin_make)是在 CMake 标准工作流程中依次调用了 cmake 和 make。

​ **使用方法:**

```bash
# 在 catkin 工作空间目录下执行此命令
$ catkin_make [make_targets] [-DCMAKE_VARIABLES=...]
```

​ 下面我们将通过一个示例来演示CMake 工作流程，CMake 标准工作流程主要可以分为以下几个步骤：

​ **注意：**&#x4E0B;面的命令只是演示用，运行是无效的

```bash
# 在一个 CMake 项目里
$ mkdir build
$ cd build
$ cmake ..
$ make
$ make install    # (可选)
```

​ 这些是 linux 系统的操作命令，创建 build 目录，确定如果进入 build 目录，执行 cmake 生成Makefile 文件，然后 make 操作根据前面生成的Makefile 文件的编译规则来编译程序， 最后也可以通过 make install 将程序安装到系统指定目录。对初学者来说只需要简单了解一下即可。

​ 每个 CMake 工程在编译时都会执行这个操作过程。相反，多个 catkin 项目可以放在工作空间中一起编译，工作流程如下：

```bash
# 在 catkin 工作空间目录下执行此命令
$ catkin_make
$ catkin_make install # (可选)
```

​ 上述命令会编译工作空间中的 src 文件夹下的所有功能包。对初学者来说，我们只需要知道在工作空间目录下使用 catkin\_make 编译功能包即可，无需在 catkin\_make 后面加入其他参数，待后面熟悉 ROS 系统后再深入了解。想更深入了解请参考[REP128](http://ros.org/reps/rep-0128.html)。

​ 如果你的源代码不在工作空间中的默认 src 目录下（\~/catkin\_ws/src)，比如说存放在了工作空间的my\_src 目录下，那么可以这样使用 catkin\_make 来编译你的功能包：

```bash
# 在工作空间路径下
$ catkin_make --source my_src
$ catkin_make install --source my_src    # (optionally)
```

​ 上面操作仅供了解，不建议这么做，建议最好将功能包存放在工作空间的 src 目录下， 这样的话更方便操作。对于 [catkin\_make ](http://wiki.ros.org/catkin/commands/catkin_make)更高级的使用方法，请参考[catkin/commands/catkin\_make](http://wiki.ros.org/catkin/commands/catkin_make)。

### 4.2 开始编译你的程序包

​ 按照上一节课内容，我们已经创建好了一个工作空间 catkin\_ws 和一个名为 beginner\_tutorials 的功能包。现在切换到工作空间目录下并查看 src 文件夹：

```bash
$ cd ~/catkin_ws/
$ ls src
```

![image-20210803150619305](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyukUXuBD9_VmKMN%2Fimage-20210803150619305.png?generation=1628073370541246\&alt=media)

​ 可以看到一个名为 beginner\_tutorials 的文件夹，这就是上一节课通过catkin\_create\_pkg 创建的功能包。现在可以使用 [catkin\_make ](http://wiki.ros.org/catkin/commands/catkin_make)来编译它了:

```bash
$ catkin_make
```

![image-20210803150644201](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyulEmmGsGfScAl2%2Fimage-20210803150644201.png?generation=1628073370896130\&alt=media)

​ 你可以看到很多 cmake 和 make 输出的信息：

```bash
Base path: /home/user/catkin_ws Source space: /home/user/catkin_ws/src
Build space: /home/user/catkin_ws/build Devel space: /home/user/catkin_ws/devel Install space: /home/user/catkin_ws/install ####
#### Running command: "cmake /home/user/catkin_ws/src
-DCATKIN_DEVEL_PREFIX=/home/user/catkin_ws/devel
-DCMAKE_INSTALL_PREFIX=/home/user/catkin_ws/install" in "/home/user/catkin_ws/build" ####
-- The C compiler identification is GNU 4.2.1
-- The CXX compiler identification is Clang 4.0.0
-- Checking whether C compiler has -isysroot
-- Checking whether C compiler has -isysroot - yes
-- Checking whether C compiler supports OSX deployment target flag
-- Checking whether C compiler supports OSX deployment target flag - yes
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Using CATKIN_DEVEL_PREFIX: /tmp/catkin_ws/devel
-- Using CMAKE_PREFIX_PATH: /opt/ros/groovy
-- This workspace overlays: /opt/ros/groovy
-- Found PythonInterp: /usr/bin/python (found version "2.7.1")
-- Found PY_em: /usr/lib/python2.7/dist-packages/em.pyc
-- Found gtest: gtests will be built
-- catkin 0.5.51
-- BUILD_SHARED_LIBS is on
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

\-- \~\~ traversing packages in topological order: -- \~\~ - beginner\_tutorials -- ~~\~\~\~\~\~\~\~\~\~~~ -- +++ add\_subdirectory(beginner\_tutorials) -- Configuring done -- Generating done -- Build files have been written to: /home/user/catkin\_ws/build ####

**Running command: "make -j4" in "/home/user/catkin\_ws/build" ####**

```
​    [catkin_make ](http://wiki.ros.org/catkin/commands/catkin_make)首先输出它所使用到的每个**空间**所在的路径。更多关于**空间**的信息，请参考 [REP128 ](http://ros.org/reps/rep-0128.html)和 [catkin/workspaces](http://wiki.ros.org/catkin/workspaces)。  

​    需要注意的是这些空间存在默认配置，build 和 devel 文件夹已经在 catkin 工作空间自动生成了，使用 **ls** 查看:

~~~bash
$ ls
    build 
    devel 
    src
```

​ build 目录是 [build space ](http://wiki.ros.org/catkin/workspaces#Build_Space)的默认所在位置，同时 cmake 和 make 也是在这里被调用来配置并编译你的程序包。devel 目录是 [devel space ](http://wiki.ros.org/catkin/workspaces#Development_.28Devel.29_Space)的默认所在位置, 同时也是在安装功能包之前存放可执行文件和库文件的地方。

​ 现在我们已成功编译了一个 ROS 功能包，下一节课我们将介绍 ROS 节点。

## 第五章 ROS节点学习

​ 本节课主要介绍 ROS 图（graph）概念 并讨论 [roscore](http://wiki.ros.org/roscore)、[rosnode ](http://wiki.ros.org/rosnode)和 [rosrun ](http://wiki.ros.org/rosrun)命令行工具的使用。

### 5.1 先决条件

​ 在本节课中我们将使用到一个轻量级的模拟器，请使用以下命令来安装：

```bash
$ sudo apt-get install ros-<distro>-ros-tutorials
```

​ 上面命令中的 要替换成你的 ROS 版本，下面以 ROS kinetic 版本为例

```bash
$ sudo apt-get install ros-kinetic-ros-tutorials
```

![image-20210803151230051](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyumkmfsKSRRPFg3%2Fimage-20210803151230051.png?generation=1628073370678561\&alt=media)

​ 由于我们已经安装过了，所以无需再安装。

### 5.2 图概念概述

[Nodes](http://wiki.ros.org/Nodes):节点,一个节点即为一个可执行文件（程序代码在编译链接之后生成的文件，可在系统上直接运行），它可以通过ROS 与其它节点进行通信。

[Messages](http://wiki.ros.org/Messages):消息，消息是一种ROS 数据类型，用于订阅或发布到一个话题。ROS 系统提供了常用 消息类型，我们也可以自定义消息类型。

[Topics](http://wiki.ros.org/Topics):话题,节点可以发布消息到话题，也可以订阅话题以接收消息。话题好比像一个广播频道，广播员可以通过其播放当天的交通路况信息，用户也可订阅这个频道，收听交通路状信息。其中广播员和用户就相当于节点，而交通路况信息就相当于消息。

[Master](http://wiki.ros.org/Master):节点管理器，ROS 名称服务 (比如帮助节点找到彼此)。节点管理器就好比如一个婚姻介绍所，男女两方就像 ROS 节点，在婚姻介绍所登记注册信息，并通过婚姻介绍所认识彼此，相互交流。

[rosout](http://wiki.ros.org/rosout): ROS 中相当于 stdout/stderr，用于日志输出。

[roscore](http://wiki.ros.org/roscore): 主机+ rosout + 参数服务器 (参数服务器会在后面介绍)。

### 5.3 节点

​ 一个节点其实就是ROS 程序包中的一个可执行文件。ROS 节点可以使用ROS 客户库与其他节点通信。节点可以发布或接收一个或多个话题。节点也可以提供或使用某种服务。

​ 节点是ros 中非常重要的一个概念，为了帮助初学者理解这个概念，这里举一个通俗的例子：

​ 例如，咱们有一个机器人，和一个遥控器，当这个机器人和遥控器开始工作后，就是两个节点。遥控器起到下达指令的作用；机器人负责监听遥控器下达的指令，完成相应动作。从这里我们可以看出，节点是一个能执行特定工作任务的工作单元，并且能够相互通信，从而实现一个机器人系统的整体功能。在这里我们把遥控器和机器人简单定义为两个节点，实际上在机器人中根据控制器、传感器、执行机构等不同组成模块，还可以将其进一步细分为更多的节点，这个是根据用户编写的程序来定义的。

### 5.4 客户端库

ROS 客户端库允许使用不同编程语言编写的节点之间互相通信:

rospy = python 客户端库

roscpp = c++ 客户端库

例如，两个节点使用不同代码编写，一个用 c++代码编写的，一个用 Python 代码编写的， 它们之间仍可以通信，只需要发布或订阅指定消息类型的话题即可。

### 5.5 rosmaster

​ 在运行所有ROS 程序前，首先要运行 roscore 命令，用来启动rosmaster：

```bash
$ roscore
```

​ 然后你可看到你使用的ROS 版本和版本号，ROS\_MASTER\_URI 和master 里程的pid，具体输出信息如下图所示:

![image-20210803151548098](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyun85PV1Qx5n7jH%2Fimage-20210803151548098.png?generation=1628073370711973\&alt=media)

​ 如果 roscore 运行后无法正常初始化，很有可能是存在网络配置问题。参见[网络设置](http://www.ros.org/wiki/ROS/NetworkSetup#Single_machine_configuration)[——单机设置](http://www.ros.org/wiki/ROS/NetworkSetup#Single_machine_configuration)

​ 如果你使用的是多ROS 系统间组网，比如 ROS 机器人和 ubuntu 虚拟机，请检查你虚拟机配置的ROS\_MASTER\_URI 是否正确。

​ 如果 roscore 不能初始化并提示缺少权限，这可能是因为\~/.ros 文件夹归属于 root 用户（只有 root 用户才能访问），需要修改该文件夹的用户归属关系：

```bash
$ sudo chown -R <your_username> ~/.ros
```

​  用你的系统用户名替换。

### 5.6 使用 rosnode

​ 当打开一个新的终端时，你的运行环境会复位，同时你的\~/.bashrc 文件会复原。如果你在运行类似于rosnode 的指令时出现一些问题，也许是你需要添加一些环境设置文件（如ROS 环境变量和创建的工作空间配置）到你的\~/.bashrc 或者手动重新配置他们。

​ rosnode 显示当前运行的ROS 节点信息。Rosnode list 指令列出活跃的节点:

```bash
$ rosnode list
```

​ 你会看到 /rosout，如下图所示:

![image-20210803151722306](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuofmf6x2qYIaH2%2Fimage-20210803151722306.png?generation=1628073370528708\&alt=media)

​ 这表示当前只有一个节点在运行: [rosout](http://wiki.ros.org/rosout)。因为这个节点用于收集和记录节点调试输出信息，所以它总是在运行的。

​ **rosnode info** 命令将会返回指定节点的信息：

```bash
$ rosnode info /rosout
```

​ 我们可以看到一些有关于 rosout 的信息, 如它发布和订阅的话题名及其对应的消息类型，以及它支持的服务。

![image-20210803151808346](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuqREiOTbWtNemL%2Fimage-20210803151808346.png?generation=1628073370530595\&alt=media)

### 5.7 使用 rosrun

​ rosrun 允许你使用包名直接运行一个包内的节点(而不需要知道这个包的路径)。但注意的是，使用rosrun 运行节点前，一定要先运行 roscore，否则将打不开该节点。

​ 用法:

```bash
$ rosrun [package_name] [node_name]
```

​ 其中，package\_name 为功能包名，node\_name 为该功能包内的节点名。

​ 现在我们可以运行 turtlesim 包中的 turtlesim\_node。打开在一个**新的终端，**&#x6267;行下面命令:

```bash
$ rosrun turtlesim turtlesim_node
```

​ 你会看到 turtlesim 窗口:

![image-20210803151927360](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyusg7tSlT972pNt%2Fimage-20210803151927360.png?generation=1628073370529292\&alt=media)

​ **注意:** 这里的 turtle 海龟图标可能和你的 turtlesim 窗口不同。别担心，这里有[许多版本的](http://wiki.ros.org/Distributions#Current_Distribution_Releases)[turtle ](http://wiki.ros.org/Distributions#Current_Distribution_Releases)，而你的是一个惊喜!（一个可爱的小彩蛋～）

​ 在一个 **新的终端**:

```bash
$ rosnode list
```

​ 你会看见多出一个节点 turtlesim :

![image-20210803152037153](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyutG9xfcMn0pSJB%2Fimage-20210803152037153.png?generation=1628073370533334\&alt=media)

​ **通过命令行重新配置节点名称，这是 ROS 的一个强大特性。** ​ 首先关闭刚打开的 turtlesim\_node 节点，可以直接关闭 turtlesim 窗口来停止运行节点 ，或者回到 rosrun turtlesim 的终端，然后使用`ctrl + C`组合键来关闭turtlesim\_node 节点。 ​ 现在让我们重新运行该节点，但是这次我们使用[Remapping Argument ](http://wiki.ros.org/Remapping%20Arguments)改变节点名称:

```bash
$ rosrun turtlesim turtlesim_node name:=my_turtle
```

```
 现在，我们退回使用 rosnode list:
```

```bash
$ rosnode list
```

​ 你会看到 my\_turtle 出现在正在运行的节点列表中:

![image-20210803152145256](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuuq40u_Yessiso%2Fimage-20210803152145256.png?generation=1628073370531126\&alt=media)

​ **注意:** 如果你仍看到 /turtlesim 在列表中，这可能意味着你在终端中使用 ctrl-C 停止节点而不是关闭窗口，或者你没有$ROS\_HOSTNAME 环境变量，这在 [Network Setup - Single](http://www.ros.org/wiki/ROS/NetworkSetup#Single_machine_configuration) [Machine Configuration ](http://www.ros.org/wiki/ROS/NetworkSetup#Single_machine_configuration)中有定义。你可以尝试清除 rosnode 列表，通过: $ rosnode cleanup ， 也可以 关闭 roscore 后，再重新运行 roscore。

​ 我们可以看到新的/my\_turtle 节点。使用另外一个 rosnode ping 指令来测试：

```bash
$ rosnode ping my_turtle
    rosnode: node is [/my_turtle]
    pinging /my_turtle with a timeout of 3.0s
    xmlrpc reply from http://aqy:42235/    time=1.152992ms 
    xmlrpc reply from http://aqy:42235/    time=1.120090ms     
    xmlrpc reply from http://aqy:42235/    time=1.700878ms 
    xmlrpc reply from http://aqy:42235/    time=1.127958ms
```

​ 如果您想关闭 turtlesim\_node 节点，请按下“Ctrl-C”组合键。

### 5.8 回顾

​ 本节所讲的内容:

* roscore = ros+core : master (提供 ROS 名称服务) + rosout (stdout/stderr 日志输出) + parameter server (参数服务器稍后介绍)
* rosnode = ros+node : 获取 ROS 节点信息的工具 。
* rosrun = ros+run : 运行指定功能包的节点.

  到这里，你已经了解了 ROS 节点是如何工作的，下一节，我们将介绍一下 ROS 话题。

## 第六章 ROS 话题学习

​ **描述：**&#x672C;节课将介绍 ROS 话题（topics）以及如何使用 [rostopic ](http://wiki.ros.org/rostopic)和 [rxplot ](http://wiki.ros.org/rxplot)命令行工具。

### 6.1 话题基础

#### 6.1.1 roscore

​ 首先确保 roscore 已经运行, 打开一个新的终端：

```bash
$ roscore
```

​ 如果你已运行了roscore，那么执行 roscore 命令，你可能会看到下面的错误信息：

```bash
roscore cannot run as another roscore/master is already running. 
Please kill other roscore/master processes before relaunching
```

```
 这是正常的，因为只需要有一个 roscore 在运行就够了。
```

#### 6.1.2 turtlesim

​ 在本节课中我们也会用到 turtlesim，**在一个新的终端中**运行：

```bash
$ rosrun turtlesim turtlesim_node
```

![image-20210803153754494](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuvzuI4dM3utagU%2Fimage-20210803153754494.png?generation=1628073369880263\&alt=media)

#### 6.1.3 通过键盘远程控制 turtle

​ 我们也需要通过键盘来控制 turtle 的运动，**在一个新的终端中**运行：

```bash
$ rosrun turtlesim turtle_teleop_key
```

![image-20210803153839415](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuwdxTVG7JUsCMG%2Fimage-20210803153839415.png?generation=1628073370523133\&alt=media)

​ 现在你可以使用键盘上的方向键来控制 turtle 运动了。如果不能控制，请使你的鼠标光标处于打开 **turtle\_teleop\_key** **节点的终端窗口(即鼠标左键点击该终端窗口)**,以确保你的按键输入能够被捕获。

![image-20210803154040685](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuxwtM4O9QQJHR3%2Fimage-20210803154040685.png?generation=1628073370527665\&alt=media)

​ 现在你可以控制turtle 运动了，下面我们一起来看看这背后发生的事。

### 6.2 ROS 话题（Topics）

​ turtlesim\_node 节点和turtle\_teleop\_key 节点之间是通过一个ROS **话题**来互相通信的。turtle\_teleop\_key 在一个话题上**发布**按键输入消息，而 turtlesim 则**订阅**该话题以接收该消息。下面让我们使用 [rqt\_graph ](http://wiki.ros.org/rqt_graph)来显示当前运行的节点和话题。

​ **注意**：如果你使用的是 electric 或更早期的ROS 版本，那么 rqt 是不可用的，请使用rxgraph 代替。

#### 6.2.1 使用 rqt\_graph

​ rqt\_graph 能够创建一个显示当前系统运行情况的动态图形。rqt\_graph 是 rqt 程序包中的一部分。如果你没有安装，请通过以下命令来安装：

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

​ 请使用你的ROS 版本名称（比如 hydro、indigo、kinetic 、melodic 等）来替换掉， 以 ROS kinetic 版本为例：

```bash
$ sudo apt-get install ros-kinetic-rqt
$ sudo apt-get install ros-kinetic-rqt-common-plugins
```

​ **在一个新终端中**运行:

```bash
$ rosrun rqt_graph rqt_graph
```

​ 你将会看到当前运行的节点和话题关系图：

![image-20210803154252907](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuyskk26E_XQ7Dj%2Fimage-20210803154252907.png?generation=1628073370531700\&alt=media)

​ 如果你将鼠标放在/turtle1/cmd\_vel 上方，相应的 ROS 节点（蓝色和绿色）和话题（红色）就会高亮显示。

​ 正如你所看到的，turtlesim\_node 和 turtle\_teleop\_key 节点正通过一个名为 /turtle1/cmd\_vel 的话题进行通信，从而实现键盘控制海龟移动。

![image-20210803154304256](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyuzF2Q_rtKG9Idf%2Fimage-20210803154304256.png?generation=1628073370554279\&alt=media)

#### 6.2.2 rostopic 介绍

​ 可以通过 rostopic 命令工具来获取有关ROS **话题**的信息。你可以使用帮助选项查看 rostopic 的子命令：

```bash
$ rostopic -h 
    rostopic bw        显示话题使用的带宽
    rostopic echo    在屏幕上打印该话题内容
    rostopic hz        显示该话题被发布的频率
    rostopic list    打印处于活动状态的话题信息
    rostopic pub    发布话题
    rostopic type    打印话题类型
```

​ 接下来我们将使用其中的一些子命令来查看 turtlesim。

#### 6.2.3 使用 rostopic echo

​ rostopic echo 可以显示在某个话题上发布的数据。

​ **用法：**

```bash
rostopic echo [topic]
```

​ 我们在**一个新终端中查看** turtle\_teleop\_key 节点在/turtle1/cmd\_vel 话题上发布的数据.

```bash
$ rostopic echo /turtle1/cmd_vel
```

​ 你可能看不到任何东西因为现在还没有数据发布到该话题上。接下来我们通过按下方向键使 turtle\_teleop\_key 节点发布数据。**记住如果** **turtle** **没有动起来的话就需要你重新选\*\***&#x4E2D;运行 turtle\_teleop\_key 节点时所在的终端窗口。\*\*

​ 现在当你按下向上方向键时应该会看到下面的信息：

![image-20210803154533986](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv-EEs5HZZb0UqD%2Fimage-20210803154533986.png?generation=1628073370556651\&alt=media)

​ 现在让我们再看一下 rqt\_graph（可能需要刷新一下 ROS graph）。正如你所看到的， rostopic echo(红色显示部分）现在也**订阅**了 turtle1/cmd\_vel 话题。

![image-20210803154553392](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv08J2fR4csjRsp%2Fimage-20210803154553392.png?generation=1628073370523939\&alt=media)

#### 6.2.4 使用 rostopic list

​ rostopic list 能够列出所有当前订阅和发布的话题。让我们查看一下 list 子命令需要的参数，在**一个新终端中**运行：

```bash
$ rostopic list -h
    Usage: rostopic list [/topic]

    Options:
    -h, --help    show this help message and exit
    -b BAGFILE, 
    --bag=BAGFILE list topics in .bag file
    -v, --verbose list full details about each topic
    -p    list only publishers
    -s    list only subscribers
```

​ 在 rostopic list 中使用 **verbose** 选项：

```bash
$ rostopic list -v
```

​ 这会显示出有关所发布和订阅的话题及其类型的详细信息。

![image-20210803154722270](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv1CJ1ACYlZhKkw%2Fimage-20210803154722270.png?generation=1628073370717277\&alt=media)

### 6.3 ROS 消息（Messages）

​ 话题之间的通信是通过在节点之间发送ROS **消息**实现的。对于发布器(turtle\_teleop\_key)和订阅器(turtulesim\_node)之间的通信，发布器和订阅器之间必须发送和接收相同**类型**的消息。这意味着话题的**类型**是由发布在它上面的消息**类型**决定的。使用rostopic type 命令可以查看发布在某个话题上的消息**类型**。

#### 6.3.1 使用 rostopic type

​ Rostopic type 命令用来查看所发布话题的消息类型。

​ **用法：**

```bash
rostopic type [topic]
```

​ **示例**：

```bash
$ rostopic type /turtle1/cmd_vel
```

​ 你应该会看到 geometry\_msgs/Twist，如下图：

![image-20210803155015419](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv2e8UcVrGn6j8W%2Fimage-20210803155015419.png?generation=1628073370534785\&alt=media)

​ 我们可以使用 rosmsg 命令来查看消息的详细情况：

```bash
$ rosmsg show geometry_msgs/Twist
```

![image-20210803155036243](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv3GIPGanZ6nb5c%2Fimage-20210803155036243.png?generation=1628073370535159\&alt=media)

​ 现在我们已经知道了 turtlesim 节点所期望的消息类型，接下来我们就可以给 turtle 发布命令了。

### 6.4 继续学习 rostopic

​ 现在我们已经了解了什么是 ROS 的**消息**，接下来我们开始结合消息来使用 rostopic。

#### 6.4.1 使用 rostopic pub

​ rostopic pub 可以把数据发布到当前某个正在广播的话题上。

​ **用法：**

```bash
$rostopic pub [topic] [msg_type] [args]
```

​ **示例：**

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

​ 以上命令会发送一条消息给 turtlesim，让它以 2.0 大小的线速度和 1.8 大小的角速度开始移动。

![image-20210803162428644](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv4i9SO8ihmC371%2Fimage-20210803162428644.png?generation=1628073369882468\&alt=media)

这是一个非常复杂的例子，因此让我们来详细分析一下其中的每一个参数。

rostopic pub 表示这条命令将会发布消息到某个给定的话题。

* -1    表示（单个破折号）这个参数选项使 rostopic 发布一条消息后马上退出。
* /turtle1/cmd\_vel 为消息所发布到的话题名称。
* geometry\_msgs/Twist 为所发布消息的类型。
* \-- （双破折号）这会告诉命令选项解析器接下来的参数部分**都不是**命令选项。这在参数里面包含有破折号-（比如负号）时是必须要添加的。
* '\[2.0, 0.0, 0.0]' '\[0.0, 0.0, 1.8]' 分别是速度消息中线速度 linear 和角速度 angular 在 x,y,z 轴方向的值，单位分别是 m/s 和 rad/s。这些参数其实是按照 YAML 语法格式编写的，这在[ YAML 文档](http://wiki.ros.org/ROS/YAMLCommandLine)中有更多的描述。

​ 你可能已经注意到turtle 已经停止移动了。这是因为这只发布一次消息，保持 3 秒，而要让 turtle 一直运动，则需要以一个稳定的频率比如 1Hz 来发布速度命令，从而来让其保持移动状态。

​ 我们可以使用 rostopic pub -r 命令来发布一个稳定的命令流：

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

​ 这条命令以 1Hz 的频率发布速度命令到速度话题上。

![image-20210803162601839](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv5SLLPydrrlg8z%2Fimage-20210803162601839.png?generation=1628073370542392\&alt=media)

​ 正如你所看到的，turtle 正沿着一个圆形轨迹连续运动。我们可以在**一个新终端**中通过rostopic echo 命令来查看 turtlesim 所发布的数据。

#### 6.4.2 使用 rostopic hz

​ rostopic hz 命令可以用来查看数据发布的频率。

​ **用法：**

```bash
$rostopic hz [topic]
```

​ 我们来看下 turtlesim\_node 发布/turtle/cmd\_vel 时有多快：

```bash
$ rostopic hz /turtle1/cmd_vel
```

​ 你会看到：

![image-20210803162707355](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv60IzKqIz8gYD9%2Fimage-20210803162707355.png?generation=1628073370558985\&alt=media)

​ 现在我们可以知道了 turtlesim 正以大约 1Hz 的频率发布数据给 turtle。我们也可以尝试修改前面发布速度话题的频率，然后再通过此命令看看实际的频率是不是与之接近，大家可以私下尝试下。 ​ 我们也可以结合 rostopic type 和 rosmsg show 命令来获取关于某个话题的更深层次的信息：

```bash
$ rostopic type /turtle1/cmd_vel | rosmsg show
```

​ 到此我们已经完成了通过 rostopic 来查看话题相关情况的过程，接下来我将使用另一个工具来查看turtlesim 发布的数据。

### 6.5 使用 rqt\_plot

​ **注意：**&#x5982;果你使用的是 electric 或更早期的ROS 版本，那么 rqt 命令是不可用的，请使用 [rxplot ](http://wiki.ros.org/rxplot)命令来代替。

​ rqt\_plot 命令可以实时显示一个发布到某个话题上的数据变化图形。这里我们将使用rqt\_plot 命令来绘制正在发布到/turtle1/pose 话题上的数据变化图形。首先，在一个新终端中运行 rqt\_plot 命令：

```bash
$ rosrun rqt_plot rqt_plot
```

​ 这会弹出一个新窗口，在窗口左上角的一个文本框里面你可以添加需要绘制的话题。在 里面输入/turtle1/pose/x 后之前处于禁用状态的加号按钮将会被使能变亮。按一下该按钮， 并对/turtle1/pose/y 重复相同的过程。现在你会在图形中看到turtle 的x-y 位置坐标图。

![image-20210803162818639](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv7Nw-66_ivYii9%2Fimage-20210803162818639.png?generation=1628073370523177\&alt=media)

​ 按下减号按钮会显示一组菜单让你隐藏图形中指定的话题。现在隐藏掉你刚才添加的话题并添加/turtle1/pose/theta，你会看到如下图所示的图形：

![image-20210803163231500](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv8rPeZ3PnlU0JN%2Fimage-20210803163231500.png?generation=1628073370545462\&alt=media)

​ 本节课到此已经结束，我们已经理解了ROS 话题是如何工作的，接下来我们开始学习理解ROS 服务和参数。

## 第七章 学习 ROS 的服务和参数

​ **描述:** 本节将介绍ROS 服务和参数的知识，以及命令行工具 [rosservice ](http://wiki.ros.org/rosservice)和 [rosparam ](http://wiki.ros.org/rosparam)的使用方法。

​ 首先，在两个不同的终端中分别启动 roscore 和 turtlesim\_node 节点，命令如下

```bash
$ roscore
$ rosrun turtlesim turtlesim_node
```

### 7.1 ROS 服务（Services）

​ **服务（services）**&#x662F;节点之间通讯的另一种方式。服务允许节点发送请求（request）并获得一个**响应（response）**

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

​ **使用方法:**

```bash
rosservice list    列出可用服务的信息列表
rosservice call 调用带参数的服务
rosservice type 输出服务的类型
rosservice find 依据类型寻找服务
rosservice uri  输出服务的 ROSRPC uri
```

### 7.2 服务节点

#### 7.2.1 rosservice list

```bash
$ rosservice list
```

​ list 命令显示 turtlesim 节点提供了 9 个服务：重置（reset）, 清除（clear）, 再生（spawn）, 终止（kill），turtle1/set\_pen， /turtle1/teleport\_absolute，/turtle1/teleport\_relative，turtlesim/get\_loggers，和 turtlesim/set\_logger\_level。同时还有另外两个rosout 节点提供的服务:

​ /rosout/get\_loggers and /rosout/set\_logger\_level。

```bash
/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 服务:

#### 7.2.2 rosservice type

​ **使用方法:**

```bash
rosservice type [service]
```

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

```bash
$ rosservice type clear std_srvs/Empty
```

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

#### 7.2.3 rosservice call

**使用方法:**

```bash
rosservice call [service] [args]
```

​ 因为 clear 服务类型是空，所以进行无参数调用：

```bash
$ rosservice call clear
```

​ 调用 clear 服务前，我们用键盘控制乌龟行走，产生一段轨迹如下：

![image-20210803190610359](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv9osLUwG0yhTNi%2Fimage-20210803190610359.png?generation=1628073370534202\&alt=media)

​ 然后调用 clear 服务：

![image-20210803190621126](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvAKB_-Zqhp8e1y%2Fimage-20210803190621126.png?generation=1628073370528749\&alt=media)

​ 正如我们所期待的，服务清除了 turtlesim\_node 的背景上行走的轨迹。

![image-20210803190631033](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvByyMiGxAzL_63%2Fimage-20210803190631033.png?generation=1628073370536048\&alt=media)

​ 通过查看再生（spawn）服务的信息，我们来了解带参数的服务:

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

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

```bash
$ rosservice call spawn 2 2 0.2 ""
```

​ 服务返回了新产生的乌龟的名字 turtle2：

![image-20210803191132772](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvC68z7X8bToIqO%2Fimage-20210803191132772.png?generation=1628073370529859\&alt=media)

​ 现在我们可以看到 TurtleSim 窗口上又多出个乌龟：

![image-20210803191142346](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvDWZ4fuooRIHDM%2Fimage-20210803191142346.png?generation=1628073370727503\&alt=media)

### 7.3 使用 rosparam

​ rosparam 使得我们能够存储并操作ROS [参数服务器（Parameter Server）](http://wiki.ros.org/%E5%8F%82%E6%95%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88Parameter%20Server%EF%BC%89)上的数据。参数服务器能够存储整型、浮点、布尔、字符串、字典和列表等数据类型。rosparam 使用YAML 标记语言的语法。一般而言，YAML 的表述很自然：1 是整型, 1.0 是浮点型, one 是字符串, true 是布尔, \[1, 2, 3]是整型列表, {a: b, c: d}是字典。 rosparam 有很多指令可以用来操作参数，如下所示：

​ **使用方法:**

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

#### 7.3.1 rosparam list

```bash
$ rosparam list
```

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

![image-20210803191810597](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvEUzrkofJtz_Dw%2Fimage-20210803191810597.png?generation=1628073370528995\&alt=media)

​ 接下来我们使用rosparam set 来设置其中的一个参数值。

#### 7.3.2 rosparam set and rosparam get

​ **使用方法:**

```bash
rosparam set [param_name] 
rosparam get [param_name]
```

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

```bash
$ rosparam set background_r 150
```

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

```bash
$ rosservice call clear
```

​ 现在小乌龟窗口背影的颜色则变成下图：

![image-20210803191958031](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvFy6Tx74nP9k8i%2Fimage-20210803191958031.png?generation=1628073370253942\&alt=media)

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

```bash
$ rosparam get background_g 
86
```

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

```bash
$ rosparam get /
```

![image-20210803192040621](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvGyTMcD13FGTEI%2Fimage-20210803192040621.png?generation=1628073370774145\&alt=media)

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

#### 7.3.3 rosparam dump and rosparam load

​ **使用方法:**

```bash
rosparam dump [file_name]
rosparam load [file_name] [namespace]
```

​ 现在我们将所有的参数写入 params.yaml 文件（存储在当前目录下）：

```bash
$ rosparam dump params.yaml
```

​ 然后使用 cat 打印文件内容，可见文件中存储的参数与我们之前查看的参数一致：

![image-20210803192210004](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvHGCfUy4GdN7g8%2Fimage-20210803192210004.png?generation=1628073370705163\&alt=media)

​ 我们不仅可以从yaml 文件中加载参数，还可以将 yaml 文件重载入新的命名空间，比如说 copy 空间:

```bash
$ rosparam load params.yaml copy
```

​ 然后我们查看当前的参数列表，可见 copy 空间下的参数如下图：

![image-20210803192239275](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvI07zdrjx6lBgq%2Fimage-20210803192239275.png?generation=1628073370704141\&alt=media)

​ 然后查看 copy 空间下 background\_b 参数的值：

```bash
$ rosparam get copy/background_b 
255
```

​ 至此，我们已经了解了 ROS 服务和参数服务器的使用，接下来，我们将学习 rqt\_console 和 roslaunch 。

## 第八章 使用 rqt\_console 和 roslaunch

​ **描述:** 本节将介绍如何使用 [rqt\_console ](http://wiki.ros.org/rqt_console)和 [rqt\_logger\_level ](http://wiki.ros.org/rqt_logger_level)进行调试，以及如何使用[roslaunch ](http://wiki.ros.org/roslaunch)同时运行多个节点。

​ 早期版本中的 [rqt ](http://wiki.ros.org/rqt)工具并不完善，因此，如果你使用的是“ROS fuerte”或更早期的版本， 请同时参考[这个页面](http://wiki.ros.org/ROS/Tutorials/UsingRxconsoleRoslaunch)学习使用老版本的“rx”工具。

### 8.1 预先安装 rqt 和 turtlesim 程序包

​ 本节会用到 rqt 和 turtlesim 这两个程序包，如果你没有安装，请先安装：

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

​ 请使用 ROS 发行版名称(比如 hydro、indigo、kinetic 或melodic)替换掉。

​ **注意：**&#x4F60;可能已经在之前的某节课中编译过 rqt 和 turtlesim，如果你不确定的话可再重新编译一次。然后运行roscore ，如果已经运行了，则无需再运行。

### 8.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：

```bash
$ rosrun rqt_console rqt_console   
$ rosrun rqt_logger_level rqt_logger_level
```

​ 你会看到弹出两个窗口

![image-20210803210357860](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvJRH68rsekBnxV%2Fimage-20210803210357860.png?generation=1628073370525714\&alt=media)

​ rqt\_logger\_level 窗口如下：

![image-20210803210410881](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvKfD4zwsnfzTJI%2Fimage-20210803210410881.png?generation=1628073370698882\&alt=media)

​ 现在让我们在一个**新终端**中启动 turtlesim：

```bash
$ rosrun turtlesim turtlesim_node
```

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

![image-20210803210443087](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvMgcXMB1N5Kg4P%2Fimage-20210803210443087.png?generation=1628073370808901\&alt=media)

​ 现在让我们刷新一下 rqt\_logger\_level 窗口（点击窗口中的 Refresh 按钮，刷新）并选择Warn 将日志等级修改为WARN，如下图所示：

![image-20210803210503958](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvNwdcqOgvnVyBq%2Fimage-20210803210503958.png?generation=1628073370755249\&alt=media)

现在我们让 turtle 动起来并观察rqt\_console 中的输出：

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

![image-20210803210529984](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvOMG07eE4QKK2R%2Fimage-20210803210529984.png?generation=1628073370901260\&alt=media)

#### 8.2.1 日志等级说明

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

```bash
Fatal 
Error 
Warn 
Info 
Debug
```

​ Fatal 是最高优先级，Debug 是最低优先级。

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

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

#### 8.2.2 使用 roslaunch

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

​ **用法：**

```bash
$ roslaunch [package] [filename.launch]
```

​ 先切换到 beginner\_tutorials 程序包目录下：

```bash
$ roscd beginner_tutorials
```

​ 如果 roscd 执行失败了，记得 source 你创建的工作空间的环境配置:

![image-20210803220247158](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvPgDBVzEwsl-Mw%2Fimage-20210803220247158.png?generation=1628073370738565\&alt=media)

​ 然后创建一个launch 文件夹：

```bash
$ mkdir launch
$ cd launch
```

![image-20210803220301521](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvQK3_hFmOmpyu5%2Fimage-20210803220301521.png?generation=1628073370721685\&alt=media)

#### 8.2.3 Launch 文件

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

```markup
<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>
```

​ **执行过程如下**：

​ 先使用 touch turtlemimic.launch 创建launch 文件，然后使用 gedit 打开，然后将上面的内容复制到文件中，并保存。

![image-20210803220413368](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvRk73RYquPC3KB%2Fimage-20210803220413368.png?generation=1628073370772808\&alt=media)

![image-20210803220417128](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvSCZLe39E83Y-4%2Fimage-20210803220417128.png?generation=1628073370589365\&alt=media)

#### 8.2.4 Launch 文件解析

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

```markup
<launch>
```

​ 以 launch 标签开头表明这是一个 launch 文件。

```markup
    <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 模拟器而不会产生命名冲突。

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

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

```markup
</launch>
```

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

#### 8.2.5 使用 roslaunch

​ 现在让我们通过 roslaunch 命令来启动launch 文件：

```bash
$ roslaunch beginner_tutorials turtlemimic.launch
```

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

```bash
$ 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 发送了速度设定消息。

![image-20210803220745174](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvTSChCTf9yUuG5%2Fimage-20210803220745174.png?generation=1628073370352371\&alt=media)

​ 原因是 模仿节点 mimic 让 turtlesim2 跟随 turtlesim1 运行，它订阅turtlesim1 的位姿话题，然后向 turtlesim2 发布速度话题，从而让 turtlesim2 跟随 turtlesim1 运行，在下面的图中可以清晰地看到这些节点之间的订阅关系。

​ 我们可以通过 [rqt\_graph ](http://wiki.ros.org/rqt_graph)来更好的理解在launch 文件中所做的事情。运行 [rqt ](http://wiki.ros.org/rqt)并在主窗口中选择[rqt\_graph](http://wiki.ros.org/rqt_graph)：

```bash
$ rqt
```

​ 或者直接运行：

```bash
$ rqt_graph
```

![image-20210803220840190](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvUHJsis9pzX_-5%2Fimage-20210803220840190.png?generation=1628073370733360\&alt=media)

​ 到此，我们已经学会了 rqt\_console 和 roslaunch 命令的使用，接下来我们开始学习使用 rosed ---ROS 中的编辑器。现在你可以按 Ctrl-C 退出所有 turtlesims 节点了，因为在下一节中，将不再用到它们。

## 第九章 使用 rosed 编辑 ROS 中的文件

​ **描述:**&#x672C;节将展示如何使用 [rosed ](http://wiki.ros.org/rosbash)来简化编辑过程。

### 9.1 使用 rosed

​ rosed 是 [rosbash ](http://wiki.ros.org/rosbash)的一部分。利用它可以直接通过 package 名来获取到待编辑的文件而无需指定该文件的存储路径了。与其它 ubuntu 上的编辑器相比，更加的方便，比如使用 gedit 编辑文件，我们必须输入文件的全路径才能对它进行编辑，如下图所示。

![image-20210803223847817](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvV2BFINRr3Y8Qs%2Fimage-20210803223847817.png?generation=1628073370528108\&alt=media)

​ **使用方法:**

```bash
$ rosed [package_name] [filename]
```

​ 以上一节在功能包 beginner\_tutorials 中创建的 turtlemimic.launch 文件为例：

```bash
$ rosed beginner_tutorials turtlemimic.launch
```

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

​ 首先，在运行此命令之前，要生效 beginner\_tutorials 包所在的工作空间，并运行 roscore。

​ 然后运行该命令编辑该文件，如果该实例没有运行成功，那么很有可能是你没有安装vim 编辑器，如下图所示

![image-20210803223934177](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvWOK5_Dw939PAT%2Fimage-20210803223934177.png?generation=1628073370700634\&alt=media)

​ 安装 vim 编辑器：

```bash
sudo apt-get install vim
```

​ 然后运行 rosed 编辑器

```bash
$ rosed beginner_tutorials turtlemimic.launch
```

​ 如下图所示：

![image-20210803224008981](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvXVHZkC56UXG3U%2Fimage-20210803224008981.png?generation=1628073370531545\&alt=media)

​ 则在终端打开该launch 文件如下：

![image-20210803224017018](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvYQk9tAoqqVtVz%2Fimage-20210803224017018.png?generation=1628073370728963\&alt=media)

​ 下面我们来简单介绍如何使用 vim 编辑器，来编辑文件： ​ 刚打开文件时，我们是不能对其编辑的，首先按下“i" 键，让编辑器进入编辑模式，则在编辑器左下角出现-- INSERT -- ，此时我们才可以去该文件进行编辑。

![image-20210803224039498](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvZa2MbRhxE8Qcv%2Fimage-20210803224039498.png?generation=1628073370763151\&alt=media)

​ 我们可以使用上下左右箭头来调整光标的位置，并在文件中加入一行注释  。 ​ 我们要保存文件并退出编辑器，则首先按下”ESC"，退出编辑模式，则左下角的--INSERT -- 标志消失，如下图所示：

![image-20210803224057988](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyv_LuNr88siJaOn%2Fimage-20210803224057988.png?generation=1628073370795154\&alt=media)

​ 然后输入":wq"，并按下 Enter 键，完成保存文件并退出。其中 w 表示保存文件，q 表示退出。如下图所示。

![image-20210803224113186](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvarE5BHda4il7Y%2Fimage-20210803224113186.png?generation=1628073369968207\&alt=media)

​ 我们也可以输入 ":q!"，仅退出不保存文件，如下图所示。

![image-20210803224121701](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvbzkD2OKG8-9Jd%2Fimage-20210803224121701.png?generation=1628073369956652\&alt=media)

### 9.2 使用 Tab 键补全文件名

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

```bash
$ rosed [package_name] <tab>
```

​ 我们仍以编辑功能包 beginner\_tutorials 中的 turtlemimic.launch 文件为例，当我们输入 rosed b 后，我们按 TAB 键，则直接将 beginner\_tutorials 补全，然后我们再输入 t 然后按 TAB 键，则直接将 turtlemimic.launch。若有多个此字母开头的文件，则需要进一步输入更多文件名后再按 TAB 补全。

![image-20210803224159701](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvce1E7r262gyzq%2Fimage-20210803224159701.png?generation=1628073370527753\&alt=media)

### 9.3 编辑器

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

```bash
export EDITOR='gedit'
```

​ 因为 \~/.bashrc 文件是系统文件，所以若想修改此文件，需要使用系统权限，因此我们使用 sudo gedit 来修改此文件：

```bash
sudo gedit .bashrc
```

![image-20210803224253227](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvdzpgAF1D4wRAb%2Fimage-20210803224253227.png?generation=1628073370533218\&alt=media)

![image-20210803224255712](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvePzFEzYu1yFS2%2Fimage-20210803224255712.png?generation=1628073370713374\&alt=media)

​ 这将 gedit 设置成为默认编辑器。 .bashrc 文件的改变，只会在新的终端才有效。已经打开的终端不受环境变量的影响。 ​ 打开一个新的终端，看看那是否定义了 EDITOR:

```bash
$ echo $EDITOR
gedit
```

​ 现在我们已经成功设置并使用了 rosed，接下来我们将学习创建 ROS 消息和 ROS 服务。

## 第十章 创建 ROS 消息和 ROS 服务

​ **描述:** 本节将详细介绍如何创建并编译 ROS 消息和服务，以及 [rosmsg](http://wiki.ros.org/rosmsg)，rossrv 和 roscp 命令行工具的使用。

### 10.1 消息(msg)和服务(srv)介绍

​ \[消息(msg)]\([http://wiki.ros.org/消息(msg](http://wiki.ros.org/%E6%B6%88%E6%81%AF\(msg))): msg 文件就是一个描述ROS 中所使用消息类型的简单文本。它们会被用来生成不同语言的源代码。

​ \[服务(srv)]\([http://wiki.ros.org/服务(srv](http://wiki.ros.org/%E6%9C%8D%E5%8A%A1\(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]

  同时 msg 文件也可以使用其它自定义的消息类型。

​ 在 ROS 中有一个特殊的数据类型：Header，它含有时间戳和坐标系信息。在 msg 文件的第一行经常可以看到 Header header 的声明.下面是一个msg 文件的样例，它使用了Header，string，和其他另外两个消息类型。其中，child\_frame\_id 为坐标系名，PoseWithCovariance 和 TwistWithCovariance 为ROS geometry\_msgs 消息，在后面的课程中将会详细介绍。

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

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

```
int64 A 
int64 B
---
int64 Sum
```

```
 其中 A 和 B 是请求, 而 Sum 是响应。就好如，网络请求一样，我们传入两个整型数据， 然后请求，服务响应回一个整型结果。
```

### 10.2 使用 msg

#### 10.2.1 创建一个 msg

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

```bash
$ roscd beginner_tutorials
$ mkdir msg
$ cd msg
$ echo "int64 num" > msg/Num.msg
```

​ 按上述命令在功能包 beginner\_tutorials 中创建目录msg，并在该目录下创建消息文件Num.msg 并写入消息类型，一个简单的 64 位整型，如下图所示：

![image-20210803225557951](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvfFVNgm5kmjNgB%2Fimage-20210803225557951.png?generation=1628073370721200\&alt=media)

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

```
string first_name 
string last_name 
uint8 age
uint32 score
```

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

```markup
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
```

​ 如果没有，添加进去，如下图：

![image-20210803225704612](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvgg0YCHervIqPJ%2Fimage-20210803225704612.png?generation=1628073370688934\&alt=media)

​ **注意，**&#x5728;构建（build）的时候，我们只需用到"message\_generation"。然而，在运行的时候，我们只需用到"message\_runtime"。

使用你最常用的编辑器中打开 CMakeLists.txt 文件(可以参考前边的教程 [rosed](http://wiki.ros.org/ROS/Tutorials/UsingRosEd))。

​ 在 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)
```

​ 如下图所示：

![image-20210803225753662](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvhxUyj0T60VwPT%2Fimage-20210803225753662.png?generation=1628073370604191\&alt=media)

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

​ 同样，你需要确保设置了运行依赖：

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

​ 如下图所示：

![image-20210803225832572](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvisl3NPM91scJg%2Fimage-20210803225832572.png?generation=1628073370540357\&alt=media)

​ 找到如下代码块:

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

​ 添加效果如下图所示：

![image-20210803225919423](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvksG5BCNC0BxF6%2Fimage-20210803225919423.png?generation=1628073370534877\&alt=media)

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

```
generate_messages()
```

​ 如下图所示：

![image-20210803225948239](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvlSSK_pX6m_V7l%2Fimage-20210803225948239.png?generation=1628073370582858\&alt=media)

​ **注意：**&#x4E0A;面添加和修改的代码都是有位置顺序的，要按上面的位置添加，不可随意在其他位置上添加。 ​ 现在，你可以生成自己的消息源代码了。如果你想立即实现，那么就跳过以下部分，到下面的第 3 部分 msg 和 srv 都需要的步骤。

#### 10.2.2 使用 rosmsg

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

​ 使用方法:

```bash
$ rosmsg show [message type]
```

​ 示例:

```bash
$ rosmsg show beginner_tutorials/Num
```

​ 你将会看到:

![image-20210803230204064](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvmwydJmuNRJUpr%2Fimage-20210803230204064.png?generation=1628073370575043\&alt=media)

​ 在上边的示例中，消息类型包含两部分：

* beginner\_tutorials -- 消息所在的功能包
* Num -- 消息名 Num

​ 如果你忘记了消息所在的功能包，你也可以省略掉功能包名。输入：

```bash
 $ rosmsg show Num
```

​ 你将会看到消息所在的功能包和消息类型：

![image-20210803230440325](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvnetVAs1nJ7j1z%2Fimage-20210803230440325.png?generation=1628073370538046\&alt=media)

​ 使用这个命令的前提是，你自定义的消息名不能和系统的消息名冲突，否则会出错。

### 10.3 使用 srv

#### 10.3.1 创建一个 srv

​ 在刚刚那个 package 中创建一个服务：

```bash
$ roscd beginner_tutorials
$ mkdir srv
```

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

​ **使用方法:**

```bash
$ roscp [package_name] [file_to_copy_path] [copy_path]
```

​ 现在我们可以从rospy\_tutorials package 中复制一个服务文件了：

```bash
$ roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv
```

![image-20210803230636981](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvow-dvgIjfMpUO%2Fimage-20210803230636981.png?generation=1628073370532864\&alt=media)

​ 还有很关键的一步：我们要确保 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)
```

​ **注意：**&#x8FD9;里不用重复添加，因为 message\_generation 对 msg 和 srv 都起作用。

​ 同样，跟 msg 文件类似，你也需要在 CMakeLists.txt 文件中做一些修改。查看上边的说明， 增加额外的依赖项。 ​ 删掉#，去除对下边语句的注释:

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

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

```yaml
add_service_files( 
FILES
AddTwoInts.srv
)
```

​ 也可以直接将上述代码复制到这个注释代码的后面，如下图所示：

![image-20210803231613419](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvpatwy10l47ufx%2Fimage-20210803231613419.png?generation=1628073370580665\&alt=media)

​ 现在，你可以生成自己的服务源代码了。如果你想立即实现，那么就跳过以下部分，到下面的第 3 部分 msg 和 srv 都需要的步骤。

#### 10.3.2 使用 rossrv

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

​ **使用方法:**

```bash
$ rossrv show <service type>
```

​ **示例:**

```bash
$ rossrv show beginner_tutorials/AddTwoInts
```

​ 你将会看到:

```bash
int64 a 
int64 b
---
int64 sum
```

![image-20210803231810041](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvqHlwOF9ZF25X4%2Fimage-20210803231810041.png?generation=1628073370539531\&alt=media)

​ 跟 rosmsg 类似, 你也可以不指定具体的功能包名来查找服务文件：

![image-20210803231819244](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvrvz_v87PF72nR%2Fimage-20210803231819244.png?generation=1628073370556242\&alt=media)

​ 此时你将看到两个服务，因为我们是复制过来的，所以有同名的，但是在不同功能包里的。

### 10.4 msg 和 srv 都需要的步骤

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

```markup
# generate_messages( 
# DEPENDENCIES
# std_msgs    
# Or other packages containing msgs
```

​ 去掉注释并附加上所有你消息文件所依赖的那些含有.msg 文件的 package（这个例子是依赖 std\_msgs,不要添加 roscpp,rospy)，代码如下:

```bash
generate_messages( 
DEPENDENCIES
std_msgs
)
```

​ 由于前面我们已经添加了 generate\_messages() ，所以这里我们需要将其用上面的代码替换，最后效果如下图所示：

![image-20210803232318402](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvsXa24EpNvWKz9%2Fimage-20210803232318402.png?generation=1628073370704228\&alt=media)

​ 由于增加了新的消息，所以我们需要重新编译我们的功能包：

```bash
# 进入到你的 catkin 工作空间目录下编译
$ cd ~/catkin_ws/
$ catkin_make
```

​ 如下图所示：

![image-20210803232700628](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvtwOFwJWIaBA9W%2Fimage-20210803232700628.png?generation=1628073370550766\&alt=media)

​ 所有在msg 路径下的.msg 文件都将转换为ROS 所支持语言的源代码。生成的 C++头文件将会放置在\~/catkin\_ws/devel/include/beginner\_tutorials/目录下，如下图所示：

![image-20210803232942020](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvu8-VWWWEl5jGF%2Fimage-20210803232942020.png?generation=1628073370538090\&alt=media)

​ 可以看到前面和消息文件和服务文件分别生成对应的头文件，其中服务文件也生成对应的请求和响应头文件。 ​ 而 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 ](http://wiki.ros.org/ROS/Message_Description_Language)页面。

#### 10.5 获取帮助

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

​ 输入:

```bash
$ rosmsg -h
```

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

![image-20210803233435611](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvv9KkksY-HrpJu%2Fimage-20210803233435611.png?generation=1628073370534639\&alt=media)

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

```bash
$ rosmsg show -h
```

​ 这会显示 rosmsg show 所需的参数：

```bash
Usage: rosmsg show [options] <message type>

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

#### 10.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

#### 10.7 下一个课

​ 既然已经学习了如何创建ROS 消息和服务，接下来我们将学习如何编写简单的发布器和订阅器\[(python) ]\([http://wiki.ros.org/cn/ROS/Tutorials/WritingPublisherSubscriber(python))\[(c++)\](http://wiki.ros.org/cn/ROS/Tutorials/WritingPublisherSubscriber(c%2B%2B))。](http://wiki.ros.org/cn/ROS/Tutorials/WritingPublisherSubscriber\(python\)\)\[\(c++\)]\(http://wiki.ros.org/cn/ROS/Tutorials/WritingPublisherSubscriber\(c%2B%2B\)\)%E3%80%82)

## 第十一章 编写消息发布器和订阅器 (C++)

​ **描述:** 本节将介绍如何用C++ 编写发布器节点和订阅器节点。

### 11.1 编写发布器节点

​ 节点(Node) 是指 ROS 网络中可执行文件。接下来，我们将会创建一个发布器节点("talker")，它将在 ROS 网络中不断的广播消息。 切换到之前创建的 beginner\_tutorials 功能包路径下：

```bash
roscd beginner_tutorials
```

#### 11.1.1 源代码

​ 在 beginner\_tutorials 包 路径下创建一个 src 文件夹：

```bash
mkdir -p ~/catkin_ws/src/beginner_tutorials/src
```

​ 这里使用的是全路径创建 src 文件夹，由于前面我们已经进入到 beginner\_tutorials 目录下，所以可以不加路径直接创建 src 文件夹，如下：

```bash
mkdir src
```

这个文件夹将会用来放置 beginner\_tutorials 功能包的所有源代码。在 beginner\_tutorials 包的 src 目录下创建 talker.cpp 文件，并将如下代码粘贴到文件内： <https://raw.github.com/ros/ros_tutorials/groovy-devel/roscpp_tutorials/talker/talker.cpp>

```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;
}
```

​ 删掉注释后，代码会变得非常简洁，如下图所示：

![image-20210804153904743](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvw1RFeEU9O2zG6%2Fimage-20210804153904743.png?generation=1628073370557189\&alt=media)

#### 11.1.2 代码说明

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

```cpp
#include "ros/ros.h"
```

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

```cpp
#include "std_msgs/String.h"
```

​ 这引用了 [std\_msgs/String ](http://docs.ros.org/en/api/std_msgs/html/msg/String.html)消息, 它存放在 [std\_msgs ](http://wiki.ros.org/std_msgs)package 里，是由 String.msg 文件自动生成的头文件。需要关于消息的定义，可以参考 [msg ](http://wiki.ros.org/msg)页面。

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

​ 初始化 ROS 。它允许 ROS 通过命令行进行名称重映射——然而这并不是现在讨论的重点。在这里，我们也可以指定节点的名称——运行过程中，同一命名空间下的节点的名称必须唯一。这里的名称必须是一个 [base name ](http://wiki.ros.org/Names#Graph)，也就是说，名称内不能包含 / 等符号。

```bash
ros::NodeHandle n;
```

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

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

​ 告诉 master 我们将要在话题 chatter 上发布 [std\_msgs/String ](http://docs.ros.org/en/api/std_msgs/html/msg/String.html)消息类型的消息。这样master 就会告诉所有订阅了 chatter 话题的节点，将要有数据发布。第二个参数是发布序列的大小。如果我们发布的消息的频率太高，缓冲区中的消息在大于 1000 个的时候就会开始丢弃先前发布的消息。

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

```cpp
ros::Rate loop_rate(10);
```

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

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

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

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

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

* SIGINT 被触发 (Ctrl-C)
* 被另一同名节点踢出 ROS 网络
* ros::shutdown() 被程序的另一部分调用
* 节点中的所有 ros:[:NodeHandles ](http://wiki.ros.org/NodeHandles)都已经被销毁

一旦 ros::ok() 返回 false, 所有的 ROS 调用都会失效。

```cpp
std_msgs::String msg;

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

​ 我们使用一个由 [msg file ](http://wiki.ros.org/msg)文件产生的『消息自适应』类在 ROS 网络中广播消息。现在我们使用标准的String 消息，它只有一个数据成员 "data"。当然，你也可以发布更复杂的消息类型。

```cpp
chatter_pub.publish(msg);
```

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

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

​ ROS\_INFO 和其他类似的函数可以用来代替 printf/cout 等函数，在终端打印信息。具体可以参考 [rosconsole documentation](http://wiki.ros.org/rosconsole)，以获得更多信息。

```cpp
ros::spinOnce();
```

​ 在这个例子中并不是一定要调用 ros::spinOnce()，因为我们不接收回调。然而，如果你的程序里包含其他回调函数，最好在这里加上 ros::spinOnce()这一语句，否则你的回调函数就永远也不会被调用了。spinOnce 的作用就是一次刷新消息，查看回调信息等。而ros::spin()则会定期一直刷新消息。

```cpp
loop_rate.sleep();
```

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

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

* 初始化 ROS 系统
* 在 ROS 网络内广播我们将要在 chatter 话题上发布[ std\_msgs/String ](http://docs.ros.org/en/api/std_msgs/html/msg/String.html)类型的消息
* 以每秒 10 次的频率在 chatter 上发布消息

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

### 11.2 编写订阅器节点

#### 11.2.1 源代码

在 beginner\_tutorials 功能包的 src 目录下创建 listener.cpp 文件，并粘贴如下代码：

[*https://raw.github.com/ros/ros\_tutorials/groovy-devel/roscpp\_tutorials/listener/listener.cpp*](https://raw.github.com/ros/ros_tutorials/groovy-devel/roscpp_tutorials/listener/listener.cpp)

```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;
}
```

​ 将注释删除后，代码变得非常简洁，如下图所示：

![image-20210804155412310](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvxloryrc7E6qxZ%2Fimage-20210804155412310.png?generation=1628073370536410\&alt=media)

#### 11.2.2 代码说明

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

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

​ 这是一个回调函数，当接收到 chatter 话题的时候就会被调用。消息是以 [boost](http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm) [shared\_ptr ](http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm)指针的形式传输，这就意味着你可以存储它而又不需要复制数据。

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

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

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

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

```cpp
ros::spin();
```

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

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

**下边，我们来总结一下:**

* 初始化 ROS 系统   &#x20;
* 订阅 chatter 话题
* 进入自循环，等待消息的到达
* 当消息到达，调用 chatterCallback() 函数

### 11.3 编译节点

​ 之前课程中使用 [catkin\_create\_pkg ](http://wiki.ros.org/catkin/commands/catkin_create_pkg)创建了 [package.xml ](http://wiki.ros.org/catkin/package_manifest)和 [CMakeLists.txt ](http://wiki.ros.org/catkin/CMakeLists.txt)文件。生成的 [CMakeLists.txt ](http://wiki.ros.org/catkin/CMakeLists.txt)看起来应该是这样(在 [Creating Msgs and Srvs ](http://wiki.ros.org/ROS/Tutorials/CreatingMsgAndSrv)教程中的修改和未被使用的注释和例子都被移除了):

[*https://raw.github.com/ros/catkin\_tutorials/master/create\_package\_modified/catkin\_ws/sr*](https://raw.github.com/ros/catkin_tutorials/master/create_package_modified/catkin_ws/src/beginner_tutorials/CMakeLists.txt) [*c/beginner\_tutorials/CMakeLists.txt*](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 ](http://wiki.ros.org/catkin/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 ](http://wiki.ros.org/catkin/workspaces#Development_.28Devel.29_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](http://wiki.ros.org/catkin/CMakeLists.txt)

​ 如果需要关于 [CMakeLists.txt ](http://wiki.ros.org/catkin/CMakeLists.txt)更详细的信息，请参考 [catkin/CMakeLists.txt](http://wiki.ros.org/catkin/CMakeLists.txt)

​ 现在运行 catkin\_make：

```bash
# In your catkin workspace
$ catkin_make
```

​ **注意：**&#x5982;果你是添加了新的 package，你需要通过 --force-cmake 选项告诉 catkin 进行强制编译。(也可不用）参考 [catkin/Tutorials/using\_a\_workspace#With\_catkin\_make](http://wiki.ros.org/catkin/Tutorials/using_a_workspace#With_catkin_make)。

​ 编译过程如下，这会生成两个可执行文件 talker 和 listener, 默认存储到 \~/catkin\_ws/devel/lib/beginner\_tutorials 中。

![image-20210804160356073](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvyV4gjXNELJwCW%2Fimage-20210804160356073.png?generation=1628073371226820\&alt=media)

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

## 第十二章 测试消息发布器和订阅器（C++）

​ **描述:** 本节将测试上一节所写的消息发布器和订阅器。

### 12.1 启动发布器

​ 确保 roscore 可用，并运行：

```bash
$ roscore
```

​ 在调用 catkin\_make 后，在运行你自己的程序前，确保已经 source 了 catkin 工作空间下的 setup.sh 文件：

```bash
# 进入你的 catkin 工作空间
$ cd ~/catkin_ws
$ source ./devel/setup.bash
```

​ **注意：**&#x82E5;已在\~/.bashrc 文件中配置过工作空间，则无需再执行上面操作。在上一节中我们编写了一个叫"talker"的消息发布器，现在运行它：

```bash
$ rosrun beginner_tutorials talker
```

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

![image-20210804161638516](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyvzJRvr_buo1PbM%2Fimage-20210804161638516.png?generation=1628073370701216\&alt=media)

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

### 12.2 启动订阅器

​ 上一节中我们编写了一个名为"listener"的订阅器节点，现在运行它：

```bash
$ rosrun beginner_tutorials listener
```

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

![image-20210804161722059](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw-winxL9kJJy7P%2Fimage-20210804161722059.png?generation=1628073371231480\&alt=media)

​ 现在我们再新打开一个终端运行 rqt\_graph 来查看两节点间的话题关系：

```bash
$ rqt_graph
```

![image-20210804161741156](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw0TXqXzi61Wz2Z%2Fimage-20210804161741156.png?generation=1628073370563275\&alt=media)

​ 由下图可见，现在启动了两个节点 talker 和 listener，两节点通过话题 chatter 通信， 节点 talker 发布话题 chatter，而 节点 listener 订阅 话题 chatter。

![image-20210804161751382](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw1A87LnsKmVdsz%2Fimage-20210804161751382.png?generation=1628073370542203\&alt=media)

​ 现在已经测试完了用 C++代码编写的发布器和订阅器，下一节我们将介绍使用 Python 代码编写发布器和订阅器。

## 第十三章 编写消息发布器和订阅器 (Python)

​ **描述:** 本节将通过Python 编写一个发布器节点和订阅器节点。

### 13.1 编写发布器节点

​ 节点(Node) 是指 ROS 网络中可执行文件。接下来，我们将会创建一个发布器节点("talker")，它将在 ROS 网络中不断的广播消息。 切换到之前创建的 beginner\_tutorials 功能包路径下：

```bash
$ roscd beginner_tutorials
```

#### 13.1.1 源代码

​ 在 beginner\_tutorials 包 路径下创建一个 scripts 文件夹够存储Python 脚本文件：

```bash
$ mkdir scripts
$ cd scripts
```

​ 然后在 scripts 目录下创建一个 talker.py，并赋给它可执行权限：

```bash
$ touch talker.py
$ chmod +x talker.py
```

​ **注意：**&#x8BE5; python 代码文件必须被赋给可执行权限，否则后面运行该节点时，将无法执行该文件。我们也可以运行 ls -ll 命令来查看该文件的读写及可执行权限的变化，w 表示可写，r 表示可读，x 表示可执行：

```bash
$ ls -ll
```

​ 具体执行过程如下图所示：

![image-20210804162756992](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw2TYxG_7Zkmjj1%2Fimage-20210804162756992.png?generation=1628073371348718\&alt=media)

​ 然后使用 gedit 打开 talker.py 文件，

```bash
$ gedit talker.py
```

​ 并将下面的代码复制到 talker.py 文件中：

```python
#!/usr/bin/env python
# license removed for brevity import rospy
from std_msgs.msg import String

def talker():
    pub = rospy.Publisher('chatter', String, queue_size=10) 
    rospy.init_node('talker', anonymous=True)
    rate = rospy.Rate(10) # 10hz 
    while not rospy.is_shutdown():
        hello_str = "hello world %s" % rospy.get_time() 
        rospy.loginfo(hello_str)
        pub.publish(hello_str) rate.sleep()

if __name__ == '_main_':
    try:
        talker()
    except rospy.ROSInterruptException: 
        pass
```

#### 13.1.2 代码说明

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

```python
#!/usr/bin/env python
```

​ 每一个 Python ROS 节点在文件开头都有这一行声明，这一行代码确保该脚本文件被当作Python 脚本执行。

```python
import rospy
from std_msgs.msg import String
```

​ 使用Python 来编写ROS 节点，则需要导入rospy 模块。导入 std\_msgs.msg 模块中的 String 对象，因为下面我们要发布一个字符串话题，其消息类型是 std\_msgs/String（一个简单的字符串容器）。

```python
    pub = rospy.Publisher('chatter', String, queue_size=10) 
    rospy.init_node('talker', anonymous=True)
```

​ 这部分代码定义一个与 ROS 其它节点通讯的发布器。

​ pub=rospy.Publisher("chatter",String,queue\_size=10)声明你的节点将在 chatter 话题上发布消息类型为 String 的消息。参数 queue\_size 是发布序列的大小。如果我们发布的消息的频率太高，缓冲区中的消息在大于 10 个的时候就会开始丢弃先前发布的消息。

​ 下一行代码 rospy.init\_node('talker', anonymous=True) 非常重要，它告诉 rospy 你创建的节点的名称，只有rospy 得到了这个信息，你创建的这个节点才能与ROS Master 通信。此时你的节点名为talker。注意：这里的名称必须是一个 [base name ](http://wiki.ros.org/Names#Graph)，也就是说，名称内不能包含 / 等符号。

​ 参数 anonymous=True 通过在名称末尾添加随机数来确保节点具有唯一的名称。有关节点初始化选项的更多信息，请参阅 rospy 文档中的 [Initialization and Shutdown - Initializing](http://wiki.ros.org/rospy/Overview/Initialization%20and%20Shutdown#Initializing_your_ROS_Node) [your ROS Node](http://wiki.ros.org/rospy/Overview/Initialization%20and%20Shutdown#Initializing_your_ROS_Node)

```python
    rate = rospy.Rate(10) # 10hz
```

​ 此行创建Rate 对象 rate ，借助 rate 的方法 sleep()，它提供了以所需速率循环的方便方法。参数为 10 时，我们希望每秒执行 10 次循环（只要我们的处理时间不超过 1/10 秒！）

```python
    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 或其他）。现在它的作用，就调用 pub.publish(hello\_str)，往 Chatter 话题上发布字符串。循环调用 rate.sleep() 来休眠足够长的时间，从而维持循环按指定的频率运行。

​ 您也可以运行 rospy.sleep()，它与 time.sleep()类似，只是 time.sleep()也可以用于模拟时间（参见[Clock](http://wiki.ros.org/Clock)）。

​ 这个循环还调用rospy.loginfo(str)，它执行三重任务：打印消息到屏幕上；将消息写入节点的日志文件；将消息写入 rosout。rosout 对于调试是一个很方便的工具：你可以使用[rqt\_console ](http://wiki.ros.org/rqt_console)来查看你的节点输出的消息，而不需要到你打开节点的窗口来查看节点输出的消息。

​ std\_msgs.msg.String 是一种非常简单的消息类型，因此您可能想知道发布更复杂的消息类型是什么样子的。一般的经验法则是，构造函数参数的顺序与.msg 文件中的顺序相同。您还可以不传入任何参数并直接初始化字段，例如:

```python
msg = String() msg.data = str
```

​ 或者，您可以初始化某些字段并将其余字段保留为默认值：

```python
String(data=str)
```

​ 你可能想知道最后一点代码：

```python
    try:
        talker()
    except rospy.ROSInterruptException: 
        pass
```

​ 除了标准的 Python **main** 检查之外，这还会捕获一个rospy.ROSInterruptException 异常，当按下 ctrl-c 或关闭节点时，rospy.sleep()和rospy.rate.sleep()方法会抛出异常。引发此异常的原因是，你意外地不在 sleep()之后继续执行代码。 ​ 现在我们需要编写一个节点来接收消息。

### 13.2 编写订阅器节点

#### 13.2.1 源代码

​ 现在进入到 beginner\_tutorials 功能包下 scripts 目录中创建一个 talker.py，并赋给它可执行权限：

```bash
$ roscd beginner_tutorials/scripts/
$ touch listener.py
$ chmod +x listener.py
```

​ **注意：**&#x8BE5; python 代码文件必须被赋给可执行权限，否则后面运行该节点时，将无法执行该文件。

​ 然后使用 gedit 打开listener.py 文件，

```bash
$ gedit listener.py
```

​ 并将下面的代码复制到listener.py 文件中：

```python
#!/usr/bin/env python 
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data) 

def listener():
    # In ROS, nodes are uniquely named. If two nodes with the same 
    # name are launched, the previous one is kicked off. The
    # anonymous=True flag means that rospy will choose a unique 
    # name for our 'listener' node so that multiple listeners can
    # run simultaneously. 
    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("chatter", String, callback)

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

if __name__ == '_main_': 
    listener()
```

​ 上述过程同发布器 talker.py 的编写一样，这里就不再详细介绍了。

#### 13.2.2 代码说明

​ listener.py 的代码与 talker.py 类似，只是我们引入了一种新的基于回调的消息订阅机制。

```python
    rospy.init_node('listener', anonymous=True) 
    rospy.Subscriber("chatter", String, callback)
    # spin() simply keeps python from exiting until this node is stopped 
    rospy.spin()
```

​ 这声明你的节点订阅了消息类型为 std\_msgs.msgs.String 的 chatter 话题。当接收到新消息时，将使用消息作为第一个参数调用回调函数。

​ 我们还稍微改变了对 rospy.init\_node()的调用。我们添加了 anonymous=true 关键字参数。ROS 要求每个节点都要有一个唯一的名称。如果出现同名节点，它将与前一个节点发生碰撞。这样就可以很容易地将出现故障的节点从网络中踢出。anonymous=true 标志告诉 rospy 为节点生成一个唯一的名称，这样你就可以轻松地运行多个listener.py 节点。

​ 最后，rospy.spin()，它只是在关闭节点之前阻止你的节点退出。与 roscpp 不同，rospy.spin()不会影响订阅者回调函数，因为它们有自己的线程。

### 13.3 编译节点

​ 我们使用 cmake 作为编译系统，是的甚至你必须将它来编译你的Python 节点。这是为了确保为消息和服务创建了自动生成的 python 代码。

​ 进入你的 catkin 工作空间，然后运行 catkin\_make:

```bash
$ cd ~/catkin_ws
$ catkin_make
```

​ 如下图所示，实质上也没生成什么，只是重新生效环境：

![image-20210804164121953](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw3q1pq0xmsYW2n%2Fimage-20210804164121953.png?generation=1628073371293939\&alt=media)

### 13.4 测试发布器和订阅器

#### 13.4.1 启动发布器

​ 确保 roscore 可用，并运行：

```bash
$ roscore
```

​ 在调用 catkin\_make 后，在运行你自己的程序前，确保已经 source 了 catkin 工作空间下的 setup.sh 文件：

```bash
# 进入你的 catkin 工作空间
$ cd ~/catkin_ws
$ source ./devel/setup.bash
```

​ **注意：**&#x82E5;已在\~/.bashrc 文件中配置过工作空间，则无需再执行上面操作。上面我们已经编写了一个叫"talker"的消息发布器，现在运行它：

```bash
$ rosrun beginner_tutorials talker.py
```

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

![image-20210804164328167](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw47ik8Y5-l-ZLH%2Fimage-20210804164328167.png?generation=1628073371261224\&alt=media)

#### 13.4.2 启动订阅器

​ 上面我们编写了一个名为"listener"的订阅器节点，现在运行它：

```bash
$ rosrun beginner_tutorials listener.py
```

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

![image-20210804164428265](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw6sWrZ2fwaZGce%2Fimage-20210804164428265.png?generation=1628073371267807\&alt=media)

​ 现在你也可以新打开一个终端运行 rqt\_graph 来查看两节点间的话题关系：

```bash
$ rqt_graph
```

​ 这里我们就不再详细介绍了。

​ 现在你已经学会用 Python 编写一个简单的消息发布器和订阅器，并对它们进行测试， 下一节让我们来学习编写服务器和客户端 (C++)。

## 第十四章 编写服务器和客户端 (C++)

​ **描述:** 本节将介绍如何用 C++ 编写服务器 Service 和客户端 Client 节点。 ​ 服务和话题是ROS 的两种不同节点间的通信机制，它们的用处是不同的，话题更适合于实时的消息发布，比如实时变化的传感器数据，不停地向ROS 节点广播消息，而对于不需要频繁更新的数据的获取，则显得很不高效。此时使用服务就很简单，当我们需要时才会请求，数据就放在那里，我们需要时再将其取过来，更适合用于不会频繁更新的数据的获取等。

### 14.1 编写 Service 节点

​ 这里，我们将创建一个简单的 service 节点("add\_two\_ints\_server")，该节点将接收到两个整形数字，并返回它们的和。 ​ 首先进入到之前创建的 beginner\_tutorials 包所在的目录下：

```bash
cd ~/catkin_ws/src/beginner_tutorials
```

​ 请确保已经按照《**第** **10** **章创建** **ROS** **消息和** **ROS** **服务**》中的步骤创建了本节所需要的 srv 服务AddTwoInts（确保选择了对应的编译系统“catkin”和“rosbuild”）。

#### 14.1.1 代码

​ 在 beginner\_tutorials 包中的 src 目录下创建 add\_two\_ints\_server.cpp 文件：

```bash
$ touch add_two_ints_server.cpp
```

​ 使用 gedit 打开该文件：

```bash
$ gedit add_two_ints_server.cpp
```

​ 然后将下面的代码复制到文件中并保存：

```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;
}
```

​ 以上的操作过程如下图所示：

![image-20210804164850644](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw7yPzbtr8EAPM2%2Fimage-20210804164850644.png?generation=1628073370868911\&alt=media)

​ 将代码复制到文件中，如下图所示：

![image-20210804164904791](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw8VvOjvD4s6Peb%2Fimage-20210804164904791.png?generation=1628073370866835\&alt=media)

#### 14.1.2 代码解释

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

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

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

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

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

```cpp
{
    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 值。函数返回值为 true 表示服务请求成功，服务器正常工作的。

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

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

### 14.2 编写 Client 节点

#### 14.2.1 代码

​ 在 beginner\_tutorials 包中的 src 目录下创建 add\_two\_ints\_client.cpp 文件：

```bash
$ touch add_two_ints_client.cpp
```

​ 使用 gedit 打开该文件：

```bash
$ gedit add_two_ints_client.cpp
```

​ 然后将下面的代码复制到文件中并保存：

```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;
}
```

​ 上述操作过程具体如下：

![image-20210804165452010](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw9QeKeFTXLx62q%2Fimage-20210804165452010.png?generation=1628073370709004\&alt=media)

​ 复制代码到文件如下图所示：

![image-20210804165500754](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywANZ8LqOGDyGxc%2Fimage-20210804165500754.png?generation=1628073370904197\&alt=media)

#### 14.2.2 代码解释

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

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

​ 这段代码为 add\_two\_ints 服务创建一个客户端。ros::ServiceClient 对象待会用来调用 service。

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

​ 这里，我们实例化一个由ROS 编译系统自动生成的 service 类，并给其request 成员赋值。一个 service 类包含两个成员 request 和 response。同时也包括两个类定义 Request 和 Response。argv 为命令行参数数组，数组元素为字符串，argv\[1]和 argv\[2]是下面我们通过命令行传入的两个整型数的字符串形式。atoll 函数将数字字符串转成整型。

```cpp
    if (client.call(srv))
```

​ 这段代码是在调用 service。由于 service 的调用是模态过程（调用的时候占用进程阻止其他代码的执行），一旦调用完成，将返回调用结果。如果 service 调用成功，call()函数将返回 true，srv.response 里面的值将是合法的值。如果调用失败，call()函数将返回false，srv.response 里面的值将是非法的，说明服务调用失败，也可能是服务器未开启。

### 14.3 编译节点

​ 再来编辑一下 beginner\_tutorials 里面的CMakeLists.txt，文件位于\~/catkin\_ws/src/beginner\_tutorials/目录下，将下面的代码添加在文件末尾：

```
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\_server"和"add\_two\_ints\_client"，这两个可执行程序默认被放在你的 \[devel space ]\(*<http://wiki.ros.org/catkin/workspaces#Development>.28Devel.29\_Space)下的包目录下，默认为\~/catkin\_ws/devel/lib/share/beginner\_tutorials。你可以直接调用可执行程序，或者使用 rosrun 命令去调用它们。它们不会被装在/bin 目录下，因为当你在你的系统里安装这个包的时候，这样做会污染PATH 变量。如果你希望在安装的时候你的可执行程序在 PATH 变量里面，你需要设置一下 install target，请参考：[catkin/CMakeLists.txt](http://wiki.ros.org/catkin/CMakeLists.txt)

​ 关于[CMakeLists.txt ](http://wiki.ros.org/catkin/CMakeLists.txt)文件更详细的描述请参考：[catkin/CMakeLists.txt](http://wiki.ros.org/catkin/CMakeLists.txt)

​ 操作过程如下图所示：

![image-20210804165902921](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywBQW_LElxH9SMR%2Fimage-20210804165902921.png?generation=1628073370701009\&alt=media)

![image-20210804165908094](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywCuBuwLc0Ba8wS%2Fimage-20210804165908094.png?generation=1628073371287286\&alt=media)

​ 现在运行 catkin\_make 命令：

```bash
# In your catkin workspace
$ cd ~/catkin_ws
$ catkin_make
```

​ 如果你的编译过程因为某些原因而失败：请确保已经按照《第 10 章 创建 ROS 消息和 ROS 服务》中的步骤创建了本节所需要的 srv 服务AddTwoInts。操作过程如下图所示，生成两个可执行程序"add\_two\_ints\_server"和 "add\_two\_ints\_client"，则说明编译成功：

![image-20210804170017769](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywDarLZverrqy2K%2Fimage-20210804170017769.png?generation=1628073370911233\&alt=media)

![image-20210804170027506](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywEaPoaFBOTWZ4M%2Fimage-20210804170027506.png?generation=1628073371244176\&alt=media)

​ 现在你已经学会如何使用C++编写简单的服务器 Service 和客户端 Client，下一节我们将测试它们。

## 第十五章 测试 Service 和 Client

​ **描述:** 本节将测试上一节用 C++代码编写的 Service 和Client。首先确保 roscore 已运行，运行roscore 命令：

```bash
$ roscore
```

### 15.1 运行 Service

​ 让我们从运行Service 服务器开始：

```bash
$ rosrun beginner_tutorials add_two_ints_server
```

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

![image-20210804170139312](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywFLFVKxOcvVnzw%2Fimage-20210804170139312.png?generation=1628073370734324\&alt=media)

### 15.2 运行 Client

​ 现在，运行 Client 客户端并附带一些参数：

```bash
$ rosrun beginner_tutorials add_two_ints_client 12 19
```

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

![image-20210804170225486](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywGamTtV3OgGz77%2Fimage-20210804170225486.png?generation=1628073370734342\&alt=media)

​ 同时，我们再查看刚才开启服务器的窗口，服务端已接收到我们客户端的请求，并将计算结果响应给我们，如下图所示：

![image-20210804170240434](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywHdcEVmLJYFaUz%2Fimage-20210804170240434.png?generation=1628073370737023\&alt=media)

​ 现在，你已经成功地运行了你使用 C++编写的Service 和Client 程序，下一节我们将学习如何使用Python 来编写一个简单的服务器和客户端。

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

​ 如果你想做更深入的研究，或者是得到更多的操作示例，你可以从这个链接找到 [here](https://github.com/fairlight1337/ros_service_examples/)。一个简单的Client 与 Service 的组合程序演示了自定义消息类型的使用方法。如果Service 节点是用C++代码编写的，编写 Client 用 C++，Python 或者是 LISP 都可以。

## 第十六章 编写 Service 和 Client (Python)

​ **描述：**&#x672C;节将介绍如何用Python 编写 Service 和Client 节点。

### 16.1 编写 Service 节点

​ 这里，我们将创建一个简单的 service 节点("add\_two\_ints\_server")，该节点将接收到两个整形数字，并返回它们的和。 ​ 首先进入到之前创建的 beginner\_tutorials 包中的 scripts 目录下：

```bash
$ roscd beginner_tutorials/scripts
```

​ 请确保已经按照《第 10 课 创建 ROS 消息和 ROS 服务》中的步骤创建了本节所需要的 srv 服务AddTwoInts（确保选择了对应的编译系统“catkin”和“rosbuild”）。

#### 16.1.1 代码

​ 在 beginner\_tutorials 包中的 scripts 目录下创建 add\_two\_ints\_server.py 文件：

```bash
$ touch add_two_ints_server.py
```

​ 使用 gedit 打开该文件：

```bash
$ gedit add_two_ints_server.py
```

​ 然后将下面的代码复制到 add\_two\_ints\_server.py 文件中并保存：

```python
#!/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()
```

​ 然后赋给该文件可执行权限：

```bash
$ sudo chmod +x add_two_ints_server.py
```

​ 操作过程如下图所示：

![image-20210804170720164](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywIVx_uGzbt8Uj0%2Fimage-20210804170720164.png?generation=1628073370733799\&alt=media)

![image-20210804170726710](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywJrf7k183i1vAM%2Fimage-20210804170726710.png?generation=1628073370936402\&alt=media)

![image-20210804170737360](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywKez1EEadZNAT-%2Fimage-20210804170737360.png?generation=1628073370722018\&alt=media)

#### 16.1.2 代码解释

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

​ 使用 [rospy ](http://wiki.ros.org/rospy)来编写一个 service 代码很少，我们使用 init\_node()来声明我们的节点， 然后使用 rospy.Service()声明我们的 service：

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

​ 这行代码声明了一个新的名为 add\_two\_ints 的服务，它的服务类型为 AddTwoInts 。所有的请求将被传给 handle\_add\_two\_ints 函数处理。函数 handle\_add\_two\_ints 被调用时传入请求参数 AddTwoIntsRequest 对象，然后返回响应结果 AddTwoIntsResponse 对象。

​ 和订阅者中的一样， rospy.spin() 保持服务不退出，直到服务被关闭为止。

### 16.2 编写 Client 节点

#### 16.2.1 代码

​ 在 beginner\_tutorials 包中的 scripts 目录下创建 add\_two\_ints\_client.py 文件：

```bash
$ touch add_two_ints_client.py
```

​ 使用 gedit 打开该文件：

```bash
$ gedit add_two_ints_client.py
```

​ 然后将下面的代码复制到文件中并保存：

```python
#!/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)))
```

​ 然后赋给该文件可执行权限：

```bash
$ sudo chmod +x add_two_ints_client.py
```

​ 上述操作过程具体如下：

![image-20210804171305449](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywL_YDL0diVik2X%2Fimage-20210804171305449.png?generation=1628073370797517\&alt=media)

![image-20210804171328112](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywMWdHDe_y5-4PE%2Fimage-20210804171328112.png?generation=1628073370890009\&alt=media)

![image-20210804171335943](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywNVdvqzUcaxXMU%2Fimage-20210804171335943.png?generation=1628073370729440\&alt=media)

#### 16.2.2 代码解释

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

​ 这个调用服务的客户端代码也很简单。对于客户端 clients，你不必调用 init\_node() 函数。 我们首先调用:

```python
rospy.wait_for_service('add_two_ints')
```

​ wait\_for\_service 这个方法会阻塞程序，一直等待服务端，直到服务端 add\_two\_ints 可用，才开始执行下面的程序。

​ 下一步，我们创建一个调用服务的句柄:

```python
    add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
```

​ 我们可以像使用函数一样来使用 add\_two\_ints 这个句柄并调用它:

```python
    resp1 = add_two_ints(x, y) 
    return resp1.sum
```

​ 因为我们已经声明了这个服务的类型为AddTwoInts，它会自动帮我们生成AddTwoIntsRequest 对象（你也可以传入你自己创建的）。返回值是一个AddTwoIntsResponse 对象。如果调用失败，将抛出一个 ospy.ServiceException 异常，所以我们应该使用try/except 代码块来捕捉这个异常，并进行相应处理，如上面代码打印出异常错误信息。

### 16.3 编译节点

​ 我们使用 cmake 作为编译系统，是的甚至你必须用它来编译你的Python 节点。这是为了确保为使用的自定义的消息和服务创建了自动生成的 python 代码。 ​ 进入你的 catkin 工作空间，然后运行 catkin\_make:

```bash
# In your catkin workspace
$ cd ~/catkin_ws
$ catkin_make
```

### 16.4 测试 service 和 client

#### 16.4.1 启动 service

​ 确保 roscore 可用，并运行：

```bash
$ roscore
```

​ 在调用 catkin\_make 后，在运行你自己的程序前，确保已经 source 了 catkin 工作空间下的 setup.sh 文件：

```bash
# 进入你的 catkin 工作空间
$ cd ~/catkin_ws
$source ./devel/setup.bash
```

​ **注意：**&#x82E5;已在\~/.bashrc 文件中配置过工作空间，则无需再执行上面操作。然后运行 service 节点：

```bash
$ rosrun beginner_tutorials add_two_ints_server.py
```

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

![image-20210804171631367](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywOolvX2uh9vJQm%2Fimage-20210804171631367.png?generation=1628073370747845\&alt=media)

#### 16.4.2 启动 client

​ 新打开一个终端，生效自己创建的工作空间，然后运行 Client 客户端并附带一些参数：

```bash
$ rosrun beginner_tutorials add_two_ints_client.py 12 17
```

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

![image-20210804171707456](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywQsiNMCAPz71D6%2Fimage-20210804171707456.png?generation=1628073370741426\&alt=media)

​ 同时，我们再查看刚才开启服务器的窗口，服务端已接收到我们客户端的请求，并将计算结果响应给我们，如下图所示：

![image-20210804171719588](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywR-QoqcANF78s5%2Fimage-20210804171719588.png?generation=1628073370733974\&alt=media)

​ 现在，你已经成功地运行了你使用Python 编写的Service 和Client 程序，下一节我们将学习 Rosbag 录制与回放数据。

## 第十七章 Rosbag 录制与回放数据

​ 描述: 本节将教你如何将 ROS 系统运行过程中的数据录制到一个.bag 文件中，然后通过回放数据来重现相似的运行过程。 ​ 这节课其实是挺实用的，后面我们再进行复杂实验的时候，我们可以将实验的某个重要环节的数据记录下来，这样我们下次再用时，不用每次都去复现。同时，我们也可以将这数据分享给别人，同时也可以从别人那得到数据，这样更方便大家的学习交流，而不用受限于实际的硬件条件。

### 17.1 录制数据（通过创建一个 bag 文件）

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

​ 首先，分别在三个终端上执行以下命令：

```bash
$ roscore
$ rosrun turtlesim turtlesim_node
$ rosrun turtlesim turtle_teleop_key
```

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

![image-20210804171856133](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywT0Lxcu3d-FFwK%2Fimage-20210804171856133.png?generation=1628073370733405\&alt=media)

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

#### 17.1.1 录制所有发布的话题

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

```bash
rostopic list -v
```

​ 这应该会生成以下输出：

![image-20210804172012321](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywUz6pJsV2WgXiC%2Fimage-20210804172012321.png?generation=1628073370909876\&alt=media)

​ 上图中公告话题部分列出的话题消息是唯一可以被录制保存到文件中的的话题消息，因为只有消息已经被发布了才可以被录制。/turtle1/cmd\_vel 话题是 teleop\_turtle 节点所发布的命令消息并作为 turtlesim 节点的输入。而/turtle1/color\_sensor 和/turtle1/pose 是 turtlesim 节点发布出来的话题消息。

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

```bash
$ rosbag record -a
```

​ 在这里，我们直接在当前目录下运行 **rosbag record** 命令，并附&#x52A0;**-a** 选项，该选项表示将当前发布的所有话题数据都录制保存到一个 bag 文件中。而这个bag 文件就存放在当前目录下，你也可以先建立一个用于录制的临时目录，然后进入到该目录下执行 **rosbag record** 命令，将 bag 文件保存到该目录下。

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

​ 然后我们**退出录制：**&#x5728;运行 **rosbag** **record** 命令的窗口中按Ctrl-C 退出该命令。现在我们用 ls 命令检查看当前目录中的内容，应该会看到一个以年份、日期和时间命名并以.bag 作为后缀的文件。这个就是 bag 文件，它包含 **rosbag** **record** 运行期间所有节点发布的话题。

​ 具体执行过程如下图所示：

![image-20210804172059661](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywV_dgyZ_tjVL__%2Fimage-20210804172059661.png?generation=1628073370891225\&alt=media)

### 17.2 检查并回放 bag 文件

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

```bash
rosbag info <your bagfile>
```

​ 用你自己刚才生成的 bag 文件名替代，而以我们刚才生成的 bag 文件为例：

```bash
$ rosbag info 2020-10-28-20-13-39.bag
```

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

![image-20210804172201145](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywWY5In8t231Zjo%2Fimage-20210804172201145.png?generation=1628073370865070\&alt=media)

​ 这些信息告诉我们这个 bag 文件中所包含话题的名称、类型和消息数量。我们可以看到，在之前使用 rostopic 命令查看到的五个已发布的话题中，其实只有其中的四个在我们录制过程中发布了消息。因为我们带-a 参数选项运行 rosbag record 命令时系统会录制下所有节点发布的所有消息。 ​ 下一步是回放 bag 文件以再现系统运行过程。首先在运行turtle\_teleop\_key 节点时所在的终端窗口中，按Ctrl+C 键退出该节点。但让turtlesim 节点继续运行。在终端中 bag 文件所在目录下运行以下命令：

```bash
rosbag play <your bagfile>
```

​ 用你自己刚才生成的 bag 文件名替代，而以我们刚才生成的 bag 文件为例：

```bash
$ rosbag play 2020-10-28-20-13-39.bag
```

​ 在这个窗口中我们应该会立即看到如下类似信息：

![image-20210804172237389](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywX_ln-GNhAmPLw%2Fimage-20210804172237389.png?generation=1628073371561763\&alt=media)

​ 默认模式下，rosbag play 命令在公告每条消息后会等待一小段时间（0.2 秒）后才真正开始发布 bag 文件中的内容。等待一段时间的过程可以通知消息订阅器消息已经公告了， 消息数据可能会马上到来。如果 rosbag play 在公告消息后立即发布，订阅器可能会接收不到几条最先发布的消息。等待时间可以通过-d 选项来指定。 ​ 最终/turtle1/cmd\_vel 话题将会被发布，同时在 turtuelsim 虚拟画面中 turtle 应该会像之前你通过 turtle\_teleop\_key 节点控制它一样开始移动。从运行 rosbag play 到 turtle 开始移动时所经历时间应该近似等于之前在本节开始部分运行 rosbag record 后到开始按下键盘发出控制命令时所经历时间。你可以通过-s 参数选项让 rosbag play 命令等待一段时间跳过 bag 文件初始部分后再真正开始回放。最后一个可能比较有趣的参数选项是-r 选项，它允许你通过设定一个参数来改变消息发布速率。如果你执行：

```bash
$rosbag play -r 2 <your bagfile>
```

​ 你应该会看到 turtle 的运动轨迹有点不同了，这时的轨迹应该是相当于当你以两倍的速度通过按键发布控制命令时产生的轨迹。大家课下可以自己去尝试下，这里就不再详细介绍了。

### 17.3 录制数据子集

​ 当运行一个复杂的系统时，比如 PR2 软件系统，会有几百个话题被发布，有些话题会发布大量数据（比如包含摄像头图像流的话题）。在这种系统中，要想把所有话题都录制保存到硬盘上的单个bag 文件中是不切实际的。rosbag record 命令支持只录制某些特别指定的话题到单个 bag 文件中，这样就允许用户只录制他们感兴趣的话题。 ​ 如果 turtlesim 节点还在运行，则先退出它们，然后重新启动，同时也再启动键盘控制节点：

```bash
$ rosrun turtlesim turtlesim_node
```

​ 新的终端

```bash
$ rosrun turtlesim turtle_teleop_key
```

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

```bash
$ rosbag record /turtle1/cmd_vel
```

​ rosbag record 后面的话题参数告诉 **rosbag record** 只录制这个指定的话题。然后通过键盘控制 turtle 随处移动几秒钟，最后按 Ctrl+C 退出 **rosbag record** 命令。执行过程如下图所示：

![image-20210804172417065](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywY1UBq9eiOvA2F%2Fimage-20210804172417065.png?generation=1628073370917923\&alt=media)

​ **注：**&#x72;osbag record 命令中的-O 参数，可用于指定bag 文件名，如下面的命令，则告诉**rosbag record** 将数据记录保存到名为 subset.bag 的文件中，课后大家可以去尝试下。

​ 同样，回话我们刚才录制的消息包 bag 文件，我们会看到小乌龟动起来了，和之前用键盘控制的效果一样。

![image-20210804172454557](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywZoLoTEfWHU697%2Fimage-20210804172454557.png?generation=1628073370932824\&alt=media)

### 17.4 rosbag record/play 命令的局限性

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

​ 现在你已经学会了如何录制和回放数据，接下来我们将对之前的课程进行总结，并对后面的ROS 相关学习进行展望。

## 第十八章 ROS 基础总结和展望

​ **描述：**&#x672C;节我们将对ROS 系统进行概括总结，对前面学习的ROS 基础课程进行总结，以及对后期ROS 深入学习进行展望。 ​ 到目前为止，我们已经学完了 ROS 基础课程，下面我们将对这些所学的内容进行总结， 首先，我们对ROS 系统进行概括总结：

### 18.1 ROS 系统概述

#### 18.1.1ROS 是什么

​ ROS 是用于编写机器人软件程序的一种具有高度灵活性的软件架构。它包含了大量工具软件、库代码和约定协议，旨在简化跨机器人平台创建复杂、鲁棒的机器人行为这一过程的难度与复杂度。 ​ ROS 设计者将 ROS 表述为“ROS = Plumbing + Tools + Capabilities + Ecosystem”，即ROS 是通讯机制、工具软件包、机器人高层技能以及机器人生态系统的集合体。

​ 关于 ROS 是什么，一些不同解释如下：

**ROS wiki** **的解释**

​ ROS（Robot Operating System，下文简称“ROS”）是一个适用于机器人的开源的元操作系统。它提供了操作系统应有的服务，包括硬件抽象，底层设备控制，常用函数的实现， 进程间消息传递，以及包管理。它也提供用于获取、编译、编写、和跨计算机运行代码所需的工具和库函数。

​ ROS 的主要目标是为机器人研究和开发提供代码复用的支持。ROS 是一个分布式的进程（也就是“节点”）框架，这些进程被封装在易于被分享和发布的程序包和功能包中。ROS 也支持一种类似于代码储存库的联合系统，这个系统也可以实现工程的协作及发布。这个设计可以使一个工程的开发和实现从文件系统到用户接口完全独立决策（不受 ROS 限制）。同时，所有的工程都可以被 ROS 的基础工具整合在一起。

**Brian Gerkey** **的网上留言**

​ 我通常这样解释ROS：

1. 通道：ROS 提供了一种发布-订阅式的通信框架用以简单、快速地构建分布式计算系。
2. 工具：ROS 提供了大量的工具组合用以配置、启动、自检、调试、可视化、登录、测试、终止分布式计算系统。
3. 强大的库：ROS 提供了广泛的库文件实现以机动性、操作控制、感知为主的机器人功能。
4. 生态系统：ROS 的支持与发展依托着一个强大的社区。ros.org 尤其关注兼容性和支持文档，提供了一套“一站式”的方案使得用户得以搜索并学习来自全球开发者数以千计的ROS 程序包。

**摘自《ROS by Example》的解释** ROS 的首要目标是提供一套统一的开源程序框架，用以在多样化的现实世界与仿真环境中实现对机器人的控制。

#### 18.1.2 ROS 的历史

​ ROS 是一个由来已久、贡献者众多的大型软件项目。在 ROS 诞生之前，很多学者认为，机器人研究需要一个开放式的协作框架，并且已经有不少类似的项目致力于实现这样的框架。在这些工作中，斯坦福大学在 2000 年年中开展了一系列相关研究项目，如斯坦福人工智能机器人（STandford AI Robot, STAIR）项目、个人机器人（Personal Robots, PR） 项目等，在上述项目中，在研究具有代表性、集成式人工智能系统的过程中，创立了用于室内场景的高灵活性、动态软件系统，其可以用于机器人学研究。 ​ 2007 年，柳树车库（Willow Garage）提供了大量资源，用于将斯坦福大学项目中的软件系统进行扩展与完善，同时，在无数研究人员的共同努力下，ROS 的核心思想和基本软件包逐渐得到完善。

#### 18.1.3 许可协议

​ ROS 生态系统中的每个软件包都需要指定一个许可协议：

* ROS 核心代码使用“三句版 BSD 协议” 。
* 由于ROS 软件包还包含大量社区软件包，这些软件包不一定使用BSD 协议，而是使用其他某种许可协议，如 Apache 2.0 协议、GPL 协议、MIT 协议或自定义的某种协议。

#### 18.1.4 主要发行版本

​ ROS 的发行版本（ROS distribution）指 ROS 软件包的版本，其与 Linux 的发行版本（如 Ubuntu）的概念类似。推出 ROS 发行版本的目的在于使开发人员可以使用相对稳定的代码库，直到其准备好将所有内容进行版本升级为止。因此，每个发行版本推出后，ROS 开发者通常仅对这一版本的bug 进行修复，同时提供少量针对核心软件包的改进。截至 2019 年 10 月，ROS 的主要发行版本的版本名称、发布时间与版本生命周期如下表所示 ：

![image-20210804173116186](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiyw_cQddA12Y0jGc%2Fimage-20210804173116186.png?generation=1628073371260759\&alt=media)

#### 18.1.5 主要功能

​ ROS 提供一些标准操作系统服务，例如硬件抽象，底层设备控制，常用功能实现，进程间消息以及数据包管理。ROS 是基于一种图状架构，从而不同节点的进程能接受，发布， 聚合各种信息（例如传感，控制，状态，规划等等）。 ​ ROS 可以分成两层，低层是上面描述的操作系统层，高层则是广大用户群贡献的实现不同功能的各种软件包，例如定位绘图，行动规划，感知，模拟等等。

**参考资料：**

1.[ ](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[1]_15404171)[ROS.org | History](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[1]_15404171) ．ROS．2007-09-01\[引用日期 2019-10-11]

2.[ ](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[2]_15404171)[About ROS ](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[2]_15404171)．ROS.org．2019-10-11\[引用日期 2019-10-11]

3.[ ](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[3]_15404171)[The 3-Clause BSD License ](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[3]_15404171)．Open Source Initiative．2019-01-01\[引用日期2019-10-11]

4.[ ](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[4]_15404171)[Distributions - ROS Wiki ](https://baike.baidu.com/item/ros/4710560?fr=aladdin\&ref_\[4]_15404171)．ROS.org．2019-06-26\[引用日期 2019-10-11]

### 18.2 ROS 课程总结和展望

#### 18.2.1 ROS 基础课程总结

​ 到此，我们已经参照 [ROS WIKI 教程 ](http://wiki.ros.org/cn/ROS/Tutorials)将 ROS 核心教程中的初级课程学习完毕，我们已经掌握了 ROS 基础：可以编写 ROS 节点，发布和订阅话题；编写服务和客户端；自定义消息和服务；使用常用的 ROS 工具等。其中 roswtf 工具入门这节由于这个工具目前对我们来说没什么用处，所以这里就没有再讲解。我们已学习内容如下图所示：

![image-20210804173238666](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywaVgR6w3ckJm5V%2Fimage-20210804173238666.png?generation=1628073371595099\&alt=media)

#### 18.2.2 ROS 后期学习展望

​ 有了前面ROS 基础课程的学习，接下来我们就可以学习ROS 的高阶课程。后面我们将对ROS 系统进行深入了解，并结合我们的机器人平台，教大家如何搭建自己的ROS 机器人，并且学习如何实现机器人的SLAM 导航和视觉导航等。 我们会参照ROS WIKI 教程进行后期ROS 高阶课程的学习，其中教程中的第 4 部分 在机器人上运行ROS 的 ros\_control 使用 ROS 的标准控制器框架来与硬件连接（即如何搭建自己的ROS 机器人）；第 5 部分中的 导航功能包；第 6 部分的 TF 后面课程我们会详细重点的讲解。大家也可以先去ROS WIKI 上了解更多详细的内容，如下图所示：

![image-20210804173420140](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywbJ_-MBPUzCZRp%2Fimage-20210804173420140.png?generation=1628073370933619\&alt=media)

![image-20210804173425999](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywcu-sdKGFHFTHo%2Fimage-20210804173425999.png?generation=1628073370898659\&alt=media)

![image-20210804173432062](https://2529405142-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2Fjujon%2F-MgFdRwJbmjMEwhVCILi%2F-MgFiywdnrneQjXTMayL%2Fimage-20210804173432062.png?generation=1628073370736619\&alt=media)

​ ROS 基础课程到此就结束了，谢谢大家的阅读。大家课下再多多练习，将我们的基础课程吃透，然后我们再一起进入下面的ROS 高阶课程的学习。
