wanshenmean
6 小时以前 bd40cc9e7dd6352915568ce49baa4accd1d9645b
feat: 添加机器人任务处理逻辑和消息去重功能

- 在TcpSocketServer中添加lastMessage字段用于消息去重
- 修改RobotSocketState添加BatteryArrived属性判断电芯到位状态
- 优化机器人任务处理流程,调整取货放货条件判断
- 更新设备地址映射配置,使用标准化设备名称
- 修复托盘条码读取逻辑,处理特殊字符和空值情况
- 调整组盘任务处理流程,简化条码处理逻辑
- 修改API路由缓存预热日志信息为中文
已修改13个文件
164 ■■■■■ 文件已修改
Code/.omc/state/last-tool-error.json 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/HostedService/ApiRouteCacheWarmupHostedService.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotBarcodeGenerator.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Robot.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/last-tool-error.json
@@ -1,7 +1,7 @@
{
  "tool_name": "Read",
  "tool_input_preview": "{\"file_path\":\"D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WCS\\\\WIDESEAWCS_Server\\\\WIDESEA_DTO\\\\Stock\\\\StockDTO.cs\"}",
  "error": "File does not exist. Note: your current working directory is D:\\Git\\ShanMeiXinNengYuan\\Code.",
  "timestamp": "2026-04-16T15:04:27.059Z",
  "retry_count": 2
  "tool_name": "Bash",
  "tool_input_preview": "{\"command\":\"node -e \\\"\\nconst f=require('fs'),p=require('path'),h=require('os').homedir();\\nconst d=process.env.CLAUDE_CONFIG_DIR||p.join(h,'.claude');\\nconst cf=p.join(d,'.omc-config.json');\\nconst e...",
  "error": "Exit code 1\n<anonymous_script>:1\r\n\r\n\r\nSyntaxError: Unexpected end of JSON input\r\n    at JSON.parse (<anonymous>)\r\n    at [eval]:5:38\r\n    at runScriptInThisContext (node:internal/vm:219:10)\r\n    at node:internal/process/execution:451:12\r\n    at [eval]-wrapper:6:24\r\n    at runScriptInContext (node:internal/process/execution:449:60)\r\n    at evalFunction (node:internal/process/execution:283:30)\r\n    at evalTypeScript (node:internal/process/execution:295:3)\r\n    at node:internal/main/eval_string:71:...",
  "timestamp": "2026-04-18T06:15:45.665Z",
  "retry_count": 1
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/HostedService/ApiRouteCacheWarmupHostedService.cs
@@ -46,8 +46,16 @@
        public Task StartAsync(CancellationToken cancellationToken)
        {
            const string cacheKey = $"{RedisPrefix.Code}";
            _cache.RemoveByPrefix($"{cacheKey}");
            const string cacheKeyPrefix = $"{RedisPrefix.Code}:{RedisName.API}:";
            int warmedCount = 0;
            foreach ((string configKey, string routePath) in ApiRouteMappings)
            {
@@ -55,7 +63,7 @@
                warmedCount++;
            }
            _logger.LogInformation("��API·�ɻ���Ԥ����ɡ�����={Count}", warmedCount);
            _logger.LogInformation("API路由缓存预热完成,共加载={Count}个路由映射", warmedCount);
            return Task.CompletedTask;
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -88,11 +88,11 @@
      "110681": "Split"
    },
    "AddressRoadwayMap": { // 对应设备地址映射
      "11001": "换盘机械手",
      "11010": "换盘机械手",
      "11068": "注液组盘机械手",
      "10010": "换盘机械手",
      "10030": "换盘机械手"
      "11001": "HCSC1",
      "11010": "HCSC1",
      "11068": "GWSC1",
      "10010": "GWSC1",
      "10030": "GWSC1"
    },
    "AddressSourceLineNoMap": { // 对应输送线编号地址映射
      "11001": "10010",
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs
@@ -93,12 +93,12 @@
            if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty)
            {
                WebResponseContent content = _robotTaskService.GetWMSRobotTask(task);
                if (!content.Status)
                {
                    return content;
                }
                return OutboundFinishTaskTray(task);
                return _robotTaskService.GetWMSRobotTask(task);
                //if (!content.Status)
                //{
                //    return content;
                //}
                //return OutboundFinishTaskTray(task);
            }
            if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutExecuting)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotBarcodeGenerator.cs
