编辑 | blame | 历史 | 原始文档

换盘任务假电芯补充逻辑 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
using SqlSugar;
using WIDESEAWCS_Core.DB.Models;

namespace WIDESEAWCS_Model.Models
{
    /// <summary>
    /// 假电芯平面点位表
    /// </summary>
    /// <remarks>
    /// 用于管理假电芯抓取点的平面点位信息。
    /// 3行×16列布局,共48个点位(1-48),行优先排列。
    /// 第1行:1-16,第2行:17-32,第3行:33-48。
    /// </remarks>
    [SugarTable(nameof(Dt_FakeBatteryPosition), "假电芯平面点位表")]
    public class Dt_FakeBatteryPosition : BaseEntity
    {
        /// <summary>
        /// 主键ID
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键ID")]
        public int Id { get; set; }

        /// <summary>
        /// 平面点位索引(1-48)
        /// </summary>
        /// <remarks>
        /// 3×16布局,行优先(1-16为第1行,17-32为第2行,33-48为第3行)。
        /// </remarks>
        [SugarColumn(ColumnDescription = "平面点位索引")]
        public int PositionIndex { get; set; }

        /// <summary>
        /// 所在行(1-3)
        /// </summary>
        [SugarColumn(ColumnDescription = "所在行")]
        public int Row { get; set; }

        /// <summary>
        /// 所在列(1-16)
        /// </summary>
        [SugarColumn(ColumnDescription = "所在列")]
        public int Col { get; set; }

        /// <summary>
        /// 是否已使用
        /// </summary>
        [SugarColumn(ColumnDescription = "是否已使用")]
        public bool IsUsed { get; set; }
    }
}
  • [ ] Step 2: Commit
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
using WIDESEAWCS_Core.BaseRepository;
using WIDESEAWCS_Model.Models;

namespace WIDESEAWCS_ITaskInfoRepository
{
    /// <summary>
    /// 假电芯位置仓储接口
    /// </summary>
    public interface IFakeBatteryPositionRepository : IRepository<Dt_FakeBatteryPosition>
    {
        /// <summary>
        /// 获取下N个可用的假电芯平面点位
        /// </summary>
        /// <param name="count">需要获取的点位数量</param>
        /// <returns>可用点位列表,按PositionIndex升序</returns>
        List<int> GetNextAvailable(int count);

        /// <summary>
        /// 重置所有点位为未使用
        /// </summary>
        /// <returns>影响的行数</returns>
        int ResetAll();

        /// <summary>
        /// 标记指定点位为已使用
        /// </summary>
        /// <param name="positions">点位索引列表</param>
        /// <returns>是否成功</returns>
        bool MarkAsUsed(List<int> positions);

        /// <summary>
        /// 根据行和列获取点位索引
        /// </summary>
        /// <param name="row">行(1-3)</param>
        /// <param name="col">列(1-16)</param>
        /// <returns>点位索引(1-48),找不到返回null</returns>
        int? GetPositionIndex(int row, int col);
    }
}
  • [ ] Step 2: 创建 FakeBatteryPositionRepository.cs
using WIDESEAWCS_Core.BaseRepository;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_Model.Models;

namespace WIDESEAWCS_TaskInfoRepository
{
    /// <summary>
    /// 假电芯位置仓储实现
    /// </summary>
    public class FakeBatteryPositionRepository : RepositoryBase<Dt_FakeBatteryPosition>, IFakeBatteryPositionRepository
    {
        public FakeBatteryPositionRepository(IUnitOfWorkManage unitOfWorkManage) : base(unitOfWorkManage)
        {
        }

        /// <inheritdoc/>
        public List<int> GetNextAvailable(int count)
        {
            return Db.Queryable<Dt_FakeBatteryPosition>()
                .Where(x => !x.IsUsed)
                .OrderBy(x => x.PositionIndex)
                .Take(count)
                .Select(x => x.PositionIndex)
                .ToList();
        }

        /// <inheritdoc/>
        public int ResetAll()
        {
            return Db.Updateable<Dt_FakeBatteryPosition>()
                .SetColumns(x => x.IsUsed, false)
                .ExecuteCommand();
        }

        /// <inheritdoc/>
        public bool MarkAsUsed(List<int> positions)
        {
            if (positions == null || positions.Count == 0)
                return true;

            return Db.Updateable<Dt_FakeBatteryPosition>()
                .SetColumns(x => x.IsUsed, true)
                .Where(x => positions.Contains(x.PositionIndex))
                .ExecuteCommand() > 0;
        }

        /// <inheritdoc/>
        public int? GetPositionIndex(int row, int col)
        {
            return Db.Queryable<Dt_FakeBatteryPosition>()
                .Where(x => x.Row == row && x.Col == col)
                .Select(x => x.PositionIndex)
                .FirstOrDefault();
        }
    }
}
  • [ ] Step 3: Commit
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
using WIDESEAWCS_Core.BaseServices;
using WIDESEAWCS_Model.Models;

