MQTT_SCHEMA COMPANION_ROBOT - zero1ne-26/doc GitHub Wiki

λ°˜λ €λ‘œλ΄‡ μ œμ–΄ MQTT μŠ€ν‚€λ§ˆ

μΉ΄μ΄μŠ€νŠΈνŒ€(REX) μž‘μ„±. λͺ¨λ“  λ©”μ‹œμ§€λŠ” CloudEvents 1.0 envelope으둜 감싼닀.


λͺ©μ°¨


κ°œμš”

ν•­λͺ© λ‚΄μš©
μ œκ³΅κ°’ session_id (μ˜€μΌ€μŠ€νŠΈλ ˆμ΄μ…˜ μ„œλ²„μ—μ„œ λ°œκΈ‰)
μž‘μ„± 주체 μΉ΄μ΄μŠ€νŠΈνŒ€
μ΅œμ’… μˆ˜μ •μΌ 2026-09-29
robot_id REX
envelope CloudEvents 1.0 (specversion, type, source, id, time, subject, data)

2026-09-29 λ³€κ²½ μš”μ•½: λ‘œλ΄‡μ„ 바퀴 ꡬ동(μ£Όν–‰) 으둜 λ³€κ²½ν•˜κ³  REXκ°€ μ‹€μ œ μˆ˜ν–‰ν•˜λŠ” capability만 μœ μ§€ν•˜λ„λ‘ μ •λ¦¬ν–ˆλ‹€. (1) capabilityλ₯Ό sayΒ·gotoΒ·gesture(+μ•ˆμ „ stop)둜 μ œν•œ(λ‚˜λ¨Έμ§€ μ‚­μ œ), (2) 이쑱보행 μ „μ œμ˜€λ˜ locomotion 채널을 νκΈ°ν•˜κ³  물리적으둜 λΆ„λ¦¬λœ base(바퀴 μ£Όν–‰)Β·body(머리·상체 제슀처)Β·audio(λ°œν™”) 3μ±„λ„λ‘œ μž¬μ •μ˜ β€” 채널 κ°„ 병렬 μ‹€ν–‰ (gotoΒ·gestureΒ·say λ™μ‹œ κ°€λŠ₯), (3) 이쑱보행 μ „μš© ν•­λͺ©(unstable_posture μ‚¬μœ , "μ „μ‹  RL" μ„œμˆ ) μ‚­μ œ, (4) say paramsμ—μ„œ emotionΒ·interrupt 제거, (5) λ°œν™” ν™”μžλ₯Ό μ„Έμ…˜ AI 페λ₯΄μ†Œλ‚˜ κΈ°μ€€μœΌλ‘œ κ²°μ •(μ„œλ²„κ°€ voice_id μ£Όμž…). 빈/미지원 voice_idλŠ” 거절 없이 κΈ°λ³Έ ν™”μž sohee둜 λ°œν™”ν•œλ‹€.

ν† ν”½ λͺ©λ‘

ꡬ뢄 Topic λ°©ν–₯ μ„€λͺ…
REQUEST robot/REX/command μ„œλ²„ β†’ λ°˜λ €λ‘œλ΄‡ λ°˜λ €λ‘œλ΄‡ μ œμ–΄ λͺ…λ Ή 전솑
RESPONSE robot/REX/ack λ°˜λ €λ‘œλ΄‡ β†’ μ„œλ²„ λ°˜λ €λ‘œλ΄‡ μ œμ–΄ κ²°κ³Ό μˆ˜μ‹ 

채널 λͺ¨λΈ

λ°˜λ €λ‘œλ΄‡μ€ 물리 μžμ›μ„ 3개 μ±„λ„λ‘œ λ‚˜λˆ„μ–΄ κ΄€λ¦¬ν•œλ‹€. capability 값에 따라 점유 채널이 κ²°μ •λ˜λ©°, REQUEST νŽ˜μ΄λ‘œλ“œμ— 채널을 λͺ…μ‹œν•˜μ§€ μ•ŠλŠ”λ‹€.

