21.4.3.1.1. `strcmp` 최적화를 통한 서브 커맨드 처리 및 `print_usage()` 연동을 통한 잘못된 명령 피드백 제어

21.4.3.1.1. strcmp 최적화를 통한 서브 커맨드 처리 및 print_usage() 연동을 통한 잘못된 명령 피드백 제어

사용자가 custom_app 뒤에 붙이는 커스텀 명령어들(argv)은 기계가 아닌 인간의 손끝에서 타이핑되어 들어온다. 이는 곧 오타, 누락, 문법 오류라는 인간의 필연적인 실수를 동반한다는 뜻이다.

custom_command() 내부의 strcmp 분기 로직은 단지 올바른 명령어를 찾아내는 것뿐만 아니라, 사용자의 헛발질을 정확히 캐치하여 안전한 기본 상태(Safe Default)로 회귀시키고, 무엇이 틀렸는지 친절하게 알려주는 일종의 ‘방어벽’ 역할을 수행해야 한다.

1. print_usage(): 펌웨어의 친절한 가이드북

여러분이 인터넷에서 linux ls --help를 치면 ls 명령어의 사용법이 화면을 꽉 채우듯, PX4 모듈 역시 사용자가 헤맬 때마다 나타나는 나만의 사용 설명서 함수인 **print_usage()**를 의무적으로 구현해야 한다.

int CustomApp::print_usage(const char *reason)
{
    if (reason) {
        // 사용자가 왜 쫓겨났는지(에러 사유)를 붉은 글씨로 먼저 출력
        PX4_WARN("%s\n", reason);
    }

    PRINT_MODULE_DESCRIPTION(
        R"DESCR_STR(
### Description
This is an awesome custom module for PX4.
)DESCR_STR");

    PRINT_MODULE_USAGE_NAME("custom_app", "template");
    PRINT_MODULE_USAGE_COMMAND("start");
    PRINT_MODULE_USAGE_PARAM_FLAG('f', "Optional fast mode flag", true);
    PRINT_MODULE_USAGE_COMMAND_DESCR("test_motor", "Run motor test");
    PRINT_MODULE_USAGE_ARG("motor_index", "Index of the motor (0-3)", false);
    PRINT_MODULE_USAGE_DEFAULT_COMMANDS();

    return 0;
}

이 함수 안에는 PRINT_MODULE_USAGE_ 로 시작하는 독특한 매크로들이 잔뜩 들어있다. 이 매크로들은 놀랍게도 여러분이 터미널에서 칠 때만 텍스트로 나타나는 것이 아니다. PX4 빌드 시스템은 이 코드들을 스크래핑(Scraping)하여 PX4 공식 웹사이트의 모듈 레퍼런스(Reference) 문서 HTML 페이지를 자동으로 렌더링해서 만들어낸다.

따라서 이곳의 주석과 가이드를 대충 한 줄로 적고 넘어가는 것은 전 세계의 수많은 오픈소스 동료 개발자들에게 빅엿(?)을 날리는 행위와 같다.

2. 오타와 꼼수에 대응하는 strcmp 매트릭스

자, 이제 이 든든한 print_usage()를 방패 삼아, 앞서 짠 strcmp 다중 분기 로직을 해커의 시선(Edge Case)으로 뚫어보자.

int CustomApp::custom_command(int argc, char *argv[])
{
    const char *cmd = argv[0];
    CustomApp *inst = get_instance();

    if (!strcmp(cmd, "test_motor")) {
        // 해커의 공격 1: 파라미터 누락 (custom_app test_motor 만 치고 엔터)
        if (argc < 2) {
            return print_usage("Error: 'test_motor' requires a motor_index.");
        }
        
        // 해커의 공격 2: 범위를 벗어난 쓰레기 값 (custom_app test_motor 999)
        int motor_idx = atoi(argv[1]);
        if (motor_idx < 0 || motor_idx > 3) {
            return print_usage("Error: motor_index must be between 0 and 3.");
        }
        
        inst->test_single_motor(motor_idx);
        return 0; // 정상 패스!
    }

    // 해커의 공격 3: 아예 스펠링을 틀림 (custom_app tset_mtoro)
    // 위쪽의 모든 if (!strcmp(...) ) 그물을 매끄럽게 통과해 버림
    
    // 최종 방어선 (Fallback)
    return print_usage("Unrecognized command.");
}

위의 코드처럼, custom_command() 함수는 위에서 아래로 물 흐르듯 떨어지는 폭포수(Waterfall) 검증 구조를 가져야 한다.

모든 strcmp 분기 블록은 자신이 담당하는 명령어의 인자(Arguments) 개수(argc)와 값의 범위를 철저히 의심해야 하며, 조금이라도 이상하면 즉시 자기 할 일을 멈추고 print_usage("구체적 이유")를 호출하며 return 해버려야 한다.

그리고 함수의 맨 밑바닥(Bottom)에는 그 어떤 strcmp 그물에도 걸리지 않은 정체불명의 쓰레기 텍스트들을 몽땅 집어삼켜서 부드럽게 뱉어내는 최후의 print_usage("Unrecognized command") 짬처리 로직이 반드시 입을 벌리고 있어야 한다.

이 완벽한 라우팅 체계가 갖춰지면 사용자들은 절대로 우리 모듈을 런타임에 크래시 낼 수 없다. 이제 명령어 처리를 마스터했으니, 기체의 튜닝 값을 저장하고 불러오는 심장부, 파라미터(Parameter) 시스템을 내 C++ 데이터 구조와 어떻게 결속 시킬 수 있는지 다음 장(21.5)에서 우아하게 연결해 보자.