28.5.3.1. `mavlink_messages.cpp` 내 `vehicle_status.nav_state`의 MAVLink `HEARTBEAT` 직렬화(Serialization) 매핑

28.5.3.1. mavlink_messages.cppvehicle_status.nav_state의 MAVLink HEARTBEAT 직렬화(Serialization) 매핑

비행 로봇의 두뇌인 PX4-Autopilot에서 결정된 복잡한 내부 항법 상태(Navigation State)가 외부 세계로 송출되기 위해서는 국제 표준인 MAVLink 프로토콜 규격에 맞게 변환되어야 한다. 이 핵심적인 브릿지(Bridge) 역할을 수행하는 코드가 바로 src/modules/mavlink/mavlink_messages.cpp 파일 내부에 위치한 직렬화(Serialization) 파이프라인이다.

본 절에서는 PX4의 핵심 상태 변수인 nav_state가 어떻게 분해되고 비트 스트림으로 맵핑되어 최종적으로 1Hz 주기의 HEARTBEAT 메시지에 실려 나가는지 그 로직을 상세히 파헤친다.


1. 직렬화(Serialization)의 필요성과 MAVLink HEARTBEAT 구조

내부 uORB 토픽인 vehicle_status.msgnav_state는 1바이트(uint8) 크기의 열거형 데이터로, PX4 펌웨어 버전업에 따라 수시로 그 의미(예: 1은 매뉴얼, 4는 자동 등)가 추가되거나 변경된다.

