MAVSDK-Python 프로그래밍 완벽 가이드 [제11편]: 키보드 입력을 활용한 드론 수동 제어 만들기

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

지난 **[제10편]**까지 우리는 드론이 스스로 판단하고 정해진 목표를 향해 날아가는 자율비행(위치 제어, 속도 제어, 피드포워드 동시 제어 등)의 핵심 원리들을 심도 있게 다루었습니다. 모든 코드가 완벽하게 짜여 있어 드론이 스스로 장애물을 피하고 목표지점에 도달하는 것을 보면 엄청난 성취감을 느끼셨을 것입니다.

하지만 연구를 진행하다 보면, 자율비행 알고리즘 개발만큼이나 중요한 것이 바로 **’비상 상황 시의 수동 개입(Human-in-the-loop)’**과 **’데이터 수집을 위한 직접 조종’**입니다. 강화학습이나 모방학습(Imitation Learning)을 위해 사람이 직접 드론을 비행시키며 센서 데이터를 수집해야 할 때도 있고, 코딩한 알고리즘이 오작동을 일으켰을 때 즉각적으로 연구원이 조종권을 가져와야 할 때도 있습니다.

그래서 이번 **[제11편: 키보드 입력을 활용한 드론 수동 제어 만들기]**에서는, 복잡한 RC 조종기 없이 우리가 연구실에서 흔히 사용하는 PC의 키보드(WASD, 방향키 등)만으로 마치 PC 게임을 하듯 드론을 수동 조종(Manual Control)하는 프로그램을 파이썬으로 직접 구현해 보겠습니다.


1. Manual Control(수동 제어) 클래스의 이해

지금까지 기체의 움직임을 제어할 때는 Action 클래스나 Offboard 클래스를 주로 사용했습니다. 하지만 키보드나 조이스틱의 입력을 드론에게 직접 전달하기 위해서는 MAVSDK의 ManualControl 클래스를 사용해야 합니다.

키보드를 통해 드론을 조종한다는 것은 결국 조종기의 스틱을 움직이는 것과 동일한 신호를 파이썬 코드로 쏘아주는 것을 의미합니다. 이를 위해 우리는 4가지 핵심 축(Axis)의 개념을 이해해야 합니다.

  • Roll (롤): 좌우 기울기. (일반적으로 -1.0 ~ 1.0 범위)
  • Pitch (피치): 전후 기울기. (일반적으로 -1.0 ~ 1.0 범위)
  • Throttle (스로틀): 상하 상승/하강 속도. (일반적으로 0.0 ~ 1.0 범위. 0.5가 고도를 유지하는 호버링(Hover) 상태입니다.)
  • Yaw (요): 좌우 회전. (일반적으로 -1.0 ~ 1.0 범위)

우리가 사용할 핵심 메서드는 바로 **set_manual_control_input(roll, pitch, throttle, yaw)**입니다. 키보드의 특정 키가 눌렸을 때, 이 4가지 변수 값을 적절하게 변경하여 비행 제어기(FC)에 지속적으로 전송하면 드론이 움직이게 됩니다.


2. 키보드 입력 처리와 비동기(asyncio) 병렬 처리의 마법

키보드로 드론을 조종하는 프로그램을 짤 때 초보 연구원분들이 가장 많이 겪는 병목이 바로 **’무한 루프의 충돌’**입니다.

드론에게 조종 신호를 보내기 위해서는 while True:와 같은 무한 루프를 돌며 끊임없이 현재의 roll, pitch, throttle, yaw 상태값을 쏘아주어야 합니다. 그런데 동시에 우리가 어떤 키보드를 누르고 있는지도 끊임없이 백그라운드에서 감지해야 합니다. 동기식(Synchronous) 프로그래밍에서는 키 입력을 기다리느라 제어 신호를 보내는 루프가 멈추는(Blocking) 현상이 발생합니다.

우리는 이 문제를 **[제5편]**에서 완벽하게 마스터한 **파이썬의 asyncio**를 통해 해결할 수 있습니다!

  • 태스크 1: 키보드 입력을 감지하여 값을 업데이트하는 무한 루프 (getKeyboardInput 함수)
  • 태스크 2: 업데이트된 값을 드론에게 지속적으로 전송하는 무한 루프 (manual_control_drone 함수)

이 두 가지 작업을 asyncio.ensure_future() (또는 asyncio.create_task())를 사용하여 백그라운드에서 병렬로 실행시키면, CPU 멈춤 없이 아주 매끄러운 수동 조종이 가능해집니다.


3. 실전 예제 코드 분석

쿼드 드론연구소의 튜토리얼에서 사용하는 KeyPressModule(약칭 kp) 모듈을 활용하여 키보드 입력을 받는 예제를 단계별로 분석해 보겠습니다. (💡 참고: KeyPressModule은 키보드 이벤트를 쉽게 캡처하기 위해 제공되는 커스텀 모듈입니다.)

