一、先看 .action 文件写了什么
在 learning_interface/action/MoveCircle.action 中:
bool enable ← 目标:客户端说"我要启动"还是"我不要启动"
---
bool finish ← 结果:服务端最后说"完成了"还是"没完成"
---
int32 state ← 反馈:服务端过程中说"我现在转到 30°了""我现在转到 60°了"...
三段分别叫 Goal(目标)、Result(结果)、Feedback(反馈),这是动作通信的三要素。
二、colcon build 之后发生了什么
.action 文件被 ROS2 的编译工具自动翻译成 C++ 代码,生成一个名叫 MoveCircle 的类,里面装了三个小结构体:
类名: learning_interface::action::MoveCircle
内部包含:
MoveCircle::Goal → 结构体 { bool enable }
MoveCircle::Result → 结构体 { bool finish }
MoveCircle::Feedback → 结构体 { int32 state }
代码里写的 using Action = learning_interface::action::MoveCircle; 就是给这个长名字起个短名。之后:
cpp
Action::Goal → MoveCircle::Goal → 有 .enable 字段
Action::Result → MoveCircle::Result → 有 .finish 字段
Action::Feedback → MoveCircle::Feedback → 有 .state 字段
三、rclcpp_action 是什么
rclcpp_action 是一个命名空间 (可以理解为一个装相关功能的文件夹),里面放着 ROS2 动作通信需要的所有东西。通过 #include "rclcpp_action/rclcpp_action.hpp" 引入。
它里面装了这些:
rclcpp_action (命名空间)
├── create_server<Action>() → 创建一个动作服务端,返回智能指针
├── create_client<Action>() → 创建一个动作客户端,返回智能指针
├── Server<Action> → 服务端类(你创建出来的就是它)
├── Client<Action> → 客户端类
├── ServerGoalHandle<Action> → 服务端用来管理"一个具体目标"的句柄
├── ClientGoalHandle<Action> → 客户端用来管理"一个具体目标"的句柄
├── GoalResponse → 枚举:ACCEPT_AND_EXECUTE(接受)/ REJECT(拒绝)
├── CancelResponse → 枚举:ACCEPT / REJECT
├── ResultCode → 枚举:SUCCEEDED / ABORTED / CANCELED
└── GoalUUID → 本质是 std::array<uint8_t, 16>(16字节唯一ID)
四、create_server 怎么创建服务端
cpp
action_server_ = rclcpp_action::create_server<Action>(
this, // 我是哪个节点
"move_circle", // 动作名叫什么(客户端通过这个名字找到我)
回调1, // 收到目标时调用
回调2, // 收到取消请求时调用
回调3 // 目标被接受后调用
);
create_server是一个函数模板 ,<Action>告诉它要用哪个动作接口类型- 返回值是
Server<Action>::SharedPtr,也就是std::shared_ptr<Server<Action>>(智能指针,自动管理内存) - 和
create_publisher、create_subscription是同一个模式
五、ServerGoalHandle<Action> 是什么?里面有什么?
using GoalHandle = rclcpp_action::ServerGoalHandle<Action>; 定义了一个别名。
ServerGoalHandle<Action> 是一个类模板,"句柄"的意思就是"抓手"------每次客户端发来一个目标,服务端就拿到一个句柄,通过这个句柄来控制和反馈这个具体目标。好比你去餐厅点餐,服务员给你一个号码牌,通过这个号码牌你可以查进度、取餐、取消。
它有哪些方法:
| 方法 | 什么时候用 | 作用 |
|---|---|---|
get_goal() |
执行时想知道客户端发了什么 | 返回 shared_ptr<Action::Goal>,包含 .enable 字段 |
get_goal_id() |
想知道是哪个目标 | 返回目标 UUID |
is_canceling() |
每轮循环检查一次 | 客户端是否请求取消?返回 true/false |
is_active() |
查目标还在不在 | 是否还没到达终态 |
is_executing() |
查是否正在执行 | 是否处于执行状态 |
publish_feedback(feedback) |
有进展就发 | 向客户端发送进度反馈 |
succeed(result) |
任务完成了 | 通知客户端"成功了"(终态,调完不能再调别的) |
abort(result) |
任务没法完成了 | 通知客户端"中止了"(终态) |
canceled(result) |
响应客户端的取消 | 通知客户端"已取消"(终态) |
execute() |
开始执行时 | 标记进入执行状态 |
六、完整流程串起来
客户端 服务端
│ │
│── 发送 Goal (enable=true) ──────────→ │ 触发 handle_goal
│ │ ↓ 返回 ACCEPT_AND_EXECUTE
│ │ 触发 handle_accepted
│ │ ↓ 开新线程 → execute()
│ ←── Feedback (state=0°) ────────── │ publish_feedback(0°)
│ ←── Feedback (state=30°) ───────── │ publish_feedback(30°)
│ ←── Feedback (state=60°) ───────── │ publish_feedback(60°)
│ ... │ ...
│ ←── Feedback (state=360°) ──────── │ publish_feedback(360°)
│ ←── Result (finish=true) ───────── │ succeed(result)
│ │
└─ 收到最终结果,关闭 └─ 执行完毕
七、怎么自己看头文件学习
ROS2 的头文件就在系统目录里,可以直接打开看源码:
bash
ls /opt/ros/foxy/include/rclcpp_action/
核心文件就几个:
client.hpp → Client<T> 类的完整定义
server.hpp → Server<T> 类的完整定义
create_client.hpp → create_client<T>() 函数声明
create_server.hpp → create_server<T>() 函数声明
client_goal_handle.hpp → ClientGoalHandle<T> 的所有方法
server_goal_handle.hpp → ServerGoalHandle<T> 的所有方法
types.hpp → GoalUUID、GoalStatus 等基础类型
想看哪个直接 cat 就行,头文件是最准确的文档。