28.7.3.1. commander.cpp의 모드 유효성 검사 루틴(main_state_transition()) 내 신규 State 허용 조건 분기 추가
비행 모드를 FlightTask로 아름답게 추상화해 개발을 마쳤다 하더라도, 조종사가 조종기 스위치를 올렸을 때 PX4가 맹목적으로 그 모드로 기체를 내다 꽂지는 않는다. 기체의 모든 상태(센서, 배터리, Arming 여부)를 종합하여 “네가 만든 커스텀 모드를 지금 켜는 것이 물리적으로 안전한가?” 를 심사하는 최종 보안관(Guard)이 모드 스위쳐 앞을 가로막고 있다.
그 보안관 역할을 수십 년째 담당해오고 있는 핵심 모듈이 바로 commander.cpp (PX4 버전에 따라 state_machine_helper.cpp 내부 루틴으로 분리되기도 함) 이다. 본 절에서는 신규 비행 모드 진입을 허가받기 위해 유효성 검사 루틴에 커스텀 분기를 얹어놓는 방법을 살펴본다.
1. Main State 와 Navigation State 의 관계
commander 모듈은 내부의 상태 머신을 돌리기 위해 외부의 navigation_state(우리가 1단계에서 만든 상수)를 그대로 쓰지 않고, 이를 MAIN_STATE 라는 커맨더 시스템 자체의 내부 상태(Internal State)로 1:1 매핑하여 관리한다.
따라서 커맨더 소스 코드를 건드리기 전, commander_state.msg (명령 체계의 내부 메시지) 파일에 우리의 커스텀 모드용 내부 상태 상수를 만들어 NAVIGATION_STATE_CUSTOM_XXX와 짝(Pair)을 지어줘야 한다.
# msg/CommanderState.msg 내부 예시
uint8 MAIN_STATE_MANUAL = 0
uint8 MAIN_STATE_ALTCTRL = 1
# ... (중략) ...
uint8 MAIN_STATE_CUSTOM_FOO = 22 # [NEW] 커맨더용 내부 매핑 상태 추가
이어지는 소스 코드 내 상태 매핑 함수(예: set_nav_state())에서 외부 통신용 NAV 상태가 들어오면 내부용 MAIN 상태로 번역해 주는 한 줄의 매핑 케이스(Case)를 추가해 두는 것이 전제되어야 한다.
2. 모드 전환 허용을 결정하는 main_state_transition()
조종사가 스위치를 조작하거나 보조 컴퓨터(Companion Computer)가 MAVLink 모드 변경 명령을 내리면, commander 스레드는 즉시 main_state_transition() 함수를 호출하여 전환 허가증을 발급할지 판별(Evaluate)한다.
이 거대한 함수는 switch-case 문을 통해 기체가 넘어가려고 하는 타겟 상태(main_state_req, Request State)를 평가한다.
2.1 커스텀 case 분기 추가 및 진입 전제 조건(Pre-conditions) 설계
여기에 우리가 만든 MAIN_STATE_CUSTOM_FOO 케이스를 추가해야 한다. 로직의 생사가 달린 중요한 작업이다. 자신이 만든 커스텀 알고리즘이 “어떤 센서가 반드시 있어야만 비행이 가능한가?“를 철저히 자문해야 한다.
- 제어 로직 내에 위치(X,Y) 연산이 들어가는가? \rightarrow GPS(Global Position) 또는 VIO(Local Position)가 Valid 해야만 허용.
- 제어 로직 내에 고도(Z) 제어가 들어가는가? \rightarrow 기압계(Barometer)나 라이다 센서 플래그가 Valid 해야만 허용.
- 기체가 추락 중이거나 수동 모드를 껐을 때 폭주할 위험이 있는가?
// src/modules/commander/state_machine_helper.cpp 내부 함수 예시
// bool main_state_transition(const status_flags_s &status_flags, ... , uint8_t main_state_req, uint8_t &main_state)
bool transition_allowed = false;
switch (main_state_req) {
// ... (기존 모드 판별 생략) ...
case commander_state_s::MAIN_STATE_CUSTOM_FOO: // 우리가 만든 모드로 진입 요청
// 1. 센서 유효성 플래그(Status Flags)를 검토한다.
// 이 커스텀 모드는 GPS 전역 위치(Global Position) 추정이 파탄나면 추락하므로 반드시 검사한다.
if (status_flags.condition_global_position_valid) {
// 2. 기체 유형 검사. (멀티로터 전용 모드인가, 고정익 전용 모드인가?)
// 이 로직은 멀티로터의 호버링 특성에 의존한다면 콥터 모드(is_rotary_wing)인지도 판별해야 한다.
if (status.is_rotary_wing) {
transition_allowed = true; // 최종 합격 (허가 승인)
}
}
// 3. 거부(Denial) 사유 생성
if (!transition_allowed) {
// 진입이 거부되었다면, 조종사에게 왜 안되었는지 MAVLink GCS 텍스트로 이유를 뿌려준다.
mavlink_log_critical(&mavlink_log_pub, "Custom Mode denied: Global position required");
}
break;
}
3. 엄격한 거절(Reject)의 중요성
만약 귀찮다는 이유로 case 문에 들어가자마자 transition_allowed = true; 를 하드코딩(Hardcoding)해 버리면 당장 컴파일을 넘기고 비행 모드를 켜는 데는 지장이 없을 것이다.
하지만 실외 비행 중 강한 전자파 간섭으로 찰나의 순간 GPS condition_global_position_valid 플래그가 false로 떨어졌을 때, Commander는 커스텀 모드를 즉시 박탈(Failsafe Transition)시키지 못한다. 결국 하위 제어기인 FlightTaskCustom은 존재하지 않는(NAN 인) 궤적을 억지로 계산하려다 위치 제어 발산(Position Divergence)을 일으켜 기체가 허공에서 플립(Flip) 현상을 일으키며 추락하게 된다.
따라서 commander.cpp의 유효성 검사 루틴은 당신의 커스텀 로직 밖에서 버그를 막아주는 최후이자 최고의 생명보험이다. 이곳의 if 분기 조건문은 로직의 요구사항에 맞춰 보수적으로 빈틈없이 작성되어야 한다.