① 글로벌 변수 초기화

Python
import asyncio
from mavsdk import System
import KeyPressModule as kp

kp.init()
drone = System()
# 조종 초기값: Roll, Pitch, Yaw는 중립(0), Throttle은 호버링 유지(0.5)로 설정
roll, pitch, throttle, yaw = 0, 0, 0.5, 0 

② 태스크 1: 키보드 입력 감지 로직 (getKeyboardInput)

이 비동기 함수는 0.1초마다 키보드 상태를 확인하여 전역 변수(Global)를 업데이트합니다.

Python
async def getKeyboardInput(my_drone):
    global roll, pitch, throttle, yaw
    while True:
        # 매 루프마다 값을 초기 중립 상태로 리셋 (키를 떼면 제자리에 멈추도록)
        roll, pitch, throttle, yaw = 0, 0, 0.5, 0
        value = 0.5

        # 방향키를 통한 Pitch 및 Roll 제어
        if kp.getKey("LEFT"): pitch = -value
        elif kp.getKey("RIGHT"): pitch = value
        
        if kp.getKey("UP"): roll = value
        elif kp.getKey("DOWN"): roll = -value

        # W, S 키를 통한 고도(Throttle) 제어
        if kp.getKey("w"): throttle = 1.0  # 상승
        elif kp.getKey("s"): throttle = 0.0  # 하강

        # A, D 키를 통한 기수 회전(Yaw) 제어
        if kp.getKey("a"): yaw = -value  # 좌회전
        elif kp.getKey("d"): yaw = value   # 우회전

        # 기능키: i(비행 모드 확인), r(무장/Arm), l(착륙/Land)
        elif kp.getKey("i"):
            asyncio.ensure_future(print_flight_mode(my_drone))
        elif kp.getKey("r") and my_drone.telemetry.landed_state():
            await my_drone.action.arm()
        elif kp.getKey("l") and my_drone.telemetry.in_air():
            await my_drone.action.land()

        # 다른 비동기 태스크가 실행될 수 있도록 숨고르기
        await asyncio.sleep(0.1)

여기서 주목할 점은 루프가 시작될 때마다 변수들을 0, 0, 0.5, 0으로 리셋한다는 것입니다. 덕분에 사용자가 키보드에서 손을 떼면, 드론은 즉시 조종 신호를 중립으로 판단하고 그 자리에서 안전하게 호버링(제자리 비행)을 유지하게 됩니다.

③ 태스크 2: 수동 제어 신호 전송 로직 (manual_control_drone)

Python
async def manual_control_drone(my_drone):
    global roll, pitch, throttle, yaw
    while True:
        # 업데이트된 R,P,T,Y 값을 드론에 전송
        await my_drone.manual_control.set_manual_control_input(roll, pitch, throttle, yaw)
        await asyncio.sleep(0.1)

이 함수 역시 무한 루프를 돌며 getKeyboardInput에 의해 실시간으로 변하는 roll, pitch, throttle, yaw 값을 드론에게 쏘아줍니다.


4. 전체 통합 예제 코드 (manual_control.py)

앞서 살펴본 로직들과 드론 연결 과정을 모두 통합한 완전한 실행 코드입니다. 여러분의 개발 환경에 스크립트를 작성하여 테스트해 보시기 바랍니다.

Python
#!/usr/bin/env python3
# Copyright. 2025. 쿼드(QUAD) 드론연구소

import asyncio
from mavsdk import System
import KeyPressModule as kp

# 키보드 입력 모듈 초기화
kp.init()
drone = System()

# 수동 제어 글로벌 변수 (Format: [roll, pitch, throttle, yaw])
roll, pitch, throttle, yaw = 0, 0, 0.5, 0

async def getKeyboardInput(my_drone):
    global roll, pitch, throttle, yaw
    while True:
        # 기본값 리셋 (호버링)
        roll, pitch, throttle, yaw = 0, 0, 0.5, 0
        value = 0.5

        if kp.getKey("LEFT"): pitch = -value
        elif kp.getKey("RIGHT"): pitch = value

        if kp.getKey("UP"): roll = value
        elif kp.getKey("DOWN"): roll = -value

        if kp.getKey("w"): throttle = 1.0
        elif kp.getKey("s"): throttle = 0.0

        if kp.getKey("a"): yaw = -value
        elif kp.getKey("d"): yaw = value

        elif kp.getKey("i"):
            asyncio.ensure_future(print_flight_mode(my_drone))
        elif kp.getKey("r") and my_drone.telemetry.landed_state():
            print("드론 아밍(Arm) 명령 전송!")
            await my_drone.action.arm()
        elif kp.getKey("l") and my_drone.telemetry.in_air():
            print("드론 착륙(Land) 명령 전송!")
            await my_drone.action.land()

        await asyncio.sleep(0.1)

