Files
bj_power/bj_power_mes/完整工单流转.md
T

618 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 完整工单流转手册(信号级)
> 本文档按 **信号 → 执行单元 → 状态变化** 的粒度,完整描述一个工单从建单到
> 全部工件下线的全过程:每一步"发生了什么、执行哪个最小单元、结果状态是什么
> (job 在哪/什么步骤、槽位谁占用、锁谁持有)"。
>
> 对应实现:`internal/line/executor.go`(任务→PLC 动作映射)、`internal/plcactions/executor.go`
> (信号序列)、`internal/device/*.go`(各设备模块状态推进)、`internal/robot/`
> (任务队列/优先级仲裁/预约制)、`internal/line/priority.go`(优先级配置加载)、
> `doc/signals.sql`(信号表)。
---
## 一、产线拓扑与资源
| 设备 | 类型码 | 槽位 | 说明 |
|------|--------|------|------|
| 接驳台 ×2 | DOCK | 各 42 槽 | 毛坯入口/成品出口;工件绑定取料槽位 |
| 磨床 | GRINDER | 1 槽 | 配油盘,M3600 加工完成 |
| 换向机构 | TURN_TABLE | 1 槽 | 方形件换向(PLC 自动旋转) |
| 气吹清洗 | AIR_BLOW | 1 槽 | 持料模型:机器人夹持吹洗,吹洗位不持工件 |
| 全检台 | FULL_CHECK | 6 槽 | 人工检验,批次检测(M2098 广播) |
| 缓存台 | CACHE | 1 槽 | 全检满时暂存待检件 |
| 机器人 | TEMP_SLOT(夹爪) | — | 全厂唯一共享资源,持料锁 |
**资源锁(resource_lock**
- `robot`:任务派发时持有、完成时释放(单在途任务互斥)
- `temp_slot`(夹爪):取料完成后归取料 job;放料完成释放;换料交接转移给前任
---
## 二、PLC 信号体系(M 区地址)
约定:**PC→PLC** 为软件触发信号(置 1 后 PLC 自复位);**PLC→PC** 为完成/状态通知
(信号轮询器每 1 秒读一遍全部状态信号、上升沿触发,speedFactor 加速时最快 50ms)。
每组操作前必须先写 **M2022 工件类型**
> 地址均为当前 DB signal 表种子配置(`doc/signals.sql`),程序启动时加载,
> 代码只写信号名、不写地址(详见 4.0)。
### 接驳台(M2000-M2019
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2000 | DockFetchReq | PC→PLC | 取料请求(byte,触发) |
| M2001 | DockFetchPos | PC→PLC | 取位置号(1-42) |
| M2002 | DockFetchTray | PC→PLC | 取料台号(1/2) |
| M2009 | DockFetchDone | PLC→PC | 取料完成 |
| M2010 | DockPlaceReq | PC→PLC | 放料请求(byte,触发) |
| M2011 | DockPlacePos | PC→PLC | 放位置号(1-42) |
| M2012 | DockPlaceTray | PC→PLC | 放料台号(1/2) |
| M2019 | DockPlaceDone | PLC→PC | 放料完成 |
### 相机(M2020-M2029
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2022 | CameraWorkType | PC→PLC | 工件类型(每组操作前必写) |
| M2027 | CameraShootResult | PLC→PC | 拍照结果(1=有料,0=无料) |
| M2029 | CameraShootDone | PLC→PC | 拍照完成 |
### 磨床(M2030-M2059
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2030 | GrinderFetchReq | PC→PLC | 取料请求 |
| M2039 | GrinderFetchDone | PLC→PC | 取料完成 |
| M2040 | GrinderPlaceReq | PC→PLC | 放料请求 |
| M2049 | GrinderPlaceDone | PLC→PC | 放料完成 |
| M2050 | GrinderExchangeReq | PC→PLC | 换料请求 |
| M2059 | GrinderExchangeDone | PLC→PC | 换料完成 |
### 换向机构(M2060-M2079
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2060 | TurnTableFetchReq | PC→PLC | 取料请求 |
| M2069 | TurnTableFetchDone | PLC→PC | 取料完成 |
| M2070 | TurnTablePlaceReq | PC→PLC | 放料请求 |
| M2079 | TurnTablePlaceDone | PLC→PC | 放料完成 |
| M3601.0/1 | TurnTableClamped/Released | PLC→PC | 夹紧/松开到位 |
### 全检换料(M2080-M2089
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2081 | FullCheckExchangeFetchPos | PC→PLC | 换料取位置号 |
| M2082 | FullCheckExchangePlacePos | PC→PLC | 换料放位置号 |
| M2080 | FullCheckExchangeReq | PC→PLC | 换料请求 |
| M2089 | FullCheckExchangeDone | PLC→PC | 换料完成 |
### 全检台(M2090-M2109
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2091 | FullCheckFetchPos | PC→PLC | 取位置号 |
| M2090 | FullCheckFetchReq | PC→PLC | 取料请求 |
| M2099 | FullCheckFetchDone | PLC→PC | 取料完成 |
| M2101 | FullCheckPlacePos | PC→PLC | 放位置号 |
| M2100 | FullCheckPlaceReq | PC→PLC | 放料请求 |
| M2109 | FullCheckPlaceDone | PLC→PC | 放料完成 |
| M2102 | FullCheckPlaceDoneRequestInspect | PC→PLC | 放料完成请求检验(纯脉冲不等) |
| M2098 | FullCheckInspectDone | PLC→PC | 检测完成(批次广播,全槽转 DONE) |
| M2108 | FullCheckDetecting | PLC→PC | 人工检验中 |
### 缓存台(M2110-M2129
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2111 | CacheFetchPos | PC→PLC | 取料位置号 |
| M2110 | CacheFetchReq | PC→PLC | 取料请求 |
| M2119 | CacheFetchDone | PLC→PC | 取料完成 |
| M2121 | CachePlacePos | PC→PLC | 放料位置号 |
| M2120 | CachePlaceReq | PC→PLC | 放料请求 |
| M2129 | CachePlaceDone | PLC→PC | 放料完成 |
### 清洗(M2130-M2139
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M2130 | WashStartReq | PC→PLC | 清洗请求(写 1 触发) |
| M2139 | AirBlowCompleteDone | PLC→PC | 清洗完成(**byte 型** |
### 设备状态位(M3600.x
| 地址 | 名称 | 方向 | 含义 |
|------|------|------|------|
| M3600.0 | GrinderMachiningDone | PLC→PC | 磨床加工完成 |
| M3600.1 | GrinderAllowEntry | PLC→PC | 磨床允许进入 |
| M3604.0/1 | Dock1Present/Dock2Present | PLC→PC | 接驳台在位 |
---
## 三、状态机总览
### job 状态(status
`CREATED(建单)``IN_HANDLING/PROCESSING/WAITING_UNLOAD/WAITING_DECISION`(流转中)
`COMPLETED(收尾)``SCRAPPED(报废)/SUSPENDED(暂停)` 为终态/挂起。
### job 步骤(current_step_id,圆形件主线)
```
OP10_LOAD(接驳台取料) → OP15_DECIDE(持毛坯等磨床) → OP20_LOAD/EXCHANGE(上磨床)
→ OP20_WAIT(等M3600) → OP30_LOAD(成品入夹爪待清洗) → OP35_DECIDE(清洗完成持料)
→ OP40_LOAD/EXCHANGE(放全检/换料) → OP40_BATCH_DECIDE(批次决策) → OP40_REQ(已发M2102)
→ OP40_WAIT(待检) → OP40_UNLOAD(检测完成待取) → OP60_LOAD(下料接驳台) → COMPLETED
```
方形件在 OP10 后先走 `OP25_TURN_LOAD → OP25_TURN_UNLOAD`(换向)再进磨床。
### 槽位状态(equipment_slot.status
`EMPTY` →(预约)→ `PROCESSING` →(落料完成)→ `OCCUPIED` →(M3600/M2098)→ `DONE`
→(取走)→ `EMPTY`。换料任务预约 **DONE** 槽位(取成品放毛坯一步完成)。
### 夹爪锁(temp_slot
`free` →(取料完成 acquire)→ `held_by_job` →(放料完成 release / 换料交接 handover)→ ...
---
## 四、最小执行单元全集(信号级)
### 4.0 通用骨架:一次 PLC 握手到底发生了什么
机器人单爪串行作业:一个最小执行单元 = 一个搬运任务 =
"从 A 点取件(装载)→ 机器人物理搬运(代码不参与)→ 放到 B 点(卸载)",
或**换料(交换式)**:到 A 点把设备里的**加工完成品**取走、同时把夹爪里
持着的工件放进 A 点——一个 Req 同时完成取+放(U11 磨床 / U12 全检 /
二次换料)。**注意夹爪里的工件不一定是毛坯**:磨床换料时是毛坯,全检
换料与二次换料时是成品(待检成品/缓存暂存成品),链式连续换料见 4.2。
**代码只负责两端与 PLC 的信号握手**,中间的物理搬运由机器人自行完成,
软件以 done 信号到达作为任务完成标志。
一次完整握手的六步(代码 `signalActionWithParams`,约束见 agent约束 6.4.1),
以 U1 接驳台取料为例逐步拆解:
| 步 | 代码动作 | U1 实例 | 为什么这么做 |
|----|---------|---------|-------------|
| 1 | 写工件类型 | 写 `CameraWorkType` = 产品配方 ID | PLC 按工件类型切换抓手/程序参数,每组动作前必须告知 |
| 2 | 连续写参数 | 写 `DockFetchPos`=槽号、`DockFetchTray`=台号 | 两个值是给同一个取料动作用的数据,**参数之间不停顿、连续写** |
| 3 | 空 1 秒 | `sleep(1s)` | 给 PLC 一个扫描周期消化参数(S7 通信异步),否则 Req 到了参数还没生效 |
| 4 | 写 Req=1 | 写 `DockFetchReq`=1 | 触发 PLC 执行取料。**只写 1 不写 0**:动作完成后 PLC 自行复位 |
| 5 | 空 1 秒 | `sleep(1s)` | 防止紧接着的下一次写操作覆盖本次写入 |
| 6 | 等 done | 等 `DockFetchDone` 上升沿 | 见下方"done 怎么等" |
**done 怎么等(不是轮询)**:派车时先把 done 信号名登记进 `doneWaits`
(在发 Req **之前**登记——因为第 5 步的 1 秒间隔里 PLC 往往已干完并回了
done,后登记会错过上升沿导致任务永久等待)。此后完全事件驱动:
PLC done 信号上升沿 → 信号轮询器 → `onSignal` → 匹配 `doneWaits`
`OnTaskDone` 广播任务完成。**没有 300ms 轮询**。
**M 地址从哪来?配置粒度分两层,如实说明**
1. **地址层(M2000/M2009…)已配置化**:地址存在 **DB signal 表**(种子数据
`doc/signals.sql`id=1 行即 DockFetchReq → M2000)。程序启动时 preload
全量加载进内存,执行时按信号名查地址,查不到直接报错"信号地址未配置,
请检查DB signal表"。**改地址只改 DB,不用改代码**。
2. **名称层(`DockFetchReq`…)仍是代码硬编码**:所有信号名集中定义在
`constants``Sig*` 常量(一处定义,执行器全部引用常量,不再散落
魔法字符串)。**改信号名仍需同步改 constants + DB**——"只改 DB"只对
地址层成立。
3. **动作→信号组映射**(如 DockFetch 动作 = Req+Done+Pos+Tray 一组)也
硬编码在 `plcactions` 方法内;如需现场按动作配置信号组,需给 signal 表
增加动作维度(架构级改造,未做)。
下文每个单元的序列按 `信号名(当前配置地址)` 书写:括号内地址是当前种子
配置值,现场可调(改 DB);信号名对应 constants 常量(改代码)。
### 4.1 前置条件:生成 vs 预约(两层,缺一不可)
每个单元的"前置"分两层,全部满足任务才会真正派车执行:
**第一层 · 生成条件**:设备模块周期巡检(`Evaluate`)时,按槽位状态 +
job 步骤判断"现在该干什么",生成任务**入队**。此时槽位状态没有任何变化。
**第二层 · 预约通过**:机器人仲裁器从队头取任务,派车前调用 `line.reserve`
- 目的=夹爪(一切取料类任务):查 `temp_slot` 锁。锁被**其他 job** 持有 →
预约失败,任务留队等下一轮(单爪防覆盖);free 或本人持 → 通过。
- 目的=设备槽位(放料/换料/清洗类):开事务 `SELECT ... FOR UPDATE` 锁住
槽位行(防并发抢占)→ 校验槽位状态符合预期(**换料预约 DONE**,其余
预约 EMPTY)→ 通过则写 **EMPTY/DONE → PROCESSING** + `current_job_id`
=本 job → 提交。状态不符 → 预约失败留队。
**槽位归属就是这一步写下的**,任务 done 时再由模块推进到 OCCUPIED。
- job 已终态(COMPLETED/SCRAPPED/CANCELLED)→ 任务直接作废丢弃。
**重点**:取料类任务**不会**把源槽位预约为 PROCESSING——源槽位只是任务
生成的依据(OCCUPIED/DONE),任务 done 时由模块直接写回 EMPTY。
下文每个单元按 `生成:…;预约:…` 两行书写前置。
### 4.2 换料与连续换料链(U11 / U12 / U15 的关系)
**一次换料 = 取成品 + 放夹爪工件同步完成**:一个 Req(磨床 M2050 /
全检 M2080)让 PLC 同时完成"取出设备里的加工完成品 + 把夹爪工件放进
空出的位置",夹爪工件随即变成设备里取出的成品(temp_slot 交接给成品
job)。三种换料的夹爪持件不同:
| 换料 | 夹爪持 | 设备槽 | 完成效果 |
|------|--------|--------|---------|
| U11 磨床换料 | **毛坯**(本 job) | 磨床 DONE 成品 | 毛坯开磨;成品入夹爪(交接给前任) |
| U12 全检换料 | **待检成品**(本 job,清洗完) | 全检 DONE 成品 | 待检件落全检;done 成品入夹爪(交接给前任) |
| 二次换料(U12 同序列) | **缓存暂存成品**(代做链 job) | 全检 DONE 成品 | 缓存件落全检;done 成品入夹爪(交接给 ProxyJobID 成品 job |
**连续换料链(continuous_exchange**:换料任务把 done 槽的成品带进夹爪
后,链 job 不是直接放全检,而是**代下料 → 回链决策点 → 再换**——每轮把
一件成品从全检 done 槽(或缓存)"换"出并下料,磨床不停机、全检不空闲:
```
链首 job(B):U12 换料(B 待检件→全检槽,A 成品入夹爪)
→ U9 代下料 A → 链决策点 OP61_DECIDE
→ 缓存有件:U15 代取缓存成品 C → 二次换料(C 落全检槽,D 成品入夹爪)
→ U9 代下料 D → 回 OP61_DECIDE,循环
→ 缓存无件 或 全检已满 2 件:链结束 → 批次决策(M2102)
```
全检模块每轮评估的决策优先级(evaluateTx,从上到下命中即止):
1. done 槽 + 持待检件 → **U12 换料**(最高优先,成品 carry 下料)
2. done 槽 + 二次换料等待 job(proxy 标记)→ 补发二次换料
3. 代做链 OP61_DECIDE:全检已满 2 件 → 批次决策;done 槽 + 缓存有件 →
U15 代取;缓存无件 → 链结束回批次决策;无 done → 等待(幂等重评),
**但本工单最后一件除外:回批次决策触发 M2102 收尾**2026-08-17 修复:
最后一件无后续换料/取料事件可等,原逻辑待检件永久等待)
4. done 槽 + **无换料链活跃** → U4 自取(链活跃时抑制自取,防止 done 槽
被抢走导致二次换料链断裂;链 job 离开换料步骤后恢复自取)
5. 空位 → U7 放料;全满 → U10 缓存暂存
### 4.3 任务队列与优先级仲裁(谁先派车)
**队列模型**:全部设备模块(接驳台/磨床/转台/全检/缓存)生成的任务进入**同一个**
机器人任务队列(`robot.Queue`),仲裁器(`robot.Arbiter`)从队头取任务 →
预约目的地成功 → 派车。机器人单爪,**同一时刻至多一个在途任务**;预约失败的
任务留队下一轮、让位队尾(`EnqueueBack`),保证每轮扫描覆盖全队不饿死新任务。
**优先级规则**`robot_task_priority` 表,数据驱动;数值越小越优先,改表重启即生效):
**组合优先级(2026-08-17 定稿)**:优先级语义为「任务类型 + 搬运源设备 + 目标设备」
三要素组合——一条搬运路径(如换向台→磨床链)是否优先由组合决定,任何设备组合
可单独配置,无需发明新任务类型。配置行 `from_type`/`to_type` 为 NULL 表示通配;
查找顺序(最具体优先):精确 (kind,from,to) → (kind,from) → (kind,to) → kind-only → 内置默认。
| 组合键(kind, from, to | 默认优先级 | 典型任务 | 业务含义 |
|---------|-----------|---------|---------|
| EXCHANGE | 0 | U11 磨床换料 / U12 全检换料 | 磨床不停机、全检链不断,最高 |
| **(UNLOAD, TURN_TABLE)** | **5** | **U3 换向取料** | **换向机构与磨床同链:换向件优先取走供磨床,磨床空时优先供料(组合优先)** |
| LOAD | 10 | U6 磨床放料 / U7 全检放料 / U8 转台放料 / U9 接驳台放料 | 上料类 |
| UNLOAD | 10 | U1 接驳台取料 / U2 磨床取料 / U4 全检取料 / U5 缓存取料 | 卸料类 |
| WASH | 10 | U13 清洗 | 持料模型 |
| RELOAD | 20 | U14 缓存回流 | 回流 |
| TRANSFER | 30 | U15 代取缓存件 | 普通转运 |
**同优先级 FIFO**:相同优先级的任务按入队时间先进先出(先入队先派车)。
**计数老化防饿死(确定性,非墙钟时间)**:任务每预约失败让位一次(`YieldCount` +1),
有效优先级提升一级(下限 0 与最高优先级平齐)——让位次数是**事件序**而非时间:
同样的状态序列必然产生同样的让位次数,调度结果可复现(正向状态机语义),
同时低优先级任务最终必然被调度,不会无限排队。
**持料隐式优先级**:夹爪被某 job 持有时,其他取料类任务(目的=夹爪)预约失败
→ 让位队尾;持料 job 的下一步任务(放料/换料/清洗)反而畅通——持料模型天然
把机器人让给"已持有工件"的 job 优先走完,这是优先级之外的隐式排序。
**换向取料组合优先级(业务硬规则,2026-08-17 修复)**
- 换向台有换向件(转台槽 DONE)时,**必须先取走换向件供磨床**
(组合行 `('UNLOAD','TURN_TABLE',NULL)`=5),不能先派接驳台取毛坯
(UNLOAD 通配=10)——换向机构与磨床同属磨床链,磨床空转等待会拖慢整线节拍。
- 历史缺陷:换向取料原与接驳台取料同为 UNLOAD(10),FIFO 下换向件排队滞留
转台、磨床空等(实测:TURN-FETCH 入队排第 6 位,DOCK-FETCH 先执行)。
- 修复演进:先新增 TURN_FETCH 任务类型(优先级 5)应急 → 被否(组合语义压扁成
单模块语义,无法覆盖"换向+其他设备"组合)→ 定稿为组合优先级:UNLOAD 类型
不变,换向取料优先由组合行表达,任意设备组合可配,不再发明新类型。
---
### U1 接驳台取料(DOCK→夹爪,毛坯上线)
- **生成**job 停在首道工序 OP10_LOADStepDockFetch)且已绑定接驳台槽位
AGV/人工放毛坯后槽位 OCCUPIED);未绑槽则静默跳过等送料
- **预约**:目的=夹爪 → `temp_slot` 必须 free(或本 job 已持);
**源槽位不预约**(仍是 OCCUPIED,不是 PROCESSING
- **序列**`CameraWorkType`(M2022)=配方ID → `DockFetchPos`(M2001)=槽号 →
`DockFetchTray`(M2002)=台号 → 1s → `DockFetchReq`(M2000)=1 → 1s →
`DockFetchDone`(M2009)
- **结果**:接驳台槽→EMPTY;夹爪→持本 job 毛坯(temp_slot 上锁);job→
OP15_DECIDE(圆形件,持毛坯等磨床)/ OP25_TURN_LOAD(方形件,先去换向)
### U2 磨床取料(GRINDER→夹爪,断料收尾专用)
- **生成**(磨床模块三条同时成立):
1. 磨床槽 **DONE**——M3600.0 上升沿(磨削完成)时模块把槽 OCCUPIED→DONE
成品仍挂在原 job 名下(current_job_id),job 停在 OP20_WAIT
2. **没有待磨件**(无 job 停在 OP15_DECIDE 持毛坯等磨床);
3. **没有在途磨床任务**(防重复评估重复生成)。
含义:磨完这一件后没有下一件毛坯可换,成品自己取走去清洗(断料收尾)。
- **预约**:目的=夹爪 → `temp_slot` 必须 free
- **序列**`CameraWorkType`(M2022) → `GrinderFetchReq`(M2030)=1 → 1s →
`GrinderFetchDone`(M2039)
- **结果**:磨床槽→EMPTY;夹爪→持本 job 成品;job→OP30_LOAD(持成品待清洗)
- **对比**:有毛坯待磨时磨床 DONE **不走本单元**,走 U11 换料(毛坯进、
成品出、磨床不停机)
### U3 转台取料(TURN_TABLE→夹爪,方形件换向后取回)
- **生成**:转台槽 **DONE** 且 job 在 OP25_TURN_UNLOAD——放料任务 done 时
槽位直接 OCCUPIED→DONETurnTablePlaceDone 到达即放料+换向完成),
模块随即生成取料任务
- **任务类型**:UNLOAD(组合优先级 `(UNLOAD, TURN_TABLE)`=5,配置表驱动)——
换向机构与磨床同链,换向件优先取走供磨床,高于普通卸料(UNLOAD 通配=10),
规则详见 4.3
- **预约**:目的=夹爪 → `temp_slot` 必须 free
- **序列**`CameraWorkType`(M2022) → `TurnTableFetchReq`(M2060)=1 → 1s →
`TurnTableFetchDone`(M2069)
- **结果**:转台槽→EMPTY;夹爪→持本 job 毛坯;job→OP15_DECIDE(继续去磨床)
### U4 全检取料(FULL_CHECK→夹爪,成品出全检)
- **生成**:全检槽 DONEM2098 批次检测完成时 OCCUPIED→DONE)且 job 在
OP40_UNLOAD;且**无换料链活跃**(有链 job 时成品由换料带走,不单独取料)
- **预约**:目的=夹爪 → `temp_slot` 必须 free
- **序列**`CameraWorkType`(M2022) → `FullCheckFetchPos`(M2091)=槽号 → 1s →
`FullCheckFetchReq`(M2090)=1 → 1s → 等 `FullCheckFetchDone`(M2099)
- **结果**:全检槽→EMPTY;夹爪→持本 job 成品;job→OP60_LOAD(待下料)
### U5 缓存取料(CACHE→夹爪,回流第一步)
- **生成**:缓存槽 OCCUPIED(全检满时暂存的待检件)且 job 在 OP50_UNLOAD
(待回流)
- **预约**:目的=夹爪 → `temp_slot` 必须 free
- **序列**`CameraWorkType`(M2022) → `CacheFetchPos`(M2111)=槽号 → 1s →
`CacheFetchReq`(M2110)=1 → 1s → 等 `CacheFetchDone`(M2119)
- **结果**:缓存槽→EMPTY;夹爪→持本 job 待检件;job→OP40_LOAD(待放全检)
### U6 磨床放料(夹爪→GRINDER,毛坯上磨床)
- **生成**:磨床槽 EMPTY 且 job 持毛坯在 OP15_DECIDE
- **预约**:目的=磨床槽 → FOR UPDATE 锁行 → **EMPTY→PROCESSING(owner=job)**
- **序列**`CameraWorkType`(M2022) → `GrinderPlaceReq`(M2040)=1 → 1s →
`GrinderPlaceDone`(M2049)
- **结果**:磨床槽→OCCUPIED(owner=job,磨削开始);夹爪释放;job→OP20_WAIT
(等 M3600
### U7 全检放料(夹爪→FULL_CHECK,成品送检)
- **生成**:全检有空槽 EMPTY 且 job 持成品在 OP35_DECIDE(清洗完持料等容量)
- **预约**:目的=全检空槽 → **EMPTY→PROCESSING(owner=job)**
- **序列**`CameraWorkType`(M2022) → `FullCheckPlacePos`(M2101)=槽号 → 1s →
`FullCheckPlaceReq`(M2100)=1 → 1s → 等 `FullCheckPlaceDone`(M2109)
- **结果**:全检槽→OCCUPIED(owner=job);夹爪释放;job→OP40_BATCH_DECIDE
批次已满 2 件或工单最后一件 → 立即发 `FullCheckPlaceDoneRequestInspect`
(M2102)=1 脉冲请求检验 → job→OP40_REQ;否则→OP40_WAIT 等下一件凑批
### U8 转台放料(夹爪→TURN_TABLE,方形件换向)
- **生成**:转台槽 EMPTY 且 job 持毛坯在 OP25_TURN_LOAD
- **预约**:目的=转台槽 → **EMPTY→PROCESSING(owner=job)**
- **序列**`CameraWorkType`(M2022) → `TurnTablePlaceReq`(M2070)=1 → 1s →
`TurnTablePlaceDone`(M2079)
- **结果**:转台槽→**DONE**(放料协议含 PLC 自动旋转,done 即换向完成);
夹爪释放;job→OP25_TURN_UNLOAD(随后立即生成 U3 取回)
### U9 接驳台放料(夹爪→DOCK,成品下线)
- **生成**:接驳台有 EMPTY 槽且 job 持成品在 OP60_LOAD
- **预约**:目的=接驳台槽 → **EMPTY→PROCESSING**;代下料场景槽位归属
PrevJobID(成品是谁的就归谁),自行下料归本人
- **序列**`CameraWorkType`(M2022) → `DockPlacePos`(M2011)=槽号 →
`DockPlaceTray`(M2012)=台号 → 1s → `DockPlaceReq`(M2010)=1 → 1s →
`DockPlaceDone`(M2019)
- **结果**:接驳台槽→OCCUPIED;夹爪释放;job→**COMPLETED**
FinishJobfinished_num+1,全部完成则工单 COMPLETED
### U10 缓存放料(夹爪→CACHE,全检满暂存)
- **生成**:全检 6 槽全满且缓存有空槽;job 持成品在 OP35_DECIDE
- **预约**:目的=缓存槽 → **EMPTY→PROCESSING(owner=job)**
- **序列**`CameraWorkType`(M2022) → `CachePlacePos`(M2121)=槽号 → 1s →
`CachePlaceReq`(M2120)=1 → 1s → 等 `CachePlaceDone`(M2129)
- **结果**:缓存槽→OCCUPIED`job.temp_slot_no` 记录暂存槽;夹爪释放;
job→OP50_UNLOAD(待回流,全检出空后走 U5+U7 回流)
### U11 磨床换料(夹爪毛坯 ↔ GRINDER 成品,磨床不停机)
- **生成**:磨床槽 DONE 且**有**待磨件(job 持毛坯在 OP15_DECIDE
——与 U2 互斥:有待磨件走换料,无待磨件走取料
- **预约**:目的=磨床槽 → FOR UPDATE 锁行 → **DONE→PROCESSING(owner=本
job)**;派车时校正 PrevJobID=槽位原主人(成品 job
- **序列**`CameraWorkType`(M2022) → `GrinderExchangeReq`(M2050)=1 → 1s →
`GrinderExchangeDone`(M2059)
- **结果**:磨床槽→OCCUPIED(owner=本 job,新毛坯开磨)**夹爪交接**:本 job
毛坯已放进磨床,夹爪转为持 PrevJobID 成品(temp_slot 锁转移);本 job→
OP20_WAITPrevJobID→OP30_LOAD(持成品待清洗)
### U12 全检换料(夹爪待检件 ↔ FULL_CHECK 成品)
- **生成**:全检有 done 槽,且存在换料链 job——持成品在 OP35_DECIDE 的 job
被判定为链首,done 槽归属链前任(成品 job)
- **预约**:目的=全检 done 槽 → **DONE→PROCESSING(owner=本 job)**
- **序列**`CameraWorkType`(M2022) → `FullCheckExchangeFetchPos`(M2081)=槽号
→ 1s → `FullCheckExchangePlacePos`(M2082)=槽号 → 1s →
`FullCheckExchangeReq`(M2080)=1 → 1s → 等 `FullCheckExchangeDone`(M2089)
(注:三条写信号之间**必须**各空 1 秒,与配方 wait_after_ms 一致,
缺间隔生产 PLC 会信号未生效即写下一条)
- **结果**done 槽→OCCUPIED(owner=本 job);成品入夹爪(PrevJobID 持有)→
OP60_EXCHANGE_LOAD(链 job 代下料);本 job→OP40_BATCH_DECIDE(待检件
已落全检,走批次决策)
### U13 清洗(夹爪持成品 → AIR_BLOW,持料模型)
- **生成**:吹洗位可用且 job 持成品在 OP30_LOAD
- **预约**:目的=吹洗位 → **EMPTY→PROCESSING**(清洗位物理不持工件,洗完
仍夹在夹爪上)
- **序列**`CameraWorkType`(M2022) → `WashStartReq`(M2130)=1 → 1s →
`AirBlowCompleteDone`(M2139)**信号名是 AirBlowCompleteDone**
旧名 WashCompleteDone 已不在信号表中,byte 型)
- **结果**:吹洗位→EMPTY;**夹爪不释放(持料模型)**job→OP35_DECIDE
(持成品等全检容量决策)
### U14 缓存回流(CACHE → FULL_CHECK,两步串行)
- **生成**:缓存槽 OCCUPIED(owner=job) 且全检有空槽;job 在 OP50_UNLOAD
- **预约**:目的=全检空槽 → **EMPTY→PROCESSING(owner=job)**(取缓存这半程
的夹爪锁由任务本身保证)
- **序列**U5 序列(`CacheFetchPos`(M2111)→`CacheFetchReq`(M2110)→等
`CacheFetchDone`(M2119))→ 立刻 U7 序列(`FullCheckPlacePos`(M2101)→
`FullCheckPlaceReq`(M2100)→等 `FullCheckPlaceDone`(M2109))→
`FullCheckPlaceDoneRequestInspect`(M2102) 脉冲(完成信号=放料完成)
- **结果**:缓存槽→EMPTY;全检槽→OCCUPIED(owner=job);夹爪释放;job→
OP40_BATCH_DECIDE
### U15 代取缓存件(CACHE→夹爪,换料链代做)
- **生成**:全检二次换料链需要缓存里的成品(缓存槽 OCCUPIED(owner=
ProxyJobID)),由链 job 持夹爪代取
- **预约**:目的=夹爪 → `temp_slot` 已由链 job 持有(本人持→通过)
- **序列**`CameraWorkType`(M2022) → `CacheFetchPos`(M2111)=槽号 → 1s →
`CacheFetchReq`(M2110)=1 → 1s → 等 `CacheFetchDone`(M2119)
- **结果**:缓存槽→EMPTY;夹爪→链 job 持缓存成品;job→OP40_EXCHANGE_2ND
(持缓存件去做二次换料)
---
## 五、完整工单流转(示例:11 件圆形件,1 磨床 / 6 槽全检 / 1 槽缓存 / 1 机器人)
### 阶段 0:建单与放料
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 0.1 | 新建工单,11 件圆形件(product_type=1 | 11 个 job=CREATED;工单=CREATED |
| 0.2 | 接驳台放毛坯:AGV/人工把 11 件放接驳台各槽 | 接驳台槽1-11=OCCUPIED(各绑定 job1-11);磨床=EMPTY;全检 6 槽=EMPTY;缓存=EMPTYtemp_slot=freerobot=idle |
### 阶段 1:首件上线(接驳台→磨床)
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 1.1 | 启动工单,接驳台模块评估:job1 绑槽1 → 生成 DOCK-FETCH 任务(U1) | 任务入队(源槽位不预约,派车前才查夹爪锁) |
| 1.2 | 派发 U1M2022 → M2001=1 → M2002=1 → 1s → M2000 → 等 M2009 | robot=held |
| 1.3 | **M2009 到达**:取料完成 | 接驳台槽1→EMPTYtemp_slot→job1(毛坯入夹爪);job1→OP10_LOAD→OP15_DECIDErobot=idle |
| 1.4 | 磨床模块评估:磨床 EMPTY + job1 持毛坯 → 生成 PLACE 任务(U6) | 派车前预约磨床槽 EMPTY→PROCESSING(owner=job1) |
| 1.5 | 派发 U6M2022 → M2040 → 等 M2049 | robot=held |
| 1.6 | **M2049 到达**:放料完成 | 磨床槽→OCCUPIED(owner=job1)temp_slot 释放;job1→OP20_WAITrobot=idle |
| 1.7 | 磨床开始磨削 job1 | job1=PROCESSING;磨床槽=OCCUPIED(job1) |
**结果**:job1 在磨床加工中;夹爪空;接驳台槽1 空(余 10 件)。
### 阶段 2:首件完成 M3600 与换料(磨床优先)
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 2.1 | **PLC M3600.0 置位**(磨削完成)→ 上升沿 → 磨床模块处理 | 磨床槽 OCCUPIED→DONE(owner=job1)job1 仍 OP20_WAIT(成品在磨床等出路) |
| 2.2 | 评估出路:job2 持毛坯在夹爪(OP15_DECIDE)→ 生成 EXCHANGE 任务(job2 换 job1 | 派车前预约磨床 DONE 槽→PROCESSING(owner=job2)PrevJobID 取槽位原主人 job1 |
| 2.3 | 派发 U11M2022 → M2050 → 等 M2059 | robot=held |
| 2.4 | **M2059 到达**:换料完成 | 磨床槽→OCCUPIED(owner=job2)job2→OP20_WAIT(开始磨);**temp_slot 交接 job2→job1**(成品入夹爪);job1→OP30_LOADrobot=idle |
**结果**:磨床磨 job2;夹爪持 job1 成品;磨床不停机(换料即取即放)。
### 阶段 3:清洗(持料模型)与放全检
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 3.1 | 清洗模块评估:job1 持成品 → 生成 WASH 任务(U13) | 预约吹洗位 |
| 3.2 | 派发 U13M2022 → M2130 → 等 M2139 | robot=heldjob1 持料贯穿(temp_slot 不释放) |
| 3.3 | **M2139 到达**:清洗完成 | 吹洗位→EMPTY**temp_slot 仍归 job1(持料模型)**job1→OP35_DECIDErobot=idle |
| 3.4 | 全检模块评估:全检有空槽 + job1 持成品 → 生成 PLACE 任务(U7) | 派车前预约全检空槽→PROCESSING(owner=job1) |
| 3.5 | 派发 U7M2022 → M2101=槽号 → 1s → M2100 → 等 M2109 → **M2102 请求检验**(脉冲) | robot=held |
| 3.6 | **M2109 到达**:放料完成 | 全检槽→OCCUPIED(owner=job1)**temp_slot 释放**job1→OP40_BATCH_DECIDErobot=idle |
| 3.7 | 批次决策:槽1 已 OCCUPIED 但未满 2 件且非末件 → 不发 M2102job1→OP40_WAIT | job1 在全检台待检;夹爪空 |
**结果**:job1 待检;夹爪空(持料在放全检后释放);磨床磨 job2。
### 阶段 4:批量流转(第 2-10 件循环)
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 4.1 | job2 M3600 → 换料给 job3(重复阶段 2);job2 清洗 → 放全检槽2(重复阶段 3) | 全检槽1=job1、槽2=job2 均 OCCUPIEDjob3 磨床加工 |
| 4.2 | 全检槽1+槽2 均待检 → **发 M2102 请求检验**(满 2 件批次规则) | job1/job2→OP40_REQ;人工检验开始 |
| 4.3 | **PLC M2098 置位**(批次检测完成广播)→ 上升沿 | 全检槽1/2 OCCUPIED→**DONE**job1/job2→OP40_UNLOAD(待取料) |
| 4.4 | 评估:job3 持成品在 OP35_DECIDE(清洗完)→ 生成**全检换料**任务(U12:job3 换 job1) | 任务入队;派车前预约槽1DONE)→PROCESSING(owner=job3)PrevJobID=job1 |
| 4.5 | 派发 U12M2081=槽1 → 1s → M2082=槽1 → 1s → M2080 → 等 M2089 | robot=held |
| 4.6 | **M2089 到达**:全检换料完成 | 槽1→OCCUPIED(owner=job3)(待检件落全检);**成品 job1 入夹爪**temp_slot→job1);job1→OP60_EXCHANGE_LOADjob3→OP40_BATCH_DECIDE |
| 4.7 | 全检评估:槽2 DONE 无换料者 → 生成**全检取料**任务(U4:job2 自取) | 任务入队;派车前查夹爪锁(源槽不预约) |
| 4.8 | 派发 U4M2022 → M2091=槽2 → 1s → M2090 → 等 M2099 | robot=held |
| 4.9 | **M2099 到达**:取料完成 | 槽2→EMPTYtemp_slot→job2job2→OP60_LOAD(持成品待下料) |
| 4.10 | 评估:job2 持成品 → 下料任务(U9,自行下料回自己取料槽位) | 派车前预约接驳台槽2(EMPTY→PROCESSINGowner=job2 |
| 4.11 | 派发 U9M2022 → M2011=槽2 → M2012=台号 → 1s → M2010 → 等 M2019 | robot=held |
| 4.12 | **M2019 到达**:下料完成 | 接驳台槽2→OCCUPIED(owner=job2)temp_slot 释放;**job2→COMPLETED**(工单 finished_num=1 |
| 4.13 | job1 换料下料(job1 持成品,U9)→ COMPLETED | 接驳台槽1→OCCUPIED(owner=job1)finished_num=2 |
| 4.14 | 循环:job4-10 重复阶段 2-4(磨床换料 → 清洗 → 全检换料/放料 → 批次 → 下料) | 每 2 件一批 M2102/M2098finished_num 递增 |
### 阶段 5:缓存暂存与回流(全检 6 槽满时)
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 5.1 | 全检 6 槽全 OCCUPIED(待检)且 job7 清洗完成持料 | job7=OP35_DECIDE(持成品)等全检空位 |
| 5.2 | 全检模块评估:无空槽 → 生成**缓存暂存**任务(U10) | 派车前预约缓存槽(EMPTY→PROCESSING |
| 5.3 | 派发 U10M2121 → 1s → M2120 → 等 M2129 | job7 持成品去缓存 |
| 5.4 | **M2129 到达**:暂存完成 | 缓存槽→OCCUPIED(owner=job7)job7.temp_slot_no=缓存槽;temp_slot 释放;job7→OP50_UNLOAD(待回流) |
| 5.5 | 全检批次 M2098 → 槽1-6 DONE → 逐步取走/换料 → 出现空槽 | 全检有空槽 |
| 5.6 | 缓存回流评估:缓存槽 OCCUPIED + 全检空槽 → 生成**回流**任务(U14) | 派车前预约全检空槽(EMPTY→PROCESSING |
| 5.7 | 派发 U14U5M2111→M2110→等 M2119)→ U7M2101→M2100→等 M2109 + M2102 | robot=held |
| 5.8 | **M2129 之后 M2109 到达**:回流完成 | 缓存槽→EMPTY;全检槽→OCCUPIED(owner=job7)temp_slot 释放;job7→OP40_BATCH_DECIDE(继续正常批次) |
### 阶段 6:全检+缓存全满的背压(no_space 场景:全检 2 槽+缓存 1 槽,15 件)
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 6.1 | job1/job2 磨完→清洗→放全检槽1/槽2 | 全检满(2 槽 OCCUPIED)→ 满 2 件即发 M2102 → 检验中 |
| 6.2 | job3 磨完→清洗→全检满→**缓存暂存**(U10 | 缓存槽→OCCUPIED(owner=job3)job3→OP50_UNLOAD |
| 6.3 | job4 磨完→清洗→全检满+缓存满→**无出路** | job4=OP35_DECIDE 持成品;temp_slot=job4(夹爪被占) |
| 6.4 | job5 毛坯取料任务(U1**预约夹爪失败**(被 job4 持)→ 留队老化 | 接驳台不再上料;没有新 job 进入 OP15_DECIDE |
| 6.5 | 磨床磨完 job5 → M3600 → DONE,但**无待磨件**(无 OP15_DECIDE)→ 断料收尾生成 U2 取料任务 | U2 预约夹爪仍失败(被 job4 持)→ 留队;磨床停在 DONE 等出路 |
| 6.6 | **全线背压**:磨床 DONE、夹爪被 job4 持、全检/缓存全满;唯一出路是 PLC **M2098**(批次检测完成) | 全检槽1/2→DONEjob1/job2→OP40_UNLOAD |
| 6.7 | job4 持成品 → **全检换料链**U12job4 换 job1)→ job1 成品入夹爪→代下料(U9)→ job1 COMPLETED | 槽1→OCCUPIED(owner=job4);夹爪释放后按优先级恢复:上料/断料取料/缓存回流依次解锁 |
背压断言:暂停期间不崩溃、不空转、无死循环;恢复后槽位归属守恒。
### 阶段 7:最后一件收尾(last_piece
| 步骤 | 发生 | 结果状态 |
|------|------|---------|
| 7.1 | job11 M3600(磨削完成)→ 评估:**无后继待磨件** → 不换料,生成断料取料任务(U2) | 任务入队;派车前查夹爪锁;无 EXCHANGE 任务 |
| 7.2 | 取料完成:磨床槽→EMPTYtemp_slot→job11job11→OP30_LOAD | 磨床空(收尾完成) |
| 7.3 | 清洗 → 放全检(U13→U7)→ 批次决策:**工单最后一件(IsLastPieceOfOrder)→ 必发 M2102** | job11→OP40_REQ;即使不满 2 件也收尾批次 |
| 7.4 | **M2098** → job11→OP40_UNLOAD → 取料(U4)→ 下料(U9 | 接驳台槽11→OCCUPIED(owner=job11)**job11→COMPLETED**finished_num=11 |
| 7.5 | FinishJob 检查:工单下全部 job 终态 → **工单→COMPLETED** | 工单完成;全部 11 件在接驳台槽位;temp_slot=freerobot=idle;磨床/全检/缓存全 EMPTY |
---
## 六、done 信号先后交替场景(done_cross
同一时刻可能有多条 done 信号在不同设备上先后/交叉到达,验证系统不会错配、错序、漏消费:
| 子场景 | 注入方式 | 系统行为 | 预期结果 |
|--------|---------|---------|---------|
| a. 磨床 done 迟到 | M3600.0 置位延迟 **10s**(mock 注入延迟) | 期间其他 job 正常流转(清洗/放全检不受影响);磨床 done 迟到达后仍被正确消费 | job 消费 M3600 **恰好 1 次**,不因迟到丢失、不重复推进 |
| b. 全检 done 提前(陈旧) | 启动 ~5s 注入一次 **M2098=1**(此时无 job 处于 OP40_REQ) | 全检模块陈旧信号守卫:`jobsAtStep(OP40_REQ)` 为空 → **忽略**(不广播、不推进) | 陈旧 M2098 被过滤,不误唤醒任何 job |
| c. 两信号交叉到达 | M3600(迟到 10s)与真实 M2098 到达时间重叠 | doneWaits 按**信号名**匹配(单机器人同一时刻至多一个在途任务);M2098 仅在**发过 M2102** 后消费(批次广播与触发请求严格配对) | 各 job 消费各自的 doneM2098 消费不早于本批 M2102 发送;无错配 |
**时序图(子场景 a+c 示意)**
```
job5 磨削中 ──────── M3600(迟到10s) ──► 磨床 DONE → 换料
job6 放全检 ── M2109 ──► M2102 请求检验 ──► M2098 批次广播 ──► 全检槽 DONE
(两信号在 ~10s 窗口内交叉到达,互不干扰)
```
**关键机制**
1. **上升沿检测**:信号从 0→1 才算一次事件(持续为 1 不重复触发)
2. **doneWaits 登记先行**:派发前先登记 done 等待,防完成信号先于登记到达(约束 6.4.1)
3. **陈旧守卫**M2098 只在有 job 处于 OP40_REQ(已发 M2102)时消费;M3600 只在有 OCCUPIED 槽时消费
4. **单在途任务**:机器人同一时刻只有一个在途任务,done 信号天然串行匹配
---
## 七、关键交互场景状态流转
### 7.1 磨床换料链(continuous_exchange4 件 A/B/C/D 预置)
```
A(持毛坯,OP15) ──换B──► 磨床 OCCUPIED(A) [B 成品入夹爪]
B(持成品) ──清洗──► 全检换料 ──► B 成品下料(OP60_EXCHANGE_LOAD)
B ──OP61_DECIDE 决策:缓存有 C → 代取 C(OP50_EXCHANGE_TAKE)
B(持 C) ──二次换料(OP40_EXCHANGE_2ND, proxy C)──► 全检 done 槽换主人 → D 成品入夹爪
D 下料(OP60_LOAD_2ND) ──► D COMPLETEDB 完成链 → 批次决策 OP40_BATCH_DECIDE
```
- ProxyJobID:代做链中缓存件 C 的槽位归属(预约/落位都按 ProxyJobID
- 断言:M2102 批次请求**恰好 1 次**;换料链 done 槽位不被自取任务抢走
hasActiveExchangeChain 保护)
### 7.2 持料模型(airblow_hold
- 清洗期间 robot 被本 job 持有(temp_slot 不释放)→ 其他 job 的取料任务预约失败留队
- **唯一释放点**:放全检(U7 完成,M2109 到达后 temp_slot release
- 断言:temp_slot 释放审计事件仅落料点 1 次、清洗完成 0 次;其他 job 清洗晚于本 job 放全检
### 7.3 槽位归还(dock_slot_return6 件跨 2 接驳台)
- 自行下料:成品回到**自己取料槽位**dock_equipment_id 绑定)
- 代下料(换料收尾件):回到 **PrevJobID 绑定槽位**(成品从哪来回哪去)
- 断言:接驳台 OCCUPIED 槽数守恒(每件成品恰好一个落位)
### 7.4 缓存回流(cache_reflow
- 全检满 → 缓存暂存(U10temp_slot_no 记录)
- 全检空 → 回流(U14:取缓存+放全检连续动作,一次任务完成)
- 断言:OP50_LOAD 暂存 → OP50_UNLOAD 回流步骤序列正确;temp_slot_no 清除
---
## 八、执行方式与验收标准
```bash
# 全量 14 场景回归(每场景独立 DB 环境)
go run ./cmd/mockrun/ -scenario <round|single_job|square|exchange|cache_reflow|
no_material|no_space|last_piece|mom|cancel_restart|continuous_exchange|
airblow_hold|dock_slot_return|done_cross> [-jobs N] [-timeout T] [-verify]
```
验收标准:每场景 `验证结果: N 通过, 0 失败`、exit=0、无 `level=ERROR` 日志。
场景与验证项见 `14个场景.md`;本文档为信号级流转依据。