ROS 2机器人系统开发实践(视频教学版) 梁伟、李园奇、李海慧 清华大学出版社【行情 报价 价格 评测】-京东
节点是ROS 2分布式架构中最基础的“功能执行单元”,即最小功能单元,也是机器人系统模块化开发中的核心载体。无论是从简单的传感器数据采集还是复杂的路径规划,ROS 2中的几乎所有功能都是通过节点来实现的。这些节点类似于“积木”,我们可以通过标准化的通信机制对这些“积木”进行组合,从而实现机器人系统的各种功能。
本节主要介绍ROS 2中的节点定义、节点功能的C++和Python实现、功能包及工作空间操作、构建工具colcon的使用以及ROS 2编程特性。
2.1.1 ROS 2节点介绍
在ROS 2中,“节点”作为机器人系统中“最小的功能单元”,承载着系统基本功能的实现。我们将这些节点组合起来,就可以实现复杂的功能。因此,本小节将从节点的定义、类比示例、特性、节点操作等方面进行详细介绍,帮助我们充分理解ROS 2的节点概念,为后续的开发学习打好基础。
1. ROS 2节点到底是什么
在官方定义中,ROS 2节点是“执行特定任务的独立进程”。这里的“进程”可以理解为“系统程序中能够独立运行的单个功能程序”。例如,用户计算机中同时运行的三个应用程序—“浏览器”“微信”“音乐播放器”,即三个独立的进程。这三个应用程序可以分别看作ROS 2中的三个独立节点。
ROS 2引入节点的概念,主要是为了将复杂的机器人系统“拆小、拆细”。对于一个自主导航的机器人系统,我们无须开发一个“大而全”的程序,而是可以将其拆分为视觉处理、路径计算、动作控制等多个独立进程;每个进程的功能又可以由一个或多个节点共同完成。这种拆分方式的优点在于:如果系统中的某个节点出现问题,只需重启或修正该节点即可让其重新加入机器人系统并继续执行功能,而不会导致整个机器人系统停止运行。
备注:本小节将不断使用实际生活中的示例来介绍ROS 2节点,帮助读者充分理解其定义与作用,为后续开发打下坚实的基础。
2. 用“智能家居系统”来理解节点
为了帮读者进一步深入理解节点的概念,假设当前存在一套能够自动调节环境的家居智能系统。该系统功能可以拆分为多个功能模块,其与ROS 2节点的对应关系如表2.1所示。
表2.1 智能家居功能模块与ROS 2节点类比
| 智能家居中的 功能模块 | ROS 2中的 节点(节点命名示例) | 核心功能 | 对应关系 |
| 客厅温度传感器 | temp_sensor_node (温度传感节点) | 实时采集室内温度,并将数据传送给“控制中心” | 传感器类节点:主要负责“感知环境”,将物理信号(温度、图像等)转化为ROS 2能够处理的数据 |
| 智能灯光控制器 | light_control_node (灯光控制节点) | 接收“控制中心”命令,开关灯光/调节亮度 | 灯光执行器类节点:主要负责“动作执行”,将ROS 2的命令转化为动作执行命令(例如,电机转动、灯亮等动作) |
(续表)
| 智能家居中的 功能模块 | ROS 2中的 节点(节点命名示例) | 核心功能 | 对应关系 |
| 空调控制器 | aircon_control_node(空调控制节点) | 接收“控制中心”命令,调节温度/风速 | 空调执行器类节点:与灯光执行器节点分工不同,但都是“动作执行命令” |
| 家庭控制中心 (例如App等) | env_manager_node (环境管理节点) | 接收室内温度数据,判断是否开启空调/调节灯光,并将命令发送给空调控制器/灯光控制器 | 决策类节点:负责“算法、逻辑判断”,整合其他节点发送过来的数据,向执行节点发出控制命令 |
示例“智能家居系统”中的4个关键功能模块和ROS 2中的4个节点一一对应,关系总结如下:
(1)单一职责:每个功能模块或节点只负责一件事。例如,温度传感器不会去开启空调,灯光控制器不会去采集温度。这对应ROS 2节点的“单一职责”原则。
(2)独立运行:功能模块或节点之间独立运行,互不干扰。例如,空调故障停机,并不影响灯光的正常开关。这对应ROS 2节点的“独立性”。
(3)通信交互:功能模块或节点之间需要通信。例如,温度传感器将温度数据“28℃”以编码方式传送给控制中心,控制中心将“设置温度为26℃”的命令下达给空调控制器执行。这对应ROS 2节点的“通信交互”能力。
3. ROS 2节点的三个核心特性
接下来,结合“真实移动机器人”的示例,介绍ROS 2节点的特性如何体现,具体如下。
1)独立性—不会影响系统运行
在前面已经介绍了ROS 2的节点是“独立进程”。例如,机器人的“激光雷达节点(lidar_node)”主要负责采集周围障碍物数据,如果这个节点出现崩溃或者数据丢失等故障(例如,雷达线接线松脱),系统中的其他节点依然能够正常运行,继续接收或者发出动作命令。
开发者只需对故障节点lidar_node进行重启或者修复,即可恢复机器人的障碍物感知功能,而无须重启整个机器人系统,从而避免扩大故障影响的范围。
2)单一职责—一个节点实现一个功能
ROS 2开发中并不鼓励“一个节点干所有的活”,而是倡导“专岗专责”。以“机器人识别并抓取杯子”为例,系统任务将会被拆分成多个节点,说明如下。
(1)camera_node:只进行图像采集,不进行任何图像内容的识别操作。
(2)image_detect_node:只从图像内容中找到杯子并获取其坐标(例如“x=0.5,y=0.3”),不参与抓取动作的任何决策。
(3)coord_convert_node:只负责把节点image_detect_node获取到的图像坐标转换成机械臂能理解的三维坐标,不参与其他功能环节。
(4)gripper_control_node:只接收节点coord_convert_node转换后的三维坐标用于控制机械爪开合,而无须关心这个坐标是如何获取和转换的。
这样进行功能拆分的优点在于,如果我们需要优化图像中杯子的坐标识别精度,只需要修改节点image_detect_node中的代码,而不用修改其他节点。
备注:如果读者具备面向对象编程经验,也能对ROS 2中的节点概念有较深入的理解。
3)通信交互—使用“话题/服务”方式进行通信
ROS 2中的节点虽各自独立,但组成一个完成的系统功能,还需要在节点之间传递信息和反馈信息。ROS 2提供了“话题(Topic)”和“服务(Service)”等通信方式,对于两者的理解如下。
(1)话题(类似于“广播”):例如,某个节点将数据以“话题”的形式发送给所有订阅了此话题的“订阅者”。这类似于电台播新闻,所有收听了这个电台的人都能够收到。
(2)服务(类似于“打电话”):例如,当节点计算出导航路径后,会主动“打电话”给电机节点,并发送类似于“前进1米,左转90度”等命令,电机节点执行完动作之后也会回复“完成”。这类似我们打电话叫外卖,商家或外卖员完成后会回复“已接单”或“已送达”。
通过“话题”和“服务”通信方式,多个节点之间就能够协同完成从“感知”到“执行”的完整功能。
4. 查看节点
正如第1章中已经介绍的启动ROS 2内置的海龟模拟器节点一样。我们在系统终端或者VSCode中启动海龟模拟器后,除了使用rqt工具查看节点之间的关系外,还可以使用命令在终端中直接查看当前已启动的节点数量、名称等信息,命令如下:
ros2 node list #输出ROS 2当前运行节点列表
运行上述命令,终端窗口中将输出/turtlesim,说明/turtlesim是当前运行的节点,如图2.1所示。

图2.1 使用命令输出运行的节点列表
备注:如读者再启动键盘控制节点,使用命令ros2 run turtlesim turtle_teleop_key后,需要再次运行节点列表查看命令,以获取更新后的节点列表。
如果我们需要进一步查看节点之间的“通信关系”,例如该节点订阅了什么话题、提供了什么服务等信息,可以使用节点参数info获取,命令如下:
ros2 node info <节点名称> #获取指定节点的详细信息
例如,查看节点turtlesim的信息,运行命令后,节点信息输出结果及格式如下:
ros2 node info /turtlesim
# 输出结果中包含以下内容
# 订阅的话题(例如/turtle1/cmd_vel,用于接收“控制海龟运动的命令”)
# 发布的话题(例如/turtle1/pose,发布“海龟当前的位置和角度”)
# 提供的服务(例如/spawn,能创建新的海龟)
运行以上命令后,将输出节点turtlesim的详细信息,包括“订阅的话题”“发布的话题”以及“提供的服务”,如图2.2所示。

图2.2 节点turtlesim的详细信息
我们在进行开发时,通过节点信息查看命令可以直观地看到与该节点相关的详细信息,以便了解节点之间的相互关系等,避免节点混淆,提高开发效率。
本小节通过对ROS 2节点的概念介绍、生活示例类比、节点关系信息查看等相关知识点的讲解,旨在帮助读者深入理解ROS 2节点,以便在后续开发中能够编写出合格、正确的节点程序。
2.1.2 ROS 2节点实现(Python)
在前面的章节中,介绍了ROS 2节点之间如何通过话题、服务、动作等通信机制实现数据交互与协同工作,从而共同完成复杂的机器人任务。在ROS 2的开发架构中,节点(Node)是构成机器人系统的基本功能单元,每个节点专注于实现单一或一组同类型的特定功能,例如传感器数据采集、运动控制、路径规划等。
本节将介绍使用Python创建ROS 2节点的方法与步骤,帮助读者深入理解ROS 2节点的工作原理,并掌握从Python代码编写到节点运行的全流程关键操作,为后续构建复杂的ROS 2应用打下坚实的开发基础。
首先,在主目录下新建一个项目文件夹chapter2_ws,如图2.3所示。

