VDA5050 order 메시지 필드 해설: node·edge·nodePosition·액션 실무 가이드

이 글의 핵심

order는 노드와 엣지로 이루어진 그래프를 차량에 보내는 메시지입니다. 유효한 order의 규칙(노드 하나 이상, 엣지는 노드보다 하나 적게, 첫 노드는 차량이 바로 닿을 수 있어야 함, 확정 구간 뒤에 예고 구간만 올 수 있음)과 node·edge·nodePosition·trajectory 필드의 의미를 표준 2.0.0 문서 기준으로 풀고, 규칙을 어겼을 때 시뮬레이터가 어떻게 반응했는지 확인합니다.

order는 VDA5050에서 관제가 차량에 일을 시키는 거의 유일한 방법이고, 그만큼 필드도 규칙도 많습니다. 이 글은 VDA5050 2.0.0 문서(공식 저장소 2.0.0 태그의 VDA5050_EN_V1.md, 머리말에 “RELEASE CANDIDATE, FOR REVIEW”로 표기)의 6.6절과 6.7절을 기준으로 order의 구조와 필드를 설명하고, 관제가 order를 만들 때 지킬 점을 체크리스트로 정리합니다. 규칙을 어겼을 때 차량이 어떻게 반응하는지는 libVDA5050++ v2.11.3 위의 시뮬레이터(Ubuntu 24.04 aarch64 컨테이너, eclipse-mosquitto:2)에서 직접 보내 기록했습니다. 시나리오마다 시뮬레이터를 재시작하고 차량이 n0(0,0)에 서 있는 상태에서 order 하나만 보냈습니다. 표준의 내용과 구현의 동작은 구분해서 적었습니다.

이 글은 order를 만드는 사람(관제 쪽)을 위한 글입니다. 처리 과정은 order 수명주기, 거절 오류 모음은 order 거절 오류, 진행 상황 보고는 state 메시지 해설, 액션은 instantActions 총정리에 있습니다.

order는 그래프의 일부를 보내는 것

표준은 order의 기본 구조를 노드와 엣지의 그래프로 정의합니다. 전체 그래프는 관제가 쥐고 있고, 어느 차량이 어느 엣지를 지날 수 있는지 같은 제약도 관제가 가지고 있습니다. 이 제약은 차량에 전달되지 않으며, 관제는 그 차량이 지나도 되는 엣지만 order에 넣습니다. 표준은 차량 종류마다 별도의 그래프를 두는 것을 피하고 가능하면 한 장소(예: 방화문 앞 대기 위치)에는 노드 하나만 두라고 권합니다. 차량 크기와 사양 차이로 어쩔 수 없을 때만 예외로 둡니다.

order 메시지 하나가 전체 운송 작업을 담을 필요도 없습니다. 교통 제어와 자원이 제한된 차량을 위해 전체 작업을 여러 하위 order로 나누고 orderId와 orderUpdateId로 이어붙입니다.

유효한 order의 규칙

6.6.1절이 정하는 규칙입니다.

  1. 노드와 엣지는 두 목록으로 전달되고, 목록의 순서가 통과 순서를 정합니다.
  2. 유효한 order에는 노드가 최소 하나 있어야 합니다. 엣지 수는 노드 수에서 하나를 뺀 값이어야 하며 더 많거나 적으면 안 됩니다. 노드 하나짜리 order는 엣지 목록을 비워 둡니다.
  3. order의 첫 노드는 차량이 “바로 도달할 수 있는” 곳이어야 합니다. 차량이 이미 그 노드에 서 있거나, 노드의 허용 오차 범위 안에 있다는 뜻입니다.
  4. 노드와 엣지는 released 불리언을 가집니다. true면 차량이 지나가야 하고, false면 지나가면 안 됩니다.
  5. 엣지는 시작 노드와 끝 노드가 모두 released일 때만 released일 수 있습니다.
  6. released가 아닌 엣지 뒤에는 released인 노드나 엣지가 올 수 없습니다.
  7. released인 노드와 엣지의 집합을 base, 아닌 것의 집합을 horizon이라 하고, horizon 없이 order를 보내도 유효합니다.

order 헤더 필드

