# 换盘任务假电芯补充逻辑 Implementation Plan > **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:** 在 `HandlePutFinishedStateAsync` 中实现换盘任务的特殊逻辑:当 `task.RobotTaskTotalNum != 48` 时,正常电芯任务完成后需补充假电芯至48个。 **Architecture:** 换盘任务分两阶段执行:(1) 正常电芯抓取阶段;(2) 假电芯补充阶段。假电芯从位置5抓取,指令格式 `Pickbattery,5,1-3`。新增 `Dt_FakeBatteryPosition` 表管理3×16平面点位的占用状态。 **Tech Stack:** C# / .NET 6, SqlSugar ORM, Redis缓存 --- ## File Structure ``` WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/ + Dt_FakeBatteryPosition.cs # 假电芯平面点位表实体 WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/ + IFakeBatteryPositionRepository.cs # 假电芯位置仓储接口 WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/ + FakeBatteryPositionRepository.cs # 假电芯位置仓储实现 WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/ + IFakeBatteryPositionService.cs # 假电芯位置服务接口 WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/ + FakeBatteryPositionService.cs # 假电芯位置服务实现 WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/ = RobotSocketState.cs # +IsInFakeBatteryMode 标志 = RobotTaskProcessor.cs # +SendSocketRobotFakeBatteryPickAsync 方法 WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/ = RobotWorkflowOrchestrator.cs # 实现 ChangePallet 分支完整逻辑 ``` --- ## Task 1: 创建假电芯平面点位表实体 **Files:** - Create: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/Dt_FakeBatteryPosition.cs` - [ ] **Step 1: 创建 Dt_FakeBatteryPosition.cs** ```csharp using SqlSugar; using WIDESEAWCS_Core.DB.Models; namespace WIDESEAWCS_Model.Models { /// /// 假电芯平面点位表 /// /// /// 用于管理假电芯抓取点的平面点位信息。 /// 3行×16列布局,共48个点位(1-48),行优先排列。 /// 第1行:1-16,第2行:17-32,第3行:33-48。 /// [SugarTable(nameof(Dt_FakeBatteryPosition), "假电芯平面点位表")] public class Dt_FakeBatteryPosition : BaseEntity { /// /// 主键ID /// [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键ID")] public int Id { get; set; } /// /// 平面点位索引(1-48) /// /// /// 3×16布局,行优先(1-16为第1行,17-32为第2行,33-48为第3行)。 /// [SugarColumn(ColumnDescription = "平面点位索引")] public int PositionIndex { get; set; } /// /// 所在行(1-3) /// [SugarColumn(ColumnDescription = "所在行")] public int Row { get; set; } /// /// 所在列(1-16) /// [SugarColumn(ColumnDescription = "所在列")] public int Col { get; set; } /// /// 是否已使用 /// [SugarColumn(ColumnDescription = "是否已使用")] public bool IsUsed { get; set; } } } ``` - [ ] **Step 2: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/Dt_FakeBatteryPosition.cs git commit -m "feat(Robot): 添加假电芯平面点位表实体 Dt_FakeBatteryPosition" ``` --- ## Task 2: 创建假电芯位置仓储层 **Files:** - Create: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs` - Create: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs` - [ ] **Step 1: 创建 IFakeBatteryPositionRepository.cs** ```csharp using WIDESEAWCS_Core.BaseRepository; using WIDESEAWCS_Model.Models; namespace WIDESEAWCS_ITaskInfoRepository { /// /// 假电芯位置仓储接口 /// public interface IFakeBatteryPositionRepository : IRepository { /// /// 获取下N个可用的假电芯平面点位 /// /// 需要获取的点位数量 /// 可用点位列表,按PositionIndex升序 List GetNextAvailable(int count); /// /// 重置所有点位为未使用 /// /// 影响的行数 int ResetAll(); /// /// 标记指定点位为已使用 /// /// 点位索引列表 /// 是否成功 bool MarkAsUsed(List positions); /// /// 根据行和列获取点位索引 /// /// 行(1-3) /// 列(1-16) /// 点位索引(1-48),找不到返回null int? GetPositionIndex(int row, int col); } } ``` - [ ] **Step 2: 创建 FakeBatteryPositionRepository.cs** ```csharp using WIDESEAWCS_Core.BaseRepository; using WIDESEAWCS_ITaskInfoRepository; using WIDESEAWCS_Model.Models; namespace WIDESEAWCS_TaskInfoRepository { /// /// 假电芯位置仓储实现 /// public class FakeBatteryPositionRepository : RepositoryBase, IFakeBatteryPositionRepository { public FakeBatteryPositionRepository(IUnitOfWorkManage unitOfWorkManage) : base(unitOfWorkManage) { } /// public List GetNextAvailable(int count) { return Db.Queryable() .Where(x => !x.IsUsed) .OrderBy(x => x.PositionIndex) .Take(count) .Select(x => x.PositionIndex) .ToList(); } /// public int ResetAll() { return Db.Updateable() .SetColumns(x => x.IsUsed, false) .ExecuteCommand(); } /// public bool MarkAsUsed(List positions) { if (positions == null || positions.Count == 0) return true; return Db.Updateable() .SetColumns(x => x.IsUsed, true) .Where(x => positions.Contains(x.PositionIndex)) .ExecuteCommand() > 0; } /// public int? GetPositionIndex(int row, int col) { return Db.Queryable() .Where(x => x.Row == row && x.Col == col) .Select(x => x.PositionIndex) .FirstOrDefault(); } } } ``` - [ ] **Step 3: 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): 添加假电芯位置仓储层" ``` --- ## Task 3: 创建假电芯位置服务层 **Files:** - Create: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs` - Create: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs` - [ ] **Step 1: 创建 IFakeBatteryPositionService.cs** ```csharp using WIDESEAWCS_Core.BaseServices; using WIDESEAWCS_Model.Models; namespace WIDESEAWCS_ITaskInfoService { /// /// 假电芯位置服务接口 /// public interface IFakeBatteryPositionService : IService { /// /// 获取下N个可用的假电芯平面点位 /// /// 需要获取的点位数量 /// 可用点位列表,按PositionIndex升序 List GetNextAvailable(int count); /// /// 重置所有点位为未使用 /// /// 影响的行数 int ResetAll(); /// /// 标记指定点位为已使用 /// /// 点位索引列表 /// 是否成功 bool MarkAsUsed(List positions); /// /// 根据行和列获取点位索引 /// /// 行(1-3) /// 列(1-16) /// 点位索引(1-48),找不到返回null int? GetPositionIndex(int row, int col); /// /// 初始化假电芯点位表(48个点位) /// /// /// 仅当表为空时插入1-48的初始数据。 /// 3行×16列,行优先排列。 /// /// 是否成功 bool InitializeIfEmpty(); } } ``` - [ ] **Step 2: 创建 FakeBatteryPositionService.cs** ```csharp using WIDESEAWCS_Core.BaseServices; using WIDESEAWCS_ITaskInfoRepository; using WIDESEAWCS_Model.Models; namespace WIDESEAWCS_TaskInfoService { /// /// 假电芯位置服务实现 /// public class FakeBatteryPositionService : ServiceBase, IFakeBatteryPositionService { public FakeBatteryPositionService(IFakeBatteryPositionRepository BaseDal) : base(BaseDal) { } /// public List GetNextAvailable(int count) { return BaseDal.GetNextAvailable(count); } /// public int ResetAll() { return BaseDal.ResetAll(); } /// public bool MarkAsUsed(List positions) { return BaseDal.MarkAsUsed(positions); } /// public int? GetPositionIndex(int row, int col) { return BaseDal.GetPositionIndex(row, col); } /// public bool InitializeIfEmpty() { var existing = BaseDal.QueryFirst(x => true); if (existing != null) return true; // 生成48个点位:3行×16列,行优先 var positions = new List(); for (int row = 1; row <= 3; row++) { for (int col = 1; col <= 16; col++) { int positionIndex = (row - 1) * 16 + col; positions.Add(new Dt_FakeBatteryPosition { PositionIndex = positionIndex, Row = row, Col = col, IsUsed = false, Creater = "System" }); } } return BaseDal.AddData(positions) > 0; } } } ``` - [ ] **Step 3: 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): 添加假电芯位置服务层" ``` --- ## Task 4: 修改 RobotSocketState 添加 IsInFakeBatteryMode 标志 **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs:175` (在 `RobotTaskTotalNum` 属性后添加) - [ ] **Step 1: 添加 IsInFakeBatteryMode 属性** 在 `RobotSocketState.cs` 的 `RobotTaskTotalNum` 属性后添加: ```csharp /// /// 机器人已处理的任务总数 /// public int RobotTaskTotalNum { get; set; } /// /// 是否处于假电芯补充模式 /// /// /// 当正常电芯任务完成后设为 true,机器人从假电芯位置补充电芯至48个。 /// public bool IsInFakeBatteryMode { get; set; } ``` - [ ] **Step 2: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs git commit -m "feat(Robot): RobotSocketState 添加 IsInFakeBatteryMode 标志" ``` --- ## Task 5: 修改 RobotTaskProcessor 添加假电芯取货指令方法 **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs` (在 `SendSocketRobotPickAsync` 后添加新方法) - [ ] **Step 1: 添加 IFakeBatteryPositionService 依赖** 在 `RobotTaskProcessor` 构造函数中添加: ```csharp private readonly IFakeBatteryPositionService _fakeBatteryPositionService; // 构造函数参数中添加: , IFakeBatteryPositionService fakeBatteryPositionService // 构造函数赋值: _fakeBatteryPositionService = fakeBatteryPositionService; ``` - [ ] **Step 2: 添加 SendSocketRobotFakeBatteryPickAsync 方法** 在 `SendSocketRobotPickAsync` 方法后添加: ```csharp /// /// 下发假电芯取货指令到机器人客户端 /// /// /// 发送格式:Pickbattery,5,{startPosition}-{endPosition} /// 例如:Pickbattery,5,1-3 表示从假电芯位置5抓取,平面点位1到3 /// /// 下发成功后: /// 1. 标记点位为已使用 /// 2. 更新任务状态为"机器人执行中" /// 3. 安全更新状态到 Redis /// /// 要下发的任务对象 /// 机器人当前状态 /// 要抓取的平面点位列表 public async Task SendSocketRobotFakeBatteryPickAsync(Dt_RobotTask task, RobotSocketState state, List positions) { if (positions == null || positions.Count == 0) { _logger.LogWarning("SendSocketRobotFakeBatteryPickAsync:平面点位列表为空,任务号: {TaskNum}", task.RobotTaskNum); return; } // 计算点位范围,格式:1-3 int startPos = positions.Min(); int endPos = positions.Max(); string taskString = $"Pickbattery,5,{startPos}-{endPos}"; // 标记点位为已使用 _fakeBatteryPositionService.MarkAsUsed(positions); // 通过 Socket 网关发送指令到机器人客户端 bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString); if (result) { _logger.LogInformation("下发假电芯取货指令成功,指令: {TaskString},点位: {Positions},设备: {DeviceName}", taskString, string.Join(",", positions), state.RobotCrane?.DeviceName); QuartzLogger.Info($"下发假电芯取货指令成功,指令: {taskString}", 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: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs git commit -m "feat(Robot): RobotTaskProcessor 添加假电芯取货指令方法" ``` --- ## Task 6: 实现 HandlePutFinishedStateAsync 中的 ChangePallet 完整逻辑 **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs:285-300` - [ ] **Step 1: 实现 ChangePallet 分支逻辑** 将现有的占位代码替换为: ```csharp else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) { // 换盘任务 // 目标:正常电芯抓取完成后,补充假电芯至48个 const int targetTotal = 48; const int fakeBatteryPickPosition = 5; // 假电芯抓取位置 const int pickCountPerExecution = 4; // 每次抓取数量 int targetNormalCount = task.RobotTaskTotalNum; // 正常电芯目标数量 int currentCompletedCount = stateForUpdate.RobotTaskTotalNum; // 已完成数量 // 如果目标数量为48,直接下发正常任务 if (targetNormalCount == targetTotal) { await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); } // 如果已完成数量小于目标数量,继续抓取正常电芯 else if (currentCompletedCount < targetNormalCount) { await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); } // 正常电芯已完成,进入假电芯补充模式 else if (currentCompletedCount == targetNormalCount && !stateForUpdate.IsInFakeBatteryMode) { // 首次进入假电芯模式,设置标志 stateForUpdate.IsInFakeBatteryMode = true; _logger.LogInformation("HandlePutFinishedStateAsync:正常电芯抓取完成,进入假电芯补充模式,任务号: {TaskNum}", task.RobotTaskNum); QuartzLogger.Info($"正常电芯抓取完成,进入假电芯补充模式", stateForUpdate.RobotCrane?.DeviceName); } // 如果处于假电芯补充模式,计算并下发补数任务 if (stateForUpdate.IsInFakeBatteryMode) { int remaining = targetTotal - currentCompletedCount; if (remaining > 0) { // 计算每次抓取的数量(最多4个) int pickCount = Math.Min(pickCountPerExecution, remaining); // 获取可用的假电芯平面点位 var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(pickCount); if (positions.Count == 0) { _logger.LogError("HandlePutFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum); QuartzLogger.Error($"无可用假电芯点位", stateForUpdate.RobotCrane?.DeviceName); return; } // 下发假电芯取货指令 await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions); } else { // 假电芯补充完成,重置标志 stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsync:换盘任务完成,任务号: {TaskNum}", task.RobotTaskNum); QuartzLogger.Info($"换盘任务完成", stateForUpdate.RobotCrane?.DeviceName); } } } ``` - [ ] **Step 2: 在 RobotTaskProcessor 中添加 GetNextAvailableFakeBatteryPositions 辅助方法** 在 `RobotTaskProcessor` 中添加: ```csharp /// /// 获取下N个可用的假电芯平面点位 /// /// 需要获取的点位数量 /// 可用点位列表 public List GetNextAvailableFakeBatteryPositions(int count) { return _fakeBatteryPositionService.GetNextAvailable(count); } ``` - [ ] **Step 3: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs git commit -m "feat(Robot): 实现换盘任务假电芯补充逻辑" ``` --- ## Task 7: 验证构建 - [ ] **Step 1: 运行 dotnet build** ```bash dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln ``` - [ ] **Step 2: 检查是否有编译错误** --- ## 附录:数据库初始化SQL ```sql -- 初始化假电芯平面点位表(48个点位:3行×16列,行优先) -- 第1行:1-16,第2行:17-32,第3行:33-48 IF NOT EXISTS (SELECT 1 FROM Dt_FakeBatteryPosition) BEGIN INSERT INTO Dt_FakeBatteryPosition (PositionIndex, Row, Col, IsUsed, Creater, CreateDate) SELECT ((row - 1) * 16 + col) AS PositionIndex, row AS Row, col AS Col, 0 AS IsUsed, 'System' AS Creater, GETDATE() AS CreateDate FROM (SELECT 1 AS row UNION SELECT 2 UNION SELECT 3) AS rows CROSS JOIN (SELECT 1 AS col UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION SELECT 15 UNION SELECT 16) AS cols ORDER BY row, col; END ``` --- **Plan complete and saved to `docs/superpowers/plans/2026-04-15-change-pallet-fake-battery.md`. Two execution options:** **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration **2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints **Which approach?**