ROS2介绍与安装

ROS2介绍安装

什么是ROS

随着智能制造2025的到来,国内机器人行业也随之兴起,越来越多的小伙伴接触并学习了ROS,国内关于ROS的教程也越来越多。

ROS2作为第二代机器人操作系统,比ROS更加的强大,有一些在ROS中不好实现或者无法实现的功能,在ROS2中就可以找到方法。

虽然ROS2很强大,但国内相关教程较少,影响大家对ROS2的了解和学习。所以本课程从基础开始讲起,带大家一起动手学ROS2

那什么是ROS呢?

我们可以简单的认为ROS就是机器人界的Android

ROS本质上其实就是用于快速搭建机器人的软件库(核心是通讯)和工具集。追求稳定、安全和实时的通讯能力。

ROS2的前世今生

要说ROS2,那就不得不提起ROS,ROS就是传说中的机器人操作系统,英文全称(Robot Operating System),但ROS本身并不是一个操作系统,而是可以安装在现在已有的操作系统上(Linux、Windows、Mac)上的软件库和工具集

ROS出生于2007年,ROS的出现解决了机器人各个组件之间的通信问题,同时基于ROS的完善的通信机制,越来越多的优秀的机器人算法集成到了ROS中来。

现在的ROS功能已经变得非常的丰富和强大。但随着对ROS功能上要求越来越多,一些原始的架构和设计不能够满足目前的使用需求,这也是ROS2出现的原因。

ROS2继承了ROS原有的优秀之处,同时又带来了很多新的功能,ROS2相对于ROS更加的强大。

ROS为什么会出现?

ROS的设计目的是:简化在各种机器人平台上创建复杂而强大的机器人行为的任务即不重复造轮子

在ROS没有出现之前,做一个机器人是非常复杂的一件事情,因为一个机器人需要涉及到多个部分,而且这些部分之间还要进行通信。

例如设计一个像下图一样的轮式移动机器人,我们对其进行拆解。可以分为感知、决策、控制三个部分。

image-20240808095437891

  • 感知部分有:激光雷达、深度相机、IMU、里程计、碰撞感知、建图
  • 决策部分有:路径规划(navigation)算法、定位算法
  • 控制部分有:轮子驱动

机器人复杂之处就在于此,如果想要整个机器人可以跑起来,那么必须要有一个东西将上面的几个部分合理的连接到一起,这个东西就是ROS。

ROS的作用就像我们的身体的神经系统一样,通过神经系统将我们身体的各个部分接入大脑。

有了ROS1为什么还要ROS2?

2007年ROS开发人员设计和制作ROS时,当时只想着简化机器人的开发,并没有想到过今天那么多的功能需求,比如商业化要求的稳定性、生命周期管理、多机协同、数据加密等。ROS发展的后面的几年里,机器人对ROS的功能要求越来越多,ROS开发人员只能在原有的ROS上修修补补。

随着ROS不断的添加新功能,ROS变得越来越臃肿,祖传代码也越来越多。ROS开发人员发现在原有的ROS架构上修修补补十分消耗头发,于是ROS官方也重新设计制作了ROS2。

ROS2介绍

ROS2是在ROS的基础上设计开发的第二代机器人操作系统,可以帮助我们简化机器人开发任务,加速机器人落地的软件库和工具集

ROS2发展历程

image-20240908154132617

image-20240908154341637

ROS与ROS2对比

ROS问题举例

ROS的设计目标是简化机器人的开发,如何简化呢?ROS为此设计了一整套通信机制(话题、服务、参数、动作)。

通过这些通信机制,ROS实现了将机器人的各个组件给的连接起来,在设计这套通信机制的时候就设计了一个叫做Ros Master的东西,所有节点(可以理解为某一个组件,比如:激光雷达)的通信建立必须经过这个主节点。

这种组合结构图如下:

image-20240808095651535

一旦Ros Master主节点挂掉后,就会造成整个系统通信的异常,此时避障策略将会失效,如果机器人正在运行,碰到障碍物会径直装上去,机毁人亡!

ROS的不稳定这个问题在虽然对大家做机器人研究问题不大,但如果是想基于ROS做商业化机器人(比如无人驾驶汽车),就会造成非常严重的后果除了不稳定这个问题,ROS还有很多其他地方存在着问题:

  • 通信基于TCP实现,实时性差、系统开销大
  • 对Python3支持不友好,需要重新编译
  • 消息机制不兼容
  • 没有加密机制、安全性不高

