21.8.2.1. `enum class AppState { INIT, CLIMB, TRIGGER, CONFIRM_ACK, RTL }` 상태 정의

21.8.2.1. enum class AppState { INIT, CLIMB, TRIGGER, CONFIRM_ACK, RTL } 상태 정의

우리가 가장 먼저 스케치했던 4단계 상태(IDLE, ARMED, DROPPING, FINISHED)는 교육 목적상 가장 단순화된 모델이었다.
실제 산업용 페이로드(예: 정밀 택배 배송 드론, 다목적 정찰 드론) 환경에서는 더 복잡하고 정교한 피드백 메커니즘을 요구한다. 따라서 여기서는 실전에서 널리 쓰이는 5단계의 확장된 상태 머신 상수를 정의해 볼 것이다.

1. 실전형 AppState 5단계 구조

단순히 물건을 툭 떨구는 것을 넘어서, “상승하여(CLIMB) -> 물건을 떨구고(TRIGGER) -> 떨어진 것을 확인받은 뒤(CONFIRM_ACK) -> 집으로 돌아오는(RTL)” 완벽한 폐쇄형 루프(Closed-loop) 시나리오를 구상해 보자.

// 헤더 파일의 FSM 상태 상수 선언부
enum class AppState {
    // 1. 시스템 초기화 및 대기
    INIT = 0,
    
    // 2. 이륙 후 목표 고도(Target Altitude)를 향해 상승 및 순항 중인 상태
    CLIMB,
    
    // 3. 목표 고도에 도달하여 물리적 액추에이터(투하 장치, 카메라 셔터 등)를 작동시킨 상태
    TRIGGER,
    
    // 4. 외부 센서(예: 리미트 스위치, 중량 센서)로부터 페이로드 투하 성공 응답(ACK)을 기다리는 상태
    CONFIRM_ACK,
    
    // 5. 모든 임무를 무사히 마치고 원래의 위치 또는 착륙 지점으로 귀환(Return To Launch)하는 상태
    RTL 
};

이 확장된 5단계의 상태 구조가 그 자체만으로도 코드가 아닌 한 편의 미션 시나리오를 묘사하고 있음을 주목하라.

1.1 왜 enum이 아닌 enum class 인가?

이때 구형 C언어 스타일의 enum AppState 가 아니라 반드시 **C++11 형 enum class AppState**를 사용해야 하는 타당한 이유가 있다.

  1. 스코프 오염(Scope Pollution) 방지: 기존 C 스타일의 enum에서는 내부 상수(INIT, TRIGGER)가 전역 공간으로 터져 나와, 다른 모듈의 enum { INIT = 1} 등과 이름 충돌을 일으킨다. enum classAppState::INIT 처럼 소속을 철저히 감싼다.
  2. 강력한 타입 검사(Strong Type Assurance): 실수로 _current_state = 1; 이나 _current_state = _nav_state; 라고 다른 정수형이나 다른 enum 값을 대입하려고 하면, 컴파일러가 강력한 에러를 띄워준다. 드론이 공중에서 터지는 막대한 비용을 C++ 컴파일 타임에 0원으로 막아주는 기적의 족쇄다.

2. 디버깅을 좌우하는 상태 문자열 매핑 로직

FSM을 디버깅할 때 콘솔창(nsh)에 “현재 상태: 2” 따위의 숫자가 찍히면 개발자는 미쳐버릴 것이다. 우리는 반드시 이 enum class 값들을 인간이 읽을 수 있는(Human-readable) 문자열로 매핑해 주는 헬퍼(Helper) 함수를 짝꿍처럼 만들어 두어야 한다.

// 소스 파일 내부의 디버깅 헬퍼 함수
const char* PayloadAutoDrop::state_to_str(AppState state) const {
    switch (state) {
        case AppState::INIT:        return "INIT";
        case AppState::CLIMB:       return "CLIMB";
        case AppState::TRIGGER:     return "TRIGGER (Action!)";
        case AppState::CONFIRM_ACK: return "CONFIRM_ACK (Waiting...)";
        case AppState::RTL:         return "RTL (Mission Accomplished)";
        // 만약 개발자가 enum을 추가하고 여기에 추가 안 하면 컴파일 Warning이 뜸 (-Wswitch)
    }
    return "UNKNOWN_STATE_ERROR";
}

이 헬퍼 함수를 추가해 주면, 상태가 천이될 때마다 PX4_INFO("State Changed: %s", state_to_str(_current_state)); 한 줄로 블랙박스 로그를 아름답게 도배할 수 있다.

이제 FSM의 부품(열거형 상수)들을 찍어 냈으니, 진짜 FSM 코딩의 핵심 정수, 즉 저 switch-case 블록 안에서 상태가 ‘입장(Entry) -> 평가(Evaluate) -> 퇴장(Exit)’ 하는 메커니즘을 어떻게 구조적으로 쪼개서 분리해야 결함 없는 FSM 코드를 짤 수 있는지 다음 장 21.8.2.1.1에서 본격적으로 다루어 보자.