AGV调度仿真实战:基于时间窗的启发式路由与死锁检测
在工厂物流和仓储自动化场景中,AGV 调度系统的核心矛盾从来不是“车跑多快”,而是多车共用有限路径资源时如何避免冲突、死锁和空驶。再强的硬件,如果调度算法在拥堵区域把几台车同时逼进单行道,系统就会频繁停摆。
本文介绍一种在仿真中验证过的 AGV 调度思路:基于时间窗的启发式路由 + 死锁检测。不追求理论最优,而是追求工程可落地、可参数调优、可在仿真中快速验证效果。
一、AGV 调度的三类经典问题
在写代码之前,先把问题归类清楚。
| 问题类型 | 描述 | 典型场景 |
|---|---|---|
| 任务分配 | 把搬运任务分配给哪台 AGV | 仓库补货、产线供料 |
| 路径规划 | 从 A 到 B 走哪条路 | 多车道、单行道、十字路口 |
| 冲突避免 | 两辆车会不会在同时占用同一段路径 | 交叉路口、窄巷道 |
本文重点讨论路径规划与冲突避免,任务分配采用最常见的“就近分配 + 负载均衡”策略即可。
二、时间窗法:把路径变成时空资源
最简单的 AGV 调度是 A* 寻路,每车独立规划。但独立规划的问题是:它只考虑空间,不考虑时间。两车可能在同一时刻到达同一个路口。
时间窗法(Time-Window Based Routing)的思路是:
- 把路径网络抽象成图
G = (V, E),边e有长度l(e)和通行能力c(e)。 - 每辆车
k申请从o_k到d_k,预计到达各边的时间窗[t_in, t_out]。 - 如果某条边在同一时间窗内被占用数超过
c(e),就标记为冲突,需要重新规划或等待。
伪代码
defplan_route_with_timewindow(agv,origin,dest,network,reservations):# 1. 用 A* 找空间最短路径path=a_star(network,origin,dest)# 2. 计算路径上每条边的预计时间窗schedule=[]t=current_timeforedgeinpath:t_in=t t_out=t+edge.length/agv.speed schedule.append((edge,t_in,t_out))t=t_out# 3. 检查与已有 reservations 的冲突foredge,t_in,t_outinschedule:ifhas_conflict(reservations[edge],t_in,t_out,edge.capacity):# 4. 冲突处理:等待 或 重新规划ifcan_wait_at_node(edge.start):delay=find_min_delay(reservations[edge],t_in,t_out)t+=delay# 在节点等待else:# 临时禁用该边,重新寻路network.temporarily_block(edge)path=a_star(network,origin,dest)returnplan_route_with_timewindow(agv,origin,dest,network,reservations)# 5. 提交预约foredge,t_in,t_outinschedule:reservations[edge].append((agv.id,t_in,t_out))returnpath,schedule三、启发式策略:在求解速度与解质量之间取舍
完全的时间窗法在车辆数多、路径复杂时计算量会爆炸。工程上通常引入启发式规则:
1. 分区优先级
把地图分成“主干道”和“巷道”。主干道优先给长距离运输,巷道留给近距离取放货。可以通过边的权重动态调整实现。
ifedge.type=='main_corridor'andtask.distance>50:edge.weight*=0.8# 鼓励长距离任务走主道elifedge.type=='aisle'andtask.distance<20:edge.weight*=0.9# 鼓励短任务走巷道2. 方向约定
在环形路径中设定单向通行规则,虽然会增加某些任务的距离,但能显著降低死锁概率。我们在一个 3C 电子仓库项目中,把环形通道改为单向通行后,死锁次数从平均每天 12 次降到了 0 次,整体运输时间只增加了 7%。
3. 预留缓冲时间
不要把时间窗排得太满。预留 10%~15% 缓冲,可以给车辆加减速、避让留出余地。我们在一个锂电正极材料车间项目中,把时间窗利用率控制在 75% 以下后,系统拥堵次数下降了 60%。
4. 任务优先级与让行规则
在多车型混合调度场景中,可以给不同任务设置优先级。例如,产线缺料导致的紧急补货任务优先于普通移库任务。当高优先级车辆接近路口时,低优先级车辆主动让行。让行规则需要在仿真中充分验证,避免低优先级任务长期被饿死。
四、死锁检测:比避免冲突更重要
死锁比冲突更隐蔽。冲突可以通过等待解决,死锁是“互相等待,谁都动不了”。
资源等待图(Wait-For Graph)
每辆车是一个节点。如果车 A 占用了边 e1,等待边 e2;车 B 占用了边 e2,等待边 e1,则图中存在环,即死锁。
defdetect_deadlock(agv_fleet):graph=defaultdict(list)foragvinagv_fleet:ifagv.status=='waiting_for_edge':forheld_edgeinagv.held_edges:graph[agv.id].append(held_edge.occupier.id)# 检测有向环ifhas_cycle(graph):trigger_deadlock_recovery()死锁恢复策略
| 策略 | 适用场景 | 副作用 |
|---|---|---|
| 优先级退让 | 轻载系统 | 低优先级车辆频繁等待 |
| 强制回退 | 已发生死锁 | 需要倒车或绕行空间 |
| 预分配资源 | 关键路径 | 降低资源利用率 |
| 死锁预防(Banker 算法思想) | 高可靠场景 | 实现复杂 |
仿真阶段的建议是:不要假设现场不会死锁,而是要在仿真中主动构造死锁场景,验证恢复机制是否有效。例如,可以人为提高某区域的订单密度,观察系统是否会进入死锁,以及死锁恢复需要多长时间。
除了等待图法,还可以引入“占用时长上限”机制:如果某辆车在一条边上等待超过阈值,就触发告警并启动退让流程。这种机制实现简单,对大部分工业场景已经足够。在高可靠场景下,可以结合 Banker 算法的思想,在任务分配阶段就判断系统是否处于安全状态,从源头避免死锁发生。
五、仿真验证的关键指标
AGV 调度算法不能只在代码里跑通,必须在仿真中验证。我们建议关注以下指标:
| 指标 | 定义 | 合理范围 |
|---|---|---|
| 任务完成率 | 完成任务数 / 下发任务数 | > 98% |
| 平均运输时间 | 从接单到卸货的时间 | 依场景而定 |
| 空驶率 | 空驶里程 / 总里程 | < 25% |
| 死锁次数 | 单位仿真时间内死锁触发次数 | 0(触发恢复即算) |
| 路口等待占比 | 等待时间 / 总运行时间 | < 15% |
除了这些核心指标,还建议做敏感性分析:车辆数从 8 台增加到 16 台时,平均运输时间、拥堵次数、任务完成率分别如何变化?这些曲线能帮助你判断系统的容量上限。
实际项目中,我们还发现“车辆充电策略”对指标影响很大。如果所有 AGV 都等到电量低于 20% 才充电,可能会在充电站形成排队;如果采用错峰充电或 opportunity charging,可以显著降低充电等待时间。建议在仿真中加入电池模型,验证不同充电策略的效果。
六、工具选型与实施建议
- 小规模(<10 台 AGV):直接用 Plant Simulation 或 FlexSim 内置 AGV 模块即可。
- 中大规模(10~50 台):建议自研调度核心,用 Python/C++ 实现时间窗算法,再与仿真软件通过 OPC UA 或 Socket 对接。
- 超大规模(>50 台):考虑分布式调度 + 数字孪生实时映射。此时单节点计算可能成为瓶颈,需要把路径规划、任务分配、死锁检测拆分成独立服务。
无论哪种规模,先在仿真环境里把冲突和死锁场景跑透,再上车部署,都是成本最低、风险最小的路径。仿真环境的价值在于可以低成本地重复试错:你可以把车辆数翻倍、把订单密度拉满、把某条路径临时关闭,观察系统行为。这些实验如果在真实现场做,代价可能是停产和撞车。因此,一个合格的 AGV 仿真项目,应该把极端场景作为必测项,而不是可选项。只有经过极端场景验证的算法,才值得信赖。这也是仿真技术区别于简单路径规划的核心价值所在。
*本文由数预智(广东)科技有限公司技术团队撰写。团队深耕工厂仿真、物流仿真、AGV仿真、仓储立体库仿真、三维动画及数字孪生领域,已服务多家制造企业完成数字化转型。