You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
14 KiB
14 KiB
接驳位驱动的任务调度方案
一、核心概念
| 概念 | 物理含义 | 数据对应 |
|---|---|---|
| 接驳位 | 货物放置/取走的物理位置 | LiveTaskDetail.startPoint / endPoint,设备参数中 接驳位状态(0空闲/1占用) |
| RFID 条码 | 托盘上的电子标签 | LiveTaskDetail.palletBarcode |
| 任务 | 一个完整的搬运工单 | LiveTaskQueue |
| 子任务步骤 | 任务中的一段搬运步骤 | LiveTaskDetail(objId 决定先后顺序) |
任务与子任务的关系
一个 LiveTaskQueue 包含多个 LiveTaskDetail,按 objId 从小到大串行执行。前一步的 endPoint 等于下一步的 startPoint,通过接驳位串联:
LiveTaskQueue (任务, taskCode = "T001")
│
├─ LiveTaskDetail objId=1: AGV 13#_L1_01 → BRIDGE_13_14 (子任务步骤1)
├─ LiveTaskDetail objId=2: AGV BRIDGE_13_14 → 14#_L1_HOIST (子任务步骤2)
├─ LiveTaskDetail objId=3: 提升机 14#_L1_HOIST → 14#_L2_HOIST (子任务步骤3)
├─ LiveTaskDetail objId=4: AGV 14#_L2_HOIST → 15#_L2_HOIST (子任务步骤4)
├─ LiveTaskDetail objId=5: 提升机 15#_L2_HOIST → 15#_L4_HOIST (子任务步骤5)
└─ LiveTaskDetail objId=6: AGV 15#_L4_HOIST → 15#_L4_03 (子任务步骤6)
数据库关键字段
- LiveTaskQueue:
taskCode,taskType(1入库/2出库),taskCategory(1包材/2成品/3托盘),taskStatus(1待执行/2执行中/3已完成),startPoint,endPoint - LiveTaskDetail:
taskCode,objId,palletBarcode,startPoint,endPoint,deviceType(0输送线/1AGV/2提升机),taskStatus - BaseDeviceParam:
deviceCode,paramName(如"接驳位状态"),paramValue(0空闲/1占用)
二、现场拓扑
楼栋与设备
廊桥(2F)
┌───────────┐
│ AGV交接 │
└─────┬─────┘
│
13#(1F) ──13#提升机── 13#(2F)─┘ 14#(2F) ──AGV── 15#(2F) ──15#提升机── 15#(1F~5F)
(1F↔2F) │
14#提升机(1F↔2F)
│
14#(1F)
AGV 运行区域
| 区域 | 行驶范围 | 说明 |
|---|---|---|
| 13# AGV | 13# 栋 1F~2F | 不能驶入 14# |
| 14# AGV | 14# 栋 1F~2F + 至 15# 2F | 负责中间段搬运 |
| 15# AGV | 仅在 15# 栋内 | 不能驶入 14# |
核心约束
- AGV 不能跨栋:13# 和 14# 之间在 2F 有廊桥交接点,两边 AGV 在此完成货物交换
- 所有楼栋均为 1~5F,各栋有 1 台提升机连通所有楼层
- 跨栋必须经 2F:不管起点在几楼,跨栋必须经 2F 的廊桥(13↔14)或 AGV 通道(14↔15)
接驳位点表
| 接驳位 | 编码 | 所属 AGV |
|---|---|---|
| 13# 各层普通点 | 13#_L{f}_{n} |
13# AGV |
| 13# 提升机入口/出口 | 13#_L{f}_HOIST |
13# AGV |
| 廊桥交接点(2F) | BRIDGE_13_14 |
13# AGV ↔ 14# AGV 交接 |
| 14# 各层普通点 | 14#_L{f}_{n} |
14# AGV |
| 14# 提升机入口/出口 | 14#_L{f}_HOIST |
14# AGV |
| 15# 提升机 2F 入口 | 15#_L2_HOIST |
14# AGV ↔ 15# AGV |
| 15# 提升机各层出口 | 15#_L{f}_HOIST |
15# AGV |
| 15# 各层普通点 | 15#_L{f}_{n} |
15# AGV |
拓扑连通图
节点: (building, floor)
边 (提升机, 同栋跨层):
(13,1) ←→ (13,2)
(14,1) ←→ (14,2)
(15,2) ←→ (15,1)
(15,2) ←→ (15,3)
(15,2) ←→ (15,4)
(15,2) ←→ (15,5)
边 (AGV, 同层跨栋):
(13,2) ←→ (14,2) (廊桥交接)
(14,2) ←→ (15,2) (AGV直连)
三、任务创建方案 —— 路径分解与明细生成
输入
起始楼栋: startBuilding (13/14/15)
起始楼层: startFloor (1~5)
起始位置: startLocation (位置号,如 "01")
结束楼栋: endBuilding (13/14/15)
结束楼层: endFloor (1~5)
结束位置: endLocation (位置号,如 "02")
系统自动拼接为 {building}#_L{floor}_{location} 格式。
输出
返回一个完整的 LiveTaskQueue,包含按 objId 排序的 List<LiveTaskDetail>。
生成算法
输入: (b_s, f_s, loc_s), (b_e, f_e, loc_e)
Step 1 — BFS 最短路径
在拓扑图中找 (b_s, f_s) → ... → (b_e, f_e) 的节点序列
Step 2 — 沿节点路径逐段生成明细
currentPos = 实际起点
for each 相邻节点 (b1,f1) → (b2,f2):
跨栋 (b1 != b2):
if 13↔14 (廊桥交接):
生成 [AGV] currentPos → BRIDGE_13_14
生成 [AGV] BRIDGE_13_14 → 接驳位(b2,f2)
注: 廊桥在 2F,两边都须先经提升机到达 2F
else (14↔15, AGV直连):
生成 [AGV] currentPos → 接驳位(b2,f2)
currentPos = 接驳位(b2,f2)
同栋跨层 (b1 == b2, f1 != f2):
生成 [AGV] currentPos → 提升机入口(b,f1)
生成 [提升机] 提升机入口(b,f1) → 提升机出口(b,f2)
currentPos = 提升机出口(b,f2)
Step 3 — 最终送达
if currentPos != 实际终点:
生成 [AGV] currentPos → 实际终点
┌─ 同栋同层 (b1 == b2 && f1 == f2) ──────────────┐
│
│ 跳过(不会出现在最短路径中)
│
└────────────────────────────────────────────────┘
Step 3 — 替换首尾实际坐标
首条 Detail.startPoint → pt_s 尾条 Detail.endPoint → pt_e 中间节点起点/终点 → 使用对应的接驳位编码
### 各场景示例(修正后——廊桥在 2F)
| 场景 | 输入 | 生成 Detail 序列 |
|------|------|-----------------|
| **同栋同层** | 14,1,"01" → 14,1,"05" | `[1] AGV: 14#_L1_01 → 14#_L1_05` |
| **同栋跨层** | 14,1,"01" → 14,2,"03" | `[1] AGV: 14#_L1_01 → 14#_L1_HOIST` `[2] 提升机: 14#_L1_HOIST → 14#_L2_HOIST` `[3] AGV: 14#_L2_HOIST → 14#_L2_03` |
| **13→14 (均为1F)** | 13,1,"01" → 14,1,"03" | `[1] AGV: 13#_L1_01 → 13#_L1_HOIST` `[2] 提升机: 13#_L1_HOIST → 13#_L2_HOIST` `[3] AGV: 13#_L2_HOIST → BRIDGE_13_14` `[4] AGV: BRIDGE_13_14 → 14#_L2_01` `[5] AGV: 14#_L2_01 → 14#_L2_HOIST` `[6] 提升机: 14#_L2_HOIST → 14#_L1_HOIST` `[7] AGV: 14#_L1_HOIST → 14#_L1_03` |
| **13→14 (目标2F)** | 13,1,"01" → 14,2,"05" | `[1] AGV: 13#_L1_01 → 13#_L1_HOIST` `[2] 提升机: 13#_L1_HOIST → 13#_L2_HOIST` `[3] AGV: 13#_L2_HOIST → BRIDGE_13_14` `[4] AGV: BRIDGE_13_14 → 14#_L2_01` `[5] AGV: 14#_L2_01 → 14#_L2_05` |
| **14→15** | 14,1,"01" → 15,4,"02" | `[1] AGV: 14#_L1_01 → 14#_L1_HOIST` `[2] 提升机: 14#_L1_HOIST → 14#_L2_HOIST` `[3] AGV: 14#_L2_HOIST → 15#_L2_01` `[4] AGV: 15#_L2_01 → 15#_L2_HOIST` `[5] 提升机: 15#_L2_HOIST → 15#_L4_HOIST` `[6] AGV: 15#_L4_HOIST → 15#_L4_02` |
| **13→15 完整链路** | 13,1,"01" → 15,4,"02" | `[1] AGV: 13#_L1_01 → 13#_L1_HOIST` `[2] 提升机: 13#_L1_HOIST → 13#_L2_HOIST` `[3] AGV: 13#_L2_HOIST → BRIDGE_13_14` `[4] AGV: BRIDGE_13_14 → 14#_L2_01` `[5] AGV: 14#_L2_01 → 15#_L2_01` `[6] AGV: 15#_L2_01 → 15#_L2_HOIST` `[7] 提升机: 15#_L2_HOIST → 15#_L4_HOIST` `[8] AGV: 15#_L4_HOIST → 15#_L4_02` |
| **14→15 2F直连** | 14,2,"01" → 15,2,"03" | `[1] AGV: 14#_L2_01 → 15#_L2_03` |
### 伪代码
LiveTaskQueue CreateTask( int b_s, int f_s, string loc_s, int b_e, int f_e, string loc_e, int taskType, int taskCategory, string palletBarcode, string materialCode) { var startPoint = $"{b_s}#L{f_s}{loc_s}"; var endPoint = $"{b_e}#L{f_e}{loc_e}"; var nodes = BFS(b_s, f_s, b_e, f_e); // 拓扑最短路径 string currentPos = startPoint;
for (int i = 0; i < nodes.Count - 1; i++)
{
var (b1, f1) = nodes[i];
var (b2, f2) = nodes[i + 1];
if (b1 != b2) // 跨栋
{
if (13↔14)
{
Add [AGV] currentPos → BRIDGE_13_14;
Add [AGV] BRIDGE_13_14 → DockingPoint(b2,f2);
}
else // 14↔15
{
Add [AGV] currentPos → DockingPoint(b2,f2);
}
currentPos = DockingPoint(b2,f2);
}
else if (f1 != f2) // 同栋跨层
{
var entry = $"{b1}#_L{f1}_HOIST";
var exit = $"{b2}#_L{f2}_HOIST";
Add [AGV] currentPos → entry;
Add [提升机] entry → exit;
currentPos = exit;
}
}
if (details.Count == 0 || currentPos != endPoint)
Add [AGV] currentPos → endPoint;
return new LiveTaskQueue { ... };
}
拓扑路由表
FindPath(b_s, f_s, b_e, f_e):
以 BFS 在拓扑图中搜索,典型路径:
(13,1) → (13,2): [(13,1), (13,2)]
(13,1) → (14,1): [(13,1), (13,2), (14,2), (14,1)]
(13,1) → (14,2): [(13,1), (13,2), (14,2)]
(13,1) → (15,2): [(13,1), (13,2), (14,2), (15,2)]
(13,1) → (15,4): [(13,1), (13,2), (14,2), (15,2), (15,4)]
(14,1) → (15,4): [(14,1), (14,2), (15,2), (15,4)]
(15,4) → (13,1): [(15,4), (15,2), (14,2), (13,2), (13,1)]
反向: 路径取反
四、调度触发链路
人工放货到接驳位
→ PLC 传感器检测到货物 → 接驳位状态 = 1
→ PLC 读 RFID → 得到托盘条码
→ WCS 收到事件: (positionCode, rfidBarcode)
→ 进入匹配调度逻辑
五、匹配调度逻辑
输入: positionCode (接驳位编码), rfidBarcode (RFID条码)
Step 1 ─ 匹配明细
查 LiveTaskDetail WHERE
palletBarcode == rfidBarcode
AND startPoint == positionCode
AND taskStatus == 1 (待执行)
未匹配 → 告警,返回
匹配到 detail
Step 2 ─ 前序依赖校验
查同 taskCode 下 objId < detail.objId 的所有明细
全部 taskStatus == 3 (已完成) ?
否 → 等待(不应发生,正常前序完成才会到这一步),返回
Step 3 ─ 接驳位加锁
获取该接驳位的锁(信号量)
二次确认 detail.taskStatus 仍为 1(防并发)
Step 4 ─ 标记执行中
detail.taskStatus = 2
若 LiveTaskQueue.taskStatus == 1 → 也改为 2
Step 5 ─ 下发设备
detail.deviceType:
1 → AGV
2 → 提升机
0 → 输送线
Step 6 ─ 结果处理
成功:
detail.taskStatus = 3
释放接驳位 (status = 0)
检查整个 task 是否全部完成 → taskStatus = 3
失败:
detail.taskStatus 回退为 1
下次重试
六、链条式执行(以 13#_1F → 15#_4F 为例)
Task: P001, 13#_L1_01 → 15#_L4_02
路径节点: (13,1) → (13,2) → (14,2) → (15,2) → (15,4)
Detail 1: AGV 13#_L1_01 → 13#_L1_HOIST
Detail 2: 提升机 13#_L1_HOIST → 13#_L2_HOIST
Detail 3: AGV 13#_L2_HOIST → BRIDGE_13_14
Detail 4: AGV BRIDGE_13_14 → 14#_L2_01
Detail 5: AGV 14#_L2_01 → 15#_L2_01
Detail 6: AGV 15#_L2_01 → 15#_L2_HOIST
Detail 7: 提升机 15#_L2_HOIST → 15#_L4_HOIST
Detail 8: AGV 15#_L4_HOIST → 15#_L4_02
════════════════════ 时间线 ════════════════════
T0: 人工放货到 13#_L1_01,读 RFID
→ 匹配 Detail 1,无前序 ✓ → 13# AGV 取走
→ 13#_L1_01 释放
T1: 13# AGV 运到 13#_L1_HOIST,放下
→ 匹配 Detail 2,前序 1 ✓
→ 13# 提升机启动,13#_L1_HOIST 释放
T2: 提升机运到 2F,到达 13#_L2_HOIST
→ 匹配 Detail 3,前序 1,2 ✓
→ 13# AGV 取走,运往廊桥
T3: 13# AGV 到达 BRIDGE_13_14 (2F廊桥)
→ 匹配 Detail 4,前序 1~3 ✓
→ 14# AGV 取走,BRIDGE_13_14 释放
T4: 14# AGV 运到 14#_L2_01,放下
→ 匹配 Detail 5,前序 1~4 ✓
→ 14# AGV 运往 15# (同层2F,直连)
T5: 14# AGV 到达 15#_L2_01,放下
→ 匹配 Detail 6,前序 1~5 ✓
→ 15# AGV 转运到提升机入口
T6: 15# AGV 到达 15#_L2_HOIST,放下
→ 匹配 Detail 7,前序 1~6 ✓
→ 15# 提升机启动
T7: 提升机运到 4F,到达 15#_L4_HOIST
→ 匹配 Detail 8,前序 1~7 ✓
→ 15# AGV 取走,运到 15#_L4_02
任务 P001 全部完成 ✓
七、并发模型
接驳位 A ─┐
接驳位 B ─┤ 各自独立并行(不同锁保护)
接驳位 C ─┘
同一接驳位内 → 串行(同一把锁保护)
不同任务在不同接驳位上可以同时执行,互不干扰。
八、异常场景
| 场景 | 处理 |
|---|---|
| RFID + 位置匹配不到待执行明细 | 货物放错位置或任务未创建 → 告警 |
| 前序明细未完成但货物已到 | 等待 |
| 设备下发失败 | detail 状态回退为 1,下次循环重试 |
| 同一明细被并发处理 | Step 3 二次确认防御 |
| 接驳位长时间占用不释放 | 需超时告警机制(后续) |
| 全部明细完成 | task 标记为已完成(status = 3) |
九、依赖设备信息
设备类型 (deviceType)
| 值 | 类型 | 对应服务 |
|---|---|---|
| 0 | 输送线 | PLC 直接控制 |
| 1 | AGV | HikRoBotServer (端口 5200) |
| 2 | 提升机 | HoistServer (端口 5100) |
客户端 API
HoistServer (端口 5100):
/api/hoist/receive-pallet- 接收托盘/api/hoist/task-run- 提升机启动/api/task/dispatch- 下发调度任务/api/hoist/free- 获取空闲提升机
HikRoBotServer (端口 5200):
/api/task/receive- 下发 AGV 任务