MAVSDK-Python 프로그래밍 완벽 가이드 [제12편]: 문제 해결 가이드 및 로깅 기법

안녕하세요! 자율비행 로봇과 드론 제어 알고리즘 연구에 매진하고 계신 대학생 및 연구원 여러분. 쿼드(QUAD) 드론연구소의 MAVSDK-Python 연재 블로그 열두 번째 시간에 오신 것을 환영합니다.

지금까지 우리는 오프보드(OFFBOARD) 모드를 통한 정밀한 위치/속도 제어부터 키보드를 활용한 수동 제어까지, 자율비행 구현을 위한 핵심적이고 화려한 기능들을 차근차근 마스터해 왔습니다. 여러분의 코드에 맞춰 시뮬레이터 속 드론이 완벽한 궤적을 그리며 날아갈 때의 쾌감은 이루 말할 수 없었을 것입니다.

하지만 실제 연구 환경이나 대학원 랩실에서는 모든 것이 한 번에 매끄럽게 돌아가지 않습니다. “어제까지는 분명히 잘 날았는데, 오늘은 왜 갑자기 연결이 안 되지?”, “코드는 완벽한데 왜 드론이 이륙 명령을 무시할까?”와 같은 벽에 수없이 부딪히게 됩니다. 자율비행 연구에 있어 알고리즘을 짜는 것만큼이나 중요한 역량이 바로 **’신속하고 정확하게 문제를 추적하고 해결하는 디버깅(Debugging) 능력’**입니다.

그래서 이번 **[제12편: 문제 해결 가이드 및 로깅 기법]**에서는 MAVSDK-Python을 사용하며 가장 빈번하게 마주치는 에러들의 원인을 분석하고, 파이썬의 로깅(Logging) 모듈과 MAVSDK 서버 분리 실행을 통해 시스템의 내부 상태를 엑스레이처럼 투명하게 들여다보는 고급 디버깅 기법을 아주 친절하고 상세하게 설명해 드리겠습니다.


1. COMMAND_DENIED 에러의 진실과 try...except 예외 처리

MAVSDK 프로그래밍을 처음 시작하는 연구원들이 가장 당황하는 순간은 붉은색 에러 메시지와 함께 프로그램이 튕겨 나갈 때입니다. 그중 가장 대표적인 것이 바로 ActionError: COMMAND_DENIED입니다.

Python
# 에러 메시지 예시
raise ActionError(result, "arm()")
mavsdk.generated.action.ActionError: COMMAND_DENIED: 'Command denied'; origin: arm(); params: ()

위와 같은 예외(Exception)가 발생했다고 해서 여러분이 작성한 MAVSDK 코드 자체에 문법적인 버그가 있는 것은 아닙니다. 이 메시지는 PX4 비행 제어기(FC)가 안전상의 이유로 여러분의 명령을 명시적으로 거부(Rejected)했다는 뜻입니다.

가장 흔한 원인은 **’글로벌 위치 정보(GPS Fix)가 확보되지 않은 상태에서 무장(Arming)을 시도했을 때’**입니다. 실제 드론이나 시뮬레이터(SITL)가 초기화되고 GPS 신호를 안정적으로 수신하기까지는 시간이 걸리는데, 파이썬 스크립트가 실행되자마자 냅다 arm() 명령을 던지면 비행 제어기는 안전을 위해 이를 단호하게 거부하는 것입니다.

따라서 MAVSDK-Python의 대부분의 제어 함수를 호출할 때는 프로그램이 비정상 종료되는 것을 막기 위해 반드시 try… except 블록을 사용하여 예외 처리를 해주는 습관을 들여야 합니다.

[예외 처리가 적용된 안전한 이륙 코드 예제]

Python
import asyncio
from mavsdk import System
from mavsdk.action import ActionError

async def safe_takeoff():
    drone = System()
    await drone.connect(system_address="udp://:14540")

    print("GPS 신호 대기 중...")
    async for health in drone.telemetry.health():
        if health.is_global_position_ok and health.is_home_position_ok:
            print("-- GPS 신호 확보 완료!")
            break

    # try...except를 활용한 안전한 명령 하달
    try:
        print("-- 모터 무장 (Arming) 시도")
        await drone.action.arm()
        print("-- 이륙 (Takeoff) 시도")
        await drone.action.takeoff()
    except ActionError as e:
        # 명령이 거부되었을 때 프로그램이 죽지 않고 원인을 출력함
        print(f"비행 제어기가 명령을 거부했습니다: {e}")
        # 필요한 비상 안전 로직 추가 (예: 재시도, 관리자 알림 등)

asyncio.run(safe_takeoff())

2. 파이썬 logging 모듈을 활용한 심층 디버깅

