wanshenmean
8 天以前 adb4016b5eb5b119a899480c321be996d9bf10bd
feat(robot): 完善机械手任务处理逻辑与状态管理

主要更新:
- 新增 QueryRobotCraneExecutingTask 方法获取执行中任务
- 优化 TaskService 重复任务处理逻辑,返回重复任务信息
- 修复 HttpClientHelper 缓存键生成问题
- RobotJob 增加 Picking/Puting 状态检查及执行中任务获取
- RobotMessageHandler 增加 MaxTaskTotalNum 任务总数限制检查
- RobotSocketState 新增 Homed 属性追踪机械手回零状态
- RobotSimpleCommandHandler 优化 allpickfinished/allputfinished 处理
- RobotWorkflowOrchestrator 增加 Homed 状态检查与条码重复校验
- TcpSocketServer 增加启动延迟,确保服务就绪
- 新增 RobotConst.cs 机械手常量定义

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
已添加2个文件
已修改11个文件
164 ■■■■ 文件已修改
Code/WCS/DataDictionary.pdf 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/RobotEnum/RobotConst.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Http/HTTP/HttpClientHelper.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IRobotTaskService.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/DataDictionary.pdf
Binary files differ
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/RobotEnum/RobotConst.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
namespace WIDESEAWCS_Common
{
    public class RobotConst
    {
        /// <summary>
        /// ä»»åŠ¡æ€»æ•°ä¸Šé™
        /// </summary>
        /// <remarks>
        /// å½“机器人处理的货物数量达到此上限时,不再下发新任务。
        /// é˜²æ­¢æœºå™¨äººè¿‡åº¦åŠ³ç´¯æˆ–ç³»ç»Ÿè¿‡è½½ã€‚
        /// </remarks>
        public const int MaxTaskTotalNum = 48;
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Http/HTTP/HttpClientHelper.cs
@@ -121,7 +121,7 @@
        public HttpResponseResult<TResponse> Get<TResponse>(string url, HttpRequestConfig? config = null)
        {
            url = BaseAPI.WMSBaseUrl + _cache.Get(url);
            url = BaseAPI.WMSBaseUrl + _cache.Get($"{RedisPrefix.Code}:{RedisName.API}:{url}");
            HttpResponseResult httpResponseResult = Get(url, config);
            HttpResponseResult<TResponse> result = new HttpResponseResult<TResponse>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IRobotTaskService.cs
@@ -57,6 +57,13 @@
        public Dt_RobotTask? QueryRobotCraneTask(string deviceCode);
        /// <summary>
        /// æ ¹æ®è®¾å¤‡ç¼–码获取执行中的机械手任务
        /// </summary>
        /// <param name="deviceCode">设备编码</param>
        /// <returns>执行中的任务对象,如果没有则返回 null</returns>
        public Dt_RobotTask? QueryRobotCraneExecutingTask(string deviceCode);
        Task<bool> UpdateRobotTaskAsync(Dt_RobotTask robotTask);
        /// <summary>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
@@ -111,6 +111,11 @@
            return BaseDal.QueryFirst(x => x.RobotRoadway == deviceCode && x.RobotTaskState != (int)TaskRobotStatusEnum.RobotExecuting, TaskOrderBy);
        }
        public Dt_RobotTask? QueryRobotCraneExecutingTask(string deviceCode)
        {
            return BaseDal.QueryFirst(x => x.RobotRoadway == deviceCode && x.RobotTaskState == (int)TaskRobotStatusEnum.RobotExecuting, TaskOrderBy);
        }
        public async Task<bool> UpdateRobotTaskAsync(Dt_RobotTask robotTask)
        {
            return await BaseDal.UpdateDataAsync(robotTask);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
@@ -101,10 +101,13 @@
            try
            {
                List<Dt_Task> tasks = new List<Dt_Task>();
                List<Dt_Task> duplicates = new List<Dt_Task>();
                foreach (var item in taskDTOs)
                {
                    if (BaseDal.QueryFirst(x => x.TaskNum == item.TaskNum || x.PalletCode == item.PalletCode) != null)
                    Dt_Task existingTask = BaseDal.QueryFirst(x => x.TaskNum == item.TaskNum || x.PalletCode == item.PalletCode);
                    if (existingTask != null)
                    {
                        duplicates.Add(existingTask);
                        continue;
                    }
                    Dt_Task task = _mapper.Map<Dt_Task>(item);
@@ -117,7 +120,10 @@
                _taskExecuteDetailService.AddTaskExecuteDetail(tasks.Select(x => x.TaskNum).ToList(), "接收WMS任务");
                content = WebResponseContent.Instance.OK("成功", tasks);
                // å°†é‡å¤ä»»åŠ¡ä¿¡æ¯ä¹Ÿä¸€å¹¶è¿”å›ž
                tasks.AddRange(duplicates);
                var result = tasks;
                content = WebResponseContent.Instance.OK("成功", result);
            }
            catch (Exception ex)
            {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging;
using Quartz;
using System.Net;
using WIDESEA_Core;
using WIDESEAWCS_Common;
using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
@@ -29,15 +29,6 @@
    [DisallowConcurrentExecution]
    public class RobotJob : IJob
    {
        /// <summary>
        /// ä»»åŠ¡æ€»æ•°ä¸Šé™
        /// </summary>
        /// <remarks>
        /// å½“机器人处理的货物数量达到此上限时,不再下发新任务。
        /// é˜²æ­¢æœºå™¨äººè¿‡åº¦åŠ³ç´¯æˆ–ç³»ç»Ÿè¿‡è½½ã€‚
        /// </remarks>
        private const int MaxTaskTotalNum = 48;
        /// <summary>
        /// æ¶ˆæ¯äº‹ä»¶è®¢é˜…标志
        /// </summary>
@@ -131,7 +122,7 @@
            // åˆå§‹åŒ–命令处理器
            // ç®€å•命令处理器:处理状态更新等简单命令
            var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor);
            var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor, socketGateway);
            // å‰ç¼€å‘½ä»¤å¤„理器:处理 pickfinished、putfinished ç­‰å¸¦å‚数的命令
            var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway);
@@ -214,8 +205,19 @@
                    return;
                }
                if (state.CurrentAction == "Picking" || state.CurrentAction == "Puting")
                {
                    return;
                }
                // è½®è¯¢èŽ·å–è¯¥è®¾å¤‡çš„å¾…å¤„ç†ä»»åŠ¡
                var task = _taskProcessor.GetTask(robotCrane);
                // å¦‚果没有获取到待处理任务,且RobotArmObject为1(有物料),则获取该设备执行中的任务
                if (task == null && state.RobotArmObject == 1)
                {
                    task = _taskProcessor.GetExecutingTask(robotCrane);
                }
                // å¦‚果有待处理任务
                if (task != null)
@@ -229,12 +231,12 @@
                    }
                    // æ£€æŸ¥ä»»åŠ¡æ€»æ•°æ˜¯å¦æœªè¾¾åˆ°ä¸Šé™
                    //if (latestState.RobotTaskTotalNum < MaxTaskTotalNum)
                    //{
                    if (latestState.RobotTaskTotalNum < RobotConst.MaxTaskTotalNum)
                    {
                        // è°ƒç”¨å·¥ä½œæµç¼–排器执行任务
                        // ç¼–排器会根据当前状态决定下一步动作
                        await _workflowOrchestrator.ExecuteAsync(latestState, task, ipAddress);
                    //}
                    }
                }
            }
            catch (Exception ex)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs
@@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using System.Net;
using System.Net.Sockets;
using WIDESEAWCS_Common;
using WIDESEAWCS_Core.Caches;
@@ -119,6 +118,17 @@
            _logger.LogInformation($"接收到客户端【{state.RobotCrane.DeviceName}】发送消息【{message}】");
            QuartzLogger.Info($"接收到客户端消息【{message}】", state.RobotCrane.DeviceName);
            // æ£€æŸ¥ä»»åŠ¡æ€»æ•°æ˜¯å¦æœªè¾¾åˆ°ä¸Šé™
            if (state.RobotTaskTotalNum > RobotConst.MaxTaskTotalNum)
            {
                // è®°å½•接收到的消息日志
                _logger.LogInformation($"接收到客户端【{state.RobotCrane.DeviceName}】发送消息【{message}】");
                QuartzLogger.Info($"接收到客户端消息【{message}】", state.RobotCrane.DeviceName);
                // å¤„理成功后,将原消息回写到客户端(保持原有行为)
                await _socketClientGateway.SendMessageAsync(client, message);
                return null;
            }
            // æž„建缓存键,检查 Redis ä¸­æ˜¯å¦å­˜åœ¨è¯¥è®¾å¤‡çš„状态
            var cacheKey = $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}";
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
@@ -74,6 +74,16 @@
        public RobotCraneDevice? RobotCrane { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹åˆå§‹åŒ–完成回到待机位
        /// </summary>
        /// <remarks>
        /// å¯èƒ½çš„值:
        /// - "Homed": å·²å›žé›¶
        /// - "Homing": å›žé›¶ä¸­
        /// </remarks>
        public string? Homed { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹å½“前正在执行的动作
        /// </summary>
        /// <remarks>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -113,6 +113,20 @@
        }
        /// <summary>
        /// æŒ‰è®¾å¤‡ç¼–码获取当前机器人的执行中任务
        /// </summary>
        /// <remarks>
        /// ä»Žæ•°æ®åº“中查询指定设备编码的执行中机器人任务。
        /// å½“RobotArmObject为1(有物料)且没有待处理任务时调用。
        /// </remarks>
        /// <param name="robotCrane">机器人设备信息,包含设备编码</param>
        /// <returns>执行中的任务对象,如果没有则返回 null</returns>
        public Dt_RobotTask? GetExecutingTask(RobotCraneDevice robotCrane)
        {
            return _robotTaskService.QueryRobotCraneExecutingTask(robotCrane.DeviceCode);
        }
        /// <summary>
        /// åˆ é™¤æœºå™¨äººä»»åŠ¡
        /// </summary>
        /// <remarks>
@@ -208,7 +222,7 @@
            // æ ¹æ®å··é“名称判断仓库 ID
            // ZYRB1 -> 1, HPRB001 -> 2, å…¶ä»– -> 3
            int warehouseId = currentTask.RobotRoadway == "ZYRB1" ? 1 : currentTask.RobotRoadway == "HPRB001" ? 2 : 3;
            int warehouseId = currentTask.RobotRoadway == "注液组盘机械手" ? 1 : currentTask.RobotRoadway == "HPRB001" ? 2 : 3;
            // ä»»åŠ¡ç±»åž‹ï¼ˆ0 è¡¨ç¤ºæœªå®šä¹‰ï¼Œç¨åŽæ ¹æ®ä»»åŠ¡ç±»åž‹è®¾ç½®ï¼‰
            int taskType = 0;
@@ -303,7 +317,8 @@
            }
            // è§£æžè¿”回的任务信息
            var taskInfo = JsonConvert.DeserializeObject<Dt_Task>(content.Data.ToJson() ?? string.Empty) ?? new Dt_Task();
            var taskInfos = JsonConvert.DeserializeObject<List<Dt_Task>>(content.Data.ToJson() ?? string.Empty) ?? new List<Dt_Task>();
            var taskInfo = taskInfos.FirstOrDefault();
            // èŽ·å–æºåœ°å€
            string sourceAddress = taskDTO.SourceAddress;
