Files
433_STM32/Core/Src/cmd_parser.c
zhongxuanzhen 71027ebc46 3.27_433:添加UART2调试打印、IO监控、指令解析和继电器控制模块。
能够接收UART2指令控制继电器开关,或向UART2发送四路IO输入状态,并使用轮询方式检测IO状态进行及时反馈。
2026-03-27 10:09:13 +08:00

379 lines
11 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_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 <string.h>
#include <ctype.h>
#include <stdio.h> // snprintf
#include <stdlib.h> // atoi
#define DEBUG_CMD_PARSER 1
#if DEBUG_CMD_PARSER
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[CMD] " fmt "\r\n", ##__VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...)
#endif
typedef enum {
PARSE_IDLE,
PARSE_CMD,
PARSE_PARAM1,
PARSE_PARAM2,
PARSE_CHECKSUM,
PARSE_COMPLETE
} parse_state_t;
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;
static parser_context_t ctx;
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));
}
static bool is_valid_cmd_char(char c)
{
return isupper((unsigned char)c) || isdigit((unsigned char)c);
}
static bool is_valid_param_char(char c)
{
return isprint((unsigned char)c) && c != '*' && c != '\r' && c != '\n';
}
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;
}
static uint8_t hex_to_byte(char high, char low)
{
return (hex_char_to_val(high) << 4) | hex_char_to_val(low);
}
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;
}
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);
UART2_Print_String(msg);
}
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);
UART2_Print_String(msg);
}
static bool is_str_empty(const char *str)
{
return (str == NULL || str[0] == '\0');
}
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;
}
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)" : "");
if (strcmp(frame->cmd, "RL") == 0) {
if (!is_str_numeric(frame->param1) || !is_str_numeric(frame->param2)) {
send_response_err("PARAM");
DEBUG_LOG("Invalid RL params: not numeric");
return;
}
int relay_id = atoi(frame->param1);
int state = atoi(frame->param2);
if (relay_id >= 1 && relay_id <= 4 && (state == 0 || state == 1)) {
Relay_SetState(relay_id, state ? true : false);
char resp[32];
snprintf(resp, sizeof(resp), "RL,%d,%d", relay_id, state);
send_response_ok(resp);
DEBUG_LOG("Relay %d -> %s", relay_id, state ? "ON" : "OFF");
} else {
send_response_err("PARAM");
DEBUG_LOG("Invalid RL params: id=%d state=%d", relay_id, state);
}
}
else if (strcmp(frame->cmd, "DI") == 0) {
if (is_str_empty(frame->param1) || strcmp(frame->param1, "0") == 0) {
uint8_t states = IO_Monitor_GetAllStates();
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);
}
else if (is_str_numeric(frame->param1)) {
int channel = atoi(frame->param1);
if (channel >= 1 && channel <= 4) {
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);
}
}
else {
send_response_err("PARAM");
DEBUG_LOG("Invalid DI param: not numeric");
}
}
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);
}
}
void CmdParser_Init(void)
{
memset(&ctx, 0, sizeof(ctx));
ctx.state = PARSE_IDLE;
DEBUG_LOG("Init OK");
}
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) {
case PARSE_IDLE:
if (byte == '$') {
reset_parser();
ctx.state = PARSE_CMD;
}
break;
case PARSE_CMD:
if (byte == ',') {
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;
case PARSE_PARAM1:
if (byte == ',') {
ctx.frame.param1[ctx.field_index] = '\0';
ctx.state = PARSE_PARAM2;
ctx.field_index = 0;
} else if (byte == '*') {
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;
case PARSE_PARAM2:
if (byte == '*') {
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;
case PARSE_CHECKSUM:
if (byte == '\n') {
ctx.frame.received_cs = hex_to_byte(ctx.cs_buffer[0], ctx.cs_buffer[1]);
ctx.frame.calculated_cs = ctx.checksum_acc;
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') {
if (ctx.cs_index < 2) {
ctx.cs_buffer[ctx.cs_index++] = byte;
}
}
break;
case PARSE_COMPLETE:
reset_parser();
if (byte == '$') {
ctx.state = PARSE_CMD;
}
break;
}
}
void CmdParser_Task(void)
{
if (ctx.state == PARSE_COMPLETE && ctx.frame.valid) {
process_cmd_frame(&ctx.frame);
reset_parser();
}
}
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;
}
void CmdParser_Acknowledge(void)
{
reset_parser();
}
uint32_t CmdParser_GetErrorCount(void)
{
return ctx.error_count;
}
uint32_t CmdParser_GetValidCount(void)
{
return ctx.valid_count;
}