VDA5050 factsheet 해설: protocolLimits·agvActions·loadSpecification 읽는 법

이 글의 핵심

factsheet는 AGV가 자기 종류와 능력을 관제에 알리는 정적인 명세서입니다. 차량의 종류·운동학·크기, MQTT 메시지의 길이와 개수 한도, 지원하는 액션과 파라미터, 바퀴와 외곽선, 적재 능력이 들어 있습니다. 표준 2.0.0의 정의를 필드별로 풀고, libVDA5050++ 위의 어댑터가 실제로 발행한 factsheet에서 어떤 값이 어디서 오는지 확인합니다.

VDA5050의 여섯 토픽 중 factsheet는 평소에는 거의 보이지 않다가 새 차량을 관제에 붙일 때 가장 중요해지는 메시지입니다. 이 글은 VDA5050 2.0.0 문서(공식 저장소 2.0.0 태그의 VDA5050_EN_V1.md, 머리말에는 “RELEASE CANDIDATE, FOR REVIEW”로 표기)의 7절을 필드 단위로 풀고, libVDA5050++ v2.11.3 위에 만든 어댑터가 실제로 내보낸 factsheet와 비교합니다. 환경은 Ubuntu 24.04 aarch64 컨테이너와 eclipse-mosquitto:2입니다. 표준이 정한 내용과 이 라이브러리의 동작은 구분해서 적었습니다.

관련된 글은 instantActions 총정리, state 메시지 해설, libVDA5050++ 어댑터 만들기입니다.

factsheet의 역할

표준은 factsheet를 “특정 AGV 종류 시리즈에 대한 기본 정보”로 소개합니다. 서로 다른 AGV 종류를 비교하고, 시스템의 계획·규모 산정·시뮬레이션에 쓰고, VDA 5050을 지키는 관제 시스템에 AGV 종류를 통합할 때 필요한 통신 인터페이스 정보를 담습니다. 사람이 읽는 문서이면서 관제가 가져가 처리하는 JSON이기도 합니다. 표준은 프로젝트별로 정해지는 값(적재 종류, 스테이션 종류와 그 지원 목록)은 시스템 통합 단계에서만 정할 수 있다고도 적습니다.

토픽 표(6.5절)에서 factsheet는 AGV가 발행하고 관제가 구독하며, 용도는 “관제 시스템에 AGV 설정”, 구현은 필수입니다. 메시지의 headerId와 timestamp는 factsheet를 전자적으로 자동 전송할 때만 의미가 있다고 7.1.2절이 적습니다. 최상위 구조는 아래 일곱 가지입니다.

필드내용
typeSpecificationAGV의 종류와 능력
physicalParameters기본 물리 특성
protocolLimitsMQTT 통신에서 식별자·배열·문자열의 길이 한도
protocolFeatures지원하는 프로토콜 기능(선택 파라미터, 액션)
agvGeometry바퀴 배치와 외곽선 같은 상세 형상
loadSpecification적재 능력의 추상적 명세
localizationParameters위치 추정에 관한 상세 명세

typeSpecification

필드설명
seriesName제조사가 정한 시리즈 이름(자유 텍스트)
seriesDescription선택. 사람이 읽는 시리즈 설명
agvKinematic운동학 종류. DIFF(차동 구동), OMNI(전방향), THREEWHEEL(3륜 구동 또는 유사)
agvClass차량 분류. FORKLIFT, CONVEYOR(컨베이어 탑재), TUGGER, CARRIER(리프팅 유무와 상관없는 적재 운반차)
maxLoadMass최대 적재 질량(kg)
localizationTypes위치 추정 방식 목록. 예: NATURAL(자연 지형지물), REFLECTOR, RFID, DMC, SPOT, GRID
navigationTypes지원하는 경로 계획 방식을 우선순위 순으로. PHYSICAL_LINE_GUIDED(설치된 선로를 따름), VIRTUAL_LINE_GUIDED(고정된 가상 경로), AUTONOMOUS(스스로 경로 계획)

physicalParameters

speedMin(제어 가능한 최소 연속 속도, m/s), speedMax(m/s), accelerationMax와 decelerationMax(최대 적재 상태에서, m/s²), heightMin과 heightMax(m), width, length(m)입니다. 가속도 값이 “최대 적재 시”로 정의돼 있다는 점은 관제가 이동 시간을 추정할 때 보수적인 값으로 받아들여야 한다는 뜻으로 읽힙니다.

protocolLimits

표준은 이 객체에서 정의하지 않았거나 0으로 둔 값은 명시적인 한도가 없다는 뜻이라고 적습니다. 세 묶음으로 나뉩니다.