채널 물리 μžμ› μ‚¬μš© capability λ™μ‹œ μ‹€ν–‰
base 바퀴 μ£Όν–‰λΆ€ goto 채널 λ‚΄ 1κ°œμ”©(순차)
body 머리·상체(제슀처 λͺ¨ν„°) gesture 채널 λ‚΄ 1κ°œμ”©(순차)
audio μŠ€ν”Όμ»€ say 병렬

μ„œλ‘œ λ‹€λ₯Έ 채널은 λ³‘λ ¬λ‘œ μ‹€ν–‰λœλ‹€. 즉 μ£Όν–‰(goto)·제슀처(gesture)Β·λ°œν™”(say)λŠ” λ™μ‹œμ— μˆ˜ν–‰ν•  수 있고, 같은 채널에 λ“€μ–΄μ˜¨ λͺ…λ Ήλ§Œ 순차 μ²˜λ¦¬λœλ‹€.

채널별 κ·œμΉ™

base β€” μ£Όν–‰ (채널 λ‚΄ 순차)

바퀴 μ£Όν–‰λΆ€λŠ” ν•œ λ²ˆμ— ν•œ λͺ©μ μ§€λ‘œ μ΄λ™ν•œλ‹€. μ„œλ²„λŠ” 직전 goto의 completed λ₯Ό ν™•μΈν•œ λ’€ λ‹€μŒ gotoλ₯Ό λ³΄λ‚΄λŠ” 것을 κΈ°λ³Έ νλ¦„μœΌλ‘œ ν•œλ‹€. μˆ˜ν–‰ 쀑 λ„μ°©ν•œ gotoλŠ” κ±°μ ˆν•˜μ§€ μ•Šκ³  큐에 λ³΄κ΄€ν–ˆλ‹€κ°€ 순차 μ‹€ν–‰ν•˜λ©°, expires_at 이 κ²½κ³Όν•˜λ©΄ νμ—μ„œ κΊΌλ‚Ό λ•Œ rejected(expired) 둜 νšŒμ‹ ν•œλ‹€. priority: safety λͺ…λ Ήκ³Ό stop 은 μ§„ν–‰ 쀑 주행을 μ¦‰μ‹œ μ„ μ ν•œλ‹€.

body β€” 제슀처 (채널 λ‚΄ 순차)

머리·상체 μ œμŠ€μ²˜λŠ” ν•œ λ²ˆμ— ν•˜λ‚˜μ”© 순차 μ‹€ν–‰ν•œλ‹€. base μ£Όν–‰κ³ΌλŠ” 독립 μ±„λ„μ΄λ―€λ‘œ 이동 쀑에도 제슀처λ₯Ό λ³‘λ ¬λ‘œ μˆ˜ν–‰ν•  수 μžˆλ‹€.

audio β€” λ°œν™” (병렬)

say λŠ” μ£Όν–‰Β·μ œμŠ€μ²˜ 쀑에도 병렬 μˆ˜ν–‰ κ°€λŠ₯ν•˜λ‹€. 같은 채널에 say κ°€ 겹쳐 λ„μ°©ν•˜λ©΄ λ‘œλ΄‡ ν‘œν˜„ 계측이 κ²°ν•© 방식(μˆœμ°¨Β·μƒλž΅)을 자율 νŒλ‹¨ν•΄ μ‹€ν–‰ν•œλ‹€.


REQUEST

Topic: robot/REX/command

Payload

