第5章:动作通信(Actions)
课程:ROS2 Python 编程
章节:第5章
课时:2 课时(90 分钟)
教学方式:讲授 + 演示
5.1 动作通信架构
知识点 5.1.1:动作 vs 话题 vs 服务
| 特性 | 话题 (Topic) | 服务 (Service) | 动作 (Action) |
|---|---|---|---|
| 通信模式 | 异步多对多 | 同步一对一 | 异步+长期任务 |
| 时长 | 持续 | 短时 | 长时间(秒~分钟) |
| 反馈 | 无 | 无 | 有(进度反馈) |
| 取消 | 不支持 | 不支持 | 支持取消和抢占 |
知识点 5.1.2:动作通信时序图
Action Client Action Server
│ │
│ ──── Goal Request ◄── /send_goal ──── │
│ ◄── Goal Accepted ◄── 服务 ────────── │
│ │ (开始执行)
│ ◄── Feedback →→ /feedback 话题 →→─ │
│ ◄── Feedback →→ ... →→─ │
│ │
│ ──── Cancel Request 服务 ─────────► │
│ ◄── Cancel Done ◄── 服务 ─────────── │
│ │
│ ◄── Final Result ◄── 服务 ────────── │
图 5-1:动作通信时序图。动作结合了话题(Feedback 流)和服务(Goal/Result/Cancel)的优势。
5.2 动作接口定义
知识点 5.2.1:.action 文件结构
# DoDishes.action — 洗碗任务定义
# Goal(目标)
uint32 total_dishes # 需要洗的碗数量
---
# Result(结果)
uint32 cleaned_dishes # 实际洗干净的碗数量
bool success # 是否成功
---
# Feedback(反馈)
float32 progress # 进度百分比 (0.0 ~ 1.0)
uint32 current_dish # 当前正在洗的碗编号三条
---分隔线分别定义:Goal(目标)、Result(结果)、Feedback(反馈)。
知识点 5.2.2:Action Server Python API
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer, GoalResponse, CancelResponse
from action_demo_interfaces.action import DoDishes
class DoDishesServer(Node):
def __init__(self):
super().__init__('do_dishes_server')
self.action_server = ActionServer(
self, # 所属节点
DoDishes, # 动作类型
'do_dishes', # 动作名称
execute_callback=self.execute, # 执行回调
goal_callback=self.goal, # 目标接收回调
cancel_callback=self.cancel) # 取消回调
def goal(self, goal_request):
"""接收目标时调用 — 决定是否接受"""
self.get_logger().info(
f'收到洗碗目标: {goal_request.total_dishes} 个盘子')
return GoalResponse.ACCEPT # 或 REJECT
def cancel(self, goal_handle):
"""收到取消请求时调用"""
self.get_logger().info('收到取消请求')
return CancelResponse.ACCEPT
async def execute(self, goal_handle):
"""执行目标 — 异步函数,支持 await"""
total = goal_handle.request.total_dishes
feedback_msg = DoDishes.Feedback()
for i in range(1, total + 1):
# 检查是否被取消
if goal_handle.is_cancel_requested:
goal_handle.canceled()
result = DoDishes.Result()
result.cleaned_dishes = i - 1
result.success = False
return result
# 模拟任务执行(1秒/盘子)
await asyncio.sleep(1.0)
feedback_msg.progress = i / total
feedback_msg.current_dish = i
goal_handle.publish_feedback(feedback_msg)
self.get_logger().info(
f'进度: {feedback_msg.progress:.0%} ({i}/{total})')
# 返回最终结果
goal_handle.succeed()
result = DoDishes.Result()
result.cleaned_dishes = total
result.success = True
return result程序 5-1:Action Server 完整示例。核心方法:execute(异步执行)、goal(接受/拒绝)、cancel(取消)。
知识点 5.2.3:Action Client Python API
from rclpy.action import ActionClient
class DoDishesClient(Node):
def __init__(self):
super().__init__('do_dishes_client')
self.client = ActionClient(self, DoDishes, 'do_dishes')
def send_goal(self, total_dishes):
"""发送目标到 Action Server"""
# 等待 Server 上线
self.client.wait_for_server()
goal_msg = DoDishes.Goal()
goal_msg.total_dishes = total_dishes
# 发送目标,注册反馈和结果回调
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 goal_response_callback(self, future):
"""目标被接受/拒绝的回调"""
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('目标被拒绝')
return
self.get_logger().info('目标已接受,开始执行...')
result_future = goal_handle.get_result_async()
result_future.add_done_callback(self.result_callback)
def feedback_callback(self, feedback_msg):
"""接收进度反馈的回调"""
progress = feedback_msg.feedback.progress
current = feedback_msg.feedback.current_dish
self.get_logger().info(f'反馈: {progress:.0%} (盘子 {current})')
def result_callback(self, future):
"""获取最终结果的回调"""
result = future.result().result
self.get_logger().info(
f'完成: {result.cleaned_dishes} 个, 成功: {result.success}')
def cancel_goal(self):
"""取消当前目标"""
self.get_logger().info('请求取消...')
self.goal_handle.cancel_goal_async()程序 5-2:Action Client 完整示例。通过 send_goal_async 发送目标,feedback_callback 接收进度。
知识点 5.2.4:官方要点——动作模型与命令行工具
官方 Understanding ROS 2 actions 教程指出:动作(Action)是面向「长时间运行、可反馈、可取消」任务的通信模式,由目标(Goal)、反馈(Feedback)、结果(Result)三部分组成,底层实现为「一对服务 + 一对话题」。教程要求掌握的动作命令与本章 5.2 节一致:ros2 action list -t、ros2 action info、ros2 action send_goal。其中 send_goal 加 --feedback 参数可在命令行实时打印反馈,如斐波那契示例 ros2 action send_goal /fibonacci action_tutorials/action/Fibonacci "{order: 5}" --feedback。
Articulated Robotics 用「点外卖并要求送货跟踪」作比喻:下单是发送目标,外卖 App 的进度推送是反馈,餐到是结果;中途取消订单对应动作的取消机制。这个比喻有助于区分服务(问一次答一次)与动作(任务全程有往返)。
知识点 5.2.5:官方要点——编写动作服务器与客户端
官方 Python 教程的 Fibonacci 是标准范例:服务器继承 ActionServer,回调分解为三个钩子——goal_callback(接受/拒绝目标)、execute_callback(执行体,必须返回 GoalResponse/Result)、cancel_callback(响应取消请求);执行期间通过 goal_handle.publish_feedback() 周期发布反馈,用 rclpy.spin 驱动。客户端则用 ActionClient.wait_for_server() 等待可用,send_goal_async() 发送异步目标,并通过 add_done_callback 链式处理响应与结果。
教程逐步演进了三个版本:单目标顺序执行 → 多目标排队 → 支持中止与抢占。官方明确指出,execute 回调中的循环应周期性检查 goal_handle.is_cancel_requested,收到取消后设置 goal_handle.canceled() 并返回 ABORTED/CANCELED 结果——这正是本章 5.3.2 节抢占逻辑的官方出处。
知识点 5.2.6:官方要点——自定义动作接口
Creating an action 教程演示了 .action 文件的定义:三段式结构 # Goal\n---\n# Result\n---\n# Feedback,各段间以 --- 分隔,例如 uint32 order\n---\nint32[] sequence\n---\nint32[] partial_sequence。这与本章 5.2.1 节的 .action 文件定义完全一致。接口包需在 package.xml 中声明 rosidl_default_generators,编译后即可用 ros2 interface show action_tutorials/action/Fibonacci 验证。
一个易错点:.action 文件必须放在接口包的 action/ 目录下,且包类型须为纯接口包(package.xml 中 rosidl_default_generators + rosidl_default_runtime + member_groups_group 三件套),否则编译期报「未找到 action 生成器」。The Construct 的课程建议把 .msg、.srv、.action 统一放在一个 interfaces 包中管理,便于跨项目复用。
5.3 取消与抢占机制
知识点 5.3.1:取消流程
Client 发送取消 ──► Server.execute() 中检测 is_cancel_requested
├── True → goal_handle.canceled() → return
└── False → 继续执行
知识点 5.3.2:抢占机制
# 发送新目标时,可选择抢占旧目标
# Server 端通过 GoalResponse.REJECT 拒绝旧目标
def goal(self, goal_request):
# 如果有正在执行的目标,拒绝新目标
if self.current_goal is not None:
self.get_logger().info('已有目标在执行,拒绝新目标')
return GoalResponse.REJECT
return GoalResponse.ACCEPT知识点 5.3.3:官方要点——反馈、取消与抢占的工程实践
在真实机器人上,动作的典型应用是导航(Nav2 的 navigate_to_pose)、机械臂运动(MoveIt 2 的执行接口)与 SLAM 建图(slam_toolbox 的异步建图)。这些系统共同体现了三个工程准则:其一,反馈频率要适中(一般 1–10 Hz),过低客户端无进度感,过高则挤占通信带宽;其二,抢占语义必须明确——新目标到来时旧目标是中止还是排队,要在文档中写清楚,本章 FibonacciServer 采用的「单执行线程 + 排队」与 Nav2 的「抢占式」是两种典型选择;其三,客户端要处理全部三种终态(SUCCEEDED、CANCELED、ABORTED),官方教程的 get_result_callback 正是这样写的。
建议读者将本章练习 5.4 的抢占测试与 Nav2 源码(nav2_simple_commander)对照阅读,体会同一套动作机制在工业级系统中的封装方式。
5.4 本章小结
动作通信可以概括为五个要点:动作通信 = 服务(Goal/Result/Cancel)+ 话题(Feedback),适用于长时间任务;.action 文件分 Goal / Result / Feedback 三部分;ActionServer 核心方法为 execute(异步)、goal、cancel;ActionClient 通过 send_goal_async 发送目标,feedback_callback 接收进度;支持取消(cancel_goal_async)和抢占(GoalResponse.REJECT)。
5.5 练习题
练习 5.1:基于 action_demo_interfaces/action/DoDishes 编写完整的 Action Server 和 Client。
练习 5.2:设计自定义 Action Tracking.action(Goal:目标ID, speed; Result:成功与否; Feedback:当前位置、距离)。
练习 5.3:在 Client 中实现中途取消:发送目标后等待 3 秒,然后发送取消请求。
练习 5.4:测试抢占行为:Server 执行中收到新目标时的处理策略。
练习 5.5:使用 ros2 action 命令行工具查看动作列表、发送目标和获取结果。
仿真结合实例(当前仓库):用 Action 编排一次巡检任务
目标与知识点对应
动作适合持续时间较长、需要反馈和最终结果的任务。本实例启动移动机器人仿真作为任务背景,同时运行 DoDishes Action Server/Client,观察目标接受、百分比反馈和最终结果,理解 Action 与 Gazebo 传感器 Topic 的分工。
运行步骤
# 工作区根目录;三个终端均先执行
source /opt/ros/jazzy/setup.bash
source install/setup.bash# 终端 1:启动机器人仿真
ros2 launch robot_sim_demo gazebo2.launch.py gui:=false rviz:=false drive:=false# 终端 2:启动 Action Server
ros2 run action_demo_cpp dishes_server# 终端 3:发送目标并查看动作状态
ros2 run action_demo_cpp dishes_client
ros2 action list
ros2 action info /dishes观察结果
运行后应看到三类现象:客户端依次收到 20% 到 100% 的反馈,并得到清洗数量结果;ros2 action info /dishes 显示 Action Server 和 Client,同时 Gazebo 继续提供 /odom、/scan 等连续数据;使用 action_demo_py 的同名入口重做一次,可比较 C++ 与 Python 的动作回调实现。
源码与边界
C++ 实现位于 src/action_demo_cpp/src/dishes_server.cpp、dishes_client.cpp;Python 实现位于 src/action_demo_py/action_demo_py/dishes_server.py、dishes_client.py;接口定义位于 src/action_demo_interfaces/;仿真位于 src/robot_sim_demo/。
本例的动作任务是可重复的教学任务,不会直接驱动 Gazebo 底盘;若要控制机器人,仍需另行向 /cmd_vel 发布 geometry_msgs/msg/Twist。

学习材料:
- ROS 2 Documentation (Humble) —— Understanding ROS 2 actions:https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Actions.html
- ROS 2 Documentation (Humble) —— Writing an action server and client (Python):https://docs.ros.org/en/humble/Tutorials/Intermediate/Writing-A-Simple-Py-Action-Server-Client.html
- ROS 2 Documentation (Humble) —— Creating an action:https://docs.ros.org/en/humble/Tutorials/Intermediate/Creating-an-Action.html
- The Construct —— ROS 2 Basics in 5 Days:https://www.theconstructsim.com/
- Articulated Robotics —— ROS 2 Basics 系列视频:https://www.youtube.com/@ArticulatedRobotics
RuyiSDK Board Docs