| | |
| | | using Microsoft.Extensions.Logging; |
| | | using Microsoft.Extensions.Logging; |
| | | using System.Net; |
| | | using System.Net.Sockets; |
| | | using WIDESEAWCS_Common; |
| | | using WIDESEAWCS_Core.Caches; |
| | | using WIDESEAWCS_Core.LogHelper; |
| | | using WIDESEAWCS_Tasks.Workflow.Abstractions; |
| | | |
| | | namespace WIDESEAWCS_Tasks |
| | | { |
| | | /// <summary> |
| | | /// 机器人消息路由入口:负责缓存状态读取、命令分发和回包触发。 |
| | | /// 机器人消息处理器 - 消息路由入口 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 核心职责: |
| | | /// 1. 缓存状态读取:从 Redis 中获取机器人最新的状态 |
| | | /// 2. 命令分发:根据消息类型分发给不同的处理器 |
| | | /// - 简单命令(如 homing、running):由 <see cref="IRobotSimpleCommandHandler"/> 处理 |
| | | /// - 前缀命令(如 pickfinished、putfinished):由 <see cref="IRobotPrefixCommandHandler"/> 处理 |
| | | /// 3. 回包触发:将原始消息回写到客户端 |
| | | /// |
| | | /// 这是消息处理管道的入口点,由 TcpSocketServer 的 MessageReceived 事件触发。 |
| | | /// </remarks> |
| | | public class RobotMessageHandler : IRobotMessageRouter |
| | | { |
| | | /// <summary> |
| | | /// Socket 客户端网关接口 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于向客户端发送响应消息。 |
| | | /// </remarks> |
| | | private readonly ISocketClientGateway _socketClientGateway; |
| | | |
| | | /// <summary> |
| | | /// 机械手状态管理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于读取和更新机器人的状态。 |
| | | /// </remarks> |
| | | private readonly RobotStateManager _stateManager; |
| | | |
| | | /// <summary> |
| | | /// 缓存服务 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 直接使用缓存服务检查状态是否存在。 |
| | | /// </remarks> |
| | | private readonly ICacheService _cache; |
| | | |
| | | /// <summary> |
| | | /// 简单命令处理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 处理简单的状态更新命令,如运行状态、模式切换等。 |
| | | /// </remarks> |
| | | private readonly IRobotSimpleCommandHandler _simpleCommandHandler; |
| | | |
| | | /// <summary> |
| | | /// 前缀命令处理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 处理带参数的前缀命令,如 pickfinished(取货完成)、putfinished(放货完成)。 |
| | | /// </remarks> |
| | | private readonly IRobotPrefixCommandHandler _prefixCommandHandler; |
| | | |
| | | /// <summary> |
| | | /// 日志记录器 |
| | | /// </summary> |
| | | private readonly ILogger<RobotJob> _logger; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <param name="socketClientGateway">Socket 网关</param> |
| | | /// <param name="stateManager">状态管理器</param> |
| | | /// <param name="cache">缓存服务</param> |
| | | /// <param name="simpleCommandHandler">简单命令处理器</param> |
| | | /// <param name="prefixCommandHandler">前缀命令处理器</param> |
| | | /// <param name="logger">日志记录器</param> |
| | | public RobotMessageHandler( |
| | | ISocketClientGateway socketClientGateway, |
| | | RobotStateManager stateManager, |
| | |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理接收到的消息。保持原有行为:简单命令和前缀命令都回写原消息。 |
| | | /// 处理接收到的消息 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 处理流程: |
| | | /// 1. 记录日志(记录原始消息内容) |
| | | /// 2. 验证缓存中是否存在该设备的状态 |
| | | /// 3. 尝试用简单命令处理器处理(状态更新类命令) |
| | | /// - 如果处理成功,回写原消息并更新状态 |
| | | /// 4. 如果不是简单命令,检查是否是前缀命令(pickfinished/putfinished) |
| | | /// - 如果是,调用前缀命令处理器处理 |
| | | /// 5. 保持原有行为:简单命令和前缀命令都回写原消息 |
| | | /// |
| | | /// 注意:此方法可能在 TCP 消息接收的上下文中被频繁调用,需注意性能。 |
| | | /// </remarks> |
| | | /// <param name="message">原始消息字符串</param> |
| | | /// <param name="isJson">消息是否为 JSON 格式(当前未使用)</param> |
| | | /// <param name="client">TCP 客户端连接</param> |
| | | /// <param name="state">机器人当前状态</param> |
| | | /// <returns>响应消息,如果无需回复则返回 null</returns> |
| | | public async Task<string?> HandleMessageReceivedAsync(string message, bool isJson, TcpClient client, RobotSocketState state) |
| | | { |
| | | // 记录接收到的消息日志 |
| | | _logger.LogInformation($"接收到客户端【{state.RobotCrane.DeviceName}】发送消息【{message}】"); |
| | | QuartzLogger.Error($"接收到客户端消息【{message}】", state.RobotCrane.DeviceName); |
| | | |
| | | // 构建缓存键,检查 Redis 中是否存在该设备的状态 |
| | | var cacheKey = $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}"; |
| | | |
| | | // 如果缓存中不存在或状态为 null,忽略此消息 |
| | | if (!_cache.TryGetValue(cacheKey, out RobotSocketState? cachedState) || cachedState == null) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | // 使用缓存中获取的状态 |
| | | var activeState = cachedState; |
| | | |
| | | // 将消息转换为小写(用于简单命令匹配) |
| | | string messageLower = message.ToLowerInvariant(); |
| | | |
| | | // 尝试用简单命令处理器处理 |
| | | // 简单命令包括:homing、homed、running、pausing、runmode、controlmode 等 |
| | | if (await _simpleCommandHandler.HandleAsync(messageLower, activeState)) |
| | | { |
| | | // 处理成功后,将原消息回写到客户端(保持原有行为) |
| | | await _socketClientGateway.SendMessageAsync(client, message); |
| | | _logger.LogInformation($"发送消息【{message}】"); |
| | | QuartzLogger.Error($"发送消息:【{message}】", state.RobotCrane.DeviceName); |
| | | |
| | | // 安全更新状态到 Redis |
| | | _stateManager.TryUpdateStateSafely(activeState.IPAddress, activeState); |
| | | return null; |
| | | } |
| | | |
| | | // 如果不是简单命令,检查是否是前缀命令 |
| | | // 前缀命令包括:pickfinished、putfinished(后面跟逗号分隔的位置参数) |
| | | if (_prefixCommandHandler.IsPrefixCommand(messageLower)) |
| | | { |
| | | // 调用前缀命令处理器 |
| | | // 前缀命令处理器会解析位置参数并更新状态 |
| | | await _prefixCommandHandler.HandleAsync(message, activeState, client); |
| | | } |
| | | |
| | | // 默认返回 null,不产生响应消息 |
| | | return null; |
| | | } |
| | | } |