{
  "session_id": "string",
  "command_id": "string",
  "robot_id": "REX",
  "capability": "string",
  "params": {},
  "priority": "operator",
  "expires_at": "2026-08-22T10:17:30.000Z",
  "context": {
    "scenario_id": "string",
    "step_index": 0,
    "reason": "string",
    "utterance_hint": "string",
    "must_complete": true
  }
}
ν•„λ“œ νƒ€μž… ν•„μˆ˜ μ„€λͺ…
session_id string Y μ˜€μΌ€μŠ€νŠΈλ ˆμ΄μ…˜ μ„œλ²„μ—μ„œ λ°œκΈ‰ν•˜λŠ” μ„Έμ…˜ μ‹λ³„μž
command_id string Y μ„œλ²„κ°€ λΆ€μ—¬ν•˜λŠ” λͺ…λ Ή μ‹λ³„μž(ULID ꢌμž₯). RESPONSE에 κ·ΈλŒ€λ‘œ echo
robot_id string Y λŒ€μƒ λ‘œλ΄‡. REX κ³ μ •. 뢈일치 μ‹œ rejected(robot_id_mismatch)
capability string Y μ•„λž˜ whitelist κ°’. 미등둝 μ‹œ rejected(capability_not_whitelisted:{name})
params object N capability별 νŒŒλΌλ―Έν„°
priority string N safety | operator (κΈ°λ³Έ operator). safetyλŠ” μ§„ν–‰ 쀑 λ™μž‘μ„ 선점
expires_at string N RFC3339 UTC. νμ—μ„œ κΊΌλ‚Ό λ•Œ κ²½κ³Ό μ‹œ rejected(expired)
context.scenario_id string N μ‹œλ‚˜λ¦¬μ˜€ μ‹λ³„μž
context.step_index number N μ‹œλ‚˜λ¦¬μ˜€ λ‚΄ μŠ€ν… 순번
context.reason string N λͺ…λ Ή μ˜λ„(λ‘œλ΄‡μ΄ ν‘œν˜„μ„ μž¬ν•΄μ„ν•˜λŠ” κ·Όκ±°)
context.utterance_hint string N λ°œν™” μ œμ•ˆ(say). λ‘œλ΄‡μ΄ κ·ΈλŒ€λ‘œ 읽지 μ•Šκ³  자기 말투둜 μž¬ν•΄μ„
context.must_complete boolean N trueλ©΄ RESPONSE의 거절 μ‚¬μœ ν‘œμ—μ„œ ν—ˆμš©λœ μ‚¬μœ  μ™Έμ—λŠ” λ°˜λ“œμ‹œ μˆ˜ν–‰

capability whitelist

REXκ°€ μ§€μ›ν•˜λŠ” capabilityλŠ” μ•„λž˜ 4κ°œλ‹€. μ‹€μ œ ν™œμ„± λͺ©λ‘μ€ robot/REX/capabilities(retained)에 μ„ μ–Έλ˜λ©°, μ‹œλ‚˜λ¦¬μ˜€λŠ” ν•΄λ‹Ή 토픽에 μ„ μ–Έλœ κ°’λ§Œ μ‚¬μš©ν•œλ‹€.

capability 채널 params μ„€λͺ…
stop μ „ 채널 channel, command_id, scenario_id (λͺ¨λ‘ 선택) μ •μ§€Β·μ·¨μ†Œ. λ―Έμ§€μ • μ‹œ μ „ 채널 쀑단 (μ•ˆμ „μƒ 항상 유효)
say audio text, voice_id λ°œν™”. ν™”μžλŠ” voice_id둜 선택(Β§say voice 선택)
goto base x,y,yaw,velocity_max λ˜λŠ” zone_id μ§€μ • μœ„μΉ˜λ‘œ μ£Όν–‰
gesture body name, repeat 제슀처 μž¬μƒ

stop의 channel 은 base | body | audio 쀑 ν•˜λ‚˜λ₯Ό μ§€μ •ν•  수 있고, μƒλž΅ μ‹œ μ „ 채널을 μ€‘λ‹¨ν•œλ‹€.

say voice 선택

say λͺ…령은 params.voice_id둜 λ°œν™” ν™”μžλ₯Ό μ„ νƒν•œλ‹€. voice_idλŠ” say capabilityμ—λ§Œ μ μš©ν•œλ‹€.

voice_id TTS speaker μ„€λͺ…
sohee Sohee Qwen CustomVoice Sohee (REX ν˜„μž¬ κΈ°λ³Έ ν™”μž)
serena Serena Qwen CustomVoice Serena
vivian Vivian Qwen CustomVoice Vivian
eric Eric Qwen CustomVoice Eric

