에러 커맨드 추가

- rxx: (Unknown command) - 수신 TAG가 명령 테이블에 없는 경우
- rxd: (Disabled command) - 수신 TAG가 테이블에 있지만 enabled=false로 비활성화된 경우
- rxn: (NULL handler) - 엔트리는 매칭됐지만 함수 포인터가 NULL인 경우(펌웨어 버그 방어)
- rxc: (CRC fail) - CRC16 검증 실패
- rxs: (Too short) - 패킷이 너무 짧은 경우(CRC 활성 시 7바이트 미만, 비활성 시 4바이트 미만)
This commit is contained in:
2026-04-16 01:20:52 +09:00
parent a826d09dfa
commit c11ce4ec3e

View File

@@ -278,6 +278,20 @@ static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len)
return dr_crc16_check(packet, data_len, expected_crc); return dr_crc16_check(packet, data_len, expected_crc);
} }
/* ---- 에러 응답 헬퍼 ----
* 패킷: [err_tag 4B] [cmd_tag 에코 4B] = 8바이트 = 4워드
* CRC16은 tx_bin 레이어에서 자동 부가
*/
static void dr_send_error(const char *err_tag, const char *cmd_tag)
{
if (g_plat.tx_bin) {
uint8_t err_buf[8];
memcpy(&err_buf[0], err_tag, 4);
memcpy(&err_buf[4], cmd_tag, 4);
g_plat.tx_bin(err_buf, 4);
}
}
/* ---- 수신 버퍼 → ParsedCmd 구조체 변환 ---- */ /* ---- 수신 버퍼 → ParsedCmd 구조체 변환 ---- */
/* 수신된 원시 바이트 버퍼를 파싱하여 TAG와 데이터를 분리 /* 수신된 원시 바이트 버퍼를 파싱하여 TAG와 데이터를 분리
@@ -289,43 +303,57 @@ static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len)
*/ */
static bool dr_parse_cmd(const uint8_t *buffer, uint8_t length, ParsedCmd *out) static bool dr_parse_cmd(const uint8_t *buffer, uint8_t length, ParsedCmd *out)
{ {
uint8_t i; uint8_t data_len;
/* TAG 4바이트조차 수신되지 않음 → rxs: + "????" (TAG 특정 불가) */
if (length < 4) { if (length < 4) {
return false; /* TAG 4바이트조차 수신되지 않음 */ dr_send_error("rxs:", "????");
if (g_plat.log && g_log_enable) {
g_plat.log("[parser] too short (%u bytes) -> rxs:\r\n", length);
}
return false;
} }
/* TAG 먼저 추출 (에러 응답 에코에 사용) */
dr_copy_tag(buffer, out->tag);
/* CRC 검증이 활성화된 경우 패킷 무결성 확인 */ /* CRC 검증이 활성화된 경우 패킷 무결성 확인 */
if (g_plat.crc_check) if (g_plat.crc_check)
{ {
if (!dr_crc16_check_packet(buffer, length)) if (length < 7) {
{ dr_send_error("rxs:", out->tag);
if (g_plat.log && g_log_enable) { if (g_plat.log && g_log_enable) {
g_plat.log("CRC check FAILED!\n"); g_plat.log("[parser] CRC enabled but too short (%u) -> rxs:\r\n", length);
} }
return false; return false;
} }
/* CRC 검증 성공 → CRC 2바이트를 제외한 실제 데이터 길이로 조정 */ if (!dr_crc16_check_packet(buffer, length))
length = (uint8_t)(length - 2); {
dr_send_error("rxc:", out->tag);
if (g_plat.log && g_log_enable) {
g_plat.log("[parser] CRC mismatch '%s' -> rxc:\r\n", out->tag);
}
return false;
}
data_len = (uint8_t)(length - 4 - 2); /* TAG(4) + CRC(2) 제외 */
} }
else
/* TAG 4바이트 복사 */
dr_copy_tag(buffer, out->tag);
/* TAG 이후 데이터 길이 계산 (최대 DR_MAX_DATA까지) */
out->data_len = (length > 4) ? (uint8_t)(length - 4) : 0;
if (out->data_len > DR_MAX_DATA)
{ {
out->data_len = DR_MAX_DATA; data_len = (uint8_t)(length - 4); /* TAG(4) 제외 */
} }
/* TAG 뒤의 데이터 바이트를 ParsedCmd.data[]에 복사 */ /* 최대 길이 클램프 */
for (i = 0; i < out->data_len; i++) if (data_len > DR_MAX_DATA) {
{ data_len = DR_MAX_DATA;
out->data[i] = buffer[4 + i];
} }
if (data_len > 0) {
memcpy(out->data, buffer + 4, data_len);
}
out->data_len = data_len;
return true; return true;
} }
@@ -488,10 +516,20 @@ static int dr_cmd_dispatch(const ParsedCmd *cmd)
for (i = 0; i < g_cmd_count; i++) { for (i = 0; i < g_cmd_count; i++) {
if (dr_tag_eq(tag_lower, g_cmd_table[i].tag)) { if (dr_tag_eq(tag_lower, g_cmd_table[i].tag)) {
/* 비활성화된 명령이면 실행하지 않음 */ /* 비활성화된 명령 → rxd: 응답 */
if (!g_cmd_table[i].enabled) { if (!g_cmd_table[i].enabled) {
dr_send_error("rxd:", cmd->tag);
if (g_plat.log && g_log_enable) { if (g_plat.log && g_log_enable) {
g_plat.log("Command '%s' disabled\n", cmd->tag); g_plat.log("Command '%s' disabled -> rxd:\n", cmd->tag);
}
return 0;
}
/* NULL 핸들러 가드 → rxn: 응답 */
if (g_cmd_table[i].handler == NULL) {
dr_send_error("rxn:", cmd->tag);
if (g_plat.log) {
g_plat.log("[parser] NULL handler for '%s' -> rxn:\n", cmd->tag);
} }
return 0; return 0;
} }
@@ -501,9 +539,10 @@ static int dr_cmd_dispatch(const ParsedCmd *cmd)
} }
} }
/* 테이블에 없는 TAG 수신 시 */ /* 테이블에 없는 TAG → rxx: 응답 */
dr_send_error("rxx:", cmd->tag);
if (g_plat.log && g_log_enable) { if (g_plat.log && g_log_enable) {
g_plat.log("Unknown TAG '%s'\n", cmd->tag); g_plat.log("Unknown TAG '%s' -> rxx:\n", cmd->tag);
} }
return 0; return 0;
} }
@@ -523,16 +562,10 @@ int dr_cmd_parser(const uint8_t *buf, uint8_t len)
{ {
ParsedCmd cmd; ParsedCmd cmd;
/* 패킷 파싱 (CRC 검증 포함) */ /* 패킷 파싱 (CRC 검증 포함) - 에러 응답은 dr_parse_cmd 내부에서 전송 */
if (!dr_parse_cmd(buf, len, &cmd)) { if (!dr_parse_cmd(buf, len, &cmd)) {
if (g_plat.log) g_plat.log("[PARSER] PARSE FAIL\r\n"); if (g_plat.log) g_plat.log("[PARSER] PARSE FAIL\r\n");
return -1;
/* CRC 실패 시 "crc!" 에러 응답을 BLE로 전송 (65530 = 에러 코드) */
if (g_plat.crc_check && g_plat.tx_bin) {
single_format_data(ble_bin_buffer, "crc!", 65530);
dr_binary_tx_safe(ble_bin_buffer, 3);
}
return -1; /* CRC/파싱 실패 → 음수 반환으로 레거시 파서에 위임 */
} }
/* 수신 명령어 종류 확인용 로그 */ /* 수신 명령어 종류 확인용 로그 */