/** ****************************************************************************** * @file cmd_parser.c * @brief ASCII指令解析模块实现 * @author Application Layer * @version 1.1 ****************************************************************************** * @attention * 本模块实现ASCII文本指令的解析和处理 * 关键特性: * 1. 状态机解析,健壮可靠 * 2. 完善的安全防护(缓冲区边界检查、超时重置、字符过滤) * 3. 异或校验,FF特权后门 * 4. 支持RL、DI、ECHO指令 * * 修订历史: * v1.1 - 修复审查报告高危-6、中危-7/8 ****************************************************************************** */ #include "cmd_parser.h" #include "uart2_print.h" #include "io_monitor.h" #include "relay_control.h" #include #include #include // snprintf #include // atoi /*============================================================================== * 调试宏定义 *============================================================================*/ /* DEBUG_CMD_PARSER: 调试日志开关,置1时启用解析过程日志输出 */ #define DEBUG_CMD_PARSER 1 #if DEBUG_CMD_PARSER /* 调试日志宏,带模块前缀"[CMD]"方便过滤 */ #define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[CMD] " fmt "\r\n", ##__VA_ARGS__) #else #define DEBUG_LOG(fmt, ...) #endif /*============================================================================== * 解析器状态机定义 *============================================================================*/ /** * @brief 指令解析器状态枚举 * @note 状态机各状态定义,描述指令解析的完整生命周期 * * 状态转换图: * * [空闲] ---'$'---> [解析命令] * [解析命令] ---','---> [解析参数1] * [解析命令] ---'*'---> [解析校验和] * [解析参数1] ---','---> [解析参数2] * [解析参数1] ---'*'---> [解析校验和] * [解析参数2] ---'*'---> [解析校验和] * [解析校验和] ---'\n'---> [完成] * [完成] ---'$'---> [解析命令] * 任何状态 ---超时---> [空闲] * * 各状态说明: * - PARSE_IDLE: 等待帧起始符'$',处于空闲状态 * - PARSE_CMD: 正在解析命令字段(直到遇到','或'*') * - PARSE_PARAM1: 正在解析第一个参数(直到遇到','或'*') * - PARSE_PARAM2: 正在解析第二个参数(直到遇到'*') * - PARSE_CHECKSUM: 正在解析校验和(两个十六进制字符直到'\n') * - PARSE_COMPLETE: 完整帧已解析完成,等待处理 */ typedef enum { PARSE_IDLE = 0, /**< 空闲状态,等待帧起始 */ PARSE_CMD, /**< 正在解析命令字段 */ PARSE_PARAM1, /**< 正在解析第一个参数 */ PARSE_PARAM2, /**< 正在解析第二个参数 */ PARSE_CHECKSUM, /**< 正在解析校验和 */ PARSE_COMPLETE /**< 解析完成,可处理 */ } parse_state_t; /*============================================================================== * 数据结构定义 *============================================================================*/ /** * @brief 解析器上下文数据结构 * @note 保存指令解析的完整状态信息,包括当前状态、已解析字段和统计计数 * * 设计目的: * 解析器需要记忆多个中间状态:当前解析状态、已接收的各字段内容、 * 校验和累加值、上下文字符索引等。这些信息集中保存在此结构体中, * 实现无状态的逐字节处理(Stateless Byte-by-Byte Processing)模式。 * * 字段说明: * - state: 当前解析状态机的状态 * - frame: 已解析完成的指令帧结构(cmd_frame_t类型) * - field_index: 当前字段的字符写入位置索引 * - checksum_acc: 校验和累加器,逐字节异或得到 * - cs_buffer: 接收的校验和字符缓存(两个HEX字符) * - cs_index: 校验和字符缓存的当前写入位置 * - last_rx_tick: 上次接收到数据的时间戳(用于超时检测) * - error_count: 解析错误累计次数 * - valid_count: 有效指令帧累计次数 */ typedef struct { parse_state_t state; /**< 解析状态机当前状态 */ cmd_frame_t frame; /**< 已解析指令帧数据 */ uint8_t field_index; /**< 当前字段的字符写入位置 */ uint8_t checksum_acc; /**< 校验和累加器(异或运算) */ uint8_t cs_buffer[2]; /**< 接收的校验和字符缓存 */ uint8_t cs_index; /**< 校验和字符缓存写入位置 */ uint32_t last_rx_tick; /**< 上次接收时间戳(毫秒) */ uint32_t error_count; /**< 解析错误累计次数 */ uint32_t valid_count; /**< 有效帧累计次数 */ } parser_context_t; /*============================================================================== * 全局变量定义 *============================================================================*/ /** * @brief 解析器上下文实例 * @note static修饰确保仅本文件内可访问,保存解析过程全部状态 */ static parser_context_t ctx; /** * @brief 响应回调函数指针 * @note 用于将响应发送到正确的源端口 */ static cmd_response_callback_t g_response_callback = NULL; /** * @brief 当前源端口ID * @note 记录当前正在解析的指令来自哪个端口 */ static uint8_t g_current_source_port = 0; /*============================================================================== * 内部静态函数声明 *============================================================================*/ static void reset_parser(void); static bool is_valid_cmd_char(char c); static bool is_valid_param_char(char c); static uint8_t hex_char_to_val(char c); static uint8_t hex_to_byte(char high, char low); static uint8_t calc_checksum(const char *data, uint8_t len); static void send_response_ok(const char *content); static void send_response_err(const char *err_code); static bool is_str_empty(const char *str); static bool is_str_numeric(const char *str); static void process_cmd_frame(const cmd_frame_t *frame); /*============================================================================== * 内部辅助函数实现 *============================================================================*/ /** * @brief 重置解析器到初始状态 * @note 清除所有上下文信息,准备接收新指令帧 * * @param 无 * @return 无 * * 重置内容: * - 状态恢复为PARSE_IDLE * - 字段索引清零 * - 校验和累加器清零 * - 校验和缓存索引清零 * - cmd_frame结构体全部清零 * * 调用时机: * - 解析出错时 * - 解析完成处理后 * - 接收到完整帧后的空闲期 */ static void reset_parser(void) { ctx.state = PARSE_IDLE; ctx.field_index = 0; ctx.checksum_acc = 0; ctx.cs_index = 0; memset(&ctx.frame, 0, sizeof(ctx.frame)); } /** * @brief 验证命令字符合法性 * @note 命令字段只能包含大写字母和数字 * * @param c: 待验证的字符(输入) * @return bool: true=合法,false=非法 * * 过滤规则: * - 允许: 'A'-'Z', '0'-'9' * - 禁止: 小写字母、标点符号、控制字符等 * * 安全意义: * - 限制命令字符集可防止注入攻击 * - 简化解析逻辑,避免处理边界情况 */ static bool is_valid_cmd_char(char c) { return isupper((unsigned char)c) || isdigit((unsigned char)c); } /** * @brief 验证参数字符合法性 * @note 参数字段可包含大部分可打印字符,但排除特殊分隔符 * * @param c: 待验证的字符(输入) * @return bool: true=合法,false=非法 * * 过滤规则: * - 允许: isprint()返回true的字符,但不包括'*' * - 禁止: '*'、'\r'、'\n'及其他不可打印字符 * * 特殊说明: * - '*'是校验和起始分隔符,不能出现在参数中 * - '\r'和'\n'是帧结束符 */ static bool is_valid_param_char(char c) { return isprint((unsigned char)c) && c != '*' && c != '\r' && c != '\n'; } /** * @brief 十六进制字符转换为数值 * @note 将单个十六进制字符('0'-'9','A'-'F')转换为对应的4位数值 * * @param c: 十六进制字符(输入) * @return uint8_t: 转换后的数值(0-15),非法字符返回0 * * 转换规则: * '0'-'9' -> 0-9 * 'A'-'F' -> 10-15 * 'a'-'f' -> 10-15 (小写也支持) * 其他字符 -> 0 (默认处理) */ static uint8_t hex_char_to_val(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; return 0; } /** * @brief 将两个十六进制字符转换为一个字节 * @note 高位字符在前,地位字符在后,组合成完整的字节值 * * @param high: 高位十六进制字符(输入) * @param low: 低位十六进制字符(输入) * @return uint8_t: 组合后的字节值(0-255) * * 计算公式:result = (high_nibble << 4) | low_nibble * * 示例: * hex_to_byte('A', 'F') 返回 0xAF (十进制175) */ static uint8_t hex_to_byte(char high, char low) { return (hex_char_to_val(high) << 4) | hex_char_to_val(low); } /** * @brief 计算异或校验和 * @note 对输入数据的每个字节执行异或运算,生成校验码 * * @param data: 待计算校验和的数据缓冲区(输入) * @param len: 数据长度,以字节为单位(输入) * @return uint8_t: 校验和(异或结果) * * 算法原理: * 遍历数据缓冲区,将每个字节与累加器进行异或操作。 * 初始累加器为0,最终结果为所有字节的异或和。 * XOR的特性:a^a=0, a^0=a, 具有可逆性,适合简单校验 */ static uint8_t calc_checksum(const char *data, uint8_t len) { uint8_t cs = 0; for (uint8_t i = 0; i < len; i++) { cs ^= (uint8_t)data[i]; } return cs; } /** * @brief 发送成功响应消息 * @note 构造并发送指令执行成功的响应帧 * * @param content: 响应内容字符串指针(输入) * @return 无 * * 响应帧格式: * $OK,*\r\n * * 校验和计算范围: * 从'$'之后到'*'之前的全部内容(不含$和*) * * 使用示例: * send_response_ok("RL,1") -> $OK,RL,1*XX\r\n */ static void send_response_ok(const char *content) { char msg[64]; uint8_t cs; int len = snprintf(msg, sizeof(msg), "$OK,%s*", content); cs = calc_checksum(msg + 1, len - 1); snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs); if (g_response_callback != NULL) { g_response_callback(g_current_source_port, msg); } else { UART2_Print_String(msg); } } /** * @brief 发送错误响应消息 * @note 构造并发送指令执行失败的响应帧 * * @param err_code: 错误代码字符串(输入),如"PARAM"、"CS"、"CMD" * @return 无 * * 响应帧格式: * $ERR,*\r\n * * 错误代码含义: * - "PARAM": 参数格式或值非法 * - "CS": 校验和不匹配 * - "CMD": 命令无法识别 */ static void send_response_err(const char *err_code) { char msg[32]; uint8_t cs; int len = snprintf(msg, sizeof(msg), "$ERR,%s*", err_code); cs = calc_checksum(msg + 1, len - 1); snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs); if (g_response_callback != NULL) { g_response_callback(g_current_source_port, msg); } else { UART2_Print_String(msg); } } /** * @brief 判断字符串是否为空 * @note 检查指针是否为NULL或首字符是否为'\0' * * @param str: 待检查的字符串指针(输入) * @return bool: true=空字符串或NULL,false=非空 */ static bool is_str_empty(const char *str) { return (str == NULL || str[0] == '\0'); } /** * @brief 判断字符串是否全为数字 * @note 验证字符串中的所有字符是否都是十进制数字 * * @param str: 待检查的字符串指针(输入) * @return bool: true=全为数字,false=包含非数字字符或为空 * * 实现逻辑: * 从字符串开头遍历到'\0',检查每个字符是否满足isdigit()。 * 一旦遇到非数字字符立即返回false。 */ static bool is_str_numeric(const char *str) { /* 空字符串检查 */ if (is_str_empty(str)) { return false; } /* 逐字符检查是否为数字 */ while (*str) { if (!isdigit((unsigned char)*str)) { return false; } str++; } return true; } /** * @brief 指令帧处理函数 * @note 根据解析出的命令类型分发到相应的处理函数 * * @param frame: 已解析完成的指令帧结构指针(输入) * @return 无 * * 支持的指令列表: * - RL : 控制继电器开关 * - DI [channel]: 查询数字输入状态 * - ECHO: 回显测试指令 * * 处理流程: * 1. 调试日志输出指令详情 * 2. 根据cmd字段分发到对应处理分支 * 3. 参数合法性校验 * 4. 调用业务层函数执行操作 * 5. 发送成功/失败响应 */ static void process_cmd_frame(const cmd_frame_t *frame) { /*---------------------------------------------------------- * 调试日志:打印完整指令信息 *----------------------------------------------------------*/ DEBUG_LOG("CMD=%s P1=%s P2=%s CS=%02X/%02X %s", frame->cmd, frame->param1, frame->param2, frame->received_cs, frame->calculated_cs, frame->skip_checksum ? "(skip)" : ""); /*---------------------------------------------------------- * 继电器控制指令: RL * 格式: $RL,* * 说明: 单路继电器,state为0(关)或1(开) *----------------------------------------------------------*/ if (strcmp(frame->cmd, "RL") == 0) { /* 参数合法性检查:param1必须为数字 */ if (!is_str_numeric(frame->param1)) { send_response_err("PARAM"); DEBUG_LOG("Invalid RL param: not numeric"); return; } /* 将参数字符串转换为整数 */ int state = atoi(frame->param1); /* 状态值范围检查: 0或1 */ if (state == 0 || state == 1) { /* 调用业务层函数设置继电器状态 */ Relay_SetState(state ? true : false); /* 构造并发送成功响应 */ char resp[32]; snprintf(resp, sizeof(resp), "RL,%d", state); send_response_ok(resp); DEBUG_LOG("Relay -> %s", state ? "ON" : "OFF"); } else { /* 参数值超出有效范围 */ send_response_err("PARAM"); DEBUG_LOG("Invalid RL param: state=%d", state); } } /*---------------------------------------------------------- * 数字输入查询指令: DI [channel] * 格式1(查询全部): $DI* 或 $DI,0* * 格式2(查询单通道): $DI,* *----------------------------------------------------------*/ else if (strcmp(frame->cmd, "DI") == 0) { /*---------------------------------------------------------- * 分支1: 查询全部通道状态 * param1为空或为"0"时触发 *----------------------------------------------------------*/ if (is_str_empty(frame->param1) || strcmp(frame->param1, "0") == 0) { /* 获取所有通道状态的组合掩码 */ uint8_t states = IO_Monitor_GetAllStates(); /* 构造响应消息,将四路状态按位展开为ASCII字符 */ char resp[32]; snprintf(resp, sizeof(resp), "DI,%d%d%d%d", (states >> 0) & 1, (states >> 1) & 1, (states >> 2) & 1, (states >> 3) & 1); send_response_ok(resp); DEBUG_LOG("DI all states: 0x%02X", states); } /*---------------------------------------------------------- * 分支2: 查询指定单通道状态 * param1为数字时触发 *----------------------------------------------------------*/ else if (is_str_numeric(frame->param1)) { int channel = atoi(frame->param1); /* 通道编号范围检查: 1-4 */ if (channel >= 1 && channel <= 4) { /* 获取该通道的当前状态(内部使用0-base索引) */ uint8_t state = IO_Monitor_GetState(channel - 1); char resp[32]; snprintf(resp, sizeof(resp), "DI,%d,%d", channel, state); send_response_ok(resp); DEBUG_LOG("DI%d = %d", channel, state); } else { /* 通道编号超出范围 */ send_response_err("PARAM"); DEBUG_LOG("Invalid DI channel: %d", channel); } } /*---------------------------------------------------------- * 分支3: 参数格式非法 *----------------------------------------------------------*/ else { send_response_err("PARAM"); DEBUG_LOG("Invalid DI param: not numeric"); } } /*---------------------------------------------------------- * 回显测试指令: ECHO * 格式: $ECHO* * 用途: 测试通信链路是否正常 *----------------------------------------------------------*/ else if (strcmp(frame->cmd, "ECHO") == 0) { send_response_ok("ECHO"); DEBUG_LOG("ECHO response sent"); } /*---------------------------------------------------------- * 未知命令处理 *----------------------------------------------------------*/ else { send_response_err("CMD"); DEBUG_LOG("Unknown command: %s", frame->cmd); } } /*============================================================================== * 公共函数实现 *============================================================================*/ /** * @brief 指令解析器初始化 * @note 在系统启动时调用,初始化解析器上下文 * * @param 无 * @return 无 * * 初始化内容: * - 使用memset将整个上下文结构清零 * - 将状态显式设置为PARSE_IDLE * * 注意: * 全局变量ctx本身是static且初始化为{0}, * 此函数调用memset是确保明确初始化, * 防止未来新增字段时出现未初始化问题 */ void CmdParser_Init(void) { memset(&ctx, 0, sizeof(ctx)); ctx.state = PARSE_IDLE; DEBUG_LOG("Init OK"); } /** * @brief 向解析器输入一个字节 * @note 核心解析函数,实现状态机逻辑,应对每个接收字节调用一次 * * @param byte: 待解析的接收字节(输入) * @param current_tick: 当前系统时间戳,毫秒单位(输入) * @return 无 * * 算法说明 - 状态机核心逻辑: * 本函数是整个解析器的核心,实现了有限状态机(FSM)。 * 每次调用处理一个字节,根据当前状态(ctx.state)和输入字节 * 决定状态转换和动作。 * * 状态转换详细说明: * * [PARSE_IDLE]: * - byte='$' -> reset_parser(), state=PARSE_CMD * - 其他字节 -> 忽略,保持IDLE * * [PARSE_CMD]: * - byte=',' -> 字段结束,state=PARSE_PARAM1,field_index=0 * - byte='*' -> 字段结束,state=PARSE_CHECKSUM,cs_index=0 * - is_valid_cmd_char(byte) -> 写入cmd缓冲区,checksum_acc^=byte * - 其他情况 -> error_count++, reset_parser() * * [PARSE_PARAM1]: * - byte=',' -> 字段结束,state=PARSE_PARAM2,field_index=0 * - byte='*' -> 字段结束,state=PARSE_CHECKSUM,cs_index=0 * - is_valid_param_char(byte) -> 写入param1缓冲区,checksum_acc^=byte * - 其他情况 -> error_count++, reset_parser() * * [PARSE_PARAM2]: * - byte='*' -> 字段结束,state=PARSE_CHECKSUM,cs_index=0 * - is_valid_param_char(byte) -> 写入param2缓冲区,checksum_acc^=byte * - 其他情况 -> error_count++, reset_parser() * * [PARSE_CHECKSUM]: * - byte='\n' -> 校验和接收完成,验证并设置state=PARSE_COMPLETE * - byte='\r' -> 忽略(回车符) * - 其他情况 -> 写入cs_buffer[cs_index++],最多2个字符 * * [PARSE_COMPLETE]: * - reset_parser() * - byte='$' -> state=PARSE_CMD (允许连续帧) * * 超时处理: * 如果当前状态不是IDLE且距离上次接收超过PARSE_TIMEOUT_MS, * 则判定为接收超时,重置解析器到IDLE状态 */ void CmdParser_FeedByte(uint8_t byte, uint32_t current_tick) { /*---------------------------------------------------------- * 超时检测 * 如果接收到新字节且距离上次接收已超时,则重置解析器 *----------------------------------------------------------*/ if (ctx.state != PARSE_IDLE && ctx.state != PARSE_COMPLETE) { if (current_tick - ctx.last_rx_tick >= PARSE_TIMEOUT_MS) { ctx.error_count++; DEBUG_LOG("Timeout, reset parser"); reset_parser(); /* 超时后如果收到'$',立即开始解析新帧 */ if (byte == '$') { ctx.state = PARSE_CMD; } return; } } /* 更新最后接收时间戳 */ ctx.last_rx_tick = current_tick; /*---------------------------------------------------------- * 状态机主分支:根据当前状态处理输入字节 *----------------------------------------------------------*/ switch (ctx.state) { /*---------------------------------------------------------- * PARSE_IDLE: 等待帧起始符'$' *----------------------------------------------------------*/ case PARSE_IDLE: if (byte == '$') { reset_parser(); ctx.state = PARSE_CMD; } break; /*---------------------------------------------------------- * PARSE_CMD: 解析命令字段 *----------------------------------------------------------*/ case PARSE_CMD: if (byte == ',') { /* 命令字段结束符,遇到逗号转入参数1解析 */ ctx.frame.cmd[ctx.field_index] = '\0'; ctx.state = PARSE_PARAM1; ctx.field_index = 0; } else if (byte == '*') { /* 无参数命令,遇到'*'直接转入校验和解析 */ ctx.frame.cmd[ctx.field_index] = '\0'; ctx.state = PARSE_CHECKSUM; ctx.field_index = 0; ctx.cs_index = 0; } else if (is_valid_cmd_char(byte)) { /* 有效的命令字符,写入缓冲区 */ if (ctx.field_index < CMD_MAX_LEN - 1) { ctx.frame.cmd[ctx.field_index++] = byte; ctx.checksum_acc ^= byte; /* 累加到校验和 */ } else { /* 缓冲区溢出,命令过长 */ ctx.error_count++; reset_parser(); } } else { /* 无效字符,命令只能包含大写字母和数字 */ ctx.error_count++; reset_parser(); } break; /*---------------------------------------------------------- * PARSE_PARAM1: 解析第一个参数 *----------------------------------------------------------*/ case PARSE_PARAM1: if (byte == ',') { /* 参数1结束符,遇到逗号转入参数2解析 */ ctx.frame.param1[ctx.field_index] = '\0'; ctx.state = PARSE_PARAM2; ctx.field_index = 0; } else if (byte == '*') { /* 参数1结束符,遇到'*'直接转入校验和解析 */ ctx.frame.param1[ctx.field_index] = '\0'; ctx.state = PARSE_CHECKSUM; ctx.field_index = 0; ctx.cs_index = 0; } else if (is_valid_param_char(byte)) { /* 有效参数字符,写入缓冲区 */ if (ctx.field_index < PARAM_MAX_LEN - 1) { ctx.frame.param1[ctx.field_index++] = byte; ctx.checksum_acc ^= byte; } else { /* 缓冲区溢出,参数过长 */ ctx.error_count++; reset_parser(); } } else { /* 无效字符 */ ctx.error_count++; reset_parser(); } break; /*---------------------------------------------------------- * PARSE_PARAM2: 解析第二个参数 *----------------------------------------------------------*/ case PARSE_PARAM2: if (byte == '*') { /* 参数2结束符,遇到'*'转入校验和解析 */ ctx.frame.param2[ctx.field_index] = '\0'; ctx.state = PARSE_CHECKSUM; ctx.field_index = 0; ctx.cs_index = 0; } else if (is_valid_param_char(byte)) { /* 有效参数字符,写入缓冲区 */ if (ctx.field_index < PARAM_MAX_LEN - 1) { ctx.frame.param2[ctx.field_index++] = byte; ctx.checksum_acc ^= byte; } else { ctx.error_count++; reset_parser(); } } else { ctx.error_count++; reset_parser(); } break; /*---------------------------------------------------------- * PARSE_CHECKSUM: 解析校验和 *----------------------------------------------------------*/ case PARSE_CHECKSUM: if (byte == '\n') { /* 帧结束符'\n'到达,校验和解析完成 */ /* 将两个十六进制字符转换为字节 */ ctx.frame.received_cs = hex_to_byte(ctx.cs_buffer[0], ctx.cs_buffer[1]); ctx.frame.calculated_cs = ctx.checksum_acc; /* 0xFF作为特殊值,跳过校验和验证(后门) */ ctx.frame.skip_checksum = (ctx.frame.received_cs == 0xFF); /*---------------------------------------------------------- * 校验和验证 *----------------------------------------------------------*/ if (ctx.frame.skip_checksum || ctx.frame.received_cs == ctx.frame.calculated_cs) { /* 校验通过,设置帧有效标志 */ ctx.frame.valid = true; ctx.state = PARSE_COMPLETE; ctx.valid_count++; } else { /* 校验失败,发送错误响应 */ ctx.error_count++; DEBUG_LOG("Checksum error: recv=%02X calc=%02X", ctx.frame.received_cs, ctx.frame.calculated_cs); send_response_err("CS"); reset_parser(); } } else if (byte != '\r') { /* 忽略回车符'\r',接收校验和字符(最多2个HEX) */ if (ctx.cs_index < 2) { ctx.cs_buffer[ctx.cs_index++] = byte; } } break; /*---------------------------------------------------------- * PARSE_COMPLETE: 解析完成状态 *----------------------------------------------------------*/ case PARSE_COMPLETE: /* 重置解析器,准备接收下一帧 */ reset_parser(); /* 如果立即收到'$',允许连续帧解析 */ if (byte == '$') { ctx.state = PARSE_CMD; } break; } } /** * @brief 指令解析器任务函数 * @note 在主循环中周期性调用,处理已解析完成的指令帧 * * @param 无 * @return 无 * * 工作模式: * CmdParser_FeedByte()负责接收字节并解析, * 此函数负责在帧解析完成后执行相应的业务处理。 * * 处理流程: * 1. 检查是否处于PARSE_COMPLETE状态 * 2. 检查帧是否valid * 3. 调用process_cmd_frame()执行指令 * 4. 重置解析器到IDLE状态 * * 注意: * 此设计将"接收解析"与"业务处理"分离, * 避免了中断处理函数中执行复杂业务逻辑 */ void CmdParser_Task(void) { if (ctx.state == PARSE_COMPLETE && ctx.frame.valid) { process_cmd_frame(&ctx.frame); reset_parser(); } } /** * @brief 查询是否存在已完成的指令帧 * @note 非阻塞查询接口,供外部模块检查是否有待处理帧 * * @param frame: 帧数据输出指针,如果不为NULL则复制帧数据(输出) * @return bool: true=存在有效帧,false=无有效帧 * * 使用场景: * 外部模块(如主循环)可轮询此函数, * 当返回true时获取帧数据进行处理 * * 注意: * 此函数只是查询,不自动清除帧数据。 * 帧数据的清除需要调用CmdParser_Acknowledge() */ bool CmdParser_HasCompleteFrame(cmd_frame_t *frame) { if (ctx.state == PARSE_COMPLETE && ctx.frame.valid) { if (frame) { memcpy(frame, &ctx.frame, sizeof(cmd_frame_t)); } return true; } return false; } /** * @brief 确认并清除已完成的指令帧 * @note 在处理完一帧后调用,重置解析器到初始状态 * * @param 无 * @return 无 * * 使用说明: * 当外部模块通过CmdParser_HasCompleteFrame获取帧数据后, * 处理完毕应调用此函数清除状态,防止重复处理 */ void CmdParser_Acknowledge(void) { reset_parser(); } /** * @brief 获取解析错误累计次数 * @note 用于诊断通信质量或协议错误统计 * * @param 无 * @return uint32_t: 错误帧累计次数 * * 错误类型包括: * - 字符验证失败 * - 缓冲区溢出 * - 校验和不匹配 * - 接收超时 */ uint32_t CmdParser_GetErrorCount(void) { return ctx.error_count; } /** * @brief 获取有效帧累计次数 * @note 用于诊断通信成功率统计 * * @param 无 * @return uint32_t: 有效帧累计次数 * * 计算公式:成功率 = valid_count / (valid_count + error_count) */ uint32_t CmdParser_GetValidCount(void) { return ctx.valid_count; } /** * @brief 设置响应回调函数 * @note 用于将响应路由到正确的源端口 * * @param callback: 回调函数指针 * @return 无 * * 回调函数原型: * void callback(uint8_t source_port, const char *response) * * 参数说明: * - source_port: 指令来源端口ID * - response: 待发送的响应字符串 */ void CmdParser_SetResponseCallback(cmd_response_callback_t callback) { g_response_callback = callback; } /** * @brief 设置当前源端口 * @note 在开始解析新指令前调用,记录指令来源 * * @param port_id: 端口ID * @return 无 */ void CmdParser_SetSourcePort(uint8_t port_id) { g_current_source_port = port_id; }