/*
 * 2025-12-08 power loop bug fix
 * 2025-12-07 msn, mta, mqq
 * 2025-12-04 by Charles KWON
 * parser.c : Common parser + command table + handlers
 *  - Firmware/PC shared
 *  - Hardware-dependent parts left as TODO
 *  - Added CRC16 validation support
 *
 * [파일 개요]
 *  BLE(Bluetooth Low Energy)를 통해 수신된 바이너리 명령 패킷을 파싱하고, 명령 테이블에서 해당 핸들러를 찾아 실행하는 공통 파서 모듈
 *
 *  패킷 구조: [TAG 4바이트] [데이터 N바이트] [CRC16 2바이트(선택)]
 *   - TAG: 4글자 ASCII 명령 식별자 (예: "mta?", "mec?")
 *   - 데이터: Little-Endian uint16 또는 ASCII 문자열
 *   - CRC16: g_plat.crc_check가 true일 때 패킷 끝 2바이트로 무결성 검증
 *
 *  동작 흐름:
 *   1) dr_cmd_parser()   → 외부에서 호출하는 진입점
 *   2) dr_parse_cmd()    → CRC 검증 + TAG/데이터 분리
 *   3) dr_cmd_dispatch() → 명령 테이블(g_cmd_table) 순회하여 핸들러 호출
 *   4) Cmd_xxx()         → 각 명령별 하드웨어 제어 및 BLE 응답 전송
 */

#include "parser.h"
#include <string.h>

#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "dr_piezo.h"
#include "dr_util.h"
#include "dr_adc121s051.h"


/*==============================================================================
 * 외부 함수 선언부
 * - 다른 모듈(센서, BLE, 디바이스 제어 등)에서 정의된 함수들의 프로토타입
 *============================================================================*/

/* 센서 측정 함수 */
extern void battery_level_meas(void);          /* 배터리 잔량 ADC 측정 */
extern void pressure_all_level_meas(void);     /* 압력 센서 1,2 동시 측정 */
extern void tmp235_voltage_level_meas(void);   /* TMP235 온도 센서 전압 측정 */

/* 디바이스 전원/상태 제어 함수 */
extern int device_activated(void);             /* 디바이스 활성화 (센서 전원 ON) → 0:성공 */
extern int device_sleep_mode(void);            /* 슬립 모드 진입 (저전력) → 0:성공 */

/* 에러 처리 */
extern void param_error(const char *cmd);      /* 파라미터 오류 시 BLE로 에러 응답 전송 */

/* BLE 바이너리 전송 함수 */
extern void single_format_data(uint8_t *buffer, const char *tag, uint16_t value); /* TAG(4바이트) + uint16 값(2바이트)을 buffer에 포맷팅 */
extern void ascii_format_data(uint8_t *buffer, const char *tag, const char *ascii, uint8_t len); /* TAG(4바이트) + ASCII 문자열을 buffer에 포맷팅 */
extern void dr_binary_tx_safe(const uint8_t *buffer, uint16_t length); /* BLE NUS를 통해 바이너리 데이터 전송 (length는 워드 단위) */
extern volatile bool data_tx_in_progress; /* BLE TX 진행 중 플래그 */
extern void dr_sd_delay_ms(uint32_t ms); /* SoftDevice 호환 딜레이 (BLE 스택 이벤트 처리 허용) */

/* FDS(Flash Data Storage) 설정 관련 */
#include "fstorage.h"
extern char SERIAL_NO[12];          /* 시리얼 넘버 (12자 ASCII, FDS에 저장) */
extern char HW_NO[12];              /* 하드웨어 넘버 (12자 ASCII, FDS에 저장) */
extern char m_static_passkey[6];    /* BLE 정적 패스키 (6자리 숫자 문자열) */
extern uint32_t m_life_cycle;       /* 디바이스 수명 카운터 (FDS에 영구 저장) */
extern void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length); /* TAG + uint16 배열을 buffer에 포맷팅 (가변 길이 응답용) */

#define DR_DEVICE_VERSION  "FW25LIT2B102"  /* 펌웨어 버전 문자열 - cmd_parse.c의 DEVICE_VERSION과 반드시 일치해야 함 */         


/*==============================================================================
 * 외부 변수 선언부
 * - main.c 등 다른 모듈에서 정의된 전역 변수 참조
 *============================================================================*/

extern volatile bool processing;    /* true: 측정 진행 중 (중복 명령 방지 플래그) */
extern bool device_status;          /* true: 디바이스 활성화 상태 (센서 전원 ON) */
extern uint8_t resetCount;          /* 통신 타임아웃 카운터 (리셋 감지용) */
extern uint8_t ble_bin_buffer[];    /* BLE 바이너리 전송 버퍼 (공용) */

extern bool con_single;             /* 단일 연결 모드 플래그 */
extern bool lock_check;             /* 보안 잠금 상태 플래그 */


extern bool info4;                  /* true: 센서값을 전역 변수에 저장 (BLE 전송 안 함) */
extern bool ble_got_new_data;       /* true: BLE로 새 데이터 수신됨 (처리 대기) */
extern bool go_batt;                /* true: 배터리 측정 요청 플래그 */
extern bool motion_data_once;       /* true: IMU 1회 측정, false: 연속 측정 */
extern bool motion_raw_data_enabled;/* true: IMU 원시 데이터 전송 활성화 */
extern int  imu_read_direct(void);  /* IMU 레지스터 직접 읽기 + BLE 전송 */

/* info4 모드 센서값 저장 변수 (mbb? rbb: 패킷 조립용) */
extern volatile uint16_t info_batt;     /* 배터리 전압 (mV) */
extern volatile uint16_t info_temp;     /* 온도 (°C x 100) */
extern volatile uint16_t info_imu[6];   /* IMU 6축 (accel XYZ + gyro XYZ) */
extern volatile bool tmp235_saadc_done; /* TMP235 SAADC 콜백 완료 플래그 */

extern void pressure_all_level_meas(void);  /* 압력 센서 전체 측정 */
extern void battery_timer_stop(void);       /* 배터리 타이머 중지 */
extern void main_timer_start(void);         /* 메인 타이머 시작 (주기적 측정 트리거) */
extern void hw_i2c_init_once(void);         /* IMU용 I2C 버스 초기화 (최초 1회) */

/* 전원/리셋/본딩 제어 플래그 */
extern bool go_device_power_off;    /* true → 메인 루프에서 전원 OFF 실행 */
extern bool go_NVIC_SystemReset;    /* true → 메인 루프에서 시스템 리셋 실행 */
extern bool bond_data_delete;       /* true → 리셋 시 BLE 본딩 정보 삭제 */
extern uint8_t m_reset_status;      /* 리셋 상태 코드 (FDS에 저장, 부팅 시 참조) */
extern void config_save(void);      /* 설정값을 FDS(Flash)에 저장 */
extern config_data_t m_config;      /* 전체 설정 구조체 (FDS 저장 대상) */

/* AGC(자동 이득 제어) 게인 스위치 매크로
 * - P0.20 핀으로 아날로그 수신 회로의 게인을 HIGH/LOW 전환
 * - measurements.h의 매크로를 여기서도 사용하기 위해 재정의 */
#include "nrf_gpio.h"
#define GAIN_SW_PIN  NRF_GPIO_PIN_MAP(0, 20)
#define AGC_GAIN_SW(x) do { if(x) nrf_gpio_pin_set(GAIN_SW_PIN); else nrf_gpio_pin_clear(GAIN_SW_PIN); } while(0)


/* ---- 피에조 관련 제어 함수 ---- */
extern void dr_piezo_power_on( void );               /* 피에조 회로 전원 ON (TX/RX 보드) */
extern void dr_piezo_power_off( void );              /* 피에조 회로 전원 OFF */
extern bool dr_piezo_is_power_on( void );            /* 피에조 전원 상태 확인 */
extern void dr_piezo_burst_sw(uint8_t cycles);       /* 2.1MHz 버스트 펄스 발생 (기본 주파수) */
extern void dr_piezo_burst_sw_18mhz(uint8_t cycles); /* 1.8MHz 버스트 펄스 발생 */
extern void dr_piezo_burst_sw_20mhz(uint8_t cycles); /* 2.0MHz 버스트 펄스 발생 */
extern void dr_piezo_burst_sw_17mhz(uint8_t cycles); /* 1.7MHz 버스트 펄스 발생 */


