MAVSDK-Python 프로그래밍 완벽 가이드 [제5편]: MAVSDK 제어를 위한 Python ‘asyncio’ 완벽 이해

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

지난 4편에서는 System, Action, Telemetry, Offboard 등 드론에게 구체적인 명령을 내리는 MAVSDK의 주요 클래스들을 살펴보았습니다. 그때 예제 코드들을 보시면서 아마 공통적으로 눈에 띄는 낯선 파이썬 키워드들이 있었을 것입니다. 바로 async, await, async for와 같은 문법들입니다.

기존의 순차적인 파이썬 스크립트 작성에 익숙하신 분들이라면 이 부분에서 큰 진입장벽을 느끼곤 합니다. 하지만 MAVSDK-Python은 모든 주요 함수가 async def로 작성된 철저한 비동기(asyncio) 기반 라이브러리입니다. 따라서 이 산을 넘지 않고서는 드론을 제대로 제어할 수 없습니다.

이번 **[제5편: MAVSDK 제어를 위한 Python ‘asyncio’ 완벽 이해]**에서는 프로그래머들이 종종 어려워하는 파이썬의 비동기 프로그래밍 개념을 드론 제어의 관점에서 아주 친절하고 알기 쉽게 파헤쳐 보겠습니다.


1. 드론 제어에 왜 하필 비동기(asyncio)가 필수적일까요?

우리가 작성하는 일반적인 파이썬 코드는 ‘동기식(Synchronous)’으로 작동합니다. 첫 번째 줄의 실행이 완전히 끝날 때까지 두 번째 줄은 멈춰서 기다리는(Blocking) 방식이죠.

하지만 드론과 지상국(또는 컴패니언 컴퓨터) 간의 MAVLink 통신 환경을 생각해 볼까요? 드론 통신은 다음과 같은 특성이 있습니다.

  1. 실시간 통신: 고도, GPS 위치, 배터리 상태 등 센서 데이터가 초당 수십 번씩 끊임없이 쏟아져 들어옵니다.
  2. 이벤트 기반: “GPS 신호가 잡혔다!”, “배터리가 부족하다!”, “임무를 완료했다!” 등 언제 발생할지 모르는 이벤트에 즉각적으로 반응해야 합니다.
  3. I/O 지연(Delay): 네트워크나 UDP 통신 기반이기 때문에 신호를 보내고 응답을 받기까지 필연적으로 미세한 딜레이가 존재합니다.

만약 이를 동기식 코드로 작성하여 time.sleep()과 같은 함수로 CPU를 멈춰버린다면 어떻게 될까요? 코드가 멈춰있는 동안 드론은 새로운 센서 데이터를 처리하지 못해 궤도를 이탈하거나, 장애물 회피 명령을 받지 못해 충돌해 버릴 수도 있습니다.

이러한 상황을 해결하기 위해 Python의 asyncio 라이브러리를 사용하여 비동기(Non-blocking)적으로 처리하면, CPU가 하나의 작업이 끝나기를 멍하니 기다리지 않고 그 자투리 시간에 다른 센서 데이터를 처리하는 등 효율적이고 끊김 없는 병렬 드론 제어가 가능해집니다.


2. 논문을 쓰기 전 꼭 알아야 할 ‘asyncio’ 핵심 문법 7가지

