비스캣 관제에 robotCode(예: R-001)가 사전 등록된 뒤, 로봇은 robotCode와 공개키를 제출합니다. 관제는 robotCode로 로봇을 조회해 내부 식별자 robotId(UUID)를 회신하고 HMAC 비밀키를 발급하며, 로봇은 X.509 인증서 번들(다운로드 URL)을 내려받습니다. 이후 로봇은 인증·MQTT에 robotId를 사용합니다. 등록·인증 요청은 REST로 진행하고, 인증서를 설치한 뒤 MQTT에 연결합니다. 이후 미션·상태·알람 메시지는 모두 MQTT로 주고받습니다.
/key·/cert를 다시 호출하면 409 ROBOT_ALREADY_ACTIVATED로 거절됩니다. 재발급(인증서 회전)이 필요하면 관제 관리자 초기화 후 다시 발급받습니다.X-Robot-ID, X-Robot-Ts(ISO8601, +09:00 KST), X-Robot-Signature(HMAC-SHA256). timestamp는 ±60초 윈도우.r2s/{robotId}/# publish, s2r/{robotId}/# subscribe)만 사용할 수 있습니다.로봇이 보유한 지도와 의미 거점 노드(매장·하역장·충전소·대기 등 goal.type 거점)를 siteId 기준으로 관제에 등록합니다. 운영자가 로봇 UI의 업로드 버튼을 누르면 로봇이 보유 맵을 자동 업로드합니다. 등록 개시 시 siteId·제시 mapId와 업로드할 파일 목록을 선언하면 관제가 파일별 업로드 URL을 발급하고, 로봇은 지도 이미지·메타(yml)·노드(json)를 각각 해당 URL로 올립니다(압축하지 않음). 흐름은 등록 개시 → 파일 업로드 → 등록 확정 3단계입니다.
siteId·제시 mapId와 업로드할 파일 목록을 관제에 전송. 관제가 mapVersion과 파일별 업로드 URL을 발급.uploadExpiresIn 내 완료.location.map_id가 활성 mapId를 가리키고, 관제는 등록된 노드 좌표로 goal을 내립니다.재매핑 시 siteId당 mapVersion이 증가하며 이력을 보관합니다(롤백·감사 가능). 노드 type은 goal.type(store·unloading·charging·waiting 등)과 동일 어휘이며, 관제가 goal의 location을 구성하는 기준이 됩니다. 지도 파일 포맷은 협의로 확정합니다. 상세는 API 문서 §7.
미션이 생기면 관제가 수행 가능한 후보 로봇에게 미션(goal 배열)을 제시합니다. 흐름은 입찰 요청 → 입찰 응답 → 배차 결과 3단계입니다. 각 로봇은 예상 도착시간을 입찰하고, 어느 로봇이 수행할지의 최종 배차 결정은 관제가 단독으로 내려(최소 도착시간 기준, 추후 적재공간 포함) result로 통보합니다.
goals/set과 동일 본문). 입찰·낙찰은 goalId 단위로 식별합니다.estArrival)을 자체 계산해 회신.claimed·reason으로 통보. claimed=true를 받은 로봇만 수행합니다.일부 로봇이 무응답이어도 bid 타임아웃 시점에 관제가 모인 입찰로 배차를 결정하고, 마감 후 도착한 지각 bid는 배제합니다. 라운드는 단발이며 재오퍼하지 않습니다 — 입찰 가능한 로봇이 없으면 해당 미션은 수행 불가로 종료됩니다. mission.atomic이 true면 해당 미션 goal들의 순서가 보존되어(거리 최적화 재정렬 시에도 묶음 유지) 다른 미션 goal이 사이에 끼지 못합니다. 입찰·배차 단위(goal vs 미션 묶음)는 협의 대상입니다.
주문이 확정되기 전, 관제가 후보 로봇에게 "지금 이 배송을 수행할 수 있는지"를 미리 물어 가용성과 예상 도착시간을 받아 두는 사전 견적 흐름입니다. 미션(goal)을 만들지 않는 비구속 질의라 배차 입찰(goals/*)과 토픽을 분리해 quote/*로 운영하며, 낙찰(result) 없이 질의 → 견적 2단계로 끝납니다.
s2r/{robot_id}/quote/offer) — goals/set(입찰 offer)과 동일한 본문 구조. 미션을 만들지 않는 비구속 질의이며, 입찰과 동일하게 goalId 단위로 식별합니다.available)와 예상 도착시간(estArrival, 불가 시 null·사유)을 회신(r2s/{robot_id}/quote/bid). 입찰 bid에 available만 더한 형태입니다.배차 입찰과 달리 미션을 생성하지 않고 낙찰(result) 단계가 없습니다(2단계). 결과는 배차 확정이 아니라 가용성 집계로, 관제가 available=true 로봇 중 최소 estArrival을 채택합니다. 입찰과 동일하게 goalId 단위 단발 라운드이며 bid 타임아웃 내 무응답은 available=false로 간주합니다. 토픽·페이로드 상세는 API 문서 §5.4. 타임아웃 값 협의
배차 이후의 한 사이클입니다(mode: delivery). 한 오더(mission.id)는 매장 goal(type:store, mission.type:pickup)과 고객 goal(type:customer, mission.type:dropoff)로 구성됩니다. 로봇은 goal 배열을 arrivalOrder 순으로 수행하며, 진행 상태를 r2s/{robot_id}/goals로 발행합니다. PIN은 mission.PIN으로 선전달되어 로봇이 로컬 검증하고, 사용 결과를 관제에 감사 이벤트로 보고합니다. 열 칸은 mission.door로 관제가 지정합니다.
goal.type:store · mission.type:pickup)으로 주행. 도착 시 goal status가 going → arrived로 발행되고, 관제가 오토메타에 매장 도착을 통지.mission.PIN으로 로컬 검증 → mission.door로 지정된 칸 개방. 사용·검증 결과를 관제에 감사 이벤트(r2s)로 보고.closed로 전이. 이 시점이 배달 출발 트리거이며, 관제가 오토메타에 배달 출발을 통지.goal.type:customer · mission.type:dropoff)로 주행, 도착 발행. 관제가 도착 통지.mission의 autoDropOff·PIN으로 결정 — 비대면(autoDropOff=true)은 도착 즉시 하차 후 복귀, WEB 버튼 인증(기본)은 관제 → 로봇 문열림 지시로 개방, 로봇 PIN 입력(예외 fallback)은 로컬 검증 후 개방. 사용 결과는 감사 이벤트로 보고.done이 되고(goal은 arrived 유지 — goal엔 종료값 없음) 관제가 미션을 종료. 전 mission이 done이면 state.status=done. 이후 처리는 아래 갈래.전 mission이 done이 되어 state.status=done이 되면 다음 처리는 세 갈래이며, 충전 복귀만 mode가 바뀝니다.
mode=delivery를 유지한 채 새 goal을 할당(모드 전이 없음).mode를 navi로 전이하고 goal.type=charging(mission.type=charge).mode=navi·goal.type=charging으로 충전소에 복귀.4칸 적재함의 어느 칸을 쓸지는 관제가 주문 수량 기반으로 결정해 mission.door로 지정합니다. 점주가 임의로 칸을 고를 수 없으며, 로봇은 PIN 로컬 검증 후 지정된 도어만 개방합니다. 주문 수량이 적재 용량을 초과하면 배차 단계에서 거부됩니다.
비대면(autoDropOff:true) / WEB 버튼 인증(기본, PIN:null·autoDropOff:false) / 로봇 PIN 입력(예외 fallback, PIN:랜덤)으로 goal.mission에 전달됩니다. 비대면은 autoDropOff:true인 경우만 해당하므로 (autoDropOff, PIN) 조합으로 3방식이 모두 구분됩니다. 상세는 API 문서 §6.
mode: trash_pickup. 흐름은 배송과 동일한 MQTT 입찰·배차·주행 구조(goal 배열)를 따르되, 매장↔세대 대신 세대·수거 요청 지점(goal.type:customer, mission.type:pickup) → 하역장(goal.type:unloading, mission.type:dropoff) 경로이고 점주 PIN 단계가 없습니다. trash_pickup의 goal/mission 매핑과 세부 적재·개폐 동작은 제로웍스 펌웨어 capability에 맞춰 협의 시 확정합니다. 매핑 협의 대기
엘리베이터 호출·문 열림은 관제가 엘리베이터·공동현관 연동을 통해 처리합니다. 로봇은 자신의 위치·상태·도착(goal status)만 발행하면 되고, 인프라 제어 책임은 지지 않습니다. 관제가 로봇 위치를 기준으로 적절한 시점에 엘리베이터·공동현관을 제어합니다.
goals/result에서 claimed=false + 사유(reason) 수신 시 해당 입찰은 종료(재오퍼 없음). 사유 코드 집합은 bid 타임아웃 값과 함께 확정.requestId 중복 흡수로 안전). 텔레메트리(상태)는 occurredAt 기준 최신값만 반영하므로 누락에 강건.r2s/{robot_id}/alarm으로 code·level(warn|error|fatal)·msg를 발행 → 관제가 레벨별로 대응(자동/수동 리셋). 레벨별 관제 대응 매핑은 시나리오 협의로 확정.
배송 두 케이스의 토픽·메시지 흐름을 단계별로 보입니다 — ① 단건 배송과 ② 주행 중 추가 주문(묶음 배송).
토픽 규약(s2r/r2s)·메시지 봉투·필드 정의는 API 문서 §5를 따릅니다. 등장 주체는
관제·로봇 Robot1이며, 주문 인입·앱 표출(오토메타)은 맥락 노트로만 표시합니다.
mode는 로봇이 어느 서비스로 활용되는지를 나타내는 상태값입니다. 배송 로봇은 배차 전 대기·이동, 그리고 다음 배송으로의 연속 배차까지 mode: delivery를 유지하며, 일감 유무·활동 진행은 state.status·goal.status로 표현합니다(mode로 나타내지 않음). 단 미션 완료 후 충전소로 복귀할 때는 delivery가 아니라 mode: navi + goal.type=charging으로 전이합니다(관제 지시 또는 로봇 timeout 자동 회차). 완료 후 처리 갈래는 §4 참조.
아래 JSON의 requestId·좌표·시각·PIN·estArrival 등은 형식 예시입니다. 상태/진행 발행은 변동 필드만 싣는 이벤트 모델이라 단계별로 필요한 필드만 노출했습니다. 적재 칸은 주문 단위로 관제가 배정하며(mission.door), 본 예시는 주문 Order1 → door1, 주문 Order2 → door2를 쓰고 같은 주문의 픽업·하차는 동일 칸을 씁니다.
고객 Order1 주문 → 입찰 → Robot1 확정 → 매장 도착·상차 → 배송 → Order1 도착·배송 완료. 한 주문(mission_Order1)이 매장 goal(goal_s1, pickup)과 고객 goal(goal_c1, dropoff) 두 goal에 같은 mission.id로 걸칩니다.
s2r/Robot1/goals/offer · 관제 → Robot1관제가 goal 전체(location 포함)를 제시합니다. location 좌표는 로봇이 사전 등록한 맵 노드 기준으로 관제가 채웁니다(§2 맵 등록).
{
"requestId": "req-offer-Order1",
"occurredAt": "2026-06-17T10:00:00+09:00",
"payload": {
"goals": [
{
"id": "goal_s1", "type": "store", "status": "ready", "arrivalOrder": 1,
"location": { "x": 12.4, "y": 8.1, "th": 0.0, "map_id": "apt1023-b1" },
"mission": [
{ "id": "mission_Order1", "type": "pickup", "status": "ready", "atomic": false,
"PIN": null, "door": [ { "name": "door1", "status": "closed" } ], "orderId": "Order1",
"address": "", "addressDetail": "", "phoneNumber": "", "message": "" }
]
},
{
"id": "goal_c1", "type": "customer", "status": "ready", "arrivalOrder": 2,
"location": { "x": 40.2, "y": 22.7, "th": 1.57, "map_id": "apt1023-3f" },
"mission": [
{ "id": "mission_Order1", "type": "dropoff", "status": "ready", "atomic": false,
"PIN": "1482", "door": [ { "name": "door1", "status": "closed" } ], "orderId": "Order1",
"address": "103동", "addressDetail": "302호", "phoneNumber": "010-0000-1234", "message": "문 앞" }
]
}
]
}
}
r2s/Robot1/goals/bid · Robot1 → 관제Robot1이 goal별 예상 도착시간(estArrival, min)을 자체 계산해 회신. 식별은 goalId 단위.
{
"requestId": "req-bid-Order1",
"occurredAt": "2026-06-17T10:00:03+09:00",
"payload": {
"goals": [
{ "id": "goal_s1", "arrivalOrder": 1, "estArrival": 4 },
{ "id": "goal_c1", "arrivalOrder": 2, "estArrival": 11 }
]
}
}
s2r/Robot1/goals/result · 관제 → Robot1{
"requestId": "req-result-Order1",
"occurredAt": "2026-06-17T10:00:05+09:00",
"payload": {
"goals": [
{ "id": "goal_s1", "claimed": true, "reason": null },
{ "id": "goal_c1", "claimed": true, "reason": null }
]
}
}
r2s/Robot1/state, r2s/Robot1/goals) · Robot1 → 관제배송 로봇이므로 mode는 delivery로 계속 유지되고, 일감을 잡으면 state.status가 doing으로 전이합니다. 매장 goal이 going → arrived로 발행되고 pickup mission이 doing으로 전이합니다. (텔레메트리는 requestId 생략)
// r2s/Robot1/state — 모드 전이
{ "occurredAt": "2026-06-17T10:00:06+09:00",
"payload": { "mode": "delivery", "status": "doing",
"location": { "x": 12.5, "y": 8.0, "th": 0.0, "map_id": "apt1023-b1" } } }
// r2s/Robot1/goals — 매장 도착·픽업 시작
{ "occurredAt": "2026-06-17T10:04:00+09:00",
"payload": { "goals": [
{ "id": "goal_s1", "status": "arrived", "progress": 100,
"mission": [ { "id": "mission_Order1", "type": "pickup", "status": "doing", "door": [ { "name": "door1", "status": "opened" } ] } ] }
] } }
r2s/Robot1/goals) · Robot1 → 관제점주가 PIN을 입력하면 Robot1이 mission.PIN으로 로컬 검증 후 지정 칸(door1)을 개방하고, 사용·결과를 감사 이벤트(r2s)로 보고합니다(토픽·페이로드는 API 문서 §6 협의 대상). 상차 후 박스를 닫으면 mission.door의 해당 도어가 closed로 전이(=배달 출발 트리거)하고 pickup mission이 done으로 완료됩니다. 도어 상태는 미션 컨텍스트이므로 state가 아니라 goals 발행의 mission.door[]에 실립니다.
// r2s/Robot1/goals — 도어 닫힘(door1) → 픽업 완료 (goal은 arrived 유지, 완료는 mission.status=done)
{ "occurredAt": "2026-06-17T10:05:11+09:00",
"payload": { "goals": [
{ "id": "goal_s1", "status": "arrived",
"mission": [ { "id": "mission_Order1", "type": "pickup", "status": "done",
"door": [ { "name": "door1", "status": "closed" } ] } ] }
] } }
r2s/Robot1/goals) · Robot1 → 관제세대 goal이 going → arrived, 고객 수령 후 dropoff mission이 done. 고객 수령은 기본 WEB 버튼 인증(관제 → 로봇 문열림 지시), 예외 시 로봇 PIN 입력입니다(§4 참조). 관제가 오토메타에 배송 완료를 통지합니다. 전 mission이 done이 되면 state.status=done이 되고, 이후 연속 배차가 있으면 mode=delivery를 유지한 채 새 goal을 받고, 없으면 충전소로 복귀합니다(mode=navi·goal.type=charging — §4).
// r2s/Robot1/goals — 하차 완료
{ "occurredAt": "2026-06-17T10:10:40+09:00",
"payload": { "goals": [
{ "id": "goal_c1", "status": "arrived",
"mission": [ { "id": "mission_Order1", "type": "dropoff", "status": "done", "door": [ { "name": "door1", "status": "closed" } ] } ] }
] } }
Order1 배차 후 Robot1이 매장1로 이동하는 중 고객 Order2 주문이 들어와 같은 Robot1에 추가됩니다. 낙찰 후 Robot1이 Order1·Order2 goal을 직접 통합·정렬해
매장1 상차 → 매장2 상차 → Order1 배송 → Order2 배송 순서로 수행합니다(관제의 plan 재주입 없음). Order2는 다른 매장(goal_s2)·다른 세대(goal_c2), 적재 칸은 door2를 씁니다.
Order1 주문 → offer → bid → result(claimed=true) → Robot1이 goal_s1로 이동(going). 형식은 8.1과 같아 생략합니다.
s2r/Robot1/goals/offer · 관제 → Robot1Robot1 주행 중 Order2 주문이 인입되면, 관제가 Order2의 두 goal만 제시합니다(Order1 goal은 Robot1이 이미 보유). arrivalOrder는 단건 주문과 동일하게 주문 내 순서(픽업 1·하차 2)로 보내며, 병합 시 관제가 전체 plan 기준으로 재배정합니다(④).
{
"requestId": "req-offer-Order2",
"occurredAt": "2026-06-17T10:01:00+09:00",
"payload": {
"goals": [
{
"id": "goal_s2", "type": "store", "status": "ready", "arrivalOrder": 1,
"location": { "x": 18.9, "y": 6.4, "th": 0.0, "map_id": "apt1023-b1" },
"mission": [
{ "id": "mission_Order2", "type": "pickup", "status": "ready", "atomic": false,
"PIN": null, "door": [ { "name": "door2", "status": "closed" } ], "orderId": "Order2",
"address": "", "addressDetail": "", "phoneNumber": "", "message": "" }
]
},
{
"id": "goal_c2", "type": "customer", "status": "ready", "arrivalOrder": 2,
"location": { "x": 44.0, "y": 30.1, "th": 3.14, "map_id": "apt1023-5f" },
"mission": [
{ "id": "mission_Order2", "type": "dropoff", "status": "ready", "atomic": false,
"PIN": "7035", "door": [ { "name": "door2", "status": "closed" } ], "orderId": "Order2",
"address": "105동", "addressDetail": "501호", "phoneNumber": "010-0000-5678", "message": "" }
]
}
]
}
}
Robot1이 현재 부하(Order1 수행 중)를 반영해 입찰하고, 관제가 낙찰을 통보합니다(형식은 8.1 ②③과 동일, goalId는 goal_s2·goal_c2).
낙찰(③) 후 Robot1은 이미 보유한 Order1 goal과 새로 받은 Order2 goal을 자체적으로 통합·정렬해 수행 순서를 정합니다. goal 정의는 offer 본문으로 이미 전달됐으므로, 관제는 별도 통합 plan을 주입하지 않습니다(goals/set 미사용) — 경로 최적화·재정렬은 로봇이 담당합니다. 각 주문의 내부 순서(픽업 → 하차)와 atomic 제약은 보존되며, Order1·Order2 모두 atomic: false라 픽업끼리·하차끼리 묶는 순서(A,C,B,D)가 허용되어 Robot1은 아래 수행 순서를 잡습니다.
| 수행 순서 | goal | type | mission | door | 의미 |
|---|---|---|---|---|---|
| 1 | goal_s1 | store | mission_Order1 · pickup | door1 | 매장1 상차 (Order1) |
| 2 | goal_s2 | store | mission_Order2 · pickup | door2 | 매장2 상차 (Order2) |
| 3 | goal_c1 | customer | mission_Order1 · dropoff | door1 | Order1 배송 |
| 4 | goal_c2 | customer | mission_Order2 · dropoff | door2 | Order2 배송 |
r2s/Robot1/goals) · Robot1 → 관제Robot1이 arrivalOrder 순으로 수행하며 각 단계 완료를 mission done으로 발행합니다. 매장2 상차 완료 시점에 두 주문(door1·door2)을 모두 적재한 상태가 됩니다.
// 매장1 상차 완료
{ "occurredAt": "2026-06-17T10:04:30+09:00",
"payload": { "goals": [
{ "id": "goal_s1", "status": "arrived",
"mission": [ { "id": "mission_Order1", "type": "pickup", "status": "done", "door": [ { "name": "door1", "status": "closed" } ] } ] }
] } }
// 매장2 상차 완료
{ "occurredAt": "2026-06-17T10:07:20+09:00",
"payload": { "goals": [
{ "id": "goal_s2", "status": "arrived",
"mission": [ { "id": "mission_Order2", "type": "pickup", "status": "done", "door": [ { "name": "door2", "status": "closed" } ] } ] }
] } }
// Order1 배송 완료 → 관제가 오토메타에 통지
{ "occurredAt": "2026-06-17T10:12:10+09:00",
"payload": { "goals": [
{ "id": "goal_c1", "status": "arrived",
"mission": [ { "id": "mission_Order1", "type": "dropoff", "status": "done", "door": [ { "name": "door1", "status": "closed" } ] } ] }
] } }
// Order2 배송 완료 → 관제가 오토메타에 통지. 전 mission done → state.status=done (연속 배차=delivery 유지 / 충전 복귀=navi·charging)
{ "occurredAt": "2026-06-17T10:16:40+09:00",
"payload": { "goals": [
{ "id": "goal_c2", "status": "arrived",
"mission": [ { "id": "mission_Order2", "type": "dropoff", "status": "done", "door": [ { "name": "door2", "status": "closed" } ] } ] }
] } }
| 단계 | mode | 주요 goal.status | 주요 mission.status |
|---|---|---|---|
| 배차 전 대기 | delivery | — | — |
| 낙찰·수행 시작 | delivery | goal_s* ready→going | pickup ready |
| 매장 도착·상차 | delivery | goal_s* arrived | pickup doing→done |
| 세대 도착·하차 | delivery | goal_c* going→arrived | dropoff doing→done |
전 mission 완료 (state.status=done) | delivery | goal arrived 유지 | 전부 done |
| 완료 후 — 연속 배차 | delivery 유지 | 새 goal ready→going | 새 mission ready |
| 완료 후 — 충전 복귀 (관제/자동 회차) | navi | charging goal going→arrived | charge |