/* ---- 전역 변수 정의 (헤더에서 extern 선언) ---- */
dr_platform_if_t g_plat = { 0, 0, 0 };
    /* 플랫폼 인터페이스 구조체:
     *  .tx_bin   = BLE 바이너리 전송 함수 포인터
     *  .log      = 디버그 로그 출력 함수 포인터
     *  .crc_check = CRC16 검증 활성화 여부 */
bool g_log_enable = false;
    /* 디버그 로그 전역 활성화 플래그 (g_plat.log와 함께 사용) */


/* ---- 내부 상수/구조체 정의 ---- */
#define DR_MAX_DATA 128  /* TAG 이후 데이터의 최대 바이트 수 */


/* 파싱된 명령 구조체 - 수신 패킷을 TAG와 데이터로 분리한 결과 */
typedef struct {
    char    tag[5];               /* 명령 TAG 문자열 (예: "sta?") 4글자 + '\0' */
    uint8_t data[DR_MAX_DATA];    /* TAG 뒤의 원시 데이터 바이트 배열 */
    uint8_t data_len;             /* data[]의 유효 길이 (바이트) */
} ParsedCmd;

/* ---- 내부 유틸리티 함수 ---- */

/* 수신 버퍼의 처음 4바이트를 TAG 문자열로 복사 (널 종료 포함) */
static void dr_copy_tag(const uint8_t *buf, char *tag_out)
{
    tag_out[0] = (char)buf[0];
    tag_out[1] = (char)buf[1];
    tag_out[2] = (char)buf[2];
    tag_out[3] = (char)buf[3];
    tag_out[4] = '\0';
}

/* TAG 4글자 비교 (memcmp 대신 바이트 단위 비교로 최적화) */
static bool dr_tag_eq(const char *tag, const char *key4)
{
    return (tag[0] == key4[0] &&
            tag[1] == key4[1] &&
            tag[2] == key4[2] &&
            tag[3] == key4[3]);
}

/* 파싱된 명령 데이터에서 uint16 값 추출 (Little-Endian)
 *
 * word_index: 추출할 워드 인덱스 (0번째 = data[0..1], 1번째 = data[2..3], ...)
 * out:        추출된 uint16 값 저장 포인터
 * 반환값:     true=성공, false=데이터 부족
 *
 * PC와 nRF52 모두 Little-Endian이므로 data[pos]=하위바이트, data[pos+1]=상위바이트 */
static bool dr_get_u16(const ParsedCmd *cmd, uint8_t word_index, uint16_t *out)
{
    uint8_t pos = (uint8_t)(word_index * 2);
    if (cmd->data_len < (uint8_t)(pos + 2)) {
        return false;  /* 데이터 길이 부족 → 기본값 유지 */
    }

    /* Little Endian 조합: data[pos]=하위, data[pos+1]=상위 */
    *out = (uint16_t)cmd->data[pos]
         | (uint16_t)((uint16_t)cmd->data[pos + 1] << 8);

    return true;
}

/* ASCII 문자열 추출: data[offset] ~ data[offset+max_len-1] → 널 종료 문자열
 *
 * 시리얼번호, HW번호, 패스키 등 FDS에 저장할 텍스트 데이터를 추출할 때 사용
 * offset:   data[] 내 시작 위치
 * out:      추출된 문자열 저장 버퍼 (max_len+1 이상 크기 필요)
 * max_len:  최대 추출 길이 (바이트)
 */
static void dr_get_ascii(const ParsedCmd *cmd, uint8_t offset, char *out, uint8_t max_len)
{
    uint8_t i;
    uint8_t remain;

    /* 오프셋이 데이터 범위를 초과하면 빈 문자열 반환 */
    if (offset >= cmd->data_len)
    {
        out[0] = '\0';
        return;
    }

    /* 실제 복사 가능한 길이 계산 (남은 데이터 vs 최대 길이 중 작은 값) */
    remain = (uint8_t)(cmd->data_len - offset);
    if (remain > max_len)
    {
        remain = max_len;
    }

    for (i = 0; i < remain; i++)
    {
        out[i] = (char)cmd->data[offset + i];
    }
    out[remain] = '\0';  /* 널 종료 보장 */
}

/* ---- CRC16 무결성 검증 함수 ---- */

/* CRC16 계산 - Nordic SDK의 crc16_compute와 동일한 알고리즘 (CRC-CCITT 변형)
 *
 * p_data: 계산 대상 데이터 포인터
 * size:   데이터 길이 (바이트)
 * p_crc:  초기 CRC 값 (NULL이면 0xFFFF로 시작)
 * 반환값: 계산된 CRC16 값
 */
static uint16_t dr_crc16_compute(const uint8_t *p_data, uint32_t size, const uint16_t *p_crc)
{
    uint32_t i;
    uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc;

    for (i = 0; i < size; i++)
    {
        crc = (uint8_t)(crc >> 8) | (crc << 8);       /* 상위/하위 바이트 교환 */
        crc ^= p_data[i];                              /* 데이터 바이트 XOR */
        crc ^= (uint8_t)(crc & 0xFF) >> 4;             /* 다항식 연산 1 */
        crc ^= (crc << 8) << 4;                        /* 다항식 연산 2 */
        crc ^= ((crc & 0xFF) << 4) << 1;               /* 다항식 연산 3 */
    }

    return crc;
}

/* CRC16 검증: 데이터에 대해 계산한 CRC와 기대값 비교 */
static bool dr_crc16_check(const uint8_t *p_data, uint32_t data_len, uint16_t expected_crc)
{
    uint16_t computed_crc = dr_crc16_compute(p_data, data_len, NULL);
    return (computed_crc == expected_crc);
}

/* 패킷 단위 CRC16 검증
 * 패킷 구조: [데이터 N바이트] + [CRC16 2바이트(Little-Endian)]
 * 마지막 2바이트를 CRC 기대값으로 추출하여, 나머지 데이터의 CRC와 비교 */
static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len)
{
    uint16_t expected_crc;
    uint32_t data_len;

    if (packet_len < 2)
    {
        return false;   /* 최소 CRC 2바이트도 없으면 실패 */
    }

    data_len = packet_len - 2;  /* CRC를 제외한 실제 데이터 길이 */

    /* 패킷 끝 2바이트에서 CRC 추출 (Little-Endian: 하위바이트 먼저) */
    expected_crc = (uint16_t)packet[packet_len - 2]
                 | ((uint16_t)packet[packet_len - 1] << 8);

    return dr_crc16_check(packet, data_len, expected_crc);
}

/* ---- 수신 버퍼 → ParsedCmd 구조체 변환 ---- */

/* 수신된 원시 바이트 버퍼를 파싱하여 TAG와 데이터를 분리
 *
 * buffer: 수신 패킷 [TAG(4)] [DATA(N)] [CRC16(2, 선택)]
 * length: 전체 패킷 길이
 * out:    파싱 결과 저장 구조체
 * 반환값: true=파싱 성공, false=CRC 실패 또는 패킷 길이 부족
 */
static bool dr_parse_cmd(const uint8_t *buffer, uint8_t length, ParsedCmd *out)
{
    uint8_t i;

    if (length < 4) {
        return false;  /* TAG 4바이트조차 수신되지 않음 */
    }

    /* CRC 검증이 활성화된 경우 패킷 무결성 확인 */
    if (g_plat.crc_check)
    {
        if (!dr_crc16_check_packet(buffer, length))
        {
            if (g_plat.log && g_log_enable) {
                g_plat.log("CRC check FAILED!\n");
            }
            return false;
        }

        /* CRC 검증 성공 → CRC 2바이트를 제외한 실제 데이터 길이로 조정 */
        length = (uint8_t)(length - 2);
    }

    /* 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;
    }

    /* TAG 뒤의 데이터 바이트를 ParsedCmd.data[]에 복사 */
    for (i = 0; i < out->data_len; i++)
    {
        out->data[i] = buffer[4 + i];
    }

    return true;
}

