Repository navigation
LOCATION_AUTOMATION_DESIGN
작성일: 2026-08-22 · 상태: 1차 구현 완료 (orchestration-server
6c0c7e0, 마이그레이션 085)구현 범위: §3 테이블 3개 · §4 실행 엔진(
internal/mqttx/arrival_automation.go) · Admin REST (/admin/{tasks|events|locations}/:id/automations,/admin/automations[/runs|/:id/fire]) · 백오피스(태스크/이벤트/장소 상세 편집기, 로봇 연동 → 규칙 목록·실행 로그). 미구현: §4.4automation/fired토픽(FE 수요 미확인), §6-5 세션 종료 시 대기 액션 취소,leave/dwell트리거, §6-1 수동>자동 우선순위. 로봇팔·반려로봇은 스키마 확정 전까지params.topic+ payload JSON 자유 입력. 2026-08-22 추가: §4.3 디바이스 명령 큐(SQS FIFO, 완료 대기·타임아웃·액션 체인) 구현. 2026-09-12 추가:leave(장소 이탈) 트리거 구현 (마이그레이션 114, §4 참고). Vision이 세션 위치를 다른 장소로 바꿔 기록하는 순간 직전 장소의 규칙을trigger='leave'로 평가한다. 규칙 모델·조건· 발화 정책·액션·실행 로그는 도착과 동일하며 모든 owner(태스크/이벤트/장소)에 붙일 수 있다. 후처리 연출이므로 액션을user_task_commands로 추적하지 않는다(태스크 자동 완료 무관).dwell은 여전히 미구현. 2026-09-12 추가 (117): 태스크 실행 방식tasks.execution_mode=parallel(기본, 동시 진행) |sequential(순차). 순차 태스크의 도착 규칙·자동 시작은 같은 장소의 앞 순서 태스크가 전부 종결될 때까지 보류(skip_reason=waiting_predecessor)되고, 앞 태스크 종결 시OnUserTaskFinished가 사용자가 아직 그 장소에 있으면 대기가 풀린 태스크의 규칙만 재평가한다(RunArrivalParams.OnlyTaskIDs). 시험 발화/강제 실행은 통과. 2026-09-12 추가 (119):device_command_logs— 큐가 보낸 명령(pub)과 로봇 응답(sub: ack·plan·hub response)을 command_id 로 묶어 원문 저장.GET /admin/automations/runs/:runId/logs로 실행 1건의 송수신 로그를 받아 파이프라인 실행 이력에서 단계별로 본다. 2026-09-12 추가: 태스크 상세 테스트 패널 — 세션을 골라 전제조건(세션 활성·코스 포함·배정·상태·현재 위치· 순차 앞 태스크·규칙·아이템·SPOT/로봇팔 가용성·타입별)을 점검하고 버튼으로 바로 조치한 뒤 도착 시뮬레이션(Vision 경로 동일) 또는 강제 실행.GET/POST /admin/tasks/:id/test/{preflight,fix}(internal/handlers/task_preflight.go).사용자가 특정 장소에 도착한 것을 Vision 위치 정보로 감지했을 때, 그 사용자에게 그 장소에 배정된 태스크/이벤트와 보유 아이템에 따라 SPOT · 로봇팔 · 반려로봇 · Hub 등에 어드민이 미리 정의한 명령을 자동으로 내리는 기능의 데이터 모델 · 서버 실행 흐름 · 백오피스 화면 구조 제안.
- Vision(카메라 트래킹)은
vision/position/person으로 방문객 좌표·zone_id를 계속 발행한다. - 서버는 이를 장소(
locations)로 매핑해orchestration/{sessionId}/position으로 재발행하고, 장소가 바뀐 순간만user_location_history에 기록한다. - 현재 "장소 도착 시 자동으로 일어나는 일"은 돌발 이벤트 발행 1가지뿐이다.
- 목표: 도착 시 여러 디바이스(SPOT, 로봇팔, 반려로봇, Hub)에 명령을 순서대로 자동 발행하고, 그 규칙을 어드민이 백오피스에서 정의·확인·테스트할 수 있게 한다.
- 핵심 전제: "무엇을 자동 수행할지"는 장소 자체가 아니라 (사용자, 장소, 태스크 or 이벤트) 매핑이
결정한다. 같은 "집"이라도 세션마다 배정된 태스크·이벤트가 다르고(
user_tasks,user_locations.event_id), 거기에 보유 아이템 유무가 더해져 수행 여부가 갈린다.
도착(세션 S, 장소 L)
├─ S에게 L에 배정된 태스크 : user_tasks ⋈ tasks(location_id = L) — 0..N개
├─ S에게 L에 배정된 이벤트 : user_locations(S, L).event_id — 0..1개
├─ (보조) L 자체 규칙 : 태스크·이벤트와 무관하게 장소에 오면 항상
└─ S의 보유 아이템 : store_purchases(S) (= orchestration/{S}/store/owned)
↓
위 태스크/이벤트/장소에 붙은 자동화 규칙 중 조건(아이템 등)을 만족하는 것만 발화
| 조각 | 위치 | 재활용 포인트 |
|---|---|---|
| 도착 감지 |
internal/mqttx/vision.go recordLocationChange() → maybePublishArrivalEvent()
|
Vision의 연속 좌표를 "장소가 바뀐 순간 1회"로 이미 디바운스. 자동화 훅 포인트는 여기 한 곳 |
| 사용자 ↔ 장소 ↔ 태스크/이벤트 매핑 |
user_locations(visit_order, event_id, event_published_at), user_tasks(task_id, status) + tasks.location_id
|
세션 생성 시 이미 확정되는 매핑. 도착 시 "이 세션이 이 장소에서 할 태스크/이벤트"를 바로 조회 가능 |
| SPOT 명령 정의 UI |
task_options / event_options의 spot_action, spot_destination_mode, spot_params + backoffice/.../components/SpotOptionFields.tsx, spotOptions.ts
|
"선택지를 고르면 SPOT 명령"이 이미 구현됨. 트리거만 "선택 → 도착"으로 바꾸면 같은 모델. 태스크·이벤트 상세 화면에 이미 SPOT 연동 UI가 있으므로 도착 자동화도 같은 화면에 붙인다 |
| SPOT 디스패치 |
internal/mqttx/spot.go DispatchSpotCommand(), SpotQueue
|
세션 → 배정 장소 → devices.type='spot' resolve + 큐잉 + ack/plan 릴레이 전부 재사용 |
| 디바이스 ↔ 장소 배정 |
devices + 장소 배정, SpotManagement(waypoint label 매핑) |
로봇팔·반려로봇도 같은 방식으로 devices.type만 추가하면 resolve 가능 |
| Hub 명령 포맷 |
MQTT_SCHEMA-HUB diorama/{device}/request {session_id, scenario_id, command, target, payload, timestamp}
|
범용 포맷이 이미 확정 |
| 보유 아이템 판정 |
task_options.required_product_id + 스토어 구매(store_purchase.go, orchestration/{sessionId}/store/owned) |
"이 상품을 가진 세션만" 조건이 선택지 단위로 이미 존재 → 규칙 조건으로 그대로 확장 |
| 테스트 경로 | 사용자 상세 "위치 테스트" 탭 (POST /admin/users/:id/position/test) |
recordLocationChange를 타므로 자동화 규칙도 함께 발화 → 별도 테스트 화면 불필요 |
⚠️ MQTT_SCHEMA-ROBOT_ARM,MQTT_SCHEMA-COMPANION_ROBOT은 아직 토픽명조차 비어 있는 템플릿이다. 이 두 디바이스는 스키마 확정 전까지 "토픽 + payload JSON 자유 입력"으로 받아둔다 (§5.3 참고).
| 컬럼 | 타입 | 설명 |
|---|---|---|
id |
TEXT PK | UUID v7 |
owner_type |
TEXT |
task · event · location — 규칙이 붙는 대상 종류 |
owner_id |
TEXT |
tasks.id / events.id / locations.id
|
task_status_filter |
TEXT |
owner_type='task'일 때: pending(기본, 아직 안 한 태스크만) · any
|
name |
TEXT | 어드민 표시용 이름 (예: "집 도착 — 반려로봇 인사 + SPOT 추종") |
trigger |
TEXT |
arrive(도착, 기본) · leave(이탈, 114 — 다른 장소로 이동 기록 시 직전 장소 기준) · wrong_item/right_item(물건 집음, 103, 태스크 전용) · option_selected(파이프라인 선택지, 118, 이벤트 전용) · pending_entered(태스크가 pending 이 된 순간, 152, 태스크 전용) · dwell (향후) |
dwell_seconds |
INTEGER |
trigger='dwell'일 때 체류 기준 초 |
condition_json |
TEXT(JSON) |
{"has_children": true|false|null, "first_visit_only": bool, "persona_ids": [], "required_product_ids": [], "required_products_mode": "all"|"any"} — null/빈 값은 "무관" |
fire_policy |
TEXT |
once_per_session (기본) · every_arrival
|
cooldown_seconds |
INTEGER |
every_arrival일 때 재발화 최소 간격 (기본 30) |
is_active |
INTEGER | 0/1 |
display_order |
INTEGER | 같은 owner 내 표시 순서 |
created_at, updated_at
|
TIMESTAMP |
장소는
owner_type으로 유도된다 —task→tasks.location_id,event→ 그 세션의user_locations.event_id가 가리키는 장소,location→ 자기 자신. 규칙 테이블에location_id를 중복 저장하지 않는다 (태스크가 다른 장소로 옮겨져도 규칙이 따라가도록).
| 컬럼 | 타입 | 설명 |
|---|---|---|
id |
TEXT PK | |
automation_id |
TEXT FK → arrival_automations (ON DELETE CASCADE) | |
device_type |
TEXT |
spot · robot_arm · companion · hub
|
action |
TEXT | SPOT: navigate_to/run_scenario/follow/return_home/stop (기존 SPOT_ACTIONS). Hub: command 값. 로봇팔/반려로봇: 자유 문자열 |
params_json |
TEXT(JSON) | SPOT: 기존 spot_params와 동일 구조 (destination은 실행 시 동적 계산). Hub: {target, scenario_id, payload}. 로봇팔/반려로봇: {topic, payload}
|
delay_ms |
INTEGER | 이전 액션 디스패치 후 대기 시간 (기본 0) |
display_order |
INTEGER | 실행 순서 |
| 컬럼 | 타입 | 설명 |
|---|---|---|
id |
TEXT PK | |
automation_id |
TEXT FK | |
user_id |
TEXT | 세션 ID (users.id) |
location_id |
TEXT | 발화 장소 |
matched_owner_type, matched_owner_id
|
TEXT | 어떤 태스크/이벤트 매핑 때문에 발화(또는 스킵)됐는지 |
fired_at |
TIMESTAMP | Vision 메시지 timestamp 기준 |
status |
TEXT |
fired · skipped · partial · failed
|
skip_reason |
TEXT |
condition_unmet · missing_item · task_completed · already_fired · cooldown · inactive · no_device
|
action_results_json |
TEXT(JSON) |
[{device_type, action, status, command_id, error, dispatched_at}] — ack 릴레이 수신 시 갱신 |
erDiagram
tasks ||--o{ arrival_automations : "owner_type=task"
events ||--o{ arrival_automations : "owner_type=event"
locations ||--o{ arrival_automations : "owner_type=location"
users ||--o{ user_tasks : assigned
users ||--o{ user_locations : assigned
user_locations }o--o| events : event_id
tasks }o--|| locations : location_id
arrival_automations ||--o{ arrival_automation_actions : contains
arrival_automations ||--o{ automation_runs : fired
users ||--o{ automation_runs : session
devices }o--o{ locations : assigned
vision/position/person 수신
└─ PublishSessionPosition()
└─ resolveSessionLocationForZone() (기존)
└─ recordLocationChange() == (prevLoc, true) (기존: 장소가 바뀐 순간만 true; 직전 장소도 반환)
├─ maybePublishArrivalEvent() (기존: 돌발 이벤트)
├─ onLeave(session, prevLoc, ts) ← 114: 직전 장소의 trigger='leave' 규칙을 아래와 같은
│ 흐름으로 평가 (LocationID=prevLoc, 도착보다 먼저 적재,
│ 태스크 자동 시작·이벤트 타이밍 등 부수 효과 없음)
└─ runArrivalAutomations(session, loc, ts) ← 신규
1. 이 세션·장소의 매핑을 수집
tasks : user_tasks ⋈ tasks WHERE tasks.location_id = loc (status 필터 적용)
event : user_locations(user, loc).event_id
location: loc 자체
→ (owner_type, owner_id) 목록으로 arrival_automations 활성 규칙 로드
(정렬: location 규칙 → event 규칙 → task 규칙(visit/display 순))
⚠️ 장소 규칙 우선(2026-10-06, Run() 내 게이트) — arrive 트리거에 location/
event 규칙과 task 규칙이 섞여 있으면, location/event 규칙을 먼저 평가·
디스패치하고 그 액션이 전부 종결(completed/failed/timeout/skipped)될
때까지 task 규칙 평가 자체를 미룬다(폴링, 최대 45초 — 넘으면 포기하고
진행). 단순 "정렬 순서"가 아니라 "장소가 먼저 끝나야 태스크가 시작"으로
강화된 것. leave·물건 집음·option_selected 트리거는 애초에 location과
task가 섞이지 않으므로 이 게이트가 안 걸린다
⚠️ 세션 단위 게이트(2026-10-07, location_gate.go) — 첫 도착은 보통 태스크
시딩보다 먼저 와서 그 평가엔 task 규칙이 없다. 그래서 도착에서 돈
location/event run 을 세션별로 기억하고, 뒤늦게 task 규칙만 도는 평가
(시딩 후 재평가·pending_entered·순차 후속)도 그 run 이 전부 종결될 때까지
(최대 45초) 기다린다. 수동 실행·leave·물건 집음·option_selected 는 안 기다림
2. 규칙별 조건 평가 (has_children / first_visit_only / persona / 보유 아이템)
보유 아이템: 세션의 구매 내역(store_purchases)에 required_product_ids가
전부(all) 또는 하나라도(any) 있어야 발화. 없으면 skipped: missing_item
3. fire_policy · cooldown 검사 (automation_runs 최근 행 조회)
4. 액션을 display_order 순으로 goroutine에서 delay_ms 두고 디스패치
spot → DispatchSpotCommand() 재사용 (세션 배정 장소의 spot 디바이스 resolve)
hub → diorama/{device}/request publish
lg → HA 전원/모드. params._lg_condition(2026-10-07)이 있으면 규칙
시작 때 가전 상태를 한 번 읽어(스냅샷) 맞을 때만 보내고, 안 맞으면
skipped: lg_condition_not_met. 모양은 발화 _lg_branches 한 줄과 같다
({appliance_id, feature(기본 power), op: is|is_not|unknown|any, value})
companion say + _lg_branches[i].lg_command(2026-10-07) → 발화 분기마다 가전 명령.
발화가 속한 동시 묶음 뒤에 "분기 가전 명령" 단계(action_id={say}#lg)를
끼우고, 발화 직전 고른 분기의 명령만 발화가 끝난 뒤 보낸다(없으면 skipped).
실행 로그에 branch·lg_command 기록, 재시도는 그 명령만 재발송
robot_arm / companion → params_json.topic 으로 payload publish (스키마 확정 전 임시)
5. automation_runs 기록 (skipped 포함), ack/response 릴레이 수신 시 결과 갱신
| 세션 | 장소 "집"에 배정된 것 | 보유 아이템 | 발화되는 규칙 |
|---|---|---|---|
| 지호 | 태스크 "조명 켜기" + 이벤트 "택배 도착" | 없음 | 이벤트 "택배 도착"의 규칙(로봇팔 전달)은 아이템 필요 → missing_item 스킵(= 즉시 종결로 간주). 이벤트 규칙이 종결된 뒤에야 태스크 규칙(반려로봇 인사) 평가 |
| 수아 | 태스크 "조명 켜기" | 간식 세트 | 이벤트가 배정 안 됐으므로 이벤트 규칙은 후보에도 안 들어감 → 장소 규칙이 없으면 태스크 규칙이 바로 평가(게이트 대상이 없음) |
| 민준 | 태스크 "조명 켜기"(이미 완료) | 간식 세트 |
task_status_filter=pending이면 후보 제외 → 아무것도 발화 안 함 (장소 규칙이 있으면 그것부터 전부 끝난 뒤에 평가하지만, 이 경우 애초에 태스크 후보가 없음) |
- 판정 소스는 세션의 스토어 구매 내역 =
orchestration/{sessionId}/store/owned와 동일한 집합. -
규칙 단위(
condition_json.required_product_ids)와 액션 단위(params_json.required_product_id, 선택) 두 층으로 둔다. 규칙 단위는 "아이템 없으면 규칙 전체 스킵", 액션 단위는 "이 액션만 건너뛰고 나머지는 진행" (예: 간식 아이템 있을 때만 로봇팔 전달, 반려로봇 인사는 항상). - 구매는 세션 중 언제든 일어나므로 발화 시점에 조회한다 (캐시 X).
once_per_session규칙이 아이템 부재로missing_item스킵된 경우는 "발화한 것"으로 치지 않아, 이후 아이템을 사고 다시 도착하면 발화된다.
- 세션에 배정된 장소(
user_locations)에 매핑된devices(해당type,is_active=1) 중 첫 번째. - 없으면 액션을
skipped: no device로 로그에 남기고 다음 액션은 계속 진행한다 (한 디바이스 부재가 전체 규칙을 막지 않도록).
모든 디바이스 명령(SPOT·로봇팔·반려로봇·Hub, 자동화/태블릿/선택지 출처 불문)은
internal/mqttx/device_queue.go의 디바이스 명령 큐를 거친다. 백엔드는
internal/devicequeue/ — SQS_ENDPOINT가 있으면 ministack SQS FIFO(device-commands.fifo
-
-dlq.fifo), 없으면 프로세스 내 메모리 큐(같은 계약).
Enqueue(job) → SQS FIFO (MessageGroupId = 디바이스 키: spot:{robotId} | hub | robot_arm:{topic} | companion:{topic})
consumer → Receive → delay_ms 대기 → MQTT 발행 → 완료 신호 대기(timeout_ms) → DeleteMessage → job.next 적재
- 디바이스 단위 직렬화: 같은 그룹은 앞 잡을 Delete하기 전까지 다음 잡이 나오지 않는다. 세션이 달라도 같은 SPOT이면 순서대로. (SQS는 한 Receive 배치 안에서 같은 그룹을 여러 개 내줄 수 있어 소비자도 그룹별 락으로 한 번 더 직렬화한다.)
-
규칙 안 액션 체인: 첫 액션만 큐에 넣고
job.next로 다음 액션을 실어, 앞 액션 완료 후 소비자가 다음을 적재한다 → 디바이스가 달라도 액션은 순차.delay_ms= "앞 액션 완료 후 대기". -
완료 신호: SPOT
robot/{id}/ack종결 status · Hubdiorama/{device}/response.result· 로봇팔/반려로봇은 스키마 확정 전까지params.duration_ms(기본 3초) 경과를 완료로 간주. -
타임아웃:
arrival_automation_actions.timeout_ms(기본 30초). 넘기면timeout으로 기록하고 다음으로 진행. 가시성 타임아웃을 delay+timeout+여유로 늘려 재전달을 막고, 소비자가 두 번 죽으면 DLQ. - 액션 상태:
queued → dispatched → completed | timeout | failed(+device_status에 ack 원문)이automation_runs.action_results_json에 갱신되고 백오피스 실행 로그·ministack 화면에서 보인다. - 우선순위(수동 > 자동)는 미구현 — 모두 같은 FIFO.
| Topic | 방향 | QoS | Retained | 설명 |
|---|---|---|---|---|
orchestration/{sessionId}/automation/fired |
S→C | 1 | X | 규칙 발화 알림 {automation_id, name, location_id, actions:[{device_type, action, command_id, status}]} — 태블릿이 "로봇이 움직입니다" 같은 안내를 띄우는 용도(선택) |
사이드바
├─ 태스크 관리 → 태스크 상세 (/tasks/:id)
│ ├─ 기본 정보 / 선택지(옵션별 SPOT 연동) (기존)
│ └─ [NEW] 도착 자동화 섹션 ← 규칙의 "주 편집 화면" ①
├─ 이벤트 관리 → 이벤트 상세 (/events/:id)
│ ├─ 기본 정보 / 선택지(옵션별 SPOT 연동) (기존)
│ └─ [NEW] 도착 자동화 섹션 ← 규칙의 "주 편집 화면" ②
├─ 장소 관리
│ └─ 장소 상세 (/locations/:id)
│ ├─ 기본 정보 / 태스크 / 콘텐츠 (기존)
│ └─ [NEW] 도착 자동화 탭
│ ├─ 장소 자체 규칙 (owner_type=location) 편집
│ └─ 이 장소의 태스크·이벤트에 붙은 규칙 **읽기 전용 집계** (클릭 → 해당 상세로)
│
└─ 로봇 연동 ← 기존 "SPOT 관리" 네비 트리를 이름 바꿔 확장
├─ SPOT 장소 관리 (/spot-management) (기존, waypoint 매핑)
├─ [NEW] 자동화 규칙 전체 (/automations) ← 장소·디바이스 축 read-mostly 목록
├─ [NEW] 자동화 실행 로그 (/automations/runs) ← automation_runs 타임라인
└─ 테스트 (/spot-test) (기존 + SPOT 위치 테스트)
왜 태스크/이벤트 상세를 주 편집 화면으로 두나 — 수행 여부를 가르는 건 "이 세션이 이
장소에서 무슨 태스크/이벤트를 하느냐"이므로, 규칙은 그 태스크/이벤트의 속성이다.
이미 태스크·이벤트 상세에 "선택지를 고르면 SPOT 명령"(SpotOptionFields)이 있어서, 바로
옆에 "도착하면 자동으로" 섹션이 붙으면 어드민이 한 화면에서 선택 시 동작 vs 도착 시 동작을
같이 설계할 수 있다. 장소 상세 탭은 "이 장소에서 일어날 수 있는 모든 자동화"를 한눈에 보는
집계 + 장소 공통 규칙 편집용. 전체 목록은 디바이스 축 역조회용 보조 뷰.
┌─ 도착 자동화 (이 태스크가 배정된 세션이 "집"에 도착하면) ──── [+ 규칙 추가] ─┐
│ │
│ ▾ 반려로봇 인사 + SPOT 추종 [활성 ●] [시험 발화 ▾] │
│ 트리거: 도착 · 대상: 미완료 태스크만 · 조건: 아이 동반 무관 · 세션당 1회 │
│ 필요 아이템: 없음 │
│ ┌────┬──────────┬──────────────┬───────────────────────┬────────┬──────┐ │
│ │ 순서│ 디바이스 │ 명령 │ 파라미터 │ 지연 │ │ │
│ ├────┼──────────┼──────────────┼───────────────────────┼────────┼──────┤ │
│ │ 1 │ 반려로봇 │ greet │ {"voice":"hello"} │ 0 ms │ ↑↓ 🗑 │ │
│ │ 2 │ SPOT │ follow │ mode=start │ 1500ms │ ↑↓ 🗑 │ │
│ └────┴──────────┴──────────────┴───────────────────────┴────────┴──────┘ │
│ [+ 액션 추가] │
│ │
│ ▸ (아이 동반) 로봇팔 간식 전달 [활성 ○] [시험 발화 ▾] │
│ 필요 아이템: 🛒 간식 세트 (all) │
└───────────────────────────────────────────────────────────────────────────┘
- 섹션 헤더에 규칙이 발화되는 장소를 명시 (
tasks.location_id/ 이벤트는 "배정된 장소") - 규칙 카드: 이름 · 트리거 · (태스크) 상태 필터 · 조건 요약 · 필요 아이템 칩(스토어 상품 다중 선택, all/any) · 발화 정책 · 활성 토글 · 시험 발화(이 태스크/이벤트가 배정된 활성 세션만 목록에 뜸 → 선택 시 즉시 실행,
automation_runs에manual=true로 기록) - 같은 컴포넌트(
ArrivalAutomationEditor)를 장소 상세 탭의 "장소 규칙"에도 재사용 - 액션 테이블: 순서 변경(↑↓), 삭제, 행 클릭 시 인라인/모달 편집
- 마지막 실행 결과 배지 (최근 run의 status) — 로그 화면으로 링크
| 컬럼 | 설명 |
|---|---|
| 주인 |
태스크 · 조명 켜기 / 이벤트 · 택배 도착 / 장소 · 집 칩 — 클릭 → 해당 상세 |
| 장소 | 유도된 장소 |
| 규칙명 | |
| 트리거 / 조건 | 요약 (필요 아이템 포함) |
| 액션 | 디바이스 칩 나열 (SPOT 반려로봇 …) |
| 활성 | 토글 (여기서도 바로 on/off) |
| 최근 발화 | 시각 + 상태 배지 |
필터: 주인 종류(태스크/이벤트/장소) · 장소 · 디바이스 타입 · 활성 여부.
| 디바이스 | 입력 | 비고 |
|---|---|---|
| SPOT |
SpotOptionFields 그대로 재사용 (spot_action / spot_destination_mode / scenario·variables·follow mode) |
navigate_to의 destination은 세션 장소 → waypoint label로 동적 계산(own/next). 2026-09-16부터 destination_mode=waypoint이면 편집기에서 SPOT의 retained waypoints 목록(+장소 매핑)에서 고른 params.destination을 그대로 사용 — SPOT 테스트 화면과 동일 |
| Hub |
command, target, scenario_id, payload(JSON) |
diorama/{device}/request 포맷 확정됨 |
| 로봇팔 · 반려로봇 |
토픽 문자열 + payload JSON 자유 입력 + |
스키마 확정되면 드롭다운/타입드 폼으로 교체. session_id는 서버가 자동 주입 |
공통: delay_ms 숫자 입력, 순서, 필요 아이템(선택) — 이 액션만 해당 상품 보유 시 실행.
시각 세션(페르소나) 장소 주인 규칙 상태 액션 결과
13:36:23 지호 (abc123) 집 태스크·조명 켜기 반려로봇+SPOT fired 반려로봇 ✓ · SPOT ACCEPTED→SUCCESS
13:36:23 지호 (abc123) 집 이벤트·택배 도착 로봇팔 간식 전달 skipped missing_item (간식 세트)
13:35:50 지호 (abc123) 마트 태스크·장보기 로봇팔 상품 전달 skipped no device (robot_arm)
13:20:01 민준 (ghi789) 집 태스크·조명 켜기 반려로봇+SPOT skipped task_completed (status filter)
13:20:01 수아 (def456) 집 집 도착 — 반려로봇+SPOT skipped already_fired (once_per_session)
- 필터: 세션 · 장소 · 규칙 · 상태 · 기간
- 행 펼치면
action_results_json원문 + 발행한 MQTT payload - 사용자 상세(
/task-completions/:sessionUuid)에도 "자동화" 탭으로 같은 데이터를 세션 필터 고정으로 노출
| # | 이슈 | 제안 |
|---|---|---|
| 1 |
수동 vs 자동 SPOT 명령 충돌 — 도착 규칙이 follow를 보낸 직후 사용자가 태블릿에서 navigate_to를 고르면? |
수동(사용자 선택) > 자동. 자동 명령은 stop 외엔 진행 중 명령을 끊지 않고 큐 뒤에 붙는다. 역으로 수동 명령은 대기 중인 자동 명령을 건너뛸 수 있게 SpotQueue에 source 필드 추가 |
| 2 |
every_arrival 폭주 — Vision이 집↔마트 경계에서 깜빡이면 도착이 연달아 찍힘 |
규칙별 cooldown_seconds 기본 30초. once_per_session을 기본값으로 |
| 3 | 디바이스 부재 — 세션 장소에 해당 타입 디바이스가 없을 때 | 조용히 스킵하지 않고 automation_runs에 skipped: no device로 남김. 규칙 카드에 경고 배지 |
| 3b | 태스크 완료 후 재도착 — 태스크를 마친 뒤 같은 장소에 다시 오면? |
task_status_filter=pending(기본)이면 후보에서 빠짐 → 완료 후엔 발화 안 함. 완료 후에도 돌아가길 원하면 any. 이벤트는 event_published_at과 무관하게 도착마다 후보(fire_policy로 제어) |
| 3c | 한 도착에 규칙 여러 개 — 태스크 2개 + 이벤트 1개가 각각 SPOT 명령을 갖고 있으면? | 전부 발화하되 순서는 location → event → task(visit/display 순). SPOT은 SpotQueue가 직렬화. 상충(follow vs navigate_to)은 어드민 책임으로 두고, 규칙 전체 목록에서 같은 장소·같은 디바이스에 규칙이 여러 개면 |
| 4 |
zone_id='home' 인스턴스 선택 — 첫 방문은 STEP이 가장 앞선 집, 두 번째부터는 첫 인스턴스로 돌아가지 않고 나머지 안에서 순환 (2026-09-27) |
자동화도 동일 인스턴스 기준으로 first_visit_only 판단. 집-아침/집-저녁이 같은 규칙을 공유하려면 장소 인스턴스마다 규칙을 따로 두거나, 향후 "zone 단위 규칙" 옵션 검토 |
| 5 | 세션 종료 시 | 세션 종료(end-session) 시 해당 세션의 대기 중 자동 액션(delay 중인 goroutine) 취소 |
| 6 | 아이템 조건과 once_per_session |
missing_item 스킵은 발화로 치지 않음 → 구매 후 재도착 시 발화. 시험 발화는 아이템 조건을 무시할지 토글 제공 |
| 7 | 서버 재기동 | delay 중이던 액션은 유실 허용 (전시 현장 특성상 수 초 단위라 영속화 불필요) |
| 9 | 액션 병렬 실행 (2026-08-30, 반려로봇 채널 모델) |
arrival_automation_actions.parallel=1 이면 앞 액션의 완료를 기다리지 않고 앞 액션과 동시에 적재. 연속된 병렬 액션은 한 단계로 묶이고 단계 전체가 종결돼야 다음 단계로. 반려로봇 명령은 큐 그룹이 companion:{robot}:{채널}이라 locomotion(하체)만 직렬, audio/led/gaze/projection은 실제로 동시에 나간다(예: 제스처 ∥ 발화). 첫 액션의 parallel은 무시. 병렬 그룹 카운트는 프로세스 메모리(재기동 시 그 단계의 다음 적재 유실 — 7번과 동일 수준) |
| 9-1 | 기다리지 않는 단계 (2026-10-04) |
params_json._no_wait=true 면 같이 출발하되 아무도 그 단계의 완료를 기다리지 않는다. 드론 배송처럼 오래 걸리는 연출이 뒤 단계(로봇팔·SPOT)를 붙잡지 않게 하려고 넣었다 — 전에는 Hub(드론) ∥ SPOT 묶음이 전부 끝나야 로봇팔이 나갔고, 드론 ack 가 타임아웃이면 체인이 멈췄다. 서버는 액션을 buildChainStages 로 "한꺼번에 적재할 묶음"으로 나누고, 그 묶음의 기다리는 잡(= _no_wait 아닌 것)이 전부 종결될 때만 다음 묶음을 적재한다. 묶음이 통째로 _no_wait 면 다음 묶음을 같이 적재한다. 기다리지 않는 단계는 실패해도 체인을 멈추지 않고(내부적으로 _continue_on_failure 취급), 수동 재시도는 그 단계만 다시 보낸다(RetryRunFrom). 같은 로봇에 뒤 단계가 또 명령을 넣으면 그 로봇 큐에서 차례를 기다린다 — 병렬 금지 규칙(§같은 로봇 동시 금지)은 묶음 안에서만 검사한다. 어드민: 단계 카드 "뒤 단계가 기다리지 않음" 체크, 다이어그램은 점선 가지로 빠져나가게 그린다. 마이그레이션 없음 — parallel(100) 과 달리 컬럼이 아니라 params_json 예약 키다(_continue_on_failure 와 같은 자리) |
| 8 | 태스크 자동 완료 (2026-08-30) — 태스크는 태블릿 선택 팝업 없이 도착 자동화로만 실행된다 | owner_type=task 규칙이 실제 발화(시험 발화 제외)하면 각 액션이 user_task_commands에 queued로 기록되고, 큐가 액션을 종결(ack/timeout/failed)할 때마다 갱신 → 전부 종결되면 user_tasks를 자동 completed 하고 task/complete와 같은 후속 발행. 실패·타임아웃도 종결로 센다. 백오피스 세션 상세(위치별 태스크)에 명령별 상태 표시. 자동 완료 후 태블릿 task/complete가 또 와도 포인트 재지급 없음 |
| 10 |
pending_entered 트리거 (2026-10-06, 152) — 기존 트리거(arrive/leave/물건 집음/선택지)와 동일한 규칙 모델(UI·조건·발화 정책·액션·실행 로그·시험 발화)로, 트리거 값만 다르다. 태스크 전용(owner_type='task') — 장소·이벤트에는 pending 상태 개념이 없다 |
"내 차례" = 관람객이 그 장소에 있고 앞 태스크에 막히지 않음 (2026-10-10). 발화 지점 세 곳: ① 세션 시딩 직후 — 관람객이 지금 있는 장소의 pending 이면서 앞 태스크에 막히지 않은 태스크만(seedPendingEnteredTargets). 예전엔 코스 전체 장소를 훑어, 공원 첫 태스크의 pending_entered 가 관람객이 집에 있을 때 돌아 태스크가 자동 완료되고 공원 이벤트까지 발행됐다(세션 01a125b1). ② 장소 도착(onArrival → fireReadyPendingEntered, 도착 평가 뒤라 장소 연출 게이트를 탄다). ③ 순차 실행(117) 체인에서 앞 태스크가 종결돼 대기가 풀리는 순간(OnUserTaskFinished, 관람객이 그 장소에 있을 때). 중복 발화는 규칙의 fire_policy 가 막는다. 용도: detailed 처럼 태블릿의 명시적 task/start 없이 자동화로만 시작되는 태스크 타입이, 장소 도착(arrive)을 다시 기다리지 않고도 "내 차례가 된 순간" 바로 로봇 명령을 받을 수 있다 — 관람객이 이미 그 장소에 서 있는 채로 순차 체인의 두 번째 이후 태스크가 시작되는 경우, 그 사람은 다시 "도착"하지 않으므로 arrive 만으로는 영영 발화하지 않았다. 구체적으로 언제 쓸지는 운영자가 자동화 규칙을 만들며 정한다. 추적: arrive 와 동일하게 user_task_commands 로 추적해 액션이 전부 종결되면 태스크를 자동 completed 한다(isTrackableTaskTrigger) — leave·물건 집음처럼 "연출·후처리"가 아니라 "이 태스크를 수행하라"는 명령이기 때문 |
-
마이그레이션 + sqlc — §3 테이블 3개 (
-- +goose Up지시자 필수). 매핑 수집용 쿼리:ListUserTasksAtLocation(user, location), 기존GetUserLocationByUserAndLocation재사용 -
서버 실행 엔진 —
internal/mqttx/automation.go:runArrivalAutomations()+recordLocationChange훅. 매핑 수집 → 규칙 로드 → 아이템/상태 조건 → 디스패치. SPOT만 먼저, 로봇팔/반려로봇/Hub는 generic publish -
Admin REST —
/admin/{tasks|events|locations}/:id/automationsCRUD(공통 핸들러, owner_type만 다름),/admin/automations,/admin/automations/runs,POST /admin/automations/:id/fire?user_id=(시험 발화) -
백오피스 —
ArrivalAutomationEditor공용 컴포넌트 → 태스크 상세 → 이벤트 상세 → 장소 상세 탭(장소 규칙 + 집계) → 전체 목록 → 실행 로그 -
문서 —
MQTT_SCHEMA-ORCHESTRATION_SERVER에automation/fired토픽, 이 문서 상태를 "확정"으로 - (스키마 확정 후) 로봇팔·반려로봇 타입드 폼 교체
- 로봇팔 · 반려로봇 MQTT 스키마 (각 팀 작성 대기) — 확정 전까지 JSON 자유 입력
-
leave트리거 — 2026-09-12 구현(114). 타임아웃 기반이 아니라 다른 장소 도착 = 직전 장소 이탈로 판정한다. 따라서 마지막 장소(보통 home)는 세션 종료로는 이탈이 발화하지 않음 — 세션 종료 시 발화가 필요하면 §6-5(종료 시 대기 액션 취소)와 함께 결정 -
dwell트리거 실제 수요 확인 -
orchestration/{sessionId}/automation/fired를 태블릿이 실제로 쓸지 (FE 팀 확인) - 장소 인스턴스 단위 규칙 vs zone 단위 규칙 (§6-4)
- 태스크 선택지(option)별 도착 자동화가 필요한지 — 현재는 태스크 단위. "이 선택지를 이미 고른 세션이 도착하면"은
task_option_selections조건으로 확장 가능 - 이벤트 규칙의 발화 시점 — 도착 즉시 vs 돌발 이벤트 팝업(
maybePublishArrivalEvent) 직후. 로봇팔 전달처럼 "이벤트 연출"인 경우 팝업과 타이밍을 맞춰야 할 수 있음 - 아이템 "소모" 여부 — 로봇팔이 간식을 전달하면 보유 목록에서 차감해야 하는지 (현재
store/owned는 누적 보유만 표현)
- Google ADK 검토 (프레임워크·모델 비교)
- 페르소나 + 상황 카드 모델 (2차 시나리오 구동 방식)
- Mac Studio 로컬 LLM + Claude Opus 하이브리드 라우팅 전략
- 개발 일정·할 일 점검표 (단계별 진행 점검)
- 로봇 Adapter 개발 온보딩 (HA·MQTT 계정 등록, 토픽 적용, 테스트 송수신, 상태 확인)
- LG ThinQ 가전 연동 (HA 통합 · REST 데이터 취득 · 제어)
- 장소 도착 자동화 (태스크/이벤트/장소 규칙 → SPOT·로봇팔·반려로봇·Hub 자동 명령)
- 엔딩 화면 데이터 출처 점검 (차량 3필드 · 세션 종료 엔딩 5화면)