ROS2 任务状态机、视觉桥接与安全控制闭环复盘:从 /perception/target 到 /cmd_vel 的机器人目标搜索与靠近实现

一、FSM 的基本概念

FSM 是 Finite State Machine,有限状态机

在机器人任务中,FSM 用来描述机器人处于哪个任务阶段,以及收到事件后应该切换到哪个状态。

核心公式可以理解为:

当前状态 + 输入事件 = 下一个状态

例如机器人执行"寻找目标并靠近"的任务时,不应该一直发布同一个速度,而是要经历:

空闲 → 准备 → 搜索 → 靠近 → 停止

FSM 的价值是把复杂任务拆成清晰状态,避免控制逻辑写成一堆混乱的 if-else


二、状态和事件的区别

FSM 中最重要的是区分 状态 state事件 event

|--------|-------------|-------------------------------------------------|
| 概念 | 含义 | 项目例子 |
| 状态 | 机器人当前处于什么阶段 | IDLESEARCHAPPROACHERROR |
| 事件 | 触发状态变化的输入 | starttarget_foundtarget_reachederror |

状态表示"现在是什么"。 事件表示"发生了什么"。

在我的项目里:

|---------------|-------------|
| 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 |
| 输入 | startstand_donetarget_foundtarget_reachedtarget_losterrorreset |
| 输出 | IDLESTANDSEARCHAPPROACHSTOPREPORTERROR |

它不直接发布 /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 |