引言
截止到 2019年5月,ROS 2 已经正式发布了 4 个版本(以及更早期的几个 alpha, beta 测试版本)。ROS 2 版本的命名方式依然延续了 ROS 的规则(实际上也是 Ubuntu 版本的命名规则),按照字母顺序依次命令,而且都是 ×... ×... 格式的:
- Ardent Apalone
- Bouncy Bolson
- Crystal Clemmys
- Dashing Diademata
我们在学习和做项目过程中,逐渐感觉到 ROS 2 趋于成熟,参考的很多项目已经改用 ROS 2 实现。就像 python 2 向 python 3 的转换,随着开发者贡献的 package 越来越多,新的平台越来越成熟,功能逐渐完善,最后彻底的转换就成了大势所趋。
ROS 2 不是 ROS 的简单扩展,而是全新的架构,如下所示:
关于 ROS 2 新框架有如下描述 (来源):
For ROS 2 the decision has been made to build it on top of an existing middleware solution (namely DDS). The major advantage of this approach is that ROS 2 can leverage an existing and well developed implementation of that standard.
ROS 2 几乎所有的优点 (无 master节点、实时性好、安全性高等) 都得益于站在了 DDS (Data Distribution Service) 这个巨人的肩膀上。
DDS 只是一个协议标准,它有多种具体的实现 (implementation),例如:
- FastRTPS (ROS 2 默认的 DDS 实现)
- OpenSplice
- FreeRTPS
- RTI Connext
在 ROS 2 的安装过程中可以设定使用哪个 DDS 实现。一般情况下,使用默认的 Fast-RTPS 即可。
如果只是将 ROS 2 作为工具来用,在 ROS 2 client library 的基础上搭建自己的项目,可以暂时忽略掉底层的 DDS 等新机制,掌握 ROS 2 的常用命令应该就够了。回想一下,在之前使用 ROS 的时候,我们也并不需要了解底层的 TCPROS 和 UDPROS 通讯协议。
相比于 ROS 整洁条理的 wiki,ROS 2 的文档稍显粗糙。
本文的目的是尝试整理总结 ROS 2 的基本操作命令,方便以后使用时查阅。
如果用 Debian package 方式安装,即 sudo apt install 方式, Ubuntu 16.04 只能安装 Ardent 版本,Ubuntu 18.04 可以安装 Bouncy,Crystal 和 Dashing。
如果用源码编译的方式安装,Ubuntu 16.04 可以安装 Ardent,Bouncy 和 Crystal 。
我们选择的平台:
- Ubuntu 18.04
- ROS 2 (Dashing 版本)
这两个都是 LTS (Long Term Support) 版本,在未来较长的一段时间内应该是主流选择。
ROS 2 安装
ROS 2 官网上有详细的安装步骤。为了方便查阅,抄录到这里。
- 设置环境变量 LC_ALL 和 LANG,这两个变量描述了用户所在地区、使用的语言、日期、货币格式等。网站上并没有说为什么必须设置这两个变量。猜测可能是 ROS 2 中的某些显示结果 (例如报错信息、日期、货币格式等) 会受到这两个环境变量的影响。这里按照网站上的例子设置就可以了。
sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 export LANG=en_US.UTF-8
- 添加 ROS 2 的 repo 以及对应的 key
sudo apt update sudo apt install curl gnupg2 lsb-release curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - sudo sh -c 'echo "deb [arch=amd64,arm64] http://packages.ros.org/ros2/ubuntu `lsb_release -cs` main" > /etc/apt/sources.list.d/ros2-latest.list'
- 安装 ROS 2
sudo apt update sudo apt install ros-dashing-desktop
- 添加命令自动补全功能,即在命令行中用 TAB 键可以自动补全 ROS 2 的命令。
sudo apt install python3-argcomplete
- 添加环境变量。这样每次打开 Terminal 就可以自动加载 ROS 2 相关的环境变量,进而使用 ROS 2 相关的命令
echo "source /opt/ros/dashing/setup.bash" >> ~/.bashrc
- 如果涉及到 ROS 节点与 ROS 2 节点通讯,还要安装 ros1_bridge
sudo apt install ros-dashing-ros1-bridge
colcon 编译工具
colcon 的设计理念可以在这里查到。概括来说,colcon 的目标就是成为一个通用的编译工具,现在主要用来编译 ROS,ROS 2 以及 Gazebo,未来可能使用更广泛。所以尽管 colcon 最早的开发动力来自 ROS 2,但它的定位并不是 ROS 2 的附属品。 colcon 有非常详细的文档,可以在这里查阅。
在 ROS 2 Ardent 版本中编译工具是 ament_tools,从 ROS 2 Bouncy 版本开始,colcon 就成了默认的编译工具。
安装 colcon
colcon 包含在了 ROS 2 的 repo 中,前边已经添加过了这个 repo,所以这里直接安装即可:
sudo apt install python3-colcon-common-extensions
由于 colcon 本质上独立于 ROS 2,我们并不一定先添加 ROS 2 的 repo 再安装 colcon,也可以直接用 pip 方式安装,要求 python 3.5 及以上版本
sudo pip3 install -U colcon-common-extensions
创建工作空间
跟 ROS 相同,ROS 2 也是建议创建一个工作空间 workspace,方便管理同一个项目的 packages,而且也是将 package 源文件都放在 src 文件夹中。这里我们用 ROS 2 tutorial 中的例子,创建工作空间 ros2_example_ws 并进入 src
mkdir -p ~/ros2_example_ws/src
cd ~/ros2_example_ws/src
现在我们先关注 colcon 的编译过程,所以 package 源文件就先借用官网的。
git clone https://github.com/ros2/examples
git checkout $ROS_DISTRO # 切换到与本机版本对应的 branch 上
目前文件路径结构如下:
$ tree -L 3
.
└── src
└── examples
├── CONTRIBUTING.md
├── LICENSE
├── rclcpp
├── rclpy
└── README.md
4 directories, 3 files
可以用 colcon list
命令列出 src 文件夹中的所有 packages,即所有包含 package.xml
和 setup.py
/CMakeLists.txt
的文件夹。
$ colcon list
examples_rclcpp_minimal_action_client /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_action_client (ros.ament_cmake)
examples_rclcpp_minimal_action_server /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_action_server (ros.ament_cmake)
examples_rclcpp_minimal_client /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_client (ros.ament_cmake)
examples_rclcpp_minimal_composition /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_composition (ros.ament_cmake)
examples_rclcpp_minimal_publisher /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_publisher (ros.ament_cmake)
examples_rclcpp_minimal_service /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_service (ros.ament_cmake)
examples_rclcpp_minimal_subscriber /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_subscriber (ros.ament_cmake)
examples_rclcpp_minimal_timer /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_timer (ros.ament_cmake)
examples_rclpy_executors /home/automan/ros2_example_ws/src/examples/rclpy/executors (ros.ament_python)
examples_rclpy_minimal_action_client /home/automan/ros2_example_ws/src/examples/rclpy/actions/minimal_action_client (ros.ament_python)
examples_rclpy_minimal_action_server /home/automan/ros2_example_ws/src/examples/rclpy/actions/minimal_action_server (ros.ament_python)
examples_rclpy_minimal_client /home/automan/ros2_example_ws/src/examples/rclpy/services/minimal_client (ros.ament_python)
examples_rclpy_minimal_publisher /home/automan/ros2_example_ws/src/examples/rclpy/topics/minimal_publisher (ros.ament_python)
examples_rclpy_minimal_service /home/automan/ros2_example_ws/src/examples/rclpy/services/minimal_service (ros.ament_python)
examples_rclpy_minimal_subscriber /home/automan/ros2_example_ws/src/examples/rclpy/topics/minimal_subscriber (ros.ament_python)
用 colcon 编译下载的 package
colcon build --symlink-install
上边命令中 --symlink-install
表示编译时如果 install 中文件已经存在于 src 或者 build 文件夹中,就用超链接指向该文件,避免浪费空间,也可以实现同步更新。
例如,在 install 文件夹的 examples_rclcpp_minimal_publisher 中有如下超链接:
.
├── lib
│ └── examples_rclcpp_minimal_publisher
│ ├── publisher_lambda -> /home/automan/ros2_example_ws/build/examples_rclcpp_minimal_publisher/publisher_lambda
│ ├── publisher_member_function -> /home/automan/ros2_example_ws/build/examples_rclcpp_minimal_publisher/publisher_member_function
│ └── publisher_not_composable -> /home/automan/ros2_example_ws/build/examples_rclcpp_minimal_publisher/publisher_not_composable
└── share
├── ament_index
│ └── resource_index
├── colcon-core
│ └── packages
└── examples_rclcpp_minimal_publisher
├── cmake
├── environment
├── hook
├── local_setup.bash -> /home/automan/ros2_example_ws/build/examples_rclcpp_minimal_publisher/ament_cmake_environment_hooks/local_setup.bash
├── local_setup.sh -> /home/automan/ros2_example_ws/build/examples_rclcpp_minimal_publisher/ament_cmake_environment_hooks/local_setup.sh
├── local_setup.zsh -> /home/automan/ros2_example_ws/build/examples_rclcpp_minimal_publisher/ament_cmake_environment_hooks/local_setup.zsh
├── package.bash
├── package.ps1
├── package.sh
├── package.xml -> /home/automan/ros2_example_ws/src/examples/rclcpp/minimal_publisher/package.xml
└── package.zsh
如果去掉 --symlink-install
参数,仅用命令 colcon build
来编译,则上述超链接文件全都变成实体拷贝的文件,得到如下结果:
.
├── lib
│ └── examples_rclcpp_minimal_publisher
│ ├── publisher_lambda
│ ├── publisher_member_function
│ └── publisher_not_composable
└── share
├── ament_index
│ └── resource_index
├── colcon-core
│ └── packages
└── examples_rclcpp_minimal_publisher
├── cmake
├── environment
├── hook
├── local_setup.bash
├── local_setup.sh
├── local_setup.zsh
├── package.bash
├── package.ps1
├── package.sh
├── package.xml
└── package.zsh
编译之后,得到的文档结构如下:
.
├── build
├── install
├── log
└── src
即 colcon 编译产生了 build
, install
, log
三个新文件夹。
编译之后还可以测试一下 packages
$ colcon test-result --all
build/examples_rclpy_executors/pytest.xml: 3 tests, 0 errors, 0 failures, 0 skipped
build/examples_rclpy_minimal_action_client/pytest.xml: 0 tests, 0 errors, 0 failures, 0 skipped
build/examples_rclpy_minimal_action_server/pytest.xml: 0 tests, 0 errors, 0 failures, 0 skipped
build/examples_rclpy_minimal_client/pytest.xml: 3 tests, 0 errors, 0 failures, 0 skipped
build/examples_rclpy_minimal_publisher/pytest.xml: 3 tests, 0 errors, 0 failures, 0 skipped
build/examples_rclpy_minimal_service/pytest.xml: 3 tests, 0 errors, 0 failures, 0 skipped
build/examples_rclpy_minimal_subscriber/pytest.xml: 3 tests, 0 errors, 0 failures, 0 skipped
Summary: 15 tests, 0 errors, 0 failures, 0 skipped
如果要单独编译某一个 package,可以用如下命令:
colcon build --packages-select PACKAGE_NAME
如果不希望编译某一个 package,可以在该 package中创建名为 COLCON_IGNORE
的空文件,colcon 就会忽略掉该 package,不但不编译,连 colcon list
都不显示,这个 package 对 colcon 就是透明的。
完成编译之后,要 source 一下 setup.bash 文件,确保系统能够找到当前编译生成的可执行文件和库
source install/setup.bash
或者将 source 命令放入 .bashrc 文件,这样每次打开 terminal 就可以自动加载路径信息
echo "source ~/ros2_example_ws/install/setup.bash" >> ~/.bashrc
实例测试:
首先启动一个 publisher
ros2 run examples_rclcpp_minimal_publisher publisher_member_function
再启动一个 subscriber
ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function
如果一切顺利,应该会有如下的界面:
这里与 ROS 的最大区别是不需要启动 ROS master 节点,即不需要类似 roscore
的命令。ROS 2 是真正的分布式系统,不需要中心节点,这样系统的鲁棒性更强,不会因为中心节点失效而影响整个系统。
terminal 命令
分门别类
ROS 2 根据命令的作用对象划分成多个类别,其中常用的几个类别:
Commands:
launch Run a launch file
node Various node related sub-commands
param Various param related sub-commands
pkg Various package related sub-commands
run Run a package specific executable
service Various service related sub-commands
srv Various srv related sub-commands
topic Various topic related sub-commands
在调用时都是采用如下命令格式:
ros2 COMMAND ...
我们可以对比 ROS 和 ROS 2 中的几个命令,应该很容易找到其中的规律
- 运行 ROS node
- ROS:rosrun <package> <executive>
- ROS 2:ros2 run <package> <executive>
- 查看当前运行的 node
- ROS :rosnode list
- ROS 2:ros2 node list
修改 node 名字
在前边的例子中,默认 node 名字分别为 /minimal_publisher
和 /minimal_subscriber
。我们可以在启动 node 时用 __node:=NEW_NAME
命令修改他们的名字,例如修改 /minimal_publisher
为 /my_publisher
:
ros2 run examples_rclcpp_minimal_publisher publisher_member_function __node:=my_publisher
还可以通过 __ns:=NEW_NAMESPACE
修改 node 的 namespace。有些 node 的名字形式为 /a/b/c/d
,其中 d
称为 basename,前边的 /a/b/c
被称为 namespace。
例如,我们给刚才的 node /my_publisher
加个 namespace /mynode
,即最后 node 的名字为 /mynode/my_publisher
:
ros2 run examples_rclcpp_minimal_publisher publisher_member_function __ns:=/mynode __node:=my_publisher
修改 topic, service 的名字
如果要修改 node 文件中的某个 topic 或者 service 的名字,则用 OLD_NAME:=NEW_NAME
的形式,例如原本 /minimal_publisher
中的 topic 名字为 topic
,这里修改成 my_topic
:
ros2 run examples_rclcpp_minimal_publisher publisher_member_function topic:=my_topic
编写自己的 node 文件 (基于 rclpy)
在 ROS 2 的 wiki 和 github 中提供了一些例子,这里整理总结一下,方便以后查阅。
参考文献:
https://github.com/ros2/examples
https://github.com/ros2/demos/tree/master/demo_nodes_py
Talker 与 Listener
publish 与 subscribe 是 ROS / ROS 2 中最基本的场景。下边是一个简单的 publish 的例子。定义了一个 node 名为 talker
,以 0.5s 每次的频率向 chatter
这个 topic 上发布消息,同时也在屏幕上显示消息内容:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Talker(Node):
def __init__(self):
super().__init__('talker') # 继承 Node class 的初始化函数,生成 node 名为 talker
self.pub = self.create_publisher(String, 'chatter', 10)
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback) # 设定计时器,到时间就调用 callback 函数
self.i = 1
def timer_callback(self):
msg = String()
msg.data = 'Hello World: %d' % self.i
self.pub.publish(msg) # 向 topic 上发布数据
self.get_logger().info('Publishing: "%s"' % msg.data) # 显示 log 信息
self.i += 1
def main(args=None): # 这里的 args 是从命令行中接收的参数
rclpy.init(args=args) # 在初始化时不再设定 node 的名字
talker = Talker()
rclpy.spin(talker)
if __name__ == '__main__':
main()
与之对应的,下边是 listener
node 的内容:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Listener(Node):
def __init__(self):
super().__init__('listener')
self.subscription = self.create_subscription(String, 'chatter', self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info('I heard: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
listener = Listener()
rclpy.spin(listener)
if __name__ == '__main__':
main()
有了两个 python 文件,下边就是设置 ROS 2 package。
设置 ROS 2 package
假设现在的 ROS workspace 路径是 ~/ros2_example_ws/src
。在其中新建 ROS 2 package 文件夹 pub_sub_pkg
,内部文档目录结构如下:
.
├── package.xml
├── pub_sub_examples
│ ├── __init__.py
│ ├── listener.py
│ └── talker.py
└── setup.py
2 directories, 6 files
其中
__init__.py
是个空文件,主要作用是表明当前文件夹是 python package-
package.xml
文件内容如下:<?xml version="1.0"?> <?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> <package format="2"> <name>pub_sub_examples</name> <version>0.0.1</version> <description>Examples of publisher and subscriber using rclpy.</description> <maintainer email="myemail@address">myname</maintainer> <license>Apache License 2.0</license> <exec_depend>rclpy</exec_depend> <exec_depend>std_msgs</exec_depend> <export> <build_type>ament_python</build_type> </export> </package>
其中关键是指定 node 文件中依赖的 package,我们的 talker 和 listener 文件都比较简单,只用到了
rclpy
和std_msgs
两个 python package. -
setup.py
内容如下:from setuptools import setup package_name = 'pub_sub_examples' setup( name=package_name, version='0.0.1', packages=[package_name], install_requires=['setuptools'], zip_safe=True, entry_points={ 'console_scripts': [ 'my_talker = pub_sub_examples.talker:main', 'my_listener = pub_sub_examples.listener:main' ] } )
其中的关键是指定
console_scripts
命令和对应的函数。colcon 编译之后可以直接在命令行中执行my_talker
和my_listener
实现 pub 和 sub 的功能。关于 python package 中setup.py
的编写,可以参考我们之前的文章。
使用 colcon 编译
设定好了上述文件之后,在 workspace ~/ros2_example_ws/
目录下编译:
colcon build --symlink-install
如果一切顺利,会在 ~/ros2_example_ws/install/pub_sub_examples/bin
中生成可执行文件 my_talker
和 my_listener
。此时还要再 source 一下 install
文件夹中的 setup.bash
,更新 ROS 2 package 路径信息,如果之前已经将 source setup.bash 加入 ~/.bashrc
文件,则 source ~/.bashrc
即可。source 之后,~/ros2_example_ws/install/pub_sub_examples/bin
就被加入了 PATH 环境变量中,在命令行中可以直接执行程序,效果如下:
由于我们编译时用了 --symlink-install
参数,所以对 src
中 python 源文件 talker.py
和 listener.py
的修改可以马上反映到 install
中的可执行文件中。打开 install
中的 talker
文件可以看到如下内容:
#!/usr/bin/python3
# EASY-INSTALL-ENTRY-SCRIPT: 'pub-sub-examples','console_scripts','my_talker'
__requires__ = 'pub-sub-examples'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('pub-sub-examples', 'console_scripts', 'my_talker')()
)
实际上就是执行了我们之前在 setup.py
文件中设定 console_scripts
中的内容。
通过 ros2 run 方式执行
上述编译方式得到的 node 不能用标准的 ros2 run <pkg> <node>
方式执行,ROS 2 默认要调用 install/<pkg>/<lib>
中的文件。如果要用标准方式启动 node 文件,需要做一些额外设置。在 setup.py
同一目录中添加如下 setup.cfg
文件:
[develop]
script-dir=$base/lib/pub_sub_examples
[install]
install-scripts=$base/lib/pub_sub_examples
这里是将编译输出的目标文件夹设置为 install/pub_sub_examples/lib/pub_sub_examples
。再次用 colcon 编译,然后可以看到对应文件夹下有 my_talker
和 my_listener
两个文件。重新 source 一下 ~/.bashrc
文件,就可以用 ros2 run pub_sub_examples my_talker/my_listener
命令启用两个 node 了: