[PX4 ROS 2 Programming] 5편: 심화 기법 (서비스 서버 및 다중 기체 시뮬레이션)
안녕하세요! 자율 비행 로봇과 군집 드론 시스템을 연구하시는 대학생 및 연구원 여러분, 다시 뵙게 되어 대단히 반갑습니다.
지난 4편에서는 비행 제어의 꽃인 ‘Offboard(오프보드) 제어’를 통해 ROS 2에서 C++ 노드를 작성하여 드론에 시동을 걸고 목표 고도까지 이륙시키는 과정을 함께 진행해 보았습니다. 여기까지 성공하셨다면 단일 기체를 제어하는 기본적인 ROS 2 아키텍처는 완벽히 마스터하신 셈입니다.
하지만 실제 연구 현장에서는 요구사항이 훨씬 복잡해집니다. *”내가 내린 이륙 명령이 씹히지 않고 제대로 전달되었을까?”*를 코드로 확실히 보장받고 싶거나, *”한 대가 아니라 여러 대의 드론을 띄워서 군집 비행(Swarm Flight) 알고리즘을 테스트하고 싶은데?”*와 같은 고민이 생기기 마련입니다.
따라서 이번 5편에서는 여러분의 연구 수준을 한 단계 끌어올려 줄 두 가지 심화 기법인 PX4 ROS 2 서비스(Service) 통신과 다중 기체(Multi-Vehicle) 시뮬레이션 제어에 대해 아주 상세하고 친절하게 알아보겠습니다.
1. 명령과 응답의 확실한 교환: PX4 ROS 2 서비스 (Services)
ROS 2의 통신 방식은 크게 데이터를 일방적으로 뿌리는 토픽(Topic, 발행/구독) 방식과, 요청을 보내고 응답을 기다리는 서비스(Service, 클라이언트/서버) 방식으로 나뉩니다.
기존 4편에서 우리는 VehicleCommand 메시지를 **토픽(Topic)**으로 발행하여 시동을 걸었습니다. 이 방식은 구현이 간단하지만, 이른바 ‘Fire-and-Forget(쏘고 잊어버리기)’ 방식이므로 네트워크 지연이나 시스템 상태 문제로 PX4가 명령을 무시하더라도 ROS 2 노드는 그 사실을 알 길이 없습니다.
연구원분들이 강화학습이나 정밀 제어 로직을 짤 때 이는 치명적인 버그로 이어질 수 있습니다. 이를 해결하기 위해 PX4 v1.15부터 uXRCE-DDS 미들웨어에 ROS 2 서비스(Service) 서버 기능이 정식으로 도입되었습니다.