비동기 프로그래밍의 깊은 곳까지 전부 알 필요는 없습니다. MAVSDK 제어를 위해 연구원들이 반드시 숙지해야 할 핵심 문법 7가지를 요약해 드립니다.

  • async def (비동기 함수 정의): 일반적인 def 앞에 async를 붙여 선언합니다. 이 함수는 호출 시 즉시 실행되지 않고, 비동기적으로 실행될 수 있는 ‘코루틴(Coroutine)’ 객체를 반환합니다.
  • await (비동기 함수 호출 대기): async def로 선언된 함수를 호출할 때는 반드시 앞에 await 키워드를 붙여야 합니다. 이는 “이 작업의 결과를 기다리겠지만, 기다리는 동안 CPU를 멈추지 않고 다른 작업이 실행될 수 있도록 기회를 주겠다(Non-blocking)”는 의미입니다.
  • asyncio.run() (프로그램의 진입점): 일반 동기식 파이썬 코드에서 최초의 비동기 함수(주로 main()이나 run())를 실행하여 전체 비동기 이벤트 루프를 가동하는 역할을 합니다.
  • asyncio.create_task() (병렬 작업 예약): 비동기 함수를 백그라운드 작업(Task)으로 등록하여 동시에 병렬로 실행되도록 만듭니다. 여러 센서를 동시에 감시할 때 핵심적으로 사용됩니다.
  • async for (비동기 반복문 – 스트림 처리): 드론의 텔레메트리(Telemetry) 정보처럼 실시간으로 끊임없이 들어오는 데이터 스트림을 구독(Subscribe)하고 반복 처리할 때 매우 유용하게 사용됩니다.
  • async with (비동기 컨텍스트 매니저): 네트워크 소켓이나 파일을 비동기적으로 열고 닫는 등 리소스를 관리할 때 사용합니다.
  • asyncio.gather() (여러 작업 동시 실행 후 대기): 여러 개의 비동기 함수를 한꺼번에 던져놓고, 모든 작업이 완료될 때까지 기다릴 때 사용합니다.

3. 실전 예제 1: asyncio.sleep()의 중요성

비동기 환경에서 절대 사용하면 안 되는 파이썬 내장 함수가 바로 time.sleep()입니다. 이 함수는 CPU 자체를 멈춰버리기 때문입니다. 대신 우리는 await asyncio.sleep()을 사용해야 합니다.

간단한 이착륙 코드에 텔레메트리 모니터링을 더한 예제를 보겠습니다.

Python
import asyncio
from mavsdk import System

# 백그라운드에서 드론의 위치를 계속 출력하는 비동기 함수
async def print_position(drone):
    # async for를 통해 위치 정보가 업데이트될 때마다 수신 [3]
    async for position in drone.telemetry.position():
        print(f"위치: 위도 {position.latitude_deg}, 경도 {position.longitude_deg}") [4]

async def run():
    drone = System()
    await drone.connect(system_address="udp://:14540")
    
    print("Arming...")
    await drone.action.arm() [4]
    
    # 위치 출력 작업을 백그라운드 태스크로 병렬 실행 시작 [3]
    task = asyncio.create_task(print_position(drone))
    
    print("Takeoff...")
    await drone.action.takeoff() [4]
    
    # 💡 핵심: time.sleep(10) 대신 asyncio.sleep(10)을 사용합니다 [3].
    # 10초를 대기하는 동안 CPU를 블로킹하지 않으므로, 
    # 백그라운드에 등록해둔 print_position 태스크가 위치 정보를 계속 출력할 수 있습니다.
    await asyncio.sleep(10) [4]
    
    print("Landing...")
    await drone.action.land() [4]
    
    # 임무가 끝나면 백그라운드 태스크 취소
    task.cancel()

if __name__ == "__main__":
    asyncio.run(run()) # 전체 루프 실행 [4]

이 예제에서 볼 수 있듯, asyncio.sleep()은 ROS(Robot Operating System)를 다뤄보신 분들에게 익숙한 rate.sleep()과 매우 유사한 개념으로, 다른 작업(위치 출력)이 실행될 수 있도록 숨통을 트여주는 역할을 합니다.


4. 실전 예제 2: 여러 센서 데이터를 동시에(병렬) 모니터링하기

연구를 진행하다 보면 기체의 배터리 전압, 현재 고도, 비행 모드 변경 상태를 동시에 수집하여 로깅(Logging)해야 하는 경우가 빈번합니다. async for 구문은 루프를 돌며 무한히 대기하는 특성이 있으므로, 이 세 가지를 동기식으로 짜면 절대 동시에 수집할 수 없습니다.

이때 **asyncio.create_task()**를 활용하면 마법처럼 병렬 처리가 가능해집니다. 아래 예제를 보며 완벽히 이해해 봅시다.

Python
import asyncio
from mavsdk import System

# 1. 배터리를 모니터링하는 태스크 [4]
async def monitor_battery(drone):
    async for battery in drone.telemetry.battery():
        print(f"[🔋 Battery] {battery.remaining_percent * 100:.1f}%")

