13.5.1.2. QGroundControl 백엔드 청크(Chunk) 분할 알고리즘: `RTCMMavlink::sendMessage()` C++ 메서드의 페이로드 분할 및 전송 큐(Queue) 관리 로직

13.5.1.2. QGroundControl 백엔드 청크(Chunk) 분할 알고리즘: RTCMMavlink::sendMessage() C++ 메서드의 페이로드 분할 및 전송 큐(Queue) 관리 로직

지상 관제 시스템(QGC)에 연결된 고가의 베이스 스테이션(Base Station)이 1\text{Hz} 주기로 수백 바이트의 RTCM3 바이너리 블록을 쏟아낼 때, 이 거대한 데이터 폭포수를 MAVLink 프로토콜의 좁은 병목(180\text{ bytes} 제한)으로 우겨넣는 문지기(Gatekeeper) 역할은 QGC C++ 백엔드의 RTCMMavlink 클래스가 전담한다.

본 절에서는 QGroundControl의 오픈소스 코드베이스(src/GPS/RTCMMavlink.cc) 내부 깊숙한 곳에서, 무식하게 쏟아지는 원시 스트림이 어떠한 C++ 큐잉(Queueing) 자료구조와 청크(Chunk) 분할 알고리즘을 거쳐 안전하고 부드러운 MAVLink 패킷 열(Train of Packets)로 조형되는지 그 로직의 정수를 파헤친다.

1. RTCM 데이터 큐(Queue)를 위한 QByteArray 버퍼의 동적 확장

QGC는 크로스 플랫폼 프레임워크인 Qt 위에서 구동된다. 따라서 RTCMMavlink 클래스의 데이터 임시 저장소는 표준 C++ std::vectorstd::queue 가 아닌, Qt 고유의 바이트 배열 클래스인 **QByteArray**를 사용한다. 베이스 안테나가 보내오는 데이터 전송(RTCMDataReady 시그널) 이벤트가 터질 때마다 RTCMMavlink::RTCMDataUpdate(QByteArray message) 슬롯 함수가 호출된다.

// QGC 백엔드 수신 핸들러 (유사 로직)
void RTCMMavlink::RTCMDataUpdate(QByteArray message)
{
    // 스레드 안전성(Mutex) 확보
    QMutexLocker locker(&_rtcmMutex);
    
    // 무한 잉크처럼 쏟아지는 RTCM을 버퍼의 꼬리에 붙여(Append) 나간다.
    _rtcmBuffer.append(message);
    
    // 만약 버퍼가 일정 크기 이상 쌓였다면 네트워크 전송 스레드를 깨운다(Wake).
    if (_rtcmBuffer.size() > 0) {
        emit invokeSendMessage(); // sendMessage() 슬롯 호출
    }
}

이 방식은 베이스 GPS 하드웨어와 MAVLink 송신 모듈 간의 직결(Direct Coupling)을 끊어버린다. 송신망이 아무리 막히더라도(예: 텔레메트리 연결 지연) 데이터는 _rtcmBuffer 라는 저수지(Reservoir)에 안전하게 확장되며 찰랑찰랑 고이게 된다.

2. RTCMMavlink::sendMessage() 페이로드 분할(Chunking) 핵심 알고리즘

저수지의 수문이 열릴 때, QGC는 _rtcmBuffer 속 물을 한 번에 쏟아버리지 않고 MAVLink 규약(최대 180\text{ bytes})에 맞게 정확히 계량해서 조각낸다. 이 분할 알고리즘이 바로 sendMessage() 메서드의 핵심 루프(Loop)이다.

2.1 최대 인출 크기(Max Chunk Size)의 연산과 슬라이싱

버퍼 안에 고인 데이터 size 와 MAVLink 허용 길이 180 을 끊임없이 저울질한다.
C++ std::min() 함수를 이용한 슬라이스 창(Window) 계량이 수행된다.

