569 lines
18 KiB
C
569 lines
18 KiB
C
/**
|
||
******************************************************************************
|
||
* @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 <string.h>
|
||
|
||
/*==============================================================================
|
||
* 调试宏定义
|
||
*============================================================================*/
|
||
/* 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;
|
||
}
|