/** ****************************************************************************** * @file cmd_router.c * @brief 指令路由与响应分发模块实现 * @author Application Layer * @version 1.0 ****************************************************************************** * @attention * 本模块实现指令路由与响应分发功能 * 设计依据:多通信接口统一指令处理系统开发计划 第3.2节 * * 核心职责: * - 从各UART端口读取数据并喂入解析器 * - 根据指令来源端口路由响应 * - 管理响应路由表 * * 工作流程: * 1. 中断中将接收字节写入对应端口的环形缓冲区 * 2. 主循环中CmdRouter_Task轮询各端口缓冲区 * 3. 读取字节喂入CmdParser解析器 * 4. 解析完成后通过回调发送响应到源端口 ****************************************************************************** */ #include "cmd_router.h" #include "multi_uart_router.h" #include "cmd_parser.h" #include "uart2_print.h" #include "debug_log.h" #include "uart3_protocol_discriminator.h" #include "uart3_passthrough.h" #include "uart3_smart_router_config.h" #include /*============================================================================== * 调试宏定义 *============================================================================*/ /* DEBUG_CMD_ROUTER: 调试日志开关,置1时启用路由过程日志输出 */ #define DEBUG_CMD_ROUTER 1 #if DEBUG_CMD_ROUTER /* 路由模块日志宏,带模块前缀"[ROUTER]"方便过滤 */ #define ROUTER_LOG(fmt, ...) UART2_Print_Printf("[ROUTER] " fmt "\r\n", ##__VA_ARGS__) #else #define ROUTER_LOG(fmt, ...) #endif /*============================================================================== * 常量定义 *============================================================================*/ /** * @brief 接收日志缓冲区大小 * @note 用于暂存接收到的原始字节,便于调试跟踪 * 当超时或帧完成后统一打印到调试串口 */ #define RX_LOG_BUFFER_SIZE 128 /*============================================================================== * 数据结构定义 *============================================================================*/ /** * @brief 端口解析器状态结构 * @note 跟踪每个UART端口的解析状态和统计信息 * * 设计目的: * 每个UART端口都可能接收到指令,需要独立跟踪其接收状态、 * 最后接收时间、字节计数等,用于诊断和路由决策。 * * 字段说明: * - source_port: 源端口ID编号 * - last_feed_tick: 上次喂入数据的系统时间戳(毫秒) * - rx_count: 累计接收字节数 * - active: 端口是否处于活跃状态(接收到有效数据) */ typedef struct { uint8_t source_port; /**< 源端口ID编号 */ uint32_t last_feed_tick; /**< 上次喂入数据的时间戳 */ uint32_t rx_count; /**< 累计接收字节数 */ bool active; /**< 端口活跃状态标志 */ } port_parser_state_t; /*============================================================================== * 全局变量定义 *============================================================================*/ /** * @brief 各端口解析器状态表 * @note 静态数组,为PORT_COUNT个端口各自维护解析状态 */ static port_parser_state_t g_port_states[PORT_COUNT]; /** * @brief 响应处理器回调函数指针 * @note 当设置此回调时,路由响应会调用此处理器而非直接发送 * 可用于自定义响应处理逻辑(如添加时间戳、过滤等) */ static cmd_response_handler_t g_response_handler = NULL; /** * @brief 已处理指令帧累计计数 * @note 统计所有端口成功解析的指令帧总数 */ static uint32_t g_processed_count = 0; /** * @brief 已路由响应累计计数 * @note 统计成功发送的响应消息总数 */ static uint32_t g_routed_count = 0; /** * @brief 当前正在解析的端口ID * @note 用于在多端口环境下跟踪当前处理哪个端口的数据 */ static uint8_t g_current_parsing_port = PORT_UART2; /** * @brief 接收原始字节日志缓冲区 * @note 三维数组:为每个端口维护一个独立的日志缓冲区 * 便于调试时还原完整的接收字节序列 */ static uint8_t g_rx_log_buffer[PORT_COUNT][RX_LOG_BUFFER_SIZE]; /** * @brief 各端口日志缓冲区的当前写入长度 */ static uint16_t g_rx_log_len[PORT_COUNT]; /** * @brief 各端口日志缓冲区最后写入时间戳 * @note 用于实现超时自动刷新机制 */ static uint32_t g_rx_log_last_tick[PORT_COUNT]; /** * @brief 接收日志超时阈值(毫秒) * @note 如果超过此时间没有新字节,则自动刷新日志 */ #define RX_LOG_TIMEOUT_MS 50 /*============================================================================== * 内部静态函数声明 *============================================================================*/ static void flush_rx_log(port_id_t port_id); static void append_rx_log(port_id_t port_id, uint8_t byte); static void cmd_parser_response_callback(uint8_t source_port, const char *response); /*============================================================================== * 内部静态函数实现 *============================================================================*/ /** * @brief 刷新指定端口的接收日志 * @note 将暂存的接收字节格式化打印到调试串口,然后清空缓冲区 * * @param port_id: 待刷新的端口ID(输入) * @return 无 * * 格式化规则: * - 可打印ASCII字符(0x20-0x7E)直接输出 * - '\r'显示为"\\r" * - '\n'显示为"\\n" * - 其他字符显示为"\\xXX"十六进制格式 * * 调用时机: * - 接收到完整帧时 * - 接收超时(RX_LOG_TIMEOUT_MS)时 */ static void flush_rx_log(port_id_t port_id) { /* 无日志数据时直接返回 */ if (g_rx_log_len[port_id] == 0) { return; } /* 打印日志头部,包含端口名称 */ UART2_Print_Printf("[ROUTER] %s RX: \"", MultiUART_GetPortName(port_id)); /*---------------------------------------------------------- * 遍历日志缓冲区,格式化输出每个字节 *----------------------------------------------------------*/ for (uint16_t i = 0; i < g_rx_log_len[port_id]; i++) { uint8_t c = g_rx_log_buffer[port_id][i]; if (c >= 0x20 && c < 0x7F) { /* 可打印ASCII字符:直接输出 */ UART2_Print_Send(&c, 1); } else if (c == '\r') { /* 回车符:显示为转义序列 */ UART2_Print_Send((const uint8_t *)"\\r", 2); } else if (c == '\n') { /* 换行符:显示为转义序列 */ UART2_Print_Send((const uint8_t *)"\\n", 2); } else { /* 其他字符:显示为十六进制格式 */ char hex[4]; snprintf(hex, sizeof(hex), "\\x%02X", c); UART2_Print_Send((const uint8_t *)hex, 4); } } /* 打印日志尾部并换行 */ UART2_Print_String("\"\r\n"); /* 清空日志缓冲区 */ g_rx_log_len[port_id] = 0; } /** * @brief 添加字节到接收日志缓冲区 * @note 暂存接收到的原始字节,支持调试回溯 * * @param port_id: 端口ID(输入) * @param byte: 待添加的字节(输入) * @return 无 * * 设计说明: * 缓冲区大小有限(RX_LOG_BUFFER_SIZE),超出时丢弃最旧数据。 * 同时记录最后写入时间戳,用于超时检测。 */ static void append_rx_log(port_id_t port_id, uint8_t byte) { /* 缓冲区未满时写入,否则丢弃(溢出处理) */ if (g_rx_log_len[port_id] < RX_LOG_BUFFER_SIZE) { g_rx_log_buffer[port_id][g_rx_log_len[port_id]++] = byte; } /* 更新最后写入时间戳 */ g_rx_log_last_tick[port_id] = HAL_GetTick(); } /** * @brief 解析器响应回调函数 * @note 作为CmdParser的响应回调,将响应路由到正确的源端口 * * @param source_port: 响应目标端口ID(输入) * @param response: 待发送的响应字符串(输入) * @return 无 * * 路由逻辑: * 1. 如果设置了自定义响应处理器(g_response_handler),调用它 * 2. 否则直接通过MultiUART_Send发送到源端口 * 3. 每次成功路由增加routed_count计数 * * 调试日志: * 使用LOG_INFO输出TX路由详情,便于问题诊断 */ static void cmd_parser_response_callback(uint8_t source_port, const char *response) { if (g_response_handler != NULL) { /* 使用自定义响应处理器 */ g_response_handler((port_id_t)source_port, response, strlen(response)); g_routed_count++; } else { /* 直接发送到源端口 */ MultiUART_SendString((port_id_t)source_port, response); g_routed_count++; } /* 输出TX路由调试日志 */ LOG_INFO("ROUTER", "TX[%s]: %s", MultiUART_GetPortName((port_id_t)source_port), response); } /*============================================================================== * 公共函数实现 *============================================================================*/ /** * @brief 指令路由模块初始化 * @note 初始化所有端口状态、统计计数,并注册解析器回调 * * @param 无 * @return 无 * * 初始化内容: * 1. 重置所有端口的解析状态 * 2. 清零所有日志缓冲区 * 3. 重置统计计数器 * 4. 设置默认解析端口为PORT_UART2 * 5. 注册响应回调函数到解析器 */ void CmdRouter_Init(void) { /*---------------------------------------------------------- * 初始化各端口解析器状态 *----------------------------------------------------------*/ for (port_id_t i = 0; i < PORT_COUNT; i++) { g_port_states[i].source_port = i; g_port_states[i].last_feed_tick = 0; g_port_states[i].rx_count = 0; g_port_states[i].active = false; g_rx_log_len[i] = 0; g_rx_log_last_tick[i] = 0; } /*---------------------------------------------------------- * 初始化统计计数器和状态变量 *----------------------------------------------------------*/ g_response_handler = NULL; g_processed_count = 0; g_routed_count = 0; g_current_parsing_port = PORT_UART2; /*---------------------------------------------------------- * 注册响应回调函数 * 将cmd_parser的响应路由到正确的源端口 *----------------------------------------------------------*/ CmdParser_SetResponseCallback(cmd_parser_response_callback); /*---------------------------------------------------------- * 初始化UART3智能路由模块 *----------------------------------------------------------*/ #if UART3_SMART_ROUTING_ENABLED UART3_Protocol_Init(); Passthrough_Init(); ROUTER_LOG("UART3 Smart Router enabled"); #endif ROUTER_LOG("Init OK, %d ports registered", PORT_COUNT); } /** * @brief 指令路由主任务函数 * @note 在主循环中周期性调用,处理所有端口的接收数据和路由 * * @param 无 * @return 无 * * 工作流程: * 1. UART1: 原有逻辑,所有数据喂给解析器 * 2. UART3: 智能路由模式(协议识别+透传) * 3. 处理日志缓冲区超时 * 4. 调用解析器任务处理完整帧 * * UART3智能路由(当UART3_SMART_ROUTING_ENABLED=1时): * 1. 读取UART3接收缓冲区字节 * 2. 协议识别:判断是CMD还是透传 * 3. CMD → CmdParser_FeedByte() * 4. PASSTHROUGH → Passthrough_PushByte() * 5. 检查SCAN超时,触发透传 * 6. 执行透传引擎任务 */ void CmdRouter_Task(void) { uint32_t current_tick = HAL_GetTick(); /*---------------------------------------------------------- * 第一阶段:UART1处理(保持原有逻辑) *----------------------------------------------------------*/ { port_id_t port_id = PORT_UART1; uint16_t rx_count = MultiUART_GetRxCount(port_id); if (rx_count > 0) { g_current_parsing_port = port_id; CmdParser_SetSourcePort(port_id); uint8_t byte; while (MultiUART_ReadByte(port_id, &byte) > 0) { append_rx_log(port_id, byte); CmdParser_FeedByte(byte, current_tick); g_port_states[port_id].rx_count++; } if (CmdParser_HasCompleteFrame(NULL)) { flush_rx_log(port_id); g_processed_count++; g_port_states[port_id].active = true; } } } /*---------------------------------------------------------- * 第二阶段:UART3智能路由处理 * 设计依据:文档第3.3.1节 修改CmdRouter_Task *----------------------------------------------------------*/ #if UART3_SMART_ROUTING_ENABLED { uint8_t byte; while (MultiUART_ReadByte(PORT_UART3, &byte) > 0) { route_result_t route = UART3_Protocol_FeedByte(byte, current_tick); switch (route) { case ROUTE_CMD: CmdParser_SetSourcePort(PORT_UART3); CmdParser_FeedByte(byte, current_tick); LOG_DEBUG("UART3", "CMD byte: 0x%02X", byte); break; case ROUTE_PASSTHROUGH: Passthrough_PushByte(byte); LOG_DEBUG("UART3", "PTX byte: 0x%02X", byte); break; case ROUTE_NONE: break; } } if (UART3_Protocol_CheckTimeout(current_tick)) { uint8_t buffer[128]; uint16_t length; if (UART3_Protocol_GetPassthroughData(buffer, &length)) { Passthrough_PushBuffer(buffer, length); LOG_INFO("UART3", "PASSTHROUGH: %d bytes queued", (int)length); } } Passthrough_Task(); } #else { port_id_t port_id = PORT_UART3; uint16_t rx_count = MultiUART_GetRxCount(port_id); if (rx_count > 0) { g_current_parsing_port = port_id; CmdParser_SetSourcePort(port_id); uint8_t byte; while (MultiUART_ReadByte(port_id, &byte) > 0) { append_rx_log(port_id, byte); CmdParser_FeedByte(byte, current_tick); g_port_states[port_id].rx_count++; } if (CmdParser_HasCompleteFrame(NULL)) { flush_rx_log(port_id); g_processed_count++; g_port_states[port_id].active = true; } } } #endif /*---------------------------------------------------------- * 第三阶段:检查各端口日志缓冲区的超时状态 *----------------------------------------------------------*/ for (port_id_t port_id = 0; port_id < PORT_COUNT; port_id++) { if (port_id == PORT_UART2) { continue; } if (g_rx_log_len[port_id] > 0 && (current_tick - g_rx_log_last_tick[port_id]) >= RX_LOG_TIMEOUT_MS) { flush_rx_log(port_id); } } /*---------------------------------------------------------- * 第四阶段:调用解析器任务处理完整帧 *----------------------------------------------------------*/ CmdParser_Task(); } /** * @brief 设置自定义响应处理器 * @note 用于在响应发送前进行自定义处理(如添加时间戳、加密等) * * @param handler: 响应处理回调函数指针,NULL则使用默认行为(输入) * @return 无 * * 回调函数原型: * void handler(port_id_t port, const char *response, uint16_t len) * * 使用场景: * - 需要在响应中添加时间戳 * - 需要对响应数据进行编码/加密 * - 需要过滤特定响应 */ void CmdRouter_SetResponseHandler(cmd_response_handler_t handler) { g_response_handler = handler; ROUTER_LOG("Response handler %s", handler ? "set" : "cleared"); } /** * @brief 向指定端口发送响应 * @note 封装MultiUART_Send,统一响应发送接口 * * @param port: 目标端口ID(输入) * @param response: 待发送的响应字符串(输入) * @param len: 响应长度(输入) * @return 无 * * 参数检查: * - port超出范围、response为NULL或len为0时直接返回 * * 统计更新: * 每次成功发送增加g_routed_count计数 */ void CmdRouter_SendResponse(port_id_t port, const char *response, uint16_t len) { /* 参数合法性检查 */ if (port >= PORT_COUNT || response == NULL || len == 0) { return; } /* 通过多UART模块发送数据 */ MultiUART_Send(port, (const uint8_t *)response, len); g_routed_count++; LOG_INFO("ROUTER", "SendResponse[%s]: %.*s", MultiUART_GetPortName(port), len, response); } /** * @brief 广播响应到所有端口 * @note 将同一响应同时发送到所有UART端口 * * @param response: 待发送的响应字符串(输入) * @param len: 响应长度(输入) * @return 无 * * 使用场景: * - 系统公告广播 * - 状态变更通知所有监听者 * * 注意: * UART2(调试端口)也会收到广播 */ void CmdRouter_BroadcastResponse(const char *response, uint16_t len) { /* 参数合法性检查 */ if (response == NULL || len == 0) { return; } /* 遍历所有端口发送 */ for (port_id_t port = 0; port < PORT_COUNT; port++) { MultiUART_Send(port, (const uint8_t *)response, len); g_routed_count++; } LOG_INFO("ROUTER", "Broadcast: %.*s", len, response); } /** * @brief 获取已处理指令帧累计计数 * @note 统计所有端口成功解析的指令帧总数 * * @param 无 * @return uint32_t: 已处理帧累计次数 * * 用途: * - 监控系统负载 * - 评估解析器性能 */ uint32_t CmdRouter_GetProcessedCount(void) { return g_processed_count; } /** * @brief 获取已路由响应累计计数 * @note 统计成功发送的响应消息总数 * * @param 无 * @return uint32_t: 已路由响应累计次数 * * 用途: * - 监控系统通信量 * - 检测是否有响应发送失败 */ uint32_t CmdRouter_GetRoutedCount(void) { return g_routed_count; }