단순한 명령 거부가 아니라, 통신 자체가 끊기거나 시스템 내부에서 무슨 일이 일어나는지 전혀 알 수 없을 때가 있습니다. MAVSDK-Python은 내부적으로 실행되는 mavsdk_server에서 발생하는 중요한 메시지들을 자동으로 캡처하여 터미널에 표시해 주는 기능을 갖추고 있습니다.

기본적으로는 에러(Error)와 경고(Warning) 메시지만 표시되지만, 파이썬 내장 logging 모듈을 활용하면 정보(Info)나 심층 디버그(Debug) 메시지까지 모두 끌어올려 확인할 수 있습니다.

① 기본 통신 디버깅 (INFO 레벨)

연구 중 드론과의 연결 시도나 기본 버전 정보, mavsdk_server의 전반적인 작동 상태를 확인하고 싶다면 로깅 레벨을 INFO로 설정하십시오.

Python
import logging
import asyncio
from mavsdk import System

# 로깅 레벨을 INFO로 설정하여 기본 연결 상태 및 서버 정보를 출력합니다.
logging.basicConfig(level=logging.INFO)

async def run():
    drone = System()
    await drone.connect(system_address="udp://:14540")
    # ... 후략 ...

이 코드를 실행하면 단순한 에러뿐만 아니라 백그라운드 서버가 시작되는 과정과 시스템 발견 여부가 상세히 출력됩니다.

② 뼛속까지 들여다보는 상세 디버깅 (DEBUG 레벨)

내부적으로 오고 가는 데이터 패킷이나, 개발자가 아니면 알기 힘든 코어 엔진의 내부 디버그 정보까지 모두 확인해야 하는 복잡한 상황이라면 레벨을 DEBUG로 낮춰보십시오.

Python
import logging
# 내부 컴포넌트 동작, 메시지 송수신 내역 등 모든 디버그 정보를 출력합니다.
logging.basicConfig(level=logging.DEBUG)

이 레벨에서는 화면에 엄청난 양의 로그가 쏟아지므로, 정말 해결이 안 되는 통신 프로토콜 이슈를 추적할 때 유용합니다.

③ 서버 메시지 필터링 기법

파이썬 코드에서 출력하는 로그는 숨기고 오직 mavsdk_server의 작동 상태만 보고 싶거나, 반대로 서버의 로그를 완전히 끄고 싶을 때는 특정 로거(Logger)만 가져와서 레벨을 조정할 수 있습니다.

Python
import logging

# 전체 로깅은 WARNING 이상만 출력하도록 제한하여 터미널을 깨끗하게 유지
logging.basicConfig(level=logging.WARNING)

# 오직 'mavsdk_server'에서 나오는 INFO 메시지만 선택적으로 표시
logging.getLogger('mavsdk_server').setLevel(logging.INFO)

# 만약 서버 출력을 완전히 숨기고 싶다면 아래와 같이 CRITICAL로 설정합니다.
# logging.getLogger('mavsdk_server').setLevel(logging.CRITICAL) 

3. 통신 연결 에러와 mavsdk_server 독립 실행 기법

MAVSDK 코드를 실행할 때 ERROR:mavsdk_server:Unknown protocol이나 Connection failed: Invalid connection URL과 같은 무시무시한 에러를 만나는 경우가 있습니다. 이는 보통 udpin://0.0.0.0:14540과 같은 연결 문자열(Connection String)의 형식이 잘못되었을 때 발생합니다.

이러한 연결 문제를 더 직관적으로 해결하기 위해 우리는 MAVSDK의 구조를 조금 이해할 필요가 있습니다. 파이썬 스크립트에서 await drone.connect()를 호출하면, 패키지에 내장된 mavsdk_server (이전 명칭: backend)라는 C++ 기반의 바이너리 파일이 백그라운드에서 자동으로 실행되어 통신을 중계합니다.

만약 여러분이 파이썬 스크립트를 켤 때마다 알 수 없는 이유로 연결이 바로 끊어진다면, 파이썬을 거치지 않고 이 mavsdk_server 바이너리 파일을 독립적으로 실행하여 순수한 서버 로그만 확인해 보는 것이 최고의 디버깅 방법입니다.

리눅스(우분투) 환경에서 pip3 install --upgrade mavsdk로 설치했다면, 서버 실행 파일은 보통 ~/.local/lib/python3.10/site-packages/mavsdk/bin/ (파이썬 버전에 따라 다름) 경로에 위치합니다.

터미널을 새로 열고 아래와 같이 서버만 단독으로 실행해 봅니다.

Bash
# MAVSDK 서버 바이너리를 수동으로 실행하며 타겟 주소 입력
~/.local/lib/python3.10/site-packages/mavsdk/bin/mavsdk_server udpin://0.0.0.0:14540

서버가 실행되면 아래와 같이 포트를 열고 기체를 기다린다는 로그가 출력됩니다.

Bash
[02:36:31 | Info ] MAVSDK version: v1.4.16
[02:36:31 | Info ] Waiting to discover system on udpin://0.0.0.0:14540...

