wanshenmean
14 小时以前 5bf10c1dafe485d506ec534f98e5220a3b83dd17
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -5,6 +5,7 @@
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_Tasks.SocketServer;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks.Workflow
@@ -152,7 +153,7 @@
        /// </summary>
        /// <remarks>
        /// 当取货完成后,向机器人发送放货指令(Putbattery)。
        /// 机器人收到指令后会将货物放置到目标地址。
        /// 换盘任务使用批次格式 SendPutWithBatchAsync。
        ///
        /// 指令格式:Putbattery,{目标地址}
        /// 例如:Putbattery,B01 表示将货物放置到 B01 位置
@@ -161,38 +162,113 @@
        /// <param name="ipAddress">机器人 IP 地址</param>
        private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress)
        {
            // 构建放货指令,格式:Putbattery,{目标地址}
            string taskString = $"Putbattery,{task.RobotTargetAddress}";
            string taskString;
            // 通过客户端管理器发送指令到机器人
            var state = _stateManager.GetState(ipAddress);
            // 换盘任务使用批次格式
            if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
            {
                int targetNormalCount = task.RobotTaskTotalNum;
                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;
                    int batchStart = targetNormalCount + 1 + (state.CurrentBatchIndex - 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 = ((currentCompletedCount - 1) / 4) * 4 + 1;
                    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
            {
                // 非换盘任务:使用原有格式
                if (state != null && state.IsGroupPallet && task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
                {
                    // 组盘任务:放货需判断是否NG,如果NG则放到NG口
                    if (state.IsScanNG)
                    {
                        taskString = $"Putbattery,NG";
                    }
                    else
                    {
                        taskString = $"Putbattery,{task.RobotTargetAddress}";
                    }
                }
                else
                    taskString = $"Putbattery,{task.RobotTargetAddress}";
            }
            bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
            if (result)
            {
                // 发送成功,记录 Info 日志
                _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;
                    // 安全更新状态到 Redis
                    if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
                    {
                        // 状态更新成功后,更新任务记录
                        await _robotTaskService.UpdateRobotTaskAsync(task);
                    }
                }
            }
            else
            {
                // 发送失败,记录 Error 日志
                _logger.LogError("HandlePickFinishedStateAsync:下发放货指令失败,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
                QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", task.RobotRoadway);
            }
@@ -240,112 +316,233 @@
            // 如果是组盘任务
            if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
            {
                // 生成托盘条码前缀
                const string prefix = "TRAY";
                // 生成两个托盘条码(用于组盘操作)(测试用,后续读取线体条码)
                string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
                string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
                // 读取线体电芯条码
                string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.990");
                string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.1020");
                // 如果条码生成成功
                if (!string.IsNullOrEmpty(trayBarcode1) && !string.IsNullOrEmpty(trayBarcode2))
                {
                    if(stateForUpdate.CellBarcode.Contains(trayBarcode1)|| stateForUpdate.CellBarcode.Contains(trayBarcode2))
                    if (stateForUpdate.CellBarcode.Contains(trayBarcode1) || stateForUpdate.CellBarcode.Contains(trayBarcode2))
                    {
                        _logger.LogError("HandlePutFinishedStateAsync:生成的托盘条码已存在,可能存在重复,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Error($"生成的托盘条码已存在,可能存在重复", stateForUpdate.RobotCrane.DeviceName);
                        _logger.LogError("HandlePutFinishedStateAsync:读取的托盘条码已存在,可能存在重复,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Error($"读取的托盘条码已存在,可能存在重复", stateForUpdate.RobotCrane.DeviceName);
                        // 条码重复,记录错误日志并停止后续操作(后续放货时会用到这些条码信息,供后续放货时使用,调试后可能会取消此逻辑)
                        return;
                        // 发送取货指令 标记扫码NG,放货时不使用这些条码,并放入NG口
                        await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                    }
                    else
                    {
                        _logger.LogInformation("HandlePutFinishedStateAsync:生成的托盘条码唯一,继续执行,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Info($"生成的托盘条码唯一,继续执行", stateForUpdate.RobotCrane.DeviceName);
                        _logger.LogInformation("HandlePutFinishedStateAsync:读取的托盘条码唯一,继续执行,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Info($"读取的托盘条码唯一,继续执行", stateForUpdate.RobotCrane.DeviceName);
                        // 将条码添加到状态中,供后续放货时使用
                        stateForUpdate.CellBarcode.Add(trayBarcode1);
                        stateForUpdate.CellBarcode.Add(trayBarcode2);
                    }
                    // 记录日志:生成托盘条码成功
                    _logger.LogInformation("HandlePutFinishedStateAsync:生成托盘条码成功: {Barcode1}+{Barcode2},任务号: {TaskNum}", trayBarcode1, trayBarcode2, task.RobotTaskNum);
                    QuartzLogger.Info($"生成托盘条码成功: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName);
                    // 记录日志:读取托盘条码成功
                    _logger.LogInformation("HandlePutFinishedStateAsync:读取托盘条码成功: {Barcode1}+{Barcode2},任务号: {TaskNum}", trayBarcode1, trayBarcode2, task.RobotTaskNum);
                    QuartzLogger.Info($"读取托盘条码成功: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName);
                    // 发送取货指令
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                }
                else
                {
                    // 条码生成失败,记录错误日志
                    _logger.LogError("HandlePutFinishedStateAsync:生成托盘条码失败,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Error($"生成托盘条码失败", stateForUpdate.RobotCrane.DeviceName);
                    // 条码读取失败,记录错误日志
                    _logger.LogError("HandlePutFinishedStateAsync:读取托盘条码失败,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Error($"读取托盘条码失败", stateForUpdate.RobotCrane.DeviceName);
                    // 发送取货指令 标记扫码NG,放货时不使用这些条码,并放入NG口
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                }
            }
            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;
                int targetNormalCount = task.RobotTaskTotalNum;  // 正常电芯目标数量
                int currentCompletedCount = stateForUpdate.RobotTaskTotalNum;  // 已完成数量
                // 判断流向(null-safe)
                bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
                // 如果目标数量为48,直接下发正常任务
                // 目标数量为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);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                    return;
                }
                // 如果处于假电芯补充模式,计算并下发补数任务
                if (stateForUpdate.IsInFakeBatteryMode)
                // 初始化批次模式
                if (stateForUpdate.ChangePalletPhase == 0)
                {
                    int remaining = targetTotal - currentCompletedCount;
                    if (remaining > 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)
                    {
                        // 计算每次抓取的数量(最多4个)
                        int pickCount = Math.Min(pickCountPerExecution, remaining);
                        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);
                            QuartzLogger.Error($"无可用假电芯点位", stateForUpdate.RobotCrane?.DeviceName);
                            return;
                        }
                        // 下发假电芯取货指令
                        await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions);
                        stateForUpdate.ChangePalletPhase = 2;
                    }
                    else
                    // Phase 2: 放假电芯到目标托盘(从 targetNormalCount+1 开始递增)
                    else if (stateForUpdate.ChangePalletPhase == 2)
                    {
                        // 假电芯补充完成,重置标志
                        stateForUpdate.IsInFakeBatteryMode = false;
                        _logger.LogInformation("HandlePutFinishedStateAsync:换盘任务完成,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Info($"换盘任务完成", stateForUpdate.RobotCrane?.DeviceName);
                        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;
                        if (remainingNormal <= 0)
                        {
                            // 正常电芯取完,切换到 Phase 3
                            stateForUpdate.ChangePalletPhase = 3;
                            stateForUpdate.CurrentBatchIndex = targetNormalCount + 1;
                            _logger.LogInformation("HandlePutFinishedStateAsync:正常电芯全部取完,进入Phase 3回收假电芯,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        int pickCount = Math.Min(4, remainingNormal);
                        var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
                        await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
                        stateForUpdate.CurrentBatchIndex += pickCount;
                        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 推导批次起始
                        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;
                    }
                }
            }
            else
            {
                // 非组盘任务,直接发送取货指令
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
            }
        }
    }