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>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | namespace WIDESEAWCS_Common |
| | | { |
| | | public class RobotConst |
| | | { |
| | | /// <summary> |
| | | /// 任塿»æ°ä¸é |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 彿ºå¨äººå¤ççè´§ç©æ°éè¾¾å°æ¤ä¸éæ¶ï¼ä¸åä¸åæ°ä»»å¡ã |
| | | /// 鲿¢æºå¨äººè¿åº¦å³ç´¯æç³»ç»è¿è½½ã |
| | | /// </remarks> |
| | | public const int MaxTaskTotalNum = 48; |
| | | } |
| | | } |
| | |
| | | |
| | | 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> |
| | |
| | | |
| | | 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> |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | |
| | | _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) |
| | | { |
| | |
| | | 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; |
| | |
| | | [DisallowConcurrentExecution] |
| | | public class RobotJob : IJob |
| | | { |
| | | /// <summary> |
| | | /// 任塿»æ°ä¸é |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 彿ºå¨äººå¤ççè´§ç©æ°éè¾¾å°æ¤ä¸éæ¶ï¼ä¸åä¸åæ°ä»»å¡ã |
| | | /// 鲿¢æºå¨äººè¿åº¦å³ç´¯æç³»ç»è¿è½½ã |
| | | /// </remarks> |
| | | private const int MaxTaskTotalNum = 48; |
| | | |
| | | /// <summary> |
| | | /// æ¶æ¯äºä»¶è®¢é
æ å¿ |
| | | /// </summary> |
| | |
| | | |
| | | // åå§åå½ä»¤å¤çå¨ |
| | | // ç®åå½ä»¤å¤çå¨ï¼å¤çç¶ææ´æ°çç®åå½ä»¤ |
| | | var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor); |
| | | var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor, socketGateway); |
| | | // åç¼å½ä»¤å¤çå¨ï¼å¤ç pickfinishedãputfinished ç另忰çå½ä»¤ |
| | | var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway); |
| | | |
| | |
| | | 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) |
| | |
| | | } |
| | | |
| | | // æ£æ¥ä»»å¡æ»æ°æ¯å¦æªè¾¾å°ä¸é |
| | | //if (latestState.RobotTaskTotalNum < MaxTaskTotalNum) |
| | | //{ |
| | | if (latestState.RobotTaskTotalNum < RobotConst.MaxTaskTotalNum) |
| | | { |
| | | // è°ç¨å·¥ä½æµç¼æå¨æ§è¡ä»»å¡ |
| | | // ç¼æå¨ä¼æ ¹æ®å½åç¶æå³å®ä¸ä¸æ¥å¨ä½ |
| | | await _workflowOrchestrator.ExecuteAsync(latestState, task, ipAddress); |
| | | //} |
| | | } |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | |
| | | using Microsoft.Extensions.Logging; |
| | | using System.Net; |
| | | using System.Net.Sockets; |
| | | using WIDESEAWCS_Common; |
| | | using WIDESEAWCS_Core.Caches; |
| | |
| | | _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}"; |
| | | |
| | |
| | | public RobotCraneDevice? RobotCrane { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æåå§å宿åå°å¾
æºä½ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// å¯è½çå¼ï¼ |
| | | /// - "Homed": å·²åé¶ |
| | | /// - "Homing": åé¶ä¸ |
| | | /// </remarks> |
| | | public string? Homed { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åæ£å¨æ§è¡çå¨ä½ |
| | | /// </summary> |
| | | /// <remarks> |
| | |
| | | } |
| | | |
| | | /// <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> |
| | |
| | | |
| | | // æ ¹æ®å··éåç§°å¤æä»åº 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; |
| | |
| | | } |
| | | |
| | | // è§£æè¿åçä»»å¡ä¿¡æ¯ |
| | | 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; |
| | |
| | | // ç®æ è¾é线ç¼å· |
| | | TargetLineNo = state.CurrentTask.RobotTargetAddressLineCode, |
| | | |
| | | // å··éç¼å·(æºå¨äººåç§°) |
| | | Roadway = state.CurrentTask.RobotRoadway, |
| | | |
| | | // çµæ± ä½ç½®è¯¦æ
å表 |
| | | // è¿æ»¤æä½ç½®ä¸º 0 æè´æ°çæ ææ°æ® |
| | | // æä½ç½®ç¼å·æåº |
| | |
| | | .Select((x, idx) => new StockDetailDTO |
| | | { |
| | | // æ°éï¼å¦æå·²æä»»å¡æ»æ°ï¼ä½¿ç¨ä»»å¡æ»æ°+å½åä½ç½®æ°ï¼å¦ååªä½¿ç¨å½åä½ç½®æ° |
| | | Quantity = state.RobotTaskTotalNum > 0 ? state.RobotTaskTotalNum + positions.Length : positions.Length, |
| | | Quantity = 1, |
| | | |
| | | // éé/ä½ç½®ç¼å· |
| | | Channel = x, |
| | |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEAWCS_Core.LogHelper; |
| | | using WIDESEAWCS_Tasks.Workflow.Abstractions; |
| | | |
| | | namespace WIDESEAWCS_Tasks.Workflow |
| | |
| | | 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> |
| | |
| | | |
| | | // æºå¨äººæ£å¨åé¶ |
| | | case "homing": |
| | | state.OperStatus = "Homing"; |
| | | state.Homed = "Homing"; |
| | | return true; |
| | | |
| | | // æºå¨äººå·²å®æåé¶ |
| | | case "homed": |
| | | state.OperStatus = "Homed"; |
| | | state.Homed = "Homed"; |
| | | return true; |
| | | |
| | | // æºå¨äººæ£å¨è¿è¡ |
| | |
| | | { |
| | | // å
¥åºæåï¼å é¤ä»»å¡è®°å½ |
| | | _taskProcessor.DeleteTask(currentTask.RobotTaskId); |
| | | await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished"); |
| | | QuartzLogger.Info($"åéæ¶æ¯ï¼ãSwap,diskFinishedã", state.RobotCrane.DeviceName); |
| | | return true; |
| | | } |
| | | } |
| | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | // 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ï¼åè´§å®æï¼ |
| | |
| | | // 妿æ¡ç çææå |
| | | 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); |
| | |
| | | /// </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; |
| | | } |
| | | |
| | | // è§£æçå¬å°å |
| | |
| | | { |
| | | ipAddress = parsedAddress; |
| | | } |
| | | |
| | | await Task.Delay(5000); |
| | | |
| | | // å建çå¬å¨ |
| | | _listener = new TcpListener(ipAddress, _options.Port); |
| | |
| | | // å¯å¨å®¢æ·ç«¯çæ§ä»»å¡ï¼æ£æ¥ç©ºé²è¶
æ¶ï¼ |
| | | _monitorTask = Task.Run(() => MonitorClientsAsync(_cts.Token)); |
| | | |
| | | return Task.CompletedTask; |
| | | return; |
| | | } |
| | | |
| | | /// <summary> |