第1章:ROS2核心概念与实战

各位同学好,我是你们这门课的主讲。今天咱们聊聊ROS2里最基础、也最重要的四个概念:节点、话题、服务、动作。

说实话,我见过太多人一上来就啃文档,结果越看越晕。其实没那么复杂。你想想看,自动驾驶系统本质上就是一堆程序在互相通信。那怎么通信?就是靠这四样东西。

1.1 节点(Node)——系统的“人”

节点是什么?说白了,就是一个独立的可执行程序。每个节点负责一件事。比如在Autoware里,有感知节点、规划节点、控制节点。

我个人习惯把节点想象成团队里的成员。每个人有自己的职责,大家互相配合。你不可能让一个人既开车又看路况又做决策,对吧?

核心要点:节点是ROS2的最小执行单元。每个节点都是一个独立的进程。

写一个节点其实很简单。我给大家看个例子:

import rclpy
from rclpy.node import Node

class MyNode(Node):
    def __init__(self):
        super().__init__('my_first_node')
        self.get_logger().info('节点已启动!')

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

if __name__ == '__main__':
    main()

这段代码创建了一个叫my_first_node的节点。启动后它会打印一条日志。嗯,这里要注意:rclpy.spin()会让节点一直运行,直到你按Ctrl+C。

小技巧:我刚开始学的时候,总忘记调用rclpy.init()。结果节点一直启动不了。后来我养成了习惯,写节点第一步就是检查init有没有写。

1.2 话题(Topic)——系统的“广播”

话题是ROS2里最常用的通信方式。它是一种发布-订阅模式。一个节点发布消息到某个话题,其他节点订阅这个话题就能收到消息。

我曾经在项目里遇到过一个问题:两个节点用话题通信,但数据总是对不上。查了半天才发现,是话题名称拼写错了。你想想看,一个字母不对,整个系统就瘫痪了。

发布者的代码长这样:

class PublisherNode(Node):
    def __init__(self):
        super().__init__('publisher_node')
        self.publisher_ = self.create_publisher(String, 'chatter', 10)
        timer_period = 0.5  # 每秒发两次
        self.timer = self.create_timer(timer_period, self.timer_callback)
    
    def timer_callback(self):
        msg = String()
        msg.data = 'Hello, ROS2!'
        self.publisher_.publish(msg)
        self.get_logger().info(f'发布了: {msg.data}')

订阅者呢?

class SubscriberNode(Node):
    def __init__(self):
        super().__init__('subscriber_node')
        self.subscription = self.create_subscription(
            String, 'chatter', self.listener_callback, 10)
    
    def listener_callback(self, msg):
        self.get_logger().info(f'收到了: {msg.data}')

避坑指南:我曾经因为队列大小设置太小,导致高频数据丢失。建议根据你的数据频率来设置队列大小。比如摄像头数据,队列至少设到100以上。

1.3 服务(Service)——系统的“问答”

话题是单向的,发布者只管发,不管有没有人收。但服务不一样,它是请求-响应模式。客户端发一个请求,服务端处理完返回结果。

我举个例子。在Autoware里,你要让车辆启动,就会调用一个服务。你发请求说“启动”,系统处理完告诉你“已启动”或“启动失败”。

服务端代码:

from example_interfaces.srv import AddTwoInts

class ServiceNode(Node):
    def __init__(self):
        super().__init__('service_node')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)
    
    def add_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info(f'收到请求: {request.a} + {request.b} = {response.sum}')
        return response

客户端代码:

class ClientNode(Node):
    def __init__(self):
        super().__init__('client_node')
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('等待服务端启动...')
        self.send_request(3, 5)
    
    def send_request(self, a, b):
        request = AddTwoInts.Request()
        request.a = a
        request.b = b
        future = self.cli.call_async(request)
        future.add_done_callback(self.response_callback)
    
    def response_callback(self, future):
        response = future.result()
        self.get_logger().info(f'结果: {response.sum}')

关键区别:话题是“发完就不管了”,服务是“问了就得等答案”。选哪个?看你需不需要立即知道结果。

1.4 动作(Action)——系统的“长任务”

动作是ROS2里最复杂的通信方式。它适合执行时间较长的任务,比如导航到某个地点。动作有三个阶段:发送目标、反馈进度、返回结果。

我记得第一次用动作时,觉得它跟服务差不多。后来才发现,动作可以中途取消,还能实时反馈进度。服务做不到这些。

动作服务端示例:

from action_tutorials_interfaces.action import Fibonacci

class ActionServerNode(Node):
    def __init__(self):
        super().__init__('action_server_node')
        self.action_server = ActionServer(
            self, Fibonacci, 'fibonacci', self.execute_callback)
    
    async def execute_callback(self, goal_handle):
        self.get_logger().info('开始执行...')
        feedback_msg = Fibonacci.Feedback()
        feedback_msg.sequence = [0, 1]
        
        for i in range(1, goal_handle.request.order):
            feedback_msg.sequence.append(
                feedback_msg.sequence[i] + feedback_msg.sequence[i-1])
            goal_handle.publish_feedback(feedback_msg)
            await asyncio.sleep(1)
        
        goal_handle.succeed()
        result = Fibonacci.Result()
        result.sequence = feedback_msg.sequence
        return result

实用建议:如果你的任务可能超过5秒,或者需要中途取消,用动作准没错。比如自动驾驶里的变道、泊车,都是动作的典型应用。

1.5 知识体系总览

说了这么多,我画张图帮你理清思路:

ROS2核心通信机制 节点 (Node) 话题 (Topic) 发布-订阅 服务 (Service) 请求-响应 动作 (Action) 目标-反馈-结果 参数 (Parameter) 配置管理 特性对比 话题 单向通信,一对多,实时性高 适合传感器数据、状态发布 服务 双向通信,一对一,同步等待 适合开关控制、参数查询 动作 长任务,可取消,有反馈 适合导航、机械臂控制 参数 节点配置,动态调整 适合PID系数、阈值设置

1.6 实战建议

学到这里,你可能觉得内容有点多。别急,我分享几个实战经验:

  • 先跑通再优化:我见过太多人一开始就追求完美代码。其实先让节点跑起来,能通信了,再慢慢优化。
  • 善用命令行工具:ros2 topic list、ros2 service list这些命令,调试时能救命。
  • 日志是你的朋友:多打日志,少用print。ROS2的日志有级别控制,方便排查问题。

重要提醒:千万别在生产环境里用rclpy.spin_once()加延时循环。我吃过这个亏,CPU占用率直接拉满。用rclpy.spin()或者定时器才是正道。

好了,这一章的内容就到这。记住这四个概念,后面的课程你会越来越顺。有什么问题,随时交流。

可可精品资料库