| | |
| | | using System; |
| | | using System; |
| | | using System.Diagnostics.CodeAnalysis; |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEAWCS_ITaskInfoService; |
| | |
| | | namespace WIDESEAWCS_Tasks |
| | | { |
| | | /// <summary> |
| | | /// 堆垛机命令构建器:封装任务到命令对象的转换与地址解析。 |
| | | /// 堆垛机命令构建器 - 封装任务到命令对象的转换与地址解析 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 核心职责: |
| | | /// 1. 根据巷道类型选择命令格式(标准命令/成型命令) |
| | | /// 2. 构建入库、出库、移库任务的命令对象 |
| | | /// 3. 解析任务地址(行-列-层格式)到命令坐标 |
| | | /// 4. 查询路由信息,确定堆垛机的取货/放货站台 |
| | | /// |
| | | /// 地址格式约定:地址字符串格式为 "行-列-层",例如 "1-2-3" 表示第1行、第2列、第3层。 |
| | | /// </remarks> |
| | | public class StackerCraneCommandBuilder |
| | | { |
| | | /// <summary> |
| | | /// 任务服务 |
| | | /// </summary> |
| | | private readonly ITaskService _taskService; |
| | | |
| | | /// <summary> |
| | | /// 路由服务 |
| | | /// </summary> |
| | | private readonly IRouterService _routerService; |
| | | |
| | | /// <summary> |
| | | /// 堆垛机命令配置 |
| | | /// </summary> |
| | | private readonly StackerCraneCommandConfig _config; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <param name="taskService">任务服务</param> |
| | | /// <param name="routerService">路由服务</param> |
| | | /// <param name="config">命令配置</param> |
| | | public StackerCraneCommandBuilder( |
| | | ITaskService taskService, |
| | | IRouterService routerService, |
| | |
| | | _config = config; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 将任务转换为堆垛机命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据巷道类型选择不同的命令构建策略。 |
| | | /// </remarks> |
| | | /// <param name="task">任务对象</param> |
| | | /// <returns>堆垛机命令对象,转换失败返回 null</returns> |
| | | public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task) |
| | | { |
| | | // 根据巷道获取命令类型 |
| | | string commandType = GetCommandType(task.Roadway); |
| | | |
| | | // 根据命令类型调用相应的构建方法 |
| | | return commandType switch |
| | | { |
| | | "Formation" => BuildCommand(task, CreateFormationCommand(task)), |
| | | _ => BuildCommand(task, CreateStandardCommand(task)) |
| | | "Formation" => BuildCommand(task, CreateFormationCommand(task)), // 成型命令 |
| | | _ => BuildCommand(task, CreateStandardCommand(task)) // 标准命令 |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据巷道获取命令类型 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 遍历配置中的映射关系,找到匹配的命令类型。 |
| | | /// 如果不匹配任何映射,返回默认命令类型。 |
| | | /// </remarks> |
| | | /// <param name="roadway">巷道编码</param> |
| | | /// <returns>命令类型(Standard 或 Formation)</returns> |
| | | private string GetCommandType(string roadway) |
| | | { |
| | | foreach (var mapping in _config.RoadwayCommandMapping) |
| | |
| | | return _config.DefaultCommandType; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 创建标准命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于标准堆垛机(GW、CW 开头巷道)。 |
| | | /// </remarks> |
| | | /// <param name="task">任务对象</param> |
| | | /// <returns>标准命令对象</returns> |
| | | private static StackerCraneTaskCommand CreateStandardCommand(Dt_Task task) |
| | | { |
| | | return new StackerCraneTaskCommand |
| | | { |
| | | TaskNum = task.TaskNum, |
| | | WorkType = 1, |
| | | WorkAction = 1 |
| | | TaskNum = task.TaskNum, // 任务号 |
| | | WorkType = 1, // 作业类型 |
| | | WorkAction = 1 // 作业指令:开始执行 |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 创建成型命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于成型堆垛机(HC 开头巷道)。 |
| | | /// 包含条码字段,用于电池追溯。 |
| | | /// </remarks> |
| | | /// <param name="task">任务对象</param> |
| | | /// <returns>成型命令对象</returns> |
| | | 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 |
| | | Barcode = task.PalletCode, // 托盘条码 |
| | | TaskNum = task.TaskNum, // 任务号 |
| | | WorkType = 1, // 作业类型 |
| | | WorkAction = 1, // 作业指令:开始执行 |
| | | FireAlarm = 0, // 火警:正常 |
| | | HeartBeat = 0, // 心跳 |
| | | FieldName = string.Empty // 保留字段 |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 构建命令(通用) |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据任务类型(入库/出库/移库)调用相应的构建方法。 |
| | | /// </remarks> |
| | | /// <typeparam name="T">命令类型</typeparam> |
| | | /// <param name="task">任务对象</param> |
| | | /// <param name="command">初始命令对象</param> |
| | | /// <returns>填充好的命令对象</returns> |
| | | 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 |
| | | TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command), // 入库 |
| | | TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command), // 出库 |
| | | TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command), // 移库 |
| | | _ => command // 未知类型,返回原命令 |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 构建入库命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 入库任务需要: |
| | | /// 1. 查询堆垛机取货站台(根据当前地址和任务类型) |
| | | /// 2. 设置起始坐标(来自站台) |
| | | /// 3. 解析目标地址,设置终点坐标 |
| | | /// </remarks> |
| | | /// <typeparam name="T">命令类型</typeparam> |
| | | /// <param name="task">任务对象</param> |
| | | /// <param name="command">命令对象</param> |
| | | /// <returns>填充好的命令对象</returns> |
| | | private T? BuildInboundCommand<T>(Dt_Task task, T command) where T : class |
| | | { |
| | | Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType); |
| | | // 确定任务类型(空托盘用特殊类型 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) |
| | | { |
| | | // 未找到站台,更新异常信息 |
| | | _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> |
| | | /// <remarks> |
| | | /// 出库任务需要: |
| | | /// 1. 查询堆垛机放货站台(根据目标地址和任务类型) |
| | | /// 2. 设置终点坐标(来自站台) |
| | | /// 3. 解析起始地址,设置起点坐标 |
| | | /// </remarks> |
| | | /// <typeparam name="T">命令类型</typeparam> |
| | | /// <param name="task">任务对象</param> |
| | | /// <param name="command">命令对象</param> |
| | | /// <returns>填充好的命令对象</returns> |
| | | private T? BuildOutboundCommand<T>(Dt_Task task, T command) where T : class |
| | | { |
| | | Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType); |
| | | // 确定任务类型 |
| | | 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) |
| | | { |
| | | _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> |
| | | /// <remarks> |
| | | /// 移库任务需要: |
| | | /// 1. 解析目标地址(新的库位) |
| | | /// 2. 解析起始地址(原来的库位) |
| | | /// 3. 设置起止坐标 |
| | | /// </remarks> |
| | | /// <typeparam name="T">命令类型</typeparam> |
| | | /// <param name="task">任务对象</param> |
| | | /// <param name="command">命令对象</param> |
| | | /// <returns>填充好的命令对象</returns> |
| | | 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> |
| | | /// <remarks> |
| | | /// 使用反射动态设置命令对象的属性值。 |
| | | /// </remarks> |
| | | /// <typeparam name="T">命令类型</typeparam> |
| | | /// <param name="command">命令对象</param> |
| | | /// <param name="propertyName">属性名称</param> |
| | | /// <param name="value">属性值</param> |
| | | 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> |
| | | /// 解析地址字符串 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 地址格式:行-列-层,例如 "1-2-3" 表示第1行、第2列、第3层。 |
| | | /// </remarks> |
| | | /// <param name="address">地址字符串</param> |
| | | /// <param name="row">解析出的行坐标</param> |
| | | /// <param name="column">解析出的列坐标</param> |
| | | /// <param name="layer">解析出的层坐标</param> |
| | | /// <returns>解析成功返回 true</returns> |
| | | private static bool TryParseAddress(string address, out short row, out short column, out short layer) |
| | | { |
| | | row = column = layer = 0; |
| | | |
| | | string[] parts = address.Split("-"); |
| | | // 按 "-" 分隔地址 |
| | | 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); |