# 高压清洗机批量上料/下料/换料设计 ## 背景 高压清洗机(设备ID=5,WASHER_HP)是2槽位 batch 设备。当前实现是"单次 Load + StartupWasher"模式,每次只上1个工件就启动清洗。 新需求:**灵活策略** — 有2个待清洗工件就上满2个再启动,只有1个就上1个启动。批量完成后逐槽位判断卸载/换料,有 Exchange 或 Load 就追加 StartupWasher。 ## 核心决策 1. **启动策略**:灵活 — 有2个待清洗就上满2个再启动,只有1个就上1个启动 2. **完成信号**:保持 batch 机制,一个信号 = 所有在加工槽位同时完成 3. **上料方式**:连续上料,中间无需等待 4. **换料策略**:逐槽位判断 — 有排队就 Exchange,没有就只卸载 5. **启动判断**:actions 中包含 Exchange 或 Load → 追加 StartupWasher;纯 Unload 不启动 ## 方案选择 **方案 A:调度器层面批量编排**(已确认) 在 `trySchedule` dispatch 阶段,当发现 WASHER_HP 相关候选时,查询槽位状态和配对情况,编排 `[]RobotAction` 数组一次性 dispatch。 理由: - 符合现有架构(调度器驱动,EventLoop 串行处理) - 不修改 Station 接口或 MockPLC 核心逻辑 - `handleMachineDone` 保持只改状态不编排 ## 设计细节 ### 1. RobotAction 数组 **`HardwareWorker.Execute` 接口变更**: 从 `Execute(ctx, RobotAction)` 改为 `Execute(ctx, []RobotAction)`。 Worker 串行执行数组中的每个 action,任一失败则中止后续 action,返回结果。 **`RobotWorker` 适配**: ```go func (w *RobotWorker) Execute(ctx context.Context, actions []action.RobotAction) error { for _, act := range actions { if err := w.executeSingle(ctx, act); err != nil { return err } } return nil } ``` ### 2. StartupWasher 作为 RobotAction 新增 `StartupWasher` 类型,与 Load/Unload/Exchange 同级。Worker 执行时调用 PLC 启动信号。 ```go // action.RobotAction 中 Kind 字段新增值 case "startup_washer": // 调用 robotCtrl.StartupWasher(ctx) ``` ### 3. 上料编排 **`trySchedule` dispatch 阶段新增逻辑**: 当候选任务包含 WASHER_HP Load 时: 1. 查询 WASHER_HP 槽位状态(空槽位数量) 2. 检查是否有第二个待清洗工件(`jobRuntimes` 中 `StepType_Load + ResourceType=WASHER_HP + OnBuffer`) 3. 编排 `[]RobotAction`: | 条件 | actions | |------|---------| | 1个空槽位 + 1个候选 | Load + StartupWasher | | 2个空槽位 + 2个候选 | Load1 + Load2 + StartupWasher | | 2个空槽位 + 1个候选 | Load + StartupWasher | ### 4. 卸载/换料编排 **`trySchedule` dispatch 阶段新增逻辑**: 当 dispatch 一个 WASHER_HP Unload 或 Exchange 时: 1. 检查 `batchMachines[machineID]` 是否为 true 2. 是 → 查询同设备另一个槽位状态 - 另一个槽位有 Done 工件 + 有配对 → 追加 Exchange - 另一个槽位有 Done 工件 + 无配对 → 追加 Unload - 另一个槽位空 + 有待上料工件 → 追加 Load - 另一个槽位空 + 无待上料工件 → 不追加 3. 否 → 正常单 action **完整场景表**: | 场景 | actions | 启动? | |------|---------|--------| | 2个 Done,0个配对 | Unload + Unload | 否 | | 2个 Done,1个配对 | Exchange + Unload + StartupWasher | 是 | | 2个 Done,2个配对 | Exchange + Exchange + StartupWasher | 是 | | 1个 Done,0个配对 | Unload | 否 | | 1个 Done,1个配对 | Exchange + Load + StartupWasher | 是 | **启动判断规则**:actions 中包含 Exchange 或 Load → 追加 StartupWasher。纯 Unload 不追加。 ### 5. handleWorkerResult 适配 Worker 返回结果后,`handleWorkerResult` 需要处理多 action 的结果: - 每个 action 的状态变更(位置更新、步骤推进)按顺序处理 - Exchange 成功 → Done 工件放暂存台 + 新工件上料到槽位 - Unload 成功 → Done 工件放暂存台 + 释放槽位 - Load 成功 → 新工件上料到槽位 - StartupWasher 成功 → 无状态变更(仅 PLC 信号) - 任一 action 失败 → 中止后续,已成功的 action 状态保留不变(不回滚),失败的 Job 标记为 ERROR **EquipmentSlot 一致性**:每个 action 成功后立即更新 `equipment_slot` 表状态(通过 DBState),确保 DB SSOT 与实际状态一致。后续 action 失败时,已更新的槽位状态反映真实物理状态(工件已在槽位中或已离开)。 ### 6. 时序保证 **不存在"等第二个工件"的中间态**。编排时已确定 Load 数量: - 1个候选 → `Load + StartupWasher`(上1个就启动,不等第二个) - 2个候选 → `Load1 + Load2 + StartupWasher`(上满2个再启动) Worker 串行执行,Load1 完成后立即执行 Load2,中间无等待。不存在"上了1个但还没启动,等第二个候选出现"的状态。 ### 7. 卸载优先级 当 `trySchedule` 发现 WASHER_HP 有 Done 槽位时,**立即处理**,不等待更多 Done 槽位出现。1个 Done 就 dispatch 对应的 Unload/Exchange,2个 Done 就一起编排。batch 信号保证所有 Occupied 槽位同时变为 Done,不会出现"先1个 Done,稍后另1个 Done"的情况。 ### 8. MarkAllOccupiedDone 对单槽位的正确性 `MarkAllOccupiedDone()` 将所有 Occupied 槽位标记为 Done。当只有1个 Occupied 槽位时,只标记那1个,空槽位不受影响。这与"1个 Done"场景一致,无需特殊处理。 ### 9. MockPLC 适配 当前 MockPLC 的 `washerDone` atomic 标志是"首个上料触发计时"。新逻辑下: - `StartupWasher` 作为独立 RobotAction,调用 `robotCtrl.StartupWasher(ctx)` 触发 PLC 启动信号 - MockPLC 收到启动信号后开始 4s 计时,到期后触发 WasherDone - MockPLC 核心逻辑不变:`triggerMachineDone` 中 `case 5` 的 `washerDone` atomic 防重复触发逻辑保留 - `executeLoadToMachine` 中 `machineID == 5` 时调用 `StartupWasher` 的逻辑移除,改为由 `[]RobotAction` 编排决定何时调用 ### 10. batch_unload 移除 当前 `candidateToRobotAction` 中有 `batch_unload` 逻辑(检查 `FindAllDoneJobsOnMachine` > 1 时批量卸载)。新逻辑下: - 卸载/换料由 `trySchedule` dispatch 阶段逐槽位编排 - `batch_unload` 逻辑移除 - `executeBatchUnload` 方法保留(用于 Worker 串行执行多个 Unload action) ## 改动文件清单 | 文件 | 改动 | |------|------| | `internal/eventloop/worker.go` | `Execute` 接口改为接收 `[]RobotAction` | | `internal/processor/robot_worker.go` | 适配新接口,串行执行 action 数组 | | `internal/eventloop/loop.go` | `trySchedule` dispatch 阶段新增 WASHER_HP 槽位查询 + 批量编排逻辑 | | `internal/eventloop/loop.go` | `handleWorkerResult` 适配多 action 结果处理 | | `internal/processor/robot_worker.go` | 移除 `executeLoadToMachine` 中 `machineID==5` 的 `StartupWasher` 调用 | | `internal/robot/washer.go` | 新增 `StartupWasher` RobotAction 执行逻辑 | | `internal/action/` | RobotAction Kind 新增 `"startup_washer"` | ## 不改动的部分 - `handleMachineDone` — 只改状态,不编排 - `WasherStation` — 槽位管理逻辑不变 - MockPLC 核心逻辑 — 不变 - 调度器 Generator/Filter/Policy — 候选任务生成逻辑不变