# 换盘任务批次指令与双流向实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 升级换盘任务取货/放货指令格式为批次模式,支持 PickTotalNum/PutTotalNum,并根据 RobotSourceAddressLineCode 区分两种流向
**Architecture:**
- RobotSocketState 新增批次状态字段(CurrentBatchIndex, ChangePalletPhase)
- RobotTaskProcessor 新增批次指令构建方法(BuildBatchRange, SendPickWithBatchAsync, SendPutWithBatchAsync)
- RobotWorkflowOrchestrator 重写 HandlePutFinishedStateAsync 和 HandlePickFinishedStateAsync 实现两种流向的状态机
- RobotPrefixCommandHandler 和 RobotSimpleCommandHandler 根据阶段区分处理逻辑
- 假电芯服务层新增 MarkAsAvailable 方法用于释放点位
**Tech Stack:** C# / .NET 8, SqlSugar ORM, Redis缓存
---
## File Structure
```
WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/
= RobotSocketState.cs # +CurrentBatchIndex, +ChangePalletPhase
= RobotTaskProcessor.cs # +BuildBatchRange, +SendPickWithBatchAsync, +SendPutWithBatchAsync
= Workflow/RobotWorkflowOrchestrator.cs # 重写 HandlePutFinishedStateAsync + HandlePickFinishedStateAsync
= Workflow/RobotPrefixCommandHandler.cs # putfinished/pickfinished 阶段判断
= Workflow/RobotSimpleCommandHandler.cs # allpickfinished/allputfinished 阶段守卫
WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/
= IFakeBatteryPositionService.cs # +MarkAsAvailable
WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/
= FakeBatteryPositionService.cs # +MarkAsAvailable
WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/
= IFakeBatteryPositionRepository.cs # +MarkAsAvailable
WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/
= FakeBatteryPositionRepository.cs # +MarkAsAvailable
```
---
## Task 1: RobotSocketState 新增批次状态字段
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs`
- [ ] **Step 1: 添加 CurrentBatchIndex 和 ChangePalletPhase 属性**
在 `IsInFakeBatteryMode` 属性后添加:
```csharp
///
/// 是否处于假电芯补充模式
///
public bool IsInFakeBatteryMode { get; set; }
///
/// 当前批次起始编号(用于递增计算取货/放货编号)
///
///
/// 在批次模式下,每批取货/放货的起始编号从1开始递增。
/// 用于计算 {start}-{end} 格式中的 start 值。
///
public int CurrentBatchIndex { get; set; } = 1;
///
/// 换盘任务当前阶段
///
///
/// 阶段定义:
/// 0: 未开始
/// 1: 取正常电芯(流向B) / 取假电芯(流向A)
/// 2: 放正常电芯(流向B) / 放假电芯(流向A)
/// 3: 取假电芯(流向B Phase2)
/// 4: 放假电芯到5号位(流向B Phase2)
///
public int ChangePalletPhase { get; set; }
```
- [ ] **Step 2: 验证编译**
```bash
cd D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 3: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
git commit -m "feat(Robot): RobotSocketState 新增 CurrentBatchIndex 和 ChangePalletPhase"
```
---
## Task 2: 假电芯仓储层新增 MarkAsAvailable 方法
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs`
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs`
- [ ] **Step 1: 在接口中添加 MarkAsAvailable 方法声明(在 MarkAsUsed 方法后)**
在 `IFakeBatteryPositionRepository.cs` 的 `MarkAsUsed` 方法后添加:
```csharp
///
/// 标记指定点位为可用(释放点位)
///
/// 点位索引列表
/// 是否成功
bool MarkAsAvailable(List positions);
```
- [ ] **Step 2: 在仓储实现中添加 MarkAsAvailable 方法(在 MarkAsUsed 方法后)**
在 `FakeBatteryPositionRepository.cs` 的 `MarkAsUsed` 方法后添加:
```csharp
///
public bool MarkAsAvailable(List positions)
{
if (positions == null || positions.Count == 0)
return true;
return Db.Updateable()
.SetColumns(x => x.IsUsed, false)
.Where(x => positions.Contains(x.PositionIndex))
.ExecuteCommand() > 0;
}
```
- [ ] **Step 3: 验证编译**
```bash
dotnet build WIDESEAWCS_Server.sln
```
Expected: 0 errors
- [ ] **Step 4: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs
git commit -m "feat(Robot): 假电芯仓储层新增 MarkAsAvailable 方法"
```
---
## Task 3: 假电芯服务层新增 MarkAsAvailable 方法
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs`
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs`
- [ ] **Step 1: 在服务接口中添加 MarkAsAvailable 方法声明(在 MarkAsUsed 后)**
在 `IFakeBatteryPositionService.cs` 的 `MarkAsUsed` 方法后添加:
```csharp
///
/// 标记指定点位为可用(释放点位)
///
/// 点位索引列表
/// 是否成功
bool MarkAsAvailable(List positions);
```
- [ ] **Step 2: 在服务实现中添加 MarkAsAvailable 方法**
```csharp
///
public bool MarkAsAvailable(List positions)
{
return BaseDal.MarkAsAvailable(positions);
}
```
- [ ] **Step 3: 验证编译**
```bash
dotnet build WIDESEAWCS_Server.sln
```
Expected: 0 errors
- [ ] **Step 4: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs
git commit -m "feat(Robot): 假电芯服务层新增 MarkAsAvailable 方法"
```
---
## Task 4: RobotTaskProcessor 新增批次指令辅助方法
**注意:** `_fakeBatteryPositionService` 字段已在上一轮迭代中添加,无需重复注入。
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs`
- [ ] **Step 1: 添加 BuildBatchRange 辅助方法**
在 `GetNextAvailableFakeBatteryPositions` 方法后添加:
```csharp
///
/// 计算批次编号范围
///
///
/// 返回格式:(start, end)
/// - remaining >= 4: (currentIndex, currentIndex + 3)
/// - remaining > 1: (currentIndex, currentIndex + remaining - 1)
/// - remaining == 1: (currentIndex, 0) -- 单个物品用 0 表示 end
///
/// 当前批次起始编号
/// 剩余数量
/// (start, end) 元组
public (int Start, int End) BuildBatchRange(int currentIndex, int remaining)
{
if (remaining >= 4)
return (currentIndex, currentIndex + 3);
else if (remaining > 1)
return (currentIndex, currentIndex + remaining - 1);
else // remaining == 1
return (currentIndex, 0);
}
```
- [ ] **Step 2: 添加 SendPickWithBatchAsync 方法**
```csharp
///
/// 下发取货指令(带批次格式和总数)
///
///
/// 发送顺序:
/// 1. PickTotalNum,{N} -- 真实电芯总数
/// 2. Pickbattery,{位置},{start}-{end} -- 批次取货指令
///
/// 下发成功后更新任务状态为"机器人执行中"。
///
/// 要下发的任务对象
/// 机器人当前状态
/// 取货位置
/// 批次起始编号
/// 批次结束编号
public async Task SendPickWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
{
// 先发送总数指令
string totalNumCmd = $"PickTotalNum,{task.RobotTaskTotalNum}";
await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
// 再发送批次取货指令
string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
string taskString = $"Pickbattery,{position},{range}";
bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
if (result)
{
_logger.LogInformation("下发批次取货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
taskString, range, state.RobotCrane?.DeviceName);
QuartzLogger.Info($"下发批次取货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
state.CurrentTask = task;
if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
{
await _robotTaskService.UpdateRobotTaskAsync(task);
}
}
else
{
_logger.LogError("下发批次取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
QuartzLogger.Error($"下发批次取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
}
}
```
- [ ] **Step 3: 添加 SendPutWithBatchAsync 方法**
```csharp
///
/// 下发放货指令(带批次格式和总数)
///
///
/// 发送顺序:
/// 1. PutTotalNum,{N} -- 真实电芯总数
/// 2. Putbattery,{位置},{start}-{end} -- 批次放货指令
///
/// 下发成功后更新任务状态为"机器人执行中"。
///
/// 要下发的任务对象
/// 机器人当前状态
/// 放货位置
/// 批次起始编号
/// 批次结束编号
public async Task SendPutWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
{
// 先发送总数指令
string totalNumCmd = $"PutTotalNum,{task.RobotTaskTotalNum}";
await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
// 再发送批次放货指令
string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
string taskString = $"Putbattery,{position},{range}";
bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
if (result)
{
_logger.LogInformation("下发放货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
taskString, range, state.RobotCrane?.DeviceName);
QuartzLogger.Info($"下发放货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
state.CurrentTask = task;
if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
{
await _robotTaskService.UpdateRobotTaskAsync(task);
}
}
else
{
_logger.LogError("下发放货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
}
}
```
- [ ] **Step 4: 验证编译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 5: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
git commit -m "feat(Robot): RobotTaskProcessor 新增批次指令辅助方法"
```
---
## Task 5: RobotSimpleCommandHandler 添加阶段守卫
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs`
- [ ] **Step 1: 修改 allpickfinished 分支**
找到 `case "allpickfinished":` 分支,替换为:
```csharp
// 全部取货完成
case "allpickfinished":
{
state.CurrentAction = "AllPickFinished";
var currentTask = state.CurrentTask;
if (currentTask == null)
return false;
var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
// 换盘任务:仅当所有阶段完成时才处理入库
if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
{
if (state.ChangePalletPhase == 0)
{
// 所有阶段完成,处理入库
if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
{
_taskProcessor.DeleteTask(currentTask.RobotTaskId);
await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
// 重置批次状态
state.ChangePalletPhase = 0;
state.CurrentBatchIndex = 1;
state.IsInFakeBatteryMode = false;
return true;
}
}
// 中间阶段不处理,仅更新状态
return true;
}
// 拆盘任务:直接处理入库
if (robotTaskType == RobotTaskTypeEnum.SplitPallet)
{
if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
{
_taskProcessor.DeleteTask(currentTask.RobotTaskId);
await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
return true;
}
}
return false;
}
```
- [ ] **Step 2: 修改 allputfinished 分支**
找到 `case "allputfinished":` 分支,替换为:
```csharp
// 全部放货完成
case "allputfinished":
{
state.CurrentAction = "AllPutFinished";
var currentTask = state.CurrentTask;
if (currentTask == null)
return false;
var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
// 换盘任务:仅当所有阶段完成时才处理入库
if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
{
if (state.ChangePalletPhase == 0)
{
// 所有阶段完成,处理入库
if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
{
_taskProcessor.DeleteTask(currentTask.RobotTaskId);
state.CurrentTask = null;
state.RobotTaskTotalNum = 0;
state.CellBarcode = new List();
await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
QuartzLogger.Info($"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
// 重置批次状态
state.ChangePalletPhase = 0;
state.CurrentBatchIndex = 1;
state.IsInFakeBatteryMode = false;
return true;
}
}
// 中间阶段不处理,仅更新状态
return true;
}
// 组盘任务:直接处理入库
if (robotTaskType == RobotTaskTypeEnum.GroupPallet)
{
if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
{
_taskProcessor.DeleteTask(currentTask.RobotTaskId);
state.CurrentTask = null;
state.RobotTaskTotalNum = 0;
state.CellBarcode = new List();
await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
QuartzLogger.Info($"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
return true;
}
}
return false;
}
```
- [ ] **Step 3: 验证编译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 4: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
git commit -m "feat(Robot): RobotSimpleCommandHandler 换盘任务添加 ChangePalletPhase 阶段守卫"
```
---
## Task 6: RobotPrefixCommandHandler 修改 putfinished/pickfinished 处理逻辑
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs`
- [ ] **Step 1: 添加 IFakeBatteryPositionService 依赖**
在类顶部添加字段和构造函数参数:
```csharp
///
/// 假电芯位置服务
///
private readonly IFakeBatteryPositionService _fakeBatteryPositionService;
public RobotPrefixCommandHandler(
IRobotTaskService robotTaskService,
RobotTaskProcessor taskProcessor,
RobotStateManager stateManager,
ISocketClientGateway socketClientGateway,
IFakeBatteryPositionService fakeBatteryPositionService)
{
_robotTaskService = robotTaskService;
_taskProcessor = taskProcessor;
_stateManager = stateManager;
_socketClientGateway = socketClientGateway;
_fakeBatteryPositionService = fakeBatteryPositionService;
}
```
- [ ] **Step 2: 修改 HandlePutFinishedAsync 方法中的放货处理逻辑**
找到 `if (putSuccess)` 块(现有代码约在第258行附近),替换其中的放货成功处理逻辑为:
```csharp
// 如果放货成功
if (putSuccess)
{
state.CurrentAction = "PutFinished";
// 判断是否为换盘任务且处于批次模式
var isChangePallet = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
var isFlowA = state.CurrentTask?.RobotSourceAddressLineCode is "11001" or "11010";
if (isChangePallet)
{
if (state.ChangePalletPhase == 2)
{
if (isFlowA)
{
// 流向A Phase 2:放假电芯到目标托盘,不调用 API,不递增计数
// 不做任何额外处理,仅更新状态
}
else
{
// 流向B Phase 2:放正常电芯,递增计数
state.RobotTaskTotalNum += positions.Length;
if (task != null)
task.RobotTaskTotalNum -= positions.Length;
}
}
else if (state.ChangePalletPhase == 4)
{
// 流向B Phase 4:放假电芯到5号位,不调用 API,不递增计数,释放点位
_fakeBatteryPositionService.MarkAsAvailable(positions.ToList());
}
else
{
// 非批次模式或非换盘任务:递增计数
state.RobotTaskTotalNum += positions.Length;
if (task != null)
task.RobotTaskTotalNum -= positions.Length;
}
}
else
{
// 非换盘任务:原有逻辑
state.RobotTaskTotalNum += positions.Length;
if (task != null)
task.RobotTaskTotalNum -= positions.Length;
}
}
```
- [ ] **Step 3: 修改 HandlePickFinishedAsync 方法**
找到 `if (state.IsSplitPallet)` 块(现有代码约在第173-198行),替换为:
```csharp
// 如果是拆盘任务
if (state.IsSplitPallet)
{
var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
state.LastPickPositions = positions;
var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
if (result.Data.Status && result.IsSuccess)
{
state.CurrentAction = "PickFinished";
}
}
// 换盘任务取假电芯时(Phase 3)不调用拆盘 API
else if (state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
&& state.ChangePalletPhase == 3)
{
state.CurrentAction = "PickFinished";
state.LastPickPositions = positions;
}
else
{
state.CurrentAction = "PickFinished";
state.LastPickPositions = positions;
}
```
- [ ] **Step 4: 验证编译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 5: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
git commit -m "feat(Robot): RobotPrefixCommandHandler 换盘任务根据阶段区分处理逻辑"
```
---
## Task 7: RobotWorkflowOrchestrator 重写 HandlePutFinishedStateAsync 的 ChangePallet 分支
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs`
- [ ] **Step 1: 替换 ChangePallet 分支逻辑**
找到 `else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())` 分支,将其完整替换为:
```csharp
else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
{
const int targetTotal = 48;
int targetNormalCount = task.RobotTaskTotalNum;
int currentCompletedCount = stateForUpdate.RobotTaskTotalNum;
// 判断流向(null-safe)
bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
// 目标数量为48:直接走原有逻辑,不进入批次模式
if (targetNormalCount == targetTotal)
{
await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
return;
}
// 初始化批次模式
if (stateForUpdate.ChangePalletPhase == 0)
{
stateForUpdate.ChangePalletPhase = 1;
stateForUpdate.CurrentBatchIndex = 1;
_logger.LogInformation("HandlePutFinishedStateAsync:换盘任务进入批次模式,任务号: {TaskNum},流向: {Flow}",
task.RobotTaskNum, isFlowA ? "A" : "B");
}
// ==================== 流向A:补假电芯到目标托盘 ====================
if (isFlowA)
{
// Phase 1: 取假电芯(从5号位,使用 PositionIndex)
if (stateForUpdate.ChangePalletPhase == 1)
{
int remaining = targetTotal - currentCompletedCount;
if (remaining <= 0)
{
stateForUpdate.ChangePalletPhase = 0;
stateForUpdate.CurrentBatchIndex = 1;
stateForUpdate.IsInFakeBatteryMode = false;
_logger.LogInformation("HandlePutFinishedStateAsync:流向A完成,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
int pickCount = Math.Min(4, remaining);
var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(pickCount);
if (positions.Count == 0)
{
_logger.LogError("HandlePutFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions);
stateForUpdate.ChangePalletPhase = 2;
}
// Phase 2: 放假电芯到目标托盘(从 targetNormalCount+1 开始递增)
else if (stateForUpdate.ChangePalletPhase == 2)
{
int remaining = targetTotal - currentCompletedCount;
if (remaining <= 0)
{
stateForUpdate.ChangePalletPhase = 0;
stateForUpdate.CurrentBatchIndex = 1;
stateForUpdate.IsInFakeBatteryMode = false;
_logger.LogInformation("HandlePutFinishedStateAsync:流向A完成,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
// 计算放货批次编号:从 targetNormalCount + 1 开始
int batchStart = targetNormalCount + 1 + (stateForUpdate.CurrentBatchIndex - 1);
int putCount = Math.Min(4, remaining);
var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end);
stateForUpdate.CurrentBatchIndex += putCount;
stateForUpdate.ChangePalletPhase = 1;
}
}
// ==================== 流向B:取正常电芯 + 回收假电芯 ====================
else
{
// Phase 1: 取正常电芯(从源地址,从1开始递增)
if (stateForUpdate.ChangePalletPhase == 1)
{
int remainingNormal = targetNormalCount - currentCompletedCount;
int pickCount = Math.Min(4, remainingNormal);
var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
// 递增批次索引(如果刚好取完最后一个,索引会超过目标,但putfinished回来时会切换阶段)
stateForUpdate.CurrentBatchIndex += pickCount;
// 切换到 Phase 2
stateForUpdate.ChangePalletPhase = 2;
}
// Phase 2: 放正常电芯到目标托盘(放货编号与取货编号一致)
else if (stateForUpdate.ChangePalletPhase == 2)
{
int remainingNormal = targetNormalCount - currentCompletedCount;
if (remainingNormal <= 0)
{
// 正常电芯放完,切换到 Phase 3
stateForUpdate.ChangePalletPhase = 3;
stateForUpdate.CurrentBatchIndex = targetNormalCount + 1;
_logger.LogInformation("HandlePutFinishedStateAsync:正常电芯全部放完,进入Phase 3回收假电芯,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
// 计算本批放货编号:基于 currentCompletedCount 推导批次起始(与取货编号一致)
// batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1
int batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1;
int putCount = Math.Min(4, remainingNormal);
var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end);
stateForUpdate.ChangePalletPhase = 1;
}
// Phase 3: 取假电芯(从源地址,从 targetNormalCount+1 开始递增)
else if (stateForUpdate.ChangePalletPhase == 3)
{
int fakeCount = targetTotal - targetNormalCount;
int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
int remainingFake = fakeCount - completedFake;
if (remainingFake <= 0)
{
stateForUpdate.ChangePalletPhase = 0;
stateForUpdate.CurrentBatchIndex = 1;
stateForUpdate.IsInFakeBatteryMode = false;
_logger.LogInformation("HandlePutFinishedStateAsync:流向B完成,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
int pickCount = Math.Min(4, remainingFake);
var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
stateForUpdate.CurrentBatchIndex += pickCount;
stateForUpdate.ChangePalletPhase = 4;
}
// Phase 4: 放假电芯到5号位(使用 PositionIndex)
else if (stateForUpdate.ChangePalletPhase == 4)
{
int fakeCount = targetTotal - targetNormalCount;
int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
int remainingFake = fakeCount - completedFake;
if (remainingFake <= 0)
{
stateForUpdate.ChangePalletPhase = 0;
stateForUpdate.CurrentBatchIndex = 1;
stateForUpdate.IsInFakeBatteryMode = false;
_logger.LogInformation("HandlePutFinishedStateAsync:流向B完成,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake));
if (positions.Count == 0)
{
_logger.LogError("HandlePutFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
int start = positions.Min();
int end = positions.Max();
await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, "5", start, end);
stateForUpdate.ChangePalletPhase = 3;
}
}
}
```
- [ ] **Step 2: 验证编译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 3: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
git commit -m "feat(Robot): RobotWorkflowOrchestrator 重写 HandlePutFinishedStateAsync 实现批次指令与双流向"
```
---
## Task 8: RobotWorkflowOrchestrator 重写 HandlePickFinishedStateAsync 的 ChangePallet 分支
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs`
- [ ] **Step 1: 修改 HandlePickFinishedStateAsync 方法**
找到 `HandlePickFinishedStateAsync` 方法(现有代码约第162-199行),将整个方法替换为:
```csharp
///
/// 处理取货完成后的放货指令
///
///
/// 根据任务类型决定放货指令格式:
/// - 换盘任务(ChangePallet):使用批次格式 SendPutWithBatchAsync
/// - 组盘任务(GroupPallet):使用原有格式 Putbattery,{目标地址}
/// - 其他任务:使用原有格式
///
/// 当前任务
/// 机器人 IP 地址
private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress)
{
string taskString;
// 换盘任务使用批次格式
if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
{
int targetNormalCount = task.RobotTaskTotalNum;
var state = _stateManager.GetState(ipAddress);
int currentCompletedCount = state?.RobotTaskTotalNum ?? 0;
bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
// 流向A:放假电芯到目标托盘(Phase 2)
if (isFlowA && state?.ChangePalletPhase == 2)
{
int remaining = 48 - currentCompletedCount;
if (remaining <= 0) return;
// 计算批次编号:从 targetNormalCount + 1 开始
int batchStart = targetNormalCount + 1;
int putCount = Math.Min(4, remaining);
var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end);
return;
}
// 流向B Phase 4:放假电芯到5号位
if (!isFlowA && state?.ChangePalletPhase == 4)
{
int fakeCount = 48 - targetNormalCount;
int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
int remainingFake = fakeCount - completedFake;
if (remainingFake <= 0) return;
var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake));
if (positions.Count == 0)
{
_logger.LogError("HandlePickFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
return;
}
int start = positions.Min();
int end = positions.Max();
await _taskProcessor.SendPutWithBatchAsync(task, state, "5", start, end);
return;
}
// 流向B Phase 2:放正常电芯到目标托盘
if (!isFlowA && state?.ChangePalletPhase == 2)
{
int remainingNormal = targetNormalCount - currentCompletedCount;
if (remainingNormal <= 0) return;
int batchStart = state.CurrentBatchIndex - (state.CurrentBatchIndex - 1) / 4 * 4;
int putCount = Math.Min(4, remainingNormal);
var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end);
return;
}
// 默认:使用原有格式
taskString = $"Putbattery,{task.RobotTargetAddress}";
}
else
{
// 非换盘任务:使用原有格式
taskString = $"Putbattery,{task.RobotTargetAddress}";
}
bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
if (result)
{
_logger.LogInformation("HandlePickFinishedStateAsync:下发放货指令成功,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
QuartzLogger.Info($"下发放货指令成功,指令: {taskString}", task.RobotRoadway);
task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
var stateToUpdate = _stateManager.GetState(ipAddress);
if (stateToUpdate != null)
{
stateToUpdate.CurrentTask = task;
if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
{
await _robotTaskService.UpdateRobotTaskAsync(task);
}
}
}
else
{
_logger.LogError("HandlePickFinishedStateAsync:下发放货指令失败,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", task.RobotRoadway);
}
}
```
- [ ] **Step 2: 验证编译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 3: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
git commit -m "feat(Robot): RobotWorkflowOrchestrator 重写 HandlePickFinishedStateAsync 支持批次指令"
```
---
## Task 9: 注册依赖注入
**Files:**
- Modify: AutofacModuleRegister.cs 或检查现有注册
- [ ] **Step 1: 检查 FakeBatteryPositionService 是否已自动注册**
```bash
grep -r "FakeBatteryPositionService" D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server\WIDESEAWCS_Core --include="*.cs"
```
Expected: 如果项目使用 `AsImplementedInterfaces()` 自动扫描,该服务已自动注册。
- [ ] **Step 2: 检查 RobotPrefixCommandHandler 构造函数变更后是否需要额外注册**
由于仅新增了依赖参数,如果项目使用 Autofac 自动装配(参数自动注入),则无需修改。如果需要手动注册,添加:
```csharp
// 确保 IFakeBatteryPositionService 已被注册(通常通过 AsImplementedInterfaces 自动完成)
// RobotPrefixCommandHandler 的新构造函数参数会自动装配
```
- [ ] **Step 3: 验证编译**
```bash
dotnet build WIDESEAWCS_Server.sln
```
Expected: 0 errors
- [ ] **Step 4: Commit**(仅当有实际改动时才提交)
```bash
git add <修改的文件>
git commit -m "feat(Robot): 注册依赖注入(如有改动)"
```
---
## Task 10: 验证整体构建
- [ ] **Step 1: 完整构建**
```bash
cd D:\Git\ShanMeiXinNengYuan\Code
dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
```
Expected: 0 errors, 0 warnings
- [ ] **Step 2: 检查测试**
```bash
dotnet test WCS/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj
```
Expected: 现有测试通过
- [ ] **Step 3: 提交完成标记**
```bash
git commit --allow-empty -m "feat(Robot): 换盘任务批次指令与双流向实现完成"
```
---
## 附录:测试验证步骤
### 手动验证流程
1. **流向A测试(RobotSourceAddressLineCode = 11001)**
- targetNormalCount = 11
- 预期:Phase1 取假电芯(Pickbattery,5,{pos})→ Phase2 放假电芯(Putbattery,{目标},12-15)→ 循环直到48
2. **流向B测试(RobotSourceAddressLineCode != 11001/11010)**
- targetNormalCount = 11
- 预期:Phase1 取正常(Pickbattery,{源},1-4)→ Phase2 放正常(Putbattery,{目标},1-4)→ 循环 → Phase3 取假(Pickbattery,{源},12-15)→ Phase4 放假到5号位(Putbattery,5,{pos})→ 循环直到回收完37个假电芯
3. **边界条件**
- targetNormalCount = 48:走原有逻辑
- targetNormalCount = 1:批次 1-0 格式
---
**Plan complete.**