/*==============================================================================
 * BLE 명령어
 * 
 * A. 디바이스 상태 제어
 * - 전원 OFF (msq? / rsq:)
 * - 재부팅 (mss? / rss:)
 * - 본딩 정보 삭제 및 재부팅 (msr? / rsr:) : 확인 필요
 * - GPIO 제어(핀 테스트용, cmd? / rmd:)
 * 
 * B. 디바이스 정보 읽기, 쓰기
 * - 하드웨어 버전 읽기 (mrh? / rrh:)
 * - 하드웨어 버전 쓰기 (mwh? / rwh:)
 * - 시리얼 넘버 읽기 (mrs? / rrs:)
 * - 시리얼 넘버 쓰기 (mws? / rws:)
 * - 펌웨어 버전 읽기 (mfv? / rfv:)
 * - 패스키 읽기 (mqz? / rqz:) : 필요 유무 검토(암호화 혹은 삭제)
 * - 패스키 쓰기 (mpz? / rpz:)
 * 
 * C. 각종 센서 측정
 * - 배터리 전압 측정 (msn? / rsn:) - test용
 * - IMU 단발 측정 (msp? / rsp:) : 동기 - test용
 * - IMU 연속 스트리밍 (msi? / rsi:) : 비동기(타이머) 1초 주기, 확인 필요 - test용
 * - 온도 측정 (mso? / rso:) - test용
 * 
 * D. Piezo 초음파 측정
 * - TX/RX 전원 활성화 (mpa? / rpa:) - test용
 * - TX/RX 전원 비활성화 (mpb? / rpb:) - test용
 * - 단일 채널 Burst (mpc? / rpc:) - test용
 * - 단일 채널 Burst + ADC -> echo capture (mec? / reb: -> red: -> ree:) : TX/RX Active -> 응답 -> TX/RX Sleep
 * - 모든 채널 Burst + ADC -> echo capture (maa? / reb: -> red: -> raa:) : TX/RX Active -> 응답 -> TX/RX Sleep
 * - 모든 채널 Burst + ADC -> echo capture (mbb? / reb: -> red: -> raa:) + 각종 센서 측정(배터리, IMU, 온도) : TX/RX Active -> 응답 -> TX/RX Sleep (NEW!)
 * 
 * 삭제 명령어
 * - 디바이스 활성화/슬립 : TX 전원 활성화/비활성화와 동일 기능
 * - 디바이스 상태 조회
 * - 디바이스 준비 확인
 * - 수명 카운터(life cycle) 읽기 및 쓰기
 * - 압력 센서 측정 : 센서 미탑재
 * - 단일 채널 ADC
 *============================================================================*/

/*==============================================================================
 * 핸들러 함수 프로토타입 선언
 * - 반환값: 1=성공, 0=실패/비활성
 * - 각 핸들러는 BLE 응답 전송까지 담당
 *============================================================================*/

/* A. 디바이스 상태 제어 */
static int Cmd_msq(const ParsedCmd *cmd);   /* msq? 디바이스 전원 OFF */
static int Cmd_mss(const ParsedCmd *cmd);   /* mss? 디바이스 재부팅 */
#if FEATURE_SECURE_CONNECTION
static int Cmd_msr(const ParsedCmd *cmd);   /* msr? 본딩 삭제 + 디바이스 재부팅 */
#endif
static int Cmd_cmd(const ParsedCmd *cmd);   /* cmd? GPIO 핀 테스트 (HIGH/LOW 제어) */

/* B. 디바이스 정보 읽기, 쓰기 */
static int Cmd_mwh(const ParsedCmd *cmd);   /* mwh? HW 넘버 FDS에 쓰기 */
static int Cmd_mws(const ParsedCmd *cmd);   /* mws? 시리얼 넘버 FDS에 쓰기 */
static int Cmd_mrh(const ParsedCmd *cmd);   /* mrh? HW 넘버 FDS에서 읽기 */
static int Cmd_mrs(const ParsedCmd *cmd);   /* mrs? 시리얼 넘버 FDS에서 읽기 */
static int Cmd_mfv(const ParsedCmd *cmd);   /* mfv? 펌웨어 버전 읽기 */
static int Cmd_mpz(const ParsedCmd *cmd);   /* mpz? BLE 패스키 FDS에 쓰기 */
static int Cmd_mqz(const ParsedCmd *cmd);   /* mqz? BLE 패스키 FDS에서 읽기 */

/* C. 각종 센서 측정 */
static int Cmd_msn(const ParsedCmd *cmd);   /* msn? 배터리 잔량 측정 */
static int Cmd_mso(const ParsedCmd *cmd);   /* mso? TMP235 온도 센서 측정 */
static int Cmd_msp(const ParsedCmd *cmd);   /* msp? IMU 6축 원시 데이터 (단발) */
static int Cmd_msi(const ParsedCmd *cmd);   /* msi? IMU 모션 센서 스트리밍 (타이머 기반) */

/* D. Piezo 초음파 측정 */
static int Cmd_mpa(const ParsedCmd *cmd);   /* mpa? 피에조 TX/RX 회로 활성화 (전원 ON) */
static int Cmd_mpb(const ParsedCmd *cmd);   /* mpb? 피에조 TX/RX 회로 비활성화 (전원 OFF) */
static int Cmd_mpc(const ParsedCmd *cmd);   /* mpc? 피에조 버스트 발생 (주파수/사이클 제어) */
static int Cmd_mec(const ParsedCmd *cmd);   /* mec? 피에조 버스트 + 에코 캡처 (16비트 원시) */
static int Cmd_maa(const ParsedCmd *cmd);   /* maa? 6채널 전체 캡처 (비동기 전송) */
static int Cmd_mbb(const ParsedCmd *cmd);   /* mbb? 6채널 캡처 + 센서 측정 (배터리 + 온도 + IMU) */
static int Cmd_mcf(const ParsedCmd *cmd);   /* mcf? 피에조 파라미터 읽기 (FDS) */
static int Cmd_mcs(const ParsedCmd *cmd);   /* mcs? 피에조 파라미터 쓰기 (FDS) */


/* ---- 명령 테이블 ---- */

/* 명령 엔트리 구조체: TAG 문자열 + 활성화 여부 + 핸들러 함수 포인터 */
typedef struct {
    char tag[5];    /* 명령 TAG (예: "sta?") - 4글자 + NULL */
    bool enabled;   /* false면 핸들러가 호출되지 않음 (비활성 명령) */
    int  (*handler)(const ParsedCmd *cmd); /* 핸들러 함수 포인터 (1=성공, 0=실패) */
} CmdEntry;

/* 전체 명령 테이블 - 수신된 TAG와 순차 비교하여 일치하는 핸들러 호출 */
static CmdEntry g_cmd_table[] = {
    /* A. 디바이스 상태 제어 */
    { "msq?", true, Cmd_msq },
    { "mss?", true, Cmd_mss },
#if FEATURE_SECURE_CONNECTION
	{ "msr?", true, Cmd_msr },
#endif
	{ "cmd?", true, Cmd_cmd },

    /* C. 디바이스 정보 읽기, 쓰기 */
    { "mwh?", true, Cmd_mwh },
	{ "mws?", true, Cmd_mws },
	{ "mrh?", true, Cmd_mrh },
	{ "mrs?", true, Cmd_mrs },
    { "mpz?", true, Cmd_mpz },
	{ "mqz?", true, Cmd_mqz },
    { "mfv?", true, Cmd_mfv },

    /* D. 각종 센서 측정 */
    { "msn?", true, Cmd_msn },
	{ "mso?", true, Cmd_mso },
    { "msp?", true, Cmd_msp },
    { "msi?", true, Cmd_msi },

    /* E. Piezo 초음파 측정 */
	{ "mpa?", true, Cmd_mpa },
	{ "mpb?", true, Cmd_mpb },
	{ "mpc?", true, Cmd_mpc },
	{ "mec?", true, Cmd_mec },
	{ "maa?", true, Cmd_maa },
	{ "mbb?", true, Cmd_mbb },
	{ "mcf?", true, Cmd_mcf },
	{ "mcs?", true, Cmd_mcs },
};

