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缓存
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
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
在 IsInFakeBatteryMode 属性后添加:
/// <summary>
/// 是否处于假电芯补充模式
/// </summary>
public bool IsInFakeBatteryMode { get; set; }
/// <summary>
/// 当前批次起始编号(用于递增计算取货/放货编号)
/// </summary>
/// <remarks>
/// 在批次模式下,每批取货/放货的起始编号从1开始递增。
/// 用于计算 {start}-{end} 格式中的 start 值。
/// </remarks>
public int CurrentBatchIndex { get; set; } = 1;
/// <summary>
/// 换盘任务当前阶段
/// </summary>
/// <remarks>
/// 阶段定义:
/// 0: 未开始
/// 1: 取正常电芯(流向B) / 取假电芯(流向A)
/// 2: 放正常电芯(流向B) / 放假电芯(流向A)
/// 3: 取假电芯(流向B Phase2)
/// 4: 放假电芯到5号位(流向B Phase2)
/// </remarks>
public int ChangePalletPhase { get; set; }
cd D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
git commit -m "feat(Robot): RobotSocketState 新增 CurrentBatchIndex 和 ChangePalletPhase"
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs
在 IFakeBatteryPositionRepository.cs 的 MarkAsUsed 方法后添加:
/// <summary>
/// 标记指定点位为可用(释放点位)
/// </summary>
/// <param name="positions">点位索引列表</param>
/// <returns>是否成功</returns>
bool MarkAsAvailable(List<int> positions);
在 FakeBatteryPositionRepository.cs 的 MarkAsUsed 方法后添加:
/// <inheritdoc/>
public bool MarkAsAvailable(List<int> positions)
{
if (positions == null || positions.Count == 0)
return true;
return Db.Updateable<Dt_FakeBatteryPosition>()
.SetColumns(x => x.IsUsed, false)
.Where(x => positions.Contains(x.PositionIndex))
.ExecuteCommand() > 0;
}
dotnet build WIDESEAWCS_Server.sln
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs
git commit -m "feat(Robot): 假电芯仓储层新增 MarkAsAvailable 方法"
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs
在 IFakeBatteryPositionService.cs 的 MarkAsUsed 方法后添加:
/// <summary>
/// 标记指定点位为可用(释放点位)
/// </summary>
/// <param name="positions">点位索引列表</param>
/// <returns>是否成功</returns>
bool MarkAsAvailable(List<int> positions);
/// <inheritdoc/>
public bool MarkAsAvailable(List<int> positions)
{
return BaseDal.MarkAsAvailable(positions);
}
dotnet build WIDESEAWCS_Server.sln
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs
git commit -m "feat(Robot): 假电芯服务层新增 MarkAsAvailable 方法"
注意: _fakeBatteryPositionService 字段已在上一轮迭代中添加,无需重复注入。
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
在 GetNextAvailableFakeBatteryPositions 方法后添加:
/// <summary>
/// 计算批次编号范围
/// </summary>
/// <remarks>
/// 返回格式:(start, end)
/// - remaining >= 4: (currentIndex, currentIndex + 3)
/// - remaining > 1: (currentIndex, currentIndex + remaining - 1)
/// - remaining == 1: (currentIndex, 0) -- 单个物品用 0 表示 end
/// </remarks>
/// <param name="currentIndex">当前批次起始编号</param>
/// <param name="remaining">剩余数量</param>
/// <returns>(start, end) 元组</returns>
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);
}
/// <summary>
/// 下发取货指令(带批次格式和总数)
/// </summary>
/// <remarks>
/// 发送顺序:
/// 1. PickTotalNum,{N} -- 真实电芯总数
/// 2. Pickbattery,{位置},{start}-{end} -- 批次取货指令
///
/// 下发成功后更新任务状态为"机器人执行中"。
/// </remarks>
/// <param name="task">要下发的任务对象</param>
/// <param name="state">机器人当前状态</param>
/// <param name="position">取货位置</param>
/// <param name="batchStart">批次起始编号</param>
/// <param name="batchEnd">批次结束编号</param>
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);
}
}
/// <summary>
/// 下发放货指令(带批次格式和总数)
/// </summary>
/// <remarks>
/// 发送顺序:
/// 1. PutTotalNum,{N} -- 真实电芯总数
/// 2. Putbattery,{位置},{start}-{end} -- 批次放货指令
///
/// 下发成功后更新任务状态为"机器人执行中"。
/// </remarks>
/// <param name="task">要下发的任务对象</param>
/// <param name="state">机器人当前状态</param>
/// <param name="position">放货位置</param>
/// <param name="batchStart">批次起始编号</param>
/// <param name="batchEnd">批次结束编号</param>
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);
}
}
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
git commit -m "feat(Robot): RobotTaskProcessor 新增批次指令辅助方法"
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
找到 case "allpickfinished": 分支,替换为:
// 全部取货完成
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;
}
找到 case "allputfinished": 分支,替换为:
// 全部放货完成
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<string>();
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<string>();
await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
QuartzLogger.Info($"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
return true;
}
}
return false;
}
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
git commit -m "feat(Robot): RobotSimpleCommandHandler 换盘任务添加 ChangePalletPhase 阶段守卫"
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
在类顶部添加字段和构造函数参数:
/// <summary>
/// 假电芯位置服务
/// </summary>
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;
}
找到 if (putSuccess) 块(现有代码约在第258行附近),替换其中的放货成功处理逻辑为:
// 如果放货成功
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;
}
}
找到 if (state.IsSplitPallet) 块(现有代码约在第173-198行),替换为:
// 如果是拆盘任务
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;
}
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
git commit -m "feat(Robot): RobotPrefixCommandHandler 换盘任务根据阶段区分处理逻辑"
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
找到 else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) 分支,将其完整替换为:
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;
}
}
}
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
git commit -m "feat(Robot): RobotWorkflowOrchestrator 重写 HandlePutFinishedStateAsync 实现批次指令与双流向"
Files:
- Modify: WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
找到 HandlePickFinishedStateAsync 方法(现有代码约第162-199行),将整个方法替换为:
/// <summary>
/// 处理取货完成后的放货指令
/// </summary>
/// <remarks>
/// 根据任务类型决定放货指令格式:
/// - 换盘任务(ChangePallet):使用批次格式 SendPutWithBatchAsync
/// - 组盘任务(GroupPallet):使用原有格式 Putbattery,{目标地址}
/// - 其他任务:使用原有格式
/// </remarks>
/// <param name="task">当前任务</param>
/// <param name="ipAddress">机器人 IP 地址</param>
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);
}
}
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
Expected: 0 errors
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
git commit -m "feat(Robot): RobotWorkflowOrchestrator 重写 HandlePickFinishedStateAsync 支持批次指令"
Files:
- Modify: AutofacModuleRegister.cs 或检查现有注册
grep -r "FakeBatteryPositionService" D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server\WIDESEAWCS_Core --include="*.cs"
Expected: 如果项目使用 AsImplementedInterfaces() 自动扫描,该服务已自动注册。
由于仅新增了依赖参数,如果项目使用 Autofac 自动装配(参数自动注入),则无需修改。如果需要手动注册,添加:
// 确保 IFakeBatteryPositionService 已被注册(通常通过 AsImplementedInterfaces 自动完成)
// RobotPrefixCommandHandler 的新构造函数参数会自动装配
dotnet build WIDESEAWCS_Server.sln
Expected: 0 errors
git add <修改的文件>
git commit -m "feat(Robot): 注册依赖注入(如有改动)"
cd D:\Git\ShanMeiXinNengYuan\Code
dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
Expected: 0 errors, 0 warnings
dotnet test WCS/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj
Expected: 现有测试通过
git commit --allow-empty -m "feat(Robot): 换盘任务批次指令与双流向实现完成"
Plan complete.