27.2.1.2. 단위 테스트 프레임워크 연동 및 시뮬레이션 환경에서의 ECL 추상화

27.2.1.2. 단위 테스트 프레임워크 연동 및 시뮬레이션 환경에서의 ECL 추상화

PX4 상태 추정 코어가 수십만 줄의 펌웨어 스택과 마이크로컨트롤러(MCU) 레지스터로부터 완벽하게 분리(Decoupling)되어 얻게 된 가장 위대한 공학적 쾌거는 바로 ‘데스크톱 환경에서의 무한한 수학적 검증’ 이 가능해졌다는 점이다.

본 절에서는 펌웨어 독립적으로 추상화된 ECL(Estimation and Control Library)이 단위 테스트(Unit Test) 프레임워크 및 SITL(Software In The Loop) 시뮬레이터와 어떻게 매끄럽게 연동되어 알고리즘의 결점을 물리적 제약 없이 찾아내는지 그 연동 마이크로-아키텍처를 분석한다.


1. Google Test (gtest) 연동과 EKF Mock-up 검증

드론이 비행 중에 치명적인 버그(예: 짐벌 락(Gimbal Lock) 부근에서의 쿼터니언 발산)를 일으켰다면, 이를 찾기 위해 매번 드론을 이륙시키는 것은 엄청난 비용과 위험을 수반한다. PX4 ECL 개발팀은 이러한 수학적 극한 상황(Edge Case)을 검증하기 위해 구글 테스트(Google Test, gtest) 프레임워크를 C++ 코드 베이스에 깊숙이 연동해 두었다.

1.1 입력 주입(Input Injection)과 결과 단언(Assertion)

ECL 코어는 운영체제의 스레드나 클럭을 알지 못한 채 오직 setIMUData(), setGpsData() 같은 단순한 API만을 노출한다. 테스트 환경에서는 이 특성을 적극 활용하여 다음과 같은 가상의 시나리오를 단위 테스트 코드로 작성한다.

  1. 가상 인스턴스화: 순수 C++ 객체인 Ekf 클래스 인스턴스를 하나 생성한다.
  2. 데이터 주조(Mocking): 드론이 0^\circ에서 90^\circ로 0.1초 만에 급격하게 피치(Pitch)를 올리는 가상의 가속도 및 자이로 데이터를 수학적으로 생성하여 imuSample 구조체에 채워 넣는다.
  3. 시간 흐름 조작: 실제 보드 시계와 무관하게, for 루프를 돌며 타임스탬프를 수동으로 4000\mu s씩 가상으로 증가시킨다.
  4. 역산 검증: 100회의 루프를 돌린 후 _ekf.get_position()이나 _ekf.get_quaternion()을 호출하여 산출된 결과값이 이론적 수학 모델(Ground Truth)과 10^{-5} 오차 범위(Epsilon) 내에서 일치하는지 EXPECT_NEAR() 매크로를 통해 단언(Assert)한다.
// src/lib/ecl/EKF/ekf_tests.cpp 의 단위 테스트 구현 예시 (의사코드)
TEST(EkfTest, GimbalLockPitchUpdate) {
    Ekf ekf;
    ekf_init_state(&ekf);

    for (int i = 0; i < 100; i++) {
        imuSample imu = generate_mock_extreme_pitch_data(i);
        ekf.setIMUData(imu);
        ekf.update(); 
    }

    // 예측된 오일러 피치 각도가 89.9도 근방에서 수학적으로 붕괴하지 않았는지 검증
    Eulerf euler_angles(ekf.getQuaternion());
    EXPECT_NEAR(euler_angles.theta(), M_PI_2, 1e-4);
}

이 단위 테스트 체계 덕분에, 코드 병합(Pull Request)이 발생할 때마다 깃허브 액션(GitHub Actions) CI/CD 서버가 수만 개의 극단적 드론 기동 테스트를 단 1분 만에 백그라운드에서 검증해 낼 수 있다.