ν™”μž κ²°μ • 방식 β€” μ„Έμ…˜ AI 페λ₯΄μ†Œλ‚˜ κΈ°μ€€. ν•œ μ„Έμ…˜μ—λŠ” ν•˜λ‚˜μ˜ AI 페λ₯΄μ†Œλ‚˜ (orchestration/{sessionId}/me 의 ai_companion)κ°€ λ§€ν•‘λ˜λ©°, λ°œν™” ν™”μžλŠ” κ·Έ 페λ₯΄μ†Œλ‚˜μ— 따라 κ²°μ •λœλ‹€. μ˜€μΌ€μŠ€νŠΈλ ˆμ΄μ…˜ μ„œλ²„λŠ” say λͺ…령을 생성할 λ•Œ μ„Έμ…˜ 페λ₯΄μ†Œλ‚˜μ˜ ν™”μžλ₯Ό params.voice_id 에 μ±„μ›Œ μ „μ†‘ν•œλ‹€.

voice_id μš°μ„ μˆœμœ„:

  1. λͺ…령에 λͺ…μ‹œλœ params.voice_id
  2. μ„Έμ…˜ 페λ₯΄μ†Œλ‚˜μ˜ κΈ°λ³Έ voice_id
  3. REX κΈ°λ³Έ ν™”μž (ν˜„μž¬ sohee)

REXλŠ” 받은 voice_idλ₯Ό ν•΄λ‹Ή say λͺ…λ Ήμ—λ§Œ μ μš©ν•΄ μž¬μƒν•˜λ©°, 페λ₯΄μ†Œλ‚˜β†”ν™”μž 맀핑을 자체 λ³΄κ΄€ν•˜μ§€ μ•ŠλŠ”λ‹€(λ¬΄μƒνƒœ). voice_idλŠ” 선택 ν•„λ“œμ΄κ³  μ†Œλ¬Έμžλ‘œ 보낸닀. λΉ„μ–΄ μžˆκ±°λ‚˜ 미지원 값이면 κ±°μ ˆν•˜μ§€ μ•Šκ³  κΈ°λ³Έ ν™”μž(sohee)둜 λ°œν™”ν•œλ‹€(값은 μ†Œλ¬Έμžλ‘œ μ •κ·œν™” ν›„ 비ꡐ). ν™”μž 선택은 이후 λͺ…λ Ήμ˜ κΈ°λ³Έ ν™”μžλ₯Ό λ°”κΎΈμ§€ μ•ŠλŠ”λ‹€.

페λ₯΄μ†Œλ‚˜λ³„ voice_id λ§€ν•‘κ°’κ³Ό μ„œλ²„ μ£Όμž… λ‘œμ§μ€ μ˜€μΌ€μŠ€νŠΈλ ˆμ΄μ…˜ μ„œλ²„/페λ₯΄μ†Œλ‚˜ μŠ€ν‚€λ§ˆ μ—μ„œ κ΄€λ¦¬ν•œλ‹€(§확인 ν•„μš” 사항). ν˜„μž¬ 페λ₯΄μ†Œλ‚˜ 객체(orchestration/ai-companion/list)μ—λŠ” voice_id ν•„λ“œκ°€ μ—†μ–΄ μΆ”κ°€κ°€ ν•„μš”ν•˜λ‹€.

μ˜ˆμ‹œ

{
  "session_id": "abc123",
  "command_id": "01K3QF8XW2N4V7B0YJ5R6T8ZAB",
  "robot_id": "REX",
  "capability": "gesture",
  "params": { "name": "κ°•ν•œ λ„λ•μž„", "repeat": 1 },
  "priority": "operator",
  "expires_at": "2026-08-22T10:17:30.000Z",
  "context": {
    "scenario_id": "SCN-WELCOME-03",
    "step_index": 2,
    "reason": "κ΄€λžŒκ° λΆ€μŠ€ μ§„μž…",
    "must_complete": true
  }
}
say + voice_id μ˜ˆμ‹œ
{
  "session_id": "abc123",
  "command_id": "01KVOICE00000000000000001",
  "robot_id": "REX",
  "capability": "say",
  "params": {
    "text": "μ•ˆλ…•ν•˜μ„Έμš”. μ €λŠ” REXμ˜ˆμš”!",
    "voice_id": "sohee"
  },
  "priority": "operator",
  "expires_at": "2026-09-20T07:00:00.000Z",
  "context": {
    "scenario_id": "SCN-VOICE-TEST-01",
    "step_index": 1,
    "reason": "REX μŒμ„± ν™”μž 선택 ν…ŒμŠ€νŠΈ",
    "must_complete": true
  }
}