namespace WIDESEAWCS_ITaskInfoService
{
    /// <summary>
    /// 假电芯位置服务接口
    /// </summary>
    public interface IFakeBatteryPositionService : IService<Dt_FakeBatteryPosition>
    {
        /// <summary>
        /// 获取下N个可用的假电芯平面点位
        /// </summary>
        /// <param name="count">需要获取的点位数量</param>
        /// <returns>可用点位列表,按PositionIndex升序</returns>
        List<int> GetNextAvailable(int count);

        /// <summary>
        /// 重置所有点位为未使用
        /// </summary>
        /// <returns>影响的行数</returns>
        int ResetAll();

        /// <summary>
        /// 标记指定点位为已使用
        /// </summary>
        /// <param name="positions">点位索引列表</param>
        /// <returns>是否成功</returns>
        bool MarkAsUsed(List<int> positions);

        /// <summary>
        /// 根据行和列获取点位索引
        /// </summary>
        /// <param name="row">行(1-3)</param>
        /// <param name="col">列(1-16)</param>
        /// <returns>点位索引(1-48),找不到返回null</returns>
        int? GetPositionIndex(int row, int col);

        /// <summary>
        /// 初始化假电芯点位表(48个点位)
        /// </summary>
        /// <remarks>
        /// 仅当表为空时插入1-48的初始数据。
        /// 3行×16列,行优先排列。
        /// </remarks>
        /// <returns>是否成功</returns>
        bool InitializeIfEmpty();
    }
}
  • [ ] Step 2: 创建 FakeBatteryPositionService.cs
using WIDESEAWCS_Core.BaseServices;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_Model.Models;

namespace WIDESEAWCS_TaskInfoService
{
    /// <summary>
    /// 假电芯位置服务实现
    /// </summary>
    public class FakeBatteryPositionService : ServiceBase<Dt_FakeBatteryPosition, IFakeBatteryPositionRepository>, IFakeBatteryPositionService
    {
        public FakeBatteryPositionService(IFakeBatteryPositionRepository BaseDal) : base(BaseDal)
        {
        }

        /// <inheritdoc/>
        public List<int> GetNextAvailable(int count)
        {
            return BaseDal.GetNextAvailable(count);
        }

        /// <inheritdoc/>
        public int ResetAll()
        {
            return BaseDal.ResetAll();
        }

        /// <inheritdoc/>
        public bool MarkAsUsed(List<int> positions)
        {
            return BaseDal.MarkAsUsed(positions);
        }

        /// <inheritdoc/>
        public int? GetPositionIndex(int row, int col)
        {
            return BaseDal.GetPositionIndex(row, col);
        }

        /// <inheritdoc/>
        public bool InitializeIfEmpty()
        {
            var existing = BaseDal.QueryFirst(x => true);
            if (existing != null)
                return true;

            // 生成48个点位:3行×16列,行优先
            var positions = new List<Dt_FakeBatteryPosition>();
            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
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.csRobotTaskTotalNum 属性后添加:

        /// <summary>
        /// 机器人已处理的任务总数
        /// </summary>
        public int RobotTaskTotalNum { get; set; }

        /// <summary>
        /// 是否处于假电芯补充模式
        /// </summary>
        /// <remarks>
        /// 当正常电芯任务完成后设为 true,机器人从假电芯位置补充电芯至48个。
        /// </remarks>
        public bool IsInFakeBatteryMode { get; set; }
  • [ ] Step 2: Commit
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 构造函数中添加:

private readonly IFakeBatteryPositionService _fakeBatteryPositionService;

// 构造函数参数中添加:
, IFakeBatteryPositionService fakeBatteryPositionService

// 构造函数赋值:
_fakeBatteryPositionService = fakeBatteryPositionService;
  • [ ] Step 2: 添加 SendSocketRobotFakeBatteryPickAsync 方法

SendSocketRobotPickAsync 方法后添加:

        /// <summary>
        /// 下发假电芯取货指令到机器人客户端
        /// </summary>
        /// <remarks>
        /// 发送格式:Pickbattery,5,{startPosition}-{endPosition}
        /// 例如:Pickbattery,5,1-3 表示从假电芯位置5抓取,平面点位1到3
        ///
        /// 下发成功后:
        /// 1. 标记点位为已使用
        /// 2. 更新任务状态为"机器人执行中"
        /// 3. 安全更新状态到 Redis
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="positions">要抓取的平面点位列表</param>
        public async Task SendSocketRobotFakeBatteryPickAsync(Dt_RobotTask task, RobotSocketState state, List<int> 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
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 分支逻辑

将现有的占位代码替换为:

            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 中添加:

        /// <summary>
        /// 获取下N个可用的假电芯平面点位
        /// </summary>
        /// <param name="count">需要获取的点位数量</param>
        /// <returns>可用点位列表</returns>
        public List<int> GetNextAvailableFakeBatteryPositions(int count)
        {
            return _fakeBatteryPositionService.GetNextAvailable(count);
        }
  • [ ] Step 3: Commit
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
dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
  • [ ] Step 2: 检查是否有编译错误

附录:数据库初始化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?