Files
433_STM32/Core/Src/cmd_router.c
zhongxuanzhen 0eea5c1424 3.27_433:实验并验证485发送数据透传至RF433模块,并在外部设备成功接收
- 新增协议识别器状态机,实现指令与透传数据的自动识别
2026-03-27 19:58:20 +08:00

569 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
******************************************************************************
* @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;
}