# 换盘任务批次指令与双流向设计 ## 概述 对换盘任务(ChangePallet)的取货/放货指令格式进行升级,支持批次编号和总数指令,并根据 `RobotSourceAddressLineCode` 区分两种流向。 ## 背景 当前换盘任务实现(上一轮迭代)仅处理了 `HandlePutFinishedStateAsync` 中的假电芯补充逻辑,指令格式为简单的 `Pickbattery,{地址}` 和 `Pickbattery,5,{start}-{end}`。现需要: 1. 所有换盘取货/放货指令统一为批次格式 `{Command},{位置},{start}-{end}` 2. 每批前发送总数指令 `PickTotalNum,{N}` / `PutTotalNum,{N}` 3. 根据源地址线体编码区分两种完全不同的操作流向 ## 指令格式 ### 批次指令 | 指令类型 | 格式 | 示例 | |----------|------|------| | 取货总数 | `PickTotalNum,{N}` | `PickTotalNum,11` | | 放货总数 | `PutTotalNum,{N}` | `PutTotalNum,11` | | 取货批次 | `Pickbattery,{位置},{start}-{end}` | `Pickbattery,3,1-4` | | 放货批次 | `Putbattery,{位置},{start}-{end}` | `Putbattery,6,5-8` | | 取货单个 | `Pickbattery,{位置},{n}-0` | `Pickbattery,3,11-0` | | 放货单个 | `Putbattery,{位置},{n}-0` | `Putbattery,6,11-0` | - `PickTotalNum/PutTotalNum` 仅换盘任务发送,每批取/放之前都发 - `N` = 真实电芯数量(即 `task.RobotTaskTotalNum`),机器人固件用此值判断托盘上正常电芯总数,两种流向均发送相同的 N 值 - 每批最多4个,不满4个按实际数发,剩1个时 end=0 ### 批次编号计算 `BuildBatchRange(currentIndex, remaining)` → `(start, end)`: - `remaining >= 4` → `(currentIndex, currentIndex + 3)` - `remaining > 1` → `(currentIndex, currentIndex + remaining - 1)` - `remaining == 1` → `(currentIndex, 0)` 示例(targetNormalCount=11):第1批 1-4,第2批 5-8,第3批 9-11 示例(targetNormalCount=9):第1批 1-4,第2批 5-8,第3批 9-0(单个,end=0) ## 两种流向 ### 流向A:补假电芯到目标托盘 **条件:** `RobotSourceAddressLineCode == "11001" || "11010"` **场景:** 目标托盘有 N 个正常电芯(N < 48),需从5号位假电芯托盘取假电芯补满48个。 **阶段流转:** ``` [取假电芯] Pickbattery,5,{positionIndex} ← 从5号位取,编号用平面点位表PositionIndex ↓ [放假电芯] Putbattery,{目标地址},{N+1}-{N+4} ← 放到目标托盘,编号从正常数+1递增 ↓ 重复直到补满48个 ``` 假电芯数量 = `48 - task.RobotTaskTotalNum` ### 流向B:取正常电芯 + 回收假电芯 **条件:** `RobotSourceAddressLineCode != "11001" && != "11010"` **场景:** 源托盘原本满48个(正常电芯 + 假电芯混合),先取走正常电芯放到目标托盘,再把源托盘上剩余的假电芯取出放回5号位。 **阶段流转:** ``` Phase 1 - 取正常电芯: [取正常] Pickbattery,{源地址},{1}-{4} ← 编号从1递增 ↓ [放正常] Putbattery,{目标地址},{1}-{4} ← 编号从1递增 ↓ 重复直到 N 个正常电芯全部取完 Phase 2 - 回收假电芯: [取假电芯] Pickbattery,{源地址},{N+1}-{N+4} ← 编号从正常数+1继续递增 ↓ [放假电芯] Putbattery,5,{positionIndex} ← 放回5号位,编号用平面点位表PositionIndex ↓ 重复直到假电芯全部回收(48-N 个) ``` ## 状态管理 ### RobotSocketState 新增字段 ```csharp /// /// 当前批次起始编号(用于递增计算取货/放货编号) /// public int CurrentBatchIndex { get; set; } = 1; /// /// 换盘任务当前阶段 /// /// /// 0: 未开始 /// 1: 取正常电芯 / 取假电芯(流向A) /// 2: 放正常电芯 / 放假电芯(流向A) /// 3: 取假电芯(流向B Phase2) /// 4: 放假电芯到5号位(流向B Phase2) /// public int ChangePalletPhase { get; set; } ``` ### CurrentBatchIndex 生命周期 **流向A:** - Phase 0→1:`CurrentBatchIndex = 1`(初始化,用于 PositionIndex 查询起点) - Phase 1→2:不重置(放货编号从 `targetNormalCount + 1` 开始,由 Orchestrator 计算) - Phase 2→1:`CurrentBatchIndex += 本批数量`(递增,下一批继续) - 完成→0:`CurrentBatchIndex = 1`(重置) **流向B:** - Phase 0→1:`CurrentBatchIndex = 1`(取正常电芯从1开始) - Phase 1→2:不重置(放货编号与取货编号一致) - Phase 2→1:`CurrentBatchIndex += 本批数量`(递增) - Phase 2→3(正常电芯取完,即 `state.RobotTaskTotalNum >= targetNormalCount` 时 putfinished 完成后触发):`CurrentBatchIndex = targetNormalCount + 1`(假电芯从正常数+1开始) - Phase 3→4:不重置(放假电芯用 PositionIndex,由 Orchestrator 计算) - Phase 4→3:`CurrentBatchIndex += 本批数量`(递增) - 完成→0:`CurrentBatchIndex = 1`(重置) ### 阶段流转状态机 **流向A:** ``` Phase 0 → Phase 1(取假电芯from 5号位)→ Phase 2(放假电芯to 目标)→ Phase 1 → ... → Phase 0(完成) ``` **流向B:** ``` Phase 0 → Phase 1(取正常from 源)→ Phase 2(放正常to 目标)→ Phase 1 → ... → Phase 3(取假电芯from 源)→ Phase 4(放假电芯to 5号位)→ Phase 3 → ... → Phase 0(完成) ``` ## 代码改动范围 | 文件 | 改动 | |------|------| | `RobotSocketState.cs` | +`CurrentBatchIndex`, +`ChangePalletPhase` | | `RobotTaskProcessor.cs` | +`BuildBatchRange()`, +`SendPickWithBatchAsync()`, +`SendPutWithBatchAsync()`, 修改现有假电芯方法 | | `RobotWorkflowOrchestrator.cs` | 重写 ChangePallet 分支(HandlePutFinishedStateAsync + HandlePickFinishedStateAsync) | | `RobotPrefixCommandHandler.cs` | HandlePutFinishedAsync 中区分换盘阶段:假电芯放货不调用 ChangePalletAsync API,不递增 `RobotTaskTotalNum`;HandlePickFinishedAsync 中假电芯取货不调用拆盘 API | | `RobotSimpleCommandHandler.cs` | allpickfinished/allputfinished 中增加 `ChangePalletPhase` 守卫:仅当 Phase==0(所有阶段完成)时才触发入库和删除任务,中间阶段不处理 | | `IFakeBatteryPositionService.cs` | +`MarkAsAvailable(List positions)` 方法 | | `FakeBatteryPositionService.cs` | +`MarkAsAvailable` 实现 | | `IFakeBatteryPositionRepository.cs` | +`MarkAsAvailable(List positions)` 方法 | | `FakeBatteryPositionRepository.cs` | +`MarkAsAvailable` 实现(将指定点位 IsUsed 设为 false) | ## 命令处理器交互 ### RobotPrefixCommandHandler 变更 **pickfinished 处理:** - 当 `ChangePalletPhase == 3`(流向B取假电芯)时,不调用拆盘 API,仅更新状态 - 其他阶段保持现有逻辑 **putfinished 处理:** - 判断流向:通过 `state.CurrentTask.RobotSourceAddressLineCode` 判断是流向A还是流向B - 当 `ChangePalletPhase == 2` 且流向B(放正常电芯)时,正常调用 ChangePalletAsync API,`state.RobotTaskTotalNum += positions.Length` - 当 `ChangePalletPhase == 4`(流向B放假电芯到5号位)时,不调用 API,不递增 `RobotTaskTotalNum`,调用 `MarkAsAvailable(positions)` 释放点位 - 当 `ChangePalletPhase == 2` 且流向A(放假电芯到目标)时,不调用 API,不递增 `RobotTaskTotalNum` ### RobotSimpleCommandHandler 变更 **allpickfinished / allputfinished:** - 增加守卫条件:仅当 `state.ChangePalletPhase == 0`(所有阶段完成)时,才执行入库回传和任务删除 - 中间阶段收到 allpickfinished/allputfinished 时,仅更新 `state.CurrentAction`,不触发入库逻辑 - 任务完成时额外重置:`state.ChangePalletPhase = 0`, `state.CurrentBatchIndex = 1`, `state.IsInFakeBatteryMode = false` ## 边界条件 - `task.RobotTaskTotalNum == 48`:直接走原有逻辑,不进入批次模式 - `task.RobotTaskTotalNum == 0`:流向A 补满48个假电芯;流向B 跳过 Phase 1/2,直接进入 Phase 3/4 回收48个假电芯 - 假电芯平面点位不足:记录错误日志,中止当前批次 - 假电芯点位碎片化:`GetNextAvailable` 要求同行连续,如果请求4个找不到,依次尝试3、2、1个 - 批次编号溢出(超过48):不应发生,但需防御性检查