379 lines
11 KiB
C
379 lines
11 KiB
C
|
|
/**
|
|||
|
|
******************************************************************************
|
|||
|
|
* @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;
|
|||
|
|
}
|