VDA5050 커스텀 데이터 전송 가이드 - actionParameters·informations로 표준 확장하기
이 글의 핵심
VDA5050은 표준 JSON 스키마로 AGV·AMR과 관제 시스템의 통신을 규격화하지만, 실무에서는 제조사 고유의 진단 데이터나 부가 상태를 함께 실어 보내야 하는 경우가 많습니다. actionParameters, state.informations, factsheet 확장 필드, 벤더 전용 MQTT 토픽이라는 네 가지 공식·비공식 확장 지점을 언제·어떻게 안전하게 쓸지, 그리고 향후 표준 버전 업그레이드와 충돌하지 않는 네이밍 전략을 배터리 셀 온도 모니터링 예제로 끝까지 구현하며 정리합니다.
들어가며: 표준을 지키면서 표준에 없는 것을 보내기
VDA5050 프로토콜 완벽 가이드에서 order·state·instantActions 같은 표준 메시지 구조와 6개 MQTT 토픽의 역할을 정리했고, VDA5050 Action 상태 머신 완전 분석에서는 표준이 정의한 필드 안에서 pick 액션이 실제로 어떻게 실행되는지를 다뤘습니다. 두 글 모두 “표준이 이미 정의해 둔 필드를 어떻게 정확히 구현하는가”에 초점을 맞췄습니다.
하지만 현장에 투입해 보면 금세 다른 종류의 질문에 부딪힙니다. 배터리 관리 시스템(BMS)이 셀 단위 온도 분포를 갖고 있는데 state 메시지의 batteryState에는 전체 배터리 잔량과 온도 하나만 담을 자리가 있다면, 나머지 온도 데이터는 어디에 실어야 할까요. 리프트나 컨베이어처럼 VDA5050이 애초에 상정하지 않은 특수 하드웨어의 부가 상태는 어떻게 보고해야 할까요. 관제 시스템이 특정 제조사의 AGV에만 있는 진단 센서 값을 미리 알고 싶다면 어디에 물어봐야 할까요.
이런 질문에 대해 VDA5050은 “알아서 아무 필드나 추가하라”고 방치하지 않습니다. 표준 자체가 커스텀 데이터를 위한 공식 확장 지점을 몇 군데 마련해 두었고, 그 지점 밖에서 확장하고 싶다면 어떤 트레이드오프를 감수해야 하는지도 문서 구조상 어느 정도 암시하고 있습니다. 이 글은 그 확장 지점들을 방향(관제→AGV, AGV→관제, 사전 공지, 완전 커스텀)별로 나누어 실전 관점에서 정리합니다.
확장 지점을 정리하는 네 가지 축
본격적인 설명에 들어가기 전에, 앞으로 다룰 네 가지 메커니즘이 서로 어떤 축 위에 있는지 먼저 짚고 가겠습니다. 이 그림을 기준점으로 삼으면 “지금 내가 보내려는 데이터는 어느 지점에 실어야 하는가”라는 질문에 훨씬 빠르게 답할 수 있습니다.
flowchart TB subgraph MC["Master Control"] end subgraph AGV["AGV / AMR"] end MC -->|"order.actions[].actionParameters
(실행 시점 커스텀 파라미터)"| AGV AGV -->|"state.informations
(런타임 진단·상태)"| MC AGV -.->|"factsheet 확장 필드
(사전 공지, 접속 시 1회성)"| MC AGV <-.->|"커스텀 MQTT 토픽
(표준 밖, 선택적 병행 채널)"| MC
actionParameters는 특정 액션이 실행되는 그 순간에만 의미 있는 일회성 지시 데이터이고, informations는 매 state 주기마다 반복해서 흘러나오는 런타임 진단 스트림입니다. factsheet 확장 필드는 연결 초기에 “내가 이런 능력이 있다”를 한 번 선언하는 정적 메타데이터이고, 커스텀 토픽은 이 세 지점 모두로 표현하기 어려운 고빈도·대용량 데이터를 위한 최후의 수단입니다. 이 네 가지를 순서대로 살펴보겠습니다.
1. actionParameters — 관제에서 AGV로 커스텀 파라미터 전달하기
order의 각 action 객체는 actionParameters라는 key-value 배열을 가질 수 있습니다. 표준 액션(pick, drop, initPosition 등)에서도 이 필드로 파라미터를 전달하지만, actionType 자체를 벤더 고유 이름으로 정의하면 액션 전체를 커스텀 기능으로 확장할 수 있습니다.
// order 메시지의 action 하나 — 벤더 고유 진단 액션
{
"actionId": "thermal-scan-0swordId1",
"actionType": "acme_runThermalScan",
"blockingType": "SOFT",
"actionParameters": [
{ "key": "cellCount", "value": 12 },
{ "key": "thresholdCelsius", "value": 55.0 },
{ "key": "sampleIntervalMs", "value": 500 }
]
}
여기서 두 가지를 눈여겨봐야 합니다. 첫째, actionParameters 배열 안의 value는 표준 스키마상 문자열·숫자·불리언 등 임의 타입을 허용하도록 느슨하게 정의되어 있어서, AGV 쪽 파서가 타입을 스스로 검증해야 한다는 점입니다. 관제 시스템이 실수로 thresholdCelsius에 문자열 "55"를 보내도 스키마 검증기는 이를 막아주지 않습니다. 둘째, actionType 이름에 acme_ 같은 벤더 prefix를 붙인 점입니다. 표준이 향후 버전에서 runThermalScan이라는 이름의 공식 액션을 추가할 가능성을 배제할 수 없으므로, 처음부터 벤더 네임스페이스를 박아 두는 편이 안전합니다. 이 네이밍 전략은 뒤에서 스키마 호환성을 다룰 때 다시 짚습니다.
factsheet의 agvActions로 커스텀 파라미터 광고하기
관제 시스템 입장에서는 acme_runThermalScan이라는 액션이 존재한다는 것과, 그 액션이 어떤 파라미터를 받는지를 미리 알아야 order를 올바르게 구성할 수 있습니다. 이를 위해 VDA5050 factsheet 메시지는 agvActions 배열을 제공하며, 각 액션 항목은 actionScopes(INSTANT/NODE/EDGE 중 어디서 쓸 수 있는지)와 함께 actionParameters 명세를 담을 수 있습니다.
// factsheet 메시지 일부
{
"agvActions": [
{
"actionType": "acme_runThermalScan",
"actionDescription": "배터리 팩 셀 단위 온도 분포를 측정해 informations로 보고",
"actionScopes": ["INSTANT", "NODE"],
"actionParameters": [
{ "key": "cellCount", "valueDataType": "NUMBER", "isOptional": false, "description": "측정할 셀 개수" },
{ "key": "thresholdCelsius", "valueDataType": "NUMBER", "isOptional": true, "description": "경고 임계값(기본 60도)" },
{ "key": "sampleIntervalMs", "valueDataType": "INTEGER", "isOptional": true, "description": "셀 간 샘플링 간격" }
]
}
]
}
valueDataType과 isOptional을 명시하면, 관제 시스템 쪽 소프트웨어가 이 factsheet를 읽어 UI 상에서 필수 파라미터가 비었는지 미리 검증하거나, 자동으로 파라미터 입력 폼을 생성하는 것도 가능해집니다. 실무에서 흔히 저지르는 실수는 factsheet에는 액션을 등록해 두지 않고 order에서만 커스텀 actionType을 슬쩍 사용하는 것입니다. 이렇게 하면 당장은 동작하지만, 다른 팀이 관제 시스템 쪽 코드를 유지보수할 때 이 액션이 존재한다는 사실 자체를 알 방법이 없어 “미확인 동작”으로 분류되어 버립니다. factsheet 등록은 선택이 아니라, 커스텀 액션을 도입할 때 세트로 해야 할 작업이라고 생각하는 편이 안전합니다.
2. state.informations — AGV에서 관제로 진단 정보 흘려보내기
actionParameters가 관제→AGV 방향의 공식 확장 지점이라면, 그 반대 방향의 공식 확장 지점은 state 메시지의 informations 배열입니다. (VDA5050 1.1에서는 필드명이 information이었고, 2.x 계열에서 복수형 informations로 정리되었습니다. 어느 버전을 타겟하는지에 따라 필드명을 맞춰야 합니다.) 각 항목은 다음 네 가지 필드로 구성됩니다.
| 필드 | 역할 |
|---|---|
infoType | 정보의 종류를 나타내는 식별자 문자열. 커스텀 정보라면 여기에 벤더 prefix를 붙입니다. |
infoLevel | INFO 또는 DEBUG. 관제 시스템·로그 뷰어가 표시 우선순위를 결정하는 데 씁니다. |
infoDescription | 사람이 읽을 설명 문자열. |
infoReferences | 이 정보가 다른 필드(주로 특정 actionId나 orderId)와 어떤 관계인지 연결하는 참조 배열. |
errors나 batteryState처럼 이미 정해진 스키마를 가진 필드와 달리, informations는 애초에 “표준이 예상하지 못한 임의의 상태를 실을 수 있는 자리”로 설계되었습니다. 표준 필드 후보를 억지로 끼워 맞추는 대신, 진짜로 표준에 없는 데이터는 여기에 담는 것이 설계 의도에 맞는 사용법입니다.
INFO와 DEBUG 레벨을 구분해서 쓰는 이유
실무에서 흔히 두 레벨을 구분하지 않고 아무거나 INFO로 채워 보내는 경우를 보는데, 이렇게 하면 관제 시스템 UI가 정말 운영자가 봐야 할 정보와 개발자만 필요한 저수준 진단값을 구분하지 못해 알림이 그대로 소음이 되어 버립니다. INFO는 운영자가 대시보드에서 바로 확인할 가치가 있는 정보(예: “셀 3번 온도 58도, 경고 임계값 근접”)에, DEBUG는 개발·현장 엔지니어가 문제를 진단할 때만 조회하는 저수준 값(예: 12개 셀 전체의 원시 온도 배열)에 쓰는 것이 실용적인 관례입니다. 관제 시스템 쪽에서 infoLevel로 필터링해 기본 화면에는 INFO만 노출하고, “상세 로그 보기”를 눌렀을 때만 DEBUG까지 펼쳐 보이도록 구현하면 두 레벨을 구분해 보낸 노력이 그대로 사용자 경험으로 이어집니다.
infoReferences로 다른 필드와 연결하기
informations 항목 하나만 따로 떼어 놓으면 “이 정보가 지금 어떤 상황에서 나온 것인지” 맥락이 사라집니다. infoReferences는 이 항목을 order의 특정 액션이나 node, 혹은 다른 상태값과 명시적으로 묶어주는 역할을 합니다. 예를 들어 앞서 정의한 acme_runThermalScan 액션이 완료된 뒤 그 결과로 나온 온도 정보라면, infoReferences에 해당 actionId를 넣어 두면 관제 시스템이 “이 진단 정보는 저 액션 요청에 대한 응답”이라는 것을 기계적으로 재구성할 수 있습니다.
// state 메시지의 informations 항목 — 액션 결과를 참조로 연결
{
"infoType": "acme_batteryThermal",
"infoLevel": "INFO",
"infoDescription": "셀 3, 7번 온도가 경고 임계값(55도)에 근접",
"infoReferences": [
{ "referenceKey": "actionId", "referenceValue": "thermal-scan-0swordId1" },
{ "referenceKey": "topic", "referenceValue": "state" }
]
}
infoReferences를 생략하고 infoDescription 텍스트 안에만 맥락을 욱여넣는 방식도 동작은 하지만, 이 경우 관제 시스템이 문자열 파싱에 의존해야 해서 깨지기 쉽습니다. 구조화된 참조를 쓰면 관제 시스템이 특정 액션에 대한 응답만 골라 별도 알림으로 묶어 보여주는 것과 같은 자동화가 훨씬 쉬워집니다.
3. factsheet 확장 필드 — 능력을 미리 알리는 정적 메타데이터
앞서 agvActions로 커스텀 액션의 파라미터 명세를 광고하는 법을 봤는데, factsheet에는 그 외에도 차량의 물리적·프로토콜적 능력을 사전에 알릴 수 있는 필드가 몇 가지 더 있습니다.
protocolFeatures: 이 AGV가 지원하는 액션 목록(agvActions)과 파라미터 목록을 담는 상위 구획입니다. 앞서의acme_runThermalScan등록이 정확히 이 구획 아래 들어갑니다.agvGeometry: 차량의 물리적 형상(휠 위치, 바운딩 박스 등)을 나타냅니다. 표준 필드로 표현되지 않는 특수 부착물(예: 상단 리프트 플랫폼의 확장 범위)이 있다면, 벤더 prefix를 붙인 커스텀 서브필드로 보강할 수 있습니다.physicalParameters: 속도·가속도·적재 용량 같은 물리 스펙입니다. 컨베이어 벨트 속도나 리프트 최대 승강 높이처럼 표준이 상정하지 않은 특수 하드웨어 스펙은 이 구획 아래 벤더 네임스페이스로 추가하는 것이 자연스럽습니다.typeSpecification: 차량 시리즈·타입을 식별하는 필드로, 관제 시스템이 차량군별로 다른 UI·정책을 적용할 때 참조합니다.
이 네 필드의 공통점은 모두 연결 초기 또는 능력이 바뀔 때마다 한 번씩만 보내면 되는 정적 정보라는 것입니다. state.informations가 매 주기 흘러나오는 스트림이라면, factsheet 확장 필드는 “앞으로 이런 진단 정보와 액션을 보낼 것”이라고 미리 예고하는 카탈로그 역할을 합니다. 리프트나 컨베이어처럼 VDA5050이 애초에 상정하지 않은 하드웨어를 다룬다면, 먼저 factsheet에 해당 하드웨어의 능력을 선언해 두고, 실제 운영 중 부가 상태는 state.informations로, 제어 지시는 actionParameters로 흘려보내는 조합이 세 확장 지점을 가장 일관되게 쓰는 방식입니다.
4. 벤더 전용 커스텀 MQTT 토픽
지금까지의 세 지점은 모두 표준 6개 토픽(order, state, instantActions, connection, visualization, factsheet) 안에서의 확장이었습니다. 하지만 고빈도 텔레메트리(예: 라이다 포인트클라우드 요약, 초당 수십 회의 IMU 원시값)처럼 state 메시지 주기에 얹기에는 데이터량이나 발행 빈도가 맞지 않는 경우도 있습니다. 이럴 때 고려하는 것이 표준 토픽 옆에 벤더 전용 커스텀 토픽을 병행 발행하는 방법입니다.
네이밍 컨벤션
표준 토픽 규칙 {interfaceName}/{majorVersion}/{manufacturer}/{serialNumber}/{topic}과 일관성을 유지하려면, 커스텀 토픽도 같은 네임스페이스 계층 아래 이름만 바꿔 추가하는 것이 원칙입니다.
uagv/v2/RobotCompany/0001/state # 표준 토픽
uagv/v2/RobotCompany/0001/acme_batteryTelemetry # 커스텀 토픽 (벤더 prefix + 표준 계층 재사용)
토픽 이름 자체에도 벤더 prefix(acme_)를 붙이는 이유는 필드 네이밍과 동일합니다. 표준이 향후 7번째 공식 토픽을 추가한다면(실제로 초기 버전 이후 몇 차례 토픽이 정리·추가된 이력이 있습니다), 이름이 우연히 겹치는 사고를 막기 위해서입니다. 또한 manufacturer·serialNumber 세그먼트를 그대로 재사용해야, 관제 시스템이 이미 구독 중인 와일드카드 패턴(uagv/v2/+/+/# 등)에 커스텀 토픽도 자연스럽게 걸려 별도 구독 로직 없이 수신할 수 있습니다.
관제 시스템이 모르는 토픽을 안전하게 무시하게 만들기
커스텀 토픽을 추가할 때 가장 중요한 설계 원칙은, 이 토픽을 모르는 관제 시스템이 있어도 표준 동작에는 전혀 영향이 없어야 한다는 것입니다. 이는 다음 두 가지를 지켜야 실현됩니다.
- 커스텀 토픽으로만 전달되는 데이터가 order 처리나 안전 관련 로직의 필수 입력이 되어서는 안 됩니다. 관제 시스템이 그 토픽을 구독하지 않아도 AGV는 표준 order를 정상적으로 수행할 수 있어야 합니다.
- AGV 쪽 구현도 커스텀 토픽에 대한 구독자가 없다는 사실 자체를 오류로 취급하지 않아야 합니다. MQTT는 발행자가 구독자 유무를 알 방법이 없으므로, 이 원칙은 자연스럽게 지켜지지만 애플리케이션 계층에서 “구독 확인 응답이 없으니 재시도”와 같은 로직을 억지로 넣으면 오히려 표준 동작을 해칠 수 있습니다.
sequenceDiagram participant MC as Master Control participant B as MQTT Broker participant AGV as AGV AGV->>B: publish uagv/v2/RobotCompany/0001/state (표준, 필수) AGV->>B: publish uagv/v2/RobotCompany/0001/acme_batteryTelemetry (커스텀, 선택) MC->>B: subscribe uagv/v2/+/+/state Note over MC,B: acme_batteryTelemetry는 구독하지 않아도\n표준 order 처리 흐름에 영향 없음 B-->>MC: state 메시지만 전달
이 트레이드오프를 정리하면, 커스텀 토픽은 표준 준수성(다른 제조사 관제 시스템과의 상호운용성)을 부분적으로 희생하는 대신 고빈도·대용량 데이터를 표준 메시지 주기에 얹지 않아도 되는 유연성을 얻는 선택입니다. 따라서 “이 데이터가 정말 state 주기(보통 초 단위 이하)로 흘려보내야 할 만큼 시급한가, 아니면 factsheet 수준의 정적 정보이거나 informations로 충분한가”를 먼저 따져보고, 세 가지 공식 확장 지점으로 표현이 안 될 때만 커스텀 토픽을 마지막 수단으로 고려하는 순서를 권장합니다.
5. JSON 스키마 additionalProperties와 필드 충돌 회피 전략
커스텀 데이터를 실제로 넣기 전에 반드시 확인해야 할 것이 스키마 검증 통과 여부입니다. VDA5050 공식 JSON 스키마는 메시지 타입·버전에 따라 객체별로 additionalProperties 제약이 다르게 걸려 있을 수 있습니다. 최상위 메시지 객체(order, state 등)에 스키마가 알지 못하는 새 키를 직접 추가하면 엄격한 검증기에서 거부당하기 쉽지만, actionParameters의 값 객체나 informations 배열처럼 애초에 자유 형식을 허용하도록 설계된 자리는 대부분의 버전에서 무리 없이 통과합니다.
이 글에서 지금까지 actionParameters, informations, factsheet의 agvActions, 커스텀 토픽 네 가지를 우선 소개한 이유가 바로 이것입니다. 모두 표준이 애초에 “여기는 자유롭게 쓰라”고 열어 둔 자리이거나, 아예 표준 스키마 검증 범위 밖(커스텀 토픽)에 있는 지점입니다. 최상위 객체에 즉흥적으로 새 키를 추가하는 것보다 훨씬 안전한 경로입니다.
실무 체크리스트
- 사용 중인 관제 시스템·AGV 라이브러리가 스키마 검증을 어느 시점에 수행하는지 먼저 확인합니다. 수신 시점에 엄격하게 검증한다면 최상위 객체 확장은 애초에 시도하지 않는 편이 낫습니다.
- 커스텀 키·
infoType·actionType·토픽 이름 전체에 조직 고유 prefix(acme_,orgname_등)를 일관되게 적용합니다. 이는 스니펫 하나하나가 아니라 팀 전체의 명명 규칙으로 문서화해 두어야 시간이 지나도 지켜집니다. - 표준 버전을 올릴 때는 새 버전의 공식 스키마에 여러분이 쓰던 커스텀 이름과 겹치는 필드가 새로 추가되지 않았는지 diff를 확인하는 절차를 마이그레이션 체크리스트에 포함시킵니다. prefix를 붙여 두었다면 이 단계에서 걸릴 위험 자체가 거의 사라집니다.
- 커스텀 필드를 새로 추가할 때마다 factsheet(액션이라면
agvActions, 정보 종류라면 별도 문서)에 등록해, 관제 시스템을 유지보수하는 다른 팀이 “미확인 필드”로 헤매지 않도록 합니다.
6. 실전 구현 — 배터리 셀 온도 분포 보고하기
지금까지 다룬 네 가지 확장 지점을 하나의 시나리오로 묶어 끝까지 구현해 보겠습니다. 시나리오는 다음과 같습니다. 관제 시스템이 특정 AGV에게 “배터리 팩의 셀별 온도를 측정해서 보고하라”는 진단 액션을 지시하고, AGV는 측정을 수행한 뒤 결과를 informations로 보고합니다. 표준 batteryState 필드는 전체 배터리 잔량(batteryCharge)과 대표 온도 하나(batteryVoltage 인접 필드로 온도가 있는 버전도 있지만 셀 단위는 아님) 정도만 표현할 수 있으므로, 셀별 분포는 정확히 커스텀 데이터가 필요한 지점입니다.
1단계 — factsheet에 진단 액션 등록
AGV가 부팅해 연결을 맺을 때 발행하는 factsheet에 acme_runThermalScan 액션을 앞서 본 형태로 등록해 둡니다. 관제 시스템은 이 factsheet를 파싱해 “이 차량은 셀 온도 진단을 지원하며, cellCount가 필수 파라미터”라는 사실을 UI나 스케줄링 로직에 반영할 수 있습니다.
2단계 — 관제 시스템이 order로 진단 액션을 요청
관제 시스템은 정기 점검 스케줄이나 운영자의 수동 요청에 따라, 대상 AGV의 order 토픽으로 앞서 예시로 든 acme_runThermalScan 액션을 담은 노드 액션을 발행합니다. blockingType을 SOFT로 지정하면 이 진단 액션이 차량의 정상 주행·다른 액션과 동시에 실행될 수 있어, 굳이 차량을 멈춰 세우지 않고도 순찰 중에 온도를 측정할 수 있습니다.
3단계 — AGV 펌웨어에서 액션을 처리하고 informations로 응답
아래는 AGV 쪽 액션 핸들러가 acme_runThermalScan을 받아 처리하고, 완료 시 informations에 결과를 채워 넣는 C++ 구현 예시입니다. 기존 Action 상태 머신 글에서 다룬 actionStatus 전이 구조를 그대로 재사용한다고 가정합니다.
// ThermalScanHandler.cpp — 커스텀 액션 처리 + informations 보고
struct CellTemperature {
int cellIndex;
double celsius;
};
class ThermalScanHandler : public ActionHandler {
public:
void onActionReceived(const Action& action, StateBuilder& state) override {
// actionParameters 파싱 — 타입은 AGV 쪽에서 직접 검증해야 한다
int cellCount = 0;
double thresholdCelsius = 60.0; // 기본값 (isOptional=true였으므로)
for (const auto& param : action.actionParameters) {
if (param.key == "cellCount") {
cellCount = param.value.get<int>();
} else if (param.key == "thresholdCelsius") {
thresholdCelsius = param.value.get<double>();
}
}
if (cellCount <= 0) {
// 필수 파라미터 누락 — INITIALIZING에서 바로 FAILED로 전이
state.setActionStatus(action.actionId, ActionStatus::FAILED);
state.addError(action.actionId, "acme_MISSING_PARAMETER",
"cellCount is required for acme_runThermalScan");
return;
}
state.setActionStatus(action.actionId, ActionStatus::RUNNING);
auto readings = bmsDriver_.readCellTemperatures(cellCount);
reportThermalResult(action.actionId, readings, thresholdCelsius, state);
state.setActionStatus(action.actionId, ActionStatus::FINISHED);
}
private:
void reportThermalResult(const std::string& actionId,
const std::vector<CellTemperature>& readings,
double thresholdCelsius,
StateBuilder& state) {
// 임계값을 넘는 셀만 골라 INFO 레벨로 요약 보고
std::vector<CellTemperature> warnings;
for (const auto& r : readings) {
if (r.celsius >= thresholdCelsius) warnings.push_back(r);
}
if (!warnings.empty()) {
std::string desc = buildWarningDescription(warnings, thresholdCelsius);
state.addInformation({
.infoType = "acme_batteryThermal",
.infoLevel = InfoLevel::INFO,
.infoDescription = desc,
.infoReferences = {{"actionId", actionId}, {"topic", "state"}},
});
}
// 셀 전체 원시값은 DEBUG 레벨로 별도 보고 — 평소 UI에는 노출되지 않는다
state.addInformation({
.infoType = "acme_batteryThermalRaw",
.infoLevel = InfoLevel::DEBUG,
.infoDescription = serializeAllCells(readings),
.infoReferences = {{"actionId", actionId}},
});
}
BmsDriver bmsDriver_;
};
이 구현에서 눈여겨볼 부분이 몇 가지 있습니다. 우선 cellCount가 비어 있을 때 곧바로 FAILED로 보고하고 리턴하는 방어 코드가 있습니다. actionParameters는 스키마 검증이 값의 존재나 타입까지 보장해주지 않으므로, factsheet에서 isOptional: false로 선언했더라도 실제 파싱 코드에서 다시 한번 검증해야 합니다. 다음으로, 경고 대상 셀만 골라 사람이 읽을 요약을 INFO 레벨로 보내고, 12개 셀 전체의 원시 온도 배열은 DEBUG 레벨로 별도 보고하는 이중 구조를 취했습니다. 이렇게 하면 관제 시스템 대시보드 기본 화면은 조용하게 유지하면서도, 문제가 생겼을 때 엔지니어가 DEBUG 레벨 정보를 펼쳐 전체 데이터를 볼 수 있습니다. 마지막으로 두 informations 항목 모두 infoReferences에 원인이 된 actionId를 넣어, 관제 시스템이 “이 온도 보고가 어떤 진단 요청에 대한 응답인지”를 문자열 파싱 없이 구조적으로 연결할 수 있게 했습니다.
4단계 — 관제 시스템에서의 소비
관제 시스템은 매 state 메시지를 받을 때마다 informations 배열을 순회하면서 infoType이 acme_로 시작하는 항목은 벤더 확장 데이터로 분류해 별도 패널에 렌더링하고, infoLevel에 따라 기본 노출 여부를 결정합니다. infoReferences의 actionId를 이용해 최근 발행했던 order의 액션 이력과 매칭하면, “3분 전에 요청한 진단이 방금 이 경고로 돌아왔다”는 식의 타임라인 뷰도 어렵지 않게 구성할 수 있습니다. 이처럼 확장 지점을 표준이 의도한 대로 쓰면, 관제 시스템 쪽 파싱 로직도 “알 수 없는 필드를 임기응변으로 처리”하는 대신 일관된 규칙(infoType prefix로 분류, infoLevel로 필터링, infoReferences로 연결) 하나로 모든 벤더의 커스텀 데이터를 수용할 수 있습니다.
정리 — 확장 지점을 고르는 순서
이 글에서 다룬 네 가지 메커니즘은 우열이 아니라 방향과 성격이 다른 도구입니다. 실무에서 새로운 커스텀 데이터를 추가해야 할 때는 다음 순서로 검토하는 것을 권장합니다.
- 관제→AGV 방향으로, 특정 액션 실행 시점에만 필요한 지시 데이터인가 →
actionParameters+ factsheetagvActions등록. - AGV→관제 방향으로, state 주기마다 흘려보낼 수 있는 진단·상태 정보인가 →
informations(INFO/DEBUG 구분,infoReferences연결). - 연결 초기에 한 번 선언해 두면 충분한 정적 능력·스펙 정보인가 → factsheet의
protocolFeatures/agvGeometry/physicalParameters/typeSpecification확장. - 위 세 가지로 표현하기 어려운 고빈도·대용량 데이터인가 → 벤더 전용 커스텀 토픽을, 표준 동작에 영향이 없도록 병행 발행.
그리고 이 네 지점 모두에서 일관되게 지켜야 할 원칙은 하나입니다. 커스텀 키·타입·토픽 이름 전체에 벤더 prefix를 붙여, 향후 표준 버전이 같은 이름을 공식 필드로 채택하더라도 충돌하지 않게 만드는 것입니다. 표준을 어기지 않으면서 표준이 다루지 못하는 현실의 다양한 하드웨어와 진단 요구를 담아내는 것, 그것이 VDA5050이 커스텀 확장 지점들을 애초에 설계에 포함시킨 이유입니다.