maxStringLens: msgLen(MQTT 메시지 최대 길이), topicSerialLen(토픽의 serialNumber 부분), topicElemLen(토픽의 나머지 부분과 헤더의 timestamp, version, manufacturer), idLen(order.orderId, zoneSetId, node.nodeId, nodePosition.mapId, action.actionId, edge.edgeId, startNodeId, endNodeId에 적용), idNumericalOnly(true면 ID 문자열은 숫자만), enumLen(actionType, blockingType, actionParameter.key, operatingMode, loadType, actionStatus, errorType, errorLevel, eStop, connectionState 등 enum과 키 문자열), loadIdLen.

maxArrayLens: order 쪽은 order.nodes, order.edges, node.actions, edge.actions, actions.actionsParameters, instantActions(한 메시지에 담을 수 있는 instantAction 수), trajectory.knotVector, trajectory.controlPoints. state 쪽은 state.nodeStates, state.edgeStates(차량이 보내는 개수이자 base에 담을 수 있는 최대 노드·엣지 수), state.loads, state.actionStates, state.errors, state.informations, error.errorReferences, informations.infoReferences입니다.

timing: minOrderInterval(order를 보낼 수 있는 최소 간격, 초), minStateInterval(state를 보내는 최소 간격), defaultStateInterval(state 기본 발행 간격, 정의하지 않으면 본문의 기본값), visualizationInterval(visualization 토픽의 기본 발행 간격)입니다.

protocolFeatures

optionalParameters: 항목마다 parameter(선택 파라미터의 전체 이름, 예: order.nodes.nodePosition.allowedDeviationTheta), support(SUPPORTED 또는 REQUIRED), description을 가집니다. 표준은 여기에 나열하지 않은 선택 파라미터는 지원하지 않는 것으로 간주한다고 적습니다. REQUIRED는 그 파라미터가 AGV의 제대로 된 동작에 필요하다는 뜻입니다.

agvActions: 이 AGV가 지원하는 모든 액션(표준 액션과 제조사 정의 액션)의 목록입니다. 항목마다 actionType, actionDescription(선택), actionScopes(INSTANT, NODE, EDGE 중 허용되는 것), actionParameters(선택), resultDescription(선택)이 있습니다. 파라미터 항목은 key, valueDataType(BOOL, NUMBER, INTEGER, FLOAT, STRING, OBJECT, ARRAY), description, isOptional입니다. 이 목록이 instantActions 총정리에서 본 표준 액션 표를 차량 하나에 맞게 확정하는 곳입니다.

agvGeometry

차량의 상세 형상입니다. wheelDefinitions는 바퀴마다 type(DRIVE, CASTER, FIXED, MECANUM), isActiveDriven, isActiveSteered, 차량 좌표계의 position(x, y, 고정 바퀴는 theta도), diameter, width, 캐스터 바퀴에 필요한 centerDisplacement(정의하지 않으면 0), constraints(제조사의 자유 텍스트)를 가집니다. envelopes2d는 외곽선 곡선 집합으로, 적재/비적재 상태의 기계적 외곽이나 속도별 안전 필드를 담습니다. 다각형은 닫혀 있고 스스로 교차하지 않아야 합니다. envelopes3d는 형식(예: DXF)과 데이터 또는 내려받을 url을 가집니다.

loadSpecification

loadPositions는 적재 위치/적재 장치의 목록으로, state.loads[].loadPosition과 pick·drop 액션의 파라미터 lhd에 쓸 수 있는 유효한 값을 정합니다. 이 목록이 없거나 비어 있으면 차량에 적재 장치가 없다는 뜻입니다. loadSets는 차량이 다룰 수 있는 적재 세트의 목록이고, 항목마다 이름(setName), 종류(loadType, 예: EPAL, XLT1200), 이 세트가 유효한 loadPositions(없으면 모든 장치), 바운딩 박스, maxWeight, 적재 취급 가능 높이·깊이·기울기의 최소·최대, 이 적재물에서의 속도·가속도·감속도 한도, pickTime과 dropTime(대략 소요 시간, 초), 설명이 선택으로 들어갑니다.

localizationParameters

최상위 구조 표에는 “위치 추정에 관한 상세 명세”로 나오지만, 이 필드의 세부 정의 표는 2.0.0 문서에서 찾지 못했습니다. libVDA5050++의 문서는 표준에 정의가 없어 { "type": "", "description": "" } 같은 객체를 기대한다고 설명합니다.

실제로 발행된 factsheet

아래는 어댑터에 값을 채워 넣고 factsheetRequest instantAction을 보냈을 때 factsheet 토픽으로 나온 내용입니다(agvActions는 뒤에서 따로 보여 줍니다).