2. 시뮬레이션 환경(SITL / HITL)에서의 추상화 레이어

PX4는 비행 제어 코드를 수정하지 않고 그대로 컴퓨터 가상 환경에서 띄우는 SITL (Software In The Loop) 과 하드웨어 칩셋 위에서 가상 센서를 물려 띄우는 HITL (Hardware In The Loop) 모드를 적극 지원한다.

ECL 입장에서 보면 SITL인지, HITL인지, 아니면 실제 하늘을 날고 있는지 구별할 필요조차 없다. 이는 ECL과 센서/구동기 모델 사이에 철저한 추상화 장벽(Abstraction Barrier)이 쳐져 있기 때문이다.

graph TD;
    subgraph "가상 환경 (e.g. Gazebo / jMAVSim)"
        A[물리 엔진: 기체 동역학 연산] -->|MAVLink HIL_SENSOR| B[가상 센서 노이즈 추가]
    end

    subgraph "PX4 Middleware (SITL 모드)"
        B -->|uORB 매핑| C[sensor_combined 토픽]
    end

    subgraph "ECL Wrapper (src/modules/ekf2)"
        C -->|구조체 변환| D[EKF2 모듈 래퍼]
    end

    subgraph "ECL Math Core (Pure C++)"
        D -->|setIMUData()| E[EKF Core 추정 알고리즘]
        E -->|getState()| D
    end
    
    style A fill:#eef,stroke:#333
    style E fill:#ccf,stroke:#333

2.1 SITL에서의 가상 센서 디스패치(Dispatch)

가상 물리 엔진인 가제보(Gazebo)에서 드론이 1미터 전진하면, 가제보는 MAVLink HIL_SENSOR 패킷에 가상의 IMU 노이즈(가우스 백색 잡음 등)를 섞어 비행 제어기로 전송한다.

PX4의 센서 드라이버는 이 패킷을 실제 I2C 버스에서 읽어온 데이터인 것처럼 위장하여 sensor_combined uORB 토픽으로 발행한다. 결국 ekf2 래퍼 입장에서는 그 데이터의 출처가 실리콘 칩(MEMS)인지 컴퓨터 연산장치(CPU)인지 따질 필요 없이, 그저 구조체를 만들어 ECL 코어에 update()를 때려주면 끝난다.


3. 리플레이(Replay) 유틸리티: 과거의 시공간 복원

ECL 추상화의 가장 궁극적인 형태는 사고 분석을 위한 로그 리플레이(Log Replay) 시스템이다.

실제 비행 중 .ulog 파일에는 PX4가 감지한 수백만 개의 센서 uORB 메시지들이 초당 수백 회씩 타임스탬프와 함께 고스란히 기록되어 있다. 만약 EKF 추정치가 튀어 드론이 추락했다면, 개발자는 이 .ulog 파일을 리눅스 데스크톱 환경의 replay 가상 모듈로 주입한다.

리플레이 모듈은 파일에 적힌 타임스탬프 간격을 정교하게 읽어내며, 사고 당시 기체가 겪었던 IMU 센서 데이터 스트림을 마이크로초 단위까지 똑같은 리듬으로 재현하여 ECL 코어에 다시 밀어 넣는다.

이때 개발자가 ECL C++ 소스의 EKF 연산 공식을 조금 수정하거나 파라미터(예: 지자기 노이즈 계수)를 바꾼 상태로 리플레이를 재실행하면, 변경된 알고리즘이 과거의 사고 센서 데이터를 맞닥뜨렸을 때 어떻게 올바르게(혹은 더 심하게) 요동치는지를 가상으로 완벽하게 추적(Post-processing)할 수 있다.

이러한 단위 테스트, SITL, 리플레이의 삼위일체(Trinity)는 ECL을 단순히 기체에 종속된 펌웨어 코드가 아닌, 순수한 시계열 다중 센서 수학 분석 플랫폼의 단원으로 격상시켰다.