/* 명령 테이블 엔트리 수 (컴파일 타임 계산) */
static const uint16_t g_cmd_count =
    (uint16_t)(sizeof(g_cmd_table) / sizeof(g_cmd_table[0]));

/* ---- 명령 디스패처 ----
 * 파싱된 TAG를 소문자로 변환 후, 명령 테이블을 순회하여 일치하는 핸들러 호출
 *
 * 반환값: 핸들러의 반환값 (1=성공), 매칭 실패 또는 비활성 명령이면 0
 */
static int dr_cmd_dispatch(const ParsedCmd *cmd)
{
    uint16_t i;
    char tag_lower[5];

    /* 수신된 TAG를 소문자로 변환 (대소문자 구분 없이 명령 매칭) */
    for (i = 0; i < 4 && cmd->tag[i]; i++) {
        tag_lower[i] = (cmd->tag[i] >= 'A' && cmd->tag[i] <= 'Z')
                       ? (cmd->tag[i] + 32) : cmd->tag[i];
    }
    tag_lower[i] = '\0';

    /* 명령 테이블 순차 검색 */
    for (i = 0; i < g_cmd_count; i++) {
        if (dr_tag_eq(tag_lower, g_cmd_table[i].tag)) {

            /* 비활성화된 명령이면 실행하지 않음 */
            if (!g_cmd_table[i].enabled) {
                if (g_plat.log && g_log_enable) {
                    g_plat.log("Command '%s' disabled\n", cmd->tag);
                }
                return 0;
            }

            /* 매칭된 핸들러 호출 */
            return g_cmd_table[i].handler(cmd);
        }
    }

    /* 테이블에 없는 TAG 수신 시 */
    if (g_plat.log && g_log_enable) {
        g_plat.log("Unknown TAG '%s'\n", cmd->tag);
    }
    return 0;
}

/*==============================================================================
 * 메인 파서 진입점 - 외부 모듈(BLE 수신 콜백 등)에서 호출
 *
 * buf: 수신된 BLE 패킷 원시 바이트
 * len: 패킷 길이
 * 반환값:
 *   1  = 명령 처리 성공
 *   0  = 알 수 없는 TAG 또는 비활성 명령
 *  -1  = CRC 실패 또는 파싱 실패 → 호출자가 레거시 파서로 위임 가능
 *============================================================================*/

int dr_cmd_parser(const uint8_t *buf, uint8_t len)
{
    ParsedCmd cmd;

    /* 패킷 파싱 (CRC 검증 포함) */
    if (!dr_parse_cmd(buf, len, &cmd)) {
        if (g_plat.log) g_plat.log("[PARSER] PARSE FAIL\r\n");

        /* 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/파싱 실패 → 음수 반환으로 레거시 파서에 위임 */
    }

    /* 수신 명령어 종류 확인용 로그 */
    //if (g_plat.log) g_plat.log("[PARSER] tag=%s\r\n", cmd.tag);

    /* 파싱 성공 → 명령 테이블에서 핸들러를 찾아 실행 */
    return dr_cmd_dispatch(&cmd);
}

/*==============================================================================
 * 각 명령 핸들러 구현부
 *============================================================================*/

/* msn? - 배터리 잔량 ADC 측정
 * 응답: battery_level_meas() 내부에서 BLE 전송 처리 */
static int Cmd_msn(const ParsedCmd *cmd)
{
    (void)cmd;
    battery_level_meas();  /* ADC로 배터리 전압 측정 → BLE 응답 전송 */
    return 1;
}

/* mso? - TMP235-Q1 온도 센서 전압 측정
 * SAADC로 TMP235 출력 전압을 측정하여 BLE로 응답
 * 응답은 tmp235_q1.c의 콜백에서 자동 전송 */
static int Cmd_mso(const ParsedCmd *cmd)
{
    (void)cmd;
    tmp235_voltage_level_meas();
    return 1;
}

/* msi? - IMU 모션 센서 원시 데이터 읽기
 *
 * 파라미터: data[0]='c'이면 연속 측정, 그 외 단발 측정
 *   단발 모드: 1회 측정 후 자동 중지
 *   연속 모드: 타이머 주기마다 반복 측정 (별도 중지 명령 필요)
 *
 * 동작: I2C 초기화 → 플래그 설정 → 메인 타이머 시작
 */
static int Cmd_msi(const ParsedCmd *cmd)
{
    /* IMU용 I2C 버스 초기화 (최초 호출 시 1회만 실행) */
    hw_i2c_init_once();

    motion_raw_data_enabled = true;   /* IMU 원시 데이터 전송 활성화 */
    ble_got_new_data = false;

    /* 'c' = 연속(continuous) 모드, 그 외 = 단발(single shot) 모드 */
    if (cmd->data_len > 0 && (char)cmd->data[0] == 'c') {
        motion_data_once = false;     /* 연속 측정 */
    } else {
        motion_data_once = true;      /* 1회 측정 후 자동 중지 */
    }

    main_timer_start();  /* 타이머 콜백에서 IMU 데이터 읽기 + BLE 전송 */

    return 1;
}

/*==============================================================================
 * J. 전원/리셋/버전/보안 핸들러
 *============================================================================*/

/* msq? - 디바이스 전원 OFF
 *
 * BLE 응답("rsq:") 전송 후 → 메인 루프에서 실제 전원 OFF 수행
 * 즉시 전원을 끄면 BLE 응답이 전달되지 않으므로, 플래그+타이머 방식 사용
 */
static int Cmd_msq(const ParsedCmd *cmd)
{
    uint16_t val = 0;
    dr_get_u16(cmd, 0, &val);
    if (g_plat.log) g_plat.log("[Cmd_msq] Power off val=%u\r\n", val);
    single_format_data(ble_bin_buffer, "rsq:", val);
    dr_binary_tx_safe(ble_bin_buffer, 2);
    go_device_power_off = true;   /* 메인 루프에서 전원 OFF 실행 예약 */
    main_timer_start();           /* 타이머 콜백에서 전원 OFF 처리 */
    return 1;
}

/* msr? - BLE 본딩 정보 삭제 + 시스템 리셋
 *
 * 동작 순서:
 *   1. BLE 응답("rsr:") 전송
 *   2. 본딩 삭제 플래그 + 리셋 상태를 FDS에 저장
 *   3. 5ms 딜레이 (FDS 쓰기 완료 대기)
 *   4. NVIC 시스템 리셋 플래그 설정 → 타이머에서 리셋 실행
 */
#if FEATURE_SECURE_CONNECTION
static int Cmd_msr(const ParsedCmd *cmd)
{
    uint16_t val = 0;
    dr_get_u16(cmd, 0, &val);
    //if (g_plat.log) g_plat.log("[Cmd_msr] Bond delete + reset val=%u\r\n", val);
    single_format_data(ble_bin_buffer, "rsr:", val);
    dr_binary_tx_safe(ble_bin_buffer, 2);

    /* 본딩 삭제 + 리셋 상태를 설정에 저장 (재부팅 후에도 유지) */
    bond_data_delete = true;
    m_config.bond_data_delete = (uint8_t)bond_data_delete;
    m_reset_status = 2;                  /* 리셋 상태 코드 2 = 본딩 삭제 리셋 */
    m_config.reset_status = m_reset_status;
    config_save();                       /* FDS에 설정 영구 저장 */
    nrf_delay_ms(5);                     /* Flash 쓰기 완료 대기 */
    go_NVIC_SystemReset = true;          /* 시스템 리셋 예약 */
    main_timer_start();
    return 1;
}
#endif

