using Microsoft.Extensions.Logging; using System; using System.Diagnostics.CodeAnalysis; using WIDESEAWCS_Common.TaskEnum; using WIDESEAWCS_Core.LogHelper; using WIDESEAWCS_ITaskInfoService; using WIDESEAWCS_Model.Models; using WIDESEAWCS_QuartzJob.Models; using WIDESEAWCS_QuartzJob.Service; using WIDESEAWCS_Tasks.StackerCraneJob; namespace WIDESEAWCS_Tasks { /// /// 堆垛机命令构建器 - 封装任务到命令对象的转换与地址解析 /// /// /// 核心职责: /// 1. 根据巷道类型选择命令格式(标准命令/成型命令) /// 2. 构建入库、出库、移库任务的命令对象 /// 3. 解析任务地址(行-列-层格式)到命令坐标 /// 4. 查询路由信息,确定堆垛机的取货/放货站台 /// /// 地址格式约定:地址字符串格式为 "行-列-层",例如 "1-2-3" 表示第1行、第2列、第3层。 /// public class StackerCraneCommandBuilder { /// /// 任务服务 /// private readonly ITaskService _taskService; /// /// 路由服务 /// private readonly IRouterService _routerService; /// /// 堆垛机命令配置 /// private readonly StackerCraneCommandConfig _config; /// /// 日志记录器 /// private readonly ILogger _logger; /// /// 构造函数 /// /// 任务服务 /// 路由服务 /// 命令配置 /// 日志记录器 public StackerCraneCommandBuilder( ITaskService taskService, IRouterService routerService, StackerCraneCommandConfig config, ILogger logger) { _taskService = taskService; _routerService = routerService; _config = config; _logger = logger; } /// /// 将任务转换为堆垛机命令 /// /// /// 根据巷道类型选择不同的命令构建策略。 /// /// 任务对象 /// 堆垛机命令对象,转换失败返回 null public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task) { // 根据巷道获取命令类型 string commandType = GetCommandType(task.Roadway); _logger.LogInformation("ConvertToStackerCraneTaskCommand:构建命令,任务号: {TaskNum},巷道: {Roadway},命令类型: {CommandType}", task.TaskNum, task.Roadway, commandType); QuartzLogger.Info($"构建命令,任务号: {task.TaskNum},巷道: {task.Roadway},命令类型: {commandType}", task.Roadway); // 根据命令类型调用相应的构建方法 return commandType switch { "Formation" => BuildCommand(task, CreateFormationCommand(task)), // 成型命令 _ => BuildCommand(task, CreateStandardCommand(task)) // 标准命令 }; } /// /// 根据巷道获取命令类型 /// /// /// 遍历配置中的映射关系,找到匹配的命令类型。 /// 如果不匹配任何映射,返回默认命令类型。 /// /// 巷道编码 /// 命令类型(Standard 或 Formation) private string GetCommandType(string roadway) { foreach (var mapping in _config.RoadwayCommandMapping) { if (roadway.Contains(mapping.Key)) { _logger.LogDebug("GetCommandType:匹配巷道 {Roadway},命令类型: {CommandType}", roadway, mapping.Value); QuartzLogger.Debug($"GetCommandType:匹配巷道 {roadway},命令类型: {mapping.Value}", roadway); return mapping.Value; } } _logger.LogDebug("GetCommandType:巷道 {Roadway} 未匹配,使用默认命令类型: {DefaultType}", roadway, _config.DefaultCommandType); QuartzLogger.Debug($"GetCommandType:巷道 {roadway} 未匹配,使用默认命令类型: {_config.DefaultCommandType}", roadway); return _config.DefaultCommandType; } /// /// 创建标准命令 /// /// /// 用于标准堆垛机(GW、CW 开头巷道)。 /// /// 任务对象 /// 标准命令对象 private static StackerCraneTaskCommand CreateStandardCommand(Dt_Task task) { return new StackerCraneTaskCommand { TaskNum = task.TaskNum, // 任务号 WorkType = 1, // 作业类型 WorkAction = 1 // 作业指令:开始执行 }; } /// /// 创建成型命令 /// /// /// 用于成型堆垛机(HC 开头巷道)。 /// 包含条码字段,用于电池追溯。 /// /// 任务对象 /// 成型命令对象 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(Dt_Task task, T command) where T : class { // 获取任务类型分组 TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup(); _logger.LogDebug("BuildCommand:任务号: {TaskNum},任务类型分组: {TaskTypeGroup}", task.TaskNum, taskTypeGroup); QuartzLogger.Debug($"BuildCommand:任务号: {task.TaskNum},任务类型分组: {taskTypeGroup}", task.Roadway); // 根据任务类型分发构建 return taskTypeGroup switch { TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command), // 入库 TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command), // 出库 TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command), // 移库 _ => command // 未知类型,返回原命令 }; } /// /// 构建入库命令 /// /// /// 入库任务需要: /// 1. 查询堆垛机取货站台(根据当前地址和任务类型) /// 2. 设置起始坐标(来自站台) /// 3. 解析目标地址,设置终点坐标 /// /// 命令类型 /// 任务对象 /// 命令对象 /// 填充好的命令对象 private T? BuildInboundCommand(Dt_Task task, T command) where T : class { _logger.LogInformation("BuildInboundCommand:构建入库命令,任务号: {TaskNum}", task.TaskNum); QuartzLogger.Info($"BuildInboundCommand:构建入库命令,任务号: {task.TaskNum}", task.Roadway); // 确定任务类型(空托盘用特殊类型 100) int taskType = 0; if (task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty) { taskType = 100; } else taskType = task.TaskType; // 查询堆垛机取货站台路由 Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, taskType); if (router == null) { // 未找到站台,更新异常信息 _logger.LogError("BuildInboundCommand:未找到站台【{CurrentAddress}】信息,任务号: {TaskNum}", task.CurrentAddress, task.TaskNum); QuartzLogger.Error($"BuildInboundCommand:未找到站台【{task.CurrentAddress}】信息", task.Roadway); _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)) { _logger.LogError("BuildInboundCommand:入库任务终点地址解析失败,终点: {NextAddress},任务号: {TaskNum}", task.NextAddress, task.TaskNum); QuartzLogger.Error($"BuildInboundCommand:入库任务终点地址解析失败,终点: {task.NextAddress}", task.Roadway); _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"入库任务终点错误,终点:【{task.NextAddress}】"); return null; } // 设置终点坐标 SetCommandProperty(command, "EndRow", endRow); SetCommandProperty(command, "EndColumn", endColumn); SetCommandProperty(command, "EndLayer", endLayer); _logger.LogInformation("BuildInboundCommand:入库命令构建成功,起点: {StartRow}-{StartColumn}-{StartLayer},终点: {EndRow}-{EndColumn}-{EndLayer},任务号: {TaskNum}", router.SrmRow, router.SrmColumn, router.SrmLayer, endRow, endColumn, endLayer, task.TaskNum); QuartzLogger.Info($"BuildInboundCommand:入库命令构建成功,起点: {router.SrmRow}-{router.SrmColumn}-{router.SrmLayer},终点: {endRow}-{endColumn}-{endLayer}", task.Roadway); return command; } /// /// 构建出库命令 /// /// /// 出库任务需要: /// 1. 查询堆垛机放货站台(根据目标地址和任务类型) /// 2. 设置终点坐标(来自站台) /// 3. 解析起始地址,设置起点坐标 /// /// 命令类型 /// 任务对象 /// 命令对象 /// 填充好的命令对象 private T? BuildOutboundCommand(Dt_Task task, T command) where T : class { _logger.LogInformation("BuildOutboundCommand:构建出库命令,任务号: {TaskNum}", task.TaskNum); QuartzLogger.Info($"BuildOutboundCommand:构建出库命令,任务号: {task.TaskNum}", task.Roadway); // 确定任务类型 int taskType = 0; if (task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty) { taskType = 100; } else taskType = task.TaskType; // 查询堆垛机放货站台路由 Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType); if (router == null) { _logger.LogError("BuildOutboundCommand:未找到站台【{TargetAddress}】信息,任务号: {TaskNum}", task.TargetAddress, task.TaskNum); QuartzLogger.Error($"BuildOutboundCommand:未找到站台【{task.TargetAddress}】信息", task.Roadway); _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)) { _logger.LogError("BuildOutboundCommand:出库任务起点地址解析失败,起点: {CurrentAddress},任务号: {TaskNum}", task.CurrentAddress, task.TaskNum); QuartzLogger.Error($"BuildOutboundCommand:出库任务起点地址解析失败,起点: {task.CurrentAddress}", task.Roadway); _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"出库任务起点错误,起点:【{task.CurrentAddress}】"); return null; } // 设置起点坐标 SetCommandProperty(command, "StartRow", startRow); SetCommandProperty(command, "StartColumn", startColumn); SetCommandProperty(command, "StartLayer", startLayer); _logger.LogInformation("BuildOutboundCommand:出库命令构建成功,起点: {StartRow}-{StartColumn}-{StartLayer},终点: {EndRow}-{EndColumn}-{EndLayer},任务号: {TaskNum}", startRow, startColumn, startLayer, router.SrmRow, router.SrmColumn, router.SrmLayer, task.TaskNum); QuartzLogger.Info($"BuildOutboundCommand:出库命令构建成功,起点: {startRow}-{startColumn}-{startLayer},终点: {router.SrmRow}-{router.SrmColumn}-{router.SrmLayer}", task.Roadway); return command; } /// /// 构建移库命令 /// /// /// 移库任务需要: /// 1. 解析目标地址(新的库位) /// 2. 解析起始地址(原来的库位) /// 3. 设置起止坐标 /// /// 命令类型 /// 任务对象 /// 命令对象 /// 填充好的命令对象 private T? BuildRelocationCommand(Dt_Task task, T command) where T : class { _logger.LogInformation("BuildRelocationCommand:构建移库命令,任务号: {TaskNum}", task.TaskNum); QuartzLogger.Info($"BuildRelocationCommand:构建移库命令,任务号: {task.TaskNum}", task.Roadway); // 解析目标地址 if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer)) { _logger.LogError("BuildRelocationCommand:移库任务终点地址解析失败,终点: {NextAddress},任务号: {TaskNum}", task.NextAddress, task.TaskNum); QuartzLogger.Error($"BuildRelocationCommand:移库任务终点地址解析失败,终点: {task.NextAddress}", task.Roadway); _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)) { _logger.LogError("BuildRelocationCommand:移库任务起点地址解析失败,起点: {CurrentAddress},任务号: {TaskNum}", task.CurrentAddress, task.TaskNum); QuartzLogger.Error($"BuildRelocationCommand:移库任务起点地址解析失败,起点: {task.CurrentAddress}", task.Roadway); _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务起点错误,起点:【{task.CurrentAddress}】"); return null; } // 设置起点坐标 SetCommandProperty(command, "StartRow", startRow); SetCommandProperty(command, "StartColumn", startColumn); SetCommandProperty(command, "StartLayer", startLayer); _logger.LogInformation("BuildRelocationCommand:移库命令构建成功,起点: {StartRow}-{StartColumn}-{StartLayer},终点: {EndRow}-{EndColumn}-{EndLayer},任务号: {TaskNum}", startRow, startColumn, startLayer, endRow, endColumn, endLayer, task.TaskNum); QuartzLogger.Info($"BuildRelocationCommand:移库命令构建成功,起点: {startRow}-{startColumn}-{startLayer},终点: {endRow}-{endColumn}-{endLayer}", task.Roadway); return command; } /// /// 设置命令属性值(通过反射) /// /// /// 使用反射动态设置命令对象的属性值。 /// /// 命令类型 /// 命令对象 /// 属性名称 /// 属性值 private static void SetCommandProperty(T command, string propertyName, object value) where T : class { var property = typeof(T).GetProperty(propertyName); property?.SetValue(command, value); } /// /// 解析地址字符串 /// /// /// 地址格式:行-列-层,例如 "1-2-3" 表示第1行、第2列、第3层。 /// /// 地址字符串 /// 解析出的行坐标 /// 解析出的列坐标 /// 解析出的层坐标 /// 解析成功返回 true 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; } // 解析各部分为 short 类型 return short.TryParse(parts[0], out row) && short.TryParse(parts[1], out column) && short.TryParse(parts[2], out layer); } } }