图2.3 新建项目文件夹
新建项目文件夹完成后,双击打开该文件夹并右击显示右键菜单,选择“在终端打开”选项,如图2.4所示。

图2.4 在终端打开新建的项目文件夹
在终端打开后,将直接定位到该项目文件夹,此时输入命令“code ./”启动VSCode并同时打开该文件夹,如图2.5所示。

图2.5 在终端启动VSCode并打开项目文件夹
VSCode启动后,会自动打开新建的项目文件夹,在“资源管理器”中的空白位置右击,打开右键菜单,选择“新建文件”选项,创建Python代码文件ros_python_node.py,如图2.6所示。

图2.6 创建Python代码文件ros_python_node.py
在右侧的代码编辑区中打开Python代码文件ros_python_node.py,输入Python代码实现ROS 2节点,代码如下:
import rclpy #导入ROS 2的Python客户端库
from rclpy.node import Node #导入Node类
def main():
rclpy.init() #初始化
node=Node("python_Node") #创建节点实例
rclpy.spin(node) #启动节点
rclpy.shutdown() #清理分配的资源并确认节点关闭
if __name__ == "__main__":
main()
在上面代码中,导入ROS 2的Python客户端库rclpy,并从该库的模块node中导入Node类。调用库rclpy的初始化函数init()进行初始化资源分配,接下来创建一个名称为python_Node的节点实例node。节点实例创建完成后,使用spin()函数以节点实例node作为参数,实现节点启动并进入阻塞等待模式。该函数会不断循环地在节点上检查是否收到了新的话题数据等事件,直至节点被关闭。shutdown()函数主要用于清理初始化时所分配的资源,并检查节点是否已关闭。
备注:代码段if __name__ == "__main__":main()的作用是当脚本通过python3 script.py直接运行时,__name__变量会被自动赋值为__main__,并执行:后面的代码,即调用main()函数。如果脚本通过代码import XX作为模块导入其他脚本中,__name__会被赋值为模块名称,即import后面的XX,并且不会调用main()函数。
以上代码编写完成后,保存代码文件并使用命令python3对其进行编译,命令如下:
python3 ros_python_node.py
执行编译命令后,终端中无任何信息输出,也没有任何错误提示信息。我们可以使用组合键Ctrl+`打开终端或者组合键Ctrl+Shift+5在已有终端旁分隔出一个新的终端,输入命令ros2 node list查看ROS 2已启动节点列表中是否有创建的节点python_Node,如图2.7所示。

图2.7 查看已启动节点列表
我们查看节点列表时,列表中包含已创建的节点python_Node,这说明使用python3命令编译后节点已成功创建。然而,终端没有任何信息输出,这可能会导致大多数读者误认为程序编写存在问题,从而认为程序不够友好。
为了解决这一问题,我们可以在创建节点后,使用节点类Node的get_logger()函数获取日志记录器,并调用其info()方法输出提示信息。示例代码如下:
node.get_logger().info("节点python_Node已启动")
备注:在使用节点类的get_logger方法时,不能省略(),否则编译时会出现错误。
添加以上述代码后,使用命令python3编译会显示提示信息“节点python_Node已启动”,如图2.8所示。

图2.8 显示提示信息
在ROS 2中,get_logger()是类Node中的一个函数,主要用于获取当前节点的日志记录器对象(logger)。通过该对象,我们可以输出不同级别的日志信息,例如提示、警告、错误等。这一功能对于调试节点、监控运行状态非常实用。
获取日志记录器后,我们可通过不同的方法输出不同级别(严重程度)的日志。这些方法及其各自用途如表2.2所示。
表2.2 输出不同级别日志的方法
| 方 法 | 级 别 | 用 途 |
| logger.debug("msg") | DEBUG | 调试信息(最详细,默认不显示) |
| logger.info("msg") | INFO | 一般运行信息(默认显示,最常用) |
| logger.warn("msg") | WARN | 警告信息(不影响运行,但需注意) |
| logger.error("msg") | ERROR | 错误信息(功能异常,但节点可继续运行) |
| logger.fatal("msg") | FATAL | 致命错误(节点即将崩溃退出) |
如果我们需要查看当前所有运行节点的日志,可以通过输入以下命令进行查看:
ros2 topic echo /rosout #实时查看所有节点的日志
我们也可以通过修改环境变量RCUTILS_CONSOLE_OUTPUT_FORMAT的值对输出的日志格式进行修改,命令如下:
export CUTILS_CONSOLE_OUTPUT_FORMAT=[{function_name}: {line_number}:{message}]
我们在终端中输入以上命令后,需要再次使用命令python3进行编译,终端将输出当前日志信息所在的函数名称、所在行数以及日志内容,如图2.9所示。

图2.9 输出日志的相关信息
本小节主要介绍了如何使用Python实现ROS 2节点的创建与运行等知识点,同时介绍了VSCode的一些使用技巧,以及日志信息、日志级别的作用与使用方法。
2.1.3 ROS 2节点实现(C++)
2.1.2节中使用Python实现了ROS 2节点的创建和运行。Python作为解释型语言,具有开发效率高、语法简洁、无须编译等优势,适用于快速开发、数据可视化、参数配置脚本等任务。然而,在ROS 2机器人开发中,传感器实时数据处理、运动控制、实时路径规划等任务场景对程序的执行效率、内存占用和实时性要求非常高。C++作为编译型语言,能够编译生成直接执行的机器码,相较于Python在执行性能上更具优势。
本小节将介绍如何使用C++实现ROS 2节点的创建、CMakeLists.txt文件的配置,以及节点的运行与验证等操作。通过本小节的学习,读者应当能够使用C++编写节点,并熟练配置CMakeLists.txt文件。
首先,启动VSCode,打开目录CHAPTER2_WS,新建一个文件并命名为ros_c++_node.cpp,如图2.10所示。

图2.10 新建.cpp文件
打开文件ros_c++_node.cpp,编写C++代码实现节点的创建、运行并输出日志信息,代码如下:
#include "rclcpp/rclcpp.hpp" //包含头文件
int main(int argc,char **argv)
{
rclcpp::init(argc,argv); //初始化分配资源
auto node=std::make_shared<rclcpp::Node>("cpp_node"); //构造节点对象并返回智能指针
RCLCPP_INFO(node->get_logger(),"C++ 节点已启动"); //通过宏定义输出INFO级别的日志信息
rclcpp::spin(node); //启动节点并循环检查事件
rclcpp::shutdown(); //清理资源并检查节点是否关闭
return 0;
}
备注:在文件ros_c++_node.cpp的代码中,::是C++中的作用域运算符,可以用于访问命名空间或者类中的公有成员。关键字auto是C++中的一个新特性,核心作用是根据变量对象的具体类型自动推导数据类型,能够在C++编程开发中避免数据类型重复或者错误。ROS 2节点命名不能使用特殊字符,例如“+”“-”等。
上述代码中,若我们包含头文件rclcpp/rclcpp.hpp时出现红色波浪线错误提示,说明程序没有找到所需要的头文件。此时需要编辑includePath设置,手动添加头文件路径。完成路径设置后系统会自动生成配置文件c_cpp_properties.json,该文件默认位于工程根目录下的.vscode文件夹中。
配置文件首次生成后,我们在后续开发中都可以在VSCode中使用组合键Ctrl+Shift+P打开命令面板,搜索并编辑配置文件c_cpp_properties.json,如图2.11所示。

图2.11 搜索配置文件c_cpp_properties.json
打开配置文件c_cpp_properties.json,并根据需要配置includePath代码,如下所示:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**", //在工程内自定义头文件
"/opt/ros/humble/include/**", //ROS2系统头文件路径(默认安装路径)
"${workspaceFolder}/install/**/include/**",//工作空间编译生成的功能包头文件路径(工作空间路径)
"/usr/include/**" //系统标准C++库头文件
],
"defines": [ ],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
我们按照上述示例输入或修改includePath路径配置后,保存并关闭配置文件,代码编辑区内的红色波浪线错误提示将会立即消除。
现在,可以对C++节点代码使用CMake工具进行构建。首先,在VSCode的资源管理器空白处右击,打开快捷菜单,选择“新建文件”,然后新建CMake配置文件并将其命名为CMakeLists.txt,如图2.12所示。

图2.12 新建CMakeLists.txt配置文件
打开新建的配置文件CMakeLists.txt并输入配置代码,如下所示:
cmake_minimum_required(VERSION 3.8) #构建文件所需CMake的最低版本
project(c++_node) #声明工程名称
add_executable(ros_c++_node ros_c++_node.cpp) #添加可执行文件
find_package(rclcpp REQUIRED) #查找rclcpp头文件和库文件的地址
target_include_directories(ros_c++_node PUBLIC ${rclcpp_INCLUDE_DIRS})#为可执行文件包含头文件路径 #含头文件
target_link_libraries(ros_c++_node ${rclcpp_LIBRARIES})#为可执行文件链接库文件
配置代码中,add_executable(ros_cpp_node ros_c++_node.cpp)中的ros_cpp_node表示生成的可执行文件名称,ros_c++_node.cpp是源代码文件相对于配置文件CMakeLists.txt的路径。find_package
(rclcpp REQUIRED)用于查找rclcpp的头文件、库文件及其全部依赖。
配置文件CMakeLists.txt编辑完成并保存后,可以使用cmake命令进行构建,命令如下:
CMake .
其中,“.”表示在当前终端所在目录下搜索配置文件CMakeLists.txt,并在构建完成后将编译结果生成在当前目录下,如图2.13所示。

图2.13 CMake构建后生成的编译结果文件
在生成的编译结果文件中,Makefile可以由命令make读取并构建生成最终的可执行文件,如图2.14所示。

图2.14 构建生成可执行文件
可执行文件生成后,我们在VSCode的资源管理器中可以看到生成的可执行文件ros_c++_node,如图2.15所示。

图2.15 生成的可执行文件
在终端窗口中,使用ls命令显示工程目录下的文件列表,可以看到文件名称ros_c++_node是绿色的,表示其为可执行文件,如图2.16所示。
可执行文件生成后,在终端中输入 ./ros_cpp_node即可直接执行。执行后,使用组合键Ctrl+Shift+5以分屏形式打开一个新的终端,并输入命令ros2 node list查看当前运行的ROS 2节点列表,如图2.17所示。

图2.17 运行可执行文件并查看ROS 2节点列表
可以看到,创建的C++节点cpp_node已在运行,并且输出了日志信息“C++ 节点已启动”。我们可以使用命令“ros2 node info /节点名称”查看指定节点的详细信息,例如节点的订阅者、发布者以及服务等,如图2.18所示。

图2.18 查看节点的详细信息
备注:在后续章节中,将使用ROS 2 的构建工具colcon。该工具以项目空间为基础进行构建时,也会搜索并使用CMake配置文件CMakeLists.txt。因此,我们需要注意将本节中生成的CMake构建配置文件删除或重命名,以避免ROS 2 构建工具colcon误用该配置文件。
本小节主要介绍了使用C++ 编写ROS 2 节点的代码实现,以及通过CMake构建编译生成可执行文件的相关配置方法。特别需要注意的是,在编写程序的过程中,如果出现头文件无法包含的问题,可以按照本小节介绍的处理方法,修改配置文件c_cpp_properties.json中的includePath配置项即可。

本文系统介绍ROS 2节点的核心概念,包括其作为独立进程、单一职责和通信交互三大特性,并通过智能家居和移动机器人等类比帮助理解。详细讲解Python和C++两种语言下节点的创建、初始化、日志输出及运行验证流程,涵盖rclpy/rclcpp库使用、节点生命周期管理、ros2 node list/info命令操作,以及CMakeLists.txt配置与colcon构建基础。

7104

被折叠的 条评论
为什么被折叠?



