从 Open-RMF 看下一代多机器人调度系统:多车协同、动态交通调度与时空路径规划深度解析
面向读者:初级工程师 / AGV 与多机器人方向学习者
文章目标:深入理解openTCS,并理清其与常见AGV物流仿真项目区别
文章目录
- 一、为什么传统 AGV 调度正在遇到瓶颈?
- 二、什么是 Open-RMF?
- 三、Open-RMF 和 [openTCS](https://blog.csdn.net/2501_94590643/article/details/161091451?sharetype=blogdetail&sharerId=161091451&sharerefer=PC&sharesource=2501_94590643&spm=1011.2480.3001.8118)的核心区别
- 四、RMF 的核心思想:机器人共享"时空"
- 五、RMF 的整体架构
- 六、多车协同:RMF 为什么比传统 AGV 更复杂?
- 七、rmf_traffic:RMF 最核心的模块
- 八、RMF 为什么强调"时间窗预约"?
- 九、时间窗(Time Window)思想的深入理解
- 二、什么是 Open-RMF?
- 三、Open-RMF 和 openTCS 的核心区别
- 四、RMF 的核心思想:机器人共享"时空"
- 五、RMF 的整体架构
- 六、多车协同:RMF 为什么比传统 AGV 更复杂?
- 七、rmf_traffic:RMF 最核心的模块
- 八、RMF 为什么强调"时间窗预约"?
- 九、时间窗(Time Window)思想的深入理解
- 十、冲突检测(Conflict Detection)
- 十一、RMF 的冲突检测为什么更先进?
- 十二、Negotiation:RMF 最核心的思想
- 十三、Negotiation 的具体机制
- 十四、Negotiation 本质是什么?
- 十五、RMF 和 MAPF 的关系
- 十六、RMF 为什么不追求"绝对最优"?
- 十七、时空路径规划(Spatio-temporal Planning)
- 十八、RMF 的交通模型
- 十九、多地图(Multi-map)
- 二十、RMF 如何管理多地图?
- 二十一、电梯联动(Lift Integration)
- 二十二、门禁联动(Door Integration)
- 二十三、异构机器人(Heterogeneous Robots)
- 二十四、Fleet Adapter:RMF 的关键设计
- 二十五、为什么异构兼容很重要?
- 二十六、rmf_demos:官方演示系统
- 二十七、rmf_demos 使用什么仿真?
- 二十八、rmf_demos 能看到什么?(详细分析)
- 二十九、RMF 的动态任务分配
- 三十、RMF 为什么适合医院场景?
- 三十一、RMF 的工程挑战
- 三十二、RMF 和 openTCS 到底怎么选?
- 三十三、如果你做比赛,RMF 最值得学什么?
- 三十四、一个适合比赛的 RMF 风格架构(详细设计)
- 三十五、CBS 算法详解(比赛中的高级选择)
- 三十六、RMF 的实时性保证
- 三十七、RMF 的容错与恢复
- 三十八、RMF 的可扩展性
- 三十九、RMF 的未来发展方向
- 四十、总结:RMF 的核心价值
一、为什么传统 AGV 调度正在遇到瓶颈?
过去很多 AGV 系统,本质上是:
固定路线
+ 中央调度
+ 路径占用
+ 区域锁
这种模式在工厂、仓储、固定站点运输中非常有效。它的核心假设是:
“机器人走固定路线,环境基本不变,冲突可以通过锁来解决。”
在这个假设下,系统设计可以非常简洁:画好路网,定义好资源,谁先申请谁先走。这就是 openTCS 那一代系统的核心逻辑。
但随着 AMR(Autonomous Mobile Robot,自主移动机器人)的普及,这个假设开始崩塌。
1.1 AMR 和传统 AGV 的本质区别
传统 AGV:
-
沿磁条/二维码/固定轨道行驶
-
路径在部署时就确定了
-
不会"自己决定"走哪条路
-
环境变化时需要人工重新布线
AMR:
-
自主导航,不依赖固定轨道
-
可以动态避障
-
可以自己规划路径
-
环境变化时可以自适应
这意味着:
| 特性 | 传统 AGV | AMR |
|---|---|---|
| 路径 | 固定 | 动态 |
| 环境 | 静态 | 动态 |
| 避障 | 停车等待 | 绕行 |
| 部署 | 需要改造场地 | 即插即用 |
| 灵活性 | 低 | 高 |
1.2 传统调度模式为什么不适合 AMR?
传统 AGV 调度的核心是"资源锁":
路段 A-B 被 AGV-1 占用
→ AGV-2 不能进入
→ 等待释放
这在固定路网里没问题。但 AMR 的路径是动态的:
AMR-1 可能走路线 A
也可能走路线 B
也可能临时绕行路线 C
如果还用传统资源锁:
-
锁什么?路径都不确定,锁哪段路?
-
锁多久?AMR 可能随时改路线,锁了的资源可能白锁
-
怎么释放?AMR 绕行了,原来锁的路段要不要释放?
更严重的问题是:
AMR 数量越来越多,场景越来越复杂。
现代建筑里可能同时有:
-
配送机器人(送药、送餐)
-
清洁机器人(扫地、消毒)
-
巡检机器人(安防、设备检查)
-
搬运机器人(物流、仓储)
这些机器人:
-
来自不同厂家
-
使用不同导航方式
-
有不同的运动能力
-
共享同一个物理空间
传统 AGV 调度系统根本没有设计来处理这种复杂度。
1.3 新场景带来的新挑战
| 挑战 | 说明 |
|---|---|
| 多楼层 | 机器人需要坐电梯 |
| 动态环境 | 人流、临时障碍物 |
| 异构机器人 | 不同品牌、不同能力 |
| 共享设施 | 电梯、门禁、走廊 |
| 实时性 | 不能等几秒再决策 |
| 规模 | 几十甚至上百台机器人 |
这些挑战叠加在一起,传统"固定路网 + 资源锁"的模式就力不从心了。
于是:
Open-RMF(Open Robotics Middleware Framework)出现了。
它不是对传统 AGV 调度的"升级",而是一种全新的系统设计哲学。
二、什么是 Open-RMF?
Open-RMF 是由 Open Robotics(ROS 的维护组织)联合多家机器人企业共同推动的开源项目。
它的全称是 Open Robotics Middleware Framework,但这个名字容易让人误解为"只是一个中间件"。实际上它是:
一个完整的多机器人协同基础设施。
2.1 Open-RMF 的定位
它的目标不是:
“控制一台机器人怎么走。”
而是:
“让整个建筑里所有机器人能够和平共处、协同工作。”
你可以把它想象成:
一个城市的交通管理局。
它不管每辆车的发动机怎么工作,不管每辆车的方向盘怎么转。它管的是:
-
这条路现在谁能走
-
这个路口谁先过
-
这个电梯现在归谁用
-
这扇门什么时候开
-
如果两辆车要撞上了怎么办
2.2 Open-RMF 管理的对象
| 对象 | 说明 |
|---|---|
| 机器人 | 各种品牌、各种类型 |
| 电梯 | 调度、锁定、楼层控制 |
| 自动门 | 开关控制、通行权 |
| 走廊/通道 | 通行调度 |
| 充电桩 | 资源分配 |
| 任务 | 配送、巡检、清洁等 |
注意:Open-RMF 不只管机器人。它管理的是整个建筑的"可移动资源"和"共享设施"。
2.3 Open-RMF 的技术栈
Open-RMF 构建在 ROS 2 之上:
ROS 2(通信基础)
↓
DDS(分布式数据服务)
↓
Open-RMF 各模块
↓
Fleet Adapter(对接各品牌机器人)
它使用 ROS 2 的通信机制(Topic、Service、Action),但在其上构建了完整的调度逻辑。
2.4 Open-RMF 的核心模块
| 模块 | 职责 |
|---|---|
| rmf_traffic | 交通调度核心:轨迹管理、冲突检测、协商 |
| rmf_task | 任务管理:任务定义、分配、状态跟踪 |
| rmf_fleet_adapter | 车队适配器:对接不同品牌机器人 |
| rmf_building_map | 建筑地图:多楼层、电梯、门 |
| rmf_lift / rmf_door | 设施联动:电梯和门的控制 |
| rmf_visualization | 可视化:Web 界面监控 |
三、Open-RMF 和 openTCS的核心区别
很多人第一次接触 RMF 时,会误以为:
“它只是另一个 AGV 调度系统。”
实际上,它们代表了两种完全不同的系统设计哲学。
3.1 设计哲学对比
| 维度 | openTCS | Open-RMF |
|---|---|---|
| 核心思想 | 资源锁 + 路径占用 | 时空轨迹 + 动态协商 |
| 路径模型 | 固定图拓扑 | 动态轨迹 |
| 冲突解决 | 先到先得(锁) | 协商(Negotiation) |
| 时间维度 | 弱(主要看空间) | 强(时空一体) |
| 环境假设 | 静态 | 动态 |
| 机器人类型 | 同构为主 | 异构为主 |
| 设施联动 | 不涉及 | 电梯、门、充电桩 |
| 地图 | 单层 | 多楼层 |
| 适用场景 | 工厂/仓库 | 医院/写字楼/商场 |
3.2 一个具体例子说明区别
场景:两台机器人要经过同一段走廊。
openTCS 的处理方式:
走廊是一个 Path 资源
Robot-A 先申请 → 获得锁 → 通过
Robot-B 申请 → 被拒绝 → 等待
Robot-A 离开走廊 → 释放锁
Robot-B 获得锁 → 通过
问题:如果走廊很长(比如 50 米),Robot-A 走完需要 30 秒。Robot-B 必须等 30 秒。即使 Robot-B 比 Robot-A 晚 2 秒进入,两者根本不会相遇。
Open-RMF 的处理方式:
Robot-A 的轨迹:走廊入口 @ 10:00 → 走廊出口 @ 10:30
Robot-B 的轨迹:走廊入口 @ 10:02 → 走廊出口 @ 10:32
系统分析:
- 两者方向相同
- Robot-B 始终在 Robot-A 后面
- 安全距离满足
→ 无冲突,两者同时通行
结果:RMF 的方案效率更高,因为它理解"时间"。
3.3 本质区别的一句话总结
openTCS 问的是:
“这个空间现在有没有人?”
Open-RMF 问的是:
“这个空间在未来某个时刻会不会有人?”
这就是从"空间调度"到"时空调度"的跨越。
四、RMF 的核心思想:机器人共享"时空"
Open-RMF 最重要的思想是:
“机器人冲突不仅发生在空间,也发生在时间。”
4.1 什么是"时空冲突"?
两台机器人"冲突"的定义不是"它们在同一个位置",而是"它们在同一时刻出现在同一个位置"。
这个区别看起来微小,但对系统设计的影响是巨大的。
传统系统只考虑空间:
位置 P 被占用 → 其他车不能进
RMF 考虑时空:
位置 P 在 10:05-10:08 被占用 → 其他车在这个时间段不能进
但 10:09 之后可以进
4.2 为什么时空视角更高效?
考虑一个十字路口:
纯空间视角:
路口被 Robot-A 占用
Robot-B、C、D 全部等待
直到 Robot-A 完全离开
时空视角:
Robot-A 在 10:00-10:03 经过路口
Robot-B 计划 10:05 到达路口
→ 不冲突,Robot-B 不需要等待
Robot-C 计划 10:02 到达路口
→ 冲突!Robot-C 需要调整(减速/等待/换路)
时空视角下,只有真正会在同一时刻相遇的机器人才需要协调。其他机器人可以正常通行。
4.3 时空路径规划的数学表示
在 RMF 中,一条路径不是:
[P1, P2, P3, P4] (纯空间序列)
而是:
[(P1, t1), (P2, t2), (P3, t3), (P4, t4)] (时空序列)
其中 ti 表示机器人到达 Pi 的时间。
这样,两条路径是否冲突,就变成了一个几何问题:
两条时空轨迹是否在某个时刻距离过近?
4.4 时空规划的优势
| 优势 | 说明 |
|---|---|
| 更高吞吐量 | 不需要为"可能的冲突"过度等待 |
| 更少停车 | 只在真正冲突时才协调 |
| 更好的预测性 | 可以提前知道未来会发生什么 |
| 更灵活的协调 | 可以通过调整时间而不是路径来解决冲突 |
4.5 时空规划的代价
| 代价 | 说明 |
|---|---|
| 计算复杂度高 | 维度从 2D 变成 3D(x, y, t) |
| 需要准确的时间估计 | 如果机器人实际速度和预测不符,计划就会失效 |
| 需要持续更新 | 机器人实际执行和计划有偏差时,需要重新规划 |
| 系统复杂度高 | 比简单的资源锁难实现得多 |
五、RMF 的整体架构
典型 RMF 架构:
+------------------+
| Web Dashboard | ← 运维监控界面
+------------------+
|
v
+----------------------------------------------------------+
| Open-RMF Core |
|----------------------------------------------------------|
| rmf_traffic | 交通调度:轨迹、冲突、协商 |
| rmf_task | 任务管理:分配、状态、优先级 |
| rmf_building_map | 建筑地图:多层、连接、设施 |
+----------------------------------------------------------+
/ | | \
/ | | \
v v v v
+--------+ +--------+ +---------+ +----------+
| Fleet | | Fleet | | Lift | | Door |
|Adapter | |Adapter | | Adapter | | Adapter |
| (A) | | (B) | | | | |
+--------+ +--------+ +---------+ +----------+
| | | |
v v v v
Robot-A Robot-B Elevator Auto-Door
(品牌X) (品牌Y)
5.1 架构的关键特点
1. 适配器模式(Adapter Pattern)
RMF 不直接控制任何设备。所有设备都通过 Adapter 接入。这意味着:
-
添加新品牌机器人 = 写一个新的 Fleet Adapter
-
添加新电梯 = 写一个新的 Lift Adapter
-
核心调度逻辑完全不需要改
2. 分布式架构
RMF 基于 ROS 2 的 DDS 通信,天然是分布式的:
-
各个 Adapter 可以运行在不同机器上
-
核心调度可以独立部署
-
没有严格的"中央服务器"概念
3. 建筑级视角
RMF 的设计单位不是"一个房间"或"一个楼层",而是"整栋建筑"。它天然考虑:
-
多楼层
-
楼层间的连接(电梯)
-
区域间的隔离(门)
5.2 信息流
一个典型的任务执行流程:
1. 用户通过 Dashboard 下发任务:"把药品从药房送到 3 楼 302 病房"
2. rmf_task 接收任务,分析任务类型(配送),确定需要:
- 一台配送机器人
- 使用电梯从 1 楼到 3 楼
- 经过 302 病房的门
3. rmf_task 选择合适的机器人(考虑位置、电量、当前任务)
4. rmf_traffic 为该机器人规划时空轨迹:
- 1楼药房 → 电梯口(避开其他机器人)
- 等待电梯
- 乘电梯到 3 楼
- 3楼电梯口 → 302 病房(避开其他机器人)
5. rmf_traffic 检测轨迹是否与其他机器人冲突
- 如果冲突 → 触发 Negotiation
- 协商出双方都可接受的方案
6. Fleet Adapter 将指令翻译为具体机器人能理解的命令
7. Lift Adapter 在适当时机调度电梯
8. Door Adapter 在机器人到达时开门
9. 机器人执行,持续上报状态
10. rmf_traffic 持续监控实际轨迹与计划轨迹的偏差
- 如果偏差过大 → 重新规划
六、多车协同:RMF 为什么比传统 AGV 更复杂?
传统 AGV 的多车协同相对简单:
任务 → 路径 → 申请资源 → 获得锁 → 执行
而 RMF 的多车协同是一个持续的、动态的过程:
任务
↓
规划时空轨迹
↓
提交轨迹到全局 Schedule
↓
持续检测冲突
↓
发现冲突 → 触发 Negotiation
↓
协商出新方案
↓
更新轨迹
↓
继续执行
↓
(循环:持续检测、持续调整)
6.1 为什么是"持续"的?
因为 AMR 的世界是动态的:
-
机器人实际速度可能和预测不同(地面摩擦、电量变化)
-
新任务随时可能到来
-
机器人可能突然故障
-
人可能突然出现在走廊里
-
电梯可能被人占用
因此,RMF 不是"规划一次就完事",而是"持续规划、持续调整"。
6.2 传统 AGV vs RMF 的协同策略对比
| 维度 | 传统 AGV | Open-RMF |
|---|---|---|
| 冲突发现时机 | 申请资源时 | 轨迹提交时(提前预测) |
| 冲突解决方式 | 等待 | 协商(可能双方都调整) |
| 调整粒度 | 停/走 | 减速/加速/换路/等待 |
| 动态性 | 低(一旦获得锁就执行到底) | 高(随时可能重新规划) |
| 信息利用 | 当前状态 | 未来轨迹预测 |
6.3 一个具体的多车协同场景
场景:医院走廊,3 台机器人
Robot-A:从药房出发,要去 3 楼(需要坐电梯)
Robot-B:在 1 楼巡检,即将经过电梯口
Robot-C:从 3 楼下来,正在电梯里
RMF 的处理:
1. Robot-A 规划轨迹:药房 → 走廊 → 电梯口 → 等电梯 → 上 3 楼
2. 提交轨迹到 Schedule
3. 系统检测到:
- Robot-A 和 Robot-B 的轨迹在走廊有时空重叠
- Robot-A 到达电梯口时,Robot-C 还在电梯里
4. 触发 Negotiation:
- Robot-A vs Robot-B:协商结果 → Robot-B 减速 2 秒,让 Robot-A 先过
- Robot-A vs 电梯:Robot-A 到达电梯口后等待 Robot-C 出来
5. 更新所有轨迹
6. 执行
这个过程中没有任何机器人"急刹车"或"碰撞后停下"。所有协调都是提前完成的。
七、rmf_traffic:RMF 最核心的模块
rmf_traffic 是 Open-RMF 的交通调度核心,也是整个系统最有技术含量的部分。
如果说 openTCS 的核心是 Scheduler(资源锁),那么:
RMF 的灵魂就是 rmf_traffic。
7.1 rmf_traffic 的职责
| 职责 | 说明 |
|---|---|
| 轨迹管理 | 维护所有机器人的计划轨迹 |
| Schedule | 全局时空调度表 |
| 冲突检测 | 检测轨迹之间的时空冲突 |
| Negotiation | 冲突协商机制 |
| 交通图 | 维护导航图(多层) |
7.2 Schedule:全局时空调度表
Schedule 是 rmf_traffic 的核心数据结构。它维护了:
所有机器人当前和未来的计划轨迹。
你可以把它想象成一个"时空预约表":
Schedule:
Robot-A: [(P1, 10:00), (P2, 10:05), (P3, 10:10)]
Robot-B: [(P4, 10:00), (P2, 10:07), (P5, 10:12)]
Robot-C: [(P6, 10:00), (P7, 10:03), (P8, 10:08)]
任何机器人要移动,都必须先把自己的计划轨迹提交到 Schedule。
系统会检查这条新轨迹是否与已有轨迹冲突。
7.3 为什么需要全局 Schedule?
因为冲突检测需要全局信息。
如果每台机器人只知道自己的轨迹,它无法判断是否会和别人冲突。只有一个全局的 Schedule,才能做到:
-
任何新轨迹提交时,立即检测与所有已有轨迹的冲突
-
任何轨迹更新时,重新检测受影响的冲突
-
任何机器人完成任务时,从 Schedule 中移除其轨迹
7.4 Schedule 和资源锁的区别
| 维度 | 资源锁(openTCS) | Schedule(RMF) |
|---|---|---|
| 锁定对象 | 空间(节点/边) | 时空(位置+时间段) |
| 粒度 | 整个资源 | 特定时间窗口 |
| 释放条件 | 车辆离开 | 时间窗口过期 |
| 并发性 | 同一资源同时只能一个车 | 同一位置不同时间可以多个车 |
| 效率 | 保守(可能过度等待) | 精确(只在真正冲突时协调) |
八、RMF 为什么强调"时间窗预约"?
传统 AGV 系统的资源锁是"二值"的:
资源状态:空闲 / 占用
这很简单,但也很浪费。
8.1 资源锁的浪费
考虑一条 100 米长的走廊:
传统方式:
Robot-A 进入走廊 → 整条走廊被锁定
Robot-A 走完 100 米需要 60 秒
→ 其他机器人必须等 60 秒
即使 Robot-B 只是想在 Robot-A 后面 30 秒进入走廊
(两者永远不会相遇)
也必须等待
这就是资源锁的根本问题:
它锁定的是"空间",而不是"时空"。
8.2 时间窗预约的思想
RMF 的做法是:
Robot-A 预约:走廊 @ 10:00-10:60
Robot-B 想预约:走廊 @ 10:30-11:30
系统分析:
- Robot-A 在 10:30 时已经走到走廊中段
- Robot-B 在 10:30 从走廊入口进入
- 两者方向相同,Robot-B 在后面
- 安全距离始终满足
→ 批准 Robot-B 的预约
结果:两台机器人可以"同时"使用走廊,只要它们在时空上不冲突。
8.3 时间窗预约的精确含义
“时间窗预约"不是简单的"预约一个时间段”。它是:
预约一条完整的时空轨迹。
即:
不是:"我要在 10:00-10:60 使用走廊"
而是:"我会在 10:00 在走廊入口,10:30 在走廊中点,10:60 在走廊出口"
这样系统可以精确判断:两条轨迹在任意时刻的空间距离是否安全。
8.4 时间窗预约的实现挑战
| 挑战 | 说明 |
|---|---|
| 时间估计准确性 | 如果机器人实际比预计慢,预约就不准了 |
| 动态更新 | 机器人速度变化时需要更新预约 |
| 计算复杂度 | 检测两条连续轨迹的冲突比检测两个点的冲突复杂得多 |
| 预约失效 | 机器人故障时,其预约需要被清除 |
RMF 通过持续监控和动态重规划来应对这些挑战。
九、时间窗(Time Window)思想的深入理解
9.1 轨迹的数学表示
在 RMF 中,一条轨迹(Trajectory)是一系列带时间戳的路径点:
Trajectory = [(x1, y1, t1), (x2, y2, t2), ..., (xn, yn, tn)]
两个路径点之间,机器人被假设为匀速直线运动(或按某种插值方式运动)。
因此,在任意时刻 t,机器人的位置可以通过插值计算出来。
9.2 冲突的数学定义
两条轨迹 A 和 B 冲突,当且仅当:
存在某个时刻 t,使得 distance(A(t), B(t)) < safety_threshold
其中:
-
A(t) 是机器人 A 在时刻 t 的位置
-
B(t) 是机器人 B 在时刻 t 的位置
-
safety_threshold 是安全距离(考虑机器人尺寸)
9.3 为什么这比"同一节点"检测更强大?
传统系统的冲突检测:
if robot_A.current_node == robot_B.current_node:
conflict!
这只能检测"已经发生"的冲突。
RMF 的冲突检测:
for t in future_time_range:
if distance(A(t), B(t)) < threshold:
conflict at time t!
这可以检测"未来将要发生"的冲突。
9.4 时间窗的实际意义
时间窗让系统可以做出更精细的决策:
场景:窄走廊,只能单向通行
传统方式:# 一、为什么传统 AGV 调度正在遇到瓶颈?
过去很多 AGV 系统,本质上是:
固定路线
+ 中央调度
+ 路径占用
+ 区域锁
这种模式在工厂、仓储、固定站点运输中非常有效。它的核心假设是:
“机器人走固定路线,环境基本不变,冲突可以通过锁来解决。”
在这个假设下,系统设计可以非常简洁:画好路网,定义好资源,谁先申请谁先走。这就是 openTCS 那一代系统的核心逻辑。
但随着 AMR(Autonomous Mobile Robot,自主移动机器人)的普及,这个假设开始崩塌。
1.1 AMR 和传统 AGV 的本质区别
传统 AGV:
-
沿磁条/二维码/固定轨道行驶
-
路径在部署时就确定了
-
不会"自己决定"走哪条路
-
环境变化时需要人工重新布线
AMR:
-
自主导航,不依赖固定轨道
-
可以动态避障
-
可以自己规划路径
-
环境变化时可以自适应
这意味着:
| 特性 | 传统 AGV | AMR |
|---|---|---|
| 路径 | 固定 | 动态 |
| 环境 | 静态 | 动态 |
| 避障 | 停车等待 | 绕行 |
| 部署 | 需要改造场地 | 即插即用 |
| 灵活性 | 低 | 高 |
1.2 传统调度模式为什么不适合 AMR?
传统 AGV 调度的核心是"资源锁":
路段 A-B 被 AGV-1 占用
→ AGV-2 不能进入
→ 等待释放
这在固定路网里没问题。但 AMR 的路径是动态的:
AMR-1 可能走路线 A
也可能走路线 B
也可能临时绕行路线 C
如果还用传统资源锁:
-
锁什么?路径都不确定,锁哪段路?
-
锁多久?AMR 可能随时改路线,锁了的资源可能白锁
-
怎么释放?AMR 绕行了,原来锁的路段要不要释放?
更严重的问题是:
AMR 数量越来越多,场景越来越复杂。
现代建筑里可能同时有:
-
配送机器人(送药、送餐)
-
清洁机器人(扫地、消毒)
-
巡检机器人(安防、设备检查)
-
搬运机器人(物流、仓储)
这些机器人:
-
来自不同厂家
-
使用不同导航方式
-
有不同的运动能力
-
共享同一个物理空间
传统 AGV 调度系统根本没有设计来处理这种复杂度。
1.3 新场景带来的新挑战
| 挑战 | 说明 |
|---|---|
| 多楼层 | 机器人需要坐电梯 |
| 动态环境 | 人流、临时障碍物 |
| 异构机器人 | 不同品牌、不同能力 |
| 共享设施 | 电梯、门禁、走廊 |
| 实时性 | 不能等几秒再决策 |
| 规模 | 几十甚至上百台机器人 |
这些挑战叠加在一起,传统"固定路网 + 资源锁"的模式就力不从心了。
于是:
Open-RMF(Open Robotics Middleware Framework)出现了。
它不是对传统 AGV 调度的"升级",而是一种全新的系统设计哲学。
二、什么是 Open-RMF?
Open-RMF 是由 Open Robotics(ROS 的维护组织)联合多家机器人企业共同推动的开源项目。
它的全称是 Open Robotics Middleware Framework,但这个名字容易让人误解为"只是一个中间件"。实际上它是:
一个完整的多机器人协同基础设施。
2.1 Open-RMF 的定位
它的目标不是:
“控制一台机器人怎么走。”
而是:
“让整个建筑里所有机器人能够和平共处、协同工作。”
你可以把它想象成:
一个城市的交通管理局。
它不管每辆车的发动机怎么工作,不管每辆车的方向盘怎么转。它管的是:
-
这条路现在谁能走
-
这个路口谁先过
-
这个电梯现在归谁用
-
这扇门什么时候开
-
如果两辆车要撞上了怎么办
2.2 Open-RMF 管理的对象
| 对象 | 说明 |
|---|---|
| 机器人 | 各种品牌、各种类型 |
| 电梯 | 调度、锁定、楼层控制 |
| 自动门 | 开关控制、通行权 |
| 走廊/通道 | 通行调度 |
| 充电桩 | 资源分配 |
| 任务 | 配送、巡检、清洁等 |
注意:Open-RMF 不只管机器人。它管理的是整个建筑的"可移动资源"和"共享设施"。
2.3 Open-RMF 的技术栈
Open-RMF 构建在 ROS 2 之上:
ROS 2(通信基础)
↓
DDS(分布式数据服务)
↓
Open-RMF 各模块
↓
Fleet Adapter(对接各品牌机器人)
它使用 ROS 2 的通信机制(Topic、Service、Action),但在其上构建了完整的调度逻辑。
2.4 Open-RMF 的核心模块
| 模块 | 职责 |
|---|---|
| rmf_traffic | 交通调度核心:轨迹管理、冲突检测、协商 |
| rmf_task | 任务管理:任务定义、分配、状态跟踪 |
| rmf_fleet_adapter | 车队适配器:对接不同品牌机器人 |
| rmf_building_map | 建筑地图:多楼层、电梯、门 |
| rmf_lift / rmf_door | 设施联动:电梯和门的控制 |
| rmf_visualization | 可视化:Web 界面监控 |
三、Open-RMF 和 openTCS 的核心区别
很多人第一次接触 RMF 时,会误以为:
“它只是另一个 AGV 调度系统。”
实际上,它们代表了两种完全不同的系统设计哲学。
3.1 设计哲学对比
| 维度 | openTCS | Open-RMF |
|---|---|---|
| 核心思想 | 资源锁 + 路径占用 | 时空轨迹 + 动态协商 |
| 路径模型 | 固定图拓扑 | 动态轨迹 |
| 冲突解决 | 先到先得(锁) | 协商(Negotiation) |
| 时间维度 | 弱(主要看空间) | 强(时空一体) |
| 环境假设 | 静态 | 动态 |
| 机器人类型 | 同构为主 | 异构为主 |
| 设施联动 | 不涉及 | 电梯、门、充电桩 |
| 地图 | 单层 | 多楼层 |
| 适用场景 | 工厂/仓库 | 医院/写字楼/商场 |
3.2 一个具体例子说明区别
场景:两台机器人要经过同一段走廊。
openTCS 的处理方式:
走廊是一个 Path 资源
Robot-A 先申请 → 获得锁 → 通过
Robot-B 申请 → 被拒绝 → 等待
Robot-A 离开走廊 → 释放锁
Robot-B 获得锁 → 通过
问题:如果走廊很长(比如 50 米),Robot-A 走完需要 30 秒。Robot-B 必须等 30 秒。即使 Robot-B 比 Robot-A 晚 2 秒进入,两者根本不会相遇。
Open-RMF 的处理方式:
Robot-A 的轨迹:走廊入口 @ 10:00 → 走廊出口 @ 10:30
Robot-B 的轨迹:走廊入口 @ 10:02 → 走廊出口 @ 10:32
系统分析:
- 两者方向相同
- Robot-B 始终在 Robot-A 后面
- 安全距离满足
→ 无冲突,两者同时通行
结果:RMF 的方案效率更高,因为它理解"时间"。
3.3 本质区别的一句话总结
openTCS 问的是:
“这个空间现在有没有人?”
Open-RMF 问的是:
“这个空间在未来某个时刻会不会有人?”
这就是从"空间调度"到"时空调度"的跨越。
四、RMF 的核心思想:机器人共享"时空"
Open-RMF 最重要的思想是:
“机器人冲突不仅发生在空间,也发生在时间。”
4.1 什么是"时空冲突"?
两台机器人"冲突"的定义不是"它们在同一个位置",而是"它们在同一时刻出现在同一个位置"。
这个区别看起来微小,但对系统设计的影响是巨大的。
传统系统只考虑空间:
位置 P 被占用 → 其他车不能进
RMF 考虑时空:
位置 P 在 10:05-10:08 被占用 → 其他车在这个时间段不能进
但 10:09 之后可以进
4.2 为什么时空视角更高效?
考虑一个十字路口:
纯空间视角:
路口被 Robot-A 占用
Robot-B、C、D 全部等待
直到 Robot-A 完全离开
时空视角:
Robot-A 在 10:00-10:03 经过路口
Robot-B 计划 10:05 到达路口
→ 不冲突,Robot-B 不需要等待
Robot-C 计划 10:02 到达路口
→ 冲突!Robot-C 需要调整(减速/等待/换路)
时空视角下,只有真正会在同一时刻相遇的机器人才需要协调。其他机器人可以正常通行。
4.3 时空路径规划的数学表示
在 RMF 中,一条路径不是:
[P1, P2, P3, P4] (纯空间序列)
而是:
[(P1, t1), (P2, t2), (P3, t3), (P4, t4)] (时空序列)
其中 ti 表示机器人到达 Pi 的时间。
这样,两条路径是否冲突,就变成了一个几何问题:
两条时空轨迹是否在某个时刻距离过近?
4.4 时空规划的优势
| 优势 | 说明 |
|---|---|
| 更高吞吐量 | 不需要为"可能的冲突"过度等待 |
| 更少停车 | 只在真正冲突时才协调 |
| 更好的预测性 | 可以提前知道未来会发生什么 |
| 更灵活的协调 | 可以通过调整时间而不是路径来解决冲突 |
4.5 时空规划的代价
| 代价 | 说明 |
|---|---|
| 计算复杂度高 | 维度从 2D 变成 3D(x, y, t) |
| 需要准确的时间估计 | 如果机器人实际速度和预测不符,计划就会失效 |
| 需要持续更新 | 机器人实际执行和计划有偏差时,需要重新规划 |
| 系统复杂度高 | 比简单的资源锁难实现得多 |
五、RMF 的整体架构
典型 RMF 架构:
+------------------+
| Web Dashboard | ← 运维监控界面
+------------------+
|
v
+----------------------------------------------------------+
| Open-RMF Core |
|----------------------------------------------------------|
| rmf_traffic | 交通调度:轨迹、冲突、协商 |
| rmf_task | 任务管理:分配、状态、优先级 |
| rmf_building_map | 建筑地图:多层、连接、设施 |
+----------------------------------------------------------+
/ | | \
/ | | \
v v v v
+--------+ +--------+ +---------+ +----------+
| Fleet | | Fleet | | Lift | | Door |
|Adapter | |Adapter | | Adapter | | Adapter |
| (A) | | (B) | | | | |
+--------+ +--------+ +---------+ +----------+
| | | |
v v v v
Robot-A Robot-B Elevator Auto-Door
(品牌X) (品牌Y)
5.1 架构的关键特点
1. 适配器模式(Adapter Pattern)
RMF 不直接控制任何设备。所有设备都通过 Adapter 接入。这意味着:
-
添加新品牌机器人 = 写一个新的 Fleet Adapter
-
添加新电梯 = 写一个新的 Lift Adapter
-
核心调度逻辑完全不需要改
2. 分布式架构
RMF 基于 ROS 2 的 DDS 通信,天然是分布式的:
-
各个 Adapter 可以运行在不同机器上
-
核心调度可以独立部署
-
没有严格的"中央服务器"概念
3. 建筑级视角
RMF 的设计单位不是"一个房间"或"一个楼层",而是"整栋建筑"。它天然考虑:
-
多楼层
-
楼层间的连接(电梯)
-
区域间的隔离(门)
5.2 信息流
一个典型的任务执行流程:
1. 用户通过 Dashboard 下发任务:"把药品从药房送到 3 楼 302 病房"
2. rmf_task 接收任务,分析任务类型(配送),确定需要:
- 一台配送机器人
- 使用电梯从 1 楼到 3 楼
- 经过 302 病房的门
3. rmf_task 选择合适的机器人(考虑位置、电量、当前任务)
4. rmf_traffic 为该机器人规划时空轨迹:
- 1楼药房 → 电梯口(避开其他机器人)
- 等待电梯
- 乘电梯到 3 楼
- 3楼电梯口 → 302 病房(避开其他机器人)
5. rmf_traffic 检测轨迹是否与其他机器人冲突
- 如果冲突 → 触发 Negotiation
- 协商出双方都可接受的方案
6. Fleet Adapter 将指令翻译为具体机器人能理解的命令
7. Lift Adapter 在适当时机调度电梯
8. Door Adapter 在机器人到达时开门
9. 机器人执行,持续上报状态
10. rmf_traffic 持续监控实际轨迹与计划轨迹的偏差
- 如果偏差过大 → 重新规划
六、多车协同:RMF 为什么比传统 AGV 更复杂?
传统 AGV 的多车协同相对简单:
任务 → 路径 → 申请资源 → 获得锁 → 执行
而 RMF 的多车协同是一个持续的、动态的过程:
任务
↓
规划时空轨迹
↓
提交轨迹到全局 Schedule
↓
持续检测冲突
↓
发现冲突 → 触发 Negotiation
↓
协商出新方案
↓
更新轨迹
↓
继续执行
↓
(循环:持续检测、持续调整)
6.1 为什么是"持续"的?
因为 AMR 的世界是动态的:
-
机器人实际速度可能和预测不同(地面摩擦、电量变化)
-
新任务随时可能到来
-
机器人可能突然故障
-
人可能突然出现在走廊里
-
电梯可能被人占用
因此,RMF 不是"规划一次就完事",而是"持续规划、持续调整"。
6.2 传统 AGV vs RMF 的协同策略对比
| 维度 | 传统 AGV | Open-RMF |
|---|---|---|
| 冲突发现时机 | 申请资源时 | 轨迹提交时(提前预测) |
| 冲突解决方式 | 等待 | 协商(可能双方都调整) |
| 调整粒度 | 停/走 | 减速/加速/换路/等待 |
| 动态性 | 低(一旦获得锁就执行到底) | 高(随时可能重新规划) |
| 信息利用 | 当前状态 | 未来轨迹预测 |
6.3 一个具体的多车协同场景
场景:医院走廊,3 台机器人
Robot-A:从药房出发,要去 3 楼(需要坐电梯)
Robot-B:在 1 楼巡检,即将经过电梯口
Robot-C:从 3 楼下来,正在电梯里
RMF 的处理:
1. Robot-A 规划轨迹:药房 → 走廊 → 电梯口 → 等电梯 → 上 3 楼
2. 提交轨迹到 Schedule
3. 系统检测到:
- Robot-A 和 Robot-B 的轨迹在走廊有时空重叠
- Robot-A 到达电梯口时,Robot-C 还在电梯里
4. 触发 Negotiation:
- Robot-A vs Robot-B:协商结果 → Robot-B 减速 2 秒,让 Robot-A 先过
- Robot-A vs 电梯:Robot-A 到达电梯口后等待 Robot-C 出来
5. 更新所有轨迹
6. 执行
这个过程中没有任何机器人"急刹车"或"碰撞后停下"。所有协调都是提前完成的。
七、rmf_traffic:RMF 最核心的模块
rmf_traffic 是 Open-RMF 的交通调度核心,也是整个系统最有技术含量的部分。
如果说 openTCS 的核心是 Scheduler(资源锁),那么:
RMF 的灵魂就是 rmf_traffic。
7.1 rmf_traffic 的职责
| 职责 | 说明 |
|---|---|
| 轨迹管理 | 维护所有机器人的计划轨迹 |
| Schedule | 全局时空调度表 |
| 冲突检测 | 检测轨迹之间的时空冲突 |
| Negotiation | 冲突协商机制 |
| 交通图 | 维护导航图(多层) |
7.2 Schedule:全局时空调度表
Schedule 是 rmf_traffic 的核心数据结构。它维护了:
所有机器人当前和未来的计划轨迹。
你可以把它想象成一个"时空预约表":
Schedule:
Robot-A: [(P1, 10:00), (P2, 10:05), (P3, 10:10)]
Robot-B: [(P4, 10:00), (P2, 10:07), (P5, 10:12)]
Robot-C: [(P6, 10:00), (P7, 10:03), (P8, 10:08)]
任何机器人要移动,都必须先把自己的计划轨迹提交到 Schedule。
系统会检查这条新轨迹是否与已有轨迹冲突。
7.3 为什么需要全局 Schedule?
因为冲突检测需要全局信息。
如果每台机器人只知道自己的轨迹,它无法判断是否会和别人冲突。只有一个全局的 Schedule,才能做到:
-
任何新轨迹提交时,立即检测与所有已有轨迹的冲突
-
任何轨迹更新时,重新检测受影响的冲突
-
任何机器人完成任务时,从 Schedule 中移除其轨迹
7.4 Schedule 和资源锁的区别
| 维度 | 资源锁(openTCS) | Schedule(RMF) |
|---|---|---|
| 锁定对象 | 空间(节点/边) | 时空(位置+时间段) |
| 粒度 | 整个资源 | 特定时间窗口 |
| 释放条件 | 车辆离开 | 时间窗口过期 |
| 并发性 | 同一资源同时只能一个车 | 同一位置不同时间可以多个车 |
| 效率 | 保守(可能过度等待) | 精确(只在真正冲突时协调) |
八、RMF 为什么强调"时间窗预约"?
传统 AGV 系统的资源锁是"二值"的:
资源状态:空闲 / 占用
这很简单,但也很浪费。
8.1 资源锁的浪费
考虑一条 100 米长的走廊:
传统方式:
Robot-A 进入走廊 → 整条走廊被锁定
Robot-A 走完 100 米需要 60 秒
→ 其他机器人必须等 60 秒
即使 Robot-B 只是想在 Robot-A 后面 30 秒进入走廊
(两者永远不会相遇)
也必须等待
这就是资源锁的根本问题:
它锁定的是"空间",而不是"时空"。
8.2 时间窗预约的思想
RMF 的做法是:
Robot-A 预约:走廊 @ 10:00-10:60
Robot-B 想预约:走廊 @ 10:30-11:30
系统分析:
- Robot-A 在 10:30 时已经走到走廊中段
- Robot-B 在 10:30 从走廊入口进入
- 两者方向相同,Robot-B 在后面
- 安全距离始终满足
→ 批准 Robot-B 的预约
结果:两台机器人可以"同时"使用走廊,只要它们在时空上不冲突。
8.3 时间窗预约的精确含义
“时间窗预约"不是简单的"预约一个时间段”。它是:
预约一条完整的时空轨迹。
即:
不是:"我要在 10:00-10:60 使用走廊"
而是:"我会在 10:00 在走廊入口,10:30 在走廊中点,10:60 在走廊出口"
这样系统可以精确判断:两条轨迹在任意时刻的空间距离是否安全。
8.4 时间窗预约的实现挑战
| 挑战 | 说明 |
|---|---|
| 时间估计准确性 | 如果机器人实际比预计慢,预约就不准了 |
| 动态更新 | 机器人速度变化时需要更新预约 |
| 计算复杂度 | 检测两条连续轨迹的冲突比检测两个点的冲突复杂得多 |
| 预约失效 | 机器人故障时,其预约需要被清除 |
RMF 通过持续监控和动态重规划来应对这些挑战。
九、时间窗(Time Window)思想的深入理解
9.1 轨迹的数学表示
在 RMF 中,一条轨迹(Trajectory)是一系列带时间戳的路径点:
Trajectory = [(x1, y1, t1), (x2, y2, t2), ..., (xn, yn, tn)]
两个路径点之间,机器人被假设为匀速直线运动(或按某种插值方式运动)。
因此,在任意时刻 t,机器人的位置可以通过插值计算出来。
9.2 冲突的数学定义
两条轨迹 A 和 B 冲突,当且仅当:
存在某个时刻 t,使得 distance(A(t), B(t)) < safety_threshold
其中:
-
A(t) 是机器人 A 在时刻 t 的位置
-
B(t) 是机器人 B 在时刻 t 的位置
-
safety_threshold 是安全距离(考虑机器人尺寸)
9.3 为什么这比"同一节点"检测更强大?
传统系统的冲突检测:
if robot_A.current_node == robot_B.current_node:
conflict!
这只能检测"已经发生"的冲突。
RMF 的冲突检测:
for t in future_time_range:
if distance(A(t), B(t)) < threshold:
conflict at time t!
这可以检测"未来将要发生"的冲突。
9.4 时间窗的实际意义
时间窗让系统可以做出更精细的决策:
场景:窄走廊,只能单向通行
传统方式:
Robot-A 进入走廊 → 锁定整条走廊
Robot-B 想从对面进入 → 等待
Robot-A 走完(60秒)→ 释放
Robot-B 进入
总耗时:120秒(串行)
RMF 方式:
```text
Robot-A 预约:走廊正向 @ 10:00-10:60
Robot-B 想预约:走廊反向 @ 10:30
系统分析:
- 如果 Robot-B 在 10:30 从对面进入,会和 Robot-A 在走廊中段相遇
- 冲突!
但系统还可以分析:
- 如果 Robot-B 等到 10:65 再进入(Robot-A 已离开)
- 或者 Robot-B 绕行另一条路,10:30 就能到达目的地
系统选择代价更小的方案。
关键区别:
传统方式只有"等"这一个选项。RMF 可以评估多种方案,选择全局代价最小的。
9.5 时间窗与"弹性"
时间窗还带来一个重要特性:弹性调度。
Robot-A 原计划 10:00 出发
但如果 Robot-A 延迟 5 秒出发(10:05)
就能完全避开 Robot-B 的轨迹
不需要任何人绕路
→ 系统选择让 Robot-A 晚出发 5 秒
这种"通过微调时间来避免空间冲突"的能力,是纯空间调度系统做不到的。
在工业场景里,这种微调往往比绕路更高效:
-
绕路:多走 50 米,多花 30 秒
-
延迟出发:等 5 秒,路径不变
时间窗让系统有了这种"时间维度的调整空间"。
十、冲突检测(Conflict Detection)
rmf_traffic 会持续检测所有已提交轨迹之间的冲突。
10.1 冲突检测的输入
输入:
- Schedule 中所有活跃轨迹
- 新提交的轨迹
- 安全距离参数
- 机器人尺寸信息
10.2 冲突检测的类型
RMF 检测的冲突不只是"两车相撞",而是多种类型:
| 冲突类型 | 说明 | 示例 |
|---|---|---|
| 正面冲突 | 两车迎面相遇 | 窄走廊对向行驶 |
| 追尾冲突 | 后车追上前车 | 快车追慢车 |
| 交叉冲突 | 路径在某点交叉 | 十字路口 |
| 资源冲突 | 同时需要同一设施 | 两车同时要用电梯 |
| 停靠冲突 | 目标位置被占用 | 两车要去同一个充电桩 |
10.3 连续轨迹冲突检测 vs 离散点检测
传统系统通常做离散检测:
每隔 1 秒检查一次:两车是否在同一节点
问题:如果两车在两次检查之间交错通过同一点,就检测不到。
t=10: Robot-A 在 P1,Robot-B 在 P3 → 无冲突
t=11: Robot-A 在 P3,Robot-B 在 P1 → 无冲突
但实际上在 t=10.5 时,两车在 P2 相遇了!
RMF 做的是连续轨迹检测:
将两条轨迹视为连续曲线
计算两条曲线之间的最小距离
如果最小距离 < 安全阈值 → 冲突
这在数学上更复杂,但保证不会遗漏任何冲突。
10.4 冲突检测的计算效率
如果有 N 台机器人,朴素的冲突检测需要 O(N²) 次两两比较。
RMF 通过以下方式优化:
-
空间索引:只检测空间上可能相遇的轨迹对
-
时间过滤:时间段不重叠的轨迹不可能冲突
-
增量检测:新轨迹只需要和已有轨迹比较,不需要全部重新计算
-
区域划分:不同区域的机器人不可能冲突
十一、RMF 的冲突检测为什么更先进?
11.1 从"被动响应"到"主动预测"
传统 AGV 的冲突处理是被动的:
传感器检测到前方有障碍物
→ 停车
→ 等待
→ 障碍物消失
→ 继续
这种方式的问题:
-
已经浪费了时间:车已经走到冲突点才发现问题
-
可能导致死锁:两车面对面停下,谁都走不了
-
无法全局优化:每辆车只看到自己前方的情况
RMF 的冲突处理是主动的:
规划阶段就预测未来所有可能的冲突
→ 在出发前就解决冲突
→ 机器人按照无冲突的轨迹执行
→ 几乎不需要紧急停车
11.2 预测式调度的优势
| 维度 | 被动响应 | 主动预测 |
|---|---|---|
| 停车次数 | 多 | 少 |
| 系统吞吐量 | 低 | 高 |
| 死锁风险 | 高 | 低 |
| 能耗 | 高(频繁启停) | 低(平滑运动) |
| 可预测性 | 低 | 高 |
| 用户体验 | 差(机器人经常停) | 好(机器人流畅运动) |
11.3 预测式调度的前提条件
要做到"主动预测",系统需要:
-
准确的运动模型:知道机器人的速度、加速度、转弯半径
-
全局信息:知道所有机器人的计划
-
实时更新:计划变化时立即重新检测
-
快速计算:冲突检测必须在毫秒级完成
这些条件在传统 AGV 时代很难满足(通信慢、计算弱)。但在现代系统中(ROS 2 + 高性能计算),已经完全可行。
11.4 RMF 如何处理预测不准的情况?
预测永远不可能 100% 准确。机器人可能:
-
比预计慢(地面湿滑、电量低)
-
比预计快(下坡)
-
临时停下(遇到行人)
-
完全偏离计划(故障)
RMF 的应对策略:
层级 1:容差设计
- 安全距离设置得比物理最小距离大
- 时间窗留有余量
- 即使有小偏差也不会真正冲突
层级 2:持续监控
- 实时比较实际轨迹和计划轨迹
- 偏差超过阈值 → 更新 Schedule → 重新检测冲突
层级 3:紧急处理
- 如果预测完全失效(机器人突然停下)
- 触发紧急重规划
- 其他机器人立即调整
这是一个"乐观规划 + 持续修正"的策略:
先假设一切按计划进行,但持续监控,一旦偏差过大就立即修正。
十二、Negotiation:RMF 最核心的思想
当冲突检测发现两条轨迹冲突时,系统需要解决这个冲突。
传统 AGV 的解决方式很简单:
优先级高的先走,优先级低的等待。
RMF 的解决方式更复杂也更高效:
Negotiation(协商)
12.1 为什么不能简单用优先级?
优先级方式的问题:
场景:
Robot-A(高优先级)要从东到西
Robot-B(低优先级)要从南到北
两者在十字路口冲突
优先级方式:Robot-B 停下等待
但如果:
- Robot-B 只需要减速 2 秒就能避开 Robot-A
- 而停下等待需要 15 秒(等 Robot-A 完全通过)
优先级方式浪费了 13 秒。
更极端的情况:
Robot-A(高优先级)要经过一条长走廊
Robot-B(低优先级)在走廊中间
优先级方式:Robot-B 必须让路
但 Robot-B 可能需要倒退 30 米才能让出空间
而 Robot-A 只需要等 5 秒,Robot-B 就自然走过去了
优先级方式反而更慢。
12.2 Negotiation 的核心思想
Negotiation 的本质是:
让冲突双方各自提出"如果我让步,代价是多少",然后选择全局代价最小的方案。
过程:
1. 检测到冲突:Robot-A 和 Robot-B 的轨迹冲突
2. 系统向双方发起协商:
- 问 Robot-A:"如果你让步(等待/绕路),代价是多少?"
- 问 Robot-B:"如果你让步(等待/绕路),代价是多少?"
3. Robot-A 回复:
- 方案 A1:等待 5 秒,代价 = 5
- 方案 A2:绕路,代价 = 20
4. Robot-B 回复:
- 方案 B1:等待 3 秒,代价 = 3
- 方案 B2:绕路,代价 = 15
5. 系统选择全局代价最小的组合:
- Robot-B 等待 3 秒(代价 3)< Robot-A 等待 5 秒(代价 5)
→ 选择让 Robot-B 等待
12.3 Negotiation 的多轮协商
实际情况可能更复杂。一次协商可能涉及多台机器人:
Robot-A 和 Robot-B 冲突
→ Robot-B 决定绕路
→ 但 Robot-B 的新路径和 Robot-C 冲突
→ 需要 Robot-B 和 Robot-C 再协商
→ Robot-C 决定等待
→ 但 Robot-C 等待会导致 Robot-D 追尾
→ ...
这就是为什么 Negotiation 可能需要多轮:
Round 1: A vs B → B 让步
Round 2: B(new path) vs C → C 让步
Round 3: C(waiting) vs D → D 减速
...
直到所有冲突解决
12.4 Negotiation 的终止条件
协商不能无限进行。RMF 设置了终止条件:
| 条件 | 处理方式 |
|---|---|
| 找到无冲突方案 | 正常结束,执行方案 |
| 达到最大轮数 | 使用当前最优方案 |
| 超时 | 回退到优先级方式 |
| 无解 | 某台机器人原地等待 |
12.5 Negotiation vs 中央决策
有人可能会问:
“为什么不直接由中央系统决定谁让步?为什么要’协商’?”
原因是:
-
信息分布:每台机器人最了解自己的"让步代价"。中央系统可能不知道某台机器人绕路需要多少额外时间。
-
可扩展性:如果所有决策都由中央做,中央会成为瓶颈。协商可以分散计算负载。
-
模块化:不同品牌的机器人可能有不同的"让步能力"(有的能倒退,有的不能)。通过协商,每台机器人可以根据自己的能力提出方案。
-
公平性:协商机制可以保证不会总是同一台机器人让步。
十三、Negotiation 的具体机制
13.1 协商的参与者
在 RMF 中,Negotiation 的参与者不是"机器人"本身,而是"Fleet Adapter"代表机器人参与协商。
冲突检测 → 识别冲突方
↓
Negotiation System
↓ ↓
Fleet Adapter A Fleet Adapter B
↓ ↓
提出替代方案 提出替代方案
↓ ↓
评估所有方案
↓
选择最优组合
↓
更新 Schedule
13.2 替代方案的生成
当一台机器人被要求"让步"时,它的 Fleet Adapter 会生成多个替代方案:
原始轨迹:P1 → P2 → P3 → P4(经过冲突点 P3)
替代方案 1:等待
P1 → P2 → [等待 5 秒] → P3 → P4
代价:+5 秒
替代方案 2:绕路
P1 → P2 → P5 → P6 → P4(绕开 P3)
代价:+15 秒
替代方案 3:减速
P1 → P2 → P3 → P4(但速度降低 30%)
代价:+8 秒
13.3 代价的计算
"代价"不只是时间。它可能包括:
| 代价因素 | 说明 |
|---|---|
| 时间延迟 | 任务完成时间推迟多少 |
| 能耗增加 | 绕路/启停带来的额外能耗 |
| 任务优先级 | 高优先级任务的延迟代价更大 |
| 连锁影响 | 这次让步是否会导致后续任务延迟 |
| 电量风险 | 绕路后电量是否还够完成任务 |
13.4 协商的公平性
为了避免"总是同一台机器人让步",RMF 的协商机制考虑了公平性:
-
历史让步次数
-
当前任务的紧急程度
-
机器人的"让步能力"(有的机器人绕路很容易,有的很难)
这保证了长期运行中,负担是均匀分布的。
十四、Negotiation 本质是什么?
从计算机科学的角度看,Negotiation 本质上是:
一个约束满足问题(CSP)的分布式求解过程。
14.1 形式化定义
给定:
- N 台机器人的计划轨迹 T1, T2, ..., TN
- 安全约束:任意两条轨迹不能时空冲突
- 每台机器人的替代轨迹集合 Alt(Ti)
求解:
- 为每台机器人选择一条轨迹 Ti' ∈ {Ti} ∪ Alt(Ti)
- 使得所有轨迹两两不冲突
- 且总代价最小
14.2 与博弈论的关系
Negotiation 也可以从博弈论角度理解:
-
每台机器人是一个"玩家"
-
每个玩家的"策略"是选择哪条轨迹
-
"收益"是负的代价(代价越小越好)
-
目标是找到一个"纳什均衡"或"社会最优"
RMF 更偏向"社会最优"(全局代价最小),而不是"纳什均衡"(每个人都不愿意单方面改变)。
因为在工业系统中:
全局效率比个体"公平"更重要。
14.3 与 MAPF 的关系
Negotiation 和学术界的 MAPF(Multi-Agent Path Finding)高度相关。
MAPF 的经典算法:
| 算法 | 思想 | 与 RMF 的关系 |
|---|---|---|
| CBS(Conflict-Based Search) | 发现冲突 → 分支 → 为冲突方生成约束 → 重新规划 | RMF 的 Negotiation 非常接近 CBS 的思想 |
| WHCA*(Windowed Hierarchical Cooperative A*) | 在时间窗口内协同规划 | RMF 的时间窗预约类似 |
| Priority-Based Planning | 按优先级顺序规划 | RMF 的回退策略 |
RMF 的 Negotiation 可以看作是 CBS 的工程化实现:
CBS 学术版:
1. 找到冲突
2. 为冲突双方分别添加约束
3. 在约束下重新规划
4. 递归直到无冲突
RMF 工程版:
1. 找到冲突
2. 让冲突双方各自提出替代方案
3. 选择全局代价最小的组合
4. 如果新方案引入新冲突,继续协商
核心思想一致,但 RMF 更注重:
-
实时性(不能算太久)
-
鲁棒性(算不出来要有兜底方案)
-
可扩展性(机器人数量增加时不能崩溃)
十五、RMF 和 MAPF 的关系
15.1 MAPF 是什么?
MAPF(Multi-Agent Path Finding)是人工智能和机器人学中的一个经典问题:
给定 N 个智能体,每个有起点和终点,在共享环境中为所有智能体找到无冲突路径,使得总代价最小。
这是一个 NP-hard 问题(当追求最优解时)。
15.2 MAPF 的主流算法
| 算法 | 核心思想 | 最优性 | 计算速度 |
|---|---|---|---|
| CBS | 冲突树搜索 | 最优 | 慢(指数级最坏情况) |
| ECBS | 有界次优 CBS | 有界次优 | 较快 |
| WHCA* | 时间窗口内协同 A* | 不保证最优 | 快 |
| Priority-Based | 按优先级顺序规划 | 不保证最优 | 很快 |
| ICTS | 递增代价树搜索 | 最优 | 慢 |
| LaCAM | 惰性约束添加 | 不保证最优 | 非常快 |
15.3 RMF 为什么不直接用 CBS?
CBS 是 MAPF 领域最著名的最优算法。但 RMF 没有直接采用它,原因是:
1. 实时性要求
CBS 在最坏情况下是指数级复杂度。工业系统不能接受"有时候算 1 毫秒,有时候算 10 分钟"。
RMF 需要的是:
保证在有限时间内给出一个"足够好"的方案。
2. 动态性
CBS 假设所有智能体同时开始规划。但现实中:
-
机器人随时加入和离开
-
新任务随时到来
-
环境随时变化
RMF 需要的是增量式规划:
新机器人加入时,只需要和已有轨迹协调,不需要全部重新规划。
3. 异构性
CBS 通常假设所有智能体能力相同。但 RMF 中:
-
不同机器人速度不同
-
不同机器人尺寸不同
-
不同机器人能力不同(有的能倒退,有的不能)
4. 鲁棒性
CBS 假设规划结果会被精确执行。但现实中机器人会偏离计划。
RMF 需要的是:
即使执行偏离计划,系统也能快速恢复。
15.4 RMF 的定位
RMF 不是一个"MAPF 求解器"。它是一个"多机器人协同系统"。
MAPF 是其中的一个子问题,但不是全部。
RMF 还需要处理:
-
任务分配
-
设施联动
-
异构机器人管理
-
故障恢复
-
人机共存
因此 RMF 选择了一种"工程化的 MAPF 方案":
不追求数学最优,但保证实时、稳定、可扩展。
十六、RMF 为什么不追求"绝对最优"?
这是一个非常重要的工程哲学问题。
16.1 "最优"在工业系统中的含义
学术论文中的"最优"通常指:
所有机器人的总路径长度(或总完成时间)最小。
但工业系统中的"最优"含义完全不同:
| 学术"最优" | 工业"最优" |
|---|---|
| 总路径最短 | 系统 7×24 不停机 |
| 理论最优解 | 99.9% 的时间能给出方案 |
| 全局重规划 | 局部调整不影响其他机器人 |
| 静态场景 | 动态变化时快速适应 |
16.2 为什么"足够好"比"最优"更重要?
场景 1:计算时间
最优方案:需要 5 秒计算
次优方案:需要 50 毫秒计算,比最优多走 2 米
在这 5 秒里,机器人在干什么?停着等。
停 5 秒的代价 >> 多走 2 米的代价。
场景 2:鲁棒性
最优方案:所有机器人精确按计划执行,总时间 100 秒
但如果任何一台机器人偏离计划 → 需要全部重新规划
次优方案:总时间 110 秒
但每台机器人有 5 秒的时间余量
即使有小偏差也不需要重新规划
工业系统选择次优方案,因为:
稳定运行 110 秒 > 理论上 100 秒但经常需要重规划。
场景 3:可维护性
最优算法:复杂的数学优化,只有博士能理解
次优算法:简单的启发式规则,任何工程师都能调试
当系统出问题时:
- 最优算法:需要请专家来分析
- 次优算法:运维工程师看日志就能定位问题
16.3 RMF 的设计原则
RMF 的设计原则可以总结为:
-
实时性优先于最优性:宁可方案差一点,也不能让机器人等太久
-
稳定性优先于效率:宁可慢一点,也不能系统崩溃
-
可恢复性优先于完美性:出了问题能快速恢复,比永远不出问题更现实
-
增量式优先于全局式:局部调整比全部重来更实用
这些原则贯穿了 RMF 的所有设计决策。
十七、时空路径规划(Spatio-temporal Planning)
17.1 从空间路径到时空轨迹
传统路径规划的输出是:
Path = [P1, P2, P3, P4]
这只告诉你"经过哪些点",不告诉你"什么时候经过"。
RMF 的路径规划输出是:
Trajectory = [(P1, t1), (P2, t2), (P3, t3), (P4, t4)]
这告诉你"什么时候在哪里"。
17.2 为什么时间信息是必要的?
没有时间信息,就无法做冲突检测。
Robot-A 的路径:P1 → P2 → P3
Robot-B 的路径:P4 → P2 → P5
问题:两者都经过 P2,是否冲突?
没有时间信息 → 不知道!
- 如果 A 在 10:00 经过 P2,B 在 10:30 经过 P2 → 不冲突
- 如果 A 在 10:00 经过 P2,B 在 10:01 经过 P2 → 冲突
因此:
没有时间维度的路径规划,无法做精确的多机器人协调。
17.3 时空路径规划的搜索空间
传统 2D 路径规划的搜索空间是:
(x, y) → 二维平面
时空路径规划的搜索空间是:
(x, y, t) → 三维时空
这意味着:
-
搜索空间增加了一个维度
-
计算复杂度显著增加
-
但表达能力也显著增强
17.4 时空图(Time-expanded Graph)
一种常见的时空路径规划方法是"时空图展开":
原始图:
P1 -- P2 -- P3
时空展开(时间步长 = 1):
t=0: P1(0) -- P2(0) -- P3(0)
| | |
t=1: P1(1) -- P2(1) -- P3(1)
| | |
t=2: P1(2) -- P2(2) -- P3(2)
| | |
t=3: P1(3) -- P2(3) -- P3(3)
在这个展开图上:
-
水平边 = 移动(从一个位置到相邻位置)
-
垂直边 = 等待(在同一位置停留一个时间步)
路径规划变成在这个 3D 图上的搜索问题。
17.5 RMF 的时空规划方式
RMF 不完全使用离散的时空图展开(那样内存消耗太大)。它使用的是:
连续时间轨迹 + 约束检查
即:
-
先规划一条空间路径(不考虑其他机器人)
-
根据机器人速度,计算每个点的到达时间
-
检查这条时空轨迹是否与 Schedule 中的已有轨迹冲突
-
如果冲突 → 调整(等待/绕路/减速)
-
重复直到无冲突
这种方式比完整的时空图搜索更高效,因为它利用了"大多数情况下不冲突"这个事实。只有在真正冲突时才需要调整。
17.6 时空规划的实际效果
场景:5 台机器人同时在一层楼运行
传统方式(纯空间 + 资源锁):
- 走廊同时只能一台车 → 大量等待
- 十字路口锁定 → 排队通过
- 系统吞吐量:每小时完成 30 个任务
RMF 方式(时空规划):
- 走廊可以多台车同向通行(时空不冲突)
- 十字路口通过时间错开(不需要锁定整个路口)
- 系统吞吐量:每小时完成 50 个任务
提升来自于:
减少了"不必要的等待"。
只有真正会在同一时刻相遇的机器人才需要协调,其他情况都可以并行。
十八、RMF 的交通模型
18.1 核心数据结构
RMF 内部维护的交通模型由以下元素组成:
| 元素 | 含义 | 属性 |
|---|---|---|
| Waypoint | 导航点 | 坐标、楼层、是否可停靠 |
| Lane | 通道 | 连接两个 Waypoint、方向、速度限制 |
| Trajectory | 轨迹 | 带时间戳的 Waypoint 序列 |
| Schedule | 调度表 | 所有活跃轨迹的集合 |
| Participant | 参与者 | 每台机器人在 Schedule 中的身份 |
18.2 交通图(Navigation Graph)
RMF 的交通图和 openTCS 的图模型有相似之处,但也有关键区别:
相似点:
-
都是有向图
-
都有节点和边
-
都支持单向通行
区别:
| 维度 | openTCS | RMF |
|---|---|---|
| 层数 | 单层 | 多层 |
| 边的属性 | 代价/长度 | 速度限制/宽度/双向性 |
| 动态性 | 静态(部署时确定) | 可动态修改 |
| 资源语义 | 每条边是一个"资源" | 边本身不是资源,轨迹才是 |
18.3 多层交通图
RMF 的交通图天然支持多层:
Floor 1 Graph:
W1 -- W2 -- W3 -- W4(lift)
Floor 2 Graph:
W5(lift) -- W6 -- W7 -- W8
Cross-floor connection:
W4(lift, floor1) ←→ W5(lift, floor2)
transition_time: 30 seconds
楼层之间通过"电梯节点"连接。跨楼层的路径规划需要考虑:
-
电梯等待时间(不确定)
-
电梯容量(一次只能一台机器人)
-
电梯调度(可能需要等其他楼层的请求)
18.4 Trajectory 的详细结构
一条 Trajectory 包含:
Trajectory {
participant_id: "robot_A"
profile: {
footprint: circle(radius=0.3m) // 机器人占地面积
vicinity: circle(radius=0.5m) // 安全区域
}
segments: [
{position: (10.0, 5.0), time: 10:00:00, velocity: (1.0, 0.0)},
{position: (15.0, 5.0), time: 10:00:05, velocity: (1.0, 0.0)},
{position: (20.0, 5.0), time: 10:00:10, velocity: (0.0, 0.0)}, // 停靠
]
}
注意 profile 字段:它定义了机器人的物理尺寸和安全区域。冲突检测时,不是检测"两个点是否重合",而是检测"两个圆是否重叠"。
这意味着大型机器人(如叉车)和小型机器人(如配送机器人)的冲突检测阈值是不同的。
18.5 Schedule 的工作方式
Schedule 是所有活跃轨迹的集合:
Schedule {
trajectories: [
Trajectory(robot_A, ...),
Trajectory(robot_B, ...),
Trajectory(robot_C, ...),
]
// 操作:
submit(trajectory) // 提交新轨迹
update(trajectory) // 更新已有轨迹
remove(participant) // 移除某个参与者的轨迹
query(region, time) // 查询某区域某时间段的所有轨迹
}
每当有新轨迹提交或更新时,系统会自动检测冲突。
十九、多地图(Multi-map)
19.1 为什么需要多地图?
传统 AGV 系统通常运行在单一平面上:一个仓库、一个车间。
但现代机器人的运行环境往往是三维的:
-
医院:地下室(药房)→ 1楼(门诊)→ 2楼(病房)→ 3楼(手术室)
-
写字楼:1楼(大堂)→ 各楼层(办公区)
-
商场:B1(仓库)→ 1楼(店铺)→ 2楼(餐饮)
机器人需要在这些楼层之间移动,完成跨楼层的任务。
19.2 多地图的挑战
多地图不是简单地"把几张地图拼在一起"。它带来了一系列新问题:
| 挑战 | 说明 |
|---|---|
| 楼层间连接 | 如何表示电梯、楼梯、坡道? |
| 跨层路径规划 | 如何计算包含"坐电梯"的路径? |
| 电梯调度 | 电梯是共享资源,如何分配? |
| 时间不确定性 | 等电梯的时间无法精确预测 |
| 坐标系统 | 不同楼层的坐标系可能不同 |
| 地图更新 | 某层地图变了,其他层怎么办? |
19.3 RMF 的多地图模型
RMF 使用 rmf_building_map 来管理多层建筑:
Building {
name: "City Hospital"
levels: [
Level {
name: "B1"
elevation: -3.0m
nav_graph: Graph(...)
walls: [...]
doors: [...]
},
Level {
name: "L1"
elevation: 0.0m
nav_graph: Graph(...)
walls: [...]
doors: [...]
lifts: ["elevator_A", "elevator_B"]
},
Level {
name: "L2"
elevation: 4.0m
nav_graph: Graph(...)
walls: [...]
doors: [...]
lifts: ["elevator_A", "elevator_B"]
}
]
lifts: [
Lift {
name: "elevator_A"
levels: ["B1", "L1", "L2"]
position: (15.0, 20.0) // 在每层的位置
capacity: 1 // 一次一台机器人
}
]
}
19.4 跨层路径规划
当机器人需要从 B1 到 L2 时,路径规划分为几个阶段:
阶段 1:B1 层内路径
当前位置 → B1 电梯口
阶段 2:等待电梯
在 B1 电梯口等待(时间不确定)
阶段 3:乘坐电梯
B1 → L2(约 15 秒)
阶段 4:L2 层内路径
L2 电梯口 → 目标位置
其中阶段 2 的时间是不确定的,这给时空规划带来了挑战。RMF 的处理方式是:
-
估计一个"期望等待时间"
-
实际等待时间可能不同
-
一旦实际进入电梯,更新后续轨迹的时间
二十、RMF 如何管理多地图?
20.1 Graph + Transition 模型
RMF 使用"图 + 转换"的方式连接多层:
每层有自己的导航图(Graph)
层与层之间通过 Transition 连接
Transition 的属性:
- 类型:电梯 / 坡道 / 楼梯
- 连接的楼层
- 转换时间
- 容量限制
- 是否需要预约
20.2 电梯作为"瓶颈资源"
在多层建筑中,电梯是最关键的瓶颈资源:
-
容量有限(通常一次一台机器人)
-
速度有限(楼层间移动需要时间)
-
共享使用(可能和人共用)
-
调度复杂(多台机器人可能同时需要)
因此电梯调度本身就是一个优化问题:
场景:
Robot-A 在 B1,要去 L3
Robot-B 在 L1,要去 L2
Robot-C 在 L3,要去 B1
电梯当前在 L2
最优调度:
1. 电梯去 L3 接 Robot-C
2. Robot-C 下到 B1
3. 电梯去 B1 接 Robot-A(顺路)
4. Robot-A 上到 L3
5. 电梯去 L1 接 Robot-B
6. Robot-B 上到 L2
vs 先到先得:
1. Robot-A 先请求 → 电梯从 L2 去 B1 → 空跑
2. Robot-A 上到 L3
3. Robot-B 请求 → 电梯从 L3 去 L1 → 空跑
4. Robot-B 上到 L2
5. Robot-C 请求 → 电梯从 L2 去 L3 → 空跑
6. Robot-C 下到 B1
先到先得方式电梯空跑了很多。
20.3 机器人乘电梯的完整流程
这个流程看似简单,但每一步都可能出问题:
1. 机器人到达电梯口
→ 如果电梯口有其他机器人在等?排队。
2. 请求电梯
→ 通过 Lift Adapter 发送请求
→ 电梯可能在其他楼层,需要等待
3. 电梯到达,门打开
→ 如果电梯里有人/其他机器人?等下一趟。
4. 机器人进入电梯
→ 需要精确对准电梯门
→ 进入后确认位置
5. 请求目标楼层
→ 电梯开始移动
6. 到达目标楼层,门打开
→ 如果门口有障碍物?等待。
7. 机器人离开电梯
→ 离开后通知系统释放电梯
8. 切换到目标楼层的地图
→ 使用目标楼层的导航图继续规划
每一步都需要:
-
状态确认
-
超时处理
-
异常恢复
这就是为什么"机器人坐电梯"在工程上比想象中复杂得多。
二十一、电梯联动(Lift Integration)
21.1 为什么电梯联动是工程难题?
电梯联动不只是"按按钮"那么简单。它涉及:
物理层面:
-
电梯门的开关时间
-
电梯的加减速
-
电梯的载重限制
-
电梯的维护时间
控制层面:
-
电梯控制系统的接口(不同品牌不同)
-
通信协议(Modbus / BACnet / 私有协议)
-
权限管理(机器人是否有权调用电梯)
调度层面:
-
多台机器人同时需要电梯
-
电梯可能被人占用
-
电梯可能故障
-
高峰期电梯等待时间长
安全层面:
-
机器人进出电梯时不能夹人
-
电梯超载保护
-
紧急情况下的处理
21.2 RMF 的电梯管理架构
rmf_traffic(调度层)
↓
决定哪台机器人什么时候用电梯
↓
rmf_lift(控制层)
↓
Lift Adapter(适配层)
↓
实际电梯控制系统
21.3 Lift Adapter 的职责
Lift Adapter 是 RMF 和实际电梯之间的桥梁:
| 职责 | 说明 |
|---|---|
| 状态上报 | 电梯当前楼层、门状态、是否可用 |
| 指令下发 | 呼叫电梯、选择楼层、开关门 |
| 协议转换 | 将 RMF 指令转换为电梯控制系统能理解的协议 |
| 异常处理 | 电梯故障时通知 RMF |
| 权限管理 | 确保机器人有权使用电梯 |
21.4 电梯调度策略
RMF 对电梯的调度不是简单的"先到先得",而是考虑全局效率:
策略 1:批量调度
如果多台机器人要去同一楼层
→ 让它们搭同一趟电梯(如果容量允许)
策略 2:顺路调度
电梯从 L1 去 L5
如果 L3 有机器人要去 L5
→ 顺路接上
策略 3:优先级调度
紧急任务的机器人优先使用电梯
策略 4:预约调度
机器人还没到电梯口就提前预约
→ 减少到达后的等待时间
21.5 电梯联动的实际案例
场景:医院,2 台电梯,5 台机器人
10:00 Robot-A 在 B1,要送药到 L3
10:01 Robot-B 在 L1,要送餐到 L2
10:02 Robot-C 在 L3,要回 B1 充电
10:03 Robot-D 在 L2,要去 L1 取物
10:05 Robot-E 在 B1,要送标本到 L4
RMF 的调度:
- 电梯 1:B1(接A) → L3(放A, 接C) → B1(放C)
- 电梯 2:L2(接D) → L1(放D, 接B) → L2(放B)
- 电梯 1:B1(接E) → L4(放E)
通过合理调度,减少电梯空跑,提高整体效率。
二十二、门禁联动(Door Integration)
22.1 为什么门禁联动重要?
在建筑环境中,门是无处不在的:
-
防火门
-
科室门
-
电梯门
-
安全门
-
自动门
-
手动门
机器人要在建筑中自由移动,必须能够通过这些门。
22.2 门的类型和挑战
| 门类型 | 挑战 |
|---|---|
| 自动感应门 | 机器人能否触发感应? |
| 门禁门 | 机器人有没有权限? |
| 防火门 | 常闭,需要主动打开 |
| 电动门 | 需要通信接口控制 |
| 手动门 | 机器人无法通过(需要人帮忙) |
22.3 RMF 的门管理
RMF 通过 Door Adapter 管理门:
机器人接近门
↓
rmf_traffic 检查:机器人是否有权通过这扇门?
↓
通过 Door Adapter 发送开门请求
↓
等待门打开确认
↓
机器人通过
↓
发送关门请求(或等待自动关闭)
22.4 门作为"资源"
在 RMF 中,门也是一种资源:
-
门打开时,只允许一台机器人通过(避免碰撞)
-
门的开关有时间成本(不能频繁开关)
-
某些门有通行方向限制
因此门的调度也需要纳入整体交通规划:
场景:一扇防火门,两侧各有一台机器人要通过
不协调:
- 两台机器人同时到达门口
- 门打开
- 两台机器人同时进入 → 卡住
RMF 协调:
- Robot-A 先到达,请求开门
- 门打开,Robot-A 通过
- Robot-A 通过后,Robot-B 再通过
- 或者:Robot-B 稍微减速,让 Robot-A 先过
22.5 门禁联动在不同场景的应用
医院:
-
手术室门:只有特定机器人有权进入
-
隔离区门:需要消毒流程
-
药房门:需要身份验证
写字楼:
-
楼层门禁:不同楼层不同权限
-
会议室门:预约时段才能进入
-
机房门:高安全级别
酒店:
-
客房门:配送机器人需要临时权限
-
后厨门:只有送餐机器人能进
-
消防通道:紧急时才开放
二十三、异构机器人(Heterogeneous Robots)
23.1 什么是异构机器人系统?
异构(Heterogeneous)意味着系统中的机器人不是"一模一样"的。它们可能在以下维度不同:
| 维度 | 差异示例 |
|---|---|
| 品牌 | 机器人 A 来自厂商 X,机器人 B 来自厂商 Y |
| 类型 | 配送机器人 vs 清洁机器人 vs 巡检机器人 |
| 尺寸 | 小型配送车 vs 大型叉车 |
| 速度 | 0.5 m/s vs 2.0 m/s |
| 能力 | 能倒退 vs 不能倒退,能旋转 vs 不能旋转 |
| 导航方式 | 激光导航 vs 视觉导航 vs 磁条导航 |
| 通信协议 | ROS 2 vs 私有 API vs VDA5050 |
| 载荷 | 5kg vs 500kg |
23.2 异构带来的调度挑战
挑战 1:不同速度
快车(2 m/s)和慢车(0.5 m/s)在同一走廊
快车会追上慢车
→ 需要考虑"追尾冲突"
→ 传统系统(假设所有车速度相同)无法处理
挑战 2:不同尺寸
小型配送机器人(宽 0.5m)
大型清洁机器人(宽 1.2m)
走廊宽 1.5m:
- 两台小机器人可以并排通过
- 一台大机器人 + 一台小机器人不能并排
- 两台大机器人完全不能并排
→ 通行规则取决于机器人组合
挑战 3:不同能力
机器人 A 可以倒退
机器人 B 不能倒退
如果两者在窄走廊相遇:
- 让 A 倒退是可行的
- 让 B 倒退是不可行的
→ 协商时必须考虑各自的能力约束
挑战 4:不同通信协议
机器人 A 使用 ROS 2 话题通信
机器人 B 使用 HTTP REST API
机器人 C 使用 MQTT + VDA5050
→ 调度系统需要统一接口
23.3 RMF 如何处理异构性?
RMF 的核心设计就是为异构而生的。它通过以下机制处理异构性:
1. Fleet Adapter 抽象
每种机器人有自己的 Fleet Adapter,负责将 RMF 的统一指令翻译为具体机器人能理解的命令。
RMF 统一指令:
"移动到坐标 (10, 5)"
Fleet Adapter A(ROS 2 机器人):
发布 nav2 goal
Fleet Adapter B(HTTP API 机器人):
POST /api/navigate {"x": 10, "y": 5}
Fleet Adapter C(VDA5050 机器人):
发布 MQTT order 消息
2. Robot Profile
每台机器人在 RMF 中有一个 Profile,描述其物理特性:
Profile {
footprint: 物理占地面积
vicinity: 安全区域
max_speed: 最大速度
max_acceleration: 最大加速度
can_reverse: 是否能倒退
rotation_speed: 旋转速度
}
冲突检测和协商时,系统会根据 Profile 做出不同决策。
3. 能力声明
每台机器人声明自己能执行的任务类型:
Robot-A capabilities: [delivery, pickup]
Robot-B capabilities: [cleaning, disinfection]
Robot-C capabilities: [patrol, inspection]
任务分配时,系统只会把任务分配给有对应能力的机器人。
23.4 异构系统的实际价值
为什么要支持异构?因为现实就是异构的。
一家医院可能有:
- 3 台配送机器人(品牌 A,负责送药送餐)
- 2 台消毒机器人(品牌 B,负责紫外线消毒)
- 1 台巡检机器人(品牌 C,负责夜间安防)
- 1 台导引机器人(品牌 D,负责引导患者)
这些机器人:
- 共享走廊、电梯、门
- 需要互相避让
- 需要协调电梯使用
- 需要统一监控
如果没有统一的调度系统:
- 配送机器人和消毒机器人在走廊撞上
- 两台机器人同时抢电梯
- 运维人员需要看 4 个不同的监控界面
RMF 的价值就在于:
让所有这些不同的机器人在同一个"交通系统"下协同工作。
二十四、Fleet Adapter:RMF 的关键设计
24.1 Fleet Adapter 的定位
Fleet Adapter 是 RMF 架构中最重要的"接口层"。它的定位是:
RMF 核心和具体机器人之间的翻译官。
RMF Core(说"RMF 语言")
↓
Fleet Adapter(翻译)
↓
具体机器人(说"自己的语言")
24.2 Fleet Adapter 的双向职责
Fleet Adapter 不是单向的,它需要双向工作:
下行(RMF → 机器人):
-
接收 RMF 的导航指令
-
翻译为机器人能理解的命令
-
下发给机器人执行
上行(机器人 → RMF):
-
获取机器人的实时状态(位置、速度、电量、任务进度)
-
翻译为 RMF 能理解的格式
-
上报给 RMF 核心
24.3 Fleet Adapter 的具体接口
RMF 定义了 Fleet Adapter 必须实现的接口:
interface FleetAdapter {
// 导航相关
navigate(robot_id, destination, path) // 让机器人去某个位置
stop(robot_id) // 让机器人停下
// 动作相关
execute_action(robot_id, action) // 执行动作(取货、放货等)
// 状态上报
get_position(robot_id) → (x, y, yaw) // 获取位置
get_battery(robot_id) → percentage // 获取电量
get_state(robot_id) → state // 获取状态
// 轨迹相关
get_remaining_path(robot_id) → path // 获取剩余路径
estimate_arrival(robot_id) → time // 估计到达时间
}
24.4 Full Control vs Traffic Light
RMF 提供两种 Fleet Adapter 模式:
Full Control 模式:
RMF 完全控制机器人的路径
- RMF 计算路径
- RMF 下发每一步指令
- 机器人只负责执行
适用于:机器人本身没有导航能力,或者愿意完全听从 RMF 调度
Traffic Light 模式:
机器人自己导航,RMF 只管"交通"
- 机器人自己规划路径
- 但在进入共享区域前,需要向 RMF "请求通行权"
- RMF 批准后才能进入
- 类似于"红绿灯"
适用于:机器人有自己的导航系统,只需要和其他机器人协调
这两种模式的区别:
| 维度 | Full Control | Traffic Light |
|---|---|---|
| 路径规划 | RMF 负责 | 机器人自己负责 |
| 控制粒度 | 精细(每一步) | 粗粒度(区域级) |
| 集成难度 | 高(需要深度对接) | 低(只需要简单接口) |
| 协调效果 | 好(RMF 完全掌控) | 一般(只能在区域边界协调) |
| 适用场景 | 新部署的机器人 | 已有导航系统的机器人 |
24.5 为什么 Traffic Light 模式很重要?
因为现实中,很多机器人已经有了成熟的导航系统。让它们完全放弃自己的导航,改用 RMF 的路径规划,成本太高。
Traffic Light 模式提供了一种"最小侵入"的集成方式:
机器人厂商只需要实现:
1. 上报当前位置
2. 在进入共享区域前请求通行权
3. 收到"停止"指令时能停下
不需要:
- 改变导航算法
- 暴露内部路径规划
- 修改底层控制逻辑
这大大降低了集成门槛,使得 RMF 可以快速接入已有的机器人系统。
24.6 Fleet Adapter 的实现示例
一个典型的 Full Control Fleet Adapter 的工作循环:
while running:
// 上行:上报状态
position = robot.get_position()
battery = robot.get_battery()
rmf.update_state(robot_id, position, battery)
// 下行:接收并执行指令
if rmf.has_new_command(robot_id):
command = rmf.get_command(robot_id)
if command.type == NAVIGATE:
robot.navigate_to(command.destination)
elif command.type == STOP:
robot.stop()
elif command.type == ACTION:
robot.execute(command.action)
// 进度上报
if robot.is_navigating():
remaining = robot.get_remaining_path()
rmf.update_trajectory(robot_id, remaining)
sleep(100ms) // 10Hz 更新频率
24.7 Fleet Adapter 的错误处理
Fleet Adapter 还需要处理各种异常:
| 异常 | 处理方式 |
|---|---|
| 机器人失联 | 上报 UNAVAILABLE 状态,RMF 释放其预约的资源 |
| 导航失败 | 上报 ERROR,RMF 重新规划或重新分配任务 |
| 电量过低 | 上报低电量,RMF 安排充电任务 |
| 被人阻挡 | 上报 WAITING,RMF 更新轨迹预测 |
| 指令超时 | 重试或上报失败 |
二十五、为什么异构兼容很重要?
25.1 现实世界的"机器人孤岛"问题
目前大多数建筑中的机器人系统是这样的:
配送机器人(厂商 A)
→ 使用厂商 A 的调度系统
→ 只能看到自己的机器人
清洁机器人(厂商 B)
→ 使用厂商 B 的调度系统
→ 只能看到自己的机器人
巡检机器人(厂商 C)
→ 使用厂商 C 的调度系统
→ 只能看到自己的机器人
结果:
-
三套系统互相不知道对方的存在
-
机器人在走廊相遇时不知道怎么办
-
电梯被一台机器人占用时,其他机器人不知道
-
运维人员需要看三个不同的界面
这就是"机器人孤岛"问题。
25.2 孤岛问题的后果
效率低下:
配送机器人在等电梯
清洁机器人也在等同一部电梯
两者互相不知道
→ 电梯来了,两台机器人同时冲向电梯门
→ 卡住
→ 需要人工干预
安全隐患:
巡检机器人在走廊高速行驶
配送机器人从侧面路口出来
两者互相看不到对方的计划
→ 差点碰撞
→ 紧急刹车
→ 货物洒落
运维困难:
出了问题:
- 看厂商 A 的日志:一切正常
- 看厂商 B 的日志:一切正常
- 但实际上两台机器人互相堵了 10 分钟
因为没有统一的视角,问题很难定位。
25.3 RMF 如何解决孤岛问题
RMF 提供了一个"统一的交通层":
所有机器人(无论品牌)都通过 Fleet Adapter 接入 RMF
→ RMF 知道所有机器人的位置和计划
→ RMF 可以协调所有机器人的交通
→ 运维人员有统一的监控界面
这不要求机器人放弃自己的导航系统,只要求它们:
-
上报自己的状态
-
在共享区域遵守 RMF 的交通规则
这就像城市交通:
每辆车可以是不同品牌(奔驰、宝马、丰田),但都必须遵守同一套交通规则(红绿灯、让行标志)。
25.4 异构兼容的商业价值
对于建筑运营方来说,异构兼容意味着:
| 价值 | 说明 |
|---|---|
| 不被锁定 | 不需要所有机器人都买同一家的 |
| 最佳组合 | 可以选择各品类最好的机器人 |
| 渐进部署 | 可以逐步添加新机器人,不需要一次性替换 |
| 统一管理 | 一个界面管理所有机器人 |
| 降低风险 | 某个厂商倒闭不影响整体系统 |
二十六、rmf_demos:官方演示系统
26.1 rmf_demos 是什么?
rmf_demos 是 Open-RMF 官方提供的完整仿真演示包。它不是一个"Hello World"级别的 Demo,而是包含了完整场景的系统演示。
26.2 rmf_demos 包含的场景
| 场景 | 说明 | 展示的能力 |
|---|---|---|
| Office | 办公楼 | 多机器人配送、门禁联动 |
| Hospital | 医院 | 多楼层、电梯、多种机器人 |
| Hotel | 酒店 | 客房配送、电梯调度 |
| Airport | 机场 | 大规模、长距离 |
| Clinic | 诊所 | 小规模、紧凑空间 |
26.3 rmf_demos 的技术栈
仿真层:
Gazebo(物理仿真)
→ 模拟机器人运动、传感器、环境
RMF 层:
rmf_traffic(交通调度)
rmf_task(任务管理)
rmf_fleet_adapter(车队适配)
rmf_building_map(建筑地图)
可视化层:
rmf_visualization(RViz 插件)
rmf-web(Web 界面)
通信层:
ROS 2(DDS)
26.4 运行 rmf_demos 能看到什么?
在一个典型的 Hospital Demo 中,你可以看到:
1. 多台机器人同时在不同楼层运行
2. 机器人自动避让(不是简单停车,而是提前调整路径)
3. 机器人排队等电梯
4. 电梯被自动调度(不是先到先得)
5. 机器人通过自动门(门自动开关)
6. 新任务下发后,系统自动选择最合适的机器人
7. 机器人电量低时自动去充电
8. Web 界面实时显示所有机器人状态
26.5 rmf_demos 的学习价值
rmf_demos 的价值不只是"看个热闹"。通过阅读其源码,可以学到:
| 学习内容 | 对应代码 |
|---|---|
| 如何定义建筑地图 | building_map 配置文件 |
| 如何写 Fleet Adapter | fleet_adapter 节点 |
| 如何配置交通图 | nav_graph 文件 |
| 如何定义任务 | task 配置 |
| 如何集成电梯 | lift_adapter 节点 |
| 如何集成门 | door_adapter 节点 |
| 如何做可视化 | rmf-web 前端 |
26.6 rmf_demos 的局限性
需要注意的是,rmf_demos 是仿真环境,和真实部署有差距:
| 仿真 | 真实 |
|---|---|
| 机器人完美执行指令 | 机器人可能偏离、延迟 |
| 网络无延迟 | 网络可能丢包、延迟 |
| 电梯立即响应 | 电梯可能被人占用 |
| 环境完全已知 | 可能有未知障碍物 |
| 不会故障 | 随时可能故障 |
二十七、rmf_demos 使用什么仿真?
27.1 Gazebo 仿真平台
rmf_demos 主要基于 Gazebo 进行仿真。Gazebo 是 ROS 生态中最主流的机器人仿真平台。
Gazebo 提供:
| 能力 | 说明 |
|---|---|
| 物理引擎 | 模拟重力、碰撞、摩擦 |
| 传感器模拟 | 激光雷达、摄像头、IMU |
| 环境建模 | 建筑、家具、障碍物 |
| 多机器人 | 同时仿真多台机器人 |
| 插件系统 | 可扩展自定义功能 |
27.2 RMF 对 Gazebo 的扩展
RMF 为 Gazebo 添加了专门的插件:
rmf_building_map_tools
→ 从建筑地图自动生成 Gazebo 世界文件
rmf_robot_sim_gazebo_plugins
→ 模拟机器人的 Fleet Adapter 行为
rmf_simulation_assets
→ 提供标准化的仿真资源(机器人模型、建筑模型)
27.3 Traffic Editor:地图编辑工具
RMF 提供了一个专门的地图编辑工具 Traffic Editor:
功能:
- 导入建筑平面图(图片)
- 在平面图上绘制导航图(Waypoint + Lane)
- 定义楼层
- 放置电梯、门
- 设置交通规则(单向、限速)
- 导出为 RMF 格式 + Gazebo 世界文件
这个工具大大简化了场景搭建的过程。不需要手写 XML 或 YAML,可以可视化地编辑。
27.4 从 Traffic Editor 到仿真的流程
1. 获取建筑平面图(CAD 或扫描图)
↓
2. 在 Traffic Editor 中导入平面图
↓
3. 绘制导航图(标记路径点和通道)
↓
4. 标记设施(电梯位置、门位置、充电桩)
↓
5. 设置交通规则(单向通道、限速区域)
↓
6. 导出:
- nav_graph.yaml(RMF 导航图)
- building_map.yaml(建筑信息)
- world.sdf(Gazebo 仿真世界)
↓
7. 启动仿真
二十八、rmf_demos 能看到什么?(详细分析)
28.1 多机器人动态避让
在 Demo 中,最直观的就是多台机器人在走廊中的动态避让:
场景:两台机器人在走廊相向而行
传统方式(你可能见过的):
- 两车接近
- 检测到对方
- 一辆停下
- 另一辆通过
- 停下的那辆继续
RMF 方式(Demo 中看到的):
- 两车还没接近时,系统就预测到冲突
- 系统协商:让其中一辆稍微减速
- 或者让其中一辆提前进入旁边的等待区
- 两车"流畅地"错开
- 几乎没有完全停车的情况
这种"流畅感"就是时空规划的直观体现。
28.2 电梯调度的可视化
场景:3 台机器人需要使用电梯
可以看到:
1. 机器人 A 到达电梯口,电梯被调来
2. 机器人 A 进入电梯,上到 3 楼
3. 同时,机器人 B 到达 1 楼电梯口
4. 系统判断:机器人 C 在 3 楼也要下来
5. 电梯先让 A 出去,再接 C 下来
6. C 到 1 楼出去后,B 进入
7. 整个过程没有"抢电梯"的混乱
28.3 任务动态分配
场景:通过 Web 界面下发配送任务
1. 用户点击"从药房送药到 302 病房"
2. 系统显示:选择了 Robot-B(最近且电量充足)
3. Robot-B 开始移动
4. 途中又来了一个紧急任务
5. 系统判断:Robot-B 正在执行中,选择 Robot-A
6. 两台机器人同时执行各自任务
7. 在走廊相遇时自动协调
28.4 故障恢复
场景:模拟机器人故障
1. Robot-A 正在执行任务
2. 手动让 Robot-A "故障"(停止响应)
3. 系统检测到 Robot-A 失联
4. 释放 Robot-A 预约的资源
5. 将 Robot-A 的任务重新分配给 Robot-C
6. Robot-C 接手任务,继续执行
7. 其他机器人绕开 Robot-A 停留的位置
二十九、RMF 的动态任务分配
29.1 任务类型
RMF 支持多种任务类型:
| 任务类型 | 说明 | 示例 |
|---|---|---|
| Delivery | 配送 | 从 A 取货送到 B |
| Loop | 循环巡逻 | 沿固定路线反复巡逻 |
| Clean | 清洁 | 清洁指定区域 |
| Charge | 充电 | 去充电桩充电 |
| Custom | 自定义 | 任何用户定义的任务 |
29.2 任务分配的考虑因素
当一个新任务到来时,系统需要决定派哪台机器人。考虑因素包括:
| 因素 | 权重 | 说明 |
|---|---|---|
| 距离 | 高 | 离任务起点的距离 |
| 电量 | 高 | 是否有足够电量完成任务 |
| 能力匹配 | 必须 | 机器人是否有执行该任务的能力 |
| 当前状态 | 必须 | 是否空闲 |
| 预计完成时间 | 中 | 如果正在执行任务,什么时候能空闲 |
| 交通影响 | 中 | 派这台机器人是否会造成交通拥堵 |
| 公平性 | 低 | 避免某台机器人过度使用 |
29.3 任务分配算法
RMF 的任务分配不是简单的"最近优先"。它使用的是基于代价的分配:
对每台候选机器人,计算总代价:
cost(robot, task) =
travel_cost(robot.position → task.start) // 去取货的代价
+ execution_cost(task) // 执行任务的代价
+ traffic_impact(robot, task) // 对交通的影响
+ opportunity_cost(robot) // 机会成本
选择总代价最小的机器人。
其中"机会成本"是一个有趣的概念:
如果 Robot-A 现在空闲,但 5 分钟后有一个高优先级任务需要它
那么现在把它派去做低优先级任务,5 分钟后它就不可用了
→ 机会成本高
→ 应该派其他机器人做当前的低优先级任务
29.4 任务队列和优先级
RMF 维护一个任务队列,支持优先级:
Task Queue:
[Priority 1] 紧急送药到 ICU → 立即执行
[Priority 2] 送餐到 302 病房 → 尽快执行
[Priority 3] 清洁 1 楼大厅 → 空闲时执行
[Priority 4] 巡检 B1 设备间 → 低优先级
高优先级任务可以:
-
抢占低优先级任务的机器人
-
获得交通优先权(协商时代价权重更高)
-
优先使用电梯
29.5 任务的生命周期
PENDING → ASSIGNED → EXECUTING → COMPLETED
↓
FAILED → REASSIGNED → EXECUTING → ...
| 状态 | 含义 |
|---|---|
| PENDING | 等待分配 |
| ASSIGNED | 已分配给某台机器人 |
| EXECUTING | 正在执行 |
| COMPLETED | 完成 |
| FAILED | 失败(机器人故障、路径不可达等) |
| REASSIGNED | 重新分配给其他机器人 |
| CANCELLED | 被取消 |
29.6 复合任务(Composed Tasks)
RMF 还支持复合任务,即由多个子任务组成的任务:
复合任务:"从药房取药送到 3 楼 302 病房"
子任务分解:
1. 移动到药房
2. 执行取药动作(等待药剂师放药)
3. 移动到电梯口
4. 乘电梯到 3 楼
5. 移动到 302 病房
6. 执行送药动作(等待护士确认)
7. 返回待命点
每个子任务有自己的状态,系统可以精确跟踪进度。
三十、RMF 为什么适合医院场景?
30.1 医院的特殊性
医院是最复杂的机器人部署场景之一:
| 特点 | 挑战 |
|---|---|
| 多楼层 | 需要电梯联动 |
| 人流密集 | 需要动态避障 |
| 24小时运行 | 不能停机维护 |
| 多种机器人 | 配送、消毒、巡检 |
| 严格的区域管理 | 手术室、隔离区有权限要求 |
| 紧急情况 | 需要快速响应 |
| 卫生要求 | 消毒机器人有特殊路线 |
| 噪音限制 | 夜间需要安静运行 |
30.2 传统 AGV 在医院的困境
传统 AGV 系统在医院面临的问题:
问题 1:固定路线不适应人流变化
- 白天门诊区人多,机器人走不动
- 夜间人少,但路线不能动态调整
- 结果:白天效率极低
问题 2:不支持多楼层
- 传统 AGV 通常只在一层运行
- 跨楼层需要人工搬运
- 或者每层独立部署一套系统
问题 3:不支持异构
- 配送机器人和消毒机器人来自不同厂商
- 两套系统互相不知道对方
- 在走廊相遇时混乱
问题 4:不支持动态优先级
- 紧急送血任务和普通送餐任务优先级不同
- 传统系统很难动态调整优先级
30.3 RMF 如何解决医院场景的问题
| 问题 | RMF 的解决方案 |
|---|---|
| 人流变化 | 时空规划 + 动态重规划,根据实时情况调整 |
| 多楼层 | 原生支持多层地图 + 电梯联动 |
| 异构机器人 | Fleet Adapter 统一管理 |
| 动态优先级 | 任务优先级系统 + 抢占机制 |
| 区域权限 | 交通图中标记权限区域 |
| 紧急响应 | 高优先级任务可以抢占资源 |
| 24小时运行 | 自动充电调度 + 故障自动恢复 |
30.4 医院场景的典型一天
06:00 - 早班开始
- 配送机器人开始送早餐
- 消毒机器人完成夜间消毒,回充电桩
07:00 - 门诊开始
- 导引机器人在大厅引导患者
- 配送机器人送检验标本到检验科
- 人流增加,RMF 自动调整机器人路线避开人流密集区
10:00 - 高峰期
- 多台配送机器人同时运行
- 电梯使用频繁,RMF 优化电梯调度
- 紧急送血任务插入,抢占电梯优先权
12:00 - 午餐配送
- 大量送餐任务
- RMF 批量调度,优化路线
- 避免所有机器人同时挤在电梯口
15:00 - 手术室补给
- 高优先级任务
- 机器人获得走廊优先通行权
- 其他机器人自动避让
18:00 - 晚班交接
- 部分机器人去充电
- 夜间巡检机器人上线
- 消毒机器人开始工作
22:00 - 夜间模式
- 机器人降低速度(噪音控制)
- 消毒机器人全面消毒
- 巡检机器人安防巡逻
- 配送机器人待命(处理紧急需求)
三十一、RMF 的工程挑战
RMF 的设计很先进,但在实际工程中也面临很多挑战。
31.1 系统复杂度
RMF 的技术栈非常深:
底层:
Linux + DDS + ROS 2
中间层:
rmf_traffic(C++,高性能计算)
rmf_task(任务调度逻辑)
rmf_fleet_adapter(Python/C++ 混合)
上层:
rmf-web(React + WebSocket)
Traffic Editor(Qt)
仿真:
Gazebo + 自定义插件
部署:
Docker + 多节点分布式
要完整理解和使用 RMF,需要掌握:
| 技术 | 用途 |
|---|---|
| C++ | 核心算法(rmf_traffic) |
| Python | Fleet Adapter、脚本 |
| ROS 2 | 通信框架 |
| DDS | 底层通信 |
| Gazebo | 仿真 |
| React | Web 界面 |
| Docker | 部署 |
| YAML | 配置 |
学习曲线相当陡峭。
31.2 调试难度
RMF 系统的调试比传统系统难得多:
问题 1:分布式系统的调试
问题现象:机器人在走廊停了 10 秒不动
可能原因:
- rmf_traffic 的协商超时了?
- Fleet Adapter 的状态上报延迟了?
- DDS 通信丢包了?
- Gazebo 仿真卡顿了?
- 机器人的导航模块出问题了?
需要同时查看多个节点的日志才能定位。
问题 2:时序问题
问题现象:两台机器人偶尔会在走廊"僵持"
原因:
- 协商结果下发到两台机器人的时间有微小差异
- 机器人 A 收到"你先走"时,已经停下了
- 机器人 B 收到"你等一下"时,已经走过了冲突点
- 结果:两者都停下等对方
这种时序 bug 很难复现,很难调试。
问题 3:状态不一致
问题现象:RMF 认为机器人在 A 点,但机器人实际在 B 点
原因:
- Fleet Adapter 上报延迟
- 机器人被人推动了
- 定位系统漂移
后果:
- RMF 基于错误位置做出的调度决策全部错误
- 可能导致"幽灵冲突"(RMF 认为有冲突但实际没有)
- 或者"遗漏冲突"(RMF 认为没冲突但实际有)
31.3 调试工具和方法
RMF 提供了一些调试工具:
| 工具 | 用途 |
|---|---|
| rmf-web Dashboard | 实时查看所有机器人状态和轨迹 |
| ROS 2 日志 | 查看各节点的详细日志 |
| RViz 可视化 | 查看导航图、轨迹、冲突 |
| rosbag 录制 | 录制通信数据,事后回放分析 |
| Schedule 可视化 | 查看时空调度表的状态 |
但即使有这些工具,调试 RMF 系统仍然需要:
-
对整体架构有深入理解
-
能够同时分析多个节点的行为
-
理解时序和并发问题
-
有耐心(有些 bug 很难复现)
31.4 性能挑战
冲突检测的计算量:
N 台机器人 → N*(N-1)/2 对需要检测
每对需要做连续轨迹的时空距离计算
机器人数量增加时,计算量快速增长
20 台机器人:190 对
50 台机器人:1225 对
100 台机器人:4950 对
协商的收敛时间:
简单冲突(2 台机器人):毫秒级
复杂冲突(多台机器人连锁):可能需要多轮协商
极端情况:协商可能不收敛,需要超时回退
实时性要求:
机器人以 1 m/s 移动
100ms 的决策延迟 = 10cm 的位置误差
如果冲突检测太慢,机器人可能已经进入冲突区域
31.5 不适合简单项目
如果你的场景是:
- 2-3 台机器人
- 单层平面
- 固定路线
- 不需要电梯/门
- 不需要异构
那么 RMF 可能过于复杂。用 openTCS 或者自己写一个简单的调度系统就够了。
RMF 的价值在规模和复杂度上体现:
| 场景规模 | 推荐方案 |
|---|---|
| 2-3 台同构机器人,单层 | 自己写简单调度 |
| 5-10 台同构机器人,单层,固定路线 | openTCS |
| 10+ 台机器人,多层,异构,动态环境 | Open-RMF |
| 50+ 台机器人,多建筑,复杂设施联动 | Open-RMF + 定制优化 |
引入 RMF 的成本:
-
学习成本:团队需要 2-3 个月熟悉
-
集成成本:每种机器人需要开发 Fleet Adapter
-
运维成本:分布式系统的监控和维护
-
调试成本:问题定位比传统系统难
如果场景复杂度不够,这些成本就不值得。
三十二、RMF 和 openTCS 到底怎么选?
这是实际项目中最常被问到的问题。
32.1 决策框架
不要从"哪个更先进"的角度选择,而要从"哪个更适合"的角度选择。
问自己以下问题:
Q1: 机器人是否需要跨楼层?
是 → 倾向 RMF
否 → 继续看
Q2: 是否有多种品牌/类型的机器人?
是 → 倾向 RMF
否 → 继续看
Q3: 路径是否固定?
固定 → 倾向 openTCS
动态 → 倾向 RMF
Q4: 是否需要和电梯/门联动?
是 → 倾向 RMF
否 → 继续看
Q5: 机器人数量?
< 10 台,同构 → openTCS 足够
> 10 台,或异构 → 考虑 RMF
Q6: 团队技术栈?
Java 为主 → openTCS 更容易上手
ROS/C++/Python 为主 → RMF 更自然
Q7: 环境动态性?
基本静态(工厂) → openTCS
高度动态(医院、商场) → RMF
32.2 详细对比
| 维度 | openTCS | Open-RMF |
|---|---|---|
| 核心语言 | Java | C++ / Python |
| 通信框架 | 自有 | ROS 2 / DDS |
| 路径模型 | 固定图 + 资源锁 | 动态轨迹 + 时空协商 |
| 冲突解决 | 先到先得 / 优先级 | Negotiation 协商 |
| 地图 | 单层 | 多层 |
| 设施联动 | 不支持 | 电梯、门、充电桩 |
| 异构支持 | 弱 | 强(Fleet Adapter) |
| 仿真 | 内置简单仿真 | Gazebo 完整仿真 |
| 可视化 | Java Swing 桌面应用 | Web 界面 |
| 社区 | 工业物流社区 | ROS 机器人社区 |
| 文档 | 完善 | 中等(在改善中) |
| 学习曲线 | 中等 | 陡峭 |
| 部署复杂度 | 低(单 JVM) | 高(分布式多节点) |
| 成熟度 | 高(10+ 年) | 中(3-4 年) |
| 适用规模 | 中小型 | 中大型 |
32.3 可以混合使用吗?
理论上可以。一种可能的架构:
场景:工厂 + 办公楼
工厂区域(固定路线 AGV):
→ 使用 openTCS 管理
→ 稳定、成熟、够用
办公楼区域(动态 AMR):
→ 使用 RMF 管理
→ 灵活、支持电梯
两个系统之间:
→ 通过网关对接
→ 在交界区域做交通协调
但这种混合架构增加了系统复杂度,通常只在大型项目中才值得。
32.4 未来趋势
从长期看:
-
openTCS 会继续在传统工业物流中占据主导地位
-
RMF 会在"建筑级机器人"领域快速发展
-
两者可能会互相借鉴(openTCS 可能加入时空规划,RMF 可能简化部署)
-
VDA5050 标准可能成为两者共同的机器人接口标准
32.5 一个真实的选型案例
项目:某三甲医院机器人系统
需求:
- 3 台配送机器人(品牌 A)
- 2 台消毒机器人(品牌 B)
- 1 台巡检机器人(品牌 C)
- 5 层楼,2 部电梯
- 需要和门禁系统联动
- 24 小时运行
分析:
- 异构 ✓ → 需要统一调度
- 多楼层 ✓ → 需要电梯联动
- 门禁 ✓ → 需要设施联动
- 6 台机器人 → 规模中等
- 动态环境(医院人流) → 需要动态规划
结论:选择 Open-RMF
如果需求变成:
- 10 台同品牌 AGV
- 单层仓库
- 固定路线
- 无电梯/门
结论:选择 openTCS(更简单、更稳定)
三十三、如果你做比赛,RMF 最值得学什么?
对于参加机器人比赛(如 RoboMaster、RoboCup、各类创新赛)的同学,RMF 最大的价值不是直接使用它,而是学习它的设计思想。
33.1 时空路径规划
这是最值得学习的第一个思想。
传统做法(大多数比赛队伍):
for each robot:
plan_path(start, goal) // 独立规划,不考虑其他机器人
if collision_detected:
stop_and_wait() // 碰到了再说
RMF 思想:
for each robot:
plan_trajectory(start, goal, time) // 规划带时间的轨迹
check_all_trajectories_for_conflicts() // 提前检测所有冲突
if conflict_found:
negotiate_and_adjust() // 提前协调
在比赛中的应用:
场景:3 台机器人需要同时到达不同目标
传统做法:
- 3 台各自规划路径
- 在路口碰到 → 停车 → 等待 → 浪费时间
- 总完成时间:45 秒
时空规划:
- 3 台同时规划,考虑时间
- 提前安排:Robot-A 先过路口(10:05),Robot-B 后过(10:08)
- 没有停车,流畅通过
- 总完成时间:30 秒
比赛中 15 秒的差距可能就是名次的差距。
具体实现建议:
# 简化版时空路径规划
class SpatioTemporalPlanner:
def __init__(self):
self.schedule = {} # robot_id → trajectory
def plan(self, robot_id, start, goal, start_time):
# 1. 规划空间路径
path = a_star(start, goal)
# 2. 计算时间(假设匀速)
trajectory = []
t = start_time
for i, point in enumerate(path):
trajectory.append((point, t))
if i < len(path) - 1:
distance = dist(path[i], path[i+1])
t += distance / robot_speed
# 3. 检测与已有轨迹的冲突
conflicts = self.detect_conflicts(trajectory)
# 4. 如果有冲突,调整
if conflicts:
trajectory = self.resolve_conflicts(trajectory, conflicts)
# 5. 提交到 schedule
self.schedule[robot_id] = trajectory
return trajectory
def detect_conflicts(self, new_trajectory):
conflicts = []
for robot_id, existing in self.schedule.items():
for t in time_range:
pos_new = interpolate(new_trajectory, t)
pos_existing = interpolate(existing, t)
if distance(pos_new, pos_existing) < SAFETY_DIST:
conflicts.append((robot_id, t))
return conflicts
33.2 预测式交通调度
传统做法:
机器人 A 在路口发现机器人 B → 停车 → 等 B 过去 → 继续
RMF 思想:
提前 5 秒就知道 A 和 B 会在路口相遇
→ 让 A 减速 0.2 m/s
→ A 到达路口时 B 已经过去了
→ A 不需要停车
在比赛中的应用:
# 预测式避让
def predictive_avoidance(robot_a, robot_b):
# 预测未来 5 秒的轨迹
future_a = predict_trajectory(robot_a, horizon=5.0)
future_b = predict_trajectory(robot_b, horizon=5.0)
# 找到最早的冲突时刻
conflict_time = find_first_conflict(future_a, future_b)
if conflict_time is None:
return # 无冲突,不需要处理
# 计算调整方案
# 方案 1:A 减速
cost_a_slow = estimate_delay(robot_a, slow_down=0.2)
# 方案 2:B 减速
cost_b_slow = estimate_delay(robot_b, slow_down=0.2)
# 方案 3:A 绕路
cost_a_detour = estimate_detour(robot_a)
# 选择代价最小的方案
best = min(cost_a_slow, cost_b_slow, cost_a_detour)
apply(best)
33.3 协商机制
传统做法:
固定优先级:编号小的先走
问题:编号大的机器人可能离目标只有 1 米,但因为优先级低,要等编号小的机器人走完 50 米。
RMF 思想:
谁让步代价小,谁让步。
在比赛中的简化实现:
def simple_negotiation(robot_a, robot_b, conflict_point):
"""
简化版协商:比较双方让步代价,代价小的让步
"""
# 计算 A 让步的代价(等待时间)
cost_a_yield = time_for_b_to_pass(robot_b, conflict_point)
# 计算 B 让步的代价(等待时间)
cost_b_yield = time_for_a_to_pass(robot_a, conflict_point)
# 考虑任务紧急程度
cost_a_yield *= robot_a.task_priority # 高优先级任务让步代价更大
cost_b_yield *= robot_b.task_priority
if cost_a_yield < cost_b_yield:
robot_a.yield() # A 让步(等待或减速)
else:
robot_b.yield() # B 让步
33.4 设施联动思想
即使比赛中没有电梯,这个思想也很有用。
核心思想:
机器人系统不只管机器人,还要管"共享资源"。
在比赛中的应用:
共享资源可能是:
- 充电桩(多台机器人共用)
- 物料站(取放物料的位置)
- 窄通道(一次只能一台通过)
- 工作台(执行任务的位置)
这些都可以用 RMF 的"设施联动"思想来管理:
- 提前预约
- 排队等待
- 使用完释放
- 动态调度
class SharedResource:
"""共享资源管理(类似 RMF 的设施联动)"""
def __init__(self, name, capacity=1):
self.name = name
self.capacity = capacity
self.current_users = []
self.queue = []
def request(self, robot_id, priority=0):
"""请求使用资源"""
if len(self.current_users) < self.capacity:
self.current_users.append(robot_id)
return True # 立即获得
else:
self.queue.append((priority, robot_id))
self.queue.sort(reverse=True) # 高优先级排前面
return False # 需要等待
def release(self, robot_id):
"""释放资源"""
self.current_users.remove(robot_id)
if self.queue:
_, next_robot = self.queue.pop(0)
self.current_users.append(next_robot)
notify(next_robot, "resource_available")
33.5 比赛中的完整架构建议
结合 RMF 的思想,一个适合比赛的多机器人系统架构:
┌─────────────────────────────────────────────┐
│ Task Manager │
│ - 任务队列 │
│ - 任务分配(基于代价) │
│ - 优先级管理 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Traffic Scheduler │
│ - 时空轨迹管理(Schedule) │
│ - 冲突检测(连续轨迹) │
│ - 简化版 Negotiation │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Path Planner │
│ - A* / CBS / WHCA* │
│ - 时空路径规划 │
│ - 动态重规划 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Resource Manager │
│ - 共享资源管理 │
│ - 预约 / 释放 │
│ - 队列管理 │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Robot Interface │
│ - 统一接口(类似 Fleet Adapter) │
│ - 状态上报 │
│ - 指令下发 │
└─────────────────┬───────────────────────────┘
│
┌───────┼───────┐
▼ ▼ ▼
Robot-1 Robot-2 Robot-3
三十四、一个适合比赛的 RMF 风格架构(详细设计)
34.1 整体设计原则
从 RMF 中提取适合比赛的设计原则:
| 原则 | 说明 | 比赛中的体现 |
|---|---|---|
| 时空一体 | 路径带时间 | 避免不必要的停车 |
| 预测优先 | 提前发现冲突 | 减少紧急制动 |
| 协商代替抢占 | 代价最小的让步 | 全局效率更高 |
| 增量式规划 | 新任务不影响已有计划 | 系统稳定 |
| 容错设计 | 偏差时快速恢复 | 比赛中不怕意外 |
34.2 推荐技术栈(详细版)
| 模块 | 推荐技术 | 备选 | 理由 |
|---|---|---|---|
| 通信 | ROS 2 Humble | ZeroMQ | ROS 2 生态完善,工具链好 |
| 核心调度 | C++ | Python(原型) | 性能关键路径用 C++ |
| 路径规划 | CBS / SIPP | A* + 时间窗 | CBS 最优但慢,SIPP 快且好 |
| 仿真 | Gazebo | Stage / 自写 | Gazebo 最接近真实 |
| 可视化 | RViz2 + Web | 纯 Web | RViz2 调试方便 |
| 前端 | Vue 3 + WebSocket | React | Vue 上手快 |
| 状态机 | SMACH / BehaviorTree | 自写 FSM | 任务执行用行为树更灵活 |
34.3 核心模块详细设计
模块 1:Schedule(时空调度表)
// C++ 实现的核心 Schedule
class Schedule {
public:
// 提交轨迹
bool submit(const std::string& robot_id, const Trajectory& trajectory) {
auto conflicts = detect_conflicts(trajectory);
if (conflicts.empty()) {
trajectories_[robot_id] = trajectory;
return true;
}
return false; // 有冲突,需要协商
}
// 更新轨迹
void update(const std::string& robot_id, const Trajectory& trajectory) {
trajectories_[robot_id] = trajectory;
// 重新检测受影响的冲突
recheck_conflicts(robot_id);
}
// 检测冲突
std::vector<Conflict> detect_conflicts(const Trajectory& new_traj) {
std::vector<Conflict> conflicts;
for (const auto& [id, existing] : trajectories_) {
auto conflict = check_pair(new_traj, existing);
if (conflict.has_value()) {
conflicts.push_back(conflict.value());
}
}
return conflicts;
}
private:
// 两条轨迹的冲突检测
std::optional<Conflict> check_pair(
const Trajectory& a, const Trajectory& b) {
// 找到时间重叠区间
double t_start = std::max(a.start_time(), b.start_time());
double t_end = std::min(a.end_time(), b.end_time());
if (t_start >= t_end) return std::nullopt; // 时间不重叠
// 在重叠区间内采样检测
double dt = 0.1; // 100ms 采样
for (double t = t_start; t <= t_end; t += dt) {
auto pos_a = a.position_at(t);
auto pos_b = b.position_at(t);
double dist = (pos_a - pos_b).norm();
if (dist < SAFETY_DISTANCE) {
return Conflict{a.robot_id(), b.robot_id(), t, pos_a};
}
}
return std::nullopt;
}
std::unordered_map<std::string, Trajectory> trajectories_;
};
模块 2:Negotiation Engine(协商引擎)
# Python 实现的协商引擎(比赛中够用)
class NegotiationEngine:
def __init__(self, schedule, planner):
self.schedule = schedule
self.planner = planner
def resolve(self, conflict):
"""
解决一个冲突
返回:调整方案
"""
robot_a = conflict.robot_a
robot_b = conflict.robot_b
# 为双方生成替代方案
alternatives_a = self.generate_alternatives(robot_a, conflict)
alternatives_b = self.generate_alternatives(robot_b, conflict)
# 评估所有组合,选择全局代价最小的
best_cost = float('inf')
best_plan = None
# 方案 1:A 让步
for alt_a in alternatives_a:
cost = alt_a.cost + 0 # B 不变,代价为 0
if cost < best_cost:
# 检查 A 的新轨迹是否引入新冲突
new_conflicts = self.schedule.detect_conflicts(alt_a.trajectory)
if not new_conflicts:
best_cost = cost
best_plan = ('a_yields', alt_a)
# 方案 2:B 让步
for alt_b in alternatives_b:
cost = 0 + alt_b.cost # A 不变
if cost < best_cost:
new_conflicts = self.schedule.detect_conflicts(alt_b.trajectory)
if not new_conflicts:
best_cost = cost
best_plan = ('b_yields', alt_b)
# 方案 3:双方都调整
for alt_a in alternatives_a:
for alt_b in alternatives_b:
cost = alt_a.cost + alt_b.cost
if cost < best_cost:
# 检查两条新轨迹之间是否冲突
if not trajectories_conflict(alt_a.trajectory, alt_b.trajectory):
best_cost = cost
best_plan = ('both_adjust', alt_a, alt_b)
return best_plan
def generate_alternatives(self, robot, conflict):
"""为机器人生成替代方案"""
alternatives = []
# 方案类型 1:在冲突点前等待
wait_traj = self.planner.plan_with_wait(
robot, conflict.point, wait_duration=3.0)
alternatives.append(Alternative(
trajectory=wait_traj,
cost=3.0 # 等待 3 秒的代价
))
# 方案类型 2:绕路
detour_traj = self.planner.plan_avoiding(
robot, conflict.point)
if detour_traj:
detour_cost = detour_traj.duration - robot.original_duration
alternatives.append(Alternative(
trajectory=detour_traj,
cost=detour_cost
))
# 方案类型 3:减速通过
slow_traj = self.planner.plan_with_speed(
robot, speed_factor=0.5)
slow_cost = slow_traj.duration - robot.original_duration
alternatives.append(Alternative(
trajectory=slow_traj,
cost=slow_cost
))
return alternatives
模块 3:Task Manager(任务管理器)
class TaskManager:
def __init__(self, fleet, schedule):
self.fleet = fleet # 所有机器人
self.schedule = schedule
self.task_queue = PriorityQueue()
self.active_tasks = {}
def submit_task(self, task):
"""提交新任务"""
self.task_queue.put((-task.priority, task)) # 负号使高优先级排前面
self.try_assign()
def try_assign(self):
"""尝试分配队列中的任务"""
while not self.task_queue.empty():
_, task = self.task_queue.get()
# 找到最合适的机器人
best_robot = None
best_cost = float('inf')
for robot in self.fleet.available_robots():
# 检查能力匹配
if not robot.can_execute(task.type):
continue
# 检查电量
if not robot.has_enough_battery(task):
continue
# 计算代价
cost = self.estimate_cost(robot, task)
if cost < best_cost:
best_cost = cost
best_robot = robot
if best_robot:
self.assign(best_robot, task)
else:
# 没有可用机器人,放回队列
self.task_queue.put((-task.priority, task))
break
def estimate_cost(self, robot, task):
"""估计机器人执行任务的代价"""
# 到达任务起点的距离
travel_dist = distance(robot.position, task.start)
travel_time = travel_dist / robot.max_speed
# 任务执行时间
exec_time = task.estimated_duration
# 交通影响(路径上是否拥挤)
traffic_factor = self.schedule.congestion_factor(
robot.position, task.start)
# 综合代价
return travel_time * (1 + traffic_factor) + exec_time
def assign(self, robot, task):
"""将任务分配给机器人"""
# 规划轨迹
trajectory = self.plan_trajectory(robot, task)
# 提交到 schedule
success = self.schedule.submit(robot.id, trajectory)
if success:
robot.execute(task, trajectory)
self.active_tasks[task.id] = (robot.id, task)
else:
# 有冲突,触发协商
self.negotiate_and_retry(robot, task, trajectory)
34.4 SIPP 算法:比赛中推荐的时空规划算法
SIPP(Safe Interval Path Planning)是一种高效的时空路径规划算法,非常适合比赛使用:
核心思想:
- 对每个节点,计算"安全时间区间"(没有其他机器人占用的时间段)
- 在安全区间内搜索路径
- 类似 A*,但搜索空间是 (节点, 安全区间) 对
class SIPP:
"""Safe Interval Path Planning"""
def __init__(self, graph, schedule):
self.graph = graph
self.schedule = schedule
def plan(self, start, goal, start_time):
"""规划时空路径"""
# 1. 计算所有节点的安全区间
safe_intervals = self.compute_safe_intervals()
# 2. A* 搜索(在安全区间上)
open_set = PriorityQueue()
open_set.put((0, start, start_time, 0)) # (f, node, time, interval_idx)
came_from = {}
g_score = {(start, 0): 0}
while not open_set.empty():
f, current, current_time, interval_idx = open_set.get()
if current == goal:
return self.reconstruct_path(came_from, current, interval_idx)
# 扩展邻居
for neighbor in self.graph.neighbors(current):
travel_time = self.graph.edge_time(current, neighbor)
arrival_time = current_time + travel_time
# 找到 neighbor 在 arrival_time 时的安全区间
for idx, interval in enumerate(safe_intervals[neighbor]):
if interval.start <= arrival_time <= interval.end:
# 可以在这个安全区间到达
new_g = g_score[(current, interval_idx)] + travel_time
if (neighbor, idx) not in g_score or new_g < g_score[(neighbor, idx)]:
g_score[(neighbor, idx)] = new_g
h = self.heuristic(neighbor, goal)
open_set.put((new_g + h, neighbor, arrival_time, idx))
came_from[(neighbor, idx)] = (current, interval_idx, arrival_time)
# 也考虑"等待后再出发"的情况
# (当前安全区间内等待,直到邻居的下一个安全区间开始)
return None # 无解
def compute_safe_intervals(self):
"""计算每个节点的安全时间区间"""
safe_intervals = {}
for node in self.graph.nodes():
# 收集所有占用这个节点的时间段
occupied = []
for robot_id, trajectory in self.schedule.items():
for segment in trajectory.segments:
if segment.passes_through(node):
# 加上安全余量
occupied.append((
segment.enter_time - SAFETY_MARGIN,
segment.exit_time + SAFETY_MARGIN
))
# 合并重叠的占用区间
occupied.sort()
merged = self.merge_intervals(occupied)
# 安全区间 = 总时间 - 占用区间
safe = []
prev_end = 0.0
for start, end in merged:
if start > prev_end:
safe.append(Interval(prev_end, start))
prev_end = end
safe.append(Interval(prev_end, float('inf'))) # 最后一个区间到无穷
safe_intervals[node] = safe
return safe_intervals
def merge_intervals(self, intervals):
"""合并重叠区间"""
if not intervals:
return []
merged = [intervals[0]]
for start, end in intervals[1:]:
if start <= merged[-1][1]:
merged[-1] = (merged[-1][0], max(merged[-1][1], end))
else:
merged.append((start, end))
return merged
34.5 SIPP 的优势
| 维度 | A* + 时间展开 | SIPP |
|---|---|---|
| 搜索空间 | 节点数 × 时间步数 | 节点数 × 安全区间数 |
| 内存 | 大(时间步多) | 小(安全区间通常少) |
| 速度 | 慢 | 快 |
| 最优性 | 最优 | 最优(在给定约束下) |
| 适用场景 | 小规模 | 中大规模 |
SIPP 的关键洞察:
大多数节点在大多数时间都是空闲的。只需要关注"被占用的时间段",而不是枚举所有时间步。
三十五、CBS 算法详解(比赛中的高级选择)
如果你的比赛需要"最优"的多机器人路径规划,CBS(Conflict-Based Search)是最值得学习的算法。
35.1 CBS 的核心思想
高层搜索:搜索"约束组合"
低层搜索:在给定约束下,为单个机器人规划路径
高层发现冲突 → 分支 → 为冲突双方分别添加约束 → 低层重新规划
35.2 CBS 的完整流程
Step 1: 为每台机器人独立规划最短路径(不考虑其他机器人)
Step 2: 检测所有路径之间的冲突
Step 3: 如果无冲突 → 完成!
Step 4: 选择第一个冲突 (robot_i, robot_j, node, time)
Step 5: 分支:
- 分支 A:给 robot_i 添加约束"不能在 time 时刻在 node"
- 分支 B:给 robot_j 添加约束"不能在 time 时刻在 node"
Step 6: 在每个分支中,为受约束的机器人重新规划路径
Step 7: 回到 Step 2
35.3 CBS 的实现
class CBS:
"""Conflict-Based Search"""
def __init__(self, graph, robots):
self.graph = graph
self.robots = robots # [(start, goal), ...]
def solve(self):
"""求解 MAPF 问题"""
# 初始节点:每台机器人独立规划
root = CTNode()
root.constraints = {i: [] for i in range(len(self.robots))}
root.solution = {}
for i, (start, goal) in enumerate(self.robots):
path = self.low_level_search(i, start, goal, [])
if path is None:
return None # 无解
root.solution[i] = path
root.cost = sum(len(p) for p in root.solution.values())
# 高层搜索(Best-First Search)
open_set = PriorityQueue()
open_set.put((root.cost, id(root), root))
while not open_set.empty():
_, _, node = open_set.get()
# 检测冲突
conflict = self.find_first_conflict(node.solution)
if conflict is None:
return node.solution # 无冲突,找到解!
# 分支
robot_i, robot_j, position, time = conflict
for robot_id in [robot_i, robot_j]:
# 创建子节点
child = CTNode()
child.constraints = deepcopy(node.constraints)
child.constraints[robot_id].append(
Constraint(position, time))
child.solution = dict(node.solution)
# 为受约束的机器人重新规划
start, goal = self.robots[robot_id]
new_path = self.low_level_search(
robot_id, start, goal,
child.constraints[robot_id])
if new_path is not None:
child.solution[robot_id] = new_path
child.cost = sum(len(p) for p in child.solution.values())
open_set.put((child.cost, id(child), child))
return None # 无解
def low_level_search(self, robot_id, start, goal, constraints):
"""
低层搜索:带约束的时空 A*
约束格式:不能在某时刻出现在某位置
"""
open_set = PriorityQueue()
open_set.put((0, (start, 0))) # (f_score, (position, time))
came_from = {}
g_score = {(start, 0): 0}
while not open_set.empty():
f, (current, t) = open_set.get()
if current == goal:
return self.reconstruct(came_from, (current, t))
# 扩展:移动到邻居 或 原地等待
successors = []
# 移动到邻居
for neighbor in self.graph.neighbors(current):
successors.append((neighbor, t + 1))
# 原地等待
successors.append((current, t + 1))
for (next_pos, next_t) in successors:
# 检查约束
if self.violates_constraint(robot_id, next_pos, next_t, constraints):
continue
new_g = g_score[(current, t)] + 1
if (next_pos, next_t) not in g_score or new_g < g_score[(next_pos, next_t)]:
g_score[(next_pos, next_t)] = new_g
h = self.heuristic(next_pos, goal)
open_set.put((new_g + h, (next_pos, next_t)))
came_from[(next_pos, next_t)] = (current, t)
return None # 在当前约束下无解
def find_first_conflict(self, solution):
"""找到第一个冲突"""
max_t = max(len(path) for path in solution.values())
for t in range(max_t):
positions = {}
for robot_id, path in solution.items():
pos = path[min(t, len(path) - 1)] # 到达后原地等待
if pos in positions:
# 顶点冲突:两台机器人同时在同一位置
return (positions[pos], robot_id, pos, t)
positions[pos] = robot_id
# 边冲突:两台机器人交换位置(对穿)
for robot_i, path_i in solution.items():
for robot_j, path_j in solution.items():
if robot_i >= robot_j:
continue
if t + 1 < len(path_i) and t + 1 < len(path_j):
if (path_i[t] == path_j[t+1] and
path_i[t+1] == path_j[t]):
return (robot_i, robot_j, path_i[t], t)
return None # 无冲突
def violates_constraint(self, robot_id, position, time, constraints):
"""检查是否违反约束"""
for constraint in constraints:
if constraint.position == position and constraint.time == time:
return True
return False
35.4 CBS 的优化变体
纯 CBS 在大规模问题上可能很慢。以下是常用的优化:
| 优化 | 思想 | 效果 |
|---|---|---|
| ECBS | 使用有界次优的低层搜索 | 速度提升 10-100 倍,解质量有界 |
| CBS + Disjoint Splitting | 更智能的分支策略 | 减少搜索树大小 |
| CBS + Bypass | 跳过不必要的分支 | 减少节点数 |
| CBS + Heuristic | 高层搜索使用启发式 | 更快找到好解 |
| CBS + Prioritized | 先用优先级规划,再用 CBS 优化 | 快速得到初始解 |
35.5 比赛中 CBS vs SIPP 的选择
| 场景 | 推荐 | 理由 |
|---|---|---|
| 3-5 台机器人,需要最优解 | CBS | 规模小,CBS 能快速求解 |
| 5-10 台机器人,需要快速解 | SIPP + 优先级 | CBS 可能太慢 |
| 10+ 台机器人 | ECBS 或 LaCAM | 需要次优但快速的算法 |
| 动态环境,频繁重规划 | SIPP | 增量式规划更适合 |
| 静态环境,一次性规划 | CBS | 可以花时间找最优解 |
三十六、RMF 的实时性保证
36.1 为什么实时性重要?
机器人以 1 m/s 移动
如果调度系统需要 1 秒才能做出决策
→ 机器人在这 1 秒内已经移动了 1 米
→ 如果这 1 米内有冲突点,就来不及避让了
因此:
- 冲突检测必须在 < 100ms 内完成
- 协商必须在 < 500ms 内完成
- 路径规划必须在 < 200ms 内完成
36.2 RMF 如何保证实时性?
策略 1:分层计算
快速层(< 10ms):
- 检查新轨迹是否与已有轨迹冲突
- 简单的时间窗检查
中速层(< 100ms):
- 精确的连续轨迹冲突检测
- 单台机器人的路径重规划
慢速层(< 1000ms):
- 多轮协商
- 全局优化
- 任务重分配
策略 2:Anytime 算法
Anytime 算法的特点:
- 快速给出一个"可行解"(可能不是最优)
- 如果还有时间,继续优化
- 随时可以被打断,返回当前最好的解
RMF 的协商就是 Anytime 的:
- 第一轮(50ms):找到一个可行方案
- 第二轮(100ms):尝试优化
- 如果超时(500ms):使用当前最好的方案
策略 3:增量式计算
不是每次都从头计算:
- 新机器人加入 → 只检测新轨迹和已有轨迹的冲突
- 机器人偏离计划 → 只更新该机器人的轨迹,重新检测相关冲突
- 新任务到来 → 只为新任务规划,不影响已有计划
策略 4:空间索引
不需要检测所有机器人对:
- 使用空间索引(R-tree / Grid)
- 只检测空间上可能相遇的机器人对
- 大幅减少计算量
例如:
- 1 楼的机器人和 3 楼的机器人不可能冲突
- 东翼的机器人和西翼的机器人短期内不可能冲突
36.3 实时性 vs 最优性的权衡
时间预算:500ms
如果在 500ms 内:
- 找到最优解 → 使用最优解
- 只找到次优解 → 使用次优解
- 连可行解都没找到 → 使用回退策略(优先级方式)
回退策略保证:
- 系统永远不会"卡住"
- 最坏情况下退化为传统的优先级调度
- 但大多数情况下能找到更好的方案
三十七、RMF 的容错与恢复
37.1 机器人系统的故障类型
| 故障类型 | 频率 | 影响 | 示例 |
|---|---|---|---|
| 定位漂移 | 高 | 低 | 位置误差 10-30cm |
| 导航失败 | 中 | 中 | 路径被阻挡,无法到达 |
| 通信中断 | 中 | 中 | WiFi 断连 |
| 机械故障 | 低 | 高 | 电机故障、轮子卡住 |
| 电量耗尽 | 低 | 高 | 机器人停在路中间 |
| 软件崩溃 | 低 | 高 | 导航节点崩溃 |
37.2 RMF 的容错层级
层级 1:容差吸收
- 安全距离 > 物理最小距离
- 时间窗有余量
- 小偏差不触发任何响应
效果:90% 的小问题自动消化
层级 2:轨迹更新
- 机器人偏离计划超过阈值
- Fleet Adapter 上报新的预计轨迹
- Schedule 更新,重新检测冲突
- 如果有新冲突,触发协商
效果:处理中等偏差
层级 3:任务重分配
- 机器人无法完成当前任务
- 任务被标记为 FAILED
- Task Manager 将任务重新分配给其他机器人
效果:处理单台机器人故障
层级 4:紧急停车
- 检测到即将发生碰撞
- 所有相关机器人紧急停车
- 系统重新规划所有受影响的轨迹
效果:最后的安全保障
层级 5:人工干预
- 系统无法自动恢复
- 通知运维人员
- 提供故障信息和建议操作
效果:处理系统级故障
37.3 故障恢复的具体流程
场景:Robot-A 在走廊中间突然停下(电机故障)
时间线:
t=0s: Robot-A 停下
t=0.5s: Fleet Adapter 检测到 Robot-A 不再移动
t=1.0s: Fleet Adapter 上报 Robot-A 状态为 ERROR
t=1.5s: Schedule 将 Robot-A 的轨迹标记为"静止在当前位置"
t=2.0s: 冲突检测发现 Robot-B 的轨迹和 Robot-A 的新位置冲突
t=2.5s: 触发协商:Robot-B 需要绕开 Robot-A
t=3.0s: Robot-B 获得新轨迹(绕路)
t=3.5s: Robot-B 开始执行新轨迹
t=5.0s: Task Manager 将 Robot-A 的任务重新分配给 Robot-C
t=10s: 通知运维人员处理 Robot-A
整个过程:
- Robot-B 只延迟了约 3 秒(而不是一直等待)
- Robot-A 的任务在 5 秒内被接手
- 系统整体继续运行
37.4 死锁检测与恢复
死锁是多机器人系统中最棘手的问题之一:
死锁场景:
Robot-A 在位置 1,要去位置 2
Robot-B 在位置 2,要去位置 1
走廊只能单向通行
→ 两者互相等待,谁都走不了
RMF 的死锁处理:
检测:
- 监控所有机器人的等待状态
- 如果一组机器人互相等待超过阈值 → 判定为死锁
恢复:
- 选择代价最小的机器人"后退"
- 或者选择一台机器人"绕路"
- 极端情况:让一台机器人回到起点重新规划
预防:
- 时空规划本身就能避免大多数死锁
- 单向通道设计
- 等待区设计(让机器人有地方"让路")
37.5 通信故障处理
场景:Fleet Adapter 和机器人之间通信中断
处理流程:
1. Fleet Adapter 检测到心跳丢失
2. 等待 3 秒(可能是临时网络波动)
3. 如果恢复 → 正常继续
4. 如果未恢复:
- 上报 Robot 状态为 UNAVAILABLE
- Schedule 中该机器人的轨迹标记为"不确定"
- 其他机器人绕开该机器人最后已知位置
- 任务标记为 INTERRUPTED
5. 通信恢复后:
- 获取机器人当前实际位置
- 更新 Schedule
- 决定是继续原任务还是重新分配
三十八、RMF 的可扩展性
38.1 水平扩展
当机器人数量增加时,RMF 如何扩展?
10 台机器人:
- 单节点运行所有 RMF 组件
- 冲突检测:45 对,毫秒级
50 台机器人:
- 冲突检测:1225 对,可能需要优化
- 考虑区域划分
100+ 台机器人:
- 需要分布式部署
- 区域独立调度 + 跨区域协调
38.2 区域划分策略
大型建筑可以划分为多个区域:
区域 A(1楼东翼):20 台机器人,独立调度
区域 B(1楼西翼):15 台机器人,独立调度
区域 C(2楼):10 台机器人,独立调度
跨区域:
- 机器人从区域 A 到区域 B 时
- 需要两个区域的调度器协调
- 类似于"跨国旅行需要签证"
这种分层调度的好处:
| 好处 | 说明 |
|---|---|
| 计算量降低 | 每个区域只需要处理自己的机器人 |
| 故障隔离 | 一个区域的问题不影响其他区域 |
| 独立部署 | 每个区域可以独立升级 |
| 并行计算 | 多个区域同时计算 |
38.3 多建筑扩展
RMF 还支持多建筑场景:
场景:一个园区有 3 栋建筑
Building A(医院主楼):30 台机器人
Building B(门诊楼):10 台机器人
Building C(后勤楼):5 台机器人
建筑之间通过连廊连接
每栋建筑有独立的 RMF 实例
建筑之间通过"跨建筑协调器"协调
三十九、RMF 的未来发展方向
39.1 当前版本的局限
| 局限 | 说明 |
|---|---|
| 学习曲线陡峭 | 需要掌握 ROS 2 + C++ + 分布式系统 |
| 文档不够完善 | 部分模块缺乏详细文档 |
| 部署复杂 | 需要多个节点协同 |
| 仿真到实际的差距 | 仿真中完美运行不代表实际部署顺利 |
| 大规模验证不足 | 100+ 台机器人的实际部署案例较少 |
39.2 发展方向
1. 简化部署
目标:一键部署 RMF
方式:
- Docker Compose 一键启动
- Kubernetes 编排
- 云原生架构
- 配置向导(不需要手写 YAML)
2. AI 增强调度
目标:用机器学习优化调度决策
方式:
- 学习历史数据,预测人流模式
- 强化学习优化协商策略
- 预测性维护(预测机器人故障)
- 自适应参数调整
3. 更好的人机共存
目标:在人流密集环境中更好地运行
方式:
- 行人轨迹预测
- 社会力模型集成
- 动态速度调整
- 礼让行为
4. 标准化接口
目标:降低集成成本
方式:
- 完全支持 VDA5050 标准
- 提供标准化的 Fleet Adapter 模板
- 插件化架构(即插即用)
5. 数字孪生
目标:实时的虚拟镜像
方式:
- 3D 可视化整个建筑
- 实时显示所有机器人位置和轨迹
- 模拟"如果"场景(what-if analysis)
- 历史回放和分析
39.3 RMF 在行业中的定位
2020:RMF 发布,概念验证阶段
2021-2022:早期采用者开始试用
2023-2024:在医院、机场等场景落地
2025+:预计更多商业化部署
竞争对手:
- 各机器人厂商的私有调度系统
- InOrbit(云端机器人管理)
- Freedom Robotics(机器人运维平台)
- 各种 MAPF 商业解决方案
RMF 的独特优势:
- 开源(无供应商锁定)
- ROS 2 生态(最大的机器人开发者社区)
- 完整的设施联动(不只是交通调度)
- 异构支持(不限品牌)
四十、总结:RMF 的核心价值
40.1 一句话总结
RMF 是一个"建筑级的机器人操作系统",它让不同品牌、不同类型的机器人能够在同一个建筑中安全、高效地协同工作。
40.2 RMF 解决的核心问题
问题 1:机器人孤岛
→ 解决方案:Fleet Adapter 统一接入
问题 2:交通混乱
→ 解决方案:时空路径规划 + Negotiation
问题 3:设施争抢
→ 解决方案:电梯/门/充电桩的统一调度
问题 4:多楼层运行
→ 解决方案:多地图 + 跨层路径规划
问题 5:动态环境
→ 解决方案:实时重规划 + 容错恢复
40.3 RMF 的技术创新
| 创新点 | 传统方式 | RMF 方式 |
|---|---|---|
| 冲突解决 | 优先级 / 先到先得 | Negotiation 协商 |
| 路径模型 | 空间路径 | 时空轨迹 |
| 冲突检测 | 离散点检测 | 连续轨迹检测 |
| 设施管理 | 独立系统 | 统一联动 |
| 机器人接入 | 同构假设 | 异构 Fleet Adapter |
| 调度策略 | 静态规则 | 动态协商 |
40.4 学习 RMF 的路径建议
阶段 1:理解概念(1-2 周)
- 读本文档
- 看 RMF 官方视频和演示
- 理解核心概念:Schedule、Trajectory、Negotiation
阶段 2:运行 Demo(1-2 周)
- 安装 ROS 2 + RMF
- 运行 rmf_demos
- 观察系统行为
- 修改参数看效果
阶段 3:理解代码(2-4 周)
- 阅读 rmf_traffic 核心代码
- 阅读 Fleet Adapter 示例
- 理解 Negotiation 的实现
阶段 4:自己实现(2-4 周)
- 写一个简化版的 Schedule + 冲突检测
- 实现简化版 Negotiation
- 在自己的项目中应用
阶段 5:实际部署(4-8 周)
- 为自己的机器人写 Fleet Adapter
- 配置建筑地图
- 集成电梯/门
- 调试和优化
40.5 最后的思考
RMF 代表了机器人系统从"单机智能"到"系统智能"的转变:
单机智能:每台机器人自己很聪明,但互相不协调
系统智能:每台机器人可能不那么聪明,但系统整体很聪明
类比:
- 单机智能 = 每个人都是天才,但没有交通规则
- 系统智能 = 普通人 + 完善的交通系统 = 高效的城市运转
RMF 的哲学是:
不要求每台机器人都是天才,而是提供一个让所有机器人都能高效协作的"基础设施"。
这和现代城市的设计理念一致:
好的城市不是因为每个市民都是天才,而是因为有好的道路、交通信号、公共交通系统。
RMF 就是机器人世界的"城市基础设施"。
全文完
这份文档覆盖了 Open-RMF 的核心概念、技术原理、工程实践和比赛应用。如果你有具体的问题或者想深入某个方面,随时可以继续讨论。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)