21.8.4.2. `vehicle_command_ack_s` 구독을 통한 트랜잭션 종료 판단

21.8.4.2. vehicle_command_ack_s 구독을 통한 트랜잭션 종료 판단

앞서 vehicle_command 구조체를 발행(Publish)하여 서보 밸브를 열어달라고 소리쳤다.
이 명령 데이터는 uORB 버스를 타고 액추에이터 제어 데몬(예: px4io, pwm_out_sim 등)에게 전달된다. 데몬은 명령을 치열하게 분석한 뒤, 실제로 하드웨어를 구동시켰는지 아니면 무슨 오류가 있어서 구동하지 못했는지에 대한 ’영수증’을 다시 uORB 버스에 올려놓는다.

이 영수증의 이름이 바로 vehicle_command_ack_s 토픽이다.

비행 제어 소프트웨어에서 “명령을 보냈다“는 사실은 중요하지 않다. “상대방이 명령을 정상적으로 수행했다는 영수증(ACK)을 받았다“는 사실만이 FSM 상태 천이를 위한 유일하고 완벽한 알리바이가 된다.

1. CONFIRM_ACK 상태의 사명

우리의 FSM 구조에서 STATE_TRIGGER 상태는 단지 명령서를 던지고 곧바로 STATE_CONFIRM_ACK 상태로 넘어왔다.
이제 이 CONFIRM_ACK 상태에 머무는 동안, 모듈은 귀를 쫑긋 세우고 오직 자신이 보낸 그 명령에 대한 영수증이 도착하기만을 필사적으로 기다린다.

#include <uORB/topics/vehicle_command_ack.h>
#include <uORB/Subscription.hpp>

// 헤더 파일에서 ACK 구독자 선언
uORB::Subscription _command_ack_sub{ORB_ID(vehicle_command_ack)};

2. 루프 내 영수증(ACK) 낚아채기 로직

FSM의 CONFIRM_ACK 상태 루프를 다음과 같이 설계한다. uORB 버스에는 세상 수많은 모듈들이 주고받는 수천 개의 ACK 영수증이 날아다닌다. 나는 오직 ’나의 명령어 번호(VEHICLE_CMD_DO_SET_ACTUATOR)’에 대한 영수증만 걸러서(Filter) 읽어야 한다.

void PayloadAutoDrop::run_state_confirm_ack() {
    
    // 1. 읽지 않은 새로운 영수증(ACK)이 도착했는지 확인한다.
    vehicle_command_ack_s ack{};
    if (_command_ack_sub.update(&ack)) {
        
        // 2. 이 영수증이 내가 보낸 서보 조작 명령에 대한 영수증이 맞는가?
        if (ack.command == vehicle_command_s::VEHICLE_CMD_DO_SET_ACTUATOR) {
            
            // 3. 영수증의 결과(Result) 코드를 분석한다!
            switch (ack.result) {
                
                case vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED:
                    // 대성공! 액추에이터가 밸브를 무사히 열었음을 100% 확신함.
                    PX4_INFO("Payload action CONFIRMED! Target destroyed.");
                    
                    // 서보 모터 움직임이 완벽히 검증되었으므로,
                    // 미련 없이 다음 상태인 RTL(Return To Launch) 상태로 천이한다.
                    _current_state = AppState::RTL;
                    break;
                    
                case vehicle_command_ack_s::VEHICLE_CMD_RESULT_TEMPORARILY_REJECTED:
                case vehicle_command_ack_s::VEHICLE_CMD_RESULT_DENIED:
                    // 실패! 하단 데몬이 바쁘거나 파라미터가 틀려서 명령이 거부됨.
                    PX4_ERR("Payload command REJECTED! Retrying...");
                    
                    // FSM을 다시 뒷걸음질 치게 만들어 명령을 재입력(Retry)하도록 돌려보낸다.
                    _current_state = AppState::TRIGGER;
                    _state_just_entered = true; 
                    break;
                    
                default:
                    // 알 수 없는 에러 상태
                    break;
            }
        }
    }
    
    // 이 상태에 머무는 동안 영수증이 올 때까지 하염없이 루프를 돌며 기다린다...
}

이 영수증 수거(ACK Handling) 로직이 추가됨으로써, 우리의 페이로드 모듈은 단순한 스크립트 쪼가리가 아니라 트랜잭션(Transaction)의 무결성을 보장하는 **클래스 1 항공 소프트웨어(Class 1 Aviation Software)**의 반열에 오르게 된다.

하지만 이 코드에는 아직 치명적인 논리적 구멍이 하나 존재한다.
만약 상대방(액추에이터 데몬)이 죽어버려서 영수증 자체가 영영 오지 않는다면 어떻게 될까? 우리의 FSM은 이 CONFIRM_ACK 구역의 늪에 영원히 빠져들어 먹통(Deadlock)이 되어 버린다.

영원한 기다림을 찢고 드론을 살려내기 위한 마지막 항공 방어술, “타임아웃(Timeout) 방어선과 GCS 에러 브로드캐스팅” 로직을 다음 장 21.8.4.2.1에서 마저 욱여넣어 보도록 하자.