VehicleCommand 서비스 활용하기
PX4는 /fmu/vehicle_command라는 이름의 서비스 서버를 제공합니다. ROS 2 애플리케이션(클라이언트)이 이 서비스로 VehicleCommand 요청을 보내면, PX4는 명령을 수행한 뒤 성공 여부를 담은 VehicleCommandAck (Acknowledge) 메시지를 정확히 해당 요청자에게만 반환해 줍니다.
그럼 어떻게 코드를 작성하는지 살펴볼까요? 아래는 비동기(Asynchronous) 방식으로 시동(Arm) 서비스 요청을 보내고 응답을 받는 C++ 예제 코드의 핵심 부분입니다.
#include <rclcpp/rclcpp.hpp>
#include <px4_msgs/srv/vehicle_command.hpp> // 토픽 msg가 아닌 srv 헤더를 사용합니다.
using namespace std::chrono_literals;
class OffboardControlSrv : public rclcpp::Node
{
public:
OffboardControlSrv() : Node("offboard_control_srv")
{
// 1. VehicleCommand 서비스 클라이언트 생성
vehicle_command_client_ = this->create_client<px4_msgs::srv::VehicleCommand>("/fmu/vehicle_command");
}
// 시동 명령 요청 함수
void request_arm()
{
RCLCPP_INFO(this->get_logger(), "시동(Arm)을 요청합니다...");
// 2. 서비스 요청(Request) 객체 생성 및 데이터 채우기
auto request = std::make_shared<px4_msgs::srv::VehicleCommand::Request>();
px4_msgs::msg::VehicleCommand msg{};
msg.command = px4_msgs::msg::VehicleCommand::VEHICLE_CMD_COMPONENT_ARM_DISARM;
msg.param1 = 1.0; // 1.0은 Arm을 의미
msg.target_system = 1;
msg.target_component = 1;
msg.source_system = 1;
msg.source_component = 1;
msg.from_external = true;
msg.timestamp = this->get_clock()->now().nanoseconds() / 1000;
request->request = msg;
// 3. 비동기식 서비스 요청 전송 및 콜백 함수(response_callback) 바인딩
auto result = vehicle_command_client_->async_send_request(
request,
std::bind(&OffboardControlSrv::response_callback, this, std::placeholders::_1)
);
RCLCPP_INFO(this->get_logger(), "명령이 네트워크로 전송되었습니다.");
}
private:
rclcpp::Client<px4_msgs::srv::VehicleCommand>::SharedPtr vehicle_command_client_;
// 4. 응답을 처리하는 콜백 함수
void response_callback(rclcpp::Client<px4_msgs::srv::VehicleCommand>::SharedFuture future)
{
// 응답이 왔는지 1초간 대기하며 확인
auto status = future.wait_for(1s);
if (status == std::future_status::ready) {
// 응답 수신 성공!
auto reply = future.get()->reply;
RCLCPP_INFO(this->get_logger(), "응답 수신 완료! 결과 코드: %d", reply.result);
// reply.result 값이 0(VEHICLE_CMD_RESULT_ACCEPTED)이면 명령이 정상 수행된 것입니다.
} else {
RCLCPP_INFO(this->get_logger(), "서비스 응답 대기 중이거나 실패했습니다...");
}
}
};
코드 핵심 파헤치기:
- 클라이언트 생성:
this->create_client<px4_msgs::srv::VehicleCommand>를 사용하여/fmu/vehicle_command서비스에 통신 채널을 엽니다. - 비동기 요청 (async_send_request): 로봇 프로그래밍에서는 코드가 멈춰있는(Blocking) 현상이 매우 위험합니다. 따라서 응답을 무작정 기다리지 않고,
async_send_request를 사용하여 명령을 던진 뒤 “응답이 오면response_callback함수를 실행해!”라고 시스템에 예약을 걸어둡니다. - 응답 확인 (future.wait_for): 콜백 함수 내에서
future.get()->reply를 통해 PX4가 보낸VehicleCommandAck데이터를 까볼 수 있습니다. 만약reply.result가 0으로 떨어졌다면, 여러분의 드론은 완벽하게 시동이 걸린 것입니다!
이 기법을 사용하면 “이륙 명령 -> 성공 확인 -> 목표 위치 비행 -> 성공 확인 -> 착륙” 과 같이 매우 견고하고 신뢰성 높은 상태 머신(State Machine)을 설계할 수 있습니다.
2. 한계 돌파: 다중 기체(Multi-Vehicle) 시뮬레이션 제어
한 대의 드론을 완벽하게 제어하게 되었다면, 다음 목표는 자연스럽게 여러 대의 드론을 이용한 **군집 비행(Multi-Vehicle)**으로 넘어갑니다. 과거에는 드론 대수만큼 통신 에이전트를 띄워야 해서 컴퓨터 자원 소모가 심했지만, uXRCE-DDS가 도입된 현재는 단 하나의 Agent만으로도 UDP 네트워크를 통해 여러 대의 PX4 클라이언트(드론)를 동시에 연결하고 제어할 수 있습니다.

다중 기체 시뮬레이션을 제어하기 위해 연구원분들이 반드시 짚고 넘어가야 할 세 가지 핵심 규칙을 안내해 드립니다.
1) px4_instance와 네임스페이스(Namespace)
시뮬레이터(Gazebo)에서 드론을 여러 대 띄우면, PX4는 내부적으로 각 기체에 0부터 시작하는 고유한 px4_instance 번호를 부여합니다. 여러 대의 드론에서 뿜어져 나오는 데이터가 ROS 2 네트워크에서 섞이는 것을 방지하기 위해, 시스템은 이 번호를 바탕으로 토픽에 자동으로 **네임스페이스(Namespace)**를 붙여줍니다.
px4_instance = 0(첫 번째 드론): 기본적으로 네임스페이스가 붙지 않습니다 (실제 드론과 동일한 환경을 유지하기 위함). 토픽 이름:/fmu/out/sensor_combinedpx4_instance = 1(두 번째 드론):px4_1이라는 접두사가 붙습니다. 토픽 이름:/px4_1/fmu/out/sensor_combinedpx4_instance = 2(세 번째 드론):px4_2라는 접두사가 붙습니다. 토픽 이름:/px4_2/fmu/out/sensor_combined
따라서 특정 드론의 데이터를 구독(Subscribe)하거나 명령을 발행(Publish)하려면, 노드를 작성할 때 토픽 이름 앞에 이 네임스페이스를 정확히 명시해 주어야 합니다. (단, 환경변수 PX4_UXRCE_DDS_NS를 통해 이 규칙을 강제로 덮어씌울 수도 있습니다).
2) 시스템 ID (MAV_SYS_ID)의 자동 할당
다중 기체 환경에서 각 드론은 자신만의 고유한 인식표인 MAVLink 시스템 ID(MAV_SYS_ID)를 갖습니다. 시뮬레이션에서는 이 값이 px4_instance에 맞춰 자동으로 계산됩니다. 수식은 매우 간단합니다: MAV_SYS_ID = px4_instance + 1
- 첫 번째 드론 (
instance 0):MAV_SYS_ID= 1 - 두 번째 드론 (
instance 1):MAV_SYS_ID= 2 - 세 번째 드론 (
instance 2):MAV_SYS_ID= 3
3) ★ 가장 많이 하는 실수: target_system 값 맞추기 ★
이 부분이 대학원생들이 군집 비행 코드를 짤 때 밤을 새우게 만드는 주범입니다!
명령을 내리기 위해 VehicleCommand 메시지를 작성할 때, target_system이라는 필드가 있습니다. PX4는 네트워크로 들어온 명령의 target_system 값이 자신의 MAV_SYS_ID와 정확히 일치하거나 0(Broadcast)일 때만 명령을 수행하고, 다르면 철저하게 무시해버립니다.
예를 들어, 여러분이 **세 번째 드론(px4_instance = 2)**에게 시동 명령을 내리고 싶다면, 코드를 다음과 같이 작성해야 합니다.
// 1. 네임스페이스를 맞추어 퍼블리셔(또는 서비스 클라이언트) 생성
auto publisher = this->create_publisher<px4_msgs::msg::VehicleCommand>("/px4_2/fmu/in/vehicle_command", 10);
// 2. target_system 값을 해당 드론의 시스템 ID로 정확히 지정!
px4_msgs::msg::VehicleCommand msg{};
msg.command = px4_msgs::msg::VehicleCommand::VEHICLE_CMD_COMPONENT_ARM_DISARM;
msg.param1 = 1.0;
// px4_instance가 2이므로, 시스템 ID는 2 + 1 = 3 입니다.
msg.target_system = 3; // <--- 이 부분을 틀리면 드론이 명령을 무시합니다! [16]
msg.target_component = 1;
// ... (나머지 메시지 채우기)
publisher->publish(msg);
이 규칙만 머릿속에 확실히 각인해 두신다면, 수십 대의 드론이 밤하늘을 수놓는 화려한 군집 비행 시뮬레이션도 여러분의 키보드 끝에서 손쉽게 통제할 수 있습니다.
마치며
이번 5편에서는 로봇 연구의 퀄리티를 대폭 높여줄 수 있는 묵직한 두 가지 무기를 장착했습니다.
- 일방적인 명령 하달을 넘어 확실하게 응답을 보장받는 PX4 ROS 2 서비스(VehicleCommand) 통신 기법
- 토픽 네임스페이스와
target_systemID의 정확한 매칭을 통한 다중 기체(Multi-Vehicle) 통제 방법
지금까지 다룬 1~5편의 지식만으로도, 여러분은 ROS 2 기반의 드론 자율 비행 시스템을 설계하고 구현하는 데 있어 전문가 수준의 튼튼한 뼈대를 갖추게 되었습니다. 이제 이 뼈대 위에 여러분만의 창의적인 컴퓨터 비전(Vision) 알고리즘이나 인공지능 강화학습 모델을 얹어 마음껏 하늘을 누비시길 바랍니다.
이어지는 마지막 **6편: 최신/실험적 기능 (Interface Library와 Translation Node)**에서는 PX4 최신 버전(v1.15, v1.16)에 도입된 매우 강력한 C++ 라이브러리와 버전을 뛰어넘는 메시지 변환 기법을 소개해 드리며 이 기나긴 블로그 연재의 대장정을 마무리하겠습니다.
질문이나 막히는 부분이 있다면 언제든 댓글로 남겨주세요. 연구원 여러분의 성공적인 시뮬레이션을 응원합니다!
YouTube 강좌

Author: maponarooo, CEO of QUAD Drone Lab
Date: February 28, 2026