@@ -1,4 +1,5 @@
using Masuit.Tools;
using WIDESEAWCS_Communicator;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks
@@ -18,7 +19,11 @@
            var device = Storage.Devices.Where(d => d.DeviceName == "A区_一注输送线").FirstOrDefault();
            if (!device.IsNullOrEmpty() && device != null && device.Communicator.IsConnected)
            {
                var trayBarcode = device.Communicator.Read<string>(prefix);
                var trayBarcode = device.Communicator.Read<string>(prefix).Trim().Replace("\u0018", "").Replace("\u0006", "");
                if(trayBarcode == "NoRead")
                {
                    trayBarcode = "";
                }
                return trayBarcode;
            }
            return "";
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
@@ -211,5 +211,15 @@
        /// 拉带线上电芯扫码是否NG。
        /// </remarks>
        public bool IsScanNG { get; set; } = false;
        /// <summary>
        /// 是否电芯到位
        /// </summary>
        /// <remarks>
        /// 拉带线上电芯是否到位。
        /// </remarks>
        public bool BatteryArrived { get; set; } = false;
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -165,7 +165,7 @@
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="isScanNG">是否扫码NG</param>
        public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state, bool isScanNG)
        public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state, bool isScanNG = false)
        {
            // 构建取货指令,格式:Pickbattery,{源地址}
            string taskString = $"Pickbattery,{task.RobotSourceAddress}";
@@ -600,7 +600,8 @@
                        Channel = x,
                        // 电池条码:如果状态中有条码列表,取对应位置的条码;否则为空
                        CellBarcode = state.CellBarcode?.Count > 0 ? state.CellBarcode[x - 1] : ""
                        //CellBarcode = state.CellBarcode?.Count > 0 ? state.CellBarcode[x - 1] : ""
                        CellBarcode = state.CellBarcode[idx].ToString()
                    })
                    .ToList()
            };
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -119,6 +119,11 @@
                    state.CurrentAction = "Putting";
                    return true;
                // 手臂无物料(手臂空闲)
                case "batteryarrived":
                    state.BatteryArrived = true;
                    return true;
                // ==================== 全部完成命令 ====================
                // 全部取货完成
