| | |
| | | using Microsoft.Extensions.Logging; |
| | | using WIDESEA_Core; |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEAWCS_Core.Helper; |
| | | using WIDESEAWCS_Core.LogHelper; |
| | | using WIDESEAWCS_ITaskInfoService; |
| | | using WIDESEAWCS_Model.Models; |
| | | using WIDESEAWCS_Tasks.Workflow.Abstractions; |
| | |
| | | namespace WIDESEAWCS_Tasks.Workflow |
| | | { |
| | | /// <summary> |
| | | /// RobotJob 流程编排器:迁移原 RobotJob 状态机分支,降低 Job 类复杂度。 |
| | | /// 机器人任务编排器 - 负责 RobotJob 中的状态机流转和执行步骤编排 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 迁移原 RobotJob 状态机流转支持,降低 Job 层的复杂度。 |
| | | /// |
| | | /// 核心职责: |
| | | /// 1. 根据机器人当前状态和任务状态,决定下一步动作 |
| | | /// 2. 处理取货完成后的放货指令下发 |
| | | /// 3. 处理放货完成后的取货指令下发(组盘场景) |
| | | /// |
| | | /// 状态机流转规则: |
| | | /// - 条件:RobotRunMode == 2(自动模式)且 RobotControlMode == 1(客户端控制)且 OperStatus != "Running" |
| | | /// - 取货完成 -> 放货:PickFinished + RobotArmObject == 1 + RobotPickFinish -> 发送 Putbattery |
| | | /// - 放货完成 -> 取货:PutFinished + Homed + RobotArmObject == 0 -> 发送 Pickbattery(组盘/换盘场景) |
| | | /// </remarks> |
| | | public class RobotWorkflowOrchestrator : IRobotWorkflowOrchestrator |
| | | { |
| | | /// <summary> |
| | | /// 机械手状态管理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于读取和更新机器人的状态。 |
| | | /// </remarks> |
| | | private readonly RobotStateManager _stateManager; |
| | | |
| | | /// <summary> |
| | | /// 机械手客户端管理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于向机器人客户端发送指令。 |
| | | /// </remarks> |
| | | private readonly RobotClientManager _clientManager; |
| | | |
| | | /// <summary> |
| | | /// 任务处理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于执行任务相关的业务逻辑,如发送取货指令。 |
| | | /// </remarks> |
| | | private readonly RobotTaskProcessor _taskProcessor; |
| | | |
| | | /// <summary> |
| | | /// 机器人任务服务 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于更新任务状态。 |
| | | /// </remarks> |
| | | private readonly IRobotTaskService _robotTaskService; |
| | | |
| | | /// <summary> |
| | | /// 日志记录器 |
| | | /// </summary> |
| | | private readonly ILogger _logger; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <param name="stateManager">状态管理器</param> |
| | | /// <param name="clientManager">客户端管理器</param> |
| | | /// <param name="taskProcessor">任务处理器</param> |
| | | /// <param name="robotTaskService">任务服务</param> |
| | | /// <param name="logger">日志记录器</param> |
| | | public RobotWorkflowOrchestrator( |
| | | RobotStateManager stateManager, |
| | | RobotClientManager clientManager, |
| | | RobotTaskProcessor taskProcessor, |
| | | IRobotTaskService robotTaskService) |
| | | IRobotTaskService robotTaskService, |
| | | ILogger logger) |
| | | { |
| | | _stateManager = stateManager; |
| | | _clientManager = clientManager; |
| | | _taskProcessor = taskProcessor; |
| | | _robotTaskService = robotTaskService; |
| | | _logger = logger; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 执行任务编排流程 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据机器人当前状态和任务状态,决定是否下发指令。 |
| | | /// |
| | | /// 执行条件: |
| | | /// 1. 机器人处于自动模式(RobotRunMode == 2) |
| | | /// 2. 机器人处于客户端控制模式(RobotControlMode == 1) |
| | | /// 3. 机器人不在运行中(OperStatus != "Running") |
| | | /// |
| | | /// 流转逻辑: |
| | | /// - 取货完成且手臂有货 -> 发送放货指令(Putbattery) |
| | | /// - 放货完成且手臂无货 -> 发送取货指令(Pickbattery) |
| | | /// </remarks> |
| | | /// <param name="latestState">机器人最新状态</param> |
| | | /// <param name="task">待执行的机器人任务</param> |
| | | /// <param name="ipAddress">机器人 IP 地址</param> |
| | | public async Task ExecuteAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress) |
| | | { |
| | | // 保持原有分支判定条件不变,确保行为一致。 |
| | | if (latestState.RobotRunMode == 2 && latestState.RobotControlMode == 1 && latestState.OperStatus != "Running") |
| | | // 按原方案分支判断,确保逻辑一致 |
| | | // 检查是否满足自动控制条件: |
| | | // 1. 运行模式为自动(2) |
| | | // 2. 控制模式为客户端控制(1) |
| | | // 3. 运行状态是 Running |
| | | if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running" && latestState.Homed == "Homed") |
| | | { |
| | | // ========== 取货完成后的放货处理 ========== |
| | | // 条件: |
| | | // - 当前动作是 PickFinished 或 AllPickFinished(取货完成) |
| | | // - 手臂上有物料(RobotArmObject == 1) |
| | | // - 任务状态为 RobotPickFinish(已记录取货完成) |
| | | if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished") |
| | | && latestState.RobotArmObject == 1 |
| | | && latestState.RobotArmObject == 0 |
| | | && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode()) |
| | | { |
| | | _logger.LogInformation("ExecuteAsync:满足放货条件,开始处理取货完成,任务号: {TaskNum}", task.RobotTaskNum); |
| | | QuartzLogger.Info($"ExecuteAsync:满足放货条件,开始处理取货完成", latestState.RobotCrane?.DeviceName ?? ipAddress); |
| | | // 发送放货指令 |
| | | await HandlePickFinishedStateAsync(task, ipAddress); |
| | | } |
| | | else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction == null) |
| | | && latestState.OperStatus == "Homed" |
| | | |
| | | // ========== 初始化或者放货完成后的取货处理 ========== |
| | | // 条件: |
| | | // - 当前动作是 PutFinished、AllPutFinished 或 null(放货完成) |
| | | // - 运行状态为 Homed(已归位) |
| | | // - 手臂上无物料(RobotArmObject == 0) |
| | | // - 任务状态为 RobotPutFinish 或不是 RobotExecuting |
| | | else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction.IsNullOrEmpty()) |
| | | && latestState.RobotArmObject == 0 |
| | | && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode() |
| | | || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode())) |
| | | { |
| | | _logger.LogInformation("ExecuteAsync:满足取货条件,开始处理放货完成,任务号: {TaskNum}", task.RobotTaskNum); |
| | | QuartzLogger.Info($"ExecuteAsync:满足取货条件,开始处理放货完成", latestState.RobotCrane?.DeviceName ?? ipAddress); |
| | | // 发送取货指令 |
| | | await HandlePutFinishedStateAsync(task, ipAddress); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理取货完成后的放货指令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当取货完成后,向机器人发送放货指令(Putbattery)。 |
| | | /// 机器人收到指令后会将货物放置到目标地址。 |
| | | /// |
| | | /// 指令格式:Putbattery,{目标地址} |
| | | /// 例如:Putbattery,B01 表示将货物放置到 B01 位置 |
| | | /// </remarks> |
| | | /// <param name="task">当前任务</param> |
| | | /// <param name="ipAddress">机器人 IP 地址</param> |
| | | private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress) |
| | | { |
| | | // 构建放货指令,格式:Putbattery,{目标地址} |
| | | string taskString = $"Putbattery,{task.RobotTargetAddress}"; |
| | | |
| | | // 通过客户端管理器发送指令到机器人 |
| | | bool result = await _clientManager.SendToClientAsync(ipAddress, taskString); |
| | | |
| | | if (result) |
| | | { |
| | | // 发送成功,记录 Info 日志 |
| | | _logger.LogInformation("HandlePickFinishedStateAsync:下发放货指令成功,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum); |
| | | QuartzLogger.Info($"下发放货指令成功,指令: {taskString}", task.RobotRoadway); |
| | | |
| | | // 更新任务状态为"机器人执行中" |
| | | task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode(); |
| | | |
| | | // 获取最新状态并更新任务关联 |
| | | var stateToUpdate = _stateManager.GetState(ipAddress); |
| | | if (stateToUpdate != null) |
| | | { |
| | | stateToUpdate.CurrentTask = task; |
| | | |
| | | // 安全更新状态到 Redis |
| | | if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate)) |
| | | { |
| | | // 状态更新成功后,更新任务记录 |
| | | await _robotTaskService.UpdateRobotTaskAsync(task); |
| | | } |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // 发送失败,记录 Error 日志 |
| | | _logger.LogError("HandlePickFinishedStateAsync:下发放货指令失败,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum); |
| | | QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", task.RobotRoadway); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理放货完成后的取货指令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当放货完成后,根据任务类型决定下一步: |
| | | /// |
| | | /// 1. 如果不是拆盘也不是组盘(普通任务): |
| | | /// - 直接发送取货指令 |
| | | /// |
| | | /// 2. 如果是组盘或换盘任务: |
| | | /// - 生成新的托盘条码 |
| | | /// - 将条码添加到状态中 |
| | | /// - 发送取货指令 |
| | | /// |
| | | /// 组盘任务的条码用于标识新生成的托盘, |
| | | /// 后续放货时会用到这些条码信息。 |
| | | /// </remarks> |
| | | /// <param name="task">当前任务</param> |
| | | /// <param name="ipAddress">机器人 IP 地址</param> |
| | | private async Task HandlePutFinishedStateAsync(Dt_RobotTask task, string ipAddress) |
| | | { |
| | | // 获取最新状态 |
| | | var stateForUpdate = _stateManager.GetState(ipAddress); |
| | | if (stateForUpdate == null) |
| | | { |
| | | _logger.LogWarning("HandlePutFinishedStateAsync:获取状态失败,IP: {IpAddress}", ipAddress); |
| | | QuartzLogger.Warn($"HandlePutFinishedStateAsync:获取状态失败,IP: {ipAddress}", ipAddress); |
| | | 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); |
| | | if(stateForUpdate.CellBarcode.Contains(trayBarcode1)|| stateForUpdate.CellBarcode.Contains(trayBarcode2)) |
| | | { |
| | | _logger.LogError("HandlePutFinishedStateAsync:生成的托盘条码已存在,可能存在重复,任务号: {TaskNum}", task.RobotTaskNum); |
| | | QuartzLogger.Error($"生成的托盘条码已存在,可能存在重复", stateForUpdate.RobotCrane.DeviceName); |
| | | |
| | | // 条码重复,记录错误日志并停止后续操作(后续放货时会用到这些条码信息,供后续放货时使用,调试后可能会取消此逻辑) |
| | | return; |
| | | } |
| | | else |
| | | { |
| | | _logger.LogInformation("HandlePutFinishedStateAsync:生成的托盘条码唯一,继续执行,任务号: {TaskNum}", task.RobotTaskNum); |
| | | QuartzLogger.Info($"生成的托盘条码唯一,继续执行", stateForUpdate.RobotCrane.DeviceName); |
| | | // 将条码添加到状态中,供后续放货时使用 |
| | | stateForUpdate.CellBarcode.Add(trayBarcode1); |
| | | stateForUpdate.CellBarcode.Add(trayBarcode2); |
| | | } |
| | | |
| | | |
| | | // 记录日志:生成托盘条码成功 |
| | | _logger.LogInformation("HandlePutFinishedStateAsync:生成托盘条码成功: {Barcode1}+{Barcode2},任务号: {TaskNum}", trayBarcode1, trayBarcode2, task.RobotTaskNum); |
| | | QuartzLogger.Info($"生成托盘条码成功: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName); |
| | | |
| | | // 发送取货指令 |
| | | await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); |
| | | } |
| | | else |
| | | { |
| | | // 条码生成失败,记录错误日志 |
| | | _logger.LogError("HandlePutFinishedStateAsync:生成托盘条码失败,任务号: {TaskNum}", task.RobotTaskNum); |
| | | QuartzLogger.Error($"生成托盘条码失败", stateForUpdate.RobotCrane.DeviceName); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // 非组盘任务,直接发送取货指令 |
| | | await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |