第7章 图与节点关系

码枢沄社 · 嵌入式体系化教程平台
入门 👤 刚接触ROS2的新手开发者 🔧 理解机器人系统中各个独立程序(节点)如何被集成发现和交互
本章你将学到
  • 理解ROS2计算图(Graph)是什么及其核心组成
  • 掌握节点(Node)的定义、命名与生命周期
  • 学会使用命令行工具查看和调试图与节点
核心概念 计算图 节点 节点发现

一、计算图:从“孤岛”到“合作网络”

想象一下一个工厂车间:每个工位都有自己的任务(比如搬运、拧螺丝、质检),他们需要互相协作才能完成一辆车的组装。如果没有一个“网络”把大家联系起来,每个人都不知道其他人完成了什么,整个流水线就会瘫痪。
生活类比 + 技术定义

工厂里的工人就像节点:每个工人(节点)只专注于做好自己的工位(单一功能)。他们通过喊话、手势(话题/服务/动作)来传递信息。

车间里的摄像头网络就像计算图:这个网络实时监控着所有工人的状态、位置以及他们之间的通信流,让你站在监控中心就能看清整个车间的运作全貌。

技术定义:ROS2计算图(Computational Graph)是一个由节点(Nodes)和它们之间的通信链路(Topics, Services, Actions)构成的网络拓扑结构。这个网络是动态的,节点可以随时加入或离开。

节点A
(传感器)
话题
(数据通道)
节点B
(控制器)
节点D
(日志记录器)
服务
(请求-回复)
节点C
(执行器)

二、节点(Node):计算图的“工作细胞”

节点是ROS2中最基本的执行单元。从代码上看,一个节点就是一个不断循环运行的进程(或线程)。它知道自己的名字,知道自己发布和订阅了什么话题,提供了什么服务。
文件系统结构 运行时定义 功能描述
包(Package) 可执行文件(Executable) 一个包可以包含多个可执行文件,每个可执行文件可以运行多个节点
节点(Node) 运行时进程 在系统中唯一标识,通过节点名区分,包含发布器、订阅器等
节点句柄(Node Handle) rclc:Node对象 程序中使用Node类实例来创建通信实体、获取参数

节点的关键特性

  • 功能单一性:一个节点通常只完成一个子功能,比如“激光驱动节点”只负责读取激光雷达数据并发布。
  • 可发现性:节点通过DDS中间件自动发现彼此,无需手动配置IP地址或端口。
  • 命名唯一性:在一个ROS2计算图中,节点名必须唯一(包含命名空间)。

三、节点是如何“认识”彼此的?

核心机制叫做节点发现(Node Discovery)。这个过程完全自动化,你不需要写任何网络代码。
  1. 广播存在:当一个节点启动时,它会在网络中发送一个UDP多播包,告诉所有人:“我叫/laser_driver,我会发布/scan话题。”
  2. 本地匹配:其他节点(比如/motion_control)收到这个消息后,检查自己是否需要订阅/scan话题。如果需要,它就记下这个信息。
  3. 建立连接:当/motion_control决定订阅时,它会和/laser_driver建立一个直接的点对点连接(通常是TCP或共享内存),之后的数据就不经过中间节点,直接传输。

编者提示

我在调试一个多机器人系统时,发现新加入的机器人始终无法被现有节点发现。排查了半天,发现是因为两个机器人的发现端口被办公室防火墙阻断了。ROS2节点发现默认使用UDP端口7400-7500,但在很多企业网络环境中,多播流量会被隔离。如果发现节点无法互相发现,可以先检查网络是否支持UDP多播,或者考虑配置DDS的发现机制,将多播改为单播。

四、用命令行“看见”图

你不会直接看到图的结构,但ROS2提供了一套命令行工具,让你可以“观察”和“操作”它。