ROS与ROS2架构对比?

所以在ROS2中,首当其冲的将ROS的主节点干掉了,这里放一张网上流传最广的ROS/ROS2架构图,接下来就会按照这篇架构图给大家讲解。

该图出自论文:Exploring the Performance of ROS2,论文在线阅读地址:https://www.researchgate.net/profile/Takuya-Azumi/publication/309128426_Exploring_the_performance_of_ROS2/links/5c908801299bf14e7e84ce61/Exploring-the-performance-of-ROS2.pdf

image-20240808095812416

OS层

从原来的只支持linux平台变成了支持windows、mac甚至是嵌入式RTOS平台,这一点要点个赞。

MiddleWare中间件层

那么中间层ROS2到底相对于ROS做了哪些优化呢?

  1. 去中心化master,ROS和ROS2中间件不同之处在于,ROS2取消了master节点。

    去中心化后,各个节点之间可以通过DDS的节点相互发现,各个节点都是平等的,且可以1对1、1对n、n对n进行互相通信。

  2. 不造通信的轮子,通信直接更换为DDS进行实现

    采用DDS通信,使得ROS2的实时性、可靠性和连续性上都有了增强。

应用层

对于应用层来说ROS2也做了很大的改进,上面那张图没有体现出来。

ROS2进行改进有:

  1. Python2到Python3的支持
  2. 编译系统的改进(catkin到ament)
  3. C标准更新到c11
  4. 可以使用相同 API 的进程间和进程内通信

ROS2新概念例举

  • 可用Python编写的Launch文件
  • 多机器人协同通信支持
  • 支持安全加密通信
  • 同一个进程支持多个节点、
  • 支持Qos服务质量
  • 支持节点生命周期管理
  • 高效的进程间通信

ROS2 机器人开发特色

  1. 四大核心通讯机制

image-20240908155434980

  1. 各种可视化以及调试工具
  2. 丰富的建模和运动学工具
  3. 强大的开源社区及应用框架

安装ROS2

一键安装

这里推荐使用鱼香ROS博主的一键安装命令:

1
wget http://fishros.com/install -O fishros && . fishros

输入密码,在选项界面选择1-一键安装ROS,接着根据你的情况选择是否更换系统源(基础篇更换了就不用了),接着等待一会就会让你选择要安装的ROS2版本了。这里选择humble版本的ROS2即可。

接着会问你安装桌面版还是基础版,我们选择桌面版,包含可视化工具,如果是在树莓派上装可以使用基础版。

安装完成后输入ros2如果看到下面的界面则安装成功

image-20240808101253510

博主博客为:

官方应用商店

寻找ROS和ROS2资源的入口,包括软件包、系统依赖项和相关文档

https://index.ros.org

image-20240806091226136

image-20240806091539030

ROS2初体验

游戏1:你说我听

游戏内容:很简单,我们启动两个节点,一个节点负责发消息(说),一个节点负责收消息(听)。

  1. 启动一个终端Ctrl+Alt+T

  2. 启动倾听者

    1
    ros2 run demo_nodes_py listener

启动一个新终端Ctrl+Alt+T

启动说话者

1
ros2 run demo_nodes_cpp talker

观察一下现象,talker节点每数一个输,倾听节点每一次都能听到一个,是不是很神奇。

image-20240808101928370

游戏2:涂鸦乌龟

游戏内容:启动海龟模拟器,启动海龟遥控器,控制海龟在地图上画出任意轨迹即可。

启动海龟模拟器

打开终端Ctrl+Alt+T,输入下面的指令

1
ros2 run turtlesim turtlesim_node

就可以看到这样的界面

image-20240808102154374

启动海龟遥控器

点一下原来的终端输入Ctrl+Shift+T 打开一个新的标签页输入

1
ros2 run turtlesim turtle_teleop_key

你可以看到这样子的界面

image-20240808102323280

这个时候你就可以使用上下左右去遥控海龟了,快试一试吧。

image-20240808102448670

RQT可视化

保持前面两个游戏在运行状态,打开终端,输入rqt。

1
rqt

打开之后的窗口如下图,空空如也,不要担心,因为我们没有选插件的原因。

