Robotics/Control Theory

ROS2 × PX4로 시작하는 자율비행 드론 제어 (4) ROS2 패키지 생성

항공학도 2025. 5. 5. 13:22

이 블로그 시리즈에서는 Gazebo + ROS 2 + PX4(MAVROS)를 활용해 드론을 제어하고, 최적 제어 알고리즘 Model Predictive Path Integral (MPPI) 를 직접 구현하는 과정을 기록합니다. 이후에는 더 진화된 강화학습(RL) 기반 제어로까지 확장하며, 최신 오픈소스 생태계가 실제 연구·개발 파이프라인에서 어떻게 유기적으로 엮이는지 보여드릴 예정입니다.

Generated by ChatGPT, super cool!

지난 글(3편) 에서는 파이썬 스크립트 하나로 PX4 Offboard 모드에서 자동 이륙하는 방법에 대해 알아보았습니다. 이번 글에서는 그 코드를 ROS2 패키지로 묶어, ros2 launch 한 줄로 실행하는 방법에 대해 살펴보겠습니다.

1. 패키지화 (+ ros2 launch)의 중요성

지난 시간에 작성한 offboard.py 스크립트를 python3 offboard.py로 실행하면 코드가 동작하면서 드론이 자동이륙하는것을 확인했습니다. 그러나 이러한 파이선 스크립트로는 “현재 터미널·현재 경로·현재 PC”라는 조건이 충족될 때만 잘 작동합니다. 

예를들어서 현재 시뮬레이션을 위한 컴퓨터에는 ~/sim_ws/offboard_control  이라는 경로에 스크립트가 들어 있다면, 실제 실행 명령어는 cd ~/sim_ws/offboard_control && python3 offboard.py 가 될 것입니다. 그러나 실기체 실험으로 넘어가면, 실기체의 온보드 컴퓨터와 개발용 노트북의 파일 위치가 달라질 수 밖에 없기 때문에 자동실행 스크립트를 열어서 위와 같이 하드코딩된 경로를 바꾸어 주어야 합니다. 

이때 패키지화 하여 ros2 launch를 쓰면 패키지에 포함된 install space에서 노드를 찾아 실행하기 때문에 파일 경로를 신경 쓸 필요가 없고, 런치 파일에서 파라미터를 한 줄 인자로 바꿀 수 있어 시뮬레이션‑실기체 간 설정 차이를 명령어 하나로 해결할 수 있습니다. (시뮬레이션과 실제 기체에서의 차이는 FCU URL 포트 설정 변경이 필요합니다, 이는 후에 다루도록 하겠습니다) 

즉, “시뮬레이션에서 잘 됐는데 왜 실기체에서는 안되지?”와 같은 반복적인 환경 의존 버그를 없애고, 실험 때마다 경로·의존성을 다시 맞추느라 시간을 낭비하지 않게 해 주는 것이 ros2 launch로의 변환이 꼭 필요한 이유입니다.

2. 패키지 폴더 구조 해부

일반적인 ROS2 패키지의 폴더 구조는 다음과 같습니다. 우리도 이와 같은 방법으로 패키지를 만들어 볼것입니다. 좀더 살펴보면 offboard_control/ 내부의 동일 이름 폴더가 실제 파이썬 노드 코드를 담고, 
launch/는 그 노드들을 한 번에 실행할 런치파일을 보관합니다.
package.xml은 패키지 이름·의존성·버전 같은 메타데이터를,
setup.py는 entry‑point를 등록해 ros2 run/launch가 실행 파일을 찾도록 합니다.
즉, 코드·실행 시나리오·메타정보가 깔끔하게 분리돼 있어 읽기 쉽고 배포도 간단한 구조입니다.

offboard_control/
├── offboard_control/          # 파이썬 모듈
│   ├── __init__.py
│   └── offboard_takeoff_node.py
├── launch/
│   └── offboard_control.launch.py
├── package.xml
└── setup.py
offboard_control/ 코드 실제 파이선 노드 코드
launch/ 런치 파일 노드를 실행할 런치파일
package.xml 메타데이터 패키지 이름·의존성·라이선스·버전 명시
setup.py 설치 스크립트 console_scripts entry‑point 등록

3. 패키지 뼈대 만들기

그럼이제부터 본격적으로 패키지화를 진행해 보겠습니다. 1편에서 만들어 두었던 ~/flight_ws/src  디렉토리에 offboard_control 노드를 생성해 보겠습니다.

# 1편에서 만들어 두었던 flight_ws/src 디렉토리로 이동
cd ~/flight_ws/src

# ament_python 패키지 생성 및 의존성 선언
ros2 pkg create --build-type ament_python offboard_control \
    --dependencies rclpy geometry_msgs mavros_msgs

