🤖 ROS2 机器人 少年创客营:Day 5
主题:动作 (Action) ------ 可中断的长时任务与实时反馈
👋 欢迎回来,指挥官!
昨天回顾 :我们学会了 Service (服务)。它像"打电话",有问必答。但是,如果任务很长怎么办?
- 场景:你命令海龟:"请走到坐标 (9, 9)!"或者"请旋转 360 度!"
- 问题 :海龟执行这些动作需要时间。如果用 Service,你的程序会卡住(阻塞)直到任务结束,期间什么都做不了!更糟糕的是,如果海龟半路被墙挡住了,你想让它立刻停下,Service 却做不到,因为它必须等到海龟到了(或失败)才能回复。
今天挑战:
- 突破阻塞 :学习 Action (动作) 通信机制。
- 实时反馈:让海龟在行走/旋转过程中不断汇报:"我已走了 50%..."、"还剩 30 度"。
- 随时取消:实现"紧急制动",在海龟走到一半时强行叫停。
今日核心 :掌握 ROS 2 中最强大的通信方式 ------ Action,它是控制机器人长时任务(如导航、机械臂抓取)的终极武器!
🗺️ 今日探险地图 (Checklist)
- 🆚 概念对决:Topic vs Service vs Action,到底什么时候用哪个?
- 🔍 现场侦查 :使用命令行查看现有的 Action 服务器(就像刚才看到的
rotate_absolute)。 - 📦 消息结构:拆解 Action 的三大件:Goal, Feedback, Result。
- 🛠️ 命令行实战 :向
/turtle1/rotate_absolute发送目标并监控进度。 - 🧠 代码进阶 :编写 Action Client,实现自动发送目标并在中途取消。
- 🏆 终极任务:指挥海龟进行"长途奔袭",并在中途实施"紧急制动"!
🆚 第一关:通信三巨头大比拼
在 ROS 2 中,我们有三种通信方式,它们各司其职:
| 特性 | Topic (话题) | Service (服务) | Action (动作) |
|---|---|---|---|
| 模式 | 发布/订阅 (广播) | 请求/响应 (同步) | 目标/反馈/结果 (异步) |
| 数据流 | 单向 (One-way) | 双向 (一次请求,一次回复) | 双向持续流 (请求 -> 多次反馈 -> 最终结果) |
| 阻塞性 | 非阻塞 (发完就跑) | 阻塞 (等待回复) | 非阻塞 (发送后可继续做别的事) |
| 可取消性 | ❌ 不可取消 | ❌ 不可取消 | ✅ 可随时取消 |
| 典型场景 | 传感器数据 (激光雷达)高频控制指令 | 开关机、重置、查询状态短任务 | 导航、机械臂抓取任何耗时长的任务 |
| 比喻 | 📻 收音机 | 📞 打电话 | 🚀 发射导弹 + 遥测(发射后实时监控,可随时自毁) |
💡 为什么需要 Action?
想象你在下载一个大文件:
- Service:点击下载 -> 屏幕黑屏等待 -> 下载完成显示"成功"。(体验极差!)
- Action:点击下载 -> 显示进度条 10%...50%...90% -> 完成。而且你可以在 50% 时点击"取消"。(完美!)
🔍 第二关:现场侦查 (命令行实战)
在你动手写代码之前,我们先看看系统中已经有哪些 Action 准备好了。
(这正是你刚才截图中展示的内容!)
1. 列出所有 Action
打开终端,输入:
bash
ros2 action list
👀 观察现象 :
你应该能看到:
text
/turtle1/rotate_absolute
这说明 turtlesim 节点已经作为一个 Action Server 启动好了,它在等待我们发送"旋转目标"。
2. 查看 Action 详情
输入:
bash
ros2 action info /turtle1/rotate_absolute
👀 观察现象(参考你的截图):
text
Action: /turtle1/rotate_absolute
Action clients: 0
Action servers: 1
/turtlesim
- Action servers: 1 (
/turtlesim):有一个服务器(海龟)在监听。 - Action clients: 0:目前还没有人(客户端)给它发指令。
🎯 任务 :这就是我们的靶子!接下来我们要成为那个
Client,把clients的数量变成 1!
📦 第三关:解剖 Action 消息
Action 的消息定义比 Service 复杂,它包含三个部分(以 RotateAbsolute 为例):
plaintext
# 1. Goal (目标):你要它做什么?
float32 theta # 目标角度(弧度),例如 1.57 (90 度)
---
# 2. Feedback (反馈):做得怎么样了?(周期性发送)
float32 remaining # 还需要转多少度
---
# 3. Result (结果):最终做完了吗?(只发送一次)
bool success # 是否成功
string message # 结果描述
- Goal: 客户端发给服务器(例如:转到 1.57 弧度)。
- Feedback: 服务器不断发给客户端(例如:还剩 1.0 弧度...还剩 0.5 弧度...)。
- Result: 任务结束时,服务器发给客户端(例如:成功到达)。
🛠️ 第四关:命令行操控 Action
现在,让我们不写代码,直接用命令行体验 Action 的强大功能:实时反馈。
1. 发送旋转目标
在终端输入以下命令(注意 --feedback 参数,这是灵魂!):
bash
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}" --feedback
🔍 命令拆解:
send_goal:发送一个行动目标。/turtle1/rotate_absolute:目标 Action 的名字。turtlesim/action/RotateAbsolute:消息类型。"{theta: 1.57}":具体的参数(转 90 度)。--feedback:关键参数! 加上它,你才能在终端看到实时的进度更新。
👀 观察现象:
-
海龟开始旋转。
-
终端不会卡住,而是疯狂刷屏:
textSending goal... Goal accepted with ID: ... Feedback: {remaining: 1.4} Feedback: {remaining: 1.2} Feedback: {remaining: 0.8} ... Result: {success: true, message: 'Target reached'} -
这就是 Action 的魅力:你在等待结果的同时,能清楚知道过程!
2. 尝试"紧急制动" (取消任务)
这次我们发一个大一点的目标,比如转两圈 (6.28 弧度),然后在它转的时候按下 Ctrl + C。
bash
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 6.28}" --feedback
- 当看到
Feedback开始滚动时,迅速按下Ctrl + C。 - 现象 :终端会显示
Canceling goal...,海龟会立刻停止旋转! - 这就是 可取消性,Service 做不到这一点。
💻 第五关:代码实战 ------ 编写智能指挥官
现在,我们要用 Python 编写一个 Action Client,让它自动执行以下逻辑:
- 发送目标:让海龟旋转 360 度 (
6.28弧度)。 - 监听反馈:打印剩余角度。
- 自动取消 :当剩余角度小于 2.0 弧度(还没转完)时,主动取消任务,测试"紧急制动"功能。
📝 代码文件:action_client_turtle.py
python
#!/usr/bin/env python3
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
from turtlesim.action import RotateAbsolute
class SmartTurtleCommander(Node):
def __init__(self):
super().__init__('smart_turtle_commander')
# 1. 创建 Action Client
# 参数:节点本身,Action 类型,Action 名称
self._action_client = ActionClient(self, RotateAbsolute, '/turtle1/rotate_absolute')
self.goal_handle = None
self.cancel_triggered = False
self.get_logger().info('⏳ 等待 Action 服务器启动...')
# 等待服务器上线 (最多等 10 秒)
if not self._action_client.wait_for_server(timeout_sec=10.0):
self.get_logger().error('❌ 未找到 Action 服务器!请确保 turtlesim_node 已运行。')
return
self.get_logger().info('✅ 服务器已连接!准备发射目标...')
# 2. 构造目标消息
goal_msg = RotateAbsolute.Goal()
goal_msg.theta = 6.28 # 目标:旋转 360 度 (2 * pi)
self.get_logger().info(f'🎯 发送目标:旋转 {goal_msg.theta} 弧度 (约 360 度)')
# 3. 异步发送目标
self._send_goal_future = self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback
)
# 注册回调:当服务器回应"收到目标"时触发
self._send_goal_future.add_done_callback(self.goal_response_callback)
def goal_response_callback(self, future):
"""处理服务器对目标的响应"""
self.goal_handle = future.result()
if not self.goal_handle.accepted:
self.get_logger().info('❌ 目标被服务器拒绝')
rclpy.shutdown()
return
self.get_logger().info('✅ 目标已接受,海龟开始旋转!监控中...')
# 注册回调:当任务最终结束时触发
self._get_result_future = self.goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)
def feedback_callback(self, feedback_msg):
"""处理实时反馈"""
feedback = feedback_msg.feedback
remaining = feedback.remaining
# 打印进度
self.get_logger().info(f'📈 [进度] 剩余角度:{remaining:.2f} 弧度')
# 🚨 核心逻辑:自动取消!
# 如果剩余角度小于 1.0 弧度 (还没转完),且尚未触发过取消
if remaining < 1.0 and not self.cancel_triggered:
self.cancel_triggered = True
self.get_logger().warn('⚠️ 触发条件:剩余 < 1.0 rad! 执行紧急取消!')
# 发送取消请求
self.goal_handle.cancel_goal_async()
def get_result_callback(self, future):
"""处理最终结果"""
result = future.result().result
status = future.result().status
# 状态码说明:1=SUCCEEDED, 2=CANCELED, 3=ABORTED
if status == 2:
self.get_logger().info('🛑 任务已被成功取消!海龟停下了。')
elif status == 1 and result.success:
self.get_logger().info('✅ 任务圆满完成!')
else:
self.get_logger().info('❌ 任务失败或被中止')
# 任务结束,关闭节点
rclpy.shutdown()
def main(args=None):
rclpy.init(args=args)
node = SmartTurtleCommander()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
if __name__ == '__main__':
main()
🚀 第四关:编译与运行
1. 更新 setup.py
添加新的入口点:
python
'start_action = my_turtle_bot.action_client_turtle:main',
2. 构建与运行
bash
cd ~/ros2_ws
colcon build
source install/setup.bash
# 终端 1: 启动仿真器
ros2 run turtlesim turtlesim_node
# 终端 2: 启动 Action 客户端
ros2 run my_turtle_bot start_action
👀 预期结果:
- 海龟开始旋转。
- 终端显示剩余角度不断减小。
- 当剩余角度小于 3.0 时,脚本自动发送取消指令。
- 海龟立刻停止。
- 终端打印:"任务已被成功取消!"
🏆 终极挑战:画个"未完成"的正方形
结合 Day 1 的手动控制和今天的 Action 知识:
任务:
- 编写一个脚本,让海龟使用 Action (
rotate_absolute) 依次旋转 90 度,共 4 次(理论上画正方形)。 - 特殊规则:在第 3 次旋转时,强制在旋转到一半(剩余 0.5 弧度)时取消任务。
- 观察海龟的轨迹:它应该画出三条完整的边,和一条只有半条边的残缺正方形。
这证明了你可以精确控制任务的生命周期,这是高级机器人控制的基础!
📝 Day 5 总结清单
| 概念 | 关键词 | 作用 |
|---|---|---|
| Action | Goal, Feedback, Result | 处理长时、可监控、可取消的任务 |
| 非阻塞 | Async (异步) | 发送任务后不卡死程序,可并行处理其他事 |
| 反馈机制 | feedback_callback |
实时了解任务进度(如导航剩余距离) |
| 取消机制 | cancel_goal_async() |
遇到危险或需求变更时,立即停止任务 |
| 命令行工具 | ros2 action list/info/send_goal |
调试和测试 Action 的神器 |
🔮 明日预告 (Day 6)
海龟现在能执行长任务了,也能随时叫停。但是,每次都要手动敲命令启动 Server、Client、Rviz 太麻烦了!
明天,我们将学习 "一键启动" 的魔法:
- Launch Files :编写
.launch.py文件,一行命令同时启动仿真器、你的 Python 脚本和可视化工具。- Parameters:在不改代码的情况下,动态修改海龟的颜色、速度上限、背景色。
- 系统集成:将之前几天的碎片拼成一个完整的、可交付的机器人系统!
准备好让你的机器人项目变得专业且高效了吗?明天见!
© 2026 ROS2 机器人 少年创客营 | 从单步指令到智能长时任务,Action 让机器人更懂人性