/* mss? - 디바이스 소프트 리셋 (리부팅)
 *
 * msr?과 유사하지만 본딩 정보는 삭제하지 않음
 * 리셋 상태 코드를 FDS에 저장하여 부팅 시 리셋 원인 확인 가능 =======> val 파라미터 확인
 */
static int Cmd_mss(const ParsedCmd *cmd)
{
    uint16_t val = 0;
    dr_get_u16(cmd, 0, &val);
    //if (g_plat.log) g_plat.log("[Cmd_mss] Device reset val=%u\r\n", val);
    single_format_data(ble_bin_buffer, "rss:", val);
    dr_binary_tx_safe(ble_bin_buffer, 2);

    m_reset_status = 2;
    m_config.reset_status = m_reset_status;
    config_save();
    nrf_delay_ms(5);
    go_NVIC_SystemReset = true;
    main_timer_start();
    return 1;
}

/* mfv? - 펌웨어 버전 읽기
 *
 * 응답: "rfv:" + 12자 ASCII 버전 문자열 (DR_DEVICE_VERSION)
 * 구 명령 ssv?에서 mfv?로 변경 (26.03.13)
 */
static int Cmd_mfv(const ParsedCmd *cmd)
{
    (void)cmd;
    ascii_format_data(ble_bin_buffer, "rfv:", DR_DEVICE_VERSION, 12);
    dr_binary_tx_safe(ble_bin_buffer, 8);  /* 4(TAG) + 12(버전) = 16바이트 = 8워드 */
    return 1;
}

/* mpa? - 피에조 초음파 TX/RX 회로 활성화
 *
 * 동작: 피에조 전원 ON → 시스템 초기화 (DAC, MUX, ADC 설정)
 * 응답: "rpa:" + 1 (성공)
 * 주의: mec?/mdc?/maa? 실행 전에 반드시 먼저 호출해야 함
 */
static int Cmd_mpa(const ParsedCmd *cmd)
{
    (void)cmd;

    dr_piezo_power_on();  /* 부팅 시 init 완료, 전원만 ON */

    if (g_plat.tx_bin)
    {
        single_format_data(ble_bin_buffer, "rpa:", 1);
        dr_binary_tx_safe(ble_bin_buffer, 3);
    }

    return 1;
}

/* mpb? - 피에조 초음파 TX/RX 회로 비활성화 (전원 OFF)
 *
 * 측정 완료 후 전력 소모 절감을 위해 호출
 * 응답: "rpb:" + 1 (성공)
 */
static int Cmd_mpb(const ParsedCmd *cmd)
{
    (void)cmd;

    dr_piezo_power_off();  /* 피에조 보드 전원 OFF */

    if (g_plat.tx_bin) {
        single_format_data(ble_bin_buffer, "rpb:", 1);
        dr_binary_tx_safe(ble_bin_buffer, 3);
    }

    return 1;
}


/**
 * @brief mpc? - 피에조 버스트 발생 명령 (주파수/사이클/채널 선택)
 *
 * 초음파 트랜스듀서에 지정된 주파수와 사이클 수로 버스트 펄스를 발생시킴
 * 에코 캡처 없이 버스트만 발생 (테스트/디버그용)
 *
 * 파라미터 (Little-Endian uint16 x 3):
 *   word 0: cycles (3~7, 기본값=5) - 버스트 펄스 사이클 수
 *   word 1: freq_option (기본값=1) - 주파수 선택
 *           0=1.8MHz, 1=2.1MHz(기본), 2=2.0MHz, 3=1.7MHz, 4=2.2MHz
 *   word 2: piezo_ch (0~7, 기본값=0) - 피에조 채널 선택
 *
 * 응답: "rpc:" + cycles (성공), "rpc:" + 2 (범위 초과 에러)
 *
 * 사용 예시 (Harbour에서):
 *   mpc?0x05,0x00,0x01,0x00 → 5사이클 @ 2.1MHz
 *   mpc?0x05,0x00,0x00,0x00 → 5사이클 @ 1.8MHz
 *   mpc?0x07,0x00,0x02,0x00 → 7사이클 @ 2.0MHz
 */
static int Cmd_mpc(const ParsedCmd *cmd)
{
    uint16_t cycles = 5;      /* 기본 5사이클 */
    uint16_t freq_option = 1; /* 기본 2.1MHz (0=1.8, 1=2.1, 2=2.0, 3=1.7, 4=2.2) */
    uint16_t piezo_ch = 0;    /* 기본 채널 0 (0~7) */

    /* 파라미터 추출 (데이터 부족 시 기본값 유지) */
    (void)dr_get_u16(cmd, 0, &cycles);      /* word 0: 사이클 수 */
    (void)dr_get_u16(cmd, 1, &freq_option); /* word 1: 주파수 옵션 */
    (void)dr_get_u16(cmd, 2, &piezo_ch);    /* word 2: 피에조 채널 */

    /* 채널 범위 검증: 초과 시 0으로 리셋 */
    if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0;

    /* 사이클 범위 검증: 3~7 유효 */
    if (cycles < 3 || cycles > 7)
    {
        dr_ble_return_1("rpc:", 2);  /* 에러 응답: 범위 초과 */
        return 1;
    }

    /* MUX로 피에조 채널 선택 (0~7번 트랜스듀서 중 하나) */
    dr_piezo_select_channel((uint8_t)piezo_ch);

    /* 선택된 주파수로 버스트 펄스 발생 */
    switch (freq_option)
    {
        case 0:  dr_piezo_burst_sw_18mhz((uint8_t)cycles);  break;  /* 1.8MHz */
        case 2:  dr_piezo_burst_sw_20mhz((uint8_t)cycles);  break;  /* 2.0MHz */
        case 3:  dr_piezo_burst_sw_17mhz((uint8_t)cycles);  break;  /* 1.7MHz */
        case 4:  dr_piezo_burst_sw_22mhz((uint8_t)cycles);  break;  /* 2.2MHz */
        /*case 9: dr_piezo_burst_sw_19mhz((uint8_t)cycles); break;*/  /* 1.9MHz (미사용) */
        case 1:
        default: dr_piezo_burst_sw((uint8_t)cycles);         break;  /* 2.1MHz (기본) */
    }

    /* 성공 응답: "rpc:" + 실행된 사이클 수 */
    dr_ble_return_1("rpc:", (uint8_t)cycles);

    return 1;
}


/**
 * @brief mec? - 피에조 버스트 + 에코 캡처 (16비트 원시 데이터, 무압축)
 *
 * 12비트 압축(mdc?)과 달리 ADC 원시 16비트 값을 그대로 전송
 * 샘플 수가 적은 근거리 측정(~25cm)에 적합
 *
 * 파라미터 (Little-Endian uint16 x 6):
 *   word 0: freq_option (기본=0)   - 주파수 선택 (0=1.8MHz, 1=2.1MHz, ...)
 *   word 1: delay_us (기본=20)     - 버스트 후 ADC 시작 지연 (us)
 *   word 2: num_samples (기본=140) - ADC 샘플 수 (140샘플 ≒ 20cm 거리)
 *   word 3: cycles (기본=5)        - 버스트 사이클 수 (3~7)
 *   word 4: averaging (기본=1)     - 평균화 횟수 (1~1000, 노이즈 저감용)
 *   word 5: piezo_ch (기본=0)      - 피에조 채널 (0~7)
 *
 * 응답 멀티패킷 형식 (16비트 원시):
 *   헤더 패킷 ("reb:"): total_pkts, peak, idx, baseline, samples
 *   데이터 패킷 ("red:"): pkt_idx + 16비트 ADC 원시 데이터
 *   종료 패킷 ("ree:"): total_bytes_sent
 *
 * 16비트 포맷: 샘플당 2바이트 (Little-Endian)
 *   예: 140샘플(20cm) = 280바이트 = 약 2패킷
 */
