🚚 WIA 배송 로봇 표준 전자책 | 제6장 (총 8장)
3단계 내비게이션 프로토콜을 따르는 배송 로봇 한 대는 혼자서도 배송 한 건을 끝낼 수 있다. 그러나 사업자는 로봇 한 대가 아니라 캠퍼스·건물 단지·도시 구역에 걸친 플릿(차량군)을 운영하며, 표준의 가치는 여러 제조사의 로봇을 하나의 클라우드 플랫폼이 관찰하고 배차하고 충전하고 업데이트할 수 있을 때 비로소 드러난다. WIA ROB-010의 4단계(Fleet Integration)는 바로 그 플랫폼을 다룬다. 플랫폼을 이루는 서비스, 그 서비스에 데이터를 공급하는 텔레메트리 채널, 작업을 할당하고 충돌을 해소하는 방식, 충전과 정비 절차, 그리고 이 모두를 둘러싼 보안·복구 요건이 그 내용이다.
이 장은 4단계를 직접 구현해야 하는 엔지니어의 눈으로 읽는다. 주제마다 명세가 실제로 무엇을 말하는지, 어디까지가 개략에 그치는지, 구현자가 스스로 결정해야 하는 것은 무엇인지를 구분한다. 인증·플릿 목록·통계 조회·오류 봉투 같은 REST 엔드포인트의 세부는 제4장에서 다루었으므로 여기서 반복하지 않으며, 플릿 KPI와 적합성 증거는 제7장에서 다룬다. 예시는 책 전체에서 쓰는 같은 로봇 550e8400-e29b-41d4-a716-446655440000을 사용한다.
4단계 §1.1은 플릿을 공용 메시지 브로커 위에 네 개의 서비스가 놓인 클라우드 플랫폼과, 그 아래에 연결된 임의 대수의 로봇으로 그린다. 브로커는 “MQTT/Kafka”로 표기되어 있는데, 이는 제품 지정이라기보다 역할에 대한 힌트로 읽는 편이 맞다. MQTT는 불안정한 링크 위의 소형 장치 다수에 적합한 경량 발행/구독 프로토콜이고, Kafka는 데이터 센터 내부의 대량 처리에 적합한 내구성 있는 로그이다. 로봇 연결은 MQTT 브로커에서 받고, 그 흐름을 로그 시스템으로 연결해 백엔드 소비자가 처리하게 하는 구성이 흔하지만, 표준이 이 분리를 요구하지는 않는다.
§2.1의 역할 분담은 인터페이스 관점에서 다시 정리해 둘 가치가 있다. 서비스마다 소비하고 생산하는 데이터가 다르기 때문이다.
| 서비스 | 4단계 §2.1의 책임 | 주요 입력 | 주요 출력 |
|---|---|---|---|
| 플릿 관리자 (Fleet Manager) | 로봇 등록과 수명 주기 관리, 배차와 부하 분산, 상태 감시와 경보, 구성 관리 | RobotState 스트림, 신규 배송 작업(1단계의 Package + Route) | 배차, 명령, 구성, 경보 |
| 경로 최적화기 (Route Optimizer) | 다중 로봇 경로 계획, 혼잡 예측과 회피, 동적 재경로, 충전 일정 최적화 | 계획 경로, 지도와 구역 데이터, 충전소 가용성 | Route 객체, 충돌 해소 결정, 충전 계획 |
| 분석 엔진 (Analytics Engine) | 실시간 대시보드, 이력 분석, 이상 탐지, 예측 정비 | 시계열 텔레메트리, 배송 결과 | 지표, 정비 경보, 보고서 |
| 운영자 대시보드 (Operator Dashboard) | 실시간 시각화, 수동 개입, 사고 관리, 보고 | 현재 플릿 상태, 경보 | 운영자 명령(2단계 API 경유), 사고 기록 |
이 아키텍처의 두 가지 성질이 이후 모든 내용을 규정한다. 첫째, 자신의 안전에 대한 최종 권한은 로봇에 있다. 클라우드는 작업을 배정하고 조언을 줄 뿐이며, 3단계에서 정의한 비상 정지·장애물 대응·고장 모드 처리는 로봇 안에서 실행되고 클라우드가 사라져도 계속 동작해야 한다(§8.1, “클라우드에 닿지 못해도 로봇은 자율적으로 운행한다”). 둘째, 이 아키텍처가 로봇과 클라우드 사이에 전제하는 데이터 계약은 1단계의 RobotState 하나뿐이다. 제조사가 보내는 그 밖의 데이터, 즉 원시 진단 정보나 독자 로그는 표준 밖에 있으며, 플릿 관리자가 배차 판단의 근거로 삼아서는 안 된다.
4단계 §2.2는 로봇 텔레메트리에 관해 세 가지를 고정한다. 로봇은 1~5초마다 상태를 발행하고, MQTT 토픽은 fleet/robots/{robot_id}/state이며, 메시지는 QoS 1로 JSON 인코딩된 RobotState를 싣는다. 브로커 제품, 스트림 처리기, 데이터베이스 같은 나머지는 구현자의 몫이다. §2.2의 흐름은 로봇 → MQTT/HTTP → 브로커 → 스트림 처리기 → 시계열 데이터베이스이고, 여기서 실시간 대시보드·경보·분석 엔진으로 가지가 나뉜다.
예시 로봇의 텔레메트리 메시지는 다음과 같다. 필수 필드 다섯 개가 모두 있고, version은 SemVer 패턴에 맞으며, mode는 1단계 열거값을 쓰므로 유효한 1단계 문서이다.
Topic: fleet/robots/550e8400-e29b-41d4-a716-446655440000/state
QoS: 1
Payload:
{
"robot_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-12-26T10:30:02.000Z",
"version": "1.0.0",
"position": {
"latitude": 37.5665,
"longitude": 126.9780,
"accuracy": 0.4,
"source": "fused"
},
"heading": 92.5,
"speed": 1.2,
"battery_level": 64,
"mode": "autonomous"
}
robot_id가 토픽과 본문에 두 번 나온다는 점에 주의해야 한다. 소비자는 두 값이 일치하는지 확인하고, 다르면 메시지를 거부해야 한다. 그렇지 않으면 설정이 잘못되었거나 탈취된 클라이언트가 다른 로봇의 상태처럼 보이는 메시지를 발행할 수 있다. 브로커의 접근 제어가 강제할 수 있는 것은 토픽 쪽 식별자이므로(6.7 참조) 둘 중 더 신뢰할 수 있는 것도 토픽 쪽이다.
MQTT QoS 1은 “최소 한 번(at least once)” 전달이다. 브로커는 수신 확인을 받을 때까지 메시지를 다시 보내므로, 예컨대 확인 도중에 재접속이 일어나면 같은 메시지가 두 번 이상 도착할 수 있다. 표준은 QoS 0(최대 한 번, 유실 가능)이나 QoS 2(정확히 한 번, 왕복이 더 많음) 대신 QoS 1을 골랐다. 그 결과 모든 소비자에게 분명한 요건이 생긴다. 처리는 멱등(idempotent)이어야 한다. 자연스러운 키는 (robot_id, timestamp) 쌍이다. 시계열 저장소는 추가가 아니라 이 키로 갱신·삽입(upsert)해야 하고, 이벤트 수를 세는 경보 규칙은 서로 다른 키의 수를 세어야 하며, “현재 상태” 캐시는 들어온 타임스탬프가 보유 중인 값보다 새로울 때만 항목을 바꾸어야 한다. 재접속 뒤에는 버퍼에 쌓였던 과거 메시지가 새 메시지보다 늦게 도착할 수 있기 때문이다.
다음 파이썬 스케치는 paho-mqtt 클라이언트(버전 2 API)로 양쪽을 보여 준다. 발행자는 주기마다 RobotState 하나를 보내고, 구독자는 와일드카드 fleet/robots/+/state를 듣고 식별자를 대조하며 중복을 버리고, 실시간 화면에는 오래된 갱신을 반영하지 않는다.
import json, ssl, time
import paho.mqtt.client as mqtt
BROKER = "broker.example.net" # 예시 호스트
ROBOT_ID = "550e8400-e29b-41d4-a716-446655440000"
# ---- 로봇 측: PUBLISH_S초마다 RobotState 발행 ----
PUBLISH_S = 2 # 4단계 §2.2 허용 범위 1~5초
def run_publisher(read_state):
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=ROBOT_ID,
clean_session=False) # 재접속해도 세션 유지
c.tls_set(certfile="robot.crt", keyfile="robot.key",
cert_reqs=ssl.CERT_REQUIRED)
c.connect(BROKER, 8883)
c.loop_start()
topic = f"fleet/robots/{ROBOT_ID}/state"
while True:
state = read_state() # 1단계 스키마로 검증되는 dict
c.publish(topic, json.dumps(state), qos=1)
time.sleep(PUBLISH_S)
# ---- 클라우드 측: 전체 로봇 구독, 중복 제거, 최신 상태 유지 ----
seen = set() # 운영 환경에서는 TTL 캐시 사용
latest = {} # robot_id -> 마지막 타임스탬프
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe("fleet/robots/+/state", qos=1)
def on_message(client, userdata, msg):
topic_id = msg.topic.split("/")[2]
state = json.loads(msg.payload)
if state.get("robot_id") != topic_id:
return # 식별자 불일치: 거부하고 기록
key = (topic_id, state["timestamp"])
if key in seen:
return # QoS 1 중복
seen.add(key)
store_timeseries(state) # (robot_id, timestamp)로 upsert
if latest.get(topic_id, "") < state["timestamp"]:
latest[topic_id] = state["timestamp"] # 모든 로봇이 같은 UTC "Z" 형식을
update_live_view(state) # 쓸 때만 안전한 비교
마지막 줄의 타임스탬프 문자열 비교는 모든 타임스탬프가 하나의 고정된 UTC 형식으로 기록될 때만 성립한다. 1단계는 +09:00 같은 오프셋을 포함해 어떤 ISO 8601 날짜·시각도 허용하므로, 견고한 소비자는 비교 전에 이를 시각(instant)으로 파싱한다. 이는 더 큰 원칙의 작은 예이다. 정규화는 소비자마다 되풀이하지 말고 파이프라인 입구에서 한 번만 한다.
1~5초라는 범위는 최신성과 부하 사이의 절충이다. 주기가 짧으면 대시보드가 부드러워지고 플릿 관리자가 멈춘 로봇을 더 빨리 알아차리지만, 브로커 트래픽과 저장량이 로봇 대수만큼 곱해진다. 실무에서는 허용 범위 안에서 상태에 따라 주기를 바꾸는 방식이 쓸 만하다. mode가 autonomous이거나 오류가 활성 상태일 때는 짧은 쪽으로, idle이나 charging일 때는 긴 쪽으로 발행한다. 비상 정지나 3단계 고장 모드 전이처럼 즉시 알려야 하는 사건은 다음 주기를 기다리지 말고 곧바로 상태 메시지를 하나 더 발행한다. 1단계 §6.3은 고빈도 텔레메트리에 Protocol Buffers와 델타 갱신도 권한다. 이진 인코딩을 채택한다면 필드 이름과 의미를 JSON 스키마와 똑같이 유지해 1단계 스키마가 유일한 정의로 남게 하고, MQTT 토픽만으로는 인코딩을 알 수 없으므로 배포 문서에 인코딩을 명시한다.
§2.2는 스트림 처리기의 일을 평균 속도·배터리 같은 집계 계산, 시계열 데이터베이스에 이력 저장, 대시보드 공급, 이상 경보 구동으로 나열한다. 여기서 세 가지 실무 결정이 따라 나온다.
robot_id로 키잉해 한 로봇의 메시지는 한 작업자가 순서대로 처리하게 한다. 구역 점유나 플릿 배터리 분포 같은 로봇 간 집계는 로봇별 최신 상태에서 계산한다.멱등 키, 순서 판단, 그리고 이 책의 모든 지연 수치는 서로 일치하는 시계에 의존한다. 명시적으로 비규범적인 4단계 부속서 E는 이 단계의 마감 시간 대부분에는 stratum-2 서버를 쓰는 NTPv4로 충분하며, 결정론적 인터록이 필요한 현장에는 PTP(IEEE 1588)를 권한다고 적고 있다. NTPv4는 IETF RFC 5905에 규정되어 있다. 실무에서는 로봇이 발행을 시작하기 전에 시계를 동기화하고, 동기화 상태를 제조사 진단 정보에 남기며, 스트림 처리기가 브로커 수신 시각보다 크게 앞선 타임스탬프를 표시하도록 한다. 시각 동기화 없이 부팅한 로봇에서 흔히 보이는 증상이다.
4단계 §3.1은 중앙집중식 알고리즘을 의사코드로 제시한다. 새 작업마다 배터리가 임계값보다 낮은 로봇과 이미 용량이 찬 로봇을 건너뛰고, 남은 로봇마다 픽업 지점까지의 거리, 예상 배송 시간, 기존 작업에 대한 영향을 더해 비용을 계산한 뒤, 가장 싼 로봇에 작업을 배정한다. 알고리즘은 짧고 취지는 옳지만, 구현자에게 네 가지 결정을 남긴다.
weight_kg, dimensions_cm), 온도 관리(temperature_controlled)가 더 현실적인 제약이다. 어떤 척도를 쓰는지 문서화한다.priority 열거값의 urgent·express 패키지에는 대개 더 큰 가중치를 준다.def assign(task, robots, v_nominal=1.5, reserve_pct=25, w_impact=1.0):
"""최소 비용 로봇의 robot_id를 반환하고, 없으면 None.
단위: 초. v_nominal(m/s)과 reserve_pct는 배포 파라미터이다."""
best, best_cost = None, float("inf")
for r in robots:
if r.telemetry_stale or r.mode in ("error", "manual"):
continue
if r.load + task.load > r.capacity:
continue
need_pct = energy_pct(r, task) + energy_pct_to_nearest_charger(r, task.dropoff)
if r.battery_level - need_pct < reserve_pct:
continue
cost = (distance_m(r.position, task.pickup) / v_nominal
+ eta_delivery_s(task)
+ w_impact * added_delay_s(r, task))
if cost < best_cost:
best, best_cost = r.robot_id, cost
return best
새 작업 하나와 로봇 세 대를 생각해 보자. 아래 수치는 모두 예시이다. 공칭 속도 1.5 m/s, 모든 로봇에 대해 픽업 후 배송 구간 720초, 예비 배터리 25%를 가정한다.
| 로봇 | 배터리 | 픽업까지 거리 | 대기 중인 작업 | 필터 | 비용(초) |
|---|---|---|---|---|---|
| R1 | 64% | 900 m | 배송 2건. 픽업을 끼워 넣으면 240초 지연 | 통과 | 600 + 720 + 240 = 1,560 |
| R2 | 91% | 1,400 m | 없음 | 통과 | 933 + 720 + 0 = 1,653 |
| R3 | 31% | 300 m | 없음 | 탈락: 작업과 충전소 복귀에 약 9% 필요, 남는 22% < 25% | — |
R1이 약 90초 차이로 선택된다. 차이는 작고, 그 차이는 전적으로 R1의 기존 고객에게 생기는 240초 지연을 얼마로 평가하느냐에 달려 있다. 그 두 배송이 urgent로 표시되어 운영자가 w_impact를 1.5로 두면 R1의 비용은 1,680초가 되고 R2가 선택된다. 이 민감도가 알고리즘의 실제 내용이다. 식은 단순하지만 가중치에 사업 정책이 담기므로, 가중치는 설정값으로 두고 모든 배차 결정과 함께 기록해 운영자가 나중에 왜 그 로봇이 선택되었는지 설명할 수 있게 해야 한다. R3도 교훈적이다. 가장 가까운 로봇이어서 거리만 보면 선택되었겠지만, 에너지 필터가 충전 예비선 아래로 고립될 배차를 막아 준다.
§3.1의 대안은 분산 경매이다. 관리자가 작업을 방송하면, 각 로봇은 자신이 그 작업을 완수하는 비용을 입찰가로 계산해 제출하고, 관리자는 최저 입찰자에게 작업을 낙찰하며, 낙찰자가 이를 확정한다. 경매는 비용 계산을 가장 최신 정보가 있는 로봇 쪽으로 옮긴다. 정확한 대기열, 자체 에너지 모델, 방금 본 주변 장애물이 모두 로봇에 있다. 또 제조사는 에너지 모델을 공개하지 않아도 된다. 모델이 아니라 숫자 하나만 발행하면 되기 때문이다.
다섯 단계만으로는 완전한 프로토콜이 되지 않는다. 구현은 최소한 다음을 더해야 하며, 표준에 없는 내용이므로 선택한 값을 문서화해야 한다.
robot_id를 택해 반복 실행해도 결과가 같게 한다.4단계는 경매 메시지의 토픽을 정하지 않는다. MQTT로 경매를 운영한다면 fleet/tasks/{task_id}/offer, .../bid, .../award 같은 배치가 무난하다. 다만 이는 배포 관례로 취급해야 하며 WIA ROB-010의 일부가 아니다.
| 항목 | 중앙집중식 최소 비용 | 경매 |
|---|---|---|
| 비용 계산 위치 | 클라우드, 텔레메트리 기반 | 각 로봇, 로컬 상태 기반 |
| 입력의 최신성 | 마지막 텔레메트리만큼 오래됨(1~5초) | 입찰 시점 기준 최신 |
| 결정 지연 | 계산 한 번 | 최소한 방송 한 번과 입찰 마감 시간 |
| 연결 불량 시 동작 | 마지막으로 알려진 상태로 동작 | 제안을 듣지 못한 로봇은 입찰하지 않을 뿐 |
| 제조사 모델 비공개 | 클라우드가 제조사별 에너지 모델을 가져야 함 | 제조사는 입찰가만 공개 |
| 결정 설명 가능성 | 간단함: 모든 입력이 중앙에 기록됨 | 받은 입찰을 모두 기록해야 함 |
많은 플릿이 결국 혼합형에 이른다. 관리자가 중앙에서 거르고 순위를 매긴 뒤, 상위 두세 후보에게 최신 비용으로 확인을 받고 낙찰한다. 어느 방식이든 배차 결과는 1단계 데이터에 다시 반영해야 한다. 패키지 상태는 pending에서 assigned로 바뀌고 로봇의 route는 새 계획을 반영해, 모든 소비자가 하나의 사실을 보게 한다.
3단계는 이미 각 로봇이 지역 경로 계획을 통해 감지할 수 있는 장애물, 다른 로봇을 포함한 장애물을 피하도록 한다(제5장). 플릿 수준의 충돌 해소는 로봇 한 대가 미리 볼 수 없는 충돌을 다룬다. 일방통행 복도에 양 끝에서 동시에 들어서려는 두 로봇이나, 엘리베이터 하나로 모여드는 세 로봇이 그런 예이다. §3.2는 두 가지 방법을 제시한다.
조정 구역은 좁은 출입문·승강기·경사로처럼 한 번에 로봇 한 대만 있을 수 있는 제한 구역이다. 접근은 토큰 방식이다. 로봇은 구역의 토큰을 요청하고, 다른 로봇이 가지고 있으면 기다리며, 허가를 받으면 들어가고 나올 때 반납한다. 이는 분산 상호 배제 문제이며, 표준의 세 문장이 현장에서 안전하려면 세 가지 안전장치가 필요하다.
엘리베이터는 따로 짚어 둘 만하다. 1단계의 navigation_constraints.elevator_allowed는 경로가 승강기를 써도 되는지를 계획기에 알려 주지만, 로봇이 승강기를 호출하거나 건물에 목적 층을 알리는 방법에 대해 4단계는 아무 말도 하지 않는다. 이 연동은 현재 건물마다 다르다. 승강기는 임대 시간이 긴 조정 구역으로 다루고, 건물 인터페이스는 표준 밖에서 처리한다.
두 번째 방법은 동적 충돌 해소이다. 로봇은 계획 경로를 플릿 관리자와 공유하고, 관리자는 시간 창 안에서 교차하는 경로를 찾으며, 긴급도와 배터리로 우선순위를 정하고, 우선순위가 낮은 로봇이 정지하거나 재경로로 양보한다. 실용적인 충돌 검사는 각 계획 경로를 시각이 붙은 점의 열로 보고, 두 로봇이 겹치는 시각에 분리 거리보다 가까워질 것으로 예측되면 충돌로 표시한다.
def conflicts(path_a, path_b, sep_m=2.0, window_s=30, dt=1.0):
"""path(t) -> 지역 미터 좌표계의 (x, y). 첫 충돌 시각 또는 None 반환."""
t = 0.0
while t <= window_s:
ax, ay = path_a(t)
bx, by = path_b(t)
if (ax - bx) ** 2 + (ay - by) ** 2 < sep_m ** 2:
return t
t += dt
return None
def who_yields(a, b):
# 배송 우선순위가 높은 쪽이 이기고, 다음은 배터리가 낮은 쪽(우회할 여유가 가장 적음),
# 마지막은 결정론적 동점 처리로 모든 노드가 같은 답에 이르게 한다.
rank = {"urgent": 2, "express": 1, "normal": 0}
ka = (rank[a.priority], -a.battery_level, a.robot_id)
kb = (rank[b.priority], -b.battery_level, b.robot_id)
return b if ka > kb else a
분리 거리, 예측 창, 시간 간격은 파라미터이지 표준의 값이 아니다. 예측 창의 상한은 경로를 어디까지 믿을 수 있느냐로 정해진다. 보행자가 많은 구역에서는 지역 계획기가 전역 경로에서 자주 벗어나므로, 수십 초를 넘는 예측은 안전보다 오경보를 늘린다. 두 가지 세부가 더 중요하다. 거리는 위도·경도를 빼서가 아니라 지역 미터 좌표계에서 계산한다(변환은 제2장). 그리고 충돌 해소 결정은 여러 노드에서 계산될 수 있고 그 결과가 일치해야 하므로 동점 처리를 결정론적으로 만든다.
양보는 새로운 종류의 안전 정지가 아니라 로봇에 보내는 일반 명령으로 표현해야 한다. 양보하는 로봇은 3단계의 통상 행동(행동 상태 기계의 WAITING 상태)으로 감속·대기·재계획하며, 바로 앞 1미터에서 일어나는 일은 여전히 로봇 자신의 장애물 감지가 전적으로 책임진다.
4단계 §4.1은 충전을 네 단계로 설명한다. 아래 표는 단계마다 로봇이 1단계 상태로 무엇을 보고해야 하는지, 무엇이 잘못될 수 있는지를 덧붙인 것이다.
| 단계 | 내용(§4.1) | 보고할 RobotState | 대비할 대표 실패 |
|---|---|---|---|
| 1. 이동 | 충전소 좌표로 이동하고, 시각 마커(AprilTag)로 정밀 위치를 맞춤 | mode: autonomous, 유형이 charging_station인 경유지로 가는 경로 | 마커가 보이지 않음(가림, 눈부심), 접근로 막힘 |
| 2. 도킹 | 카메라 피드백으로 정렬하고 충전 패드에 접촉한 뒤 전압·전류 센서로 충전 시작을 확인 | 전류가 확인된 뒤에만 mode: charging | 접촉은 됐으나 전류 없음: 정해진 횟수만 재정렬하고 이후 오류 보고 |
| 3. 충전 | 배터리 잔량과 충전 속도를 감시하고, 완충 시각을 추정해 플릿 관리자에 갱신 | mode: charging, 증가하는 battery_level | 충전 속도가 예상보다 크게 낮음: 패드 마모나 배터리 결함 가능, 정비 경보 발행 |
| 4. 완료 | 완충을 감지해 도킹을 풀고 운행에 복귀하며 가용 상태를 보고 | 배정될 때까지 mode: idle | 이탈 경로 막힘, 충전 슬롯 미반납 |
본문에 두 가지를 보완할 필요가 있다. 첫째, 표준은 완충을 “전압 정체(voltage plateau)”로 감지한다고 쓴다. 통상의 정전류·정전압(CC/CV) 방식으로 충전하는 리튬 이온 배터리에서는 정전압 구간 초반에 이미 전압이 설정값에 도달하고 그 뒤 줄어드는 것은 전류이다. 따라서 신뢰할 수 있는 충전 종료 신호는 로봇의 전압 판독값이 아니라 배터리 관리 시스템(BMS) 자체의 상태 신호이다. 둘째, 도크 위에 서 있다는 이유만으로 charging을 보고해서는 안 된다. 플릿 관리자는 이 모드로 가용성을 계획하며, 도킹했지만 전류를 받지 못하는 로봇은 충전 중인 로봇이 아니라 고장이다.
표준은 충전소 상태 목록 조회, 특정 충전소 가용성 확인, 슬롯 예약이라는 세 가지 작업을 정의하며, 그 HTTP 형태는 제4장에 정리되어 있다. 플릿 수준에서 중요한 것은 예약의 의미론이다. 본문은 예약이 얼마나 유지되는지, 로봇이 끝내 도착하지 않으면 어떻게 되는지를 말하지 않는다. 합리적인 정책은 로봇의 예상 도착 시각에 유예 시간을 더한 시점에 만료되고, 새 텔레메트리가 로봇이 계속 접근 중임을 보여 주면 연장되며, 로봇의 계획이 바뀌면 즉시 해제되는 예약이다. 만료가 없으면 충전소로 가던 로봇 한 대가 고장 나는 것만으로 그 슬롯이 조용히 플릿에서 사라진다.
| 전략(§4.2) | 발동 조건 | 이점 | 구현 메모 |
|---|---|---|---|
| 선제 충전 | 배터리 20~30% | 긴급한 저전력 상황을 막고, 플릿 관리자가 슬롯을 미리 예약 | 비상 상황인 3단계 고장 모드 임계값 15%보다 충분히 높아야 함 |
| 기회 충전 | 유휴 시간 | 긴 충전 없이 부분 보충으로 가용성 유지 | 충전소가 가까울 때만 이득, 이동 에너지를 계산에 넣을 것 |
| 동적 일정 | 플릿 전체 계획 | 수요 정점에 충분한 로봇을 확보하고, 경부하 시간대 전기 요금으로 충전을 옮길 수 있음 | 수요 예측이 필요함. 이 단계 §6.2의 분석 기법 참조 |
세 전략은 배차와 맞물린다. 6.3의 에너지 필터와 선제 충전 임계값은 같은 예비값을 써야 한다. 그렇지 않으면 충전 계획기가 돌려보내려는 로봇에 배차기가 계속 일을 준다. 두 값을 하나의 설정에 두고 모든 결정과 함께 기록한다.
§5.1은 감시할 마모 지표로 배터리의 충전 주기와 용량 열화, 모터의 누적 가동 시간과 전류 소모 이상, 바퀴의 주행 거리계와 (장착된 경우) 타이어 공기압, 센서의 교정 드리프트와 데이터 품질 저하를 든다. 이 대부분은 부품 건강이 아니라 운행 상태를 싣는 1단계 RobotState에 들어 있지 않다. 따라서 플릿에는 더 느린 주기의 별도 진단 채널이 필요하고, 제조사는 내부 계수기를 분석 엔진이 기대하는 지표로 대응시켜야 한다. 공통 출력은 표준의 경보 형식이다.
{
"alert_id": "maint-2025-12-26-001",
"robot_id": "550e8400-e29b-41d4-a716-446655440000",
"severity": "warning",
"component": "battery",
"issue": "Capacity degraded to 75% of original",
"recommendation": "Schedule battery replacement within 30 days",
"timestamp": "2025-12-26T10:30:00Z"
}
이 예시는 §5.1에서 가져온 것이며 값은 예시이다. 표준은 경보 스키마를 공개하지 않으므로 severity와 component의 허용값이 정해져 있지 않다. 시스템 사이에 경보를 주고받는다면 각각의 열거값을 정의하고(예: info/warning/critical, 감시 부품마다 하나씩), 1단계가 자신의 스키마를 버전 관리하듯 이 스키마도 버전 관리하며, 경보를 촉발한 텔레메트리 구간과 연결해 정비 기술자가 근거를 볼 수 있게 한다.
§5.2는 펌웨어·소프트웨어·지도 업데이트를 단계적 배포로 설명한다. 패키지를 준비하고, 플릿 일부(카나리)에 배포하고, 감시한 뒤, 성공하면 전체에 배포하고, 실패하면 해당 로봇을 되돌린다. 안전 규칙은 패키지에 암호학적으로 서명하고, 설치 전에 서명을 검증하며, 되돌리기를 위해 이전 버전을 보관하고, 주행 중인 로봇은 절대 업데이트하지 않는다는 것이다.
“주행 중 금지”는 로봇 자신이 평가하는 구체적인 관문이 되어야 한다. 클라우드가 보는 로봇 상태는 몇 초 전의 것일 수 있기 때문이다. 합리적인 관문은 1단계 필드와 충전 예비선을 결합한다.
def may_install_update(state, has_cargo, pkg):
return (
state["mode"] in ("idle", "charging") # autonomous·manual 아님
and not has_cargo # 고객 패키지를 싣고 있지 않음
and state["battery_level"] >= 50 # 예시 하한, 플랫폼별로 정함
and verify_signature(pkg) # 어떤 것도 쓰기 전에 검증
and pkg.target_version != current_version()
)
관문 외에 세 가지 관행이 있어야 되돌리기가 선언이 아니라 실제가 된다. 비활성 슬롯(A/B 구성)에 설치하고 새 이미지가 부팅해 자가 시험을 통과한 뒤에만 전환해, 실패한 업데이트가 기존 버전을 건드리지 않게 한다. 설치된 소프트웨어 버전을 진단 채널로 보고해 분석 엔진이 카나리 집단과 대조 집단을 비교할 수 있게 한다. 그리고 카나리의 성공 기준, 예컨대 정해진 운행 횟수 동안의 비상 정지 비율·고장 모드 발생·작업 성공률을 배포 전에 정해 두고, 결과를 본 뒤에 수치가 “괜찮아 보이는지” 판단하지 않는다.
표준은 서명을 요구하지만 형식을 지정하지 않는다. 검증된 선택지로는 저장소 탈취와 버전 되돌리기 공격을 방어하는 The Update Framework(TUF)와 이를 자동차용으로 응용한 Uptane이 있다. 제7장에서는 WIA가 적합성 증거 서명에 쓰는 Sigstore를 다룬다. 같은 도구로 업데이트 매니페스트에 서명할 수도 있지만 이는 구현상의 선택이다.
§7.1은 모든 클라우드 통신에 TLS 1.3(IETF RFC 8446), 저장 데이터에 AES-256, 운영자 대시보드에 역할 기반 접근 제어(RBAC), 모든 API 호출과 운영자 조작에 대한 감사 로그를 요구한다. 텔레메트리 채널에서는 다음 세 가지를 더해야 이 요건이 방어 가능한 설계가 된다.
fleet/robots/{자기 id}/state에만 발행하고 자기 명령 토픽만 구독하도록 브로커를 설정한다. 6.2.1의 토픽 식별자를 믿을 수 있게 만드는 것이 바로 이 설정이다.배송 로봇은 공공 공간을 촬영한다. §7.2는 영상을 클라우드에 올리기 전 얼굴 흐림 처리, 필요한 데이터만 수집, 오래된 텔레메트리를 자동 삭제하는 보존 정책(표준은 90일을 예로 든다), 그리고 GDPR·CCPA와 현지 개인정보 법령 준수를 요구한다. 구현에서는 두 가지를 조심해야 한다.
첫째는 흐림 처리를 어디서 하느냐이다. “올리기 전”은 로봇에서라는 뜻이므로 얼굴 검출을 위한 탑재 연산 능력과, 검출이 실패했을 때의 정책이 필요하다. 흐리지 않은 프레임을 기본으로 올리면 규칙이 무의미해진다. 1단계는 이미 원시 프레임 대신 검출 결과와 이미지 URL을 보내도록 권하며, 대부분의 플릿 기능에는 검출 목록만으로 충분하다.
둘째는 보존에 두 개의 시계가 있다는 점이다. 일상 텔레메트리는 90일 예시처럼 짧은 주기로 지울 수 있다. 감사 로그, 사고 기록, 적합성 증거는 목적이 다르며 대개 더 오래 보관해야 한다. 4단계 부속서 E는 7년 보존 기간이면 대부분의 관할에서 ISO/IEC 17065 감사 기대를 충족한다고 적고 있으나, 이 메모는 비규범적이고 근거를 제시하지 않으므로 의존해서는 안 된다. 각 보존 기간은 해당 배포에 실제로 적용되는 법적·계약적 요건에서 정하고, 장기 보존 저장소에는 가능한 한 개인정보를 두지 않는다.
§8.1은 네 가지 장애 허용 수단을 든다. 다중 리전 배포, 실시간 데이터베이스 복제, 클라우드에 닿지 못해도 로봇이 자율 운행하는 우아한 성능 저하(graceful degradation), 전송 중 메시지의 영속 저장이다. §8.2는 감지, 장애 조치(failover), 데이터 동기화, 로봇 재접속, 검증, 사후 분석의 6단계 복구 절차를 제시한다.
실제로 플릿이 가장 자주 무너지는 곳은 이 절차의 로봇 쪽이다. 클라우드가 사라지면 3단계 통신 두절 규칙이 적용된다. 60초 동안 클라우드 접촉이 없으면 로봇은 현재 작업을 계속하고 로컬에 기록한다. 보고할 수 없는 새 작업을 받아서는 안 되며, 어떤 안전 기능도 클라우드에 의존해서는 안 된다. 장애 조치로 접속 지점이 바뀌면 모든 로봇이 한꺼번에 재접속을 시도한다. 무작위 지터를 섞은 지수 백오프로 부하를 흩어 새 리전이 자기 플릿에 쓰러지지 않게 한다. 영속 MQTT 세션(고정 클라이언트 ID와 비정리 세션)을 쓰면 브로커는 로봇이 없는 동안 그 로봇 앞으로 온 QoS 1 메시지를 보관할 수 있다. 단, 브로커 자체의 저장소가 복제되어 있어야 하며, §8.1의 “메시지 큐 내구성”이 요구하는 것이 바로 그것이다.
| 사건 | 로봇의 동작 | 클라우드의 동작 |
|---|---|---|
| 링크 끊김, 60초 미만 | 정상 운행, 텔레메트리 버퍼링 | 몇 주기 누락 후 지연 상태로 표시, 아직 재배차하지 않음 |
| 링크 끊김, 60초 이상 | 3단계 규칙: 현재 작업 계속, 로컬 기록, 새 작업 받지 않음 | 해당 로봇의 작업 보류, 배송 기한이 위태로우면 운영자에게 경보 |
| 리전 장애 조치 | 백오프와 지터로 새 접속 지점에 재접속 | 복제본 승격, 데이터 일관성 확인, 재접속 수용 |
| 재접속 | 버퍼링한 상태를 백필로 표시해 업로드 | 중복 제거, 최신 타임스탬프로 실시간 화면 재구성, 배차 재개 전 플릿 상태 검증 |
부속서 E는 p95 지연, 오류율, 위반 시간을 담은 월간 오류 예산 요약도 권한다. 복구 사건은 이 보고서의 주된 입력이며, 이것이 4단계 KPI 목표와 함께 적합성 증거에 어떻게 들어가는지는 제7장에서 다룬다.
다음 항목은 이 장에서 4단계가 열어 둔 것으로 확인한 결정들이다. 각 항목에 대한 답을 배포 문서에 기록해야 이 단계가 서술에 그치지 않고 다른 제조사의 로봇도 합류할 수 있는 통합이 된다.
| # | 결정 사항 | 표준이 멈추는 지점 |
|---|---|---|
| 1 | 브로커 제품, TLS 구성, 로봇별 자격 증명과 토픽 ACL | MQTT/Kafka와 TLS 1.3만 명시 |
| 2 | 1~5초 범위 안의 발행 주기 정책과 사건 기반 추가 메시지 | 범위만 있고 정책은 없음 |
| 3 | 중복 제거 키, 순서가 뒤바뀐 메시지와 백필 메시지 처리 | QoS 1에서 함의될 뿐 명시되지 않음 |
| 4 | 배차 가중치, 배터리 예비선, 용량 척도, 단위 | 수치 없는 의사코드 |
| 5 | 경매 마감, 확정 시간 초과, 동점 처리, 메시지 토픽 | 시간 규정 없는 다섯 단계 |
| 6 | 조정 구역 임대, 대기 위치, 획득 순서 | 토큰 개념만 있음 |
| 7 | 충전소 예약 만료와 충전 종료 신호 | 작업 이름만 있고 의미론은 열려 있음 |
| 8 | 정비 경보 스키마와 열거값 | 예시 하나, 스키마 없음 |
| 9 | OTA 서명 형식, 설치 관문, A/B 구성, 카나리 기준 | 서명과 되돌리기만 요구 |
| 10 | 텔레메트리와 감사 증거의 보존 기간을 따로 설정 | 90일 예시, 부속서 E 메모는 비규범적 |
이 답이 갖추어지면 플릿은 완결된 고리를 이룬다. 로봇은 4단계 채널로 1단계 상태를 발행하고, 클라우드는 작업을 배정하고 충돌을 해소하며, 충전과 업데이트는 배송을 끊지 않고 이루어지고, 네트워크가 흔들려도 시스템 전체가 안전하게 성능을 낮춘다. 제7장은 이를 입증하는 일, 즉 배포가 WIA ROB-010 적합성 등급을 주장할 때 근거가 되는 시험 벡터·증거 패키지·현장 지표로 넘어간다.
← 제5장: 3단계 내비게이션 프로토콜 — 계획·교통 규칙·비상 대응 | 인덱스로 돌아가기 | 제7장: 시험·적합성·인증 — Surface·Verified·Anchored →