VDA5050으로 AGV·AMR 관제 통신하기: MQTT 토픽, order·state·instantActions, libVDA5050++ 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이 관제 시스템과 통신할 수 있는 환경을 목표로 합니다. 표준을 따르는 로봇이라면 원칙적으로 어떤 제조사의 것이든 같은 관제 시스템에 연결할 수 있습니다.

VDA5050이 정의하는 범위: 관제와 AGV 사이의 인터페이스

VDA5050은 Master Control(관제 시스템)과 AGV/AMR 사이에 주고받을 정보의 구조와, 그 정보를 어떤 MQTT Topic으로 전달할지를 정의한 표준입니다.

전송 계층은 MQTT를 전제로 하며, 하나의 브로커에 관제 시스템과 여러 대의 AGV가 클라이언트로 접속합니다. 모든 메시지는 JSON이고, 메시지 타입마다 공개된 JSON 스키마가 있습니다. 표준은 로봇의 내부 제어 로직이나 항법 알고리즘에는 관여하지 않고, 관제와 로봇 사이의 인터페이스만 정의합니다. 버전은 1.x(2019년 1.1)에서 시작해 2022년 2.0, 이후 2.1로 개정되어 왔고, 다음 메이저 버전 작업도 진행되어 왔으므로 실제 도입 시점의 최신 버전은 공식 저장소에서 확인하는 것이 안전합니다.