static int Cmd_mec(const ParsedCmd *cmd)
{
    uint16_t freq_option = 0;    /* 기본 1.8MHz */
    uint16_t delay_us = 20;      /* 기본 20us 딜레이 */
    uint16_t num_samples = 140;  /* 기본 100샘플 */
    uint16_t cycles = 5;         /* 기본 5사이클 (유효: 3~7) */
    uint16_t averaging = 1;      /* 기본 1 (평균화 없음), 최대 1000 */
    uint16_t piezo_ch = 0;       /* 기본 채널 0 (유효: 0~7) */

    /* 피에조 전원이 꺼져 있으면 켜기 */
    if (!dr_piezo_is_power_on())
    {
        //if (g_plat.log) g_plat.log("[Cmd_mec] TX/RX Sleep -> Active\r\n");
        dr_piezo_power_on();
    }

    /* 6개 파라미터 순서대로 추출 (데이터 부족 시 기본값 유지) */
    (void)dr_get_u16(cmd, 0, &freq_option);  /* word 0: 주파수 옵션 */
    (void)dr_get_u16(cmd, 1, &delay_us);     /* word 1: 버스트→ADC 딜레이 */
    (void)dr_get_u16(cmd, 2, &num_samples);  /* word 2: ADC 샘플 수 */
    (void)dr_get_u16(cmd, 3, &cycles);       /* word 3: 버스트 사이클 */
    (void)dr_get_u16(cmd, 4, &averaging);    /* word 4: 평균화 횟수 */
    (void)dr_get_u16(cmd, 5, &piezo_ch);     /* word 5: 피에조 채널 */

    /* 평균화 횟수 범위 검증: 1~1000 */
    if (averaging == 0) averaging = 1;
    if (averaging > 1000) averaging = 1000;

    /* 피에조 채널 범위 검증 */
    if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0;
    
    /*
    if (g_plat.log && g_log_enable) {
        const char *freq_str = (freq_option == 0) ? "1.8MHz" :
                               (freq_option == 1) ? "2.1MHz" :
                               (freq_option == 2) ? "2.0MHz" :
                               (freq_option == 3) ? "1.7MHz" :
                               (freq_option == 4) ? "2.2MHz" :
                               (freq_option == 9) ? "1.9MHz" : "unknown";
        g_plat.log("[Parameter] freq=%u (%s), delay=%uus, samples=%u, cycles=%u, avg=%u, piezo=%u\r\n",
                   freq_option, freq_str, delay_us, num_samples, cycles, averaging, piezo_ch);
    }
    */

    /* 통합 버스트+캡처+전송 함수 호출 */
    dr_adc_err_t err = dr_adc_burst_capture_transmit(
        (uint8_t)freq_option, delay_us, num_samples, (uint8_t)cycles,
        (uint16_t)averaging, (uint8_t)piezo_ch, ble_bin_buffer, 0);

    /* 에러 발생 시 에러 코드 + 요청 샘플 수 응답 */
    if (err != DR_ADC_OK) {
        dr_ble_return_2("rer:", 0xEE00 | (uint16_t)err, num_samples);
    }

    /* raw 데이터 덤프
    if (g_plat.log && g_log_enable) {
        g_plat.log("[Cmd_mec] result=%d\r\n", err);

        const uint16_t *buf = dr_adc_get_echo_buffer();
        g_plat.log("[mec] CH%u raw (%u samples):\r\n", piezo_ch, num_samples);
        for (uint16_t i = 0; i < num_samples; i++) {
            g_plat.log("%u ", buf[i]);
            if ((i + 1) % 16 == 0) g_plat.log("\r\n");
        }
        if (num_samples % 16 != 0) g_plat.log("\r\n");
    }*/

    /* 측정 완료 후 Piezo TX/RX Sleep */
    dr_piezo_power_off();

    return 1;
}


/* cmd? - GPIO 핀 직접 제어 (디버그/테스트용)
 *
 * 파라미터 (Little-Endian uint16 x 3, 총 6바이트):
 *   v1: GPIO 포트 번호 (0 또는 1, nRF52는 2개 포트)
 *   v2: GPIO 핀 번호 (0~31)
 *   v3: 출력 상태 (1=HIGH, 0=LOW)
 *
 * 동작: 지정된 핀을 출력 모드로 설정 후 HIGH/LOW 제어
 * 응답: "rmd:" + v1(포트) + v2(핀) + v3(상태) - 에코 확인
 *
 * 주의: 잘못된 핀 제어 시 하드웨어 손상 가능 - 디버그용으로만 사용
 */
static int Cmd_cmd(const ParsedCmd *cmd)
{
    uint16_t v1, v2, v3;
    uint32_t pin_number;

    /* 최소 6바이트(3 워드) 필요 */
    if (cmd->data_len < 6) {
        dr_ble_return_1("rmd:", 0);  /* 데이터 부족 에러 */
        return 1;
    }

    /* Little Endian 파라미터 추출 */
    v1 = (uint16_t)cmd->data[0] | ((uint16_t)cmd->data[1] << 8);  /* 포트 (0~1) */
    v2 = (uint16_t)cmd->data[2] | ((uint16_t)cmd->data[3] << 8);  /* 핀 (0~31) */
    v3 = (uint16_t)cmd->data[4] | ((uint16_t)cmd->data[5] << 8);  /* 상태 (1=HIGH, 0=LOW) */

    /* nRF52 GPIO 핀 번호 계산: port * 32 + pin */
    pin_number = NRF_GPIO_PIN_MAP(v1, v2);

    /* 핀을 출력 모드로 설정 */
    nrf_gpio_cfg_output(pin_number);

    /* HIGH 또는 LOW 출력 */
    if (v3 == 1) {
        nrf_gpio_pin_set(pin_number);      /* HIGH 출력 */
    } else {
        nrf_gpio_pin_clear(pin_number);    /* LOW 출력 */
    }

    /* 응답: 포트, 핀, 상태 에코 */
    dr_ble_return_3("rmd:", v1, v2, v3);

    return 1;
}


/**
 * @brief maa? - 4채널 전체 캡처 명령 (비동기 버전)
 *
 * 4개 피에조 채널(CH0~CH3)을 순차적으로 버스트+캡처하고 BLE로 전송
 * 블로킹 방식 대신 비동기 상태 머신으로 구현하여 BLE 스택 안정성 확보
 *
 * 파라미터: mode (uint16, word 0)
 *   mode=0: 16비트 원시 데이터 비동기 전송 (유일하게 지원)
 *
 * 비동기 아키텍처:
 *   - maa_async_start()가 CH0 캡처 시작 및 헤더 전송
 *   - BLE_NUS_EVT_TX_RDY 콜백이 나머지 데이터/채널 전송 구동
 *   - 블로킹 없음 → SoftDevice 이벤트 정상 처리
 *   - BLE TX 버퍼 오버플로우로 인한 펌웨어 브릭 방지
 *
 * 고정 파라미터 (하드코딩):
 *   주파수 = 1.8MHz, 딜레이 = 10us, 샘플 수 = 140
 *   사이클 = 7, 평균화 = 5회
 *
 * 응답 형식:
 *   각 채널(CH0~CH3)마다:
 *     reb: [총패킷수(2)] [피크(2)] [인덱스(2)] [기준선(2)] [샘플수(2)]
 *     red: [패킷순번(2)] [데이터...]
 *   전체 완료:
 *     raa: [상태(2)]
 *
 * 버전 마커: 0xA000 (vA) = 비동기 4채널
 */
/* 피에조 캡처 파라미터: FDS(m_config)에서 로드, 앱에서 변경 가능 */