async def print_flight_mode(my_drone):
    async for flight_mode in my_drone.telemetry.flight_mode():
        print("현재 FlightMode: ", flight_mode)
        break # 한 번만 출력하고 종료

async def manual_control_drone(my_drone):
    global roll, pitch, throttle, yaw
    while True:
        # print(roll, pitch, throttle, yaw) # 값 변화 모니터링 시 주석 해제
        await my_drone.manual_control.set_manual_control_input(roll, pitch, throttle, yaw)
        await asyncio.sleep(0.1)

async def run_drone():
    # 1. 키보드 입력 감지 태스크 백그라운드 실행
    asyncio.ensure_future(getKeyboardInput(drone))
    
    # 2. 드론 연결
    await drone.connect(system_address="udp://:14540")
    print("Waiting for drone to connect...")
    async for state in drone.core.connection_state():
        if state.is_connected:
            print("-- Connected to drone!")
            break

    # 3. GPS 및 위치 정보 안정화 확인
    async for health in drone.telemetry.health():
        if health.is_global_position_ok and health.is_home_position_ok:
            print("-- Global position state is good enough for flying.")
            break

    # 4. 수동 제어 신호 전송 태스크 백그라운드 실행
    asyncio.ensure_future(manual_control_drone(drone))
    
    print("=========================================")
    print(" 조종 준비 완료! 단축키 안내:")
    print(" [R]: 무장(Arm)   [L]: 착륙(Land)")
    print(" [W/S]: 고도 상승/하강  [A/D]: 기수 좌/우 회전")
    print(" [UP/DOWN/LEFT/RIGHT]: 기체 전후좌우 이동")
    print("=========================================")

async def run():
    """드론 연결 및 수동 제어 메인 함수"""
    await asyncio.gather(run_drone())

if __name__ == "__main__":
    # 메인 비동기 루프 실행 (CTRL-C 로 종료될 때까지 무한 유지)
    asyncio.ensure_future(run())
    asyncio.get_event_loop().run_forever()

5. 실행 결과 및 연구 응용 방안

코드를 실행하고 시뮬레이터 화면을 확인해 보십시오. 먼저 ‘r’ 키를 눌러 모터를 회전(Arming)시킨 후, ‘w’ 키를 꾹 누르고 있으면 드론의 Throttle 값이 1.0이 되어 힘차게 하늘로 솟아오를 것입니다. 키보드에서 손을 떼면 Throttle 값이 0.5로 돌아가며 현재 고도에서 호버링합니다.

그 상태에서 방향키를 입력하면 드론이 전후좌우로 미끄러지듯 이동하고, ‘a’‘d’ 키를 이용해 드론의 방향을 회전시킬 수도 있습니다. 마치 흥미로운 레이싱 게임을 하듯 여러분이 직접 키보드로 튜닝한 제어값을 입력해 볼 수 있습니다.

[연구원을 위한 꿀팁] 이 프로그램은 단순히 ‘재미’를 위한 것이 아닙니다. 자율 주행 연구에서 모델 학습을 위한 지도 학습(Supervised Learning) 데이터셋을 만들 때, 이 스크립트의 while True: 루프 안에 카메라 이미지 저장 코드와 당시의 R,P,T,Y 조종 값을 csv 파일로 로깅하는 코드를 추가해 보십시오. 연구원님이 직접 드론을 장애물 사이로 수동 비행시키면, 그것이 곧 훌륭한 “전문가의 행동 패턴 데이터(Expert Demonstration)”가 되어 강화학습 인공지능을 훈련시키는 핵심 재료로 쓰일 수 있습니다.


마치며

이번 제11편에서는 파이썬의 비동기(asyncio) 처리 능력을 십분 발휘하여, 키보드 입력을 받아들임과 동시에 끊임없이 제어 신호를 전송하는 수동 제어(Manual Control) 프로그램을 성공적으로 구축해 보았습니다.

복잡한 오프보드(Offboard) 알고리즘부터 오늘 배운 직관적인 수동 제어 툴까지, 이제 여러분은 상황에 맞게 드론을 자유자재로 다룰 수 있는 강력한 무기들을 모두 손에 넣으셨습니다!

다음 시간부터는 실제 연구 현장에서 가장 빈번하게 마주치는 이슈들을 해결하는 [문제 해결 (Troubleshooting) 및 시스템 확장] 단계로 넘어가 보겠습니다.

코드를 따라 하시면서 잘 안되는 부분이나 KeyPressModule 설정에 관련된 문의가 있으시다면 언제든 댓글로 질문을 남겨주세요. 쿼드 드론연구소가 여러분의 열정적인 연구를 항상 지지하고 돕겠습니다. 감사합니다!


YouTube Demo

재생

Author: maponarooo, CEO of QUAD Drone Lab

Date: March 5, 2026

Similar Posts

답글 남기기

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