VDA5050이 정의하는 범위: 관제와 AGV 사이의 인터페이스 다이어그램(주요 항목: MQTT, Master Control(Fleet Manager), MQTT Broker, AGV #1(제조사 A), AGV #2(제조사 B), 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 중 하나입니다.

MQTT Topic 이름은 대소문자를 구분하므로 instantactions와 instantActions는 서로 다른 Topic입니다. 클라이언트를 직접 구현할 때 이 철자 차이 때문에 명령이 전달되지 않는 경우가 있으니 주의해야 합니다.

핵심 메시지 타입

관제 시스템과 AGV가 MQTT 브로커를 거쳐 order·instantActions와 state·visualization·connection·factsheet를 주고받는 구조

그림: 아래 표를 그림으로 옮긴 것이다. 관제 시스템은 order와 instantActions를, 차량은 state·visualization·connection·factsheet를 발행하고, 모든 메시지는 같은 형식의 MQTT 토픽으로 구분된다.

Topic방향역할QoSRetain
orderMaster Control → AGV노드·엣지로 구성된 이동 경로와 수행할 액션을 전달0아니오
instantActionsMaster Control → AGV정지·일시정지·재개 등 즉시 실행할 단발성 명령0아니오
stateAGV → Master Control현재 위치, 주문 진행 상황, 배터리, 에러 등 전체 상태0아니오
connectionAGV → Master Control온라인/오프라인/연결 끊김 상태 (Last Will 활용)1예
visualizationAGV → Master Control관제 화면에서 실시간 위치를 그리기 위한 경량 상태0아니오
factsheetAGV → Master Control차량 제원, 지원 기능, 제약 조건 (정적에 가까운 정보)0예

표준 6.2절이 QoS를 정한 토픽은 order·state·visualization(QoS 0)과 connection(QoS 1)이고, Retain은 6.14절이 connection 메시지에 쓰라고 정합니다. 표의 instantActions·factsheet QoS와 Retain 열, order·instantActions의 비Retain은 2.0.0 문서에 명시된 값이 아니라 실무에서 쓰는 설정입니다. order와 instantActions는 Retain하지 않는 편이 안전합니다. Retain 메시지로 남겨 두면 차량이 재접속할 때마다 브로커가 예전 명령을 다시 전달해, 차량이 이미 끝난 주문을 또 실행하려 드는 오작동으로 이어질 수 있기 때문입니다. 반대로 connection은 표준대로 Retain하고, 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인 구간(base)까지만 실제로 진행하고, released: false인 뒷부분(horizon)은 앞으로 갈 예정 경로로만 참고합니다. 관제 시스템은 같은 orderId에 orderUpdateId를 올린 주문을 보내 horizon을 점진적으로 release할 수 있으므로, 장거리 경로를 처음부터 모두 확정하지 않아도 됩니다. 업데이트의 첫 노드를 직전 base의 마지막 노드와 맞춰 이어붙이는 규칙, 로봇이 주문을 받아들이거나 거부하는 판단 순서, nodeStates·lastNodeId로 진행을 보고하는 방법은 VDA5050 order 수명주기에서 예제 메시지로 자세히 다룹니다.

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 표준은 구현 언어를 규정하지 않기 때문에 ROS 2용 vda5050_connector, TypeScript/Node.js용 vda-5050-lib 등 여러 구현체가 있습니다. C++ 쪽의 대표적인 오픈소스 라이브러리가 libVDA5050++입니다. Fraunhofer IML(프라운호퍼 물류연구소)이 Silicon Economy 연구 프로젝트의 일환으로 개발해 Open Logistics Foundation을 통해 공개했습니다.

라이브러리 README에 따르면 libVDA5050++는 VDA5050 2.0.0을 구현한 오픈소스로, 제조사가 자사 AGV를 표준에 빠르게 맞출 수 있게 하는 것이 목적입니다. 이 절의 내용은 v2.11.3의 문서와 헤더를 기준으로 하며, README가 인터페이스는 이후 릴리스에서 바뀔 수 있다고 밝히고 있으므로 다른 버전을 쓴다면 해당 버전의 docs/와 헤더를 확인해야 합니다. 이전 1.x 계열(interface_agv::ActionHandler, spinParallel 같은 이름)과는 클래스 구성과 초기화 방식이 다릅니다.

구조: 관제 쪽은 MQTT, 차량 쪽만 구현

README는 라이브러리가 관제 시스템과는 MQTT 인터페이스(Master Control Adapter)로, 차량과는 가벼운 인터페이스(AGV Adapter)로 만난다고 설명합니다. 차량 쪽 개발자가 구현하는 것은 AGV Adapter이고, order 검증, 시퀀스·액션 순서 관리, state 조립, 주기 발행 같은 VDA5050 로직은 모두 라이브러리 안에 있습니다.

flowchart TB
  MC["관제 시스템 (Master Control)"] <-->|"MQTT: order, instantActions, state"| Core
  subgraph "libVDA5050++"
    Core["메시지 검증, order 스케줄러, state 조립, 타이밍"]
  end
  Core --> Handlers["내가 구현: Navigation / Action / Query 핸들러"]
  Handlers --> HW["실제 로봇 하드웨어, ROS"]
  HW -->|"위치, 도착, 액션 상태"| Core

구현해야 하는 인터페이스

v2.x에서 차량 쪽이 채우는 것은 아래입니다.

인터페이스역할
BaseNavigationHandler다음 노드로 이동(navigateToNextNode) 또는 노드 구간 단위 이동(upcomingSegment), cancel/pause/resume/reset. 도착은 setNodeReached, 위치는 setPosition으로 알린다
BaseActionHandler, SimpleMultiActionHandlerprepare가 돌려주는 on_start/on_pause/on_resume/on_cancel 콜백으로 pick·drop·충전 같은 액션 실행. 상태는 ActionState로 알린다
BaseQueryHandler일시정지·재개 가능 여부, zone set 수용 여부에 답한다. 단순한 경우 Config 기본값으로 대체
StatusSink, NavigationSink, ControlSink적재물, 오류, 배터리, 주행 여부 같은 정보를 state에 넣는다

초기화와 등록은 vda5050pp::Handle로 합니다. 핸들러 등록은 initialize 뒤에만 가능합니다.

vda5050pp::Config cfg;
auto &agv = cfg.refAgvDescription();
agv.agv_id = "agv001";
agv.manufacturer = "acme";
agv.serial_number = "agv001";
cfg.refMqttSubConfig().refOptions().server = "tcp://localhost:1883";
cfg.refMqttSubConfig().refOptions().interface = "uagv";

vda5050pp::Handle handle;
handle.initialize(cfg);
handle.registerNavigationHandler(std::make_shared<MyNavigation>());
handle.registerActionHandler(std::make_shared<MyActions>());

interface를 직접 넣은 것은 의도적입니다. 문서의 기본값은 uagv이지만 코드로 만든 Config는 빈 문자열로 시작해서, 실제로 발행되는 토픽이 /v2/...가 되는 것을 확인했습니다. 핸들러 구현, 실행해서 얻은 state 메시지, order 검증과 horizon 갱신 동작은 libVDA5050++ 어댑터 만들기에 따로 정리했습니다.

CMake로 통합하기

v2.11.3은 CMake 패키지로 설치하거나 서브모듈로 포함해 쓰고, 어댑터에서는 타깃 하나에 링크합니다. 서브모듈로 포함할 때는 add_subdirectory(third_party/libvda5050++) 뒤에 같은 타깃 이름을 씁니다. 빌드에는 CMake 3.25 이상과 OpenSSL이 필요합니다.

find_package(libvda5050++ REQUIRED)
target_link_libraries(my_app PUBLIC libvda5050++::vda5050++)

MQTT 접속 정보는 Config의 [module.Mqtt](코드로는 refMqttSubConfig)로 정하고, manufacturer와 serial_number가 토픽 경로를 결정합니다.

다른 언어 구현체

C++ 외에도 VDA5050을 채택한 생태계마다 구현체가 있습니다. ROS 2 기반이라면 vda5050_connector가 ROS 토픽·액션과 VDA5050 MQTT 메시지를 서로 변환해 주므로 가장 접근하기 쉽습니다. TypeScript/Node.js의 vda-5050-lib는 Master Control 쪽과 AGV 쪽 컨트롤러 클래스를 모두 제공해, 웹 기반 관제 도구나 가상 AGV를 만들 때 쓸 수 있습니다. 어떤 언어로 직접 구현하든 가장 먼저 확인할 출처는 JSON 스키마와 스펙 문서가 정리된 공식 저장소 VDA5050/VDA5050입니다.

도입 시나리오

VDA5050이 가장 큰 효과를 내는 경우는 이기종 AGV 플릿 통합입니다. 서로 다른 제조사의 지게차형 AGV와 자율주행 카트를 하나의 관제 소프트웨어로 운용할 때 VDA5050을 공통 인터페이스로 정해 두면, 새 차량을 들일 때 해당 차량이 표준을 지원하는지만 확인하면 됩니다.

이미 운용 중인 로봇이 VDA5050을 지원하지 않는다면, 로봇의 기존 API를 감싸는 얇은 브리지 프로세스를 만들어 state를 발행하고 order/instantActions를 구독하게 하는 방식으로 점진적으로 옮겨 갈 수 있습니다. 실제 로봇 없이 관제 시스템을 개발할 때도 VDA5050 메시지를 주고받는 가상 AGV를 MQTT 클라이언트로 띄워 통합 테스트를 진행할 수 있습니다.

트러블슈팅

증상흔한 원인대응
재접속한 차량이 예전 주문을 다시 실행함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를 발행하도록 강제

이어서 읽을 글