# 2. 고도를 모니터링하는 태스크 [4]
async def monitor_altitude(drone):
    async for position in drone.telemetry.position():
        print(f"[📡 Altitude] {position.relative_altitude_m:.2f} m")

# 3. 비행 모드를 모니터링하는 태스크 [4]
async def monitor_flight_mode(drone):
    async for flight_mode in drone.telemetry.flight_mode():
        print(f"[✈️ Flight Mode] {flight_mode}")

async def run():
    drone = System()
    await drone.connect(system_address="udp://:14540") # SITL 기본 포트로 연결 [4]
    
    print("드론 연결 대기 중...")
    async for state in drone.core.connection_state():
        if state.is_connected:
            print("✅ 드론 연결됨!")
            break
            
    # 🚀 동시에 3가지 모니터링 태스크 생성 및 병렬 실행 시작 [4]
    # 이제 이 3개의 함수는 메인 로직과 상관없이 백그라운드에서 알아서 데이터를 수집합니다.
    battery_task = asyncio.create_task(monitor_battery(drone)) [4]
    altitude_task = asyncio.create_task(monitor_altitude(drone)) [4]
    mode_task = asyncio.create_task(monitor_flight_mode(drone)) [4]
    
    # 메인 로직: 30초 동안 비행(또는 대기)한다고 가정 [4]
    await asyncio.sleep(30)
    
    # 30초 후, 실행 중인 모니터링 태스크들을 강제로 종료합니다 [4]
    battery_task.cancel()
    altitude_task.cancel()
    mode_task.cancel()
    print("⏹️ 모니터링을 안전하게 종료합니다.")

if __name__ == "__main__":
    asyncio.run(run())

이 코드를 실행(SITL 환경)하면 아래와 같이 배터리, 고도, 비행 모드 정보가 섞여서 실시간으로 출력되는 것을 볼 수 있습니다. 각 센서 데이터가 수신되는 주파수(Hz)에 맞춰 비동기적으로 병렬 처리되기 때문입니다.

Python
[🔋 Battery] 99.3%
[📡 Altitude] 0.02 m
[✈️ Flight Mode] HOLD
[🔋 Battery] 99.2%
[📡 Altitude] 0.04 m
...

💡 연구자를 위한 커스터마이징 팁: 만약 이 데이터들을 논문 데이터 분석용으로 저장하고 싶다면, 파이썬의 open('log.txt', 'w') 파일 입출력 코드를 각 모니터링 함수에 추가하기만 하면 훌륭한 비행 데이터 로거(Data Logger)로 탈바꿈합니다. 또한 drone.telemetry.armed() 등을 추가로 태스크에 등록하여 모니터링 항목을 손쉽게 늘릴 수도 있습니다.


마무리하며

이번 제5편에서는 자율비행 프로그래밍의 가장 중요한 뼈대인 Python asyncio 기반의 비동기 제어에 대해 알아보았습니다. 처음에는 async defawait가 번거롭고 낯설게 느껴질 수 있지만, 왜 이 방식이 수많은 센서와 실시간 통신해야 하는 로봇 제어에 최적의 설계인지 충분히 이해하셨을 것입니다.

오늘 배운 asyncio.create_task()await asyncio.sleep()의 개념만 확실히 잡아두셔도 앞으로 마주할 수많은 MAVSDK 예제 코드가 한눈에 들어오게 됩니다.

이제 드론의 언어(클래스)도 알았고, 대화하는 방식(비동기 프로그래밍)도 터득했습니다! 다음 **[제6편: MAVSDK-Python 기본 샘플 프로그램 분석]**에서는 Dronecode 재단에서 제공하는 공식 샘플 코드들을 분석하며, 지금까지 배운 내용들이 어떻게 종합적인 자율 비행으로 이어지는지 실제 연구 사례에 적용해 보는 시간을 갖겠습니다.

따라오시면서 헷갈리는 문법이 있다면 언제든 댓글로 질문해 주세요. 연구원 여러분의 성공적인 코딩을 응원합니다. 감사합니다!


YouTube 강좌

재생

Author: maponarooo, CEO of QUAD Drone Lab

Date: March 3, 2026

Similar Posts

답글 남기기

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