常见误区与踩坑经验

  • 节点名重复导致启动失败:如果你在一个命名空间中启动了两个同样名字的节点,第二个节点会启动失败并报错“Node with name 'xxx' already exists”。解决方法:给每个进程的不同节点设置不同的名称或使用不同的命名空间。
  • 忘记调用spin()函数:节点在创建了订阅器或服务后,必须调用spin()来进入事件循环,否则节点不会处理任何传入的消息。很多新手会定义好回调函数,然后发现节点“一动不动”——实际上就是缺少了spin()调用。
  • 把命名空间和节点名搞混:一个节点的全名是"/namespace/node_name"。当你在终端用ros2 node list时,看到的是全名。如果发现节点没有出现,先确认启动文件中的命名空间设置是否正确。

最常用的调试命令

# 终端1:启动两个节点
$ ros2 run demo_nodes_cpp talker
$ ros2 run demo_nodes_cpp listener

# 终端2:查看节点
$ ros2 node list
/talker
/listener

# 查看某个节点的详细信息
$ ros2 node info /listener
# 输出会告诉你它订阅了/chatter话题,但没发布任何话题

五、启动文件:如何组织多个节点

当机器人系统复杂起来,你需要在启动时同时运行几十个节点。手动打开一个个终端输入ros2 run显然不现实。这时就需要启动文件(Launch File)
技术定义

启动文件是一个Python或XML/ YAML脚本,它告诉ROS2:你要启动哪些节点、每个节点属于哪个包、节点参数是什么、是否需要重映射话题名称。

一个简单的Python启动文件看起来像这样:
import launch
import launch_ros.actions

def generate_launch_description():
    return launch.LaunchDescription([
        launch_ros.actions.Node(
            package='demo_nodes_cpp',
            executable='talker',
            name='my_talker',            # 重命名节点
            namespace='robot1'               # 指定命名空间
        ),
        launch_ros.actions.Node(
            package='demo_nodes_cpp',
            executable='listener',
            name='my_listener'
        ),
    ])
启动后,计算图中会出现两个节点:/robot1/my_talker/my_listener

六、节点的生命周期(可选)

为了更精细地控制节点(比如在状态机中管理),ROS2引入了节点的生命周期管理。一个节点可以处于以下状态之一:
状态 说明 典型操作
Unconfigured 节点刚创建,尚未配置 调用configure()进入Inactive
Inactive 已配置但未激活,不会处理数据 调用activate()进入Active
Active 正常运行,进行发布/订阅 调用deactivate()回到Inactive
Finalized 节点已被销毁 清理资源,不可逆
普通节点(未启用生命周期)默认直接进入Active状态。如果你需要控制节点何时开始发布话题(例如机器人的导航模块在某些条件下才需要启动),生命周期节点非常有用。

本章小结

  • ROS2计算图是一个动态网络,由节点和它们之间的通信连接组成。
  • 节点是ROS2中最小的执行单元,功能单一,名称唯一。
  • 节点通过DDS的发现机制自动感知其他节点,无需手动配置IP。
  • 使用ros2 node listros2 node info可以实时查看计算图状态。
  • 启动文件(Launch File)用于声明式地启动一组节点,支持重命名和配置参数。

动手试一试

  1. 打开三个终端:第一个运行ros2 run demo_nodes_cpp talker;第二个运行ros2 run demo_nodes_cpp listener;第三个运行ros2 node list,观察输出结果。
  2. 在第三个终端运行ros2 node info /talker,检查它发布了什么话题。
  3. 尝试修改一个Python启动文件,将talker节点重命名为/robot1/talker,然后重新启动,查看节点列表的变化。

检验你的理解

  1. 判断题:在一个ROS2计算图中,可以同时存在两个名字完全相同的节点。
  2. 选择题:当你想要查看一个名为/camera_driver的节点发布或订阅了哪些话题,你应该使用哪个命令?
    A. ros2 node list /camera_driver
    B. ros2 node info /camera_driver
    C. ros2 topic info /camera_driver
  3. 判断题:节点发现过程中,数据会通过一个中心服务器转发。
← 上一章 返回目录 下一章 →