2、ROS2核心概念速通:节点(Node)、话题(Topic)、服务(Service)、动作(Action)的实战理解

说实话,很多初学者一上来就被ROS2的这几个概念搞懵了。节点、话题、服务、动作……听着像是一堆专业术语堆砌。但你别怕,这些东西说白了就是机器人系统里几个最基本的通信方式。我做了这么多年嵌入式,接触过各种通信协议,ROS2这套设计其实非常优雅。

今天我就带你用实战的眼光,把这四个核心概念彻底搞明白。我们不谈虚的,直接上代码、上场景。

ROS2 核心概念 节点 Node 可执行进程 话题 Topic 异步发布/订阅 服务 Service 同步请求/响应 动作 Action 带反馈的异步任务 四种通信方式,各有各的适用场景

2.1 节点(Node):一切功能的载体

节点是什么?说白了就是一个独立的可执行程序。每个节点只干一件事,比如控制电机、处理激光雷达数据、发布里程计信息。我习惯把节点想象成一个「工人」,每个工人只负责自己的岗位。

为什么要拆成这么多节点?你想想看,如果所有功能都写在一个程序里,改一个地方就得重新编译整个项目,多麻烦。ROS2的节点设计,本质上就是模块化思想的体现。

核心要点:一个节点就是一个独立的进程,拥有自己的生命周期。节点之间通过话题、服务、动作进行通信。

创建节点其实很简单。以Python为例:

import rclpy
from rclpy.node import Node

class MyMotorNode(Node):
    def __init__(self):
        super().__init__('motor_controller')
        self.get_logger().info('电机控制节点已启动!')

def main(args=None):
    rclpy.init(args=args)
    node = MyMotorNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

我的经验:节点命名一定要有含义。我曾经接手过一个项目,里面全是 node1、node2 这种名字,调试的时候根本分不清谁是谁。后来我花了整整一天才理清楚。所以,命名规范从一开始就要做好。

2.2 话题(Topic):最常用的异步通信

话题是ROS2里最核心的通信方式。它的模式是发布/订阅——一个节点发布数据,其他节点订阅接收。发布者和订阅者互不知道对方的存在,完全解耦。

举个例子:激光雷达节点不断发布扫描数据,导航节点订阅这些数据来做路径规划。两者各干各的,互不干扰。

发布话题的代码:

class LidarPublisher(Node):
    def __init__(self):
        super().__init__('lidar_pub')
        self.publisher = self.create_publisher(
            LaserScan,   # 消息类型
            '/scan',     # 话题名称
            10           # 队列大小
        )
        self.timer = self.create_timer(0.1, self.publish_data)
    
    def publish_data(self):
        msg = LaserScan()
        msg.ranges = [1.0, 2.0, 3.0]  # 模拟数据
        self.publisher.publish(msg)

订阅话题的代码:

class NavigationSubscriber(Node):
    def __init__(self):
        super().__init__('nav_sub')
        self.subscription = self.create_subscription(
            LaserScan,
            '/scan',
            self.scan_callback,
            10
        )
    
    def scan_callback(self, msg):
        # 处理激光数据
        self.get_logger().info(f'收到数据:{len(msg.ranges)}个点')

避坑指南:我曾经遇到过一个问题——订阅者收不到数据,查了半天才发现是话题名称拼写错了。ROS2的话题名称是大小写敏感的,/Scan 和 /scan 完全是两个话题。这个坑我踩过,你别再踩了。

2.3 服务(Service):同步请求-响应

话题是「发完就不管了」,但有些场景你需要一问一答。比如你让机械臂去抓一个物体,你得知道它到底抓到了没有。这时候就需要服务。

服务的模式是:客户端发送请求,服务端处理并返回响应。这是一个同步操作,客户端会一直等待直到收到响应。

定义服务接口:

# GripperControl.srv
bool enable          # 请求:是否开启夹爪
---
bool success         # 响应:是否成功
string message       # 响应:附加信息

服务端实现:

class GripperService(Node):
    def __init__(self):
        super().__init__('gripper_server')
        self.srv = self.create_service(
            GripperControl,
            '/gripper/control',
            self.control_callback
        )
    
    def control_callback(self, request, response):
        if request.enable:
            # 执行夹爪控制
            response.success = True
            response.message = '夹爪已开启'
        else:
            response.success = True
            response.message = '夹爪已关闭'
        return response

适用场景:服务适合那些需要确认结果的操作,比如设置参数、触发拍照、控制开关。不适合高频数据流,因为同步等待会阻塞程序。

2.4 动作(Action):带反馈的异步任务

动作是ROS2里最「高级」的通信方式。它解决了服务的痛点——如果一个任务耗时很长(比如让机器人走到某个点),服务会一直阻塞,这显然不合理。

动作的工作流程是:

  • 客户端发送目标(Goal)
  • 服务端持续发送反馈(Feedback)
  • 任务完成后发送结果(Result)
  • 客户端可以随时取消任务

说白了,动作 = 服务 + 话题。它既有服务的请求-响应机制,又有话题的持续反馈能力。

定义动作接口:

# MoveToGoal.action
# 目标
geometry_msgs/Pose target_pose
---
# 结果
bool reached
float32 duration
---
# 反馈
float32 distance_remaining

动作服务端实现(简化版):

class MoveActionServer(Node):
    def __init__(self):
        super().__init__('move_action_server')
        self.action_server = ActionServer(
            self,
            MoveToGoal,
            '/robot/move_to',
            self.execute_callback
        )
    
    async def execute_callback(self, goal_handle):
        feedback_msg = MoveToGoal.Feedback()
        # 模拟移动过程
        for i in range(10):
            feedback_msg.distance_remaining = 10.0 - i
            goal_handle.publish_feedback(feedback_msg)
            await asyncio.sleep(1)
        
        goal_handle.succeed()
        result = MoveToGoal.Result()
        result.reached = True
        result.duration = 10.0
        return result

我的建议:在嵌入式设备上使用动作时,一定要注意反馈频率。我曾经在一个STM32上跑动作服务,反馈频率设成了100Hz,结果CPU直接跑满。后来改成10Hz,一切正常。嵌入式资源有限,别太贪心。

2.5 如何选择?一张表说清楚

通信方式 通信模式 是否阻塞 有无反馈 典型场景
话题 Topic 发布/订阅 否 无 传感器数据、状态广播
服务 Service 请求/响应 是 无 开关控制、参数设置
动作 Action 目标/结果/反馈 否 有 导航、机械臂运动

嗯,这张表基本能解决你90%的选型困惑。记住一个原则:高频数据用话题,需要确认用服务,耗时任务用动作。

实战总结:我在做AGV小车项目时,激光雷达数据用话题发布,控制指令用服务下发,路径规划用动作执行。三种方式配合使用,系统既灵活又可靠。你刚开始可能觉得有点复杂,但用多了就会发现,ROS2这套设计真的很合理。

好了,核心概念就讲到这里。记住,理论只是基础,真正理解它们的最好方式就是动手写代码。打开你的ROS2环境,试着创建几个节点,让它们通过话题聊聊天,你会有更深的体会。

可可精品资料库