static int Cmd_maa(const ParsedCmd *cmd)
{
    dr_adc_err_t err;

    /* 이전 캡처가 진행 중인지 확인 (비동기이므로 중복 실행 방지) */
    if (maa_async_is_busy())
    {
        dr_ble_return_1("raa:", 0xFFFE);  /* 처리 중(Busy) 에러 */
        return 1;
    }

    /* Piezo TX/RX 전원: OFF일 경우 ON → maa_async_start()에서 캡처 완료 후 OFF */
    if (!dr_piezo_is_power_on())
    {
        //if (g_plat.log) g_plat.log("[Cmd_maa] TX/RX Sleep -> Active\r\n");
        dr_piezo_power_on();
    }

    /*=======================================================================
     * 비동기 6채널 캡처 시작
     *   - maa_async_start(): CH0 캡처 실행 + 첫 헤더 패킷 전송
     *   - 이후 패킷은 BLE_NUS_EVT_TX_RDY 콜백에서 자동 전송
     *=======================================================================*/
    err = maa_async_start(
        m_config.piezo_freq_option,
        m_config.piezo_delay_us,
        m_config.piezo_num_samples,
        m_config.piezo_cycles,
        m_config.piezo_averaging,
        ble_bin_buffer
    );

    if (err != DR_ADC_OK)
    {
        /* 시작 실패 → 에러 응답 전송 (앱 타임아웃 방지) */
        if (g_plat.log) g_plat.log("[Cmd_maa] start failed err=%d\r\n", err);
        single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
        dr_binary_tx_safe(ble_bin_buffer, 3);
        dr_piezo_power_off();
        return 1;
    }

    return 1;
}


/**
 * @brief mbb? 비동기 캡처 완료 후 콜백 - 센서 측정 체인 실행
 *
 * raa: 전송 + 피에조 전원 OFF 이후 호출
 * info4 모드로 센서값을 전역 변수에 저장한 뒤, rbb: 패킷으로 일괄 전송
 * SAADC 측정은 비동기(콜백)이므로 dr_sd_delay_ms()로 완료 대기
 *
 * 순서: 배터리 → IMU → (Piezo TX/RX ON) → 온도
 * 응답: rbb: [배터리mV(2)] [IMU 6축(12)] [온도°Cx100(2)] = 20바이트
 */
static void all_sensors(void)
{
    uint8_t *buf;
    uint32_t timeout_cnt;

    info4 = true;  /* 센서값을 전역 변수에 저장 (BLE 전송 안 함) */

    /* 1. 배터리 전압 측정 → info_batt에 저장 */
    battery_level_meas();
    dr_sd_delay_ms(1);   /* SAADC 콜백 완료 대기 */

    /* 2. IMU 6축 단발 읽기 → info_imu[6]에 저장 */
    hw_i2c_init_once();
    imu_read_direct();

    /* 3. 온도 측정 → info_temp에 저장 (TMP235는 Piezo TX/RX 전원 필요) */
    if (!dr_piezo_is_power_on())
    {
        dr_piezo_power_on();
    }

    tmp235_saadc_done = false;
    tmp235_voltage_level_meas();
    
    for (timeout_cnt = 0; !tmp235_saadc_done && timeout_cnt < 100; timeout_cnt++)
    {
        dr_sd_delay_ms(1);  /* 콜백 오면 즉시 탈출, 최대 100ms, 타임아웃 시 이전 값 또는 쓰레기값.. */
    }

    info4 = false;

    /* rbb: 패킷 조립 및 전송 : [TAG 4B] [배터리 2B] [IMU 12B] [온도 2B] = 20바이트 = 10워드 */
    buf = ble_bin_buffer;

    buf[0] = 'r'; buf[1] = 'b'; buf[2] = 'b'; buf[3] = ':';
    buf[4] = (uint8_t)(info_batt & 0xFF);
    buf[5] = (uint8_t)(info_batt >> 8);

    for (int i = 0; i < 6; i++)
    {
        buf[6 + i * 2]     = (uint8_t)(info_imu[i] & 0xFF);
        buf[6 + i * 2 + 1] = (uint8_t)(info_imu[i] >> 8);
    }

    buf[18] = (uint8_t)(info_temp & 0xFF);
    buf[19] = (uint8_t)(info_temp >> 8);
    
    dr_binary_tx_safe(buf, 10);  /* 20바이트 = 10워드 */

    /* 배터리, IMU, 온도 확인용 로그 */
    //if (g_plat.log) g_plat.log("-------------------------------------------------------------------------------------\r\n[Battery] %u\r\n[IMU] %d,%d,%d,%d,%d,%d\r\n[Temperature] %u\r\n\r\n", info_batt, (int16_t)info_imu[0], (int16_t)info_imu[1], (int16_t)info_imu[2],
    //(int16_t)info_imu[3], (int16_t)info_imu[4], (int16_t)info_imu[5], info_temp);
}


/**
 * @brief mbb? - 6채널 전체 캡처 + 센서 측정 (배터리/온도/IMU)
 *
 * 센서 측정(rbb:) → 6채널 비동기 캡처(reb:/red:/raa:) → TX/RX OFF
 *
 * 응답 흐름:
 *   1) 센서 측정: rbb: [배터리(2) + IMU 6축(12) + 온도(2)]
 *   2) 각 채널(CH0~CH5): reb: [헤더] → red: [데이터...]
 *   3) 캡처 완료: raa: [상태]
 */

static int Cmd_mbb(const ParsedCmd *cmd)
{
    dr_adc_err_t err;

    all_sensors();  /* 배터리, IMU, 온도 센서 먼저 측정 */

    if (maa_async_is_busy())
    {
        dr_ble_return_1("raa:", 0xFFFE);
        return 1;
    }

    /* 측정 파라미터 확인용 로그 
    if (g_plat.log) g_plat.log("[Parameter] freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n\r\n",
        m_config.piezo_freq_option, m_config.piezo_cycles,
        m_config.piezo_averaging, m_config.piezo_delay_us,
        m_config.piezo_num_samples);*/

    /* 전채널 선캡처 모드: 모든 채널 캡처 완료 후 BLE 전송하도록 */
    maa_async_set_pre_capture_all(true);

    /* 비동기 6채널 캡처 시작 (FDS m_config 파라미터 사용) */
    err = maa_async_start(
        m_config.piezo_freq_option,
        m_config.piezo_delay_us,
        m_config.piezo_num_samples,
        m_config.piezo_cycles,
        m_config.piezo_averaging,
        ble_bin_buffer
    );

    if (err != DR_ADC_OK)
    {
        if (g_plat.log) g_plat.log("[Cmd_mbb] start failed err=%d\r\n", err);
        single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
        dr_binary_tx_safe(ble_bin_buffer, 3);
        dr_piezo_power_off();
        return 1;
    }

    return 1;
}


/**
 * @brief mcf? - 피에조 파라미터 읽기 (FDS)
 *
 * 응답: rcf: [freq(2)] [cycles(2)] [avg(2)] [delay_us(2)] [num_samples(2)] = 14바이트 = 7워드
 */
static int Cmd_mcf(const ParsedCmd *cmd)
{
    (void)cmd;

    uint8_t *buf = ble_bin_buffer;
    buf[0] = 'r'; buf[1] = 'c'; buf[2] = 'f'; buf[3] = ':';
    buf[4] = m_config.piezo_freq_option;  buf[5] = 0;
    buf[6] = m_config.piezo_cycles;       buf[7] = 0;
    buf[8]  = (uint8_t)(m_config.piezo_averaging   & 0xFF); buf[9]  = (uint8_t)(m_config.piezo_averaging   >> 8);
    buf[10] = (uint8_t)(m_config.piezo_delay_us    & 0xFF); buf[11] = (uint8_t)(m_config.piezo_delay_us    >> 8);
    buf[12] = (uint8_t)(m_config.piezo_num_samples & 0xFF); buf[13] = (uint8_t)(m_config.piezo_num_samples >> 8);
    dr_binary_tx_safe(buf, 7);  /* 14바이트 = 7워드 */

    return 1;
}