RESPONSE

Topic: robot/REX/ack

λͺ…λ Ή 1건당 μ΅œμ†Œ 2회 λ°œν–‰ν•œλ‹€(μ ‘μˆ˜ + μ’…λ£Œ).

Payload

{
  "session_id": "string",
  "command_id": "string",
  "robot_id": "REX",
  "status": "accepted",
  "reason": null,
  "result": null,
  "timestamp": "2026-08-22T10:16:00.220Z"
}
ν•„λ“œ νƒ€μž… ν•„μˆ˜ μ„€λͺ…
session_id string Y REQUEST의 값을 κ·ΈλŒ€λ‘œ echo
command_id string Y REQUEST의 값을 κ·ΈλŒ€λ‘œ echo
robot_id string Y REX
status string Y accepted | queued | started | completed | failed | rejected
reason string | null N failed / rejected μ‹œ μ‚¬μœ  μ½”λ“œ
result object | null N completed μ‹œ κ²°κ³Ό (예: {"duration_s": 4.1})
timestamp string Y RFC3339 UTC, λ°€λ¦¬μ΄ˆ

status

κ°’ 의미
accepted λͺ…λ Ή μˆ˜μ‹ Β·ν νˆ¬μž… (μˆ˜μ‹  ν›„ 200 ms 이내 λ°œν–‰)
queued λŒ€μƒ 채널(baseΒ·body)이 μ‚¬μš© 쀑이라 λŒ€κΈ°μ—΄μ— 보관. μ•ž λ™μž‘ μ’…λ£Œ ν›„ started λ°œν–‰
started μ‹€μ œ μ‹€ν–‰ μ‹œμž‘
completed 정상 μ™„λ£Œ. 같은 μ±„λ„μ˜ λ‹€μŒ λͺ…λ Ή 전솑 트리거둜 μ‚¬μš©
failed μ‹€ν–‰ 쀑 μ‹€νŒ¨
rejected μˆ˜ν–‰ν•˜μ§€ μ•ŠμŒ

λŒ€μƒ 채널이 λΉ„μ–΄ 있으면 accepted β†’ started β†’ completed, μ‚¬μš© 쀑이면 accepted β†’ queued β†’ started β†’ completed 순으둜 λ°œν–‰λœλ‹€.

reason (failed / rejected)

κ°’ 상황 must_complete λͺ…령에 μ‚¬μš©
schema_invalid νŽ˜μ΄λ‘œλ“œ 규격 μœ„λ°˜ κ°€λŠ₯
capability_not_whitelisted:{name} 미지원 capability κ°€λŠ₯
robot_id_mismatch λŒ€μƒ λ‘œλ΄‡ 뢈일치 κ°€λŠ₯
expired expires_at κ²½κ³Ό (λŒ€κΈ° 쀑 만료 포함) κ°€λŠ₯
queue_full:{channel} ν•΄λ‹Ή 채널 λŒ€κΈ°μ—΄ 초과 κ°€λŠ₯
estop_engaged / estop_fence 비상정지 μƒνƒœ κ°€λŠ₯
unsafe_proximity μ‚¬λžŒμ— κ·Όμ ‘ν•΄ 이동 λΆˆκ°€ κ°€λŠ₯
obstacle_blocked 경둜 물리적 차단 κ°€λŠ₯
low_battery 배터리 μž„κ³„ μ΄ν•˜ κ°€λŠ₯
preempted_by_safety priority: safety λͺ…령에 선점됨 β€”
cancelled / cancelled_by_stop stop으둜 μ·¨μ†Œλ¨ β€”