필드내용
headerId토픽마다 따로 세는 메시지 번호. 보낼 때마다 1씩 증가(받았는지와 무관)
timestampISO 8601 UTC, YYYY-MM-DDTHH:mm:ss.ssZ
version프로토콜 버전 [Major].[Minor].[Patch]
manufacturer, serialNumber차량 식별
orderId같은 order에 속한 여러 메시지를 묶는 ID
orderUpdateIdorder 안에서 유일한 update 번호. update가 거절되면 거절 메시지에 이 값을 담아야 함
zoneSetId선택. 차량이 경로 계획에 써야 하는 zone set(또는 관제가 계획에 쓴 zone set)의 ID. zone을 쓰지 않으면 넣지 않음

node 객체

필드내용
nodeId노드 식별자
sequenceIdorder 안에서 노드와 엣지에 걸쳐 매기는 번호. 같은 nodeId를 한 order에서 여러 번 지날 때 구분하는 것이 주목적이며, 새 orderId를 발급하면 초기화됩니다
nodeDescription선택. 추가 설명
releasedtrue면 base, false면 horizon
nodePosition선택. 위치를 요구하지 않는 차량(예: 라인 추종)은 생략 가능
actions노드에서 실행할 액션 배열. 없으면 빈 배열

sequenceId는 첫 노드가 0, 첫 엣지가 1, 두 번째 노드가 2처럼 노드와 엣지를 번갈아 이어서 매깁니다. 한 번 정해진 번호는 order update에서도 바뀌지 않습니다. 차량 쪽이 관제가 가리키는 노드를 알아보는 기준이기 때문입니다. 이 덕분에 1에서 2로 갔다가 다시 1로 돌아오는 순환 경로도 표현할 수 있습니다.

nodePosition

필드내용
x, y지도 좌표계 기준 위치(m). 정밀도는 구현에 달림
theta선택. 노드에서의 절대 방향, 범위 -π에서 π(rad). 정의하면 차량은 그 노드에서 그 각도를 취해야 하며, 앞 엣지가 회전을 금지하면 노드에서 회전해야 합니다. 다음 엣지가 다른 방향을 정하고 회전을 금지하면 엣지에 들어가기 전에 노드에서 그 방향으로 회전합니다
allowedDeviationXY선택. 노드를 통과한 것으로 인정하는 정밀도. 0이면 허용 오차 없음(차량 제조사의 일반적인 공차 안), 0보다 크면 반경(m). 이 반경 안을 지나면 노드를 통과한 것으로 봅니다
allowedDeviationTheta선택. 0에서 π. 허용 각도는 theta - 값에서 theta + 값까지
mapId위치가 기준으로 삼는 지도의 ID
mapDescription선택. 지도 설명

좌표계는 6.6.5절이 정합니다. 지도 좌표계는 z축이 하늘을 향하는 오른손 좌표계이고 양의 회전은 반시계 방향입니다. 차량 좌표계도 오른손 좌표계로 x축이 차량 전방, z축이 위입니다(DIN ISO 8855의 2.11장). 좌표는 미터, 각도는 라디안으로 -π에서 π 사이여야 합니다. 층마다 지도가 따로 있고 mapId로 구분하며, 모든 지도는 같은 프로젝트 전역 원점을 씁니다. 엘리베이터를 타면 차량은 출발 층 지도에서 사라지고 도착 층 지도의 엘리베이터 노드에 나타납니다.

허용 오차를 시뮬레이터에서 확인하기

order의 첫 노드는 차량이 “바로 도달할 수 있어야” 한다는 규칙(위 3번)이 라이브러리에서는 허용 오차 검사로 구현되어 있었습니다. 차량이 (0,0)에 있고 방향은 0일 때 결과입니다.

첫 노드결과
(0.4, 0), allowedDeviationXY 0.5수락, 이동 시작
(0.6, 0), allowedDeviationXY 0.5orderError: The AGV is not in the deviation radius of node n0 (last node: )
(0.6, 0), allowedDeviationXY 1.0수락
(0, 0), allowedDeviationXY 생략수락
(0.05, 0), allowedDeviationXY 0거절(같은 orderError). 0은 사실상 오차 없음으로 처리됨
(0, 0), theta 1.5, allowedDeviationTheta 0.1거절(같은 orderError). 차량 방향이 0이라 각도 범위를 벗어남
(0, 0), mapId를 차량과 다른 floor2로거절(같은 orderError)

거절 오류의 errorReferences에는 차량의 현재 위치(state.agvPosition.x/y/theta)와 order 첫 노드의 위치·방향·허용 오차(order.nodes[0].nodePosition.*)가 모두 들어 있어서 어느 값이 어긋났는지 바로 비교할 수 있었습니다. 다만 각도나 mapId가 원인이어도 설명 문구는 “deviation radius”로 같아서, 설명만 보면 거리 문제로 착각하기 쉽습니다. 레퍼런스의 값을 비교해야 합니다.

