ROS起源于2007年11月,2017年11月刚好满10岁。ROS的最初目标是在机器人领域提高代码的复用率,谁都没有想到,ROS社区中的功能包会呈指数级发展,目前已经成为机器人领域的事实标准。
ROS最初设计的目标机器人是PR2,这款机器人搭载了当时最先进的移动计算平台,而且网络性能优异,不需要考虑实时性方面的问题,主要应用于科研领域。如今ROS应用的机器人领域越来越广:轮式机器人、人形机器人、工业机械手、室外机器人(如无人驾驶汽车)、无人飞行器、救援机器人等等,美国NASA甚至考虑使用ROS开发火星探测器;而且机器人开始从科研领域走向人们的日常生活。ROS虽然仍是机器人领域的开发利器,但介于最初设计时的局限性,也逐渐暴露出不少问题。
一、 什么是ROS2.0
ROS已经走过十个年头,伴随着机器人技术的大发展,ROS也得到了极大的推广和应用。尽管还存在不少局限性,但无法掩盖ROS的锋芒,社区内的功能包还是呈指数级逐年上涨,为机器人开发带来了巨大的便利。不少开发者和研究机构还针对ROS的局限性进行了改良,但这些局部功能的改善往往很难带来整体性能的提升,机器人开发者对新一代ROS的呼声越来越大,ROS2.0的消息也不绝于耳。
终于在ROSCon 2014上,新一代ROS的设计架构(Next-generation ROS: Building on DDS)正式公布;2015年8月第一个ROS2.0的alpha版本落地;2016年12月19日,ROS2.0的beta版本正式发布;2017年12月8日,万众瞩目的ROS2.0终于发布了第一个正式版——Ardent Apalone。众多新技术和新概念应用到了新一代的ROS之中,不仅带来了整体架构的颠覆,更是增强了ROS2.0的综合性能。
1.1 ROS2的目标
相比ROS1,ROS2的设计目标更加丰富:
1. 支持多机器人系统
ROS2增加了对多机器人系统的支持,提高了多机器人之间通讯的网络性能,更多多机器人系统及应用将出现在ROS社区中。
2. 铲除原型与产品之间的鸿沟
ROS2不仅针对科研领域,还关注机器人从研究到应用之间的过渡,可以让更多机器人直接搭载ROS2系统走向市场。
3. 支持微控制器
ROS2不仅可以运行在现有的X86和ARM系统上,还将支持MCU等嵌入式微控制器,比如常用的ARM-M4、M7内核。
4. 支持实时控制
ROS2还加入了实时控制的支持,可以提高控制的时效性和整体机器人的性能。
5. 跨系统平台支持
ROS2不止能运行在Linux系统之上,还增加了对Windows和MACOS等系统的支持,让开发者的选择更加自由。
1.2 ROS2的架构
ROS2重新设计了整体架构,可以从下图中看到两代ROS之间架构的变化。
1. OS层
ROS1主要构建于Linux系统之上,ROS2带来了改变,支持构建的系统包括Linux、Windows、Mac、RTOS,甚至没有操作系统的裸机。
2. 中间件
ROS中最重要的一个概念就是基于发布/订阅模型的“节点”,可以让开发者并行开发低耦合的功能模块,并且便于二次复用。ROS1的通讯系统基于TCPROS/UDPROS,而ROS2的通讯系统基于DDS。DDS是一种分布式实时系统中数据发布/订阅的标准解决方案,下一小节会具体讲解。ROS2内部提供了DDS的抽象层实现,用户不需要关注底层DDS的提供厂家。
在ROS1的架构中Nodelet和TCPROS/UDPROS是并列的层次,为同一个进程中的多个节点提供一种更优化的数据传输方式。ROS2中也保留了这种数据传输方式,只不过换了一个名字,叫做“Intra-process”,同样也是独立于DDS。
3. 应用层
ROS1强依赖于ROS Master,可以想像一旦Master宕机,整个系统会面临如何的窘境。但是从右边ROS2的架构中我们可以发现,之前让人耿耿于怀的Master终于消失了,节点之间使用一种称为“Discovery”的发现机制来获取彼此的信息。
1.3 ROS2的核心——DDS
DDS,Data Distribution Service,即数据分发服务,2004年由对象管理组织OMG(Object Management Group)发布,是一种专门为实时系统设计的数据分发/订阅标准。
DDS最早应用于美国海军, 解决舰船复杂网络环境中大量软件升级的兼容性问题,目前已经成为美国国防部的强制标准,同时广泛应用于国防、民航、工业控制等领域,成为分布式实时系统中数据发布/订阅的标准解决方案。其技术核心是以数据为核心的发布/订阅模型(Data-Centric Publish-Subscribe ,DCPS),这种DCPS模型创建了一个“全局数据空间”(global data space)的概念,所有独立的应用都可以访问。
在DDS中,每一个发布者或者订阅者都称为参与者(participant),类似于ROS中节点的概念。每一个参与者都可以使用某种定义好的数据类型来读写全局数据空间。
1.4 ROS2的通讯模型
相信你一定还记得前边所学习的ROS1通讯模型,也就是Topic、Service等通讯机制。ROS2的通讯模型会稍显复杂,因为需要依赖于DDS,所以包含很多DDS的机制。基于DDS的ROS2通讯模型如下图所示:
ROS2的通讯模型有以下几个关键概念:
1. 参与者(Domain Participant)
一个参与者Participant就是一个容器,对应于一个使用DDS的用户,任何DDS的用户都必须通过Participant来访问全局数据空间。
2. 发布者(Publisher)
数据发布的执行者,支持多种数据类型的发布,可以与多个数据写入器(DataWriter)相联,发布一种或多种主题(Topic)的消息。
3. 订阅者(Subscriber)
数据订阅的执行者,支持多种数据类型的订阅,可以与多个数据读取器(DataReader)相联,订阅一种或多种主题(Topic)的消息。
4. 数据写入器(DataWriter)
应用向发布者更新数据的对象,每个数据写入器对应一个特定的Topic,类似于ROS1中的一个消息发布者。
5. 数据读取器(DataReader)
应用从订阅者读取数据的对象,每个数据读取器对应一个特定的Topic,类似于ROS1中的一个消息订阅者。
6. 主题(Topic)
这个和ROS1中的Topic概念一致,一个Topic包含一个名称和一种数据结构。
7. Quality of Service
质量服务原则,简称QoS Policy,这是ROS2中新增的、也是非常重要的一个原则,控制各方面与底层的通讯机制,主要从时间限制、可靠性、持续性、历史记录这几个方面,满足用户针对不同场景的数据需求。
二、安装ROS2
2017年12月8日,第一个ROS2的正式版Ardent Apalone发布,这里就以该版本的ROS2为例,介绍ROS2的安装与使用方法。
2.1 安装ROS2
ROS2支持Ubuntu16.04系统,安装方法和ROS1类似,可以按照以下步骤进行安装:
1. 添加软件源
$ sudo apt update && sudo apt install curl $ curl http://repo.ros2.org/repos.key | sudo apt-key add - $ sudo sh -c 'echo "deb [arch=amd64,arm64] http://repo.ros2.org/ubuntu/main xenial main" > /etc/apt/sources.list.d/ros2-latest.list'
2. 安装ROS2
$ sudo apt-get update $ sudo apt install `apt list ros-ardent-* 2> /dev/null | grep "/" | awk -F/ '{print $1}' | grep -v -e ros-ardent-ros1-bridge -e ros-ardent-turtlebot2- | tr "\n" " "`
以上安装命令排除了ros-ardent-ros1-bridge和ros-ardent-turtlebot2-*等功能包,这些功能包需要依赖ROS1,可以在后续单独安装。
3. 设置环境变量
$ source /opt/ros/ardent/setup.bash
如果安装了Python包——argcomplete,还需要设置以下环境变量:
$ source /opt/ros/ardent/share/ros2cli/environment/ros2-argcomplete.bash
4. 配置ROS Middleware(RMW)
DDS是ROS2中的重要部分,ROS2默认使用的RMW是FastRPTS,我们也可以通过以下环境变量将默认RMW修改为OpenSplice:
RMW_IMPLEMENTATION=rmw_opensplice_cpp
5. 安装依赖ROS1的功能包
ROS2在很长一段时间会和ROS1并存,所以目前很多ROS2中的功能包需要依赖ROS1中的功能包,ROS2也提供了与ROS1之间通信的桥梁——ros1_bridge。在安装这些与ROS1有依赖关系的功能包之前,需要系统已经成功安装有ROS1,然后才能通过以下命令安装ROS2的功能包:
$ sudo apt update $ sudo apt install ros-ardent-ros1-bridge ros-ardent-turtlebot2-*
按照以上方法安装完成后,就可以使用ROS2的命令了。ROS2的默认安装路径依然是在Ubuntu系统的/opt/ros路径下:
使用如下命令查看ROS2命令行工具相关的帮助信息:
$ ros2 --help
2.2 运行talker和listener
ROS2安装完成后,默认带有部分例程,为了验证ROS2是否安装成功,可以使用如下命令进行测试:
$ ros2 run demo_nodes_cpp talker
$ ros2 run demo_nodes_cpp listener
运行成功后,效果与我们在ROS1中实现的效果类似,如下图所示:
通过这个例程可以看到,在ROS2中运行节点时,并不需要启动ROS Master,两个节点之间建立的通讯连接完全依靠节点自身的“Discovery”机制。
三、在ROS2中实现talker与listener
接下来,我们尝试在ROS2中创建两个节点进行如上节的通讯。
3.1 创建工作目录和功能包
与ROS1相同,我们需要创建一个工作目录,作为代码管理的项目目录。ROS2中创建工作目录的方法与ROS1类似。
首先使用Linux命令创建一个工作目录文件夹:
$ mkdir -p ~/ros2_ws/src
然后在src文件夹下创建功能包文件:
$ cd ~/ros2_ws/src $ mkdir -p ros2_demo
ROS2的功能包结构与ROS1中的功能包相同,需要CMakeLists.txt和package.xml文件,这里我们可以手动创建这两个文件。package.xml文件的内容如下:
<?xml version="1.0"?> <package format="2"> <name>ros2_demo</name> <version>0.0.0</version> <description>Package containing examples of how to use the rcl API.</description> <maintainer email="huchunxu@hust.edu.cn">Hu Chunxu</maintainer> <license>Apache License 2.0</license> <buildtool_depend>ament_cmake</buildtool_depend> <buildtool_depend>rosidl_default_generators</buildtool_depend> <build_depend>example_interfaces</build_depend> <build_depend>rcl</build_depend> <build_depend>rmw_implementation</build_depend> <exec_depend>example_interfaces</exec_depend> <exec_depend>rcl</exec_depend> <exec_depend>rmw_implementation</exec_depend> <exec_depend>rosidl_default_runtime</exec_depend> <export> <build_type>ament_cmake</build_type> </export> </package>
CMakeLists.txt文件的内容如下:
cmake_minimum_required(VERSION 3.5)
project(ros2_demo)
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
ament_package()
然后就可以回到工作目录的根路径下,使用如下命令进行编译了:
$ ament build
编译完成后,在工作空间的根路径下会产生两个文件夹:build和install。
build文件夹放置了很多编译过程中的中间文件,而install文件夹里边是最终编译生成的可执行文件、库文件、环境变量等,和ROS1中的devel文件夹相对应,而devel文件夹在ROS2中已经不复存在了。
现在就可以在功能包中创建节点代码了,由于ROS2重新设计了API,我们以话题通讯的talker和listener为例,学习一下ROS2中基础API的使用方法。
3.2 创建talker
首先需要创建一个talker,发布指定的字符串消息,实现源码ros2_talker.cpp的详细内容如下:
#include <iostream> #include <memory> #include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" int main(int argc, char * argv[]) { //ros::init(argc, argv, "talker"); rclcpp::init(argc, argv); //ros::NodeHandle n; auto node = rclcpp::Node::make_shared("talker"); // Set the QoS. ROS 2 will provide QoS profiles based on the following use cases: // Default QoS settings for publishers and subscriptions (rmw_qos_profile_default). // Services (rmw_qos_profile_services_default). // Sensor data (rmw_qos_profile_sensor_data). rmw_qos_profile_t custom_qos_profile = rmw_qos_profile_default; // set the depth to the QoS profile custom_qos_profile.depth = 7; //ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); auto chatter_pub = node->create_publisher<std_msgs::msg::String>("chatter", custom_qos_profile); //ros::Rate loop_rate(10); rclcpp::WallRate loop_rate(2); auto msg = std::make_shared<std_msgs::msg::String>(); auto i = 1; //while (ros::ok()) while (rclcpp::ok()) { msg->data = "Hello World: " + std::to_string(i++); std::cout << "Publishing: '" << msg->data << "'" << std::endl; //chatter_pub.publish(msg); chatter_pub->publish(msg); //ros::spinOnce(); rclcpp::spin_some(node); //loop_rate.sleep(); loop_rate.sleep(); } return 0; }
在上边talker节点的实现里,注释中的内容对应于该语句在ROS1中的实现,从代码API的调用上可以看到,虽然API的名称变化较大,但是API接口参数的变化并不是很大。
需要注意的是,ROS2中加入了DDS的QoS机制,所以在代码中需要配置质量服务原则。ROS2所使用的DDS厂商也为我们提供了默认的配置选项,可以直接使用rmw_qos_profile_default。
3.3 创建listener
接下来创建listener节点,实现代码ros2_listerner.cpp的详细内容如下:
#include <iostream> #include <memory> #include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" //void chatterCallback(const std_msgs::String::ConstPtr& msg) void chatterCallback(const std_msgs::msg::String::SharedPtr msg) { std::cout << "I heard: [" << msg->data << "]" << std::endl; } int main(int argc, char * argv[]) { //ros::init(argc, argv, "listener"); rclcpp::init(argc, argv); //ros::NodeHandle n; auto node = rclcpp::Node::make_shared("listener"); //ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); auto sub = node->create_subscription<std_msgs::msg::String>( "chatter", chatterCallback, rmw_qos_profile_default); //ros::spin(); rclcpp::spin(node); return 0; }
listener节点的代码实现变化不大,可以参考注释对应于ROS1中的语句,需要注意的仍然是QoS的配置。
3.4 修改CMakeLists.txt
编译之前,还需要修改CMakeLists.txt,加入对应源码文件的编译规则:
add_executable(ros2_talker src/ros2_talker.cpp)
ament_target_dependencies(ros2_talker rclcpp std_msgs)
add_executable(ros2_listerner src/ros2_listerner.cpp)
ament_target_dependencies(ros2_listerner rclcpp std_msgs)
install(TARGETS
ros2_talker
ros2_listerner
DESTINATION lib/${PROJECT_NAME}
)
ROS2仍然使用CMakeLists.txt管理编译规则,内容上与ROS1相比没有太大变化。
3.5 编译并运行节点
接下来就可以回到工作空间的根目录下,使用“ament
build”命令进行编译了。编译完成后,可以在install文件夹下找到生成的可执行文件:
运行后的效果和之前的例程类似,如下图所示:
从以上ROS2中talker和listerner节点代码的实现上,我们可以看到:
1.ROS2中的API相比ROS1中发生了较大的变化。ROS2并不是在ROS1的基础上查漏补缺,而是完全从新设计。关于ROS2的API说明,可以参考API文档:http://docs.ros2.org/ardent/api/rclcpp/index.html;
2. 使用了更多C++的特性,比如auto、make_shared等;
3. 加入了QoS配置。从上边的代码中,可以看到QoS有默认的配置rmw_qos_profile_default,而且talker将QoS的depth设置为“7”;
4. 代码的总体架构还是与ROS1极为相似的。
关于ROS2的相关内容,也可以参考博主之前在ROS暑期学校中的相关介绍:http://mp.weixin.qq.com/s/aJdTkaOv01EN95tZtMiL_w