VDA5050 프로토콜 완벽 가이드 - AGV·AMR 플릿 통신 표준과 C++ 구현
이 글의 핵심
VDA5050은 서로 다른 제조사의 AGV·AMR이 하나의 관제 시스템(Master Control)과 MQTT로 통신할 수 있도록 만든 개방형 표준입니다. order·state·instantActions 같은 메시지 구조와 Topic 규칙, 그리고 Fraunhofer IML이 공개한 C++ 구현체 libVDA5050++를 이용한 실제 통합 방법을 다룹니다.
들어가며: 로봇마다 프로토콜이 다르면 생기는 문제
물류 창고나 제조 현장에 AGV(Automated Guided Vehicle)나 AMR(Autonomous Mobile Robot)을 도입할 때 흔히 마주치는 현실이 있습니다. A사의 무인지게차와 B사의 자율주행 로봇을 같은 현장에 동시에 운용하려고 보면, 두 로봇이 서로 다른 벤더 전용 프로토콜을 쓰기 때문에 하나의 관제 시스템(Fleet Manager, VDA5050에서는 Master Control이라 부릅니다)으로 통합 관제가 되지 않는 문제입니다. 결국 벤더마다 별도의 관제 소프트웨어를 두거나, 벤더별 어댑터를 그때그때 직접 만들어야 했습니다.
VDA5050은 이 문제를 표준화로 풀기 위해 등장했습니다. 독일 자동차산업협회(VDA, Verband der Automobilindustrie)와 독일 기계공업연맹(VDMA)이 카를스루에 공과대학(KIT) 물류연구소(IFL) 및 다수의 산업 파트너와 함께 개발한 개방형 인터페이스 표준으로, 제조사와 무관하게 AGV·AMR이 관제 시스템과 통신할 수 있는 plug-and-play 환경을 목표로 합니다. 표준을 따르는 로봇이라면 원칙적으로 어떤 제조사의 것이든 같은 관제 시스템에 연결해 쓸 수 있습니다.
VDA5050이란 무엇인가
VDA5050은 Master Control(관제 시스템)과 AGV/AMR 사이에 주고받을 정보의 구조와, 그 정보를 어떤 MQTT Topic으로 전달할지를 정의한 표준입니다. 핵심 설계 원칙은 다음과 같습니다.
- 전송 계층: MQTT를 전제로 하며, 하나의 브로커(Broker)에 관제 시스템과 여러 대의 AGV가 클라이언트로 접속합니다.
- 메시지 포맷: 모든 메시지는 JSON이며, 각 메시지 타입마다 공개된 JSON 스키마가 있습니다.
- 제조사 독립성: 로봇의 내부 제어 로직이나 항법 알고리즘에는 전혀 관여하지 않고, 오직 관제 ↔ 로봇 사이의 인터페이스만 표준화합니다.
- 버전 관리: 2019년 초판(1.1) 이후 2.0, 2.1(2025년 1월)을 거쳐 이기종 대규모 플릿 통합을 지원하는 3.0.0까지 발전해 왔습니다.
flowchart LR MC["Master Control
(Fleet Manager)"] <-->|MQTT| Broker(("MQTT Broker")) Broker <--> AGV1["AGV #1
(제조사 A)"] Broker <--> AGV2["AGV #2
(제조사 B)"] Broker <--> AGV3["AMR #3
(제조사 C)"]
MQTT Topic 구조
VDA5050은 Topic 이름 자체에 어떤 인터페이스·버전·제조사·차량인지가 드러나도록 계층 규칙을 정해 두었습니다.
{interfaceName}/{majorVersion}/{manufacturer}/{serialNumber}/{topic}
예를 들어 제조사 RobotCompany의 시리얼 번호 0001 차량이 state를 발행한다면 다음과 같은 Topic이 됩니다.
uagv/v2/RobotCompany/0001/state
- interfaceName: 보통
uagv(unified AGV)를 사용합니다. - majorVersion:
v1,v2처럼 메이저 버전만 표기합니다. 메이저 버전이 다르면 스키마 호환을 보장하지 않으므로 Topic 자체를 분리합니다. - manufacturer / serialNumber: 차량을 유일하게 식별하는 값으로, 관제 시스템이 여러 제조사의 차량을 동시에 구독·발행할 때 이 값으로 라우팅합니다.
- topic: 아래에서 다루는
order,state,instantActions,connection,visualization,factsheet중 하나입니다.
Topic 이름은 대소문자를 구분하므로 instantactions와 instantActions는 서로 다른 Topic으로 취급됩니다. 실무에서 흔한 실수이니 클라이언트 라이브러리를 직접 구현한다면 특히 주의해야 합니다.
핵심 메시지 타입
| Topic | 방향 | 역할 | QoS | Retain |
|---|---|---|---|---|
order | Master Control → AGV | 노드·엣지로 구성된 이동 경로와 수행할 액션을 전달 | 0 | 아니오 |
instantActions | Master Control → AGV | 정지·일시정지·재개 등 즉시 실행할 단발성 명령 | 0 | 아니오 |
state | AGV → Master Control | 현재 위치, 주문 진행 상황, 배터리, 에러 등 전체 상태 | 0 | 아니오 |
connection | AGV → Master Control | 온라인/오프라인/연결 끊김 상태 (Last Will 활용) | 1 | 예 |
visualization | AGV → Master Control | 관제 화면에서 실시간 위치를 그리기 위한 경량 상태 | 0 | 아니오 |
factsheet | AGV → Master Control | 차량 제원, 지원 기능, 제약 조건 (정적에 가까운 정보) | 0 | 예 |
order와 instantActions는 절대 Retain하지 않습니다. Retain 메시지로 남겨두면 차량이 재접속할 때마다 브로커가 예전 명령을 다시 재생해 버려, 차량이 이미 끝난 주문을 또 실행하려 드는 심각한 오작동으로 이어질 수 있기 때문입니다. 반대로 connection과 factsheet는 Retain해 두어야, 관제 시스템이나 새로 접속한 클라이언트가 즉시 최신 상태를 알 수 있습니다.
Order 메시지: Node·Edge·Action 모델
VDA5050의 order는 이동 경로를 그래프로 표현합니다. Node(정지 지점)와 Node를 잇는 Edge(구간)의 배열로 경로 전체를 기술하고, 각 Node·Edge에는 그 지점에서 수행할 Action(예: 화물 픽업, 신호등 대기)을 붙일 수 있습니다.
{
"headerId": 42,
"timestamp": "2026-09-07T09:12:00.000Z",
"version": "2.1.0",
"manufacturer": "RobotCompany",
"serialNumber": "0001",
"orderId": "order-8841",
"orderUpdateId": 0,
"nodes": [
{
"nodeId": "node-A",
"sequenceId": 0,
"released": true,
"nodePosition": { "x": 12.4, "y": 3.1, "mapId": "warehouse-1f" },
"actions": []
},
{
"nodeId": "node-B",
"sequenceId": 2,
"released": true,
"nodePosition": { "x": 18.9, "y": 3.1, "mapId": "warehouse-1f" },
"actions": [
{
"actionId": "pick-001",
"actionType": "pick",
"blockingType": "HARD",
"actionParameters": [{ "key": "lhd", "value": "pallet-77" }]
}
]
}
],
"edges": [
{
"edgeId": "edge-A-B",
"sequenceId": 1,
"released": true,
"startNodeId": "node-A",
"endNodeId": "node-B",
"actions": []
}
]
}
sequenceId는 Node와 Edge를 번갈아 가며 짝수·홀수로 부여해 전체 순서를 명시합니다. released: true인 구간까지만 차량이 실제로 진행하며, 관제 시스템은 이후 구간을 나중에 orderUpdateId를 올려가며 점진적으로 채워 넣을 수 있습니다(장거리 경로를 한 번에 다 계산해 두지 않아도 되는 구조입니다).
sequenceDiagram participant MC as Master Control participant Br as MQTT Broker participant AGV as AGV MC->>Br: PUBLISH order (nodes/edges/actions) Br->>AGV: order 전달 AGV->>Br: PUBLISH state (driving, nodeStates 갱신) Br->>MC: state 전달 Note over AGV: node-B 도착, pick 액션 시작 AGV->>Br: PUBLISH state (actionStates: RUNNING → FINISHED) Br->>MC: state 전달
state 메시지는 대칭적으로 nodeStates, edgeStates, actionStates, batteryState, errors, driving 같은 필드로 현재 진행 상황을 보고합니다. 정확한 필드 구성은 사용하는 VDA5050 메이저 버전에 따라 조금씩 다를 수 있으므로, 실제 구현에서는 반드시 공식 JSON 스키마 저장소를 함께 확인하는 것이 안전합니다.
C++로 구현하기: libVDA5050++
VDA5050 표준 자체는 언어를 규정하지 않기 때문에 Python(ROS 패키지 vda5050_connector), TypeScript/Node.js(vda-5050-lib) 등 다양한 구현체가 있습니다. 이 중 C++로 완성도 있게 구현된 대표적인 오픈소스 라이브러리가 libVDA5050++입니다. Fraunhofer IML(프라운호퍼 물류연구소)이 Silicon Economy 연구 프로젝트의 일환으로 개발해 Open Logistics Foundation을 통해 공개했습니다.
아키텍처: 두 개의 어댑터로 분리
libVDA5050++는 VDA5050 프로토콜 로직 전체를 라이브러리 내부에 캡슐화하고, 사용자는 양쪽 끝의 어댑터(Adapter) 인터페이스만 구현하면 되도록 설계되어 있습니다.
- Master Control Adapter (
vda5050pp::interface_mc): 관제 시스템 쪽에서 order·instantActions를 만들고 state·visualization을 수신하는 인터페이스. - AGV Adapter (
vda5050pp::interface_agv): 실제 차량 제어 쪽에서 라이브러리가 전달하는 주행·액션 명령을 받아 로봇 하드웨어/미들웨어(ROS 등)로 연결하는 인터페이스.
flowchart TB
subgraph "관제 시스템 프로세스"
MC["여러분의 Fleet Manager 로직"] --> MCA["Master Control Adapter"]
end
subgraph "libVDA5050++"
MCA <--> Core["VDA5050 프로토콜 코어
(order 검증·상태 관리·타이밍 감시)"]
Core <--> AGVA["AGV Adapter"]
end
subgraph "차량 제어 프로세스"
AGVA --> Nav["ActionHandler / NavigationHandler 구현체"]
Nav --> HW["실제 로봇 하드웨어·ROS"]
end
Core <-->|MQTT| Broker(("MQTT Broker"))
이런 구조 덕분에 로봇 제조사는 자사 하드웨어와 연결되는 ActionHandler, NavigationHandler 몇 개만 구현하면 되고, VDA5050 메시지의 파싱·검증·타이밍 감시·Topic 발행 같은 반복적인 프로토콜 로직은 라이브러리가 대신 처리해 줍니다.
실제 구현해야 하는 인터페이스
AGV 쪽을 구현한다면 라이브러리가 요구하는 핸들러들을 채워 넣는 방식으로 개발합니다.
| 인터페이스 | 역할 |
|---|---|
ActionHandler | start/pause/resume/stop — order·instantActions로 지정된 액션 실행 |
StepBasedNavigationHandler | 노드를 하나씩 순서대로 밟아가는(line-guided) 차량의 주행 처리 |
ContinuousNavigationHandler | 여러 노드를 동시에 고려하는 자유 주행(AMR) 차량의 주행 처리 |
PauseResumeHandler | 일시정지·재개 요청을 실제 주행 제어와 연동 |
Connector / ConnectorPassive | MQTT 송수신을 별도 스레드로 처리할지, 폴링 방식으로 처리할지 선택 |
// AGV 쪽에서 구현하는 ActionHandler 예시 (개념 스케치)
#include <vda5050++/interface_agv/action_handler.h>
class MyActionHandler : public vda5050pp::interface_agv::ActionHandler {
public:
void start(const vda5050pp::Action &action) override {
// action.actionType (예: "pick")을 보고 실제 하드웨어 제어 API 호출
// 완료되면 라이브러리에 actionState를 RUNNING -> FINISHED로 보고
}
void pause(const vda5050pp::Action &action) override { /* 일시정지 처리 */ }
void resume(const vda5050pp::Action &action) override { /* 재개 처리 */ }
void stop(const vda5050pp::Action &action) override { /* 중단 처리 */ }
};
차량 설명 정보는 vda5050pp::interface_agv::agv_description::AGVDescription 구조체로 구성해 라이브러리에 전달하며, 라이브러리 핸들은 다음과 같이 스레드 모드를 선택해 실행합니다.
// library_handle_ptr 은 라이브러리 초기화 과정에서 생성한 핸들
library_handle_ptr->spinParallel(4); // 내부 스레드 4개로 비동기 실행
// 또는
library_handle_ptr->spinOnce(); // 이벤트 루프에 직접 통합(폴링 방식)
CMake로 통합하기
libVDA5050++는 CMake 패키지로 배포되며, 필요한 커넥터·로거 모듈을 타깃 단위로 링크합니다.
find_package(libvda5050++ REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC
libvda5050++::console_logger # 콘솔 로깅 모듈
libvda5050++::mqtt_connector # MQTT 송수신 커넥터
)
미들웨어 중립적으로 설계된 덕분에, 나중에 MQTT가 아닌 다른 전송 계층(OPC UA 등)이 필요해지면 mqtt_connector 대신 다른 커넥터 모듈로 교체하는 것도 가능합니다.
⚠️ README에 명시된 대로 이 라이브러리는 VDA5050 1.1.0 버전을 기준으로 구현되어 있고, 표준·라이브러리 인터페이스 모두 계속 발전 중입니다. 최신 2.x/3.x 스펙을 그대로 요구하는 프로젝트라면 필요한 필드가 지원되는지 먼저 확인해야 합니다.
다른 언어 구현체는 어떤 것이 있나
C++ 외에도 VDA5050을 채택한 생태계마다 자체 구현체가 있습니다.
- Python / ROS:
vda5050_connector는 ROS 2 노드로 동작하며, ROS 액션·토픽과 VDA5050 MQTT 메시지를 서로 변환해 줍니다. 이미 ROS 기반으로 로봇을 개발 중이라면 가장 접근하기 쉬운 선택지입니다. - TypeScript / Node.js:
vda-5050-lib는 관제 시스템(Master Control) 쪽 구현에 초점을 맞춘 라이브러리로, 웹 기반 플릿 관리 대시보드와 함께 쓰기 좋습니다. - 공식 스펙 저장소: VDA5050/VDA5050에는 언어에 상관없이 참고할 JSON 스키마와 마크다운 스펙 문서가 정리되어 있어, 어떤 언어로 직접 구현하더라도 가장 먼저 확인해야 할 출처입니다.
실전 도입 시나리오
- 이기종 AGV 플릿 통합: 서로 다른 제조사의 지게차형 AGV와 자율주행 카트를 하나의 관제 소프트웨어로 통합 운용할 때, VDA5050을 공통 인터페이스로 채택하면 제조사별 어댑터만 추가하는 방식으로 확장할 수 있습니다.
- 레거시 로봇의 VDA5050 브리지: 이미 운용 중인 로봇이 VDA5050을 지원하지 않는다면, 로봇의 기존 API를 감싸는 얇은 브리지 프로세스를 만들어
state를 발행하고order/instantActions를 구독하도록 하는 방식으로 점진적 마이그레이션이 가능합니다. - 시뮬레이션·테스트: 실제 로봇 없이 관제 시스템을 개발·검증할 때, VDA5050을 말하는 가상 AGV 시뮬레이터를 MQTT 클라이언트로 띄워 order를 주고받으며 통합 테스트를 진행할 수 있습니다.
모범 사례와 트러블슈팅
| 증상 | 흔한 원인 | 대응 |
|---|---|---|
| 재접속한 차량이 예전 주문을 다시 실행함 | order/instantActions를 Retain으로 발행 | 두 Topic은 절대 Retain하지 않기 (표준에서도 명시적으로 금지) |
| 관제 화면에서 차량이 계속 오프라인으로 보임 | connection 토픽에 Last Will 미설정 | MQTT 연결 시 Last Will을 connection Topic에 CONNECTIONBROKEN으로 등록 |
| 서로 다른 제조사 차량 간 Topic이 충돌함 | manufacturer/serialNumber 값이 중복되거나 형식이 다름 | 팀 차원에서 식별자 네이밍 규칙을 먼저 합의 |
order 반영이 안 됨 | orderUpdateId를 올리지 않고 같은 주문을 재전송 | 주문 내용이 바뀔 때마다 orderUpdateId를 반드시 증가 |
| 여러 관제 시스템이 한 차량을 동시에 제어하려 함 | Master Control이 이중화되었는데 조율 로직이 없음 | 액티브-스탠바이 구조로 두고 한 번에 하나의 Master Control만 order를 발행하도록 강제 |
정리
VDA5050은 AGV·AMR과 관제 시스템 사이의 통신을 MQTT 기반 JSON 메시지로 표준화해, 서로 다른 제조사의 로봇을 하나의 관제 시스템으로 통합 운용할 수 있게 해주는 개방형 인터페이스입니다. order(경로+액션), state(현재 상태), instantActions(즉시 명령), connection/factsheet(연결·제원, Retain 대상) 같은 메시지 타입과 Topic 규칙만 이해하면, 이미 존재하는 오픈소스 구현체를 활용해 빠르게 통합을 시작할 수 있습니다. C++ 환경이라면 Fraunhofer IML이 공개한 libVDA5050++가 Master Control Adapter/AGV Adapter로 역할을 분리한 미들웨어 중립 설계를 제공하므로, 벤더별 어댑터 개발 부담을 크게 줄여줍니다.