edge 객체

필드내용
edgeId, sequenceId식별자와 order 안의 번호. sequenceId는 새 orderId를 발급하면 초기화
edgeDescription선택
releasedtrue면 base
startNodeId, endNodeId엣지가 잇는 두 노드. 엣지는 방향이 있는 연결입니다
maxSpeed선택. 엣지에서 허용되는 최대 속도(m/s). 차량이 낼 수 있는 가장 빠른 측정값 기준
maxHeight선택. 적재물을 포함한 차량의 최대 허용 높이(m)
minHeight선택. 적재 장치의 최소 허용 높이(m)
orientation선택. 엣지에서의 차량 방향(rad)
orientationType선택. GLOBAL(프로젝트 전역 지도 좌표계 기준) 또는 TANGENTIAL(엣지의 접선 기준). 정의하지 않으면 TANGENTIAL. 접선 기준에서 0.0은 앞방향, π는 뒤방향
direction선택. 라인 또는 와이어 유도 차량이 분기점에서 갈 방향(예: left, right, straight). 차량마다 정함
rotationAllowed선택. 엣지에서 회전을 허용할지. 설정하지 않으면 제한 없음
maxRotationSpeed선택. 최대 회전 속도(rad/s). 설정하지 않으면 제한 없음
trajectory선택. 엣지의 NURBS 곡선. 차량이 궤적을 처리하지 못하거나 스스로 계획하면 생략
length선택. 시작 노드에서 끝 노드까지 경로 길이(m). 라인 추종 차량이 정지 위치 앞에서 감속하는 데 씀
actions엣지에서 실행할 액션

orientation의 규칙이 길어서 따로 옮깁니다. 차량이 다른 방향으로 엣지를 시작하면, rotationAllowed가 true일 때는 엣지 위에서 원하는 방향으로 회전하고, false일 때는 엣지에 들어가기 전에 회전해야 합니다. 그것이 불가능하면 order를 거절해야 합니다. 궤적이 없으면 두 노드 사이의 직선 경로에 회전을 적용합니다.

엣지에 붙은 액션은 차량이 그 엣지를 지나는 동안에만 활성입니다. 엣지를 벗어나면 액션이 멈추고 엣지에 들어가기 전의 상태로 복구됩니다. 그래서 엣지 액션은 “이 구간을 달리는 동안만” 필요한 것(예: 경고등, 특정 센서 모드)에 쓰고, 끝나야 하는 작업(pick 등)은 노드 액션으로 두는 것이 자연스럽습니다. 이 문장은 표준의 정의에서 이끌어 낸 해석입니다.

trajectory(NURBS)

필드내용
degree곡선의 한 점에 영향을 주는 제어점의 수(1 이상). 높일수록 연속성이 커집니다. 정의하지 않으면 1
knotVector범위 0.0에서 1.0. 제어점이 곡선에 어떻게 영향을 주는지 정하는 매개변수 열. 크기는 제어점 수 + degree + 1
controlPoints시작점과 끝점을 포함한 제어점 목록. 각 점은 x, y(세계 좌표계), 선택 weight(0보다 큼, 기본 1.0)

표준의 크기 규칙을 적용하면 degree 1의 직선(제어점 2개)은 knotVector 크기가 2 + 1 + 1 = 4가 됩니다. 이 값은 표준 규칙으로 계산한 것이고 시뮬레이터에서 실행해 보지는 않았습니다. 차량이 스스로 경로를 계획하는 경우에는 반대로 차량이 계산한 궤적을 base와 horizon 모두 NURBS로 state의 trajectory에 담아 보고해야 하고, base 노드가 확정된 뒤에는 궤적을 바꿀 수 없다고 6.10.1절이 정합니다.

base와 horizon, 그리고 update

6.6.2절의 설명을 정리하면 이렇습니다. 교통 제어를 위해 order에는 결정 지점(decision point)까지의 경로만 담습니다. 차량이 결정 지점에 닿기 전에 관제가 경로를 늘려 주는 update를 보냅니다. base는 관제가 이미 승인한 경로이고 바뀌지 않으며, 관제는 base를 차량이 실행할 것으로 가정할 수 있습니다. horizon은 교통 정체가 없다면 갈 것으로 예상되는 경로로 아직 승인되지 않았고, 차량은 일단 base의 마지막 분기점까지만 갑니다. 무선 전송이 신뢰할 수 없기 때문에 base는 변경할 수 없고, 관제가 바꿀 수 있는 것은 horizon뿐입니다. 취소(cancelOrder)도 같은 이유로 신뢰할 수 없는 수단이라고 표준이 적습니다.

