From a8f45091019012eeafec563913dee71cda3d9790 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期三, 11 三月 2026 16:36:49 +0800
Subject: [PATCH] refactor: modularize WIDESEAWCS_Tasks workflows
---
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs | 138 +++
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-conveyorline-dispatch-handler-refactor-plan.md | 49 +
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs | 265 -----
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs | 90 ++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/ISocketClientGateway.cs | 20
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotMessageRouter.cs | 13
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotWorkflowOrchestrator.cs | 13
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs | 135 +++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs | 141 --
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs | 108 ++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs | 33
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs | 43
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs | 89 ++
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-formation-stacker-crane-job-refactor-plan.md | 49 +
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotPrefixCommandHandler.cs | 14
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs | 186 +---
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-common-stacker-crane-job-refactor-plan.md | 54 +
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs | 350 +-------
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/SocketClientGateway.cs | 40
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs | 189 ++++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs | 128 ++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs | 108 ++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs | 270 -----
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotSimpleCommandHandler.cs | 10
24 files changed, 1,488 insertions(+), 1,047 deletions(-)
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
index 0ba6a33..6dc2ef3 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
@@ -1,24 +1,23 @@
-#region << 版 本 注 释 >>
+锘�#region << 鐗� 鏈� 娉� 閲� >>
/*----------------------------------------------------------------
- * 命名空间:WIDESEAWCS_Tasks.ConveyorLineJob
- * 创建者:胡童庆
- * 创建时间:2024/8/2 16:13:36
- * 版本:V1.0.0
- * 描述:
+ * 鍛藉悕绌洪棿锛歐IDESEAWCS_Tasks.ConveyorLineJob
+ * 鍒涘缓鑰咃細鑳$搴�
+ * 鍒涘缓鏃堕棿锛�2024/8/2 16:13:36
+ * 鐗堟湰锛歏1.0.0
+ * 鎻忚堪锛�
*
* ----------------------------------------------------------------
- * 修改人:
- * 修改时间:
- * 版本:V1.0.1
- * 修改说明:
+ * 淇敼浜猴細
+ * 淇敼鏃堕棿锛�
+ * 鐗堟湰锛歏1.0.1
+ * 淇敼璇存槑锛�
*
*----------------------------------------------------------------*/
-#endregion << 版 本 注 释 >>
+#endregion << 鐗� 鏈� 娉� 閲� >>
using AutoMapper;
-using System.Data;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
@@ -36,37 +35,36 @@
private readonly IRouterService _routerService;
private readonly IMapper _mapper;
+ private readonly ConveyorLineTaskFilter _taskFilter;
+ private readonly ConveyorLineTargetAddressSelector _targetAddressSelector;
+
public ConveyorLineDispatchHandler(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper)
{
_taskService = taskService;
_taskExecuteDetailService = taskExecuteDetailService;
_routerService = routerService;
_mapper = mapper;
+
+ _taskFilter = new ConveyorLineTaskFilter(taskService);
+ _targetAddressSelector = new ConveyorLineTargetAddressSelector();
}
/// <summary>
- /// 心跳处理
+ /// 蹇冭烦澶勭悊
/// </summary>
- /// <param name="conveyorLine"></param>
- /// <param name="command"></param>
- /// <param name="childDeviceCode"></param>
public void HeartBeat(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
{
- //心跳处理逻辑
conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, 0, childDeviceCode);
}
/// <summary>
- /// 输送线请求入库
+ /// 杈撻�佺嚎璇锋眰鍏ュ簱
/// </summary>
- /// <param name="conveyorLine">输送线实例对象</param>
- /// <param name="command">读取的请求信息</param>
- /// <param name="childDeviceCode">子设备编号</param>
public void RequestInbound(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
{
- if (_taskService.RequestWMSTask(command.Barcode, childDeviceCode).Status)
+ if (_taskFilter.RequestWmsTask(command.Barcode, childDeviceCode))
{
- Dt_Task task = _taskService.QueryConveyorLineTask(conveyorLine.DeviceCode, childDeviceCode);
+ Dt_Task? task = _taskFilter.QueryPendingTask(conveyorLine.DeviceCode, childDeviceCode);
if (task != null)
{
ConveyorLineTaskCommandNew taskCommand = _mapper.Map<ConveyorLineTaskCommandNew>(task);
@@ -79,62 +77,34 @@
}
/// <summary>
- /// 输送线请求入库下一地址
+ /// 杈撻�佺嚎璇锋眰鍏ュ簱涓嬩竴鍦板潃
/// </summary>
- /// <param name="conveyorLine">输送线实例对象</param>
- /// <param name="command">读取的请求信息</param>
- /// <param name="childDeviceCode">子设备编号</param>
public void RequestInNextAddress(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
{
- Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
- if (task != null)
+ Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
+ if (task == null)
{
- const string ConstraintMachineName = "拘束机";
- const string PinMachineName = "插拔钉机";
+ return;
+ }
- var devices = Storage.Devices;
+ _targetAddressSelector.HandleInboundNextAddress(conveyorLine, task.NextAddress, childDeviceCode);
- if (string.Equals(task.NextAddress, ConstraintMachineName, StringComparison.Ordinal))
+ Dt_Task? newTask = _taskService.UpdatePosition(task.TaskNum, task.CurrentAddress);
+ if (newTask != null)
+ {
+ if (_taskService.UpdateTaskStatusToNext(newTask).Status && newTask.TaskState == (int)TaskInStatusEnum.Line_InFinish)
{
- ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
- if (constraint == null) return;
-
- ProcessDeviceRequest(conveyorLine, constraint, childDeviceCode,
- () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestUpper),
- () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestUpper),
- outputReq => constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyUpper, outputReq ? 1 : 0));
- }
- else if (string.Equals(task.NextAddress, PinMachineName, StringComparison.Ordinal))
- {
- PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
- if (pinMachine == null) return;
-
- ProcessDeviceRequest(conveyorLine, pinMachine, childDeviceCode,
- () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestUpper),
- () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestUpper),
- outputReq => pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyUpper, outputReq ? 1 : 0));
- }
-
- Dt_Task? newTask = _taskService.UpdatePosition(task.TaskNum, task.CurrentAddress);
- if (newTask != null)
- {
- if (_taskService.UpdateTaskStatusToNext(newTask).Status && newTask.TaskState == (int)TaskInStatusEnum.Line_InFinish)
- {
- conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_STB, 1, childDeviceCode);
- }
+ conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_STB, 1, childDeviceCode);
}
}
}
/// <summary>
- /// 输送线入库完成
+ /// 杈撻�佺嚎鍏ュ簱瀹屾垚
/// </summary>
- /// <param name="conveyorLine">输送线实例对象</param>
- /// <param name="command">读取的请求信息</param>
- /// <param name="childDeviceCode">子设备编号</param>
public void ConveyorLineInFinish(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
{
- Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
+ Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
if (task != null)
{
conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
@@ -144,20 +114,13 @@
}
/// <summary>
- /// 输送线请求出信息
+ /// 杈撻�佺嚎璇锋眰鍑轰俊鎭�
/// </summary>
- /// <param name="conveyorLine">输送线实例对象</param>
- /// <param name="command">读取的请求信息</param>
- /// <param name="childDeviceCode">子设备编号</param>
public void RequestOutbound(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
{
- Dt_Task task = _taskService.QueryConveyorLineTask(conveyorLine.DeviceCode, childDeviceCode);
+ Dt_Task? task = _taskFilter.QueryPendingTask(conveyorLine.DeviceCode, childDeviceCode);
if (task != null)
{
- //ConveyorLineTaskCommandNew taskCommand = _mapper.Map<ConveyorLineTaskCommandNew>(task);
- //taskCommand.WCS_ACK = command.WCS_ACK;
- //conveyorLine.SendCommand(taskCommand, childDeviceCode);
-
conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, task.TaskNum, childDeviceCode);
conveyorLine.SetValue(ConveyorLineDBNameNew.Barcode, task.PalletCode, childDeviceCode);
conveyorLine.SetValue(ConveyorLineDBNameNew.Target, task.TargetAddress, childDeviceCode);
@@ -168,62 +131,27 @@
}
/// <summary>
- /// 输送线请求出库下一地址
+ /// 杈撻�佺嚎璇锋眰鍑哄簱涓嬩竴鍦板潃
/// </summary>
- /// <param name="conveyorLine">输送线实例对象</param>
- /// <param name="command">读取的请求信息</param>
- /// <param name="childDeviceCode">子设备编号</param>
public void RequestOutNextAddress(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
{
- Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
- if (task != null)
+ Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
+ if (task == null)
{
- const string ConstraintMachineName = "拘束机";
- const string PinMachineName = "插拔钉机";
-
- var devices = Storage.Devices;
-
- if (string.Equals(task.NextAddress, ConstraintMachineName, StringComparison.Ordinal))
- {
- ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
- if (constraint == null)
- {
- // 处理 processing 为空的情况(可根据实际业务需求添加处理逻辑)
- return;
- }
- ProcessDeviceRequest(conveyorLine, constraint, childDeviceCode,
- () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestLower),
- () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestLower),
- outputReq => constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0));
- }
- else if (string.Equals(task.NextAddress, PinMachineName, StringComparison.Ordinal))
- {
- PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
- if (pinMachine == null)
- {
- // 处理 pinMachine 为空的情况(可根据实际业务需求添加处理逻辑)
- return;
- }
- ProcessDeviceRequest(conveyorLine, pinMachine, childDeviceCode,
- () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestLower),
- () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestLower),
- outputReq => pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0));
- }
-
- Dt_Task? newTask = _taskService.UpdatePosition(task.TaskNum, task.CurrentAddress);
-
+ return;
}
+
+ _targetAddressSelector.HandleOutboundNextAddress(conveyorLine, task.NextAddress, childDeviceCode);
+
+ _ = _taskService.UpdatePosition(task.TaskNum, task.CurrentAddress);
}
/// <summary>
- /// 输送线出库完成
+ /// 杈撻�佺嚎鍑哄簱瀹屾垚
/// </summary>
- /// <param name="conveyorLine">输送线实例对象</param>
- /// <param name="command">读取的请求信息</param>
- /// <param name="childDeviceCode">子设备编号</param>
public void ConveyorLineOutFinish(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
{
- Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
+ Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
if (task != null)
{
conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
@@ -231,25 +159,5 @@
Console.Out.WriteLine(content.Serialize());
}
}
-
- /// <summary>
- /// 通用的设备请求处理方法
- /// </summary>
- private void ProcessDeviceRequest<T>(CommonConveyorLine conveyorLine, T device, string childDeviceCode,
- Func<bool> getMaterialRequest, Func<bool> getOutputRequest, Action<bool> setOutputReady)
- {
- bool materialReq = getMaterialRequest();
- bool outputReq = getOutputRequest();
-
- if (materialReq)
- {
- conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode);
- conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
- }
- else
- {
- setOutputReady(outputReq);
- }
- }
}
-}
\ No newline at end of file
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
new file mode 100644
index 0000000..2983836
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
@@ -0,0 +1,108 @@
+锘縰sing WIDESEAWCS_QuartzJob;
+namespace WIDESEAWCS_Tasks
+{
+ /// <summary>
+ /// 杈撻�佺嚎璁惧璇锋眰澶勭悊鍣細澶勭悊鎷樻潫鏈�/鎻掓嫈閽夋満涓婁笅灞傝姹傘��
+ /// </summary>
+ public class ConveyorLineTargetAddressSelector
+ {
+ private const string ConstraintMachineName = "鎷樻潫鏈�";
+ private const string PinMachineName = "鎻掓嫈閽夋満";
+
+ public void HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
+ {
+ HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: true);
+ }
+
+ public void HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
+ {
+ HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: false);
+ }
+
+ private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, bool isUpper)
+ {
+ var devices = Storage.Devices;
+
+ if (string.Equals(nextAddress, ConstraintMachineName, StringComparison.Ordinal))
+ {
+ ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
+ if (constraint == null)
+ {
+ return;
+ }
+
+ ProcessDeviceRequest(
+ conveyorLine,
+ childDeviceCode,
+ getMaterialRequest: () => isUpper
+ ? constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestUpper)
+ : constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestLower),
+ getOutputRequest: () => isUpper
+ ? constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestUpper)
+ : constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestLower),
+ setOutputReady: outputReq =>
+ {
+ if (isUpper)
+ {
+ constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyUpper, outputReq ? 1 : 0);
+ }
+ else
+ {
+ constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0);
+ }
+ });
+ }
+ else if (string.Equals(nextAddress, PinMachineName, StringComparison.Ordinal))
+ {
+ PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
+ if (pinMachine == null)
+ {
+ return;
+ }
+
+ ProcessDeviceRequest(
+ conveyorLine,
+ childDeviceCode,
+ getMaterialRequest: () => isUpper
+ ? pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestUpper)
+ : pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestLower),
+ getOutputRequest: () => isUpper
+ ? pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestUpper)
+ : pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestLower),
+ setOutputReady: outputReq =>
+ {
+ if (isUpper)
+ {
+ pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyUpper, outputReq ? 1 : 0);
+ }
+ else
+ {
+ pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0);
+ }
+ });
+ }
+ }
+
+ private static void ProcessDeviceRequest(
+ CommonConveyorLine conveyorLine,
+ string childDeviceCode,
+ Func<bool> getMaterialRequest,
+ Func<bool> getOutputRequest,
+ Action<bool> setOutputReady)
+ {
+ bool materialReq = getMaterialRequest();
+ bool outputReq = getOutputRequest();
+
+ if (materialReq)
+ {
+ conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode);
+ conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
+ }
+ else
+ {
+ setOutputReady(outputReq);
+ }
+ }
+ }
+}
+
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs
new file mode 100644
index 0000000..96fdf1c
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs
@@ -0,0 +1,33 @@
+锘縰sing WIDESEAWCS_ITaskInfoService;
+using WIDESEAWCS_Model.Models;
+
+namespace WIDESEAWCS_Tasks
+{
+ /// <summary>
+ /// 杈撻�佺嚎浠诲姟璁块棶鍣細缁熶竴灏佽浠诲姟鏌ヨ涓� WMS 璇锋眰銆�
+ /// </summary>
+ public class ConveyorLineTaskFilter
+ {
+ private readonly ITaskService _taskService;
+
+ public ConveyorLineTaskFilter(ITaskService taskService)
+ {
+ _taskService = taskService;
+ }
+
+ public Dt_Task? QueryPendingTask(string deviceCode, string childDeviceCode)
+ {
+ return _taskService.QueryConveyorLineTask(deviceCode, childDeviceCode);
+ }
+
+ public Dt_Task? QueryExecutingTask(int taskNo, string childDeviceCode)
+ {
+ return _taskService.QueryExecutingConveyorLineTask(taskNo, childDeviceCode);
+ }
+
+ public bool RequestWmsTask(string barcode, string childDeviceCode)
+ {
+ return _taskService.RequestWMSTask(barcode, childDeviceCode).Status;
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs
index a006e83..00e4b70 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs
@@ -1,12 +1,11 @@
锘縰sing Quartz;
-using System.Diagnostics.CodeAnalysis;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
-using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
+using WIDESEAWCS_QuartzJob.StackerCrane;
using WIDESEAWCS_QuartzJob.StackerCrane.Enum;
using WIDESEAWCS_Tasks.StackerCraneJob;
@@ -18,14 +17,22 @@
private readonly ITaskService _taskService;
private readonly ITaskExecuteDetailService _taskExecuteDetailService;
private readonly ITaskRepository _taskRepository;
- private readonly IRouterService _routerService;
- public FormationCommonStackerCraneJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, ITaskRepository taskRepository, IRouterService routerService)
+ private readonly FormationStackerCraneTaskSelector _taskSelector;
+ private readonly FormationStackerCraneCommandBuilder _commandBuilder;
+
+ public FormationCommonStackerCraneJob(
+ ITaskService taskService,
+ ITaskExecuteDetailService taskExecuteDetailService,
+ ITaskRepository taskRepository,
+ IRouterService routerService)
{
_taskService = taskService;
_taskExecuteDetailService = taskExecuteDetailService;
_taskRepository = taskRepository;
- _routerService = routerService;
+
+ _taskSelector = new FormationStackerCraneTaskSelector(taskService, routerService);
+ _commandBuilder = new FormationStackerCraneCommandBuilder(taskService, routerService);
}
public Task Execute(IJobExecutionContext context)
@@ -52,37 +59,42 @@
commonStackerCrane.CheckStackerCraneTaskCompleted();
- if (commonStackerCrane.StackerCraneWorkStatusValue == FormationStackerCraneOperationStatus.Idle)
+ if (commonStackerCrane.StackerCraneWorkStatusValue != FormationStackerCraneOperationStatus.Idle)
{
- Dt_Task? task = GetTask(commonStackerCrane);
- if (task != null)
- {
- FormationStackerCraneTaskCommand? stackerCraneTaskCommand = ConvertToStackerCraneTaskCommand(task);
- if (stackerCraneTaskCommand != null)
- {
- bool sendFlag = commonStackerCrane.SendCommand(stackerCraneTaskCommand);
- if (sendFlag)
- {
- commonStackerCrane.LastTaskType = task.TaskType;
- _taskService.UpdateTaskStatusToNext(task.TaskNum);
- }
- }
- }
+ return Task.CompletedTask;
+ }
+
+ Dt_Task? task = _taskSelector.SelectTask(commonStackerCrane);
+ if (task == null)
+ {
+ return Task.CompletedTask;
+ }
+
+ var stackerCraneTaskCommand = _commandBuilder.ConvertToStackerCraneTaskCommand(task);
+ if (stackerCraneTaskCommand == null)
+ {
+ return Task.CompletedTask;
+ }
+
+ bool sendFlag = commonStackerCrane.SendCommand(stackerCraneTaskCommand);
+ if (sendFlag)
+ {
+ commonStackerCrane.LastTaskType = task.TaskType;
+ _taskService.UpdateTaskStatusToNext(task.TaskNum);
}
}
catch (Exception ex)
{
Console.WriteLine($"FormationCommonStackerCraneJob Error: {ex.Message}");
}
+
return Task.CompletedTask;
}
/// <summary>
/// 浠诲姟瀹屾垚浜嬩欢璁㈤槄鐨勬柟娉�
/// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, WIDESEAWCS_QuartzJob.StackerCrane.StackerCraneTaskCompletedEventArgs e)
+ private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, StackerCraneTaskCompletedEventArgs e)
{
SpeFormationStackerCrane? commonStackerCrane = sender as SpeFormationStackerCrane;
if (commonStackerCrane != null)
@@ -95,214 +107,6 @@
}
}
}
-
- /// <summary>
- /// 鑾峰彇浠诲姟
- /// </summary>
- /// <param name="commonStackerCrane">鍫嗗灈鏈哄璞�</param>
- /// <returns></returns>
- private Dt_Task? GetTask(SpeFormationStackerCrane commonStackerCrane)
- {
- Dt_Task? task = null;
-
- if (commonStackerCrane.LastTaskType == null)
- {
- task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
- }
- else
- {
- if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
- {
- task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
- task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
- }
- else
- {
- task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
- }
- }
-
- if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
- {
- if (IsOutTaskStationAvailable(task))
- {
- return task;
- }
-
- List<string> otherOutStationCodes = _routerService.QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
- .Select(x => x.ChildPosi).ToList();
-
- List<Dt_Task> tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
-
- foreach (var alternativeTask in tasks)
- {
- if (IsOutTaskStationAvailable(alternativeTask))
- {
- return alternativeTask;
- }
- }
-
- task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
- }
-
- return task;
- }
-
- /// <summary>
- /// 妫�鏌ュ嚭搴撲换鍔$殑鍑哄簱绔欏彴鏄惁鍙敤
- /// </summary>
- /// <param name="task">浠诲姟瀹炰綋</param>
- /// <returns>濡傛灉绔欏彴鍙敤杩斿洖true锛屽惁鍒欒繑鍥瀎alse</returns>
- private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
- {
- Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
- if (router == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.NextAddress}銆戜俊鎭紝鏃犳硶鏍¢獙绔欏彴");
- return false;
- }
-
- IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
- if (device == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒板嚭搴撶珯鍙般�恵router.ChildPosiDeviceCode}銆戝搴旂殑閫氳瀵硅薄锛屾棤娉曞垽鏂嚭搴撶珯鍙版槸鍚﹁鍗犵敤");
- return false;
- }
-
- CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
- return conveyorLine.IsOccupied(router.ChildPosi);
- }
-
- /// <summary>
- /// 浠诲姟瀹炰綋杞崲鎴愬懡浠odel
- /// </summary>
- /// <param name="task">浠诲姟瀹炰綋</param>
- /// <returns></returns>
- public FormationStackerCraneTaskCommand? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
- {
- FormationStackerCraneTaskCommand stackerCraneTaskCommand = new FormationStackerCraneTaskCommand
- {
- Barcode = task.PalletCode,
- TaskNum = task.TaskNum,
- WorkType = 4,
- WorkAction = 1,
- FireAlarm = 0,
- FieldName = ""
- };
-
- TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
-
- return taskTypeGroup switch
- {
- TaskTypeGroup.InboundGroup => BuildInboundCommand(task, stackerCraneTaskCommand),
- TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, stackerCraneTaskCommand),
- TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, stackerCraneTaskCommand),
- _ => stackerCraneTaskCommand
- };
- }
-
- /// <summary>
- /// 鏋勫缓鍏ュ簱鍛戒护
- /// </summary>
- private FormationStackerCraneTaskCommand? BuildInboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
- {
- Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
- if (router == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.CurrentAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鍙栬揣绔欏彴淇℃伅");
- return null;
- }
-
- command.StartRow = Convert.ToInt16(router.SrmRow);
- command.StartColumn = Convert.ToInt16(router.SrmColumn);
- command.StartLayer = Convert.ToInt16(router.SrmLayer);
-
- if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍏ュ簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
- return null;
- }
-
- command.EndRow = endRow;
- command.EndColumn = endColumn;
- command.EndLayer = endLayer;
-
- return command;
- }
-
- /// <summary>
- /// 鏋勫缓鍑哄簱鍛戒护
- /// </summary>
- private FormationStackerCraneTaskCommand? BuildOutboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
- {
- Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
- if (router == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.TargetAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鏀捐揣绔欏彴淇℃伅");
- return null;
- }
-
- command.EndRow = Convert.ToInt16(router.SrmRow);
- command.EndColumn = Convert.ToInt16(router.SrmColumn);
- command.EndLayer = Convert.ToInt16(router.SrmLayer);
-
- if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍑哄簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
- return null;
- }
-
- command.StartRow = startRow;
- command.StartColumn = startColumn;
- command.StartLayer = startLayer;
-
- return command;
- }
-
- /// <summary>
- /// 鏋勫缓绉诲簱鍛戒护
- /// </summary>
- private FormationStackerCraneTaskCommand? BuildRelocationCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
- {
- if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
- return null;
- }
-
- command.EndRow = endRow;
- command.EndColumn = endColumn;
- command.EndLayer = endLayer;
-
- if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
- return null;
- }
-
- command.StartRow = startRow;
- command.StartColumn = startColumn;
- command.StartLayer = startLayer;
-
- return command;
- }
-
- /// <summary>
- /// 瑙f瀽鍦板潃瀛楃涓诧紙鏍煎紡锛氳-鍒�-灞傦級
- /// </summary>
- private bool TryParseAddress(string address, out short row, out short column, out short layer)
- {
- row = column = layer = 0;
-
- string[] parts = address.Split("-");
- if (parts.Length != 3)
- {
- return false;
- }
-
- return short.TryParse(parts[0], out row)
- && short.TryParse(parts[1], out column)
- && short.TryParse(parts[2], out layer);
- }
}
-}
\ No newline at end of file
+}
+
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs
new file mode 100644
index 0000000..0fcc293
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs
@@ -0,0 +1,135 @@
+锘縰sing WIDESEAWCS_Common.TaskEnum;
+using WIDESEAWCS_ITaskInfoService;
+using WIDESEAWCS_Model.Models;
+using WIDESEAWCS_QuartzJob.Models;
+using WIDESEAWCS_QuartzJob.Service;
+using WIDESEAWCS_Tasks.StackerCraneJob;
+
+namespace WIDESEAWCS_Tasks
+{
+ /// <summary>
+ /// 鍒嗗鍫嗗灈鏈哄懡浠ゆ瀯寤哄櫒锛氳礋璐d换鍔″埌鍛戒护瀵硅薄鐨勮浆鎹€��
+ /// </summary>
+ public class FormationStackerCraneCommandBuilder
+ {
+ private readonly ITaskService _taskService;
+ private readonly IRouterService _routerService;
+
+ public FormationStackerCraneCommandBuilder(ITaskService taskService, IRouterService routerService)
+ {
+ _taskService = taskService;
+ _routerService = routerService;
+ }
+
+ public FormationStackerCraneTaskCommand? ConvertToStackerCraneTaskCommand(Dt_Task task)
+ {
+ FormationStackerCraneTaskCommand command = new()
+ {
+ Barcode = task.PalletCode,
+ TaskNum = task.TaskNum,
+ WorkType = 4,
+ WorkAction = 1,
+ FireAlarm = 0,
+ FieldName = string.Empty
+ };
+
+ TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
+
+ return taskTypeGroup switch
+ {
+ TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command),
+ TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command),
+ TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command),
+ _ => command
+ };
+ }
+
+ private FormationStackerCraneTaskCommand? BuildInboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
+ {
+ Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
+ if (router == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.CurrentAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鍙栬揣绔欏彴淇℃伅");
+ return null;
+ }
+
+ command.StartRow = Convert.ToInt16(router.SrmRow);
+ command.StartColumn = Convert.ToInt16(router.SrmColumn);
+ command.StartLayer = Convert.ToInt16(router.SrmLayer);
+
+ if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍏ュ簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
+ return null;
+ }
+
+ command.EndRow = endRow;
+ command.EndColumn = endColumn;
+ command.EndLayer = endLayer;
+ return command;
+ }
+
+ private FormationStackerCraneTaskCommand? BuildOutboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
+ {
+ Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
+ if (router == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.TargetAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鏀捐揣绔欏彴淇℃伅");
+ return null;
+ }
+
+ command.EndRow = Convert.ToInt16(router.SrmRow);
+ command.EndColumn = Convert.ToInt16(router.SrmColumn);
+ command.EndLayer = Convert.ToInt16(router.SrmLayer);
+
+ if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍑哄簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
+ return null;
+ }
+
+ command.StartRow = startRow;
+ command.StartColumn = startColumn;
+ command.StartLayer = startLayer;
+ return command;
+ }
+
+ private FormationStackerCraneTaskCommand? BuildRelocationCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
+ {
+ if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
+ return null;
+ }
+
+ command.EndRow = endRow;
+ command.EndColumn = endColumn;
+ command.EndLayer = endLayer;
+
+ if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
+ return null;
+ }
+
+ command.StartRow = startRow;
+ command.StartColumn = startColumn;
+ command.StartLayer = startLayer;
+ return command;
+ }
+
+ private static bool TryParseAddress(string address, out short row, out short column, out short layer)
+ {
+ row = column = layer = 0;
+ string[] parts = address.Split("-");
+ if (parts.Length != 3)
+ {
+ return false;
+ }
+
+ return short.TryParse(parts[0], out row)
+ && short.TryParse(parts[1], out column)
+ && short.TryParse(parts[2], out layer);
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs
new file mode 100644
index 0000000..519d588
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs
@@ -0,0 +1,89 @@
+锘縰sing System.Diagnostics.CodeAnalysis;
+using WIDESEAWCS_Common.TaskEnum;
+using WIDESEAWCS_ITaskInfoService;
+using WIDESEAWCS_Model.Models;
+using WIDESEAWCS_QuartzJob;
+using WIDESEAWCS_QuartzJob.Models;
+using WIDESEAWCS_QuartzJob.Service;
+
+namespace WIDESEAWCS_Tasks
+{
+ /// <summary>
+ /// 鍒嗗鍫嗗灈鏈轰换鍔¢�夋嫨鍣細灏佽浠诲姟鎸戦�変笌绔欏彴鍙敤鎬у垽鏂��
+ /// </summary>
+ public class FormationStackerCraneTaskSelector
+ {
+ private readonly ITaskService _taskService;
+ private readonly IRouterService _routerService;
+
+ public FormationStackerCraneTaskSelector(ITaskService taskService, IRouterService routerService)
+ {
+ _taskService = taskService;
+ _routerService = routerService;
+ }
+
+ public Dt_Task? SelectTask(SpeFormationStackerCrane commonStackerCrane)
+ {
+ Dt_Task? task;
+ if (commonStackerCrane.LastTaskType == null)
+ {
+ task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
+ }
+ else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
+ {
+ task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
+ task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
+ }
+ else
+ {
+ task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
+ }
+
+ if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
+ {
+ if (IsOutTaskStationAvailable(task))
+ {
+ return task;
+ }
+
+ var otherOutStationCodes = _routerService
+ .QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
+ .Select(x => x.ChildPosi)
+ .ToList();
+
+ var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
+ foreach (var alternativeTask in tasks)
+ {
+ if (IsOutTaskStationAvailable(alternativeTask))
+ {
+ return alternativeTask;
+ }
+ }
+
+ task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
+ }
+
+ return task;
+ }
+
+ private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
+ {
+ Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
+ if (router == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.NextAddress}銆戜俊鎭紝鏃犳硶鏍¢獙绔欏彴");
+ return false;
+ }
+
+ IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
+ if (device == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒板嚭搴撶珯鍙般�恵router.ChildPosiDeviceCode}銆戝搴旂殑閫氳瀵硅薄锛屾棤娉曞垽鏂嚭搴撶珯鍙版槸鍚﹁鍗犵敤");
+ return false;
+ }
+
+ CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
+ return conveyorLine.IsOccupied(router.ChildPosi);
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotMessageRouter.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotMessageRouter.cs
new file mode 100644
index 0000000..d7fd293
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotMessageRouter.cs
@@ -0,0 +1,13 @@
+using System.Net.Sockets;
+
+namespace WIDESEAWCS_Tasks.Workflow.Abstractions
+{
+ /// <summary>
+ /// 机器人消息路由入口。用于承接 TcpSocketServer 的消息事件。
+ /// </summary>
+ public interface IRobotMessageRouter
+ {
+ Task<string?> HandleMessageReceivedAsync(string message, bool isJson, TcpClient client, RobotSocketState state);
+ }
+}
+
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotPrefixCommandHandler.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotPrefixCommandHandler.cs
new file mode 100644
index 0000000..6b28478
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotPrefixCommandHandler.cs
@@ -0,0 +1,14 @@
+锘縰sing System.Net.Sockets;
+
+namespace WIDESEAWCS_Tasks.Workflow.Abstractions
+{
+ /// <summary>
+ /// 鏈哄櫒浜哄墠缂�鍛戒护澶勭悊鍣紙pickfinished / putfinished锛夈��
+ /// </summary>
+ public interface IRobotPrefixCommandHandler
+ {
+ bool IsPrefixCommand(string message);
+
+ Task HandleAsync(string message, RobotSocketState state, TcpClient client);
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotSimpleCommandHandler.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotSimpleCommandHandler.cs
new file mode 100644
index 0000000..89acb76
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotSimpleCommandHandler.cs
@@ -0,0 +1,10 @@
+锘縩amespace WIDESEAWCS_Tasks.Workflow.Abstractions
+{
+ /// <summary>
+ /// 鏈哄櫒浜虹畝鍗曞懡浠ゅ鐞嗗櫒锛堝杩愯鐘舵�併�佹ā寮忓垏鎹€�佸叏娴佺▼瀹屾垚鍛戒护锛夈��
+ /// </summary>
+ public interface IRobotSimpleCommandHandler
+ {
+ Task<bool> HandleAsync(string message, RobotSocketState state);
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotWorkflowOrchestrator.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotWorkflowOrchestrator.cs
new file mode 100644
index 0000000..de688d6
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotWorkflowOrchestrator.cs
@@ -0,0 +1,13 @@
+using WIDESEAWCS_Model.Models;
+
+namespace WIDESEAWCS_Tasks.Workflow.Abstractions
+{
+ /// <summary>
+ /// 机器人流程编排器。负责 RobotJob 内的状态机分支执行。
+ /// </summary>
+ public interface IRobotWorkflowOrchestrator
+ {
+ Task ExecuteAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress);
+ }
+}
+
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/ISocketClientGateway.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/ISocketClientGateway.cs
new file mode 100644
index 0000000..68e4edc
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/ISocketClientGateway.cs
@@ -0,0 +1,20 @@
+using System.Net.Sockets;
+using WIDESEAWCS_QuartzJob;
+
+namespace WIDESEAWCS_Tasks.Workflow.Abstractions
+{
+ /// <summary>
+ /// Socket 客户端网关。用于隔离 Robot 业务对 TcpSocketServer 的直接依赖。
+ /// </summary>
+ public interface ISocketClientGateway
+ {
+ Task<bool> SendToClientAsync(string clientId, string message);
+
+ Task SendMessageAsync(TcpClient client, string message);
+
+ IReadOnlyList<string> GetClientIds();
+
+ Task HandleClientAsync(TcpClient client, string clientId, CancellationToken cancellationToken, RobotSocketState robotCrane);
+ }
+}
+
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
index 028248c..f6b1ac5 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -1,17 +1,17 @@
-using Quartz;
+锘縰sing Quartz;
using WIDESEA_Core;
-using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_ITaskInfoService;
-using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
+using WIDESEAWCS_Tasks.Workflow.Abstractions;
+using WIDESEAWCS_Tasks.Workflow;
using WIDESEAWCS_Tasks.SocketServer;
namespace WIDESEAWCS_Tasks
{
/// <summary>
- /// 鏈烘鎵嬩换鍔′綔涓� - 璐熻矗鍗忚皟鏈烘鎵嬪鎴风杩炴帴銆佹秷鎭鐞嗗拰浠诲姟鎵ц
+ /// 鏈哄櫒浜轰换鍔′綔涓氾細璐熻矗璋冨害涓庣敓鍛藉懆鏈熺鐞嗭紝鍏蜂綋鐘舵�佹満娴佺▼浜ょ粰缂栨帓鍣ㄣ��
/// </summary>
[DisallowConcurrentExecution]
public class RobotJob : IJob
@@ -22,9 +22,9 @@
private readonly RobotClientManager _clientManager;
private readonly RobotStateManager _stateManager;
- private readonly RobotMessageHandler _messageHandler;
+ private readonly IRobotMessageRouter _messageRouter;
private readonly RobotTaskProcessor _taskProcessor;
- private readonly IRobotTaskService _robotTaskService;
+ private readonly IRobotWorkflowOrchestrator _workflowOrchestrator;
public RobotJob(
TcpSocketServer tcpSocket,
@@ -33,31 +33,32 @@
ICacheService cache,
HttpClientHelper httpClientHelper)
{
- _robotTaskService = robotTaskService;
-
- // 鍒濆鍖栫鐞嗗櫒
_stateManager = new RobotStateManager(cache);
- _taskProcessor = new RobotTaskProcessor(tcpSocket, _stateManager, robotTaskService, taskService, httpClientHelper);
- _clientManager = new RobotClientManager(tcpSocket, _stateManager);
- _messageHandler = new RobotMessageHandler(tcpSocket, _stateManager, cache, robotTaskService, _taskProcessor);
- // 璁㈤槄瀹㈡埛绔鐞嗗櫒鐨勪簨浠�
+ // 鏀跺彛 Socket 璁块棶锛屽悗缁嫢鏇挎崲閫氫俊瀹炵幇鍙渶鏇挎崲缃戝叧灞傘��
+ ISocketClientGateway socketGateway = new SocketClientGateway(tcpSocket);
+
+ _taskProcessor = new RobotTaskProcessor(socketGateway, _stateManager, robotTaskService, taskService, httpClientHelper);
+ _clientManager = new RobotClientManager(tcpSocket, _stateManager);
+
+ var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor);
+ var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway);
+ _messageRouter = new RobotMessageHandler(socketGateway, _stateManager, cache, simpleCommandHandler, prefixCommandHandler);
+
+ _workflowOrchestrator = new RobotWorkflowOrchestrator(_stateManager, _clientManager, _taskProcessor, robotTaskService);
+
_clientManager.OnClientDisconnected += OnClientDisconnected;
- // 璁㈤槄TCP鏈嶅姟鍣ㄧ殑娑堟伅浜嬩欢锛堝叏灞�鍙闃呬竴娆★級
+ // 鍏ㄥ眬鍙闃呬竴娆℃秷鎭簨浠讹紝淇濇寔鍘熸湁琛屼负銆�
if (System.Threading.Interlocked.CompareExchange(ref _messageSubscribedFlag, 1, 0) == 0)
{
- tcpSocket.MessageReceived += _messageHandler.HandleMessageReceivedAsync;
- Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 鏈哄櫒浜篢CP娑堟伅浜嬩欢宸茶闃�");
+ tcpSocket.MessageReceived += _messageRouter.HandleMessageReceivedAsync;
+ Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 鏈哄櫒鎵婽CP娑堟伅浜嬩欢宸茶闃�");
}
}
- /// <summary>
- /// 瀹㈡埛绔柇寮�杩炴帴鏃剁殑澶勭悊
- /// </summary>
private void OnClientDisconnected(object? sender, RobotSocketState state)
{
- // 鍙互鍦ㄨ繖閲屾坊鍔犳柇寮�杩炴帴鍚庣殑澶勭悊閫昏緫
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 瀹㈡埛绔凡鏂紑杩炴帴: {state.IPAddress}");
}
@@ -72,118 +73,34 @@
string ipAddress = robotCrane.IPAddress;
- // 鑾峰彇鎴栧垱寤虹姸鎬�
RobotSocketState state = _stateManager.GetOrCreateState(ipAddress, robotCrane);
state.RobotCrane = robotCrane;
try
{
- // 纭繚瀹㈡埛绔凡杩炴帴骞惰闃呮秷鎭簨浠�
if (!_clientManager.EnsureClientSubscribed(ipAddress, robotCrane))
{
- return; // 瀹㈡埛绔湭杩炴帴鎴栬闃呭け璐ワ紝璺宠繃鏈鎵ц
+ return;
}
- // 鑾峰彇浠诲姟骞跺鐞�
- Dt_RobotTask? task = _taskProcessor.GetTask(robotCrane);
+ var task = _taskProcessor.GetTask(robotCrane);
if (task != null)
{
- // 姣忔鍒ゆ柇鍓嶉噸鏂颁粠缂撳瓨鑾峰彇鏈�鏂扮姸鎬�
var latestState = _stateManager.GetState(ipAddress);
- if (latestState == null) return;
+ if (latestState == null)
+ {
+ return;
+ }
if (latestState.RobotTaskTotalNum < MaxTaskTotalNum)
{
- await ProcessTaskAsync(latestState, task, ipAddress);
+ await _workflowOrchestrator.ExecuteAsync(latestState, task, ipAddress);
}
}
}
catch (Exception)
{
- // 寮傚父澶勭悊宸插湪鍚勭粍浠朵腑澶勭悊
- }
- }
-
- /// <summary>
- /// 澶勭悊鏈烘鎵嬩换鍔�
- /// </summary>
- private async Task ProcessTaskAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
- {
- // 澶勭悊姝e湪鎵ц鐨勪换鍔�
- if (latestState.RobotRunMode == 2 && latestState.RobotControlMode == 1 && latestState.OperStatus != "Running")
- {
- // 鍙栬揣瀹屾垚鐘舵�佸鐞�
- if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished") && latestState.RobotArmObject == 1 &&
- task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
- {
- await HandlePickFinishedStateAsync(latestState, task, ipAddress);
- }
- // 鏀捐揣瀹屾垚鐘舵�佸鐞�
- else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished") && latestState.OperStatus == "Homed" &&
- latestState.RobotArmObject == 0 &&
- (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode() ||
- task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
- {
- await HandlePutFinishedStateAsync(latestState, task, ipAddress);
- }
- }
- }
-
- /// <summary>
- /// 澶勭悊鍙栬揣瀹屾垚鐘舵��
- /// </summary>
- private async Task HandlePickFinishedStateAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
- {
- string taskString = $"Putbattery,{task.RobotTargetAddress}";
- bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
- if (result)
- {
- task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
-
- // 閲嶆柊鑾峰彇鏈�鏂扮姸鎬佸苟鏇存柊
- var stateToUpdate = _stateManager.GetState(ipAddress);
- if (stateToUpdate != null)
- {
- stateToUpdate.CurrentTask = task;
- if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
- await _robotTaskService.UpdateRobotTaskAsync(task);
- }
- }
- }
-
- /// <summary>
- /// 澶勭悊鏀捐揣瀹屾垚鐘舵��
- /// </summary>
- private async Task HandlePutFinishedStateAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
- {
- // 閲嶆柊鑾峰彇鏈�鏂扮姸鎬�
- var stateForUpdate = _stateManager.GetState(ipAddress);
- if (stateForUpdate == null) return;
-
- if (!stateForUpdate.IsSplitPallet && !stateForUpdate.IsGroupPallet)
- {
- stateForUpdate.IsSplitPallet = task.RobotTaskType == RobotTaskTypeEnum.SplitPallet.GetHashCode();
- stateForUpdate.IsGroupPallet = task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode() ||
- task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
- }
-
- if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
- {
- string prefix = "TRAY";
-
- // 鐢熸垚涓や釜鎵樼洏鏉$爜
- string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
- string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
- if (!trayBarcode1.IsNullOrEmpty() && !trayBarcode2.IsNullOrEmpty())
- {
- stateForUpdate.CellBarcode.Add(trayBarcode1);
- stateForUpdate.CellBarcode.Add(trayBarcode2);
- await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
- }
- }
- else // 浠诲姟寮�濮嬫墽琛岀洿鎺ュ彂閫佸彇璐у湴鍧�
- {
- await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
+ // 寮傚父澶勭悊宸插湪缁勪欢鍐呴儴杩涜锛孞ob 灞備繚鎸佸厹搴曞悶鍚愯涔夈��
}
}
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs
index 36b521f..a46f641 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs
@@ -1,275 +1,62 @@
-using System.Net.Sockets;
+锘縰sing System.Net.Sockets;
using WIDESEAWCS_Common;
-using WIDESEAWCS_Common.HttpEnum;
-using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.Caches;
-using WIDESEAWCS_Core.Helper;
-using WIDESEAWCS_DTO.Stock;
-using WIDESEAWCS_DTO.TaskInfo;
-using WIDESEAWCS_ITaskInfoService;
-using WIDESEAWCS_Model.Models;
-using WIDESEAWCS_Tasks.SocketServer;
+using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks
{
/// <summary>
- /// 鏈烘鎵嬫秷鎭鐞嗗櫒 - 璐熻矗澶勭悊鏉ヨ嚜TCP瀹㈡埛绔殑娑堟伅
+ /// 鏈哄櫒浜烘秷鎭矾鐢卞叆鍙o細璐熻矗缂撳瓨鐘舵�佽鍙栥�佸懡浠ゅ垎鍙戝拰鍥炲寘瑙﹀彂銆�
/// </summary>
- public class RobotMessageHandler
+ public class RobotMessageHandler : IRobotMessageRouter
{
- private readonly TcpSocketServer _tcpSocket;
+ private readonly ISocketClientGateway _socketClientGateway;
private readonly RobotStateManager _stateManager;
private readonly ICacheService _cache;
- private readonly IRobotTaskService _robotTaskService;
- private readonly RobotTaskProcessor _taskProcessor;
+ private readonly IRobotSimpleCommandHandler _simpleCommandHandler;
+ private readonly IRobotPrefixCommandHandler _prefixCommandHandler;
public RobotMessageHandler(
- TcpSocketServer tcpSocket,
+ ISocketClientGateway socketClientGateway,
RobotStateManager stateManager,
ICacheService cache,
- IRobotTaskService robotTaskService,
- RobotTaskProcessor taskProcessor)
+ IRobotSimpleCommandHandler simpleCommandHandler,
+ IRobotPrefixCommandHandler prefixCommandHandler)
{
- _tcpSocket = tcpSocket;
+ _socketClientGateway = socketClientGateway;
_stateManager = stateManager;
_cache = cache;
- _robotTaskService = robotTaskService;
- _taskProcessor = taskProcessor;
+ _simpleCommandHandler = simpleCommandHandler;
+ _prefixCommandHandler = prefixCommandHandler;
}
/// <summary>
- /// 澶勭悊鎺ユ敹鍒扮殑娑堟伅
+ /// 澶勭悊鎺ユ敹鍒扮殑娑堟伅銆備繚鎸佸師鏈夎涓猴細绠�鍗曞懡浠ゅ拰鍓嶇紑鍛戒护閮藉洖鍐欏師娑堟伅銆�
/// </summary>
public async Task<string?> HandleMessageReceivedAsync(string message, bool isJson, TcpClient client, RobotSocketState state)
{
- if (!(_cache?.TryGetValue($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}", out state)) ?? false)
+ var cacheKey = $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}";
+ if (!_cache.TryGetValue(cacheKey, out RobotSocketState? cachedState) || cachedState == null)
+ {
return null;
+ }
+ var activeState = cachedState;
string messageLower = message.ToLowerInvariant();
- if (await IsSimpleCommandAsync(messageLower, state))
+ if (await _simpleCommandHandler.HandleAsync(messageLower, activeState))
{
- await _tcpSocket.SendMessageAsync(client, message);
- if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
- return null;
+ await _socketClientGateway.SendMessageAsync(client, message);
+ _stateManager.TryUpdateStateSafely(activeState.IPAddress, activeState);
+ return null;
}
- else if (IsPrefixCommand(messageLower))
+
+ if (_prefixCommandHandler.IsPrefixCommand(messageLower))
{
- await HandlePrefixCommandAsync(message, state, client);
+ await _prefixCommandHandler.HandleAsync(message, activeState, client);
}
return null;
- }
-
- /// <summary>
- /// 澶勭悊鍓嶇紑鍛戒护锛坧ickfinished, putfinished锛�
- /// </summary>
- private async Task HandlePrefixCommandAsync(string message, RobotSocketState state, TcpClient client)
- {
- try
- {
- var parts = message.Split(',');
- if (parts.Length >= 1 && state.CurrentTask != null)
- {
- var cmd = parts[0].ToLowerInvariant();
- int[] positions = parts.Skip(1)
- .Select(p => int.TryParse(p, out int value) ? value : (int?)null)
- .Where(v => v.HasValue && v.Value != 0)
- .Select(v => v!.Value)
- .ToArray();
-
- var task = await _robotTaskService.Repository.QueryFirstAsync(x => x.RobotTaskId == state.CurrentTask.RobotTaskId);
-
- if (cmd.StartsWith("pickfinished"))
- {
- await HandlePickFinishedAsync(state, positions, task, client);
- }
- else if (cmd.StartsWith("putfinished"))
- {
- await HandlePutFinishedAsync(state, positions, task, client);
- }
-
- await _tcpSocket.SendMessageAsync(client, message);
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"RobotJob MessageReceived Error: {ex.Message}");
- }
- }
-
- /// <summary>
- /// 澶勭悊鍙栬揣瀹屾垚鍛戒护
- /// </summary>
- private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task, TcpClient client)
- {
- if (state.IsSplitPallet)
- {
- var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
- state.LastPickPositions = positions;
-
- var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
-
- if (result.Data.Status && result.IsSuccess)
- {
- state.CurrentAction = "PickFinished";
- }
- }
- else
- {
- state.CurrentAction = "PickFinished";
- }
-
- state.LastPickPositions = positions;
- if (task != null)
- {
- task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode();
- if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
- await _robotTaskService.Repository.UpdateDataAsync(task);
- }
- }
-
- /// <summary>
- /// 澶勭悊鏀捐揣瀹屾垚鍛戒护
- /// </summary>
- private async Task HandlePutFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task, TcpClient client)
- {
- bool putSuccess = true;
- if (state.IsGroupPallet)
- {
- state.LastPutPositions = positions;
- var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
- var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
- ? nameof(ConfigKey.ChangePalletAsync) : nameof(ConfigKey.GroupPalletAsync);
-
- var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO);
- putSuccess = result.Data.Status && result.IsSuccess;
- }
-
- if (putSuccess)
- {
- state.CurrentAction = "PutFinished";
- state.RobotTaskTotalNum += positions.Length;
- if (task != null)
- {
- task.RobotTaskTotalNum += positions.Length;
- }
- }
-
- if (task != null)
- {
- task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode();
- if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
- await _robotTaskService.Repository.UpdateDataAsync(task);
- }
- }
-
- /// <summary>
- /// 鏈烘鎵嬬畝鍗曞懡浠ゅ鐞�
- /// </summary>
- private async Task<bool> IsSimpleCommandAsync(string message, RobotSocketState state)
- {
- RobotTaskTypeEnum? GetRobotTaskType() => state.CurrentTask != null ? (RobotTaskTypeEnum)state.CurrentTask.RobotTaskType : null;
- switch (message)
- {
- case "homing":
- state.OperStatus = "Homing";
- return true;
-
- case "homed":
- state.OperStatus = "Homed";
- return true;
-
- case "picking":
- state.CurrentAction = "Picking";
- return true;
-
- case "puting":
- state.CurrentAction = "Putting";
- return true;
-
- case "allpickfinished": // 鍙栬揣瀹屾垚
- state.CurrentAction = "AllPickFinished";
- var robotTaskType = GetRobotTaskType();
-
- if (robotTaskType == RobotTaskTypeEnum.SplitPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
- {
- if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
- {
- _taskProcessor.DeleteTask(state.CurrentTask.RobotTaskId);
- return true;
- }
- }
- return false;
-
- case "allputfinished": // 鏀捐揣瀹屾垚
- state.CurrentAction = "AllPutFinished";
- robotTaskType = GetRobotTaskType();
-
- if (robotTaskType == RobotTaskTypeEnum.GroupPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
- {
- if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
- {
- _taskProcessor.DeleteTask(state.CurrentTask.RobotTaskId);
- state.CurrentTask = null;
- state.RobotTaskTotalNum = 0;
- state.CellBarcode = new List<string>();
- return true;
- }
- }
- return false;
-
- case "running":
- state.OperStatus = "Running";
- return true;
-
- case "pausing":
- state.OperStatus = "Pausing";
- return true;
-
- case "warming":
- state.OperStatus = "Warming";
- return true;
-
- case "emstoping":
- state.OperStatus = "Emstoping";
- return true;
-
- case "runmode,1":
- state.RobotRunMode = 1;
- return true;
-
- case "runmode,2":
- state.RobotRunMode = 2;
- return true;
-
- case "controlmode,1":
- state.RobotControlMode = 1;
- return true;
-
- case "controlmode,2":
- state.RobotControlMode = 2;
- return true;
-
- case "armobject,1":
- state.RobotArmObject = 1;
- return true;
-
- case "armobject,0":
- state.RobotArmObject = 0;
- return true;
-
- default:
- return false;
- }
- }
-
- /// <summary>
- /// 鍒ゆ柇鏄惁涓哄墠缂�鍛戒护
- /// </summary>
- private static bool IsPrefixCommand(string message)
- {
- return message.StartsWith("pickfinished") || message.StartsWith("putfinished");
}
}
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
index f379baa..724a3a8 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+锘縰sing Newtonsoft.Json;
using WIDESEA_Core;
using WIDESEAWCS_Common;
using WIDESEAWCS_Common.HttpEnum;
@@ -10,29 +10,30 @@
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
-using WIDESEAWCS_Tasks.SocketServer;
+using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks
{
/// <summary>
- /// 鏈烘鎵嬩换鍔″鐞嗗櫒 - 璐熻矗鏈烘鎵嬩换鍔℃墽琛屽拰澶勭悊
+ /// 鏈哄櫒浜轰换鍔″鐞嗗櫒锛氳礋璐d换鍔¤幏鍙栥�佷笅鍙戙�佸叆搴撲换鍔″洖浼犲強搴撳瓨 DTO 鏋勫缓銆�
/// </summary>
public class RobotTaskProcessor
{
- private readonly TcpSocketServer _tcpSocket;
+ // 閫氳繃缃戝叧璁块棶 Socket锛岄伩鍏嶄笟鍔″眰鐩存帴渚濊禆 TcpSocketServer銆�
+ private readonly ISocketClientGateway _socketClientGateway;
private readonly RobotStateManager _stateManager;
private readonly IRobotTaskService _robotTaskService;
private readonly ITaskService _taskService;
private readonly HttpClientHelper _httpClientHelper;
public RobotTaskProcessor(
- TcpSocketServer tcpSocket,
+ ISocketClientGateway socketClientGateway,
RobotStateManager stateManager,
IRobotTaskService robotTaskService,
ITaskService taskService,
HttpClientHelper httpClientHelper)
{
- _tcpSocket = tcpSocket;
+ _socketClientGateway = socketClientGateway;
_stateManager = stateManager;
_robotTaskService = robotTaskService;
_taskService = taskService;
@@ -40,7 +41,7 @@
}
/// <summary>
- /// 鑾峰彇鏈烘鎵嬩换鍔�
+ /// 鎸夎澶囩紪鐮佽幏鍙栧綋鍓嶆満鍣ㄤ汉浠诲姟銆�
/// </summary>
public Dt_RobotTask? GetTask(RobotCraneDevice robotCrane)
{
@@ -48,7 +49,7 @@
}
/// <summary>
- /// 鑾峰彇鏈烘鎵嬩换鍔�
+ /// 鍒犻櫎鏈哄櫒浜轰换鍔°��
/// </summary>
public bool? DeleteTask(int ID)
{
@@ -56,26 +57,27 @@
}
/// <summary>
- /// 鍙戦�佹満姊版墜鍙栬揣鍛戒护
+ /// 涓嬪彂鍙栬揣鎸囦护锛圥ickbattery锛夊埌鏈哄櫒浜哄鎴风銆�
/// </summary>
public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state)
{
string taskString = $"Pickbattery,{task.RobotSourceAddress}";
- // 鍙戦�佷换鍔℃寚浠�
- bool result = await _tcpSocket.SendToClientAsync(state.IPAddress, taskString);
+ bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
if (result)
{
task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
state.CurrentTask = task;
- // 鏇存柊缂撳瓨涓殑鐘舵�侊紙浣跨敤瀹夊叏鏇存柊闃叉骞跺彂瑕嗙洊锛�
+ // 淇濇寔鍘熻涔夛細浠呭湪鐘舵�佸畨鍏ㄥ啓鍏ユ垚鍔熷悗鍐嶆洿鏂颁换鍔$姸鎬併��
if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
+ {
await _robotTaskService.UpdateRobotTaskAsync(task);
+ }
}
}
/// <summary>
- /// 澶勭悊鍏ュ簱浠诲姟
+ /// 澶勭悊鍏ュ簱浠诲姟鍥炰紶锛堟媶鐩�/缁勭洏/鎹㈢洏鍦烘櫙锛夈��
/// </summary>
public async Task<bool> HandleInboundTaskAsync(RobotSocketState state, bool useSourceAddress)
{
@@ -92,7 +94,6 @@
string SourceAddress = currentTask.RobotTargetAddressLineCode;
string TargetAddress = currentTask.RobotSourceAddressLineCode;
string PalletCode = string.Empty;
- // 鐩存帴杞崲涓烘灇涓剧被鍨嬭繘琛屾瘮杈�
var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
if (useSourceAddress)
@@ -134,6 +135,7 @@
PalletType = 1,
TaskType = taskType
};
+
var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.CreateTaskInboundAsync), taskDto.ToJson());
if (!result.Data.Status && result.IsSuccess)
{
@@ -142,10 +144,12 @@
WMSTaskDTO taskDTO = JsonConvert.DeserializeObject<WMSTaskDTO>(result.Data.Data.ToJson() ?? string.Empty) ?? new WMSTaskDTO();
var content = _taskService.ReceiveWMSTask(new List<WMSTaskDTO> { taskDTO });
- if (!content.Status) return false;
+ if (!content.Status)
+ {
+ return false;
+ }
var taskInfo = JsonConvert.DeserializeObject<Dt_Task>(content.Data.ToJson() ?? string.Empty) ?? new Dt_Task();
-
string sourceAddress = taskDTO.SourceAddress;
IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress));
@@ -161,11 +165,12 @@
return true;
}
}
+
return false;
}
/// <summary>
- /// 鏋勫缓搴撳瓨DTO
+ /// 鏋勫缓搴撳瓨鍥炰紶 DTO銆�
/// </summary>
public static StockDTO BuildStockDTO(RobotSocketState state, int[] positions)
{
@@ -189,7 +194,7 @@
}
/// <summary>
- /// 璋冪敤鎷嗙洏API
+ /// 璋冪敤鎷嗙洏 API銆�
/// </summary>
public HttpResponseResult<WebResponseContent> PostSplitPalletAsync(StockDTO stockDTO)
{
@@ -197,7 +202,7 @@
}
/// <summary>
- /// 璋冪敤缁勭洏鎴栨崲鐩楢PI
+ /// 璋冪敤缁勭洏/鎹㈢洏 API銆�
/// </summary>
public HttpResponseResult<WebResponseContent> PostGroupPalletAsync(string configKey, StockDTO stockDTO)
{
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
new file mode 100644
index 0000000..56ea0ab
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
@@ -0,0 +1,138 @@
+锘縰sing System.Net.Sockets;
+using WIDESEAWCS_Common.HttpEnum;
+using WIDESEAWCS_Common.TaskEnum;
+using WIDESEAWCS_DTO.TaskInfo;
+using WIDESEAWCS_ITaskInfoService;
+using WIDESEAWCS_Model.Models;
+using WIDESEAWCS_Tasks.Workflow.Abstractions;
+
+namespace WIDESEAWCS_Tasks.Workflow
+{
+ /// <summary>
+ /// 鍓嶇紑鍛戒护澶勭悊锛氳縼绉诲師 RobotMessageHandler 鐨� pickfinished/putfinished 鍒嗘敮銆�
+ /// </summary>
+ public class RobotPrefixCommandHandler : IRobotPrefixCommandHandler
+ {
+ private readonly IRobotTaskService _robotTaskService;
+ private readonly RobotTaskProcessor _taskProcessor;
+ private readonly RobotStateManager _stateManager;
+ private readonly ISocketClientGateway _socketClientGateway;
+
+ public RobotPrefixCommandHandler(
+ IRobotTaskService robotTaskService,
+ RobotTaskProcessor taskProcessor,
+ RobotStateManager stateManager,
+ ISocketClientGateway socketClientGateway)
+ {
+ _robotTaskService = robotTaskService;
+ _taskProcessor = taskProcessor;
+ _stateManager = stateManager;
+ _socketClientGateway = socketClientGateway;
+ }
+
+ public bool IsPrefixCommand(string message)
+ {
+ return message.StartsWith("pickfinished") || message.StartsWith("putfinished");
+ }
+
+ public async Task HandleAsync(string message, RobotSocketState state, TcpClient client)
+ {
+ try
+ {
+ var parts = message.Split(',');
+ if (parts.Length < 1 || state.CurrentTask == null)
+ {
+ return;
+ }
+
+ var cmd = parts[0].ToLowerInvariant();
+ int[] positions = parts.Skip(1)
+ .Select(p => int.TryParse(p, out int value) ? value : (int?)null)
+ .Where(v => v.HasValue && v.Value != 0)
+ .Select(v => v!.Value)
+ .ToArray();
+
+ var task = await _robotTaskService.Repository.QueryFirstAsync(x => x.RobotTaskId == state.CurrentTask.RobotTaskId);
+
+ if (cmd.StartsWith("pickfinished"))
+ {
+ await HandlePickFinishedAsync(state, positions, task);
+ }
+ else if (cmd.StartsWith("putfinished"))
+ {
+ await HandlePutFinishedAsync(state, positions, task);
+ }
+
+ await _socketClientGateway.SendMessageAsync(client, message);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"RobotJob MessageReceived Error: {ex.Message}");
+ }
+ }
+
+ private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task)
+ {
+ if (state.IsSplitPallet)
+ {
+ var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
+ state.LastPickPositions = positions;
+
+ var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
+ if (result.Data.Status && result.IsSuccess)
+ {
+ state.CurrentAction = "PickFinished";
+ }
+ }
+ else
+ {
+ state.CurrentAction = "PickFinished";
+ }
+
+ state.LastPickPositions = positions;
+ if (task != null)
+ {
+ task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode();
+ if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
+ {
+ await _robotTaskService.Repository.UpdateDataAsync(task);
+ }
+ }
+ }
+
+ private async Task HandlePutFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task)
+ {
+ bool putSuccess = true;
+ if (state.IsGroupPallet)
+ {
+ state.LastPutPositions = positions;
+ var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
+ var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
+ ? nameof(ConfigKey.ChangePalletAsync)
+ : nameof(ConfigKey.GroupPalletAsync);
+
+ var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO);
+ putSuccess = result.Data.Status && result.IsSuccess;
+ }
+
+ if (putSuccess)
+ {
+ state.CurrentAction = "PutFinished";
+ state.RobotTaskTotalNum += positions.Length;
+ if (task != null)
+ {
+ task.RobotTaskTotalNum += positions.Length;
+ }
+ }
+
+ if (task != null)
+ {
+ task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode();
+ if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
+ {
+ await _robotTaskService.Repository.UpdateDataAsync(task);
+ }
+ }
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
new file mode 100644
index 0000000..1869396
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -0,0 +1,128 @@
+锘縰sing WIDESEAWCS_Common.TaskEnum;
+using WIDESEAWCS_Tasks.Workflow.Abstractions;
+
+namespace WIDESEAWCS_Tasks.Workflow
+{
+ /// <summary>
+ /// 绠�鍗曞懡浠ゅ鐞嗭細浠呰縼绉诲師 RobotMessageHandler 涓殑鍛戒护鍒嗘敮锛屼笉鏀瑰彉涓氬姟璇箟銆�
+ /// </summary>
+ public class RobotSimpleCommandHandler : IRobotSimpleCommandHandler
+ {
+ private readonly RobotTaskProcessor _taskProcessor;
+
+ public RobotSimpleCommandHandler(RobotTaskProcessor taskProcessor)
+ {
+ _taskProcessor = taskProcessor;
+ }
+
+ public async Task<bool> HandleAsync(string message, RobotSocketState state)
+ {
+ switch (message)
+ {
+ case "homing":
+ state.OperStatus = "Homing";
+ return true;
+
+ case "homed":
+ state.OperStatus = "Homed";
+ return true;
+
+ case "picking":
+ state.CurrentAction = "Picking";
+ return true;
+
+ case "puting":
+ state.CurrentAction = "Putting";
+ return true;
+
+ case "allpickfinished":
+ {
+ state.CurrentAction = "AllPickFinished";
+ var currentTask = state.CurrentTask;
+ if (currentTask == null)
+ {
+ return false;
+ }
+
+ var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
+ if (robotTaskType == RobotTaskTypeEnum.SplitPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
+ {
+ if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
+ {
+ _taskProcessor.DeleteTask(currentTask.RobotTaskId);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case "allputfinished":
+ {
+ state.CurrentAction = "AllPutFinished";
+ var currentTask = state.CurrentTask;
+ if (currentTask == null)
+ {
+ return false;
+ }
+
+ var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
+ if (robotTaskType == RobotTaskTypeEnum.GroupPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
+ {
+ if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
+ {
+ _taskProcessor.DeleteTask(currentTask.RobotTaskId);
+ state.CurrentTask = null;
+ state.RobotTaskTotalNum = 0;
+ state.CellBarcode = new List<string>();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case "running":
+ state.OperStatus = "Running";
+ return true;
+
+ case "pausing":
+ state.OperStatus = "Pausing";
+ return true;
+
+ case "warming":
+ state.OperStatus = "Warming";
+ return true;
+
+ case "emstoping":
+ state.OperStatus = "Emstoping";
+ return true;
+
+ case "runmode,1":
+ state.RobotRunMode = 1;
+ return true;
+
+ case "runmode,2":
+ state.RobotRunMode = 2;
+ return true;
+
+ case "controlmode,1":
+ state.RobotControlMode = 1;
+ return true;
+
+ case "controlmode,2":
+ state.RobotControlMode = 2;
+ return true;
+
+ case "armobject,1":
+ state.RobotArmObject = 1;
+ return true;
+
+ case "armobject,0":
+ state.RobotArmObject = 0;
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
new file mode 100644
index 0000000..b344986
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -0,0 +1,108 @@
+using WIDESEA_Core;
+using WIDESEAWCS_Common.TaskEnum;
+using WIDESEAWCS_ITaskInfoService;
+using WIDESEAWCS_Model.Models;
+using WIDESEAWCS_Tasks.Workflow.Abstractions;
+
+namespace WIDESEAWCS_Tasks.Workflow
+{
+ /// <summary>
+ /// RobotJob 流程编排器:迁移原 RobotJob 状态机分支,降低 Job 类复杂度。
+ /// </summary>
+ public class RobotWorkflowOrchestrator : IRobotWorkflowOrchestrator
+ {
+ private readonly RobotStateManager _stateManager;
+ private readonly RobotClientManager _clientManager;
+ private readonly RobotTaskProcessor _taskProcessor;
+ private readonly IRobotTaskService _robotTaskService;
+
+ public RobotWorkflowOrchestrator(
+ RobotStateManager stateManager,
+ RobotClientManager clientManager,
+ RobotTaskProcessor taskProcessor,
+ IRobotTaskService robotTaskService)
+ {
+ _stateManager = stateManager;
+ _clientManager = clientManager;
+ _taskProcessor = taskProcessor;
+ _robotTaskService = robotTaskService;
+ }
+
+ public async Task ExecuteAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
+ {
+ // 保持原有分支判定条件不变,确保行为一致。
+ if (latestState.RobotRunMode == 2 && latestState.RobotControlMode == 1 && latestState.OperStatus != "Running")
+ {
+ if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished")
+ && latestState.RobotArmObject == 1
+ && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
+ {
+ await HandlePickFinishedStateAsync(task, ipAddress);
+ }
+ else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished")
+ && latestState.OperStatus == "Homed"
+ && latestState.RobotArmObject == 0
+ && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode()
+ || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
+ {
+ await HandlePutFinishedStateAsync(task, ipAddress);
+ }
+ }
+ }
+
+ private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress)
+ {
+ string taskString = $"Putbattery,{task.RobotTargetAddress}";
+ bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
+ if (result)
+ {
+ task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
+
+ var stateToUpdate = _stateManager.GetState(ipAddress);
+ if (stateToUpdate != null)
+ {
+ stateToUpdate.CurrentTask = task;
+ if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
+ {
+ await _robotTaskService.UpdateRobotTaskAsync(task);
+ }
+ }
+ }
+ }
+
+ private async Task HandlePutFinishedStateAsync(Dt_RobotTask task, string ipAddress)
+ {
+ var stateForUpdate = _stateManager.GetState(ipAddress);
+ if (stateForUpdate == null)
+ {
+ return;
+ }
+
+ if (!stateForUpdate.IsSplitPallet && !stateForUpdate.IsGroupPallet)
+ {
+ stateForUpdate.IsSplitPallet = task.RobotTaskType == RobotTaskTypeEnum.SplitPallet.GetHashCode();
+ stateForUpdate.IsGroupPallet = task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode()
+ || task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
+ }
+
+ if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
+ {
+ const string prefix = "TRAY";
+ string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
+ string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
+ if (!string.IsNullOrEmpty(trayBarcode1) && !string.IsNullOrEmpty(trayBarcode2))
+ {
+ stateForUpdate.CellBarcode.Add(trayBarcode1);
+ stateForUpdate.CellBarcode.Add(trayBarcode2);
+ await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
+ }
+ }
+ else
+ {
+ await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
+ }
+ }
+ }
+}
+
+
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/SocketClientGateway.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/SocketClientGateway.cs
new file mode 100644
index 0000000..96879a3
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/SocketClientGateway.cs
@@ -0,0 +1,40 @@
+using System.Net.Sockets;
+using WIDESEAWCS_QuartzJob;
+using WIDESEAWCS_Tasks.Workflow.Abstractions;
+
+namespace WIDESEAWCS_Tasks.SocketServer
+{
+ /// <summary>
+ /// TcpSocketServer 的适配器实现,保持底层行为不变,仅做访问收口。
+ /// </summary>
+ public class SocketClientGateway : ISocketClientGateway
+ {
+ private readonly TcpSocketServer _tcpSocket;
+
+ public SocketClientGateway(TcpSocketServer tcpSocket)
+ {
+ _tcpSocket = tcpSocket;
+ }
+
+ public Task<bool> SendToClientAsync(string clientId, string message)
+ {
+ return _tcpSocket.SendToClientAsync(clientId, message);
+ }
+
+ public Task SendMessageAsync(TcpClient client, string message)
+ {
+ return _tcpSocket.SendMessageAsync(client, message);
+ }
+
+ public IReadOnlyList<string> GetClientIds()
+ {
+ return _tcpSocket.GetClientIds();
+ }
+
+ public Task HandleClientAsync(TcpClient client, string clientId, CancellationToken cancellationToken, RobotSocketState robotCrane)
+ {
+ return _tcpSocket.HandleClientAsync(client, clientId, cancellationToken, robotCrane);
+ }
+ }
+}
+
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
index 5831db5..c9f777a 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -1,24 +1,14 @@
-锘縰sing System;
-using System.Collections.Generic;
+锘縰sing Quartz;
+using System;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
+using System.IO;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components.Routing;
-using Quartz;
using WIDESEAWCS_Common.TaskEnum;
-using WIDESEAWCS_Communicator;
-using WIDESEAWCS_Core.Enums;
-using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
-using WIDESEAWCS_QuartzJob.DeviceBase;
-using WIDESEAWCS_QuartzJob.Models;
-using WIDESEAWCS_QuartzJob.Service;
-using WIDESEAWCS_QuartzJob.StackerCrane.Enum;
-using WIDESEAWCS_TaskInfoService;
+using WIDESEAWCS_QuartzJob.StackerCrane;
using WIDESEAWCS_Tasks.StackerCraneJob;
namespace WIDESEAWCS_Tasks
@@ -29,16 +19,24 @@
private readonly ITaskService _taskService;
private readonly ITaskExecuteDetailService _taskExecuteDetailService;
private readonly ITaskRepository _taskRepository;
- private readonly IRouterService _routerService;
- private readonly StackerCraneCommandConfig _config;
- public CommonStackerCraneJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, ITaskRepository taskRepository, IRouterService routerService)
+ private readonly StackerCraneCommandConfig _config;
+ private readonly StackerCraneTaskSelector _taskSelector;
+ private readonly StackerCraneCommandBuilder _commandBuilder;
+
+ public CommonStackerCraneJob(
+ ITaskService taskService,
+ ITaskExecuteDetailService taskExecuteDetailService,
+ ITaskRepository taskRepository,
+ WIDESEAWCS_QuartzJob.Service.IRouterService routerService)
{
_taskService = taskService;
_taskExecuteDetailService = taskExecuteDetailService;
_taskRepository = taskRepository;
- _routerService = routerService;
+
_config = LoadConfig();
+ _taskSelector = new StackerCraneTaskSelector(taskService, routerService);
+ _commandBuilder = new StackerCraneCommandBuilder(taskService, routerService, _config);
}
/// <summary>
@@ -59,6 +57,7 @@
{
Console.WriteLine($"閰嶇疆鍔犺浇澶辫触: {ex.Message}锛屼娇鐢ㄩ粯璁ら厤缃�");
}
+
return new StackerCraneCommandConfig();
}
@@ -74,144 +73,60 @@
return Task.CompletedTask;
}
+ // 璁㈤槄涓�娆′换鍔″畬鎴愪簨浠躲��
if (!commonStackerCrane.IsEventSubscribed)
{
commonStackerCrane.StackerCraneTaskCompletedEventHandler += CommonStackerCrane_StackerCraneTaskCompletedEventHandler;
}
- if (commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
+ if (!commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
{
- commonStackerCrane.CheckStackerCraneTaskCompleted();
+ return Task.CompletedTask;
+ }
- Dt_Task? task = GetTask(commonStackerCrane);
- if (task != null)
- {
- object? stackerCraneTaskCommand = ConvertToStackerCraneTaskCommand(task);
- if (stackerCraneTaskCommand != null)
- {
- bool sendFlag = SendStackerCraneCommand(commonStackerCrane, stackerCraneTaskCommand);
- if (sendFlag)
- {
- commonStackerCrane.LastTaskType = task.TaskType;
- _taskService.UpdateTaskStatusToNext(task.TaskNum);
- }
- }
- }
+ commonStackerCrane.CheckStackerCraneTaskCompleted();
+
+ // 浠诲姟閫夋嫨涓嬫矇鍒颁笓鐢ㄩ�夋嫨鍣ㄣ��
+ Dt_Task? task = _taskSelector.SelectTask(commonStackerCrane);
+ if (task == null)
+ {
+ return Task.CompletedTask;
+ }
+
+ // 鍛戒护鏋勫缓涓嬫矇鍒颁笓鐢ㄦ瀯寤哄櫒銆�
+ object? stackerCraneTaskCommand = _commandBuilder.ConvertToStackerCraneTaskCommand(task);
+ if (stackerCraneTaskCommand == null)
+ {
+ return Task.CompletedTask;
+ }
+
+ bool sendFlag = SendStackerCraneCommand(commonStackerCrane, stackerCraneTaskCommand);
+ if (sendFlag)
+ {
+ commonStackerCrane.LastTaskType = task.TaskType;
+ _taskService.UpdateTaskStatusToNext(task.TaskNum);
}
}
catch (Exception ex)
{
Console.WriteLine($"CommonStackerCraneJob Error: {ex.Message}");
}
+
return Task.CompletedTask;
}
/// <summary>
/// 浠诲姟瀹屾垚浜嬩欢璁㈤槄鐨勬柟娉�
/// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, WIDESEAWCS_QuartzJob.StackerCrane.StackerCraneTaskCompletedEventArgs e)
+ private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, StackerCraneTaskCompletedEventArgs e)
{
CommonStackerCrane? commonStackerCrane = sender as CommonStackerCrane;
if (commonStackerCrane != null)
{
- //if (commonStackerCrane.GetValue<StackerCraneDBName, short>(StackerCraneDBName.WorkType) != 5)
- //{
Console.Out.WriteLine("TaskCompleted" + e.TaskNum);
_taskService.StackCraneTaskCompleted(e.TaskNum);
commonStackerCrane.SetValue(StackerCraneDBName.WorkAction, 5);
- //}
}
- }
-
- /// <summary>
- /// 鑾峰彇浠诲姟
- /// </summary>
- /// <param name="commonStackerCrane">鍫嗗灈鏈哄璞�</param>
- /// <returns></returns>
- private Dt_Task? GetTask(IStackerCrane commonStackerCrane)
- {
- Dt_Task? task = null;
- if (commonStackerCrane.LastTaskType == null)
- {
- task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
- }
- else
- {
- if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
- {
- task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
- task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
- }
- else
- {
- task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
- }
- }
-
- if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
- {
- if (IsOutTaskStationAvailable(task))
- {
- return task;
- }
-
- List<string> otherOutStationCodes = _routerService.QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
- .Select(x => x.ChildPosi).ToList();
- List<Dt_Task> tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
- foreach (var alternativeTask in tasks)
- {
- if (IsOutTaskStationAvailable(alternativeTask))
- {
- return alternativeTask;
- }
- }
- task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
- }
-
- return task;
- }
-
- /// <summary>
- /// 鍑哄簱浠诲姟鍒ゆ柇鍑哄簱绔欏彴鏄惁鍙敤
- /// </summary>
- /// <param name="task">浠诲姟瀹炰綋</param>
- /// <returns>濡傛灉绔欏彴鍙敤杩斿洖true锛屽惁鍒欒繑鍥瀎alse</returns>
- private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
- {
- Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
- if (router == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.NextAddress}銆戜俊鎭紝鏃犳硶鏍¢獙绔欏彴");
- return false;
- }
-
- IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
- if (device == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒板嚭搴撶珯鍙般�恵router.ChildPosiDeviceCode}銆戝搴旂殑閫氳瀵硅薄锛屾棤娉曞垽鏂嚭搴撶珯鍙版槸鍚﹁鍗犵敤");
- return false;
- }
-
- CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
- return conveyorLine.IsOccupied(router.ChildPosi);
- }
-
- /// <summary>
- /// 浠诲姟瀹炰綋杞崲鎴愬懡浠odel
- /// </summary>
- public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
- {
- // 鏍规嵁閰嶇疆鍒ゆ柇鍛戒护绫诲瀷
- string commandType = GetCommandType(task.Roadway);
-
- // 鍒涘缓骞舵瀯寤哄懡浠�
- return commandType switch
- {
- "Formation" => BuildCommand(task, CreateFormationCommand(task)),
- _ => BuildCommand(task, CreateStandardCommand(task))
- };
}
private static bool SendStackerCraneCommand(IStackerCrane commonStackerCrane, object command)
@@ -223,180 +138,5 @@
_ => false
};
}
-
- /// <summary>
- /// 鏍规嵁 Roadway 鑾峰彇鍛戒护绫诲瀷
- /// </summary>
- private string GetCommandType(string roadway)
- {
- foreach (var mapping in _config.RoadwayCommandMapping)
- {
- if (roadway.Contains(mapping.Key))
- {
- return mapping.Value;
- }
- }
- return _config.DefaultCommandType;
- }
-
- /// <summary>
- /// 鍒涘缓鏍囧噯鍫嗗灈鏈哄懡浠�
- /// </summary>
- private static StackerCraneTaskCommand CreateStandardCommand(Dt_Task task)
- {
- return new StackerCraneTaskCommand
- {
- //Barcode = task.PalletCode,
- TaskNum = task.TaskNum,
- WorkType = 1,
- WorkAction = 1
- };
- }
-
- /// <summary>
- /// 鍒涘缓鍒嗗鍫嗗灈鏈哄懡浠�
- /// </summary>
- private static FormationStackerCraneTaskCommand CreateFormationCommand(Dt_Task task)
- {
- return new FormationStackerCraneTaskCommand
- {
- Barcode = task.PalletCode,
- TaskNum = task.TaskNum,
- WorkType = 1,
- WorkAction = 1,
- FireAlarm = 0,
- HeartBeat = 0,
- FieldName = string.Empty
- };
- }
-
- /// <summary>
- /// 閫氱敤鍛戒护鏋勫缓鏂规硶
- /// </summary>
- private T? BuildCommand<T>(Dt_Task task, T command) where T : class
- {
- TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
-
- return taskTypeGroup switch
- {
- TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command),
- TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command),
- TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command),
- _ => command
- };
- }
-
- /// <summary>
- /// 閫氱敤鍏ュ簱鍛戒护鏋勫缓
- /// </summary>
- private T? BuildInboundCommand<T>(Dt_Task task, T command) where T : class
- {
- Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
- if (router == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.CurrentAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鍙栬揣绔欏彴淇℃伅");
- return null;
- }
-
- SetCommandProperty(command, "StartRow", Convert.ToInt16(router.SrmRow));
- SetCommandProperty(command, "StartColumn", Convert.ToInt16(router.SrmColumn));
- SetCommandProperty(command, "StartLayer", Convert.ToInt16(router.SrmLayer));
-
- if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍏ュ簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
- return null;
- }
-
- SetCommandProperty(command, "EndRow", endRow);
- SetCommandProperty(command, "EndColumn", endColumn);
- SetCommandProperty(command, "EndLayer", endLayer);
-
- return command;
- }
-
- /// <summary>
- /// 閫氱敤鍑哄簱鍛戒护鏋勫缓
- /// </summary>
- private T? BuildOutboundCommand<T>(Dt_Task task, T command) where T : class
- {
- Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
- if (router == null)
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.TargetAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鏀捐揣绔欏彴淇℃伅");
- return null;
- }
-
- SetCommandProperty(command, "EndRow", Convert.ToInt16(router.SrmRow));
- SetCommandProperty(command, "EndColumn", Convert.ToInt16(router.SrmColumn));
- SetCommandProperty(command, "EndLayer", Convert.ToInt16(router.SrmLayer));
-
- if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍑哄簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
- return null;
- }
-
- SetCommandProperty(command, "StartRow", startRow);
- SetCommandProperty(command, "StartColumn", startColumn);
- SetCommandProperty(command, "StartLayer", startLayer);
-
- return command;
- }
-
- /// <summary>
- /// 閫氱敤绉诲簱鍛戒护鏋勫缓
- /// </summary>
- private T? BuildRelocationCommand<T>(Dt_Task task, T command) where T : class
- {
- if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
- return null;
- }
-
- SetCommandProperty(command, "EndRow", endRow);
- SetCommandProperty(command, "EndColumn", endColumn);
- SetCommandProperty(command, "EndLayer", endLayer);
-
- if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
- {
- _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
- return null;
- }
-
- SetCommandProperty(command, "StartRow", startRow);
- SetCommandProperty(command, "StartColumn", startColumn);
- SetCommandProperty(command, "StartLayer", startLayer);
-
- return command;
- }
-
- /// <summary>
- /// 浣跨敤鍙嶅皠璁剧疆鍛戒护灞炴��
- /// </summary>
- private static void SetCommandProperty<T>(T command, string propertyName, object value) where T : class
- {
- var property = typeof(T).GetProperty(propertyName);
- property?.SetValue(command, value);
- }
-
- /// <summary>
- /// 瑙f瀽鍦板潃瀛楃涓诧紙鏍煎紡锛氳-鍒�-灞傦級
- /// </summary>
- private bool TryParseAddress(string address, out short row, out short column, out short layer)
- {
- row = column = layer = 0;
-
- string[] parts = address.Split("-");
- if (parts.Length != 3)
- {
- return false;
- }
-
- return short.TryParse(parts[0], out row)
- && short.TryParse(parts[1], out column)
- && short.TryParse(parts[2], out layer);
- }
}
-}
\ No newline at end of file
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs
new file mode 100644
index 0000000..71b334a
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs
@@ -0,0 +1,189 @@
+锘縰sing System;
+using System.Diagnostics.CodeAnalysis;
+using WIDESEAWCS_Common.TaskEnum;
+using WIDESEAWCS_ITaskInfoService;
+using WIDESEAWCS_Model.Models;
+using WIDESEAWCS_QuartzJob.Models;
+using WIDESEAWCS_QuartzJob.Service;
+using WIDESEAWCS_Tasks.StackerCraneJob;
+
+namespace WIDESEAWCS_Tasks
+{
+ /// <summary>
+ /// 鍫嗗灈鏈哄懡浠ゆ瀯寤哄櫒锛氬皝瑁呬换鍔″埌鍛戒护瀵硅薄鐨勮浆鎹笌鍦板潃瑙f瀽銆�
+ /// </summary>
+ public class StackerCraneCommandBuilder
+ {
+ private readonly ITaskService _taskService;
+ private readonly IRouterService _routerService;
+ private readonly StackerCraneCommandConfig _config;
+
+ public StackerCraneCommandBuilder(
+ ITaskService taskService,
+ IRouterService routerService,
+ StackerCraneCommandConfig config)
+ {
+ _taskService = taskService;
+ _routerService = routerService;
+ _config = config;
+ }
+
+ public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
+ {
+ string commandType = GetCommandType(task.Roadway);
+ return commandType switch
+ {
+ "Formation" => BuildCommand(task, CreateFormationCommand(task)),
+ _ => BuildCommand(task, CreateStandardCommand(task))
+ };
+ }
+
+ private string GetCommandType(string roadway)
+ {
+ foreach (var mapping in _config.RoadwayCommandMapping)
+ {
+ if (roadway.Contains(mapping.Key))
+ {
+ return mapping.Value;
+ }
+ }
+
+ return _config.DefaultCommandType;
+ }
+
+ private static StackerCraneTaskCommand CreateStandardCommand(Dt_Task task)
+ {
+ return new StackerCraneTaskCommand
+ {
+ TaskNum = task.TaskNum,
+ WorkType = 1,
+ WorkAction = 1
+ };
+ }
+
+ private static FormationStackerCraneTaskCommand CreateFormationCommand(Dt_Task task)
+ {
+ return new FormationStackerCraneTaskCommand
+ {
+ Barcode = task.PalletCode,
+ TaskNum = task.TaskNum,
+ WorkType = 1,
+ WorkAction = 1,
+ FireAlarm = 0,
+ HeartBeat = 0,
+ FieldName = string.Empty
+ };
+ }
+
+ private T? BuildCommand<T>(Dt_Task task, T command) where T : class
+ {
+ TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
+
+ return taskTypeGroup switch
+ {
+ TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command),
+ TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command),
+ TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command),
+ _ => command
+ };
+ }
+
+ private T? BuildInboundCommand<T>(Dt_Task task, T command) where T : class
+ {
+ Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
+ if (router == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.CurrentAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鍙栬揣绔欏彴淇℃伅");
+ return null;
+ }
+
+ SetCommandProperty(command, "StartRow", Convert.ToInt16(router.SrmRow));
+ SetCommandProperty(command, "StartColumn", Convert.ToInt16(router.SrmColumn));
+ SetCommandProperty(command, "StartLayer", Convert.ToInt16(router.SrmLayer));
+
+ if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍏ュ簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
+ return null;
+ }
+
+ SetCommandProperty(command, "EndRow", endRow);
+ SetCommandProperty(command, "EndColumn", endColumn);
+ SetCommandProperty(command, "EndLayer", endLayer);
+
+ return command;
+ }
+
+ private T? BuildOutboundCommand<T>(Dt_Task task, T command) where T : class
+ {
+ Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
+ if (router == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.TargetAddress}銆戜俊鎭紝鏃犳硶鑾峰彇瀵瑰簲鐨勫爢鍨涙満鏀捐揣绔欏彴淇℃伅");
+ return null;
+ }
+
+ SetCommandProperty(command, "EndRow", Convert.ToInt16(router.SrmRow));
+ SetCommandProperty(command, "EndColumn", Convert.ToInt16(router.SrmColumn));
+ SetCommandProperty(command, "EndLayer", Convert.ToInt16(router.SrmLayer));
+
+ if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鍑哄簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
+ return null;
+ }
+
+ SetCommandProperty(command, "StartRow", startRow);
+ SetCommandProperty(command, "StartColumn", startColumn);
+ SetCommandProperty(command, "StartLayer", startLayer);
+
+ return command;
+ }
+
+ private T? BuildRelocationCommand<T>(Dt_Task task, T command) where T : class
+ {
+ if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟缁堢偣閿欒锛岀粓鐐癸細銆恵task.NextAddress}銆�");
+ return null;
+ }
+
+ SetCommandProperty(command, "EndRow", endRow);
+ SetCommandProperty(command, "EndColumn", endColumn);
+ SetCommandProperty(command, "EndLayer", endLayer);
+
+ if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"绉诲簱浠诲姟璧风偣閿欒锛岃捣鐐癸細銆恵task.CurrentAddress}銆�");
+ return null;
+ }
+
+ SetCommandProperty(command, "StartRow", startRow);
+ SetCommandProperty(command, "StartColumn", startColumn);
+ SetCommandProperty(command, "StartLayer", startLayer);
+
+ return command;
+ }
+
+ private static void SetCommandProperty<T>(T command, string propertyName, object value) where T : class
+ {
+ var property = typeof(T).GetProperty(propertyName);
+ property?.SetValue(command, value);
+ }
+
+ private static bool TryParseAddress(string address, out short row, out short column, out short layer)
+ {
+ row = column = layer = 0;
+
+ string[] parts = address.Split("-");
+ if (parts.Length != 3)
+ {
+ return false;
+ }
+
+ return short.TryParse(parts[0], out row)
+ && short.TryParse(parts[1], out column)
+ && short.TryParse(parts[2], out layer);
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
new file mode 100644
index 0000000..707e404
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
@@ -0,0 +1,90 @@
+锘縰sing System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using WIDESEAWCS_Common.TaskEnum;
+using WIDESEAWCS_ITaskInfoService;
+using WIDESEAWCS_Model.Models;
+using WIDESEAWCS_QuartzJob;
+using WIDESEAWCS_QuartzJob.Models;
+using WIDESEAWCS_QuartzJob.Service;
+
+namespace WIDESEAWCS_Tasks
+{
+ /// <summary>
+ /// 鍫嗗灈鏈轰换鍔¢�夋嫨鍣細灏佽浠诲姟鎸戦�変笌绔欏彴鍙敤鎬у垽鏂��
+ /// </summary>
+ public class StackerCraneTaskSelector
+ {
+ private readonly ITaskService _taskService;
+ private readonly IRouterService _routerService;
+
+ public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService)
+ {
+ _taskService = taskService;
+ _routerService = routerService;
+ }
+
+ public Dt_Task? SelectTask(IStackerCrane commonStackerCrane)
+ {
+ Dt_Task? task;
+ if (commonStackerCrane.LastTaskType == null)
+ {
+ task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
+ }
+ else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
+ {
+ task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
+ task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
+ }
+ else
+ {
+ task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
+ }
+
+ if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
+ {
+ if (IsOutTaskStationAvailable(task))
+ {
+ return task;
+ }
+
+ var otherOutStationCodes = _routerService
+ .QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
+ .Select(x => x.ChildPosi)
+ .ToList();
+
+ var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
+ foreach (var alternativeTask in tasks)
+ {
+ if (IsOutTaskStationAvailable(alternativeTask))
+ {
+ return alternativeTask;
+ }
+ }
+
+ task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
+ }
+
+ return task;
+ }
+
+ private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
+ {
+ Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
+ if (router == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒扮珯鍙般�恵task.NextAddress}銆戜俊鎭紝鏃犳硶鏍¢獙绔欏彴");
+ return false;
+ }
+
+ IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
+ if (device == null)
+ {
+ _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"鏈壘鍒板嚭搴撶珯鍙般�恵router.ChildPosiDeviceCode}銆戝搴旂殑閫氳瀵硅薄锛屾棤娉曞垽鏂嚭搴撶珯鍙版槸鍚﹁鍗犵敤");
+ return false;
+ }
+
+ CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
+ return conveyorLine.IsOccupied(router.ChildPosi);
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-common-stacker-crane-job-refactor-plan.md b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-common-stacker-crane-job-refactor-plan.md
new file mode 100644
index 0000000..28f391a
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-common-stacker-crane-job-refactor-plan.md
@@ -0,0 +1,54 @@
+锘�# CommonStackerCraneJob Refactor Implementation Plan
+
+> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 鍦ㄤ笉鏀瑰彉涓氬姟琛屼负鐨勫墠鎻愪笅锛岄檷浣� `CommonStackerCraneJob` 澶嶆潅搴︼紝鎷嗗垎鈥滀换鍔¢�夋嫨鈥濆拰鈥滃懡浠ゆ瀯寤衡�濊亴璐c��
+
+**Architecture:** 淇濈暀 `CommonStackerCraneJob` 浣滀负 Quartz Job 鍏ュ彛锛屽彧璐熻矗璋冨害娴佺▼锛涘皢浠诲姟閫夋嫨閫昏緫鍜屽懡浠ゆ瀯寤洪�昏緫鍒嗗埆涓嬫矇鍒板悓鐩綍鏂扮被銆傞�氳繃鏋勯�犲嚱鏁板唴缁勮渚濊禆锛堜笉淇敼鍏ㄥ眬 DI锛夛紝纭繚鏀瑰姩鑼冨洿浠呴檺 `StackerCraneJob` 鐩綍銆�
+
+**Tech Stack:** .NET 6, Quartz, 鐜版湁 WCS Task/Router 鏈嶅姟鎺ュ彛
+
+---
+
+## Chunk 1: 缁撴瀯鎷嗗垎涓庤亴璐h惤浣�
+
+### Task 1: 鎷嗗垎浠诲姟閫夋嫨鍣�
+
+**Files:**
+- Create: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`
+- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`
+
+- [ ] Step 1: 鏂板缓浠诲姟閫夋嫨鍣ㄧ被锛岃縼绉� `GetTask` 涓� `IsOutTaskStationAvailable` 閫昏緫
+- [ ] Step 2: 鍦� Job 涓�氳繃绉佹湁瀛楁鎸佹湁浠诲姟閫夋嫨鍣ㄥ疄渚�
+- [ ] Step 3: 灏� `GetTask` 璋冪敤鏀逛负浠诲姟閫夋嫨鍣�
+
+### Task 2: 鎷嗗垎鍛戒护鏋勫缓鍣�
+
+**Files:**
+- Create: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs`
+- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`
+
+- [ ] Step 1: 鏂板缓鍛戒护鏋勫缓鍣ㄧ被锛岃縼绉� `ConvertToStackerCraneTaskCommand` 涓庡懡浠ゆ瀯寤哄垎鏀�昏緫
+- [ ] Step 2: 淇濈暀閰嶇疆璇诲彇鍦� Job 涓紝鍛戒护鏋勫缓鍣ㄦ帴鍙楅厤缃笌鏈嶅姟渚濊禆
+- [ ] Step 3: Job 涓浛鎹㈠師鏈夊懡浠ゆ瀯寤鸿皟鐢�
+
+## Chunk 2: 娓呯悊涓庨獙璇�
+
+### Task 3: Job 鍏ュ彛鐦﹁韩涓庢敞閲婅ˉ鍏�
+
+**Files:**
+- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`
+
+- [ ] Step 1: 绉婚櫎宸茶縼绉荤鏈夋柟娉曞強鏃犵敤 using
+- [ ] Step 2: 淇濈暀骞惰ˉ鍏呭叧閿祦绋嬫敞閲婏紙璁㈤槄銆佷换鍔¤幏鍙栥�佸懡浠や笅鍙戯級
+
+### Task 4: 缂栬瘧楠屾敹
+
+**Files:**
+- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`锛堝闇�寰皟锛�
+- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`锛堝闇�寰皟锛�
+- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs`锛堝闇�寰皟锛�
+
+- [ ] Step 1: 杩愯 `dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj -c Debug`
+- [ ] Step 2: 鑻ュけ璐ワ紝鎸夌紪璇戦敊璇渶灏忎慨澶嶇洿鍒伴�氳繃
+- [ ] Step 3: 杈撳嚭缁撴灉涓庢敼鍔ㄦ枃浠舵竻鍗�
diff --git a/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-conveyorline-dispatch-handler-refactor-plan.md b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-conveyorline-dispatch-handler-refactor-plan.md
new file mode 100644
index 0000000..0450f7b
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-conveyorline-dispatch-handler-refactor-plan.md
@@ -0,0 +1,49 @@
+锘�# ConveyorLineDispatchHandler Refactor Implementation Plan
+
+> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 鍦ㄤ笉鏀瑰彉璋冨害琛屼负鐨勫墠鎻愪笅锛岄檷浣� `ConveyorLineDispatchHandler` 澶嶆潅搴︼紝鎷嗗垎鈥滃湴鍧�杞绛栫暐鈥濆拰鈥滀换鍔$瓫閫夐�昏緫鈥濄��
+
+**Architecture:** 淇濈暀 `ConveyorLineDispatchHandler` 浣滀负娴佺▼鍏ュ彛锛涘皢鍙浛鎹㈢瓥鐣ワ紙杞鍦板潃閫夋嫨锛変笌浠诲姟杩囨护閫昏緫鎷嗗埌鍚岀洰褰曟柊绫讳腑锛岄伩鍏嶅崟鏂囦欢鎵挎媴鍏ㄩ儴鑱岃矗銆�
+
+**Tech Stack:** .NET 6, 鐜版湁 TaskService/RouterService 涓� DTO 妯″瀷
+
+---
+
+## Chunk 1: 缁撴瀯鎷嗗垎
+
+### Task 1: 鎷嗗垎鍦板潃閫夋嫨绛栫暐
+
+**Files:**
+- Create: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs`
+- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs`
+
+- [ ] Step 1: 杩佺Щ杞鍦板潃閫夋嫨涓庣姸鎬佺淮鎶ら�昏緫
+- [ ] Step 2: Handler 涓敼涓鸿皟鐢ㄩ�夋嫨鍣�
+
+### Task 2: 鎷嗗垎浠诲姟绛涢�夊櫒
+
+**Files:**
+- Create: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs`
+- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs`
+
+- [ ] Step 1: 杩佺Щ浠诲姟杩囨护鏉′欢鍜屽�欓�変换鍔℃瀯寤�
+- [ ] Step 2: Handler 涓敼涓鸿皟鐢ㄨ繃婊ゅ櫒
+
+## Chunk 2: 娓呯悊涓庨獙璇�
+
+### Task 3: 鍏ュ彛鐦﹁韩涓庢敞閲�
+
+**Files:**
+- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs`
+
+- [ ] Step 1: 鍒犻櫎杩佺Щ鍚庣鏈夋柟娉�
+- [ ] Step 2: 淇濈暀鍏抽敭娴佺▼娉ㄩ噴锛岃鏄庤涓轰繚鎸佺偣
+
+### Task 4: 缂栬瘧楠屾敹
+
+**Files:**
+- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/*.cs`锛堝闇�寰皟锛�
+
+- [ ] Step 1: 杩愯 `dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj -c Debug`
+- [ ] Step 2: 鏈�灏忎慨澶嶇洿鍒伴�氳繃
diff --git a/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-formation-stacker-crane-job-refactor-plan.md b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-formation-stacker-crane-job-refactor-plan.md
new file mode 100644
index 0000000..bbefbaf
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-formation-stacker-crane-job-refactor-plan.md
@@ -0,0 +1,49 @@
+锘�# FormationCommonStackerCraneJob Refactor Implementation Plan
+
+> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 鍦ㄤ笉鏀瑰彉涓氬姟琛屼负鐨勫墠鎻愪笅锛岄檷浣� `FormationCommonStackerCraneJob` 澶嶆潅搴︼紝鎷嗗垎鈥滀换鍔¢�夋嫨鈥濆拰鈥滃懡浠ゆ瀯寤衡�濊亴璐c��
+
+**Architecture:** 淇濈暀 `FormationCommonStackerCraneJob` 浣滀负 Quartz Job 鍏ュ彛锛屼粎璐熻矗璋冨害锛涘皢浠诲姟閫夋嫨鍜屽懡浠ゆ瀯寤轰笅娌夊埌鍚岀洰褰曟柊绫伙紝閫氳繃鏋勯�犲嚱鏁扮粍瑁呬緷璧栵紝鏀瑰姩闄愬畾鍦� `FormationStackerCraneJob` 鐩綍銆�
+
+**Tech Stack:** .NET 6, Quartz, 鐜版湁 WCS Task/Router 鏈嶅姟鎺ュ彛
+
+---
+
+## Chunk 1: 缁撴瀯鎷嗗垎
+
+### Task 1: 鎷嗗垎浠诲姟閫夋嫨鍣�
+
+**Files:**
+- Create: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs`
+- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs`
+
+- [ ] Step 1: 杩佺Щ浠诲姟鎸戦�夊拰绔欏彴鍙敤鎬ч�昏緫
+- [ ] Step 2: Job 涓敼涓鸿皟鐢ㄤ换鍔¢�夋嫨鍣�
+
+### Task 2: 鎷嗗垎鍛戒护鏋勫缓鍣�
+
+**Files:**
+- Create: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs`
+- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs`
+
+- [ ] Step 1: 杩佺Щ鍛戒护杞崲涓庡湴鍧�瑙f瀽閫昏緫
+- [ ] Step 2: Job 涓敼涓鸿皟鐢ㄥ懡浠ゆ瀯寤哄櫒
+
+## Chunk 2: 娓呯悊涓庨獙璇�
+
+### Task 3: Job 鍏ュ彛鐦﹁韩
+
+**Files:**
+- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs`
+
+- [ ] Step 1: 鍒犻櫎杩佺Щ鍚庣殑绉佹湁鏂规硶鍜屾棤鐢� using
+- [ ] Step 2: 琛ュ叧閿祦绋嬫敞閲�
+
+### Task 4: 缂栬瘧楠屾敹
+
+**Files:**
+- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/*.cs`锛堝闇�寰皟锛�
+
+- [ ] Step 1: 杩愯 `dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj -c Debug`
+- [ ] Step 2: 鏈�灏忎慨澶嶇洿鍒伴�氳繃
--
Gitblit v1.9.3