21.8.4.2.1. ACK 수신 실패 타임아웃 계산 및 최대 재시도(Max Retries) 도달 시 GCS에 STATUSTEXT 에러 로깅을 브로드캐스팅하는 예외 처리 루틴
통신 시스템에서 ’무한 대기(Infinite Wait)’만큼 혐오스러운 버그는 없다.
CONFIRM_ACK 상태에 돌입한 FSM이 하위 데몬의 죽음이나 uORB 버스 트래픽 마비로 인해 영영 영수증(ACK)을 받지 못한다면, 이 모듈은 평생 아무 일도 하지 못하는 좀비가 되어버린다.
이러한 재난을 방어하기 위해 FSM 루프 안에 시한폭탄(Timeout Timer)과 제한된 재시도 횟수(Max Retries)라는 두 가지 방어선을 쳐야 한다.
1. 타임아웃 타이머와 재시도 로직의 이식
PayloadAutoDrop 클래스 헤더에 두 개의 멤버 변수를 추가한다.
// 헤더 파일 추가 변수
hrt_abstime _ack_wait_start_time{0}; // ACK 대기를 시작한 시점
uint8_t _command_retry_count{0}; // 현재까지 재시도한 횟수
const uint8_t MAX_RETRIES = 3; // 최대 3번까지만 다시 물어본다.
이제 앞장의 CONFIRM_ACK 상태 루프를 타임아웃 방어선이 포함된 무결점 코드로 업그레이드해보자.
void PayloadAutoDrop::run_state_confirm_ack() {
// [Entry Action] 방금 이 상태로 넘어왔을 때 타이머를 리셋한다.
if (_state_just_entered) {
_ack_wait_start_time = hrt_absolute_time();
_state_just_entered = false;
}
// 1. [Do Action] 통상적인 ACK 수신 확인 파트
vehicle_command_ack_s ack{};
if (_command_ack_sub.update(&ack)) {
if (ack.command == vehicle_command_s::VEHICLE_CMD_DO_SET_ACTUATOR) {
if (ack.result == vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED) {
// 대성공!
_current_state = AppState::RTL;
return; // 루프를 즉시 탈출
}
}
}
// 2. [Fail-Safe] 타임아웃(Timeout) 계산 파트
// ACK가 안 온 상태에서 얼만큼의 시간이 흘렀는가?
hrt_abstime now = hrt_absolute_time();
float wait_time_s = (now - _ack_wait_start_time) / 1e6f;
// 만약 0.5초가 넘도록 대답이 없다면? (Timeout 발생)
if (wait_time_s > 0.5f) {
_command_retry_count++; // 재시도 카운트 1 증가
if (_command_retry_count <= MAX_RETRIES) {
// [플랜 B] 아직 기회가 남았다.
// 상태를 뒤로 되돌려 명령 발송(TRIGGER)부터 다시 시작한다!
PX4_WARN("ACK Timeout. Retrying Command... (%d/%d)",
_command_retry_count, MAX_RETRIES);
_current_state = AppState::TRIGGER;
_state_just_entered = true; // 진입 액션을 켜주어야 모터 명령이 다시 나간다.
} else {
// [플랜 C: 최악의 상황] 3번이나 다시 보냈는데도 대답이 없다!
// 하드웨어 핀이 타버렸거나 통신선이 절단된 치명적 상황이다.
// 1. 조종사의 화면(QGC) 한가운데에 새빨간 에러 메시지를 띄워 알린다!
mavlink_log_critical(&_mavlink_log_pub,
"CRITICAL: Payload actuator failed to respond!");
// 2. 미련 없이 미션을 포기하고 남은 시스템이라도 살리기 위해 상태를 넘긴다.
_command_retry_count = 0; // 카운트 초기화
_current_state = AppState::RTL;
}
}
}
2. STATUSTEXT 브로드캐스팅의 위력
코드 하단부의 가장 치명적인 순간에 등장한 mavlink_log_critical() 매크로는 단순한 디버깅 용도의 PX4_ERR() 따위와는 궤를 달리한다.
PX4_ERR()는 USB 케이블을 꼽고 콘솔창(nsh)을 들여다보는 개발자의 눈에만 보이는 로그다. 반면 mavlink_log_critical(&_mavlink_log_pub, ...) 함수를 때리면, 커널 공간에서 uORB의 mavlink_log_s 토픽이 전 영역으로 뿜어져 나간다.
이 텔레메트리 신호는 무선 주파수(RF) 안테나를 타고 날아가, 수 킬로미터 밖에서 QGroundControl을 보고 있는 조종사의 메인 화면 중앙에 “뚜-둑!” 하는 소리와 함께 큼지막한 빨간색 에러 배너(STATUSTEXT Message)를 띄워준다.
조종사는 이 팝업을 보고 밸브가 열리지 않았음을 즉각 소스 코드가 아닌 ’화면’으로 체감하고, 지체 없이 드론을 기지로 복귀시키는 비상 수동 조작을 결단할 수 있게 되는 것이다.
이로써 FSM 설계도, 노이즈 필터링 보수도, 그리고 최악의 상황을 대비한 예외 처리 비상 시스템까지 완벽하게 코딩이 끝났다. ’페이로드 구동 모듈’의 심장이 독립적으로 고동치기 시작했다.
남은 것은 이 위대한 모듈을 픽스호크 깡통 안에 욱여넣고, 전원을 켤 때마다 자동으로 숨을 쉬게(데몬화, Daemonization) 만드는 커널 부팅 관문 통과뿐이다. 대망의 21.9장, 펌웨어 부트 시퀀스 연동의 세계로 진입하자.