一、FSM 的基本概念
FSM 是 Finite State Machine,有限状态机。
在机器人任务中,FSM 用来描述机器人处于哪个任务阶段,以及收到事件后应该切换到哪个状态。
核心公式可以理解为:
当前状态 + 输入事件 = 下一个状态
例如机器人执行"寻找目标并靠近"的任务时,不应该一直发布同一个速度,而是要经历:
空闲 → 准备 → 搜索 → 靠近 → 停止
FSM 的价值是把复杂任务拆成清晰状态,避免控制逻辑写成一堆混乱的 if-else。
二、状态和事件的区别
FSM 中最重要的是区分 状态 state 和 事件 event。
|--------|-------------|-------------------------------------------------|
| 概念 | 含义 | 项目例子 |
| 状态 | 机器人当前处于什么阶段 | IDLE、SEARCH、APPROACH、ERROR |
| 事件 | 触发状态变化的输入 | start、target_found、target_reached、error |
状态表示"现在是什么"。 事件表示"发生了什么"。
在我的项目里:
|---------------|-------------|
| topic | 含义 |
| /task/event | 输入给 FSM 的事件 |
| /task/state | FSM 输出的当前状态 |
所以 /task/event 和 /task/state 不能混用。前者是触发条件,后者是任务结果。
三、FSM 状态设计
我项目中设计了 7 个状态。
|------------|----------|--------------|
| 状态 | 中文含义 | 任务含义 |
| IDLE | 空闲 | 等待任务开始 |
| STAND | 站立准备 | 模拟机器人进入准备姿态 |
| SEARCH | 搜索 | 原地旋转寻找目标 |
| APPROACH | 靠近 | 发现目标后向目标靠近 |
| STOP | 停止 | 到达目标后停止 |
| REPORT | 汇报 | 可扩展为任务完成上报 |
| ERROR | 异常 | 安全保护触发后的异常状态 |
主状态转移关系:
|------------|------------------|------------|
| 当前状态 | 事件 | 下一个状态 |
| IDLE | start | STAND |
| STAND | stand_done | SEARCH |
| SEARCH | target_found | APPROACH |
| APPROACH | target_reached | STOP |
异常转移关系:
|----------|---------|-----------|
| 当前状态 | 事件 | 下一个状态 |
| 任意运行状态 | error | ERROR |
| ERROR | reset | IDLE |
注意:stand_done 只在 STAND 状态下有效。如果机器人还在 IDLE,直接发送 stand_done 被忽略是正常现象。
四、task_fsm_node 的职责
task_fsm_node 是状态机节点。
它只负责一件事:
接收任务事件,更新任务状态,发布当前状态。
|--------|------------------------------------------------------------------------------------|
| 项目 | 内容 |
| 订阅 | /task/event |
| 发布 | /task/state |
| 输入 | start、stand_done、target_found、target_reached、target_lost、error、reset |
| 输出 | IDLE、STAND、SEARCH、APPROACH、STOP、REPORT、ERROR |
它不直接发布 /cmd_vel。
这样设计的核心思想是:
FSM 负责"任务决策",不负责"底层运动"。
也就是说:
|----------------------|------------------|
| 节点 | 负责问题 |
| task_fsm_node | 机器人现在应该处于哪个任务阶段 |
| task_motion_node | 这个状态下应该怎么动 |
| robot_control_node | 如何把速度交给底层 driver |
| MockDogDriver | 如何模拟机器人执行 |
这种分层让后续升级更容易。比如以后把 APPROACH 从固定前进改成视觉跟随,不需要重写 FSM。
五、视觉桥接节点的作用
视觉节点输出的是感知结果,例如是否检测到目标、置信度、目标框大小、目标位置等。
但 FSM 不应该直接处理复杂视觉字段。
所以需要 vision_fsm_bridge_node,它的作用是:
把视觉检测结果转换成 FSM 能理解的任务事件。
|----------------------|-------------|
| 输入 | 作用 |
| /perception/target | 接收目标检测结果 |
| /task/state | 判断当前 FSM 状态 |
| /task/event | 发布任务事件 |
核心逻辑:
|------------|----------|------------------|
| 当前状态 | 视觉条件 | 发布事件 |
| SEARCH | 检测到有效目标 | target_found |
| APPROACH | 目标面积足够大 | target_reached |
| APPROACH | 长时间没看到目标 | target_lost |
这样视觉层不会直接控制机器人,而是通过事件影响 FSM,再由 FSM 影响运动。
六、目标检测字段与任务事件
/perception/target 中常见字段如下。
|--------------|------------|--------------|
| 字段 | 含义 | 用途 |
| detected | 是否检测到目标 | 判断有没有目标 |
| confidence | 检测置信度 | 过滤不可靠检测 |
| area_ratio | 目标框面积占图像比例 | 判断目标是否足够近 |
| center_x | 目标中心横坐标 | 可用于左右方向判断 |
| offset_x | 目标相对中心的偏移 | 可用于转向控制 |
| class_name | 目标类别 | YOLO 中区分物体类别 |
| frame_id | 坐标系名称 | 后续结合 TF 使用 |
三个核心事件:
|------------------|-----------|----------------------|
| 事件 | 含义 | 常见状态变化 |
| target_found | 搜索时发现目标 | SEARCH -> APPROACH |
| target_reached | 目标已经足够近 | APPROACH -> STOP |
| target_lost | 靠近过程中目标丢失 | APPROACH -> SEARCH |
其中 area_ratio 是一个简化距离判断方法。
目标越靠近相机,通常检测框越大;因此 area_ratio 越大,可以认为目标越近。
七、task_motion_node 的职责
task_motion_node 负责把任务状态转换成速度命令。
|---------------|------------|
| 输入 | 输出 |
| /task/state | /cmd_vel |
它不关心视觉细节,也不关心事件来源,只关心当前状态。
当前运动规则:
|------------|----------|
| FSM 状态 | 运动行为 |
| IDLE | 停止 |
| STAND | 停止 |
| SEARCH | 原地旋转 |
| APPROACH | 低速前进 |
| STOP | 停止 |
| REPORT | 停止 |
| ERROR | 停止 |
这体现了一个重要工程原则:决策和控制分离。
FSM 只决定"现在是什么状态",motion 节点负责"这个状态下发什么速度"。
尤其要注意:ERROR 状态下必须输出零速度,因为异常时默认行为应该是停止,而不是继续运动。
八、robot_control_node、driver 与 heartbeat
robot_control_node 是 ROS2 速度命令和底层机器人 driver 之间的桥。
|------------|----------------------|-----------------|
| 上层输入 | 中间节点 | 底层执行 |
| /cmd_vel | robot_control_node | MockDogDriver |
这样上层控制逻辑不需要知道具体机器人 SDK 的细节。
上层只发布标准 ROS2 速度命令,底层 driver 负责适配具体机器人。
heartbeat 是控制安全机制。
|------------------|---------------------------|
| heartbeat 状态 | 控制行为 |
| 正常 | 执行 /cmd_vel |
| 超时 | 忽略 /cmd_vel,调用 stop() |
| 未启动 | 认为不安全,不执行速度 |
heartbeat 的意义是:
防止上层节点异常退出后,机器人继续执行旧速度命令。
九、安全保护节点
task_safety_node 是独立安全监控节点。
它不直接发布 /cmd_vel,而是把异常转换成 FSM 事件。
|----------------|-------------|
| 订阅 | 作用 |
| /robot/state | 判断机器人本体是否安全 |
| /task/state | 判断任务是否卡住或超时 |
|---------------|---------|
| 发布 | 内容 |
| /task/event | error |
安全处理链路:
|--------|------------|------------|----------|
| 异常 | 安全节点输出 | FSM 状态 | 运动结果 |
| 低电量 | error | ERROR | 停止 |
| 急停 | error | ERROR | 停止 |
| 任务超时 | error | ERROR | 停止 |
这个设计的好处是:
安全节点不抢控制权,而是让 FSM 进入 ERROR ,再由 motion 节点统一输出零速度。
这样系统不会出现多个节点同时发布 /cmd_vel 的控制冲突。
十、完整任务系统的数据流
主任务链路:
/perception/target → vision_fsm_bridge_node → /task/event → task_fsm_node → /task/state → task_motion_node → /cmd_vel → robot_control_node → MockDogDriver
安全保护链路:
/robot/state + /task/state → task_safety_node → /task/event:error → task_fsm_node → ERROR → task_motion_node → /cmd_vel=0
对应工程分层:
|--------|----------------------------------------|
| 层级 | 项目模块 |
| 感知层 | AprilTag / YOLO / /perception/target |
| 事件桥接层 | vision_fsm_bridge_node |
| 决策层 | task_fsm_node |
| 控制层 | task_motion_node |
| 执行层 | robot_control_node + MockDogDriver |
| 安全层 | heartbeat + task_safety_node |