image-20240808102611740

选择插件

这里我们可以选择现有的几个RQT插件来试一试,可以看到和话题、参数、服务、动作四大通信组件相关的工具都有,还有一些可视化、日志和系统计算图等相关的。

image-20240808102819884

Introspection / Node Graph

打开后就可以看到上面几个节点之间的数据关系了,是不是很方便的工具。

image-20240808103102468

架构与中间件

ROS2系统架构

架构图

image-20240808103410678

操作系统层

操作系统层比较好理解,ROS2本身就是基于Linux、Windows或者macOS系统建立的,驱动计算机硬件、底层网络通信等实现都是交由操作系统来实现的。

DDS实现层

要想理解这一层就需要你了解DDS是什么? 以及为什么ROS2框架中会有多个DDS的实现。

  1. DDS是什么?

DDS,全称 Data Distribution Service (数据分发服务)。是由对象管理组 (OMG) 于 2003 年发布并于 2007 年修订的开分布式系统标准。

通过类似于ROS中的话题发布和订阅形式来进行通信,同时提供了丰富的服务质量管理来保证可靠性、持久性、传输设置等。

  1. DDS实现层用来做什么

DDS实现层其实就是对不同常见的DDS接口进行再次的封装,让其保持统一性,为DDS抽象层提供统一的API。

抽象DDS层-RMW

这一层将DDS实现层进一步的封装,使得DDS更容易使用。原因在于DDS需要大量的设置和配置(分区,主题名称,发现模式,消息创建,…),这些设置都是在ROS2的抽象层中完成的。

ROS2客户端库 RCL

RCL(ROS Client Library)ROS客户端库,其实就是ROS的一种API,提供了对ROS话题、服务、参数、Action等接口。

GUI和CLI

  • GUI(Graphical User Interface)就是平常我们说的图形用户界面,大家用的Windows是就是可视化的,我们可以通过鼠标点击按钮等图形化交互完成任务。
  • CLI(Command-Line Interface)就是命令行界面了,我们所用的终端,黑框框就是命令行界面,没有图形化。

很久之前电脑还是没有图形化界面的,所有的交互都是通过命令行实现,就学习机器人而言,命令行操作相对于图形化优势更加明显。

API是什么

知道了CUI和CLI是根据是否有图形界面划分的,那API又是什么?

API( Application Programming Interface)应用程序编程接口。比如你写了一个库,里面有很多函数,如果别人要使用你这个库,但是并不知道每个函数内部是怎么实现的。使用的人需要看你的文档或者注释才知道这个函数的入口参数和返回值或者这个函数是用来做什么的。对于使用者来说来说 ,你的这些函数就是API。

API在不同语言中的表现形式不同,在C和C++表现为头文件,在Python中表现为Python文件。

  1. ROS2客户端库

ROS的客户端库就是上面所说的RCL,不同的语言对应着不同的rcl,但基本功能都是相同的。

比如Python语言提供了rclpy来操作ROS2的节点话题服务等,而C++则使用rclcpp提供API操作ROS2的节点话题和服务等。

所以后面我们使用Python和C++来编写ROS2节点实现通讯等功能时,我们就会引入rclpy和rclcpp的库。

image-20240808104129374

上面这张图时ROS2,API的实现层级,最新下面的是第三方的DDS,rmw(中间件接口)层是对各家DDS的抽象层,基于rmw实现了rclc,有了rclc,我们就可以实现各个语言的库,大家都知道C语言是各个语言的鼻祖(汇编除外)所以基于rclc,ROS2官方实现了rclpy和rclcpp。基于rclpy和rclcpp我们就可以实现上层的应用了。

有的同学可能还想着既然基于rclc可以实现多个语言的ROS2的库,那rcljava有没有?

确实有,而且还有很多,比如在AndroidAPP上或者在Web上开发可以使用rcljava或rclnodejs。

语言地址
python-rclpythonhttps://github.com/ros2/rclpy
c++ - rclcpphttps://github.com/ros2/rclcpp
java-rcljavahttps://github.com/esteve/ros2_java
rust-rclrusthttps://github.com/ros2-rust/ros2_rust
node.js-rclnodejshttps://github.com/RobotWebTools/rclnodejs
go-rclgohttps://github.com/juaruipav/rclgo
lua-rclluahttps://github.com/jbbjarnason/rcllua
kotlin-rclkinhttps://github.com/ros2java-alfred/ros2_kotlin
swift-rclswifthttps://github.com/atyshka/ros2_swift
c#-rclcshttps://github.com/RobotecAI/ros2cs

