| | |
| | | using System.Net.Sockets; |
| | | using System.Net.Sockets; |
| | | using WIDESEAWCS_Common.HttpEnum; |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEAWCS_DTO.TaskInfo; |
| | |
| | | namespace WIDESEAWCS_Tasks.Workflow |
| | | { |
| | | /// <summary> |
| | | /// 前缀命令处理:迁移原 RobotMessageHandler 的 pickfinished/putfinished 分支。 |
| | | /// 前缀命令处理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 迁移原 RobotMessageHandler 的 pickfinished/putfinished 分支。 |
| | | /// |
| | | /// 前缀命令是指以特定前缀开头的命令,后面跟随逗号分隔的参数。 |
| | | /// 格式:{前缀},{参数1},{参数2},... |
| | | /// |
| | | /// 当前支持的前缀命令: |
| | | /// - pickfinished: 取货完成,后面跟随完成的位置编号列表 |
| | | /// - putfinished: 放货完成,后面跟随完成的位置编号列表 |
| | | /// |
| | | /// 这些命令通常包含取货或放货的位置信息,需要解析并更新状态。 |
| | | /// </remarks> |
| | | public class RobotPrefixCommandHandler : IRobotPrefixCommandHandler |
| | | { |
| | | /// <summary> |
| | | /// 机器人任务服务 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于查询和更新任务记录。 |
| | | /// </remarks> |
| | | private readonly IRobotTaskService _robotTaskService; |
| | | |
| | | /// <summary> |
| | | /// 任务处理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于处理取货/放货完成时的业务逻辑,如调用拆盘/组盘 API。 |
| | | /// </remarks> |
| | | private readonly RobotTaskProcessor _taskProcessor; |
| | | |
| | | /// <summary> |
| | | /// 状态管理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于安全更新机器人的状态。 |
| | | /// </remarks> |
| | | private readonly RobotStateManager _stateManager; |
| | | |
| | | /// <summary> |
| | | /// Socket 网关 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于向客户端发送响应消息。 |
| | | /// </remarks> |
| | | private readonly ISocketClientGateway _socketClientGateway; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <param name="robotTaskService">任务服务</param> |
| | | /// <param name="taskProcessor">任务处理器</param> |
| | | /// <param name="stateManager">状态管理器</param> |
| | | /// <param name="socketClientGateway">Socket 网关</param> |
| | | public RobotPrefixCommandHandler( |
| | | IRobotTaskService robotTaskService, |
| | | RobotTaskProcessor taskProcessor, |
| | |
| | | _socketClientGateway = socketClientGateway; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查消息是否为前缀命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 前缀命令必须以 "pickfinished" 或 "putfinished" 开头(不区分大小写)。 |
| | | /// </remarks> |
| | | /// <param name="message">消息内容(小写形式)</param> |
| | | /// <returns>如果是指缀命令返回 true</returns> |
| | | public bool IsPrefixCommand(string message) |
| | | { |
| | | // 检查消息是否以 pickfinished 或 putfinished 开头 |
| | | return message.StartsWith("pickfinished") || message.StartsWith("putfinished"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理前缀命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 处理流程: |
| | | /// 1. 解析消息,提取位置参数 |
| | | /// 2. 查询当前任务 |
| | | /// 3. 根据命令类型调用相应的处理方法 |
| | | /// 4. 回写原消息到客户端 |
| | | /// |
| | | /// 消息格式:{命令前缀},{位置1},{位置2},... |
| | | /// 示例:pickfinished,1,2,3 表示取货完成,位置 1、2、3 的货物已取走 |
| | | /// </remarks> |
| | | /// <param name="message">原始消息内容</param> |
| | | /// <param name="state">机器人当前状态</param> |
| | | /// <param name="client">TCP 客户端连接,用于发送响应</param> |
| | | public async Task HandleAsync(string message, RobotSocketState state, TcpClient client) |
| | | { |
| | | try |
| | | { |
| | | // 按逗号分隔消息,提取命令和参数 |
| | | // 例如:pickfinished,1,2,3 -> ["pickfinished", "1", "2", "3"] |
| | | var parts = message.Split(','); |
| | | |
| | | // 检查消息格式是否有效:至少要有命令前缀,且状态中有当前任务 |
| | | if (parts.Length < 1 || state.CurrentTask == null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 提取命令前缀并转换为小写 |
| | | var cmd = parts[0].ToLowerInvariant(); |
| | | |
| | | // 解析位置参数(跳过命令前缀,处理后面的数字) |
| | | // 过滤掉无法解析为数字或值为 0 的位置 |
| | | 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) |
| | | .Select(p => int.TryParse(p, out int value) ? value : (int?)null) // 尝试解析为整数 |
| | | .Where(v => v.HasValue && v.Value != 0) // 过滤掉 null 和 0 |
| | | .Select(v => v!.Value) // 提取值(已知非 null) |
| | | .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}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理取货完成(pickfinished)命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 处理逻辑: |
| | | /// 1. 如果是拆盘任务,构建库存 DTO 并调用拆盘 API |
| | | /// 2. 更新当前动作为"取货完成" |
| | | /// 3. 记录取货完成的位置 |
| | | /// 4. 更新任务状态为"机器人取货完成" |
| | | /// 5. 安全更新状态到 Redis |
| | | /// </remarks> |
| | | /// <param name="state">机器人当前状态</param> |
| | | /// <param name="positions">取货完成的位置编号数组</param> |
| | | /// <param name="task">机器人任务记录</param> |
| | | private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task) |
| | | { |
| | | // 如果是拆盘任务 |
| | | if (state.IsSplitPallet) |
| | | { |
| | | // 构建库存 DTO,包含位置信息和托盘条码 |
| | | var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); |
| | | |
| | | // 记录取货完成的位置 |
| | | state.LastPickPositions = positions; |
| | | |
| | | // 调用拆盘 API |
| | | var result = _taskProcessor.PostSplitPalletAsync(stockDTO); |
| | | |
| | | // 如果 API 调用成功 |
| | | if (result.Data.Status && result.IsSuccess) |
| | | { |
| | | // 更新当前动作为"取货完成" |
| | | state.CurrentAction = "PickFinished"; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // 非拆盘任务,直接更新动作 |
| | | state.CurrentAction = "PickFinished"; |
| | | } |
| | | |
| | | // 记录取货完成的位置(无论是否拆盘都记录) |
| | | state.LastPickPositions = positions; |
| | | |
| | | // 如果任务存在 |
| | | if (task != null) |
| | | { |
| | | // 更新任务状态为"机器人取货完成" |
| | | task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode(); |
| | | |
| | | // 安全更新状态到 Redis,确保更新成功后再更新数据库 |
| | | if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) |
| | | { |
| | | await _robotTaskService.Repository.UpdateDataAsync(task); |
| | |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理放货完成(putfinished)命令 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 处理逻辑: |
| | | /// 1. 如果是组盘任务,构建库存 DTO 并调用组盘/换盘 API |
| | | /// 2. 如果组盘成功,增加任务计数 |
| | | /// 3. 更新当前动作为"放货完成" |
| | | /// 4. 更新任务状态为"机器人放货完成" |
| | | /// 5. 安全更新状态到 Redis |
| | | /// </remarks> |
| | | /// <param name="state">机器人当前状态</param> |
| | | /// <param name="positions">放货完成的位置编号数组</param> |
| | | /// <param name="task">机器人任务记录</param> |
| | | private async Task HandlePutFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task) |
| | | { |
| | | // 假设放货成功(如果后续 API 调用失败也不回退计数) |
| | | bool putSuccess = true; |
| | | |
| | | // 如果是组盘任务(包含换盘) |
| | | if (state.IsGroupPallet) |
| | | { |
| | | // 记录放货完成的位置 |
| | | state.LastPutPositions = positions; |
| | | |
| | | // 构建库存 DTO |
| | | var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); |
| | | |
| | | // 根据任务类型决定调用哪个 API |
| | | // 换盘任务调用 ChangePalletAsync,组盘任务调用 GroupPalletAsync |
| | | var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode() |
| | | ? nameof(ConfigKey.ChangePalletAsync) |
| | | : nameof(ConfigKey.GroupPalletAsync); |
| | | |
| | | // 调用组盘/换盘 API |
| | | var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO); |
| | | |
| | | // 检查 API 返回状态 |
| | | 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(); |
| | | |
| | | // 安全更新状态到 Redis |
| | | if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) |
| | | { |
| | | await _robotTaskService.Repository.UpdateDataAsync(task); |