이전 글에서는 RealFlight Link 포트를 확인하고 macOS에서 Windows의 RealFlight와 TCP 연결에 성공했습니다. 이제 시뮬레이터의 상태를 실제로 읽어 보겠습니다.
단순 TCP 연결만으로는 상태를 읽을 수 없다
RealFlight Link는 스트리밍 소켓이 아닙니다. 단순히 연결한다고 상태 데이터를 보내지 않습니다. TCP로 연결한 뒤 SOAP 형식의 XML 요청을 보내야 응답으로 상태 정보를 받을 수 있습니다. 상태를 읽는 과정은 네 단계입니다.
- TCP 연결
- 요청 전송 (SOAP)
- 응답 수신 (HTTP + XML)
- 데이터 파싱
코드 구성
상태 정보를 읽는 코드는 다음처럼 나눠 구성했습니다.
src/hoverpilot/
├── main.py
├── config.py
└── rflink/
├── client.py
├── protocol.py
└── models.py
각 스크립트가 맡은 역할은 이렇습니다.
client.py→ TCP 연결을 관리하고 요청/응답 흐름을 제어합니다.protocol.py→ SOAP 요청을 생성하고 HTTP/XML 응답을 파싱합니다.models.py→ 파싱된 상태 데이터를 구조화하는 dataclass를 정의합니다.
상태를 읽어오는 코드
실제로 상태를 읽는 코드는 생각보다 단순합니다.
state = client.request_state()
print(state.summary())
request_state() 메서드는 다음 단계를 수행합니다.
- TCP 연결
- 컨트롤러 인터페이스 초기화
ExchangeData요청 전송- HTTP 응답 수신
- XML 파싱 → 상태 객체 생성
이후 action을 보내는 구조도 이와 거의 동일하게 이루어진다.
컨트롤러 인터페이스 초기화
RealFlight Link는 컨트롤러 인터페이스가 활성화된 이후에만 정상적인 상태 값을 반환합니다. 이를 위해 다음 두 요청을 먼저 보낸다.
RestoreOriginalControllerDevice: 컨트롤러 상태를 초기 상태로 되돌린다.InjectUAVControllerInterface: 컨트롤러 인터페이스를 RealFlight에 연결합니다.
이 단계는 이후 ExchangeData로 상태와 입력을 주고받기 위한 초기화 과정입니다.
상태 요청 방식
RealFlight Link는 우리가 요청을 보내야만 응답을 주는 구조다. 그 요청은 일반적인 JSON이 아니라 XML 형식으로 작성해야 합니다.
<ExchangeData>
<pControlInputs>
<m-selectedChannels>4095</m-selectedChannels>
...
</pControlInputs>
</ExchangeData>
이 XML을 HTTP POST 요청에 담아서 RealFlight로 보내면, 그에 대한 응답으로 현재 비행 상태가 XML 형태로 돌아옵니다.
SOAP 프로토콜이라고 해서 특별히 더 알아야 할 건 없습니다. XML을 HTTP 요청에 담아 보내고 응답을 파싱하는 방식으로 이해해도 충분합니다.
응답 처리 방식
RealFlight Link의 응답은 HTTP 형태로 전달되며 그 안에 XML 형태의 상태 데이터가 들어 있습니다. 처리 흐름은 이렇습니다.
- HTTP 응답을 받는다
- 응답 본문(XML)을 추출한다
- XML을 파싱해서 상태 값을 읽는다
이제 이 값들이 실제로 무엇을 의미하는지 해석해야 합니다.
상태 데이터 구조화
응답 XML에는 수십 개의 값이 포함되어 있습니다. 속도, 고도, 자세, 각속도, 위치, quaternion, 배터리 상태 등 다양한 비행 상태가 함께 들어옵니다. XML에서 값을 직접 꺼내 쓸 수도 있지만 필드가 많고 구조가 일정하지 않아 코드가 금방 복잡해집니다.
그래서 파싱된 값을 FlightAxisState라는 dataclass로 묶어서 관리하도록 합니다. 이렇게 구조화해두면 몇 가지가 편해진다.
- 필요한 값에 이름으로 바로 접근할 수 있어 디버깅이 쉽습니다.
- 각 값의 의미가 코드에 그대로 드러나 가독성이 좋습니다.
- 이후 RL observation 벡터로 변환하기도 수월합니다.
사람이 읽을 수 있는 형태로 출력
값이 제대로 들어오는지 확인하는 가장 직관적인 방법은 값이 실제로 변하는지 확인하는 것입니다. XML 응답에는 너무 많은 값이 포함되어 있어서 모든 값을 다 출력하면 오히려 확인하기 어려우므로 앞서 본 state.summary()가 다음 값만 골라서 보여주도록 했습니다.
확인 포인트는 네 가지다.
- 위치
- 고도
- 속도
- 자세
이 상태에서 RealFlight의 Airplane Hover Trainer를 실행하고 기체를 움직이면 출력되는 값이 실제로 변하는 것을 확인할 수 있습니다. 정밀한 분석은 아니지만 값이 0에서 벗어나서 변화하는 것을 보면 일단 상태를 제대로 읽어오고 있는 것을 알 수 있습니다.
초기 상태가 0인 이유
처음 상태를 받아보면 모든 값이 0으로 채워진 것처럼 보이는 경우가 있습니다. 이 상황은 보통 다음 두 가지 이유에서 발생합니다.
- 컨트롤러 인터페이스가 아직 초기화되지 않은 경우
- 시뮬레이터가 아직 상태를 정상적으로 생성하지 못한 경우
즉, 응답은 오지만 실제 데이터가 채워지지 않은 상태다. 그래서 두 가지 처리를 추가했습니다.
- zero-state 감지
- 최초 응답 디버그 출력
이렇게 해두면 문제 상황을 빠르게 구분할 수 있습니다.
“응답이 없다”와 “응답은 있지만 값이 비어 있다”는 완전히 다른 문제다.
여기까지 한 일
이 단계까지 오면서 RealFlight와 통신할 수 있는 기본 구조를 구축했습니다. TCP 연결 위에 SOAP 요청을 얹고 HTTP 응답을 받아 XML을 파싱한 뒤, 상태 데이터를 코드에서 다룰 수 있는 형태로 정리했습니다.
다음 단계
이제 단순히 상태를 읽는 단계에서 나아가 시뮬레이터에 직접 영향을 주는 단계로 넘어갑니다. 다음 글에서는 조종 입력을 보내고 상태와 연결된 피드백 루프를 구성하는 과정을 다룹니다.
함께 보기
- GitHub 저장소: hover-pilot
- 이번 글 기준 구현: rflink-state-read