@@ -251,12 +256,12 @@
                        if (robotTaskType == RobotTaskTypeEnum.GroupPallet)
                        {
                            // 调用批量组盘确认接口
                            var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode;
                            var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet);
                            if (!confirmResult.IsSuccess)
                            {
                                QuartzLogger.Error($"批量组盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                            }
                            //var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode;
                            //var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet);
                            //if (!confirmResult.IsSuccess)
                            //{
                            //    QuartzLogger.Error($"批量组盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                            //}
                            // 处理入库任务回传
                            // useSourceAddress: false 表示使用目标地址(组盘场景)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -112,7 +112,7 @@
            // 1. 运行模式为自动(2)
            // 2. 控制模式为客户端控制(1)
            // 3. 运行状态是 Running
            if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running" && latestState.Homed == "Homed")
            if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running" && (latestState.Homed == "Homed" || latestState.Homed.IsNullOrEmpty()))
            {
                // ========== 取货完成后的放货处理 ==========
                // 条件:
@@ -120,11 +120,11 @@
                // - 手臂上有物料(RobotArmObject == 1)
                // - 任务状态为 RobotPickFinish(已记录取货完成)
                if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished")
                    && latestState.RobotArmObject == 1
                    && (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 1)
                    && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
                {
                    _logger.LogInformation("ExecuteAsync:满足放货条件,开始处理取货完成,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足放货条件,开始处理取货完成", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    _logger.LogInformation("ExecuteAsync:满足放货条件,开始下发放货任务,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足放货条件,开始下发放货任务", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    // 发送放货指令
                    await HandlePickFinishedStateAsync(task, ipAddress);
                }
@@ -136,12 +136,12 @@
                // - 手臂上无物料(RobotArmObject == 0)
                // - 任务状态为 RobotPutFinish 或不是 RobotExecuting
                else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction.IsNullOrEmpty())
                    && latestState.RobotArmObject == 0
                    && (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 0)
                    && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode()
                    || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
                {
                    _logger.LogInformation("ExecuteAsync:满足取货条件,开始处理放货完成,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足取货条件,开始处理放货完成", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    _logger.LogInformation("ExecuteAsync:满足取货条件,开始下发取货任务,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足取货条件,开始下发取货任务", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    // 发送取货指令
                    await HandlePutFinishedStateAsync(task, ipAddress);
                }
@@ -236,7 +236,7 @@
                    // 组盘任务:放货需判断是否NG,如果NG则放到NG口
                    if (state.IsScanNG)
                    {
                        taskString = $"Putbattery,NG";
                        taskString = $"Putbattery,4";
                    }
                    else
                    {
@@ -316,6 +316,11 @@
            // 如果是组盘任务
            if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
            {
                //if (!stateForUpdate.BatteryArrived)
                //{
                //    return;
                //}
                // 读取线体电芯条码
                string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.990");
                string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.1020");
@@ -331,15 +336,21 @@
                        // 条码重复,记录错误日志并停止后续操作(后续放货时会用到这些条码信息,供后续放货时使用,调试后可能会取消此逻辑)
                        // 发送取货指令 标记扫码NG,放货时不使用这些条码,并放入NG口
                        await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                        //await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                        return;
                    }
                    else
                    {
                        _logger.LogInformation("HandlePutFinishedStateAsync:读取的托盘条码唯一,继续执行,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Info($"读取的托盘条码唯一,继续执行", stateForUpdate.RobotCrane.DeviceName);
                        stateForUpdate.CellBarcode = new List<string>()
                        {
                            trayBarcode1,trayBarcode2
                        };
                        // 将条码添加到状态中,供后续放货时使用
                        stateForUpdate.CellBarcode.Add(trayBarcode1);
                        stateForUpdate.CellBarcode.Add(trayBarcode2);
                        //stateForUpdate.CellBarcode.Add(trayBarcode1);
                        //stateForUpdate.CellBarcode.Add(trayBarcode2);
                    }
@@ -348,7 +359,7 @@
                    QuartzLogger.Info($"读取托盘条码成功: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName);
                    // 发送取货指令
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                }
                else
                {
@@ -358,7 +369,8 @@
                    // 发送取货指令 标记扫码NG,放货时不使用这些条码,并放入NG口
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                    //await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                    return;
                }
            }
            else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
@@ -373,7 +385,7 @@
                // 目标数量为48:直接走原有逻辑,不进入批次模式
                if (targetNormalCount == targetTotal)
                {
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                    return;
                }
@@ -542,7 +554,7 @@
            else
            {
                // 非组盘任务,直接发送取货指令
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
            }
        }
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs
@@ -59,6 +59,12 @@
                            break;
                        }
                        if(message == lastMessage)
                        {
                            // 重复消息,忽略
                            continue;
                        }
                        // 更新客户端状态
                        UpdateClientStatus(clientId, message);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.cs
@@ -136,6 +136,11 @@
        public bool IsRunning { get; private set; }
        /// <summary>
        /// 上次接收消息源
        /// </summary>
        public string lastMessage;
        /// <summary>
        /// 消息接收事件
        /// </summary>
        /// <remarks>
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs
@@ -151,18 +151,18 @@
                    if (!updateLocationResult || !updateStockResult)
                        return WebResponseContent.Instance.Error("任务完成失败");
                    // 调用MES托盘进站
                    var inboundRequest = new InboundInContainerRequest
                    {
                        EquipmentCode = "STK-GROUP-001",
                        ResourceCode = "STK-GROUP-001",
                        LocalTime = DateTime.Now,
                        ContainerCode = taskDto.PalletCode
                    };
                    var inboundResult = _mesService.InboundInContainer(inboundRequest);
                    if (inboundResult == null || inboundResult.Data == null || !inboundResult.Data.IsSuccess)
                    {
                        return content.Error($"任务完成失败:MES进站失败: {inboundResult?.Data?.Msg ?? inboundResult?.ErrorMessage ?? "未知错误"}");
                    }
                    //var inboundRequest = new InboundInContainerRequest
                    //{
                    //    EquipmentCode = "STK-GROUP-001",
                    //    ResourceCode = "STK-GROUP-001",
                    //    LocalTime = DateTime.Now,
                    //    ContainerCode = taskDto.PalletCode
                    //};
                    //var inboundResult = _mesService.InboundInContainer(inboundRequest);
                    //if (inboundResult == null || inboundResult.Data == null || !inboundResult.Data.IsSuccess)
                    //{
                    //    return content.Error($"任务完成失败:MES进站失败: {inboundResult?.Data?.Msg ?? inboundResult?.ErrorMessage ?? "未知错误"}");
                    //}
                    return await CompleteTaskAsync(task, "入库完成");
                });
            }
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Robot.cs
@@ -129,9 +129,9 @@
                    Creater = "system"
                };
                var result = await Repository.AddDataAsync(task) > 0;
                if (!result)
                    return WebResponseContent.Instance.Error($"机械手{taskName}任务创建失败");
                //var result = await Repository.AddDataAsync(task) > 0;
                //if (!result)
                //    return WebResponseContent.Instance.Error($"机械手{taskName}任务创建失败");
                var wmstaskDto = _mapper.Map<WMSTaskDTO>(task) ?? new WMSTaskDTO();
                wmstaskDto.TaskQuantity = stock.Details?.Sum(d => d.Quantity) ?? 0;