must_complete: true λͺ…령은 μœ„ ν‘œμ—μ„œ "κ°€λŠ₯"으둜 ν‘œμ‹œλœ μ‚¬μœ λ‘œλ§Œ κ±°μ ˆν•œλ‹€. κ΄€λžŒκ°κ³Ό ꡐ감 μ€‘μ΄λΌλŠ” μ΄μœ λ‘œλŠ” κ±°μ ˆν•˜μ§€ μ•ŠμœΌλ©°, ν˜„μž¬ λ°œν™”λ₯Ό λ¬Έμž₯ λ‹¨μœ„λ‘œ λ§ˆλ¬΄λ¦¬ν•œ λ’€(μ΅œλŒ€ 3 s) startedλ₯Ό νšŒμ‹ ν•˜κ³  μˆ˜ν–‰ν•œλ‹€.

μ˜ˆμ‹œ

{
  "session_id": "abc123",
  "command_id": "01K3QF8XW2N4V7B0YJ5R6T8ZAB",
  "robot_id": "REX",
  "status": "completed",
  "reason": null,
  "result": { "duration_s": 4.1 },
  "timestamp": "2026-08-22T10:16:04.310Z"
}

QoS / 기타 μ„€μ •

ν•­λͺ© λ‚΄μš©
QoS REQUEST 1 / RESPONSE 1
Retain REQUEST false / RESPONSE false
μ„Έμ…˜ clean_session=false, client_id = rex-agent-REX (μ—°κ²° λ‹¨μ ˆ 쀑 λͺ…령은 λΈŒλ‘œμ»€κ°€ 보관)
응닡 μ‹œκ°„ command μˆ˜μ‹  β†’ accepted 200 ms 이내
쀑볡 command_id μž¬μ‹€ν–‰ν•˜μ§€ μ•Šκ³  ν˜„μž¬ statusλ₯Ό ack둜 μž¬λ°œν–‰
쀑볡 envelope id 전솑 계측 μ€‘λ³΅μœΌλ‘œ κ°„μ£Όν•˜κ³  λ¬΄μ‹œ (졜근 1024개 μΊμ‹œ)
λͺ…λ Ή 큐 채널별 독립 큐. 각 채널(baseΒ·bodyΒ·audio)은 채널 λ‚΄ 순차, 채널 κ°„ 병렬. 각 큐 μ•ˆμ—μ„œ safety > operator
μ‹œκ°„ ν‘œκΈ° RFC3339 UTC, λ°€λ¦¬μ΄ˆ, λͺ…μ‹œμ  Z
λΉ„-envelope νŽ˜μ΄λ‘œλ“œ νŒŒμ‹± μ‹€νŒ¨λ‘œ drop, ack ν•˜μ§€ μ•ŠμŒ
인증 MQTT_USERNAME / MQTT_PASSWORD

λΉ„κ³ 

CloudEvents envelope

REQUESTΒ·RESPONSE λͺ¨λ‘ μ•„λž˜ ν˜•νƒœλ‘œ 감싼닀. ꡬ독 츑은 data.*둜 μ½λŠ”λ‹€.

{
  "specversion": "1.0",
  "type": "robot.command",
  "source": "/services/orchestrator",
  "id": "01K3QF8XW2N4V7B0YJ5R6T8ZAC",
  "time": "2026-08-22T10:15:03.412Z",
  "subject": "robot/REX",
  "datacontenttype": "application/json",
  "data": { }
}
type λŒ€μƒ ν† ν”½
robot.command robot/REX/command
robot.ack robot/REX/ack

μ—°κ΄€ ν† ν”½