应用层

应用层就是我们写代码以及ROS2开发的各种常用的机器人相关开发工具所在的层了。后面我们写的所有代码其实都是属于这一层的。

ROS2中间件DDS架构

中间件

  1. 中间件是什么

顾名思义

中间件就是介于某两个或者多个节点中间的组件。干嘛用的呢?

就是提供多个节点中间通信用的。

官方解释就比较玄乎了:

中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源。中间件位于客户机/ 服务器的操作系统之上,管理计算机资源和网络通讯。是连接两个独立应用程序或独立系统的软件。相连接的系统,即使它们具有不同的接口,但通过中间件相互之间仍能交换信息。执行中间件的一个关键途径是信息传递。通过中间件,应用程序可以工作于多平台或OS环境。

  1. ROS中间件VS ROS2中间件

ROS/ROS2中间件对比

ROS1的中间件是ROS组织自己基于TCP/UDP机制建立的,为了维护该部分ROS1组织花费了大量的精力,但是依然存在很多问题。

ROS2采用了第三方的DDS作为中间件,将DDS服务接口进行了一层抽象,保证了上层应用层调用接口的统一性。

基于DDS的互相发现协议,ROS2终于干掉了ROS1中的Master节点。

DDS和ROS2架构

ROS2为每家DDS供应商都开发了对应的DDS_Interface即DDS接口层,然后通过DDS Abstract抽象层来统一DDS的API。

image-20240808104937908

ROS2架构中的DDS部分

image-20240808105043164

DDS 通信模型

DDS的模型是非常容易理解,我们可以定义话题的数据结构(类似于ROS2中的接口类型)。

  • Pos:一个编号id的车子的位置x,y

DDS的参与者(Participant)通过发布和订阅主题数据进行通信。

DDS的应用层通过DDS进行数据订阅发布,DDS通过传输层进行数据的收发。

DDS的优势与劣势

  1. 优势
  • 发布/订阅模型:简单解耦,可以轻松实现系统解耦
  • 性能:在发布/订阅模式中,与请求/回复模式相比,延迟更低,吞吐量更高。
  • 远程参与者的自动发现:此机制是 DDS 的主要功能之一。通信是匿名的、解耦的,开发者不必担心远程参与者的本地化。
  • 丰富的 Qos 参数集,允许调整通信的各个方面:可靠性、持久性、冗余、寿命、传输设置、资源…
  • 实时发布订阅协议 ( RTPS ):该协议几乎可以通过任何传输实现,允许在 UDP、TCP、共享内存和用户传输中使用 DDS,并实现不同 DDS 实现之间的真正互操作性。
  1. 劣势
  • API复杂,DDS 的灵活性是以复杂性为代价的。
  • 系统开销相对较大。
  • 社区支持问题,但ROS2近两年来使用DDS后社区表现还是不错的。

ROS2使用DDS的几个理由

  1. DDS已经应用在军事、潜艇各个领域,稳定性实时性经过实际检验。
  2. 使用DDS需要维护的代码要少得多,可以让ROS2开发人员腾出手专注机器人开发。
  3. DDS有定义好的行为和规范并且有完善的文档。
  4. DDS提供了推荐的用例和软件API,有较好的语言支持。

ROS2第一个节点

编程基础

使用g++编译ROS2节点

动态链接库

程序编译一般需要经预处理、编译、汇编和链接几个步骤。在实际应用中,有些公共代码需要反复使用,就把这些代码编译成为“库”文件。在链接步骤中,链接器将从库文件取得所需的代码,复制到生成的可执行文件中,这种库称为静态(链接)库,其特点是可执行文件中包含了库代码的一份完整拷贝,缺点是被多次使用就会多份冗余拷贝。还有一种库,就是程序在开始运行后调用库函数时才被载入,这种库独立于现有的程序,其本身不可执行,但包含着程序需要调用的一些函数,这种库称为动态(链接)库(Dynamic Link Library)。

image-20240808111738274

在widows平台下,静态链接库是.lib文件,动态库文件是.dll文件。在linux平台下,静态链接库是.a文件,动态链接库是.so文件。