{
  "headerId": 0,
  "manufacturer": "sim",
  "serialNumber": "toy001",
  "version": "2.0.0",
  "typeSpecification": { "seriesName": "blog-toy", "agvKinematic": "DIFF", "agvClass": "CARRIER",
                         "maxLoadMass": 100.0, "localizationTypes": ["NATURAL"], "navigationTypes": ["AUTONOMOUS"] },
  "physicalParameters": { "speedMin": 0.0, "speedMax": 1.0, "accelerationMax": 1.0, "decelerationMax": 1.0,
                          "heightMin": 0.5, "heightMax": 0.5, "width": 0.6, "length": 0.8 },
  "protocolLimits": { "maxStringLens": { "idLen": 8 }, "maxArrayLens": null,
                      "timing": { "defaultStateInterval": 30.0, "minOrderInterval": 0.0,
                                  "minStateInterval": 0.0, "visualizationInterval": 1.0 } },
  "agvGeometry": null,
  "loadSpecification": { "loadPositions": ["front"],
                         "loadSets": [ { "setName": "DEFAULT", "loadType": "EPAL", "maxWeight": 100.0,
                                         "pickTime": 2.0, "dropTime": 2.0 } ] },
  "localizationParameters": { "type": "NATURAL", "description": "example" },
  "protocolFeatures": { "optionalParameters": [], "agvActions": [ "... 아래 참고 ..." ] }
}

값이 어디서 오는지를 코드와 연결하면 이렇습니다. 어댑터의 Config에서 refAgvDescription()으로 얻는 AGVDescription이 factsheet의 주된 출처입니다. type_specification, physical_parameters, agv_geometry, load_specification, localization_parameters는 각각 같은 이름의 factsheet 섹션으로 나갔고, agv_id·manufacturer·serial_number는 토픽과 헤더로 나갔습니다. simple_protocol_limits는 max_id_len, id_numerical_only, max_load_id_len, max_loads 네 개뿐이고, 나머지 한도는 라이브러리가 스스로 정하는 값(state 주기, 시각화 주기 등)만 채워 줍니다. 값은 TOML 설정 파일의 [agv_description.type_specification] 같은 테이블에서도 읽을 수 있고, 각 섹션은 JSON 문자열(json)이나 파일 경로(json_file)로 줄 수 있습니다.

이 한 건에서 확인한 것과 눈여겨볼 점입니다.

  • defaultStateInterval은 30.0이었습니다. 라이브러리의 state 최대 주기 기본값(30000ms)에서 온 값입니다. 앞서 시뮬레이터에서 최대 주기를 1000ms로 줄였을 때는 같은 필드가 1.0으로 나왔습니다. 설정을 바꾸면 factsheet도 따라 바뀝니다.
  • visualizationInterval은 1.0이었습니다.
  • max_id_len = 8로 설정하자 maxStringLens.idLen이 8로 나왔습니다. 다만 max_loads = 2로 설정한 값은 maxArrayLens에 나타나지 않고 maxArrayLens가 null이었습니다. 이 필드가 state.loads 한도로 나가야 하는지는 확인하지 못했습니다.
  • agvGeometry는 어댑터에서 채우지 않으면 null로 나갔습니다.
  • optionalParameters는 빈 배열이었습니다. 표준 규칙대로 읽으면 이 차량은 선택 파라미터를 하나도 지원하지 않는다고 광고한 셈입니다. 그런데 같은 어댑터는 노드 도착을 판정할 때 order의 allowedDeviationXY 같은 허용 오차를 씁니다(라이브러리 문서의 NodeReachedHandler 설명). 광고와 실제 동작이 어긋나 있으니, 관제가 factsheet를 엄격하게 읽는 구현이라면 이 부분을 어댑터에서 채워야 하는지 확인해야 합니다.
  • max_id_len = 8로 광고한 상태에서 길이 20의 orderId를 가진 order를 보냈더니 거절되지 않고 수락되어 움직였습니다. 표준은 이 필드를 차량이 지키는 한도의 선언으로만 정의하고, 라이브러리가 한도를 대신 강제하지는 않았습니다.

agvActions는 등록한 핸들러에 따라 달랐습니다. 최소 예제 어댑터(내비게이션과 pick·drop 핸들러만 등록)는 아래 목록이 나왔습니다.

actionTypeactionScopes파라미터
cancelOrder, factsheetRequest, startPause, stateRequest, stopPauseINSTANT없음
pickEDGE, NODEloadType(필수), stationType(필수), depth, height, lhd, loadId, side, stationName(모두 선택)
dropEDGE, NODEdepth, height, lhd, loadId, loadType, side, stationName, stationType(모두 선택)