@@ -363,6 +378,9 @@
                // ç›®æ ‡è¾“送线编号
                TargetLineNo = state.CurrentTask.RobotTargetAddressLineCode,
                // å··é“编号(机器人名称)
                Roadway = state.CurrentTask.RobotRoadway,
                // ç”µæ± ä½ç½®è¯¦æƒ…列表
                // è¿‡æ»¤æŽ‰ä½ç½®ä¸º 0 æˆ–负数的无效数据
                // æŒ‰ä½ç½®ç¼–号排序
@@ -373,7 +391,7 @@
                    .Select((x, idx) => new StockDetailDTO
                    {
                        // æ•°é‡ï¼šå¦‚果已有任务总数,使用任务总数+当前位置数;否则只使用当前位置数
                        Quantity = state.RobotTaskTotalNum > 0 ? state.RobotTaskTotalNum + positions.Length : positions.Length,
                        Quantity = 1,
                        // é€šé“/位置编号
                        Channel = x,
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -1,4 +1,5 @@
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks.Workflow
@@ -26,12 +27,21 @@
        private readonly RobotTaskProcessor _taskProcessor;
        /// <summary>
        /// Socket ç½‘å…³
        /// </summary>
        /// <remarks>
        /// ç”¨äºŽå‘客户端发送响应消息。
        /// </remarks>
        private readonly ISocketClientGateway _socketClientGateway;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="taskProcessor">任务处理器实例</param>
        public RobotSimpleCommandHandler(RobotTaskProcessor taskProcessor)
        public RobotSimpleCommandHandler(RobotTaskProcessor taskProcessor, ISocketClientGateway socketClientGateway)
        {
            _taskProcessor = taskProcessor;
            _socketClientGateway = socketClientGateway;
        }
        /// <summary>
@@ -71,12 +81,12 @@
                // æœºå™¨äººæ­£åœ¨å›žé›¶
                case "homing":
                    state.OperStatus = "Homing";
                    state.Homed = "Homing";
                    return true;
                // æœºå™¨äººå·²å®Œæˆå›žé›¶
                case "homed":
                    state.OperStatus = "Homed";
                    state.Homed = "Homed";
                    return true;
                // æœºå™¨äººæ­£åœ¨è¿è¡Œ
@@ -137,6 +147,8 @@
                        {
                            // å…¥åº“成功,删除任务记录
                            _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                                await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
                                QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
                            return true;
                        }
                    }
@@ -173,6 +185,9 @@
                            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;
                        }
                    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -111,13 +111,8 @@
            // 1. è¿è¡Œæ¨¡å¼ä¸ºè‡ªåŠ¨ï¼ˆ2)
            // 2. æŽ§åˆ¶æ¨¡å¼ä¸ºå®¢æˆ·ç«¯æŽ§åˆ¶ï¼ˆ1)
            // 3. è¿è¡ŒçŠ¶æ€æ˜¯ Running
            if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running")
            if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running" && latestState.Homed == "Homed")
            {
                if(latestState.CurrentAction == "Picking" ||  latestState.CurrentAction == "Puting")
                {
                    return;
                }
                // ========== å–货完成后的放货处理 ==========
                // æ¡ä»¶ï¼š
                // - å½“前动作是 PickFinished æˆ– AllPickFinished(取货完成)
@@ -255,9 +250,23 @@
                // å¦‚果条码生成成功
                if (!string.IsNullOrEmpty(trayBarcode1) && !string.IsNullOrEmpty(trayBarcode2))
                {
                    if(stateForUpdate.CellBarcode.Contains(trayBarcode1)|| stateForUpdate.CellBarcode.Contains(trayBarcode2))
                    {
                        _logger.LogError("HandlePutFinishedStateAsync:生成的托盘条码已存在,可能存在重复,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Error($"生成的托盘条码已存在,可能存在重复", stateForUpdate.RobotCrane.DeviceName);
                        // æ¡ç é‡å¤ï¼Œè®°å½•错误日志并停止后续操作(后续放货时会用到这些条码信息,供后续放货时使用,调试后可能会取消此逻辑)
                        return;
                    }
                    else
                    {
                        _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);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs
@@ -19,11 +19,11 @@
        /// </remarks>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>启动任务</returns>
        public Task StartAsync(CancellationToken cancellationToken)
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            if (IsRunning || !_options.Enabled)
            {
                return Task.CompletedTask;
                return;
            }
            // è§£æžç›‘听地址
@@ -32,6 +32,8 @@
            {
                ipAddress = parsedAddress;
            }
            await Task.Delay(5000);
            // åˆ›å»ºç›‘听器
            _listener = new TcpListener(ipAddress, _options.Port);
@@ -45,7 +47,7 @@
            // å¯åŠ¨å®¢æˆ·ç«¯ç›‘æŽ§ä»»åŠ¡ï¼ˆæ£€æŸ¥ç©ºé—²è¶…æ—¶ï¼‰
            _monitorTask = Task.Run(() => MonitorClientsAsync(_cts.Token));
            return Task.CompletedTask;
            return;
        }
        /// <summary>