用g编译ROS2的C节点

编写节点

编写一个ROS2的C++节点非常简单,只需三行代码即可完成。

新建first_ros2_node.cpp,然后在first_ros2_node.cpp中输入下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
// 包含rclcpp头文件,如果Vscode显示红色的波浪线也没关系
// 我们只是把VsCode当记事本而已,谁会在意记事本对代码的看法呢,不是吗?
#include "rclcpp/rclcpp.hpp"

int main(int argc, char **argv)
{
// 调用rclcpp的初始化函数
rclcpp::init(argc, argv);
// 调用rclcpp的循环运行我们创建的first_node节点
rclcpp::spin(std::make_shared<rclcpp::Node>("first_node"));
return 0;
}

编译

接着我们使用g++来编译first_ros2_node节点。正常的话一定会报错。

1
g++ first_ros2_node.cpp 

报错内容如下:

image-20240808112849040

一定要记住这个错误 No such file or directory,这将是你接下来机器人学习工作生涯中最常见的错误之一。

原因我们在代码里包含了"rclcpp/rclcpp.hpp"头文件,但是g++找不到这个头文件,解决方法就是告诉g++这个头文件的目录。

首先我们要找到这个头文件在哪里,这个头文件是ROS2的客户端库,其地址肯定在ROS2的安装目录下,即/opt/ros/humble/include/rclcpp