ν† ν”½ λ°©ν–₯ QoS Retain μ„€λͺ…
robot/REX/state λ°œν–‰ 0 true 1 Hz μƒνƒœ μŠ€λƒ…μƒ·. 채널별(baseΒ·bodyΒ·audio) 점유 μƒνƒœ(data.channels) 포함. availabilityλŠ” data.connection(online/offline) + LWT둜 ν‘œν˜„
robot/REX/fault λ°œν–‰ 1 true κ³ μž₯ λ°œμƒ/ν•΄μ†Œ. critical λ°œμƒ μ‹œ μ§„ν–‰Β·λŒ€κΈ° λͺ…령을 정리해 νšŒμ‹ 
robot/REX/event λ°œν–‰ 1 false λ‘œλ΄‡μ΄ κ°μ§€ν•œ 이벀트(ν„°μΉ˜Β·μ‚¬μš©μž 감지 λ“±)
robot/REX/capabilities λ°œν–‰ 1 true λΆ€νŒ… μ‹œ 1회, 지원 capability와 채널 λ§€ν•‘ μ„ μ–Έ
safety/estop ꡬ독 2 true μ „μ—­ 비상정지. 거절 λΆˆκ°€
system/heartbeat/rex-agent λ°œν–‰ 0 true 10 s μ£ΌκΈ°

확인 ν•„μš” 사항

  • 페λ₯΄μ†Œλ‚˜λ³„ ν™”μž(voice_id) 맀핑을 μ˜€μΌ€μŠ€νŠΈλ ˆμ΄μ…˜ μ„œλ²„μ—μ„œ 지원할지 ν™•μ •. ν•„μš” μž‘μ—…: (1) 페λ₯΄μ†Œλ‚˜ 객체(orchestration/ai-companion/list, μ„Έμ…˜ /me 의 ai_companion)에 voice_id ν•„λ“œ μΆ”κ°€(sohee|serena|vivian, λ―Έμ§€μ • μ‹œ null β†’ REX κΈ°λ³Έ ν™”μž), (2) μ„œλ²„κ°€ say 생성 μ‹œ μ„Έμ…˜ 페λ₯΄μ†Œλ‚˜μ˜ voice_idλ₯Ό params.voice_id에 μ£Όμž…(μš°μ„ μˆœμœ„: μ•‘μ…˜ μ§€μ •κ°’ > 페λ₯΄μ†Œλ‚˜ voice_id > REX κΈ°λ³Έ), (3) admin 페λ₯΄μ†Œλ‚˜ νŽΈμ§‘ 화면에 ν™”μž 선택 UI μΆ”κ°€. 초기 λ§€ν•‘μ•ˆ: vivian ← μž₯λ‚œμŠ€λŸ¬μš΄μΉœκ΅¬Β·ν”ΌμΉ΄μΈ„Β·μ•Œμž˜λ”±κ°ˆμ„Ό, sohee ← μžμƒν•œμ—°μΈ, serena ← 어릴적 ν‚€μš°λ˜ κ°•μ•„μ§€Β·λ˜‘λ˜‘ν•œ λΉ„μ„œ. (μ‹€μ œ μ£Όμž…μ€ 페λ₯΄μ†Œλ‚˜ id(UUIDv7) κΈ°μ€€)
  • robot/{id}/command ν† ν”½μ˜ λ°©ν–₯(쀑앙 β†’ λ‘œλ΄‡)이 μ˜€μΌ€μŠ€νŠΈλ ˆμ΄μ…˜ μ„œλ²„ 섀계와 μΌμΉ˜ν•˜λŠ”μ§€ 확인. 뢈일치 μ‹œ robot/REX/inbox둜 뢄리 μ œμ•ˆ
  • zone_id λͺ…λͺ… κ·œμΉ™ ν™•μ • β€” goto params와 λΆ€μŠ€ μ‘΄ 이름이 같은 μ–΄νœ˜λ₯Ό μ‚¬μš©ν•΄μ•Ό 함
  • robot_idλ₯Ό λŒ€λ¬Έμž REX둜 등둝 κ°€λŠ₯ν•œμ§€ 확인
  • ν”„λ‘œλ•μ…˜ 브둜컀 μ£Όμ†ŒΒ·μΈμ¦ 방식 ν™•μ •