앞의 다섯 개는 라이브러리가 기본으로 처리하는 instantAction이고, pick과 drop은 SimpleMultiActionHandler에 넘긴 액션 선언에서 왔습니다. 충전과 위치 초기화(initPosition, startCharging, stopCharging)까지 처리하는 더 큰 시뮬레이터에서는 이 목록에 그 액션들이 함께 나왔습니다. 즉 agvActions는 설정한 값이 아니라 등록한 핸들러가 결정합니다. 액션 파라미터는 선언에 정의된 isOptional이 그대로 나가고, order 검증에서 필수 파라미터가 빠지면 거절되는 것과 같은 출처입니다(libVDA5050++ 어댑터 만들기 참고).

factsheet 발행

표준은 이 토픽의 발행 시점을 정하지 않습니다. libVDA5050++에서는 factsheetRequest를 보내면 factsheet 토픽에 발행되었습니다. 차량이 시작하자마자 자동으로 발행하는지는 이번에 확인하지 못했습니다. 이 요청 액션은 2.0.0 문서의 액션 표에 없는 라이브러리의 확장이므로, 다른 구현의 차량에 factsheetRequest를 보내도 동작한다는 보장이 없습니다. 관제는 구독해 두었다가 받은 factsheet를 저장해 두는 방식이 안전합니다.

관제에서 factsheet를 쓰는 방법

아래는 표준의 정의와 위 관찰에서 도출한 사용법이고, 내 해석이 들어 있습니다. 특정 업체의 구현은 확인하지 못했습니다.

차량 등록 단계: 새 차량을 붙일 때 factsheet를 받아 agvKinematic, agvClass, navigationTypes, 크기와 속도 한도로 이 차량이 계획에 맞는지 확인합니다. 표준이 factsheet를 비교와 계획·시뮬레이션용이라고 적은 용도 그대로입니다.

order를 만들 때의 한도: maxArrayLens.order.nodes와 order.edges가 있으면 긴 경로를 그 개수로 나눠 base로 보내야 합니다. node.actions 한도가 있으면 한 노드에 붙일 액션 수를 줄입니다. instantActions 한도는 한 메시지에 담을 액션 수를 정합니다. idLen과 idNumericalOnly가 있으면 orderId, nodeId, actionId를 그에 맞춰 만들어야 합니다(UUID를 쓰기 어려워집니다). 이 값을 지키는 책임이 관제에 있는지 차량에 있는지는 위에서 본 것처럼 구현마다 다릅니다.

전송 간격: minOrderInterval은 order를 보내는 간격의 하한입니다. state는 minStateInterval보다 빨리 받을 수 없다고 가정하고, defaultStateInterval은 state가 안 오는 상태를 장애로 판단하는 기준을 정할 때 참고합니다. state는 이벤트 때 또는 늦어도 30초마다 발행되는 것이 표준 규칙이라, 간격을 근거로 한 타임아웃은 defaultStateInterval보다 충분히 길게 잡아야 합니다.

액션 사전 검증: instantAction이나 노드 액션을 보내기 전에 agvActions에 actionType이 있는지, actionScopes에 쓰려는 위치(INSTANT, NODE, EDGE)가 있는지, 필수 파라미터를 모두 채웠는지 확인합니다. 파라미터의 valueDataType도 맞춰야 합니다. 앞선 글들에서 본 것처럼 차량은 모르는 액션이나 필수 파라미터가 빠진 order를 거절하므로, 관제가 먼저 거르면 errors가 쌓이는 일을 줄일 수 있습니다.

pick·drop 파라미터: loadSpecification.loadSets의 loadType이 pick·drop 액션의 loadType 값으로 쓸 수 있는 후보입니다. loadPositions는 lhd의 후보이고, 비어 있으면 적재 장치가 없는 차량이므로 pick·drop을 보내면 안 된다고 읽힙니다. pickTime과 dropTime은 액션 타임아웃의 하한을 정할 때 참고하는 값입니다.

광고와 실제의 불일치 감시: 위의 두 관찰, 즉 optionalParameters가 비어 있는데 실제로 선택 파라미터를 쓰는 점과 광고한 idLen이 강제되지 않는 점은 차량 쪽 구현이 factsheet를 엄격하게 관리하지 않을 수 있다는 신호입니다. 통합 시험에서 factsheet의 값과 실제 동작을 대조해 보는 단계를 넣는 것이 안전합니다.

한계

표준은 2.0.0 문서(릴리스 후보 표기)를 기준으로 했습니다. agvGeometry의 바퀴·외곽선은 정의만 읽었고 어댑터에서 값을 채워 실행해 보지는 않았습니다. max_loads가 factsheet로 나가는 경로, 차량 시작 시 factsheet의 자동 발행 여부, TOML 설정 파일로 factsheet 섹션을 채우는 방식도 이번에는 실행으로 확인하지 못했습니다. 이후 표준 버전에서 factsheet 필드가 바뀌었는지는 확인하지 않았습니다. 실행 결과는 로컬 컨테이너의 가상 차량에서 얻은 것입니다.