update의 규칙은 아래와 같습니다.

  • orderId는 같고 orderUpdateId는 증가합니다.
  • update의 첫 노드는 이전 order에서 마지막으로 확정한 base 노드(stitching 노드)입니다. 차량이 이미 아는 엣지로 도달할 수 있는 노드를 첫 노드로 삼아 이어붙일 수 있게 하고, update 메시지가 유실됐을 때도 차량이 마지막 base 노드의 nodeId와 sequenceId가 새 base의 첫 노드와 같은지 확인할 수 있게 하는 장치입니다.
  • 그 노드 하나만 다시 보냅니다. 이전 base의 나머지(표준 예시의 노드 6과 4)를 재전송하는 것은 유효하지 않습니다.
  • stitching 노드의 내용은 바꾸면 안 됩니다. 액션과 허용 오차 같은 지시는 차량이 첫 order(orderUpdateId 0)에서 받은 것을 씁니다.
  • 새 base 노드와 엣지의 sequenceId는 앞에서 이어지는 번호이고, 이미 부여된 번호는 바뀌지 않습니다.
  • 관제는 새 base로 전혀 다른 노드들을 보내 horizon을 바꿀 수 있고, horizon을 지울 수도 있습니다.

시뮬레이터에서는 n0(seq 0), n1(seq 2)을 확정하고 n2(seq 4)를 예고로 둔 order를 보내면 차량이 n1에서 멈췄고, n1(seq 2)부터 시작하는 update로 n2(seq 4)를 확정하자 n2까지 이동했습니다. 같은 update에서 다음 노드의 sequenceId를 건너뛰어(seq 6) 보내면 orderError(The order skips sequence ids)로 거절되어 orderUpdateId가 올라가지 않았습니다. 첫 노드가 n0(seq 0)인 update는 거절 오류 글에서 본 것처럼 오류 없이 orderUpdateId만 올라가고 horizon이 풀리지 않았습니다.

구조 규칙을 어겼을 때

위의 구조 규칙을 어긴 order를 보낸 결과입니다.

보낸 order결과
노드 2개, 엣지 0개orderError: The order skips sequence ids
노드 2개, 엣지 2개state에 오류 없음, order 수락 안 됨. 라이브러리 로그에 No more Nodes, but Edges 예외가 기록됨
확정 엣지가 예고 노드로 이어짐state에 오류 없음, order 수락 안 됨. 로그에 Released Edge leads to unreleased Node 예외
예고 엣지 뒤에 확정 엣지·노드가 이어짐orderError: The order contains a horizon sequence id smaller then a base sequence id
노드 1개, 엣지 0개수락. 이동 없이 lastNodeId가 그 노드로 설정됨

표에서 주목할 것은 두 번째와 세 번째 줄입니다. 표준이 거절해야 한다고 정한 order인데 차량이 errors에 아무것도 남기지 않고 버렸습니다. 관제 입장에서는 order를 보냈고 오류도 없는데 orderId가 그대로이고 차량이 움직이지 않는 상황이 됩니다. 이 글에서 확인한 원인은 라이브러리 로그의 예외뿐입니다. 관제가 order를 만들 때 위의 규칙 2, 5, 6을 직접 검사해야 하는 현실적인 이유입니다.

실무용 order 작성 체크리스트

아래는 표준의 규칙과 위 실행 결과에서 도출한 것이고, 내 해석이 들어 있습니다.

order를 만들기 전

  • 이 차량의 factsheet를 확인했는가. maxArrayLens.order.nodes와 order.edges, node.actions, idLen, idNumericalOnly 한도를 지키는가. 차량이 한도를 대신 강제해 주지 않을 수 있습니다(factsheet 해설).
  • 차량의 현재 위치를 알고 있는가. 첫 노드의 nodePosition과 allowedDeviationXY, allowedDeviationTheta, mapId가 차량의 마지막 state와 맞는가.

구조

  • 노드가 하나 이상이고 엣지가 노드 수보다 정확히 하나 적은가.
  • 노드와 엣지가 번갈아 이어지고 sequenceId가 0부터 빠짐없이 증가하는가(노드는 짝수, 엣지는 홀수).
  • 각 엣지의 startNodeId와 endNodeId가 앞뒤 노드와 일치하는가. 라이브러리는 이 불일치를 거르지 않고 수락했습니다.
  • 확정 엣지의 양 끝 노드가 모두 확정인가. 예고 엣지 뒤에는 예고만 오는가.

