第5章 动作通信实现

码枢沄社 · 嵌入式体系化教程平台
入门 👤 刚学会话题与服务通信的ROS2开发者 🔧 实现机器人长时间执行的复杂任务指令
本章你将学到
  • 理解动作通信的“三件套”结构:目标+结果+反馈
  • 用Python实现一个动作服务器和客户端
  • 掌握动作通信的典型应用场景与设计模式
核心概念动作服务器动作客户端反馈循环可抢占目标

动作通信为什么需要存在?——从一次失败的抓取说起

客户端
发送目标
服务器
客户端
持续反馈
服务器
客户端
最终结果
服务器

想象你让机器人去抓取一个杯子。话题只能持续发布位置数据,服务只能发一次指令然后等回复——但抓取这个动作需要几秒钟:手臂要先移动到杯子附近,然后调整夹爪,最后夹紧收回。在这个过程中,你希望知道“手臂走到哪了”,而且如果位置偏了,你还想中途取消重新来一次。这就是动作(Action)通信诞生的场景。

动作通信
生活类比:叫外卖。你下单(目标),骑手每过一会告诉你到哪了(反馈),最后送达(结果)。如果迟迟不到你可以取消订单(抢占)。
技术定义:ROS2中一种长期运行的、可反馈进度、可中途取消的通信模式。基于话题和服务构建,但提供了更高级的语义封装。

动作通信的三件套

组成部分类比技术含义通信方式
目标(Goal)外卖订单内容告诉服务器要执行什么任务服务(一次性)
反馈(Feedback)骑手实时位置服务器在过程中持续推送进度话题(持续)
结果(Result)外卖送达或取消任务完成后的最终输出服务(一次、最终)

一个动作定义对应一个.action文件,包含三个部分的分隔声明:#goal#result#feedback。我用一个实际抓取的动作定义来演示其结构。

# File: Grasp.action
# 目标(Goal)
string object_name       # 要抓取的目标物体名称
---
# 结果(Result)
bool success            # 是否成功
float32 grasp_force    # 抓取力度
---
# 反馈(Feedback)
float32 approach_progress  # 接近进度 0.0~1.0
float32 gripper_position  # 当前夹爪开度
关键认知:动作通信本质上是“服务+话题”的复合体。目标发送和结果返回走服务的请求-响应模式,反馈走话题的发布-订阅模式。ROS2的rclcpprclpy库把这两者封装成了统一的Action接口,你只需要关心业务逻辑。

动作服务器与客户端的工作流

客户端发送Goal
服务器接收并开始执行
持续推送Feedback
任务完成返回Result

实际项目中,动作服务器通常会创建一个后台线程来执行耗时操作,主线程继续监听新的目标请求。我在调试一个机械臂动作服务器时踩过一个坑:如果在反馈回调中执行了阻塞IO,整个动作通信会被卡住,因为反馈实际上是在DDS的回调线程中发送的。

用Python实现一个动作客户端

下面是一个动作客户端的代码,它向动作服务器发送一个抓取目标,并实时打印反馈进度。

<span class="com">import rclpy
from rclpy.action import ActionClient
from custom_interfaces.action import Grasp

class GraspClient:
    def __init__(self):
        super().__init__('grasp_client')
        self._client = ActionClient(self, Grasp, 'grasp')

    def send_goal(self, object_name):
        goal_msg = Grasp.Goal()
        goal_msg.object_name = object_name
        # 等待服务器就绪
        self._client.wait_for_server()
        # 发送目标,绑定反馈回调
        send_goal_future = self._client.send_goal_async(
            goal_msg,
            feedback_callback=self.feedback_callback
        )
        send_goal_future.add_done_callback(self.goal_response_callback)

    def feedback_callback(self, feedback_msg):
        feedback = feedback_msg.feedback
        print(f"接近进度: {feedback.approach_progress:.2f}")

常见误区

  • 误区一:在反馈回调里做耗时操作。反馈回调在DDS线程中执行,阻塞会导致整个节点通信卡死。反馈回调只做打印或缓存。
  • 误区二:忘记等待服务器就绪就发目标。直接send_goal_async()会收到异常,因为动作客户端内部的服务代理还没连上服务器。
  • 误区三:把动作当成话题来订阅。动作的反馈话题是自动生成的,名称类似/grasp/_action/feedback,直接订阅虽然可以收到数据,但无法获得完整的生命周期管理(如目标ID匹配、超时取消等)。