이 상태에서 PX4 시뮬레이터(SITL)를 켜면, 서버가 새로운 시스템(Autopilot)을 감지하고 50051 포트에서 대기(Listen)한다는 반가운 메시지가 쏟아집니다.

Bash
[02:39:01 | Info ] New system on: 127.0.0.1:14580 (with sysid: 1)
[02:39:02 | Info ] Server started
[02:39:02 | Info ] Server set to listen on 0.0.0.0:50051

이렇게 서버가 독립적으로 안정적으로 구동되는 것을 확인했다면, 이제 여러분의 파이썬 코드에서는 주소 파라미터를 비우고 await drone.connect()만 깔끔하게 호출하여 이 서버에 즉시 연결할 수 있습니다.


4. 연구자를 위한 고급 팁: 원격 연결 (Remote MAVSDK Server)

대학원 랩실 환경에서는 **”시뮬레이터(SITL)는 성능이 좋은 리눅스 워크스테이션에서 돌리고, 파이썬 제어 코드는 내 윈도우 노트북에서 실행하고 싶다”**는 요구사항이 자주 발생합니다.

MAVSDK는 내부적으로 통신에 gRPC를 사용하므로, 이러한 분리된 네트워크 환경에서는 MAVSDK-SERVER를 명시적으로 세팅해야 완벽한 원격 제어가 가능합니다.

이 경우, SITL이 실행 중인 리눅스 PC에서 mavsdk_server를 빌드하거나 설치하여 포트(-p 50051)를 열어두고 실행시켜 둡니다. (예: ./mavsdk_server -p 50051 명령어를 통해 실행.)

그리고 여러분의 윈도우 노트북에 있는 파이썬 코드에서 System() 클래스를 초기화할 때, 해당 리눅스 PC의 IP 주소(예: 172.21.77.220)와 gRPC 포트(50051)를 명시적으로 지정해 줍니다.

Python
from mavsdk import System
import asyncio

async def remote_connect():
    # 백그라운드 서버 자동 실행을 멈추고, 원격지 IP의 gRPC 서버(50051)로 직접 연결 시도
    drone = System(mavsdk_server_address="172.21.77.220", port=50051)
    
    print("원격 MAVSDK 서버에 연결 중...")
    # 이때 system_address 인자는 생략합니다 (원격 서버가 이미 드론과 연결되어 있으므로)
    await drone.connect() 
    
    print("연결 성공! 원격으로 드론을 제어합니다.")
    # 이후 제어 코드는 동일하게 작성

asyncio.run(remote_connect())

이렇게 mavsdk_server_address를 명시적으로 설정하면, 파이썬 스크립트는 내장된 서버를 새로 시작하지 않고 여러분이 지정한 원격 주소의 서버에 즉시 접속하게 됩니다. 이 구조를 이해하시면 다수의 PC를 연동한 복잡한 군집 비행(Swarm) 연구나 지상국(GCS) 소프트웨어 개발에 엄청난 유연성을 확보할 수 있습니다.

<그림> 원격 통신 아키텍처 다이어그램: 윈도우 노트북의 MAVSDK-Python 코드가 IP 네트워크(gRPC, 포트 50051)를 통해 리눅스 워크스테이션의 mavsdk_server에 접속하고, 서버는 다시 UDP(포트 14540)를 통해 PX4 SITL 시뮬레이터를 제어하는 구조도


마치며

이번 제12편에서는 MAVSDK-Python 환경에서 마주칠 수 있는 예외 처리 방법, logging 모듈을 이용한 심도 있는 로그 분석, 그리고 통신 문제 해결을 위한 mavsdk_server의 분리 실행 및 원격 접속 세팅까지 연구원분들에게 피가 되고 살이 되는 디버깅 스킬들을 망라해 보았습니다.

개발을 진행하며 마주치는 빨간 에러 메시지는 결코 여러분의 적이 아닙니다. 오히려 시스템이 현재 어떤 한계점이나 잘못된 전제 조건에 직면해 있는지 친절하게 알려주는 이정표라고 생각하시면 좋습니다. 오늘 배운 로깅과 서버 모니터링 기법을 잘 활용하신다면, 아무리 복잡한 자율비행 알고리즘의 버그라도 끈질기게 추적하여 해결하실 수 있을 것입니다!

지금까지 12편에 걸쳐 쿼드 드론연구소의 연재를 따라오시며 드론 프로그래밍의 기초부터 심화 제어, 그리고 트러블슈팅까지 훌륭하게 완주하신 연구원 여러분께 깊은 존경의 박수를 보냅니다. 여러분의 뜨거운 열정이 만들어낼 멋진 드론 로보틱스의 미래를 항상 응원하겠습니다. 감사합니다!


Author: maponarooo, CEO of QUAD Drone Lab

Date: March 5, 2026

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다