1
2
cd /opt/ros/humble/include/rclcpp
ls rclcpp/* | grep rclcpp.h

ls指令列出命令 | grep rclcpp.h 是对列出的结果进行过滤,只显示包含rclcpp.h的行。

使用上面的指令,可以看到这个文件确实在这里。

image-20240808113023376

接着我们可以用-I(大写i)来为g++指定这个目录,然后再次运行,你会发现依然报错

1
g++ first_ros2_node.cpp -I /opt/ros/humble/include/rclcpp/ 

image-20240808113113589

虽然错误有些不一样,但是核心的文件都是一样的,你应该都看到了No such file or directory这个问题,并且错误信息还提示你了,在/opt/ros/humble/include/rclcpp/rclcpp/executors/multi_threaded_executor.hpp:25这个位置,包含了rcl/guard_condition.h发现找不到这个头文件。

既然错误一样,那么解决方案也是相同的,rcl/guard_condition.h所在的路径是/opt/ros/humble/include/rcl/我们再次指定后运行。

1
g++ first_ros2_node.cpp -I /opt/ros/humble/include/rclcpp/ -I /opt/ros/humble/include/rcl/

你会发现还是相同错误,因为头文件的包含是类似于套娃形式的,一层层加下去,总有终点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs

运行完上面这段代码,你会发现报的错误变了。

image-20240808113322886

请记住上面错误中的undefined reference to xxxxx,这将是你接下来机器人学习工作生涯中另一个最常见的错误。

原因在于g++找不到库文件,解决方法就是我们帮助它定位到库文件的位置,并通过-L参数指定库目录,-l(小写L)指定库的名字。

ROS2相关的库的地址都在/opt/ros/humble/lib下,你可以使用下面的指定看到rclcpp的动态链接库。

1
ls /opt/ros/humble/lib | grep rclcpp
image-20240808113440639

指定库目录和使用的库后的终极命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs \
-L /opt/ros/humble/lib/ \
-lrclcpp -lrcutils

运行后,你会发现没有任何报错了,但是在当前目录下多出了一个a.out,这个就是我们将上面的代码编译和链接完库之后得出的可执行文件。

如果你觉得a.out不好听,可以在g++指定后添加 -o 名字 ,比如 -o first_node

运行节点

执行代码

1
./a.out

打开新的终端,使用ros2 node list查看正在运行的节点,是否有first_node

image-20240808114236856

使用make编译ROS2节点

有没有觉得用g编译节点无比的麻烦,的确是这样子,为此先行者们发明了一个叫做make的批处理工具,我们可以将g的指令写成脚本,就可以通过make自动的调用脚本完成操作。

安装make

1
sudo apt install make

编写Makefile

新建Makefile文件,然后将上面的g++编译指令用下面的形式写到Makefile里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
build:
g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs \
-L /opt/ros/humble/lib/ \
-lrclcpp -lrcutils \
-o first_node

# clean指令,用来删掉first_node
clean:
rm first_node

Makefile中使用tab而不是使用空格作为分隔符,否则出现缺失分隔符错误

编译

在Makefile同级目录输入

1
make build

运行测试

image-20240808115044077

使用CMakeLists.txt编译ROS2节点

虽然通过make调用Makefile编译代码非常的方便,但是还是需要我们手写gcc指令来编译,那有没有什么办法可以自动生成Makefile呢?

答案是有的,那就是cmake工具。

cmake通过调用CMakeLists.txt直接生成Makefile。

安装cmake

1
sudo apt install cmake

新建CMakeLists.txt

新建CMakeLists.txt,输入下面内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cmake_minimum_required(VERSION 3.22)

project(first_node)

#include_directories 添加特定的头文件搜索路径 ,相当于指定g++编译器的-I参数
include_directories(/opt/ros/humble/include/rclcpp/)
include_directories(/opt/ros/humble/include/rcl/)
include_directories(/opt/ros/humble/include/rcutils/)
include_directories(/opt/ros/humble/include/rcl_yaml_param_parser/)
include_directories(/opt/ros/humble/include/rosidl_runtime_c/)
include_directories(/opt/ros/humble/include/rosidl_typesupport_interface/)
include_directories(/opt/ros/humble/include/rcpputils/)
include_directories(/opt/ros/humble/include/builtin_interfaces/)
include_directories(/opt/ros/humble/include/rmw/)
include_directories(/opt/ros/humble/include/rosidl_runtime_cpp/)
include_directories(/opt/ros/humble/include/tracetools/)
include_directories(/opt/ros/humble/include/rcl_interfaces/)
include_directories(/opt/ros/humble/include/libstatistics_collector/)
include_directories(/opt/ros/humble/include/statistics_msgs/)

# link_directories - 向工程添加多个特定的库文件搜索路径,相当于指定g++编译器的-L参数
link_directories(/opt/ros/humble/lib/)

# add_executable - 生成first_node可执行文件
add_executable(first_node first_ros2_node.cpp)

# target_link_libraries - 为first_node(目标) 添加需要动态链接库,相同于指定g++编译器-l参数
# 下面的语句代替 -lrclcpp -lrcutils
target_link_libraries(first_node rclcpp rcutils)

编译代码

我们一般会创建一个新的目录,运行cmake并进行编译,这样的好处是不会显得那么乱。

1
2
mkdir build
cd build

创建好文件夹,接着运行cmake指令,..代表到上级目录找CMakeLists.txt

1
cmake ..
image-20240809155236600

运行完cmake你应该可以在build目录下看到cmake自动生成的Makefile了,接着就可以运行make指令进行编译

1
make
image-20240809155305990

运行完上面的指令,就可以在build目录下发现first_node节点了。

如果你安装了anaconda,请在 cmake … 之前关闭虚拟环境 conda deactivate ,否则可能报错

CMake依赖查找流程

上面我们用g++、make、cmake三种方式来编译ros2的C++节点。用cmake虽然成功了,但是CMakeLists.txt的内容依然非常的臃肿,我们需要将其进一步的简化。

优化CMakeList.txt

将上面的CmakLists.txt改成下面的样子

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.22)
project(first_node)

find_package(rclcpp REQUIRED)
add_executable(first_node first_ros2_node.cpp)
target_link_libraries(first_node rclcpp::rclcpp)

接着继续生成和编译

1
2
3
conda deactivate # 当你安装了anaconda,关闭虚拟环境
cmake ..
make

image-20240809160022360

find_package查找路径

find_package查找路径对应的环境变量如下。

1
2
3
4
5
<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
PATH

打开终端,输入指令:

1
echo $PATH

结果

1
PATH=/opt/ros/humble/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

观察PATH变量,你会发现/opt/ros/humble/bin赫然在其中,PATH中的路径如果以binsbin结尾,则自动回退到上一级目录,接着检查这些目录下的

1
2
3
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/          (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)

cmake找到这些目录后,会开始依次找<package>Config.cmakeFind<package>.cmake文件。找到后即可执行该文件并生成相关链接信息。

打开/opt/ros/humble/share/rclcpp/cmake你会发现rclcppConfig.cmake就在其中。

Python依赖查找流程

编写ROS2的Python节点

新建second_ros2_node.py,输入下面的内容

1
2
3
4
5
6
import rclpy
from rclpy.node import Node
# 调用rclcpp的初始化函数
rclpy.init()
# 调用rclcpp的循环运行我们创建的second_node节点
rclpy.spin(Node("second_node"))

运行Python节点

打开终端,输入指令

1
python3 second_ros2_node.py

打开新的终端,输入:

1
ros2 node list

image-20240809164728457

那么问题来了,我们import rclpy,rclpy到底在哪里?python是如何找到的?

若出现 ImportError: libstdc++.so.6: version GLIBCXX_3.4.30’ not found (required by /opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/_rclpy_pybind11.cpython-310-x86_64-linux-gnu.so) 错误,表明在你的系统中,libstdc++.so.6 库版本过旧,无法满足 ROS 2 Humble 中的 Python 扩展模块 _rclpy_pybind11 的要求。具体地,缺少 GLIBCXX_3.4.30 版本的符号。

解决方法:

1
2
3
cd /home/liaojie1314/anaconda3/lib # anaconda3的lib目录
mv libstdc++.so.6 libstdc++.so.6.bak
ln -s /usr/lib/x86_64-linux-gnu/libstdc++.so.6 libstdc++.so.6 # 链接系统的libstdc++.so.6

Python包查找流程

Python3运行import rclpy时候如何找到它的呢?答案是通过环境变量PYTHONPATH

1
echo $PYTHONPATH

结果

1
/opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages

你会发现里面有关于humble的python路径,在上面两个目录下找一下rclpy,看看能不能找到rclpy

查找第一个路径

1
ls -l /opt/ros/humble/lib/python3.10/site-packages | grep rclpy
image-20240809165441298

这些只是example。

没找到,第二个

1
ls -l /opt/ros/humble/local/lib/python3.10/dist-packages/ | grep rclpy
image-20240809165522982

Python打包工具之Setup

为什么需要对项目分发打包

平常我们习惯了使用 pip 来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 打包

打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。

不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI 的项目,你都要学会如何打包你的项目。

Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢?

你可能听过 distutilsdistutils2setuptools等等,好像很熟悉,却又很陌生,他们都是什么关系呢?

包分发的始祖:distutils

distutils 是 Python 的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 Python 官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。

distutils 的精髓在于编写 setup.py,它是模块分发与安装的指导文件。

那么如何编写 setup.py 呢?我会在后面进行详细的解析。

你有可能没写过 setup.py ,但你绝对使用过 setup.py 来做一些事情,比如下面这条命令,我们经常用它来进行模块的安装。

1
python setup.py install

这样的安装方法是通过源码安装,与之对应的是通过二进制软件包的安装。

分发工具升级:setuptools

setuptools 是 distutils 增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。

distribute,或许你在其他地方也见过它,这里也提一下。

distribute 是 setuptools 有一个分支版本,分支的原因可能是有一部分开发者认为 setuptools 开发太慢了。但现在,distribute 又合并回了 setuptools 中。因此,我们可以认为它们是同一个东西。

还有一个大包分发工具是 distutils2,其试图尝试充分利用distutils,detuptools 和 distribute 并成为 Python 标准库中的标准工具。但该计划并没有达到预期的目的,且已经是一个废弃的项目。

因此,setuptools 是一个优秀的,可靠的 Python 包安装与分发工具。

超详细讲解 setup.py 的编写

打包分发最关键的一步是编写 setup.py 文件。

以下是一个 setup.py 简单的使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from setuptools import setup, find_packages

setup(
# 指定项目名称,我们在后期打包时,这就是打包的包名称,当然打包时的名称可能还会包含下面的版本号哟~
name="mytest",
# 指定版本号
version="1.0",
author="flp",
author_email="flepeng@163.com",
# 这是对当前项目的一个描述
description="这只是一次测试",

# 项目主页
url="http://iswbm.com/",

# 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包
packages=find_packages()

# 指定包名,即你需要打包的包名称,要实际在你本地存在哟,它会将指定包名下的所有"*.py"文件进行打包哟,但不会递归去拷贝所有的子包内容。
# 综上所述,我们如果想要把一个包的所有"*.py"文件进行打包,应该在packages列表写下所有包的层级关系哟~这样就开源将指定包路径的所有".py"文件进行打包!
packages=['devops', "devops.dev", "devops.ops"],
)

setup 函数常用的参数如下:

参数说明
name包名称
version包版本
author程序的作者
author_email程序的作者的邮箱地址
maintainer维护者
maintainer_email维护者的邮箱地址
url程序的官网地址
license程序的授权信息
description程序的简单描述
long_description程序的详细描述
platforms程序适用的软件平台列表
classifiers程序的所属分类列表
keywords程序的关键字列表
packages需要处理的包目录(通常为包含 init.py 的文件夹)
py_modules需要打包的 Python 单文件列表
download_url程序的下载地址
cmdclass添加自定义命令
package_data指定包内需要包含的数据文件
include_package_data自动包含包内所有受版本控制(cvs/svn/git)的数据文件
exclude_package_data当 include_package_data 为 True 时该选项用于排除部分文件
data_files打包时需要打包的数据文件,如图片,配置文件等
ext_modules指定扩展模块
scripts指定可执行脚本,安装时脚本会被安装到系统 PATH 路径下
package_dir指定哪些目录下的文件被映射到哪个源码包
entry_points动态发现服务和插件,下面详细讲
python_requires指定运行时需要的Python版本
requires指定依赖的其他包
provides指定可以为哪些模块提供依赖
install_requires应用于指定项目正确运行所需的最低要求
extras_require当前包的高级/额外特性需要依赖的分发包
tests_require在测试时需要使用的依赖包
setup_requires指定运行 setup.py 文件本身所依赖的包
dependency_links指定依赖包的下载地址
zip_safe不压缩包,而是以目录的形式安装

动手使用ROS2

ROS2节点介绍

ROS2节点是什么

ROS2中每一个节点也是只负责一个单独的模块化的功能(比如一个节点负责控制车轮转动,一个节点负责从激光雷达获取数据、一个节点负责处理激光雷达的数据、一个节点负责定位等等)

image-20240809170008073

节点之间如何交互?

上面举了一个激光雷达的例子,一个节点负责获取激光雷达的扫描数据,一个节点负责处理激光雷达数据,比如去除噪点。

那节点与节点之间就必须要通信了,那他们之间该如何通信呢?ROS2早已为你准备好了一共四种通信方式:

  • 话题-topics
  • 服务-services
  • 动作-Action
  • 参数-parameters

这四种种通信方式的用途和使用方法,第四和第五章再来介绍,到时候同时会带大家手撸代码。

Nodes-TopicandService-16542449255392

如何启动一个节点?

知道了节点的概念之后,我们该如何启动一个节点呢?

因为工作空间和包的概念下一讲才讲,这里大家先尝试运行一个节点,感受一下。

使用指令:

1
ros2 run <package_name> <executable_name>

指令意义:启动 包下的 中的节点。

使用样例:

1
ros2 run turtlesim turtlesim_node

大家可以尝试一下上面的指令,就是我们在第一章中启动小乌龟模拟器的那条指令。

运行之后可以看到一只小乌龟,接下来就可以试试下一节中提到的几个指令来查看节点信息和列表。

通过命令行界面查看节点信息

  1. ROS2命令行

ROS2的CLI,就是和ROS2相关的命令行操作。什么是命令行界面呢?

CLI(Command-Line Interface)和GUI(Graphical User Interface)

  • GUI(Graphical User Interface)就是平常我们说的图形用户界面,大家用的Windows是就是可视化的,我们可以通过鼠标点击按钮等图形化交互完成任务。
  • CLI(Command-Line Interface)就是命令行界面了,我们所用的终端,黑框框就是命令行界面,没有图形化。

很久之前电脑还是没有图形化界面的,所有的交互都是通过命令行实现,就学习机器人而言,命令行操作相对于图形化优势更加明显。

ROS2为我们提供了一系列指令,通过这些指令,可以实现对ROS2相关模块信息的获取设置等操作。

  1. 节点相关的CLI

运行节点(常用)

1
ros2 run <package_name> <executable_name>

查看节点列表(常用):

1
ros2 node list

查看节点信息(常用):

1
ros2 node info <node_name>

重映射节点名称

1
ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle

运行节点时设置参数

1
ros2 run example_parameters_rclcpp parameters_basic --ros-args -p rcl_log_level:=10

ROS2命令行工具源码:

ros2/ros2cli: ROS 2 command line interface tools (github.com)