여기에서 build-type은 ament_python을 사용했습니다. ament_python패키지는 CMake 설정이나 컴파일 단계가 없기 때문에 colcon build 속도가 빠르고 코드 수정 후 별도의 빌드없이 바로 재실행이 가능합니다. 우리는 시뮬레이션 환경에서 작동을 우선적 목표로 하고 있기 때문에 설정이 쉬운 ament_python으로 하겠습니다. 추후에 실기체 태스트로 넘어갈 때 비용 효율적 한계 및 기타 요구사항으로 인해 c++코드로의 전환이 요구된다면 그때 ament_cmake 또는 ament_cmake_python등 c++의 사용을 고려한 패키지로 변경하는 방법에 대해 알아보도록 하겠습니다.

위의 코드를 실행하면 아래 그림과 같이 패키지의 뼈대가 생성됩니다

ament_python 패키지 생성!

여기서 package.xml을 열어보시면 위의 패키지 생성 명령어에서 선언한 의존성이 추가된것을 볼 수 있습니다. 여기서 rclpy는 "ROS 2 노드를 Python으로 작성할 수 있게 해 주는 클라이언트 라이브러리" 입니다, offboard제어 노드가 파이선으로 구현되어 있기 때문에 이를 ros2에서 사용하기 위해 해당 의존성이 필요합니다.

4. 핵심파일 작성

4.1. offboard_takeoff_node.py

3편에서 작성한 offboard.py 스크립트를 ~/flight_ws/src/offboard_control/offboard_control 경로에 그대로 복사하고 다음 내용을 추가합니다. 해당 스크립트는 자동이륙만 담당하므로 해당 파일이름을 offboard.py 에서 offboard_takeoff_node.py로 변경하도록 하겠습니다. 그후 스크립트의 맨 윗줄에 아래와 같이 shebang을 추가해줍니다.

#!/usr/bin/env python3           # ← shebang 추가

이렇게 하면 리눅스가 "이 스크립트는 python3로 실행해야 한다”는 것을 알아차립니다. 즉 "어느 환경에서든, python3를 찾아서 실행하라"는 지시를 파일 자체에 새겨두는 것입니다. 여담으로 shebang의 의미는 약간의 slang(?)이지만 #!를 영어로 읽은 표현입니다.
#! 이 두 문자를 영어로 읽을 때
    # = sharp 또는 hash
    ! = bang (프로그래머 슬랭)
그래서 합쳐서 “hash‑bang” → 줄여서 “shebang” 이라고 부릅니다.

또한 직접 실행 가능한 프로그램으로 만들기 위해 chmod +x offboard_takeoff_node.py 명령어를 실행하여 실행권한을 부여해 주는 것 도 잊지 마세요

4.2. setup.py - entry-point

패키지 뼈대 만들기에서 생성된 setup.py를 열고

data_files를 찾아 다음과 같이 추가해줍니다.

 data_files=[
        # 패키지 인덱스·package.xml (기존 그대로 변경필요 없음)
        ('share/ament_index/resource_index/packages',
         ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),

        # 추가할 부분, launch 파일 경로를 반드시 등록
        ('share/' + package_name + '/launch',
         ['launch/offboard_control.launch.py']),
    ],

 

다음으로 entry_points를 찾아 다음과 같이 추가해줍니다.

entry_points={
    'console_scripts': [
        #추가할 부분
        'offboard_takeoff = offboard_control.offboard_takeoff_node:main',
    ],
},

offboard_takeoff 가 CLI 실행 이름이 되어서 이후 ros2 run offboard_control offboard_takeoff 로 호출됩니다.

4.3. Launch 디렉토리 및 takeoff.launch.py 파일 생성

~/flight_ws/src/offboard_control 경로안에 launch파일들을 저장할 경로를 생성해 줍니다.

cd ~/flight_ws/src/offboard_control
mkdir launch

이 디렉토리 안에 offboard_control.launch.py의 이름으로 아래와 같이 생성해줍니다.

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='offboard_control',
            executable='offboard_takeoff',
            name='offboard_takeoff_node',
            output='screen',
            parameters=[]   # 고도·속도 파라미터화는 추후 추가
        )
    ])

5. build

  • --symlink-install → 코드를 수정해도 재빌드 없이 즉시 반영
cd ~/ws_offboard
colcon build --symlink-install
source ~/.bashrc
  • ros2 pkg list | grep offboard_control 로 설치 확인

아래와 같이 ros2 pkg list 결과가 나오면 새로 생성된 패키지가 설치 완료되고 실행할 준비가 된 것입니다.

새 패키지 설치 완료

6. 실행

이제 다음의 명령어로 실행을 하면 3편에서 python3 명령어로 실행한것과 같이 드론이 자동이륙 후 호버링하는 것을 확인하실 수 있습니다.

ros2 launch offboard_control offboard_control.launch.py

 

다음 글에서는 MPPI(Model Predictive Path Integral) 컨트롤러를 본격적으로 다룰 예정입니다. 먼저 MPPI가 무엇인지 간단히 핵심만 정리하고, 이어서 궤적 샘플링·코스트 함수·행동 선택 알고리즘을 실제 코드로 보여 드립니다. 그런 뒤 지금 만든 offboard_control 패키지에 컨트롤러 노드를 얹고, 런치 파일에 추가하여 기존 이륙 노드와 함께 사용되도록 구성할 것입니다.