// src/GPS/RTCMMavlink.cc (페이로드 분할 핵심 C++ 유사 로직)
void RTCMMavlink::sendMessage()
{
    QMutexLocker locker(&_rtcmMutex);
    
    // 큐(버퍼)에 보낼 데이터가 남아 있는 동안 무한 반복
    while (_rtcmBuffer.size() > 0) {
        
        // 1. 가져갈 조각(Chunk)의 크기 결정: (남은 크기 vs 180) 중 작은 쪽
        int chunk_size = qMin(_rtcmBuffer.size(), 180);
        
        // 2. 버퍼의 맨 앞에서 chunk_size 만큼 복사 (Dequeue)
        QByteArray chunk = _rtcmBuffer.left(chunk_size);
        
        // 3. 꺼낸 만큼 버퍼에서 삭제 (Shift O(N) 오버헤드 발생하지만 QByteArray 최적화 활용)
        _rtcmBuffer.remove(0, chunk_size);
        
        // 4. MAVLink 규격(GPS_RTCM_DATA) 포장 및 전송
        mavlink_message_t msg;
        mavlink_msg_gps_rtcm_data_pack_chan(...,
                                            0,           // flags 플래그 
                                            chunk_size,  // len (페이로드 길이)
                                            (uint8_t*)chunk.constData()); // data
        
        _linkMgr->sendMessage(msg); // 텔레메트리 무선 모뎀 송출
    }
}
  • 만약 _rtcmBuffer410\text{ bytes}였다면 반복문은 다음과 같이 돈다:
  1. 1\text{차} \text{ Loop}: qMin(410, 180) \rightarrow 180\text{ bytes} 슬라이스 및 remove. 남은 버퍼 230\text{ bytes}.
  2. 2\text{차} \text{ Loop}: qMin(230, 180) \rightarrow 180\text{ bytes} 슬라이스 및 remove. 남은 버퍼 50\text{ bytes}.
  3. 3\text{차} \text{ Loop}: qMin(50, 180) \rightarrow 50\text{ bytes} 슬라이스 및 remove. 남은 버퍼 0 (반복 종료).

3. LSB 식별 비트 flags 의 단편화 시퀀스(Sequence) 제어

단순히 썰어서 보내기만 한다면 송신 루틴은 끝이 나겠지만, 드론(FC) 코어에서 조각이 한 번에 날아온 것인지 거대 패킷을 쪼갠 것인지 알아야 하므로 플래그 처리가 병행된다. QGC 백엔드는 앞 절에서 언급했던 1바이트 변수 flags 에 단편화 고유의 조립 번호표(Tag) 비트를 비트 연산(Bitwise Operation)을 통해 각인한다.

  • flags & 1 : 단편화 토큰. (1이면 조각난 상태)
  • ((sequence_id) & 3) << 1 : 0 \sim 3 까지 순환하는 일련번호 ID 삽입.
// Flags 삽입 보완 로직 (MAVLink v2)
uint8_t flags = 1; // 단편화 상태 (Fragmented)
flags |= (sequence_id & 0x03) << 1; 

// 전송 이후 다음 루프를 위해 조각 번호 롤오버 (0->1->2->3->0)
sequence_id = (sequence_id + 1) % 4;

이렇게 정교하게 비트 마스킹된 flags 는 MAVLink 패킷 수신기가 “지금 날아오는 게 410\text{ Bytes}짜리 원본 RTCM의 첫 번째 조각(ID=0)이고, 다음이 두 번째 조각(ID=1)이구나“를 암묵적으로 인식하도록 돕는 인덱스가 된다.

4. 텔레메트리 포화(Saturation)를 막기 위한 스레드 지연(Yield)

while 루프의 가장 큰 맹점은, PC의 CPU 속도가 무선 텔레메트리의 보드레이트(57,600\text{ baud}) 속도보다 천문학적으로 빠르다는 것이다.

410\text{ Bytes}짜리 RTCM 버퍼를 쪼개어 MAVLink 시리얼 포트 큐에 sendMessage 를 세 번 연속으로 망치질(Hammering)하듯 밀어 넣으면, 하위 계층(OS 커널)의 USB/UART 버퍼 용량을 초과하여 패킷 드롭(Packet Drop) 현상이 발생하거나 운영체제가 스레드를 락(Lock)시켜 버릴 위험이 다분하다.

결과적으로 QGC 설계 팀은 무선 모뎀 송신 버퍼(Tx Buffer)에게 숨 쉴 틈을 주어 MAVLink 메시지가 라디오 전파를 타고 하늘로 원활히 날아갈 수 있도록 루프 바닥에 강제 스레드 휴식 시간(QThread::msleep)을 끼워 넣는 백오프(Back-off) 아키텍처를 적용하였다.

이렇듯 RTCMMavlink 클래스는 C++의 스레드 보호막(Mutex)과 큐 슬라이싱(Chunking) 칼날, 그리고 LSB 비트 마스킹(Bit Masking) 기교를 총동원하여 지상의 하드웨어(Base)와 무선의 한계(Telemetry) 사이에서 완벽한 통신 컨버터(Converter)로서 기능하고 있다.