update

  • orderId는 그대로, orderUpdateId는 새 값인가. 같은 값을 다시 보내면 무시됩니다.
  • 첫 노드가 마지막으로 확정한 base 노드와 nodeId와 sequenceId까지 같은가. stitching 노드의 액션과 허용 오차를 바꾸지 않았는가.
  • 차량이 base 끝에 닿기 전에 update를 보내는가. 닿기 전에 새 base가 없으면 차량이 속도를 줄이거나 멈출 수 있습니다(newBaseRequest는 state 해설 참고).

보낸 뒤

  • 보낸 orderId가 state의 orderId로 나타났는가. nodeStates가 새 노드로 바뀌었는가. 오류가 없다는 이유로 수락됐다고 판단하지 않습니다.
  • 일정 시간 안에 state가 바뀌지 않으면 같은 orderId로 다시 보내지 말고 구조를 다시 검사합니다(같은 orderId와 orderUpdateId는 무시됩니다).

값을 정하는 요령

  • 허용 오차: 반경이 작을수록 정확한 위치를 요구하고 클수록 일찍 통과로 인정됩니다. 표준에서 노드 통과 판단 주체는 차량이고, 제어점이 허용 오차 안에 있을 때입니다. 노드를 통과로 인정하는 순간 그 노드의 액션이 시작되므로(6.10.2절), 정확히 서야 하는 노드(pick 위치, 충전 위치)는 작게 두고 지나가기만 하는 노드는 넉넉히 두는 것이 자연스럽습니다. 0은 표준에서 “제조사의 일반 공차”라는 뜻인데 시뮬레이터에서는 0.05m 떨어진 첫 노드가 거절될 만큼 엄격하게 동작했으므로, 0 대신 차량의 위치 정확도에 맞춘 값을 쓰는 편이 안전합니다. 이 값 선택은 내 해석입니다.
  • theta: 정의하면 차량이 그 각도를 취해야 하므로, 방향이 중요하지 않은 노드에서는 생략합니다(표준도 선택이라고 적습니다). 정할 때는 allowedDeviationTheta를 함께 정합니다.
  • 층 이동: 엘리베이터 전후 노드는 서로 다른 mapId를 쓰고 같은 좌표의 노드로 둡니다. 시뮬레이터의 층 이동 구성이 이 형태였지만, 이번에 order의 mapId 변경 동작을 층 이동으로 확인한 것은 아닙니다.
  • 속도: 구역별 속도 제한은 엣지의 maxSpeed로 표현합니다. 이 값을 차량이 실제로 지키는지는 시뮬레이터에서 측정하지 않았습니다.
  • 분할: base는 다음 결정 지점까지, horizon은 그 뒤 예상 경로로 둡니다. horizon을 길게 보내면 차량이 미리 계획·준비할 수 있지만(표준은 pick을 horizon에 미리 알려 사전 리프팅을 하게 할 수 있다고 설명하되, 그 동작은 노드가 아직 released가 아니라 state에 running으로 보고되지 않는다고 적습니다), 바꿔야 할 때 update 한 번으로 horizon 전체를 바꿀 수 있어야 합니다.
  • ID: orderId와 actionId는 UUID처럼 겹치지 않게 만들고(표준 제안), 재시도할 때도 새 orderId를 씁니다. 단 idLen, idNumericalOnly 한도가 있는 차량은 그에 맞춥니다.

한계

표준은 2.0.0 문서(릴리스 후보 표기)를 기준으로 했고, 이후 버전에서 필드가 바뀌었는지는 확인하지 않았습니다. 실행 결과는 libVDA5050++ v2.11.3 위의 시뮬레이터 하나에서 얻은 것이고, 다른 구현은 같은 규칙에 다르게 반응할 수 있습니다. orientation, orientationType, direction, rotationAllowed, maxRotationSpeed, maxSpeed, maxHeight, minHeight, length, trajectory는 표준의 정의만 읽었고 실행으로 확인하지 않았습니다. 시뮬레이터는 내장 이동 모델로 직선 이동만 했기 때문입니다. 엘리베이터 층 이동 order와 zoneSetId도 시험하지 않았습니다.


같이 보면 좋은 글

  • VDA5050 노드 도착 판정과 state 변화 순서 정리