/**
 * @brief mcs? - 피에조 파라미터 쓰기 (FDS)
 *
 * 파라미터: [freq(2)] [cycles(2)] [avg(2)] [delay_us(2)] [num_samples(2)] = 10바이트
 * 응답:     rcs: [저장된 5개 값] = 14바이트 = 7워드
 */
static int Cmd_mcs(const ParsedCmd *cmd)
{
    if (cmd->data_len < 10)
    {
        if (g_plat.log) g_plat.log("[Cmd_mcs] missing params (data_len=%u)\r\n", cmd->data_len);
        dr_ble_return_1("rcs:", 0xFFFF);
        return 1;
    }

    uint16_t freq, cycles, averaging, delay_us, num_samples;
    dr_get_u16(cmd, 0, &freq);
    dr_get_u16(cmd, 1, &cycles);
    dr_get_u16(cmd, 2, &averaging);
    dr_get_u16(cmd, 3, &delay_us);
    dr_get_u16(cmd, 4, &num_samples);

    m_config.piezo_freq_option = (uint8_t)freq;
    m_config.piezo_cycles      = (uint8_t)cycles;
    m_config.piezo_averaging   = averaging;
    m_config.piezo_delay_us    = delay_us;
    m_config.piezo_num_samples = num_samples;
    config_save();

    /*
    if (g_plat.log) g_plat.log("[Cmd_mcs] saved: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
        m_config.piezo_freq_option, m_config.piezo_cycles,
        m_config.piezo_averaging, m_config.piezo_delay_us,
        m_config.piezo_num_samples);
    */

    uint8_t *buf = ble_bin_buffer;
    buf[0] = 'r'; buf[1] = 'c'; buf[2] = 's'; buf[3] = ':';
    buf[4] = m_config.piezo_freq_option;  buf[5] = 0;
    buf[6] = m_config.piezo_cycles;       buf[7] = 0;
    buf[8]  = (uint8_t)(m_config.piezo_averaging   & 0xFF); buf[9]  = (uint8_t)(m_config.piezo_averaging   >> 8);
    buf[10] = (uint8_t)(m_config.piezo_delay_us    & 0xFF); buf[11] = (uint8_t)(m_config.piezo_delay_us    >> 8);
    buf[12] = (uint8_t)(m_config.piezo_num_samples & 0xFF); buf[13] = (uint8_t)(m_config.piezo_num_samples >> 8);
    dr_binary_tx_safe(buf, 7);  /* 14바이트 = 7워드 */

    return 1;
}


/*==============================================================================
 * 설정: HW/시리얼 넘버 FDS 읽기/쓰기
 *
 * FDS(Flash Data Storage)에 디바이스 식별 정보를 영구 저장
 * 제조 시 공장에서 한 번 기록하고, 이후 읽기로 확인
 *============================================================================*/

/* mwh? - HW 넘버 FDS에 쓰기
 *
 * 데이터: 12바이트 ASCII HW 넘버 (예: "VB2025A00001")
 * 응답: "rwh:" + 저장된 HW 넘버 에코
 */
static int Cmd_mwh(const ParsedCmd *cmd)
{
    char buf[13];

    /* 최소 12바이트 데이터 필요 */
    if (cmd->data_len < 12)
    {
        dr_ble_return_1("rwh:", 0xFFFF);  /* 데이터 부족 에러 */
        return 1;
    }

    /* ASCII 추출 → 전역 변수 + 설정 구조체에 복사 → FDS 저장 */
    dr_get_ascii(cmd, 0, buf, 12);
    memcpy(HW_NO, buf, 12);
    memcpy(m_config.hw_no, buf, 12);
    config_save();  /* FDS(Flash Memory)에 저장 */

    ascii_format_data(ble_bin_buffer, "rwh:", buf, 12);
    dr_binary_tx_safe(ble_bin_buffer, 8);
    return 1;
}

/* mws? - 시리얼 넘버 FDS에 쓰기
 *
 * 데이터: 12바이트 ASCII 시리얼 넘버
 * 응답: "rws:" + 저장된 시리얼 넘버 에코
 */
static int Cmd_mws(const ParsedCmd *cmd)
{
    char buf[13];

    if (cmd->data_len < 12) {
        dr_ble_return_1("rws:", 0xFFFF);  /* 데이터 부족 에러 */
        return 1;
    }

    dr_get_ascii(cmd, 0, buf, 12);
    memcpy(SERIAL_NO, buf, 12);
    memcpy(m_config.serial_no, buf, 12);
    config_save();  /* FDS(Flash Memory)에 저장 */

    ascii_format_data(ble_bin_buffer, "rws:", buf, 12);
    dr_binary_tx_safe(ble_bin_buffer, 8);
    return 1;
}

/* mrh? - HW 넘버 FDS에서 읽기
 * FDS의 설정 구조체에서 HW 넘버를 읽어 BLE로 응답 */
static int Cmd_mrh(const ParsedCmd *cmd)
{
    (void)cmd;
    memcpy(HW_NO, m_config.hw_no, 12);  /* FDS 설정 → 전역 변수 동기화 */
    ascii_format_data(ble_bin_buffer, "rrh:", HW_NO, 12);
    dr_binary_tx_safe(ble_bin_buffer, 8);  /* 4(TAG) + 12(HW넘버) = 16바이트 = 8워드 */
    return 1;
}

/* mrs? - 시리얼 넘버 FDS에서 읽기 */
static int Cmd_mrs(const ParsedCmd *cmd)
{
    (void)cmd;
    memcpy(SERIAL_NO, m_config.serial_no, 12);  /* FDS 설정 → 전역 변수 동기화 */
    ascii_format_data(ble_bin_buffer, "rrs:", SERIAL_NO, 12);
    dr_binary_tx_safe(ble_bin_buffer, 8);
    return 1;
}


/*==============================================================================
 * 패스키: BLE 페어링 시 사용하는 6자리 숫자 (예: "123456")
 *============================================================================*/

/* mpz? - BLE 패스키 FDS에 쓰기
 * 패킷: mpz? + ASCII 6자리 패스키 (예: "654321")
 * 응답: rpz: + 저장된 패스키 에코 (6바이트)
 */
static int Cmd_mpz(const ParsedCmd *cmd)
{
    char passkey[7] = {0};
    dr_get_ascii(cmd, 0, passkey, 6);

    memcpy(m_static_passkey, passkey, 6);
    memcpy(m_config.static_passkey, m_static_passkey, 6);
    config_save();  /* FDS(Flash Memory)에 저장 */

    ascii_format_data(ble_bin_buffer, "rpz:", passkey, 6);
    dr_binary_tx_safe(ble_bin_buffer, 5);
    return 1;
}

/* mqz? - BLE 패스키 FDS에서 읽기
 * 패킷: mqz? (파라미터 없음)
 * 응답: rqz: + 현재 저장된 패스키 (ASCII 6바이트)
 */
static int Cmd_mqz(const ParsedCmd *cmd)
{
    (void)cmd;
    memcpy(m_static_passkey, m_config.static_passkey, 6);

    ascii_format_data(ble_bin_buffer, "rqz:", m_static_passkey, 6);
    dr_binary_tx_safe(ble_bin_buffer, 5);
    return 1;
}

/*==============================================================================
 * IMU: 6축 원시 데이터 (단발 읽기)
 *============================================================================*/

/* msp? - IMU 가속도(xyz) + 자이로(xyz) 원시 데이터 단발 읽기
 *
 * I2C로 IMU 레지스터를 직접 읽어 BLE로 전송
 * 타이머/인터럽트 없이 즉시 실행 (동기 방식)
 *
 * 응답: rsp: + 6 x uint16_t
 *   [accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z]
 */
static int Cmd_msp(const ParsedCmd *cmd)
{
    (void)cmd;

    hw_i2c_init_once();  /* I2C 버스 초기화 (최초 1회) */

    /* IMU 레지스터 직접 읽기 - 타이머, DRDY 인터럽트, 콜백 없이 동기 실행 */
    int rc = imu_read_direct();

    return 1;
}
