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
{
///
/// 机器人任务编排器 - 负责 RobotJob 中的状态机流转和执行步骤编排
///
///
/// 迁移原 RobotJob 状态机流转支持,降低 Job 层的复杂度。
///
/// 核心职责:
/// 1. 根据机器人当前状态和任务状态,决定下一步动作
/// 2. 处理取货完成后的放货指令下发
/// 3. 处理放货完成后的取货指令下发(组盘场景)
///
/// 状态机流转规则:
/// - 条件:RobotRunMode == 2(自动模式)且 RobotControlMode == 1(客户端控制)且 OperStatus != "Running"
/// - 取货完成 -> 放货:PickFinished + RobotArmObject == 1 + RobotPickFinish -> 发送 Putbattery
/// - 放货完成 -> 取货:PutFinished + Homed + RobotArmObject == 0 -> 发送 Pickbattery(组盘/换盘场景)
///
public class RobotWorkflowOrchestrator : IRobotWorkflowOrchestrator
{
///
/// 机械手状态管理器
///
///
/// 用于读取和更新机器人的状态。
///
private readonly RobotStateManager _stateManager;
///
/// 机械手客户端管理器
///
///
/// 用于向机器人客户端发送指令。
///
private readonly RobotClientManager _clientManager;
///
/// 任务处理器
///
///
/// 用于执行任务相关的业务逻辑,如发送取货指令。
///
private readonly RobotTaskProcessor _taskProcessor;
///
/// 机器人任务服务
///
///
/// 用于更新任务状态。
///
private readonly IRobotTaskService _robotTaskService;
///
/// 日志记录器
///
private readonly ILogger _logger;
///
/// 构造函数
///
/// 状态管理器
/// 客户端管理器
/// 任务处理器
/// 任务服务
/// 日志记录器
public RobotWorkflowOrchestrator(
RobotStateManager stateManager,
RobotClientManager clientManager,
RobotTaskProcessor taskProcessor,
IRobotTaskService robotTaskService,
ILogger logger)
{
_stateManager = stateManager;
_clientManager = clientManager;
_taskProcessor = taskProcessor;
_robotTaskService = robotTaskService;
_logger = logger;
}
///
/// 执行任务编排流程
///
///
/// 根据机器人当前状态和任务状态,决定是否下发指令。
///
/// 执行条件:
/// 1. 机器人处于自动模式(RobotRunMode == 2)
/// 2. 机器人处于客户端控制模式(RobotControlMode == 1)
/// 3. 机器人不在运行中(OperStatus != "Running")
///
/// 流转逻辑:
/// - 取货完成且手臂有货 -> 发送放货指令(Putbattery)
/// - 放货完成且手臂无货 -> 发送取货指令(Pickbattery)
///
/// 机器人最新状态
/// 待执行的机器人任务
/// 机器人 IP 地址
public async Task ExecuteAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
{
// 按原方案分支判断,确保逻辑一致
// 检查是否满足自动控制条件:
// 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
&& task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
{
_logger.LogInformation("ExecuteAsync:满足放货条件,开始处理取货完成,任务号: {TaskNum}", task.RobotTaskNum);
QuartzLogger.Info($"ExecuteAsync:满足放货条件,开始处理取货完成", latestState.RobotCrane?.DeviceName ?? ipAddress);
// 发送放货指令
await HandlePickFinishedStateAsync(task, ipAddress);
}
// ========== 初始化或者放货完成后的取货处理 ==========
// 条件:
// - 当前动作是 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);
}
}
}
///
/// 处理取货完成后的放货指令
///
///
/// 当取货完成后,向机器人发送放货指令(Putbattery)。
/// 机器人收到指令后会将货物放置到目标地址。
///
/// 指令格式:Putbattery,{目标地址}
/// 例如:Putbattery,B01 表示将货物放置到 B01 位置
///
/// 当前任务
/// 机器人 IP 地址
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);
}
}
///
/// 处理放货完成后的取货指令
///
///
/// 当放货完成后,根据任务类型决定下一步:
///
/// 1. 如果不是拆盘也不是组盘(普通任务):
/// - 直接发送取货指令
///
/// 2. 如果是组盘或换盘任务:
/// - 生成新的托盘条码
/// - 将条码添加到状态中
/// - 发送取货指令
///
/// 组盘任务的条码用于标识新生成的托盘,
/// 后续放货时会用到这些条码信息。
///
/// 当前任务
/// 机器人 IP 地址
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))
{
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);
}
}
}
}