动作通信的设计模式

长时间任务

  • 导航到目标点
  • 机械臂抓取
  • 无人机巡检路径

可打断任务

  • 紧急停止当前动作
  • 重新规划路径
  • 切换操作模式

带进度反馈

  • 文件传输进度
  • 数据采集进度
  • 模型推理进度

条件执行

  • 检查前置条件
  • 超时自动取消
  • 重试策略

动作通信最典型的用法是“可抢占目标队列”。服务器端可以同时接收多个目标,但同一时间只执行一个。如果收到新目标,默认行为是抢占当前目标:服务器会取消当前任务,转而执行新目标。实际开发中,大部分团队会在动作服务器中实现一个目标队列,让非紧急任务排队等待,避免频繁抢占导致机械臂来回抖动。

编者提示:在嵌入式平台(如STM32 + Micro-ROS)上实现动作通信时,动作定义中的数据类型要尽量精简。比如反馈中的进度值用uint8百分比(0~100)代替float32,可以显著减少DDS序列化的开销。我曾在一个Cortex-M4平台上把动作的目标结构体从4个字段精简到2个,反馈频率从5Hz提升到了20Hz。

动作通信与话题、服务的对比

特性话题(Topic)服务(Service)动作(Action)
通信模式发布/订阅请求/响应目标+反馈+结果
是否持续持续流一次性长期运行
是否有反馈
是否可取消
适用场景传感器数据瞬时查询复杂任务

刚入门时容易困惑的一个问题是:既然动作底层用了话题和服务,为什么还要单独学一套API?原因在于动作封装了**生命周期管理**。当你取消一个动作时,服务器会自动清理正在执行的线程、释放资源,并通知客户端。这些逻辑如果用原生话题+服务来实现,需要自己写大量状态机代码。

# 动作客户端取消示例
async def cancel_goal(self):
    cancel_future = await self._client.cancel_goal_async(self._goal_handle)
    if cancel_future.returncode == CancelResponse.ACCEPT:
        print("取消请求已接受")

动作通信的完整生命周期

客户端
Goal服务
服务器
反馈话题
持续推送
服务器
Result服务
返回最终结果
客户端
Cancel服务
双向取消协商
客户端/服务器

从上图可以看到,一个动作通信背后涉及到4个通信端点:Goal服务、Cancel服务、Result服务、Feedback话题。ROS2的ActionClient和ActionServer把这4个端点封装成了一个简洁的接口。当你在客户端调用send_goal_async()时,底层自动完成了Goal服务的调用、反馈话题的订阅、Result服务的轮询以及Cancel服务的注册。

如果你看到类似Unable to find action server的报错,大概率是命名空间不匹配。动作服务器默认在/action_name命名空间下创建4个端点,客户端必须使用完全相同的名称。一个排查技巧是运行ros2 action list -t查看当前所有可用的动作。

动手试一试

  1. 创建一个自定义动作定义 MoveTo.action,包含目标坐标(x, y),反馈为剩余距离,结果为到达时间。
  2. 用Python写一个动作客户端,向/move_to发送目标,每秒打印一次剩余距离。
  3. 在客户端中加入取消逻辑:当剩余距离大于10米时,自动取消当前动作。

检验你的理解

  1. 动作通信底层使用了哪两种ROS2通信模式?(选择题)
    A. 话题+参数
    B. 服务+话题
    C. 参数+服务
    D. 话题+话题
  2. 如果只关心动作是否完成,不需要中间反馈,是否可以只用Result服务?(判断题)
  3. 动作客户端发送目标前,必须调用哪个方法来确保服务器已就绪?(填空题)

本章小结

  • 动作通信是ROS2中用于长期任务的三段式通信模型:目标、反馈、结果。
  • 动作定义通过.action文件声明,编译后自动生成目标、结果、反馈三个消息类型。
  • 动作服务器内部维护了完整的生命周期:接收目标→执行→反馈→结果→清理。
  • 客户端可以随时取消正在执行的动作,服务器侧需正确处理抢占和资源释放。
  • 在嵌入式平台上使用动作通信时,应精简消息字段以减少序列化开销。
← 上一章 返回目录 下一章 →