Files
433_STM32/Core/Src/cmd_parser.c

897 lines
31 KiB
C
Raw Normal View History

/**
******************************************************************************
* @file cmd_parser.c
* @brief ASCII指令解析模块实现
* @author Application Layer
* @version 1.1
******************************************************************************
* @attention
* ASCII文本指令的解析和处理
*
* 1.
* 2.
* 3. FF特权后门
* 4. RLDIECHO指令
*
*
* v1.1 - -6-7/8
******************************************************************************
*/
#include "cmd_parser.h"
#include "uart2_print.h"
#include "io_monitor.h"
#include "relay_control.h"
#include <string.h>
#include <ctype.h>
#include <stdio.h> // snprintf
#include <stdlib.h> // 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,<content>*<checksum>\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,<err_code>*<checksum>\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=NULLfalse=
*/
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 <relay_id> <state>:
* - 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 <state>
* : $RL,<state>*<checksum>
* : 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*<checksum> $DI,0*<checksum>
* 2(): $DI,<channel>*<checksum>
*----------------------------------------------------------*/
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*<checksum>
* :
*----------------------------------------------------------*/
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_PARAM1field_index=0
* - byte='*' -> state=PARSE_CHECKSUMcs_index=0
* - is_valid_cmd_char(byte) -> cmd缓冲区checksum_acc^=byte
* - -> error_count++, reset_parser()
*
* [PARSE_PARAM1]:
* - byte=',' -> state=PARSE_PARAM2field_index=0
* - byte='*' -> state=PARSE_CHECKSUMcs_index=0
* - is_valid_param_char(byte) -> param1缓冲区checksum_acc^=byte
* - -> error_count++, reset_parser()
*
* [PARSE_PARAM2]:
* - byte='*' -> state=PARSE_CHECKSUMcs_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;
}