반면 MAVLink의 HEARTBEAT (#0 메시지)는 다양한 종류의 드론(ArduPilot, DJI 등)이 공통으로 사용하는 생존 신호이므로, 정해진 규격을 엄격하게 준수해야 한다.
HEARTBEAT 메시지의 페이로드(Payload) 구조는 다음과 같다.

  • type (uint8_t): 쿼드콥터, 헥사콥터, 고정익 등 기체 유형 (MAV_TYPE_xxx)
  • autopilot (uint8_t): PX4, ArduPilot 등 오토파일럿 종류 (MAV_AUTOPILOT_PX4)
  • base_mode (uint8_t): 시동(Armed/Disarmed), 자율/수동 제어 허용 여부 등 범용 상태 비트필드 (MAV_MODE_FLAG)
  • custom_mode (uint32_t): 각 오토파일럿(PX4 등)이 독자적으로 정의해서 쓰는 4바이트 32비트 특수 목적 필드
  • system_status (uint8_t): 부팅, 캘리브레이션, 정상 비행, 위험 상태 등 전체 시스템 헬스(Health) 상태

위 구조에서 볼 수 있듯, 1바이트짜리 단순한 nav_statebase_mode와 32바이트의 custom_mode 두 가지의 광활한 공간에 쪼개져 맵핑(Mapping)되는 마법 같은 직렬화 과정을 거치게 된다.


2. 변환 로직 분석: get_components() 및 송신 맵핑

mavlink_messages.cpp 코드의 안쪽 깊숙한 곳에는 1초마다 실행되는 MavlinkStreamHeartbeat 클래스가 존재한다. 여기서 직렬화를 담당하는 핵심 메서드는 기체의 현재 정보를 바탕으로 HEARTBEAT 패킷의 필드값을 갱신한다.

2.1 base_mode의 생성 (MAV_MODE_FLAG)

base_mode는 8비트의 공간을 가지며, 각 비트(Bit)가 독립적인 의미를 지니는 비트마스크(Bitmask)이다.
스트리밍 모듈은 vehicle_status에서 다음 정보들을 읽어와 OR(|) 연산으로 비트를 합성한다.

  • vehicle_status.arming_state가 ARMED 이면 \rightarrow MAV_MODE_FLAG_SAFETY_ARMED 비트를 켠다.
  • 이외에도 기체가 자율 비행 가능한 세팅(Mission)인지 조종기에 물려있는지 등을 분석하여 MAV_MODE_FLAG_AUTO_ENABLED, MAV_MODE_FLAG_MANUAL_INPUT_ENABLED 등 범용 MAVLink 속성을 덮어씌운다.

이를 통해 지상 관제소(GCS)는 오토파일럿 제조사에 관계없이 공통된 방식으로 “현재 저 드론이 무장되어 있고 매뉴얼 조종이 가능하다“는 사실을 1차적으로 추론할 수 있다.

2.2 custom_mode 인코딩 (PX4 고유 맵핑)

가장 핵심적인 변화는 32비트 custom_mode에서 일어난다. PX4는 32비트 공간을 8비트, 8비트, 16비트로 나누어 메인 모드(Main Mode)와 서브 모드(Sub-mode) 체계로 인코딩한다. 이 방식은 모드가 끝없이 늘어나는 자율 비행 시스템에서 충돌 없는 확장을 가능케 한다. (이후 절에서 상세히 다룬다.)

핵심 코드의 일부 로직 전개는 다음과 같다.

// nav_state_to_custom_mode() 유사 함수 내부
uint32_t custom_mode = 0;

switch (vehicle_status.nav_state) {
    case vehicle_status_s::NAVIGATION_STATE_MANUAL:
        custom_mode = PX4_CUSTOM_MAIN_MODE_MANUAL << 16;
        break;
    case vehicle_status_s::NAVIGATION_STATE_ALTCTRL:
        custom_mode = PX4_CUSTOM_MAIN_MODE_ALTCTL << 16;
        break;
    case vehicle_status_s::NAVIGATION_STATE_POSCTRL:
        custom_mode = PX4_CUSTOM_MAIN_MODE_POSCTL << 16;
        break;
    case vehicle_status_s::NAVIGATION_STATE_AUTO_MISSION:
        // Main 모드는 AUTO 이며, Sub 모드는 MISSION(웨이포인트 비행)
        custom_mode = (PX4_CUSTOM_MAIN_MODE_AUTO << 16) | (PX4_CUSTOM_SUB_MODE_AUTO_MISSION << 24);
        break;
    case vehicle_status_s::NAVIGATION_STATE_AUTO_RTL:
        // Main 모드는 AUTO 이며, Sub 모드는 RTL(안전 복귀)
        custom_mode = (PX4_CUSTOM_MAIN_MODE_AUTO << 16) | (PX4_CUSTOM_SUB_MODE_AUTO_RTL << 24);
        break;
    // ... 수십 개의 매핑 존재
}

결과적으로 커맨더 모듈이 생성한 1차원적인 nav_state 숫자놀음은 mavlink_messages.cpp의 직렬화 루틴을 거치면서, MAVLink 네트워크 상에서 완벽하게 독립적으로 파싱(Parsing)이 가능한 2차원(Main/Sub) 형태의 custom_mode 메타데이터로 탈바꿈하게 된다.


3. 네트워크 대역폭(Bandwidth)과 동기적 상태 전송

HEARTBEAT 메시지는 단 9바이트의 짧은 페이로드(MAVLink v2 기준)를 지닌다. 이 메시지는 무전기(Telemetry Radio)나 LTE 통신망 등 좁은 유효 대역폭(Narrow Bandwidth) 환경 시나리오에서도 절대로 유실되어서는 안 된다.

따라서 PX4-Autopilot은 상태 맵핑 직렬화와 전송 과정에서 다음과 같은 원칙을 가져간다.

  • 가장 치명적이고 빈번히 조회되는 nav_state (모드 상태) 결론만을 이 9바이트에 구겨 넣는다.
  • 정말로 EKF2가 튀었는지, 가속도가 넘었는지 등 복잡한 내부의 다이내믹스 플래그 상태(vehicle_control_mode 자체)는 1Hz의 HEARTBEAT에 통합하지 않고, 수십 Hz 단위로 전송되는 고대역폭 전용 디버깅 EXTENDED_SYS_STATELOCAL_POSITION_NED 같은 여타 데이터 스트림으로 완전히 분리 격리시켜버린다.

4. ArduPilot과의 HEARTBEAT 매핑 차이

MAVLink를 사용하는 두 생태계 모두 HEARTBEAT 메시지를 사용하지만, 직렬화 철학이 확연히 갈린다.

  • PX4-Autopilot:
  • nav_state (1바이트) \rightarrow 직렬화 \rightarrow base_mode + custom_mode (메인/서브 2계층 분할 저장)
  • PX4 코어 프레임워크와 MAVLink 프레임워크가 완벽히 차단된 레이어로 설계되어 있다.
  • ArduPilot:
  • 내부 비행 모드(mode_number) 변수를 그 자체로 custom_mode에 그대로 인코딩한다.
  • 예를 들어 Copter(멀티로터) 펌웨어에서 Loiter 모드는 5번인데, custom_mode = 5 로 그대로 실어 보낸다. (ArduPlane에서는 이 5번이 다른 모드를 뜻할 수 있음)
  • 기체 종(Copter vs Plane vs Rover)에 따라 번호의 의미가 중구난방이기 때문에, 지상 GCS(Mission Planner)는 패킷에 적힌 type 필드를 먼저 보고 기체 종류를 확정한 뒤에야 1차원 딕셔너리로 custom_mode를 해석해내는 방식을 쓴다.

PX4 방식을 채택한 QGC(QGroundControl)는 픽스호크 내부의 기체 동역학과 단절된 상태에서 custom_mode에 새겨진 하드 인코딩된 범용 서브모드 비트를 디코딩 하므로, 기체 유형과 상관 없이 통일된 상태 UI를 출력할 수 있는 견고함을 얻었다.