feat: 添加堆垛机TargetAddress输送线站台检查
当 TrySelectOutboundTask 因 TargetAddress 输送线站台不空闲返回 null 时,
先尝试同 NextAddress 的其他出库任务,再尝试不同 NextAddress 的任务
refactor(StackerCraneTaskSelector): 优化 TrySelectOutboundTask 检查顺序
将本地站台检查(TargetAddress、NextAddress)提前到 WMS 移库检查之前,
避免站台不可用时发起不必要的 HTTP 调用
docs: 添加TargetAddress不可用时继续搜索实施计划
docs: 添加TargetAddress不可用时继续搜索设计文档
feat(StackerCraneTaskSelector): 在 TrySelectOutboundTask 中调用 TargetAddress 站台空闲检查
检查顺序:先检查 NextAddress 出库站台,再检查 TargetAddress 输送线站台
若 TargetAddress 站台不空闲(CV_State != 2),任务不可选
feat(StackerCraneTaskSelector): 新增 TargetAddress 输送线站台空闲检查方法
IsTargetAddressConveyorStationAvailable 通过路由查找输送线设备,读取 CV_State 判断是否空闲(CV_State == 2)
docs: 添加堆垛机TargetAddress站台检查实施计划
docs: 添加堆垛机TargetAddress输送线站台检查设计文档
| | |
| | | using WIDESEAWCS_ITaskInfoService; |
| | | using WIDESEAWCS_Model.Models; |
| | | using WIDESEAWCS_QuartzJob; |
| | | using WIDESEAWCS_QuartzJob.ConveyorLine.Enum; |
| | | using WIDESEAWCS_QuartzJob.Models; |
| | | using WIDESEAWCS_QuartzJob.Service; |
| | | |
| | |
| | | /// <param name="httpClientHelper">HTTP 客æ·ç«¯å¸®å©ç±»</param> |
| | | /// <param name="logger">æ¥å¿è®°å½å¨</param> |
| | | public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, HttpClientHelper httpClientHelper, ILogger logger) |
| | | : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum), logger) |
| | | : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum, logger), logger) |
| | | { |
| | | } |
| | | |
| | |
| | | return selectedTask; |
| | | } |
| | | |
| | | // ===== TargetAddress ä¸å¯ç¨æ¶ï¼å
å°è¯å NextAddress çå
¶ä»ä»»å¡ ===== |
| | | var sameStationTasks = _taskService |
| | | .QueryStackerCraneOutTasks(deviceCode, new List<string> { candidateTask.NextAddress }) |
| | | .Where(x => x.TaskId != candidateTask.TaskId) |
| | | .ToList(); |
| | | |
| | | foreach (var sameStationTask in sameStationTasks) |
| | | { |
| | | selectedTask = TrySelectOutboundTask(sameStationTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, $"éä¸åç«å°å¤éåºåºä»»å¡ï¼ä»»å¡å·: {selectedTask.TaskNum}", commonStackerCrane.DeviceName); |
| | | return selectedTask; |
| | | } |
| | | } |
| | | |
| | | // ===== å NextAddress æ å¯ç¨ä»»å¡ï¼å°è¯ä¸å NextAddress çä»»å¡ ===== |
| | | // æ¥æ¾å
¶ä»å¯ç¨çåºåºç«å° |
| | | var otherOutStationCodes = _routerService |
| | | .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType) |
| | |
| | | /// <returns>å¯éä¸çä»»å¡ï¼æ nullï¼ç«å°ä¸å¯ç¨ï¼</returns> |
| | | private Dt_Task? TrySelectOutboundTask(Dt_Task outboundTask) |
| | | { |
| | | // å¯¹äºææåºåºä»»å¡ï¼å¿
é¡»å
è°ç¨ WMS 夿æ¯å¦éè¦ç§»åº |
| | | // å
è¿è¡æ¬å°ç«å°æ£æ¥ï¼PLC 读åï¼å¿«éï¼ï¼é¿å
ä¸å¿
è¦ç WMS HTTP è°ç¨ |
| | | |
| | | // 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | if (!IsTargetAddressConveyorStationAvailable(outboundTask)) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | // 夿 NextAddress åºåºç«å°æ¯å¦å¯ç¨ |
| | | if (!IsOutTaskStationAvailable(outboundTask)) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | // ç«å°æ£æ¥éè¿åï¼è°ç¨ WMS 夿æ¯å¦éè¦ç§»åº |
| | | var taskAfterTransferCheck = _transferCheck(outboundTask.TaskNum) ?? outboundTask; |
| | | var taskGroup = taskAfterTransferCheck.TaskType.GetTaskTypeGroup(); |
| | | |
| | |
| | | return taskAfterTransferCheck; |
| | | } |
| | | |
| | | // 夿åºåºç«å°æ¯å¦å¯ç¨ |
| | | return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null; |
| | | return taskAfterTransferCheck; |
| | | } |
| | | |
| | | /// <summary> |
| | |
| | | /// <param name="httpClientHelper">HTTP 客æ·ç«¯å¸®å©ç±»</param> |
| | | /// <param name="taskNum">ä»»å¡å·</param> |
| | | /// <returns>妿éè¦ç§»åºè¿åç§»åºä»»å¡ï¼å¦åè¿å null</returns> |
| | | private static Dt_Task? QueryTransferTask(HttpClientHelper httpClientHelper, int taskNum) |
| | | private static Dt_Task? QueryTransferTask(HttpClientHelper httpClientHelper, int taskNum, ILogger logger) |
| | | { |
| | | // è°ç¨ WMS çç§»åºæ£æ¥æ¥å£ |
| | | string configKey = nameof(ConfigKey.TransferCheck); |
| | | string requestParam = taskNum.ToString(); |
| | | |
| | | var response = httpClientHelper.Post<WebResponseContent>( |
| | | nameof(ConfigKey.TransferCheck), |
| | | taskNum.ToString()); |
| | | configKey, |
| | | requestParam); |
| | | |
| | | // æ£æ¥ååºæ¯å¦æå |
| | | if (response == null || !response.IsSuccess || response.Data == null || !response.Data.Status || response.Data.Data == null) |
| | | { |
| | | QuartzLogHelper.LogError(logger, $"è°ç¨WMSæ¥å£å¤±è´¥,æ¥å£:ã{configKey}ã,请æ±åæ°:ã{requestParam}ã,é误信æ¯:ã{(response?.Data?.Message ?? "æ ååº")}ã", "StackerCraneTaskSelector"); |
| | | return null; |
| | | } |
| | | |
| | | QuartzLogHelper.LogInfo(logger, $"è°ç¨WMSæ¥å£æå,æ¥å£:ã{configKey}ã,ååºæ°æ®:ã{response.Data.Data}ã", "StackerCraneTaskSelector"); |
| | | |
| | | // è§£æè¿åç任塿°æ® |
| | | var taskJson = response.Data.Data.ToString(); |
| | |
| | | |
| | | return isOccupied; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | /// </summary> |
| | | /// <param name="task">åºåºä»»å¡</param> |
| | | /// <returns>ç«å°ç©ºé²ï¼CV_State == 2ï¼è¿å true</returns> |
| | | private bool IsTargetAddressConveyorStationAvailable([NotNull] Dt_Task task) |
| | | { |
| | | // ç¡®å®ä»»å¡ç±»å |
| | | int taskType = task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty |
| | | ? StackerCraneConst.EmptyPalletTaskType |
| | | : task.TaskType; |
| | | |
| | | // éè¿è·¯ç±æ¥æ¾ TargetAddress 对åºç设å¤ä¿¡æ¯ |
| | | Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType); |
| | | if (router == null) |
| | | { |
| | | QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailableï¼æªæ¾å° TargetAddress è·¯ç±ä¿¡æ¯ï¼TargetAddress: {TargetAddress}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼æªæ¾å° TargetAddress è·¯ç±ä¿¡æ¯ï¼TargetAddress: {task.TargetAddress}", task.Roadway, task.TargetAddress, task.TaskNum); |
| | | return false; |
| | | } |
| | | |
| | | // æ¥æ¾è¾éçº¿è®¾å¤ |
| | | IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode); |
| | | if (device == null) |
| | | { |
| | | QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailableï¼æªæ¾å°è¾é线设å¤ï¼ChildPosiDeviceCode: {ChildPosiDeviceCode}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼æªæ¾å°è¾é线设å¤ï¼ChildPosiDeviceCode: {router.ChildPosiDeviceCode}", task.Roadway, router.ChildPosiDeviceCode, task.TaskNum); |
| | | return false; |
| | | } |
| | | |
| | | // 转æ¢ä¸ºè¾éçº¿è®¾å¤ |
| | | CommonConveyorLine conveyorLine = (CommonConveyorLine)device; |
| | | |
| | | // 读å CV_Stateï¼CV_State == 2 è¡¨ç¤ºç©ºé² |
| | | byte cvState = conveyorLine.GetValue<ConveyorLineStatus, byte>(ConveyorLineStatus.CV_State, task.TargetAddress); |
| | | bool isAvailable = cvState == 2; |
| | | QuartzLogHelper.LogInfo(_logger, "IsTargetAddressConveyorStationAvailableï¼TargetAddress: {TargetAddress}ï¼CV_State: {CV_State}ï¼æ¯å¦ç©ºé²: {IsAvailable}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼TargetAddress: {task.TargetAddress}ï¼CV_State: {cvState}ï¼æ¯å¦ç©ºé²: {isAvailable}", task.Roadway, task.TargetAddress, cvState, isAvailable, task.TaskNum); |
| | | |
| | | return isAvailable; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # å åæº TargetAddress è¾é线ç«å°ç©ºé²æ£æ¥ 宿½è®¡å |
| | | |
| | | > **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:** å¨ `StackerCraneTaskSelector.TrySelectOutboundTask` 䏿°å¢ `TargetAddress` è¾é线ç«å°ç©ºé²æ£æ¥ï¼CV_State == 2 表示空é²ï¼ï¼ä¸å¯ç¨åè¿å nullã |
| | | |
| | | **Architecture:** å¨ `StackerCraneTaskSelector` ç±»ä¸æ°å¢ `IsTargetAddressConveyorStationAvailable` ç§ææ¹æ³ï¼éè¿ `_routerService.QueryNextRoute` è·åè·¯ç±ä¿¡æ¯ï¼ä» `Storage.Devices` æ¾å°è¾é线设å¤ï¼è°ç¨ `GetValue<ConveyorLineStatus, byte>` 读å `CV_State` 夿æ¯å¦ç©ºé²ã |
| | | |
| | | **Tech Stack:** C# / .NET 6+ï¼SqlSugar ORMï¼Serilog |
| | | |
| | | --- |
| | | |
| | | ## æ¶åæä»¶ |
| | | |
| | | - ä¿®æ¹: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs` |
| | | |
| | | --- |
| | | |
| | | ## Task 1: æ°å¢ IsTargetAddressConveyorStationAvailable æ¹æ³ |
| | | |
| | | **Files:** |
| | | - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs` |
| | | |
| | | - [ ] **Step 1: ç¡®è®¤ç°æ using å½å空é´** |
| | | |
| | | æå¼ `StackerCraneTaskSelector.cs`ï¼æ£æ¥æ¯å¦å·²å
å«ä»¥ä¸ usingï¼ |
| | | - `WIDESEAWCS_QuartzJob.ConveyorLine.Enum.ConveyorLineStatus` |
| | | - `WIDESEAWCS_Model.Models.Dt_Router` |
| | | |
| | | å¦ç¼ºå°ï¼æ·»å ï¼ |
| | | ```csharp |
| | | using WIDESEAWCS_QuartzJob.ConveyorLine.Enum; |
| | | using WIDESEAWCS_Model.Models; |
| | | ``` |
| | | |
| | | - [ ] **Step 2: å¨ç±»æ«å°¾ï¼`IsOutTaskStationAvailable` æ¹æ³ä¹åï¼`}` ä¹åï¼æ°å¢æ¹æ³** |
| | | |
| | | ```csharp |
| | | /// <summary> |
| | | /// 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | /// </summary> |
| | | /// <param name="task">åºåºä»»å¡</param> |
| | | /// <returns>ç«å°ç©ºé²ï¼CV_State == 2ï¼è¿å true</returns> |
| | | private bool IsTargetAddressConveyorStationAvailable([NotNull] Dt_Task task) |
| | | { |
| | | // ç¡®å®ä»»å¡ç±»å |
| | | int taskType = task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty |
| | | ? StackerCraneConst.EmptyPalletTaskType |
| | | : task.TaskType; |
| | | |
| | | // éè¿è·¯ç±æ¥æ¾ TargetAddress 对åºç设å¤ä¿¡æ¯ |
| | | Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType); |
| | | if (router == null) |
| | | { |
| | | QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailableï¼æªæ¾å° TargetAddress è·¯ç±ä¿¡æ¯ï¼TargetAddress: {TargetAddress}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼æªæ¾å° TargetAddress è·¯ç±ä¿¡æ¯ï¼TargetAddress: {task.TargetAddress}", task.Roadway, task.TargetAddress, task.TaskNum); |
| | | return false; |
| | | } |
| | | |
| | | // æ¥æ¾è¾éçº¿è®¾å¤ |
| | | IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode); |
| | | if (device == null) |
| | | { |
| | | QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailableï¼æªæ¾å°è¾é线设å¤ï¼ChildPosiDeviceCode: {ChildPosiDeviceCode}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼æªæ¾å°è¾é线设å¤ï¼ChildPosiDeviceCode: {router.ChildPosiDeviceCode}", task.Roadway, router.ChildPosiDeviceCode, task.TaskNum); |
| | | return false; |
| | | } |
| | | |
| | | // 转æ¢ä¸ºè¾éçº¿è®¾å¤ |
| | | CommonConveyorLine conveyorLine = (CommonConveyorLine)device; |
| | | |
| | | // 读å CV_Stateï¼CV_State == 2 è¡¨ç¤ºç©ºé² |
| | | byte cvState = conveyorLine.GetValue<ConveyorLineStatus, byte>(ConveyorLineStatus.CV_State, task.TargetAddress); |
| | | bool isAvailable = cvState == 2; |
| | | QuartzLogHelper.LogInfo(_logger, "IsTargetAddressConveyorStationAvailableï¼TargetAddress: {TargetAddress}ï¼CV_State: {CV_State}ï¼æ¯å¦ç©ºé²: {IsAvailable}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼TargetAddress: {task.TargetAddress}ï¼CV_State: {cvState}ï¼æ¯å¦ç©ºé²: {isAvailable}", task.Roadway, task.TargetAddress, cvState, isAvailable, task.TaskNum); |
| | | |
| | | return isAvailable; |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 3: Commit** |
| | | |
| | | ```bash |
| | | git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs |
| | | git commit -m "feat(StackerCraneTaskSelector): æ°å¢ TargetAddress è¾é线ç«å°ç©ºé²æ£æ¥æ¹æ³ |
| | | |
| | | IsTargetAddressConveyorStationAvailable éè¿è·¯ç±æ¥æ¾è¾é线设å¤ï¼è¯»å CV_State 夿æ¯å¦ç©ºé²ï¼CV_State == 2ï¼ |
| | | |
| | | Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 2: å¨ TrySelectOutboundTask ä¸è°ç¨æ°æ¹æ³ |
| | | |
| | | **Files:** |
| | | - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs` |
| | | |
| | | - [ ] **Step 1: æ¾å° TrySelectOutboundTask æ¹æ³ä¸ç return è¯å¥** |
| | | |
| | | å½åæ¹æ³æ«å°¾ï¼line ~210ï¼ï¼ |
| | | ```csharp |
| | | return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null; |
| | | ``` |
| | | |
| | | - [ ] **Step 2: å¨ return è¯å¥ä¹åæå
¥ TargetAddress æ£æ¥é»è¾** |
| | | |
| | | å¨ `return IsOutTaskStationAvailable(...)` ä¹åæå
¥ï¼ |
| | | ```csharp |
| | | // 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck)) |
| | | { |
| | | return null; |
| | | } |
| | | ``` |
| | | |
| | | ä¿®æ¹åï¼ |
| | | ```csharp |
| | | // 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck)) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | // 夿åºåºç«å°æ¯å¦å¯ç¨ |
| | | return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null; |
| | | ``` |
| | | |
| | | - [ ] **Step 3: éªè¯ä¿®æ¹ä½ç½®æ£ç¡®** |
| | | |
| | | 确认修æ¹åç `TrySelectOutboundTask` æ¹æ³æ«å°¾é»è¾ä¸ºï¼ |
| | | ```csharp |
| | | // 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck)) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | // 夿åºåºç«å°æ¯å¦å¯ç¨ |
| | | return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null; |
| | | ``` |
| | | |
| | | - [ ] **Step 4: Commit** |
| | | |
| | | ```bash |
| | | git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs |
| | | git commit -m "feat(StackerCraneTaskSelector): å¨ TrySelectOutboundTask ä¸è°ç¨ TargetAddress ç«å°ç©ºé²æ£æ¥ |
| | | |
| | | æ£æ¥é¡ºåºï¼å
æ£æ¥ NextAddress åºåºç«å°ï¼åæ£æ¥ TargetAddress è¾é线ç«å° |
| | | è¥ TargetAddress ç«å°ä¸ç©ºé²ï¼CV_State != 2ï¼ï¼ä»»å¡ä¸å¯é |
| | | |
| | | Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 3: éªè¯æå»º |
| | | |
| | | - [ ] **Step 1: æ§è¡æå»ºéªè¯** |
| | | |
| | | ```bash |
| | | cd D:/Git/ShanMeiXinNengYuan/Code |
| | | dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln |
| | | ``` |
| | | |
| | | é¢æï¼æ ç¼è¯é误 |
| | | |
| | | - [ ] **Step 2: 妿é误ï¼åæå¹¶ä¿®å¤** |
| | | |
| | | 常è§éè¯¯ï¼ |
| | | - ç¼ºå° using â æ·»å å½åç©ºé´ |
| | | - ç±»å转æ¢å¼å¸¸ â 确认设å¤ç±»å为 `CommonConveyorLine` |
| | | - æ¹æ³æªæ¾å° â ç¡®è®¤æ¹æ³ç¾åæ£ç¡® |
| | | |
| | | --- |
| | | |
| | | ## éªè¯æ¸
å |
| | | |
| | | - [ ] `IsTargetAddressConveyorStationAvailable` æ¹æ³å·²æ·»å å¨ `IsOutTaskStationAvailable` æ¹æ³ä¹å |
| | | - [ ] `TrySelectOutboundTask` æ¹æ³å¨å¤æ `IsOutTaskStationAvailable` ä¹åï¼å
夿 `IsTargetAddressConveyorStationAvailable` |
| | | - [ ] æ°å¢æ¹æ³å
å«å®æ´çæ¥å¿è®°å½ï¼Warn å Infoï¼ |
| | | - [ ] `CV_State == 2` ä½ä¸ºç©ºé²å¤ææ¡ä»¶ |
| | | - [ ] `task.TargetAddress` ç´æ¥ä½ä¸º `GetValue` ç设å¤åç¼å· |
| | | - [ ] æå»ºéè¿ï¼æ ç¼è¯é误 |
| | | - [ ] å·²æäº¤ä¸¤ä¸ª commitï¼æ¹æ³æ°å¢ + è°ç¨ç¹ï¼ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # å åæº TargetAddress ä¸å¯ç¨æ¶ç»§ç»æç´¢ 宿½è®¡å |
| | | |
| | | > **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:** å¨ `SelectTask` æ¹æ³ä¸ï¼å½ `TrySelectOutboundTask` å `TargetAddress` ä¸å¯ç¨è¿å null æ¶ï¼å¢å å NextAddress å
¶ä»ä»»å¡çæç´¢é»è¾ã |
| | | |
| | | **Architecture:** å¨ `SelectTask` ç `TrySelectOutboundTask(candidateTask)` è¿å null ä¹åãç°æå¤éä»»å¡å¾ªç¯ä¹åï¼æå
¥å NextAddress ä»»å¡æç´¢ã |
| | | |
| | | **Tech Stack:** C# / .NET 6+ï¼SqlSugar ORMï¼Serilog |
| | | |
| | | --- |
| | | |
| | | ## æ¶åæä»¶ |
| | | |
| | | - ä¿®æ¹: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs` |
| | | |
| | | --- |
| | | |
| | | ## Task 1: ä¿®æ¹ SelectTask æ¹æ³ - æ·»å å NextAddress ä»»å¡æç´¢ |
| | | |
| | | **Files:** |
| | | - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs` |
| | | |
| | | - [ ] **Step 1: æ¾å° SelectTask æ¹æ³ä¸çç®æ ä½ç½®** |
| | | |
| | | æå¼ `StackerCraneTaskSelector.cs`ï¼æ¾å° `SelectTask` æ¹æ³ä¸ç以ä¸ä»£ç ï¼çº¦ lines 143-166ï¼ï¼ |
| | | |
| | | ```csharp |
| | | // å°è¯éæ©åºåºä»»å¡ï¼å¯è½éè¦ç§»åºæ£æ¥åç«å°å¯ç¨æ§å¤æï¼ |
| | | Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, $"éä¸åºåºä»»å¡ï¼ä»»å¡å·: {selectedTask.TaskNum}", commonStackerCrane.DeviceName); |
| | | return selectedTask; |
| | | } |
| | | |
| | | // æ¥æ¾å
¶ä»å¯ç¨çåºåºç«å° |
| | | var otherOutStationCodes = _routerService |
| | | .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType) |
| | | .Select(x => x.ChildPosi) |
| | | .ToList(); |
| | | |
| | | // æ¥è¯¢å
¶ä»ç«å°çåºåºä»»å¡ |
| | | var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes); |
| | | foreach (var alternativeTask in tasks) |
| | | { |
| | | selectedTask = TrySelectOutboundTask(alternativeTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, $"éä¸å¤éåºåºä»»å¡ï¼ä»»å¡å·: {selectedTask.TaskNum}", commonStackerCrane.DeviceName); |
| | | return selectedTask; |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **Step 2: å¨ `// æ¥æ¾å
¶ä»å¯ç¨çåºåºç«å°` ä¹åæå
¥å NextAddress æç´¢é»è¾** |
| | | |
| | | å°ï¼ |
| | | ```csharp |
| | | // æ¥æ¾å
¶ä»å¯ç¨çåºåºç«å° |
| | | var otherOutStationCodes = _routerService |
| | | ... |
| | | ``` |
| | | |
| | | æ¿æ¢ä¸ºï¼ |
| | | ```csharp |
| | | // ===== TargetAddress ä¸å¯ç¨æ¶ï¼å
å°è¯å NextAddress çå
¶ä»ä»»å¡ ===== |
| | | var sameStationTasks = _taskService |
| | | .QueryStackerCraneOutTasks(deviceCode, new List<string> { candidateTask.NextAddress }) |
| | | .Where(x => x.TaskId != candidateTask.TaskId) |
| | | .ToList(); |
| | | |
| | | foreach (var sameStationTask in sameStationTasks) |
| | | { |
| | | selectedTask = TrySelectOutboundTask(sameStationTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, $"éä¸åç«å°å¤éåºåºä»»å¡ï¼ä»»å¡å·: {selectedTask.TaskNum}", commonStackerCrane.DeviceName); |
| | | return selectedTask; |
| | | } |
| | | } |
| | | |
| | | // ===== å NextAddress æ å¯ç¨ä»»å¡ï¼å°è¯ä¸å NextAddress çä»»å¡ ===== |
| | | // æ¥æ¾å
¶ä»å¯ç¨çåºåºç«å° |
| | | var otherOutStationCodes = _routerService |
| | | .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType) |
| | | .Select(x => x.ChildPosi) |
| | | .ToList(); |
| | | |
| | | // æ¥è¯¢å
¶ä»ç«å°çåºåºä»»å¡ |
| | | var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes); |
| | | foreach (var alternativeTask in tasks) |
| | | { |
| | | selectedTask = TrySelectOutboundTask(alternativeTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, $"éä¸å¤éåºåºä»»å¡ï¼ä»»å¡å·: {selectedTask.TaskNum}", commonStackerCrane.DeviceName); |
| | | return selectedTask; |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 3: Commit** |
| | | |
| | | ```bash |
| | | git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs |
| | | git commit -m "feat(StackerCraneTaskSelector): TargetAddressä¸å¯ç¨æ¶æç´¢åNextAddressçå
¶ä»ä»»å¡ |
| | | |
| | | å½ TrySelectOutboundTask å TargetAddress è¾é线ç«å°ä¸ç©ºé²è¿å null æ¶ï¼ |
| | | å
å°è¯å NextAddress çå
¶ä»åºåºä»»å¡ï¼åå°è¯ä¸å NextAddress çä»»å¡ |
| | | |
| | | Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 2: éªè¯æå»º |
| | | |
| | | - [ ] **Step 1: æ§è¡æå»ºéªè¯** |
| | | |
| | | ```bash |
| | | cd D:/Git/ShanMeiXinNengYuan/Code |
| | | dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln |
| | | ``` |
| | | |
| | | é¢æï¼æ ç¼è¯éè¯¯ï¼æµè¯é¡¹ç®çé¢åå¨é误å¯å¿½ç¥ï¼ |
| | | |
| | | - [ ] **Step 2: 妿é误ï¼åæå¹¶ä¿®å¤** |
| | | |
| | | 常è§éè¯¯ï¼ |
| | | - ç±»åä¸å¹é
â 确认 `QueryStackerCraneOutTasks` è¿å `List<Dt_Task>` |
| | | - ç¼ºå° using â æ·»å `using System.Linq;` |
| | | |
| | | --- |
| | | |
| | | ## éªè¯æ¸
å |
| | | |
| | | - [ ] å NextAddress ä»»å¡æç´¢é»è¾å·²æ·»å å¨ `TrySelectOutboundTask` è¿å null ä¹å |
| | | - [ ] ä½¿ç¨ `TaskId` è¿æ»¤æé¤å½åä»»å¡ |
| | | - [ ] å NextAddress æ å¯ç¨ä»»å¡åç»§ç»å°è¯ä¸å NextAddress çä»»å¡ |
| | | - [ ] æ¥å¿è®°å½"åç«å°å¤éåºåºä»»å¡" |
| | | - [ ] æå»ºéè¿ï¼æ ç¼è¯é误 |
| | | - [ ] å·²æäº¤ commit |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # å åæºåºåºä»»å¡ TargetAddress è¾é线ç«å°ç©ºé²æ£æ¥ è®¾è®¡ææ¡£ |
| | | |
| | | ## 1. èæ¯ä¸ç®æ |
| | | |
| | | å¨ `StackerCraneTaskSelector.TrySelectOutboundTask` æ¹æ³ä¸ï¼å½ååªæ£æ¥åºåºç«å°ç `NextAddress` æ¯å¦å¯ç¨ï¼`IsOutTaskStationAvailable`ï¼ãéæ±ï¼å¢å 对 `TargetAddress` è¾é线ç«å°ç空é²ç¶ææ£æ¥ãåªæå½ `TargetAddress` è¾é线ç«å°ç©ºé²ï¼`CV_State == 2`ï¼æ¶ï¼æè®¤ä¸ºè¯¥åºåºä»»å¡çæ£å¯éã |
| | | |
| | | ## 2. è®¾è®¡æ¹æ¡ |
| | | |
| | | ### 2.1 æ°å¢ç§ææ¹æ³ |
| | | |
| | | å¨ `StackerCraneTaskSelector` ç±»ä¸æ°å¢æ¹æ³ `IsTargetAddressConveyorStationAvailable`ï¼ |
| | | |
| | | ```csharp |
| | | /// <summary> |
| | | /// 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | /// </summary> |
| | | /// <param name="task">åºåºä»»å¡</param> |
| | | /// <returns>ç«å°ç©ºé²è¿å trueï¼å¦åè¿å false</returns> |
| | | private bool IsTargetAddressConveyorStationAvailable([NotNull] Dt_Task task) |
| | | { |
| | | // ç¡®å®ä»»å¡ç±»å |
| | | int taskType = task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty |
| | | ? StackerCraneConst.EmptyPalletTaskType |
| | | : task.TaskType; |
| | | |
| | | // éè¿è·¯ç±æ¥æ¾ TargetAddress 对åºç设å¤ä¿¡æ¯ |
| | | Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType); |
| | | if (router == null) |
| | | { |
| | | QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailableï¼æªæ¾å° TargetAddress è·¯ç±ä¿¡æ¯ï¼TargetAddress: {TargetAddress}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼æªæ¾å° TargetAddress è·¯ç±ä¿¡æ¯ï¼TargetAddress: {task.TargetAddress}", task.Roadway, task.TargetAddress, task.TaskNum); |
| | | return false; |
| | | } |
| | | |
| | | // æ¥æ¾è¾éçº¿è®¾å¤ |
| | | IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode); |
| | | if (device == null) |
| | | { |
| | | QuartzLogHelper.LogWarn(_logger, "IsTargetAddressConveyorStationAvailableï¼æªæ¾å°è¾é线设å¤ï¼ChildPosiDeviceCode: {ChildPosiDeviceCode}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼æªæ¾å°è¾é线设å¤ï¼ChildPosiDeviceCode: {router.ChildPosiDeviceCode}", task.Roadway, router.ChildPosiDeviceCode, task.TaskNum); |
| | | return false; |
| | | } |
| | | |
| | | // 转æ¢ä¸ºè¾éçº¿è®¾å¤ |
| | | CommonConveyorLine conveyorLine = (CommonConveyorLine)device; |
| | | |
| | | // 读å CV_Stateï¼CV_State == 2 è¡¨ç¤ºç©ºé² |
| | | byte cvState = conveyorLine.GetValue<ConveyorLineStatus, byte>(ConveyorLineStatus.CV_State, task.TargetAddress); |
| | | bool isAvailable = cvState == 2; |
| | | QuartzLogHelper.LogInfo(_logger, "IsTargetAddressConveyorStationAvailableï¼TargetAddress: {TargetAddress}ï¼CV_State: {CV_State}ï¼æ¯å¦ç©ºé²: {IsAvailable}ï¼ä»»å¡å·: {TaskNum}", |
| | | $"IsTargetAddressConveyorStationAvailableï¼TargetAddress: {task.TargetAddress}ï¼CV_State: {cvState}ï¼æ¯å¦ç©ºé²: {isAvailable}", task.Roadway, task.TargetAddress, cvState, isAvailable, task.TaskNum); |
| | | |
| | | return isAvailable; |
| | | } |
| | | ``` |
| | | |
| | | ### 2.2 è°ç¨ç¹ä¿®æ¹ |
| | | |
| | | å¨ `TrySelectOutboundTask` æ¹æ³ä¸ï¼å¨ `return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null;` ä¹åæå
¥ï¼ |
| | | |
| | | ```csharp |
| | | // 夿 TargetAddress è¾é线ç«å°æ¯å¦ç©ºé² |
| | | if (!IsTargetAddressConveyorStationAvailable(taskAfterTransferCheck)) |
| | | { |
| | | return null; |
| | | } |
| | | ``` |
| | | |
| | | ### 2.3 æµç¨å¾ |
| | | |
| | | ``` |
| | | åºåºä»»å¡è¿å
¥ TrySelectOutboundTask |
| | | â |
| | | ç§»åºæ£æ¥ï¼_transferCheckï¼ |
| | | â |
| | | æ¯å¦ç§»åºä»»å¡ï¼æ¯ â è¿åä»»å¡ |
| | | âï¼å¦ï¼ |
| | | IsOutTaskStationAvailable(NextAddress åºåºç«å°) â ä¸å¯ç¨ â è¿å null |
| | | âï¼å¯ç¨ï¼ |
| | | IsTargetAddressConveyorStationAvailable(TargetAddress è¾é线ç«å°) â ä¸å¯ç¨ â è¿å null |
| | | âï¼å¯ç¨ï¼å³ CV_State == 2ï¼ |
| | | è¿åä»»å¡ |
| | | ``` |
| | | |
| | | ## 3. æ¶åçå½åç©ºé´ |
| | | |
| | | æ°å¢æ¹æ³éè¦ usingï¼ |
| | | - `WIDESEAWCS_QuartzJob.ConveyorLine.Enum.ConveyorLineStatus`ï¼`ConveyorLineStatus.CV_State` æä¸¾ï¼ |
| | | - `WIDESEAWCS_Model.Models.Dt_Router`ï¼è·¯ç±å®ä½ï¼ |
| | | - `WIDESEAWCS_Core.Storage`ï¼è®¾å¤åå¨ï¼ |
| | | |
| | | ## 4. é£é©ä¸çº¦æ |
| | | |
| | | - `CV_State == 2` è¡¨ç¤ºç©ºé²æ¯ä¸å¡çº¦å®ï¼éä¸ PLC ä¾§åè®®ä¿æä¸è´ |
| | | - å¦è®¾å¤æªè¿æ¥æåè®®é
置缺失ï¼`GetValue` 伿åºå¼å¸¸ï¼å½å设计让å¼å¸¸åä¸ä¼ æï¼è°ç¨æ¹éè¿ try-catch å
åº |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # å åæºåºåºä»»å¡ TargetAddress ä¸å¯ç¨æ¶ç»§ç»æç´¢ è®¾è®¡ææ¡£ |
| | | |
| | | ## 1. èæ¯ä¸ç®æ |
| | | |
| | | å¨ `StackerCraneTaskSelector.SelectTask` æ¹æ³ä¸ï¼å½ `TrySelectOutboundTask` å `TargetAddress` è¾é线ç«å°ä¸ç©ºé²ï¼`CV_State != 2`ï¼èè¿å null æ¶ï¼éè¦ç»§ç»æç´¢å
¶ä»å¯éçåºåºä»»å¡ï¼è䏿¯ç´æ¥è¿å null æä»
å°è¯ä¸å NextAddress çä»»å¡ã |
| | | |
| | | ## 2. è®¾è®¡æ¹æ¡ |
| | | |
| | | ### 2.1 æç´¢é¡ºåº |
| | | |
| | | å½ TargetAddress ä¸å¯ç¨æ¶ï¼æä»¥ä¸é¡ºåºæç´¢å¤éä»»å¡ï¼ |
| | | |
| | | 1. **å NextAddress çå
¶ä»ä»»å¡** - æ¥è¯¢ä¸å½åä»»å¡ NextAddress ç¸åçå
¶ä»åºåºä»»å¡ï¼å°è¯å
¶ TargetAddress æ¯å¦å¯ç¨ |
| | | 2. **ä¸å NextAddress çä»»å¡** - æ¥è¯¢å
¶ä»ç«å°ï¼N extAddress ä¸åï¼çåºåºä»»å¡ï¼ç°æé»è¾ï¼ |
| | | |
| | | ### 2.2 代ç ä¿®æ¹ |
| | | |
| | | å¨ `SelectTask` æ¹æ³ä¸ï¼`TrySelectOutboundTask(candidateTask)` è¿å null ä¹åãç°æå¤éä»»å¡å¾ªç¯ä¹åï¼æå
¥å NextAddress ä»»å¡æç´¢é»è¾ï¼ |
| | | |
| | | ```csharp |
| | | // å°è¯éæ©åºåºä»»å¡ |
| | | Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask); |
| | | if (selectedTask != null) |
| | | { |
| | | return selectedTask; |
| | | } |
| | | |
| | | // ===== TargetAddress ä¸å¯ç¨æ¶ï¼å
å°è¯å NextAddress çå
¶ä»ä»»å¡ ===== |
| | | // æ¥è¯¢ä¸å½åä»»å¡ NextAddress ç¸åçå
¶ä»åºåºä»»å¡ |
| | | var sameStationTasks = _taskService |
| | | .QueryStackerCraneOutTasks(deviceCode, new List<string> { candidateTask.NextAddress }) |
| | | .Where(x => x.TaskId != candidateTask.TaskId) |
| | | .ToList(); |
| | | |
| | | foreach (var sameStationTask in sameStationTasks) |
| | | { |
| | | selectedTask = TrySelectOutboundTask(sameStationTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, $"éä¸åç«å°å¤éåºåºä»»å¡ï¼ä»»å¡å·: {selectedTask.TaskNum}", commonStackerCrane.DeviceName); |
| | | return selectedTask; |
| | | } |
| | | } |
| | | |
| | | // ===== å NextAddress æ å¯ç¨ä»»å¡ï¼å°è¯ä¸å NextAddress çä»»å¡ï¼ç°æé»è¾ï¼===== |
| | | var otherOutStationCodes = _routerService |
| | | .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType) |
| | | .Select(x => x.ChildPosi) |
| | | .ToList(); |
| | | |
| | | var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes); |
| | | foreach (var alternativeTask in tasks) |
| | | { |
| | | selectedTask = TrySelectOutboundTask(alternativeTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, $"éä¸å¤éåºåºä»»å¡ï¼ä»»å¡å·: {selectedTask.TaskNum}", commonStackerCrane.DeviceName); |
| | | return selectedTask; |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 2.3 æ 鿰墿¹æ³ |
| | | |
| | | å©ç¨ç°æç `QueryStackerCraneOutTasks(deviceCode, stationCodes)` æ¹æ³ï¼ä¼ å
¥åç«å°å表å³å¯æ¥è¯¢å NextAddress çä»»å¡ãè¿æ»¤æå½åä»»å¡ï¼`TaskId` ä¸åï¼é¿å
éå¤å°è¯ã |
| | | |
| | | ### 2.4 æµç¨å¾ |
| | | |
| | | ``` |
| | | SelectTask |
| | | â |
| | | TrySelectOutboundTask(candidateTask) |
| | | â |
| | | TargetAddress å¯ç¨ï¼å¦ â è¿å null |
| | | â |
| | | æ¥è¯¢å NextAddress çå
¶ä»åºåºä»»å¡ |
| | | â |
| | | éååç«å°ä»»å¡ |
| | | â TrySelectOutboundTask(task) â å¯ç¨ â è¿åä»»å¡ |
| | | â ä¸å¯ç¨ â ç»§ç»ä¸ä¸ä¸ª |
| | | â |
| | | å NextAddress æ å¯ç¨ä»»å¡ |
| | | â |
| | | æ¥è¯¢ä¸å NextAddress çåºåºä»»å¡ï¼ç°æé»è¾ï¼ |
| | | â |
| | | éå â è¿åå¯ç¨ä»»å¡ / æ å¯ç¨ â è¿å null |
| | | ``` |
| | | |
| | | ## 3. æ¶åçå½åç©ºé´ |
| | | |
| | | æ éæ°å¢ usingï¼ç°ææ¹æ³ç¾å已满足ã |
| | | |
| | | ## 4. é£é©ä¸çº¦æ |
| | | |
| | | - `TaskId` ä½ä¸ºå¯ä¸æ è¯ç¨äºè¿æ»¤å·²å°è¯ä»»å¡ï¼å设任å¡è¡¨ä¸æ éå¤ TaskId |
| | | - `QueryStackerCraneOutTasks` è¿åçä»»å¡å表éè¦èèæåºï¼å¦ææä¸å¡è§åå³å®ä¼å
çº§ï¼ |
| | | - 妿å NextAddress æå¤§éä»»å¡ï¼å¯è½å¢å éæ©å»¶è¿ |