OSS_INTEGRATION - zero1ne-26/doc GitHub Wiki
SYSTEM_DESIGN.md์ ์ถ์ ์ค๊ณ๋ฅผ ์ค์ OSS๋ก ๊ตฌํํ ๋, ๊ฐ ์ปดํฌ๋ํธ๊ฐ ์ด๋ค ํ๋กํ ์ฝยทํ ํฝยท์ด๋ฒคํธ๋ก ์ฐ๊ฒฐ๋๋์ง๋ฅผ ์ ๋ฆฌํ ๋ฌธ์.
| ์ปดํฌ๋ํธ | ์ฑํ OSS | ๋ด๋น |
|---|---|---|
| Vision (CCTV โ ์ด๋ฒคํธ) | Frigate | RTSP(Real Time Streaming Protocol, ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ ํ๋กํ ์ฝ) ์คํธ๋ฆผ ์ ๋ ฅ, ๊ฐ์ฒด ํ์งยท์ถ์ , MQTT(Message Queuing Telemetry Transport, ๋ฉ์์ง ํ ํ ๋ ๋ฉํธ๋ฆฌ ์ ์ก ํ๋กํ ์ฝ) ์ด๋ฒคํธ ๋ฐํ |
| ๋ฉ์์ง ๋ฐฑ๋ณธ | Mosquitto (๋๋ HA ๋ด์ฅ broker) | MQTT broker โ ๋ชจ๋ ๋๋ฉ์ธ ์ด๋ฒคํธ์ ์ค์ |
| ์ํ ์ ์ฅยท๋์๋ณด๋ | Home Assistant (HA) | ์ฌ์ฉ์/๊ณต๊ฐ/๋ก๋ด ์ํฐํฐ, SQLite recorder, Lovelace UI |
| LLM(Large Language Model, ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ) ์์ฌ๊ฒฐ์ | LangGraph + Claude Agent SDK(Software Development Kit, ์ํํธ์จ์ด ๊ฐ๋ฐ ํคํธ) | ์ปจํ ์คํธ ์ด์ ๋ธ โ Claude tool use โ ์ก์ ๊ฒฐ์ |
| ๋๊ตฌ ํ์คํ | MCP(Model Context Protocol, ๋ชจ๋ธ ์ปจํ ์คํธ ํ๋กํ ์ฝ) | SPOTยทํ๋ธ๋ฆฟยทHA ์๋น์ค๋ฅผ LLM ๋๊ตฌ๋ก ๋ ธ์ถ |
| ๋ก๋ด ์ ์ด | Boston Dynamics SDK + (๋ฏธ์ ๋ก๋ด) MCP server | ๋๊ตฌ ํธ์ถ โ ์ค์ ๋ก๋ด ๋ช ๋ น |
| ๋ก๋ด ๋๋ฒ๊น (๋ณด์กฐ) | Foxglove Bridge | ๋ก๋ด ํ ๋ ๋ฉํธ๋ฆฌ ์๊ฐํ |
| ํ๋ธ๋ฆฟ UI | ์์ฒด ์ ์ (React Native) | HA REST(Representational State Transfer, ํํ ์ํ ์ ์ด)/WebSocket API ๋๋ ์ง์ MQTT ๊ตฌ๋ |
โโโโโโโโโโโโโ RTSP โโโโโโโโโโโ MQTT (frigate/events) โโโโโโโโโโโโโโโโ
โ CCTV (n) โ โโโโโโโโโโบ โ Frigate โ โโโโโโโโโโโโโโโโโโโโโโบ โ โ
โโโโโโโโโโโโโ โโโโโโโโโโโ โ โ
โ Mosquitto โ
โโโโโโโโโโโโโ Wi-Fi/SDK โโโโโโโโโโโโโโโโ MQTT โ (MQTT โ
โ SPOT โ โโโโโโโโโโโโบโ Robot Adapterโ โโโโโโโโโโโโโโโโโโบโ broker) โ
โโโโโโโโโโโโโ โ (MCP server) โ โ โ
โโโโโโโโโโโโโโโโ โ โ
โโโโโโโโโโโโโ WSS/REST โโโโโโโโโโโโโโโโ โ โ
โ Tablet x3 โ โโโโโโโโโโโโบโ Home โ โโโโโโโโโโโโโโโโโโบโ โ
โโโโโโโโโโโโโ โ Assistant โ MQTT โ โ
โ (state/UI) โ โ โ
โ + SQLite โ โ โ
โโโโโโโโโโโโโโโโ โโโโโโโโฌโโโโโโโโ
โ MQTT
โผ
โโโโโโโโโโโโโโโโโ
โ LangGraph โ
โ Orchestrator โ
โ โโโโโโโโโโโโ โ
โ โ Claude โ โ
โ โ (tool โ โ
โ โ use) โ โ
โ โโโโโโโโโโโโ โ
โ via MCP: โ
โ โข robot.* โ
โ โข tablet.* โ
โ โข ha.* โ
โโโโโโโโโโโโโโโโโ
ํต์ฌ ์์น:
- MQTT broker๊ฐ ๋ชจ๋ ๋๋ฉ์ธ ์ด๋ฒคํธ์ hub. ๋ชจ๋ ์ปดํฌ๋ํธ๋ broker๋ก๋ง ํต์ .
- HA์ SQLite๊ฐ ์์ ์ํ. Context Server๋ฅผ ๋ณ๋๋ก ๋์ง ์๊ณ HA์ entity/state ๋จธ์ ์ ๊ทธ๋๋ก ์ฌ์ฉ.
- LangGraph๋ MQTT ๊ตฌ๋ ์ + Claude ํธ์ถ์. ์์ ์ ์ํ๋ ๊ฑฐ์ ๊ฐ์ง ์์(stateless graph + ์ธ๋ถ ์ํ๋ HA์์ ์กฐํ).
Frigate๋ ๊ฐ์ฒด ํ์ง ์ ์๋์ผ๋ก MQTT ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๋ค.
frigate.yml (์์ง):
mqtt:
host: mosquitto
port: 1883
topic_prefix: frigate
client_id: frigate
cameras:
cafe_cam_01:
ffmpeg:
inputs:
- path: rtsp://camera-cafe-01/stream
roles: [detect, record]
detect: { width: 1280, height: 720, fps: 5 }
objects:
track: [person]
zones:
cafe_zone:
coordinates: "0,0,1280,0,1280,720,0,720"๋ฐํ๋๋ MQTT ํ ํฝ:
-
frigate/eventsโ ๊ฐ์ฒด ์ง์ /์ดํ/์ ๋ฐ์ดํธ (JSON payload,before/after์ค๋ ์ท ํฌํจ) -
frigate/{camera}/personโ ์นด๋ฉ๋ผ๋ณ person ์นด์ดํธ -
frigate/{camera}/{zone}โ ์กด ์ ์ ์ฌ๋ถ (ON/OFF)
์ฐ๋ฆฌ ๋๋ฉ์ธ ํ ํฝ์ผ๋ก์ ๋งคํ: HA ์๋ํ ๋๋ ์์ bridge ์๋น์ค๊ฐ frigate/events โ vision/zone/enter / vision/zone/exit๋ก ๋ณํ.
# bridge.py (์์ฌ์ฝ๋, 30๋ผ์ธ ์ ๋)
@mqtt.on("frigate/events")
def on_frigate_event(msg):
e = json.loads(msg.payload)
if e["type"] == "new":
publish("vision/zone/enter", {
"session_id": resolve_session(e["after"]), # ReID ๋งค์นญ
"zone_id": map_camera_to_zone(e["after"]["camera"]),
"confidence": e["after"]["score"],
"ts": e["after"]["frame_time"],
})Cross-camera ReID(Re-identification, ๋ณดํ์ ์ฌ์๋ณ)๋ Frigate ๊ธฐ๋ณธ ๊ธฐ๋ฅ ๋ถ์กฑ โ BoxMOT/Norfair ๋ณด๊ฐ ๋๋ ๋จ์ ์ธํ ์๋ฒ ๋ฉ ๋งค์นญ์ผ๋ก ์ง์ ๊ตฌํ.
์ ์์ฅ ๋ฐ๋ชจ ํ์ . ๋ธ๋ผ์ฐ์ ๋ RTSP๋ฅผ ์ง์ ์ฌ์ํ ์ ์์ผ๋ฏ๋ก RTSP๋ฅผ ๋ธ๋ผ์ฐ์ ํธํ ์ ์ก์ผ๋ก ๋ฐ๊ฟ์ฃผ๋ ์ค๊ณ์๋ฒ(relay) ๊ฐ ํ์ํ๋ค. ์ด๋ฅผ ๋ ๋ฆฝ ์๋น์ค
cctv-relay(go2rtc ๊ธฐ๋ฐ) ๋ก ๋๋ค โ Frigate(detect/์ด๋ฒคํธ)์ ๋ถ๋ฆฌํด ๋ผ์ด๋ธ ์์๋ง ์ฑ ์์ง๋ค. ๋ฐฐ๊ฒฝยท๋ถ๋ณ์ ์ํ ๊ทผ๊ฑฐ๋ SYSTEM_DESIGN.md ยง8.4, ์๋น์ค ์์ธ๋ SERVICES.md ยง2.5.
์ค๊ณ ํ๋ฆ:
CCTV n โโRTSP(config ๊ตฌ๋ ์๋ ์ฐ๊ฒฐ)โโโบ cctv-relay(go2rtc) โโโบ WebRTC/MSE restream
CCTV n โโRTSPโโโบ Frigate โโโบ detect ํ์ดํ๋ผ์ธ (frigate/events) โป ๋น์ ์ ๋ณ๋ ๊ฒฝ๋ก
๋ธ๋ผ์ฐ์ (tablet-fe) โโโ WHEP offer(SDP)โโโบ tablet-fe BFF(same-origin) โโโบ cctv-relay /api/webrtc
๋ธ๋ผ์ฐ์ โโโโก ๋ฏธ๋์ด(SRTP, ICE P2P)โโโบ cctv-relay (Node BFF๋ ์๊ทธ๋๋ง๋ง ์ค๊ณ, ๋ฏธ๋์ด ๋ฏธ๊ฒฝ์ )
-
n-stream config ๊ตฌ๋:
config/cctv-relay.yaml์streams:๋งต์ ์๋ณธ RTSP ์ฃผ์๋ฅผ ํ ์ค ์ถ๊ฐํ๋ฉด ์๋ ๋ฑ๋กยท์ฐ๊ฒฐ(์ฝ๋ ๋ณ๊ฒฝ ๋ถํ์). -
์๊ทธ๋๋ง์ BFF ํ๋ก์: ๋ธ๋ผ์ฐ์ ๋ ์นด๋ฉ๋ผ ์๊ฒฉ์ฆ๋ช
ยทrelay ์ฃผ์๋ฅผ ๋ชจ๋ฅธ ์ฑ same-origin(
/api/cctv/webrtc/<cam>)์ผ๋ก๋ง WHEP offer๋ฅผ ๋ณด๋ธ๋ค โ ์๊ฒฉ์ฆ๋ช ์ ์๋ฒ(cctv-relay/BFF)์๋ง(ยง3.1์{ENV_VAR}์นํ๊ณผ ๋์ผ ์์น). -
๋ฏธ๋์ด๋ P2P: SDP ํ์ ํ ์์์ ๋ธ๋ผ์ฐ์ โcctv-relay ICE๋ก ์ง์ . LAN ๋ด๋ถ์ด๋ฏ๋ก STUN/TURN ๋ถํ์(host candidate
IP:8555). - Frigate์ ๋ถ๋ฆฌ: ๊ธฐ๋ณธ์ cctv-relay๊ฐ ์นด๋ฉ๋ผ์์ ๋ ๋ฆฝ pull โ ๋น์ ํ์ดํ๋ผ์ธ๊ณผ ์ฅ์ ๊ฒฉ๋ฆฌ(๋จ์ผ-pull ๋ณํ ํธ๋ ์ด๋์คํ๋ SERVICES ยง2.5).
- ์ฝ๋ฑ: WebRTC๋ H.264๋ง โ H.265 ์นด๋ฉ๋ผ๋ go2rtc transcode ํ์(CPU/GPU ๋น์ฉ).
HA๋ MQTT integration์ ํ์ฑํํ๋ฉด broker์ ํ ํฝ์ entity์ ์๋ ๋งคํํ ์ ์๋ค.
MQTT Discovery๋ก ์ํฐํฐ ์๋ ๋ฑ๋ก (LangGraph ๋๋ Robot Adapter๊ฐ ๋ฐํ):
Topic: homeassistant/sensor/spot_01_battery/config
Payload: {
"name": "SPOT-01 Battery",
"state_topic": "robot/spot-01/state",
"value_template": "{{ value_json.battery_pct }}",
"unit_of_measurement": "%",
"device": { "identifiers": ["spot-01"], "name": "SPOT 01" }
}
ํต์ฌ ์ํฐํฐ ๋ชจ๋ธ:
-
sensor.spot_01_battery,sensor.spot_01_pose,binary_sensor.spot_01_online -
sensor.session_a_zone,sensor.session_a_mood -
binary_sensor.cafe_occupied(Frigate zone์์ ์๋ ์์ฑ)
์ํ ์์: HA ๊ธฐ๋ณธ recorder๊ฐ SQLite์ ์๋ ๊ธฐ๋ก โ ๋ณ๋ Context Server ๋ถํ์.
๋์๋ณด๋: Lovelace YAML๋ก ์ด์์ ์ฝ์ ์ฆ์ ๊ตฌ์ฑ ๊ฐ๋ฅ (๊ทธ๋ํ, ์นด๋ฉ๋ผ ์คํธ๋ฆผ, ๋ก๋ด ์ํ ์นด๋).
์๋น์ค ํธ์ถ (HA โ ์ธ๋ถ): mqtt.publish ์๋น์ค๋ก ์์ ํ ํฝ ๋ฐํ ๊ฐ๋ฅ โ ์ด์์๊ฐ ๋์๋ณด๋ ๋ฒํผ์ผ๋ก ๋น์ ์ ์งยท๋ก๋ด ํธ์ถ ๋ฑ ์๋ ๋ช
๋ น.
LangGraph ์๋น์ค์ ์ฑ ์์ ๋:
-
MQTT ๊ตฌ๋
:
vision/zone/+,tablet/+/intent,context/+/+/update๋ฑ์ ๋ฐ์. - Claude tool use ํธ์ถ: ์ปจํ ์คํธ๋ฅผ ๋ชจ์ Anthropic API ํธ์ถ, ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ MQTT publish ๋๋ MCP ํธ์ถ๋ก ์คํ.
LangGraph ๋ ธ๋ ๊ตฌ์กฐ:
from langgraph.graph import StateGraph
from anthropic import Anthropic
class FlowState(TypedDict):
trigger_event: dict
context_snapshot: dict
llm_decisions: list[dict]
graph = StateGraph(FlowState)
graph.add_node("assemble_context", assemble_from_ha) # HA REST API๋ก ํ์ฌ ์ํ ์กฐํ
graph.add_node("call_claude", call_with_tools) # Claude Agent SDK ํธ์ถ
graph.add_node("dispatch", dispatch_tool_calls) # MCP ๋๋ MQTT publish
graph.add_node("guardrail", validate_actions) # ํ์ดํธ๋ฆฌ์คํธยทhard clamp
graph.set_entry_point("assemble_context")
graph.add_edge("assemble_context", "call_claude")
graph.add_edge("call_claude", "guardrail")
graph.add_edge("guardrail", "dispatch")Claude Agent SDK ํธ์ถ (๋๊ตฌ๋ MCP๋ก ๋ฑ๋ก):
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
system=ZONE_PLAYBOOK_SYSTEM_PROMPT, # prompt cache ์ ์ฉ
tools=[
{"type": "mcp", "server": "robot-adapter"},
{"type": "mcp", "server": "tablet-gateway"},
{"type": "mcp", "server": "home-assistant"},
],
messages=[{"role": "user", "content": context_message}],
)๋๊ตฌ๋ฅผ LangGraph ์์ ์ง์ ํจ์๋ก ๋์ง ์๊ณ MCP ์๋ฒ 3๊ฐ๋ก ๋ถ๋ฆฌํ๋ฉด ์ฌ์ฌ์ฉ์ฑยทํ ์คํธยท๊ถํ ๋ถ๋ฆฌ๊ฐ ์ฉ์ด.
MCP ์๋ฒ ๊ตฌ์ฑ:
mcp-robot-adapter (Python) โ SPOT SDK ๋ํ
# ๋
ธ์ถ ๋๊ตฌ (์์)
@mcp.tool
def robot_command(robot_id: str, # ๋ฐํ์์ config/robots.yaml ๋ ์ง์คํธ๋ฆฌ๋ก ๊ฒ์ฆ
capability: Literal["goto", "follow", "stop", "look_at", "gesture"],
params: dict) -> dict:
"""๋ก๋ด์ capability ๋ช
๋ น์ ๋ฐํ. ์์ ์ธํฐ๋ก์ ํต๊ณผํด์ผ ์คํ."""
if not safety_interlock.allow(robot_id, capability, params):
return {"status": "rejected", "reason": "safety_interlock"}
return adapters[robot_id].execute(capability, params)mcp-tablet-gateway (Node.js) โ ํ๋ธ๋ฆฟ์ ์ต์
ยท๋ฉ์์ง push
@tool
async function showOptions(tabletId: string, options: Option[]): Promise<Ack> {
await ws.send(tabletId, { type: "show_options", options });
return { ok: true };
}mcp-home-assistant โ HA ์ํ ์กฐํยท์๋น์ค ํธ์ถ (์ปค๋ฎค๋ํฐ ๊ตฌํ์ฒด ํ์ฉ ๊ฐ๋ฅ)
ha.get_state("sensor.session_a_mood")ha.call_service("light.turn_on", { ... })
LangGraph๋ ์ MCP ์๋ฒ 3๊ฐ๋ฅผ Claude์ ๋๊ตฌ๋ก ๋ ธ์ถํด, Claude๊ฐ ํธ์ถํ๋ฉด ์ค์ ์ก์ ์ด ์คํ๋๋ค.
๋๊ตฌ๋ก ๋ช ๋ น์ ๋ฐ๋ ๊ฒ๊ณผ ๋ณ๋๋ก, ๋ก๋ด์ ์๊ธฐ ์ํ๋ฅผ 1Hz๋ก MQTT์ ๋ฐํํด์ผ HAยทFrigateยทLangGraph๊ฐ ๋ชจ๋ ๋ณธ๋ค.
Topic: robot/spot-01/state
Payload: {
"robot_id": "spot-01",
"pose": { "x": 2.4, "y": 1.1, "yaw": 0.78, "zone_id": "cafe" },
"battery_pct": 87,
"current_action": { "type": "look_at", "started_at": "2026-05-04T10:00:00Z" },
"faults": [],
"connection": "online",
"updated_at": "2026-05-04T10:00:01Z"
}
์ด ํ ํฝ์ HA MQTT Discovery๋ก ์๋ entityํ๋์ด Lovelace์ ๊ทธ๋๋ก ๋ ธ์ถ๋๋ค.
๋ ๊ฐ์ง ํตํฉ ์ต์ :
์ต์ A โ HA ๋ค์ดํฐ๋ธ: ํ๋ธ๋ฆฟ์ด HA ๋์๋ณด๋๋ฅผ ํ์คํฌ๋ฆฐ์ผ๋ก ํ์. ๋น ๋ฅด์ง๋ง ์ปค์คํ ์ธํฐ๋์ ํ๊ณ.
์ต์ B โ ์์ฒด ์ฑ: React Native ์ฑ์ด HA WebSocket API์ ์ธ์ฆ ํ ํฐ์ผ๋ก ์ ์, ์ํฐํฐ ๋ณ๊ฒฝ์ ๊ตฌ๋ ํ๊ณ ์ฌ์ฉ์ ์ ๋ ฅ์ MQTT๋ก publish.
// ํ๋ธ๋ฆฟ ์ฑ (์ต์
B)
import { connectToHA } from "ha-websocket";
const ha = await connectToHA(URL, TOKEN);
ha.subscribe("state_changed", (e) => {
if (e.entity_id === `sensor.session_${sessionId}_options`) {
setOptions(JSON.parse(e.new_state.state));
}
});
// ์ฌ์ฉ์๊ฐ ์ต์
์ ํ ์
mqtt.publish(`tablet/${tabletId}/intent`, { option_id: "drink_americano" });๊ถ์ฅ: ์ต์ B โ ์ธํฐ๋์ ์์ ๋๊ฐ ๋๊ณ , HA๋ ๋ฐฑ์๋ ์ํ ์ ์ฅ๋ง ๋ด๋น.
๋ก๋ด ํ ๋ ๋ฉํธ๋ฆฌ(ํนํ SPOT์ ์นด๋ฉ๋ผ/IMU(Inertial Measurement Unit, ๊ด์ฑ ์ธก์ ์ฅ์น)/๊ด์ ์ํ)๋ฅผ ์๊ฐํํ ๋ ์ฌ์ฉ. ROS 2๋ฅผ ๋์ ํ์ง ์์ผ๋ฉด ๊ฐ์น ์ ํ์ ์ด๋ฏ๋ก ์ด์ ๋จ๊ณ ์ง์ ํ ํ์ ์ ์ถ๊ฐ.
์ฐ๊ฒฐ ์ต์
: foxglove-bridge ๋๋ MQTT-to-foxglove ์ด๋ํฐ.
1. CCTV(cafe_cam_01) โ Frigate
2. Frigate publish โ mqtt:frigate/events
{ type:"new", after:{ camera:"cafe_cam_01", label:"person", id:"track_42" } }
3. bridge.py ๋ณํ โ mqtt:vision/zone/enter
{ session_id:"sess_A", zone_id:"cafe", confidence:0.97 }
4. HA ์๋ํ ํธ๋ฆฌ๊ฑฐ โ sensor.session_a_zone ์ํ ๊ฐฑ์
๋์์ LangGraph ์๋น์ค๊ฐ vision/zone/enter ๊ตฌ๋
์ฝ๋ฐฑ ์คํ
5. LangGraph: assemble_context
- HA REST: GET /api/states/sensor.session_a_mood โ "neutral"
- HA REST: GET /api/states/sensor.weather_condition โ "sunny"
- ๋ก๋ด ์ํ: GET /api/states/sensor.spot_01_pose
6. LangGraph: Claude tool use ํธ์ถ (mcp-robot-adapter, mcp-tablet-gateway ๋
ธ์ถ)
7. Claude ์๋ต โ tool_calls:
- robot_command(robot_id="spot-01", capability="look_at", params={target:"sess_A"})
- showOptions(tablet_id="t01", options=["์ปคํผ ์ถ์ฒ","์์
์ถ์ฒ"])
8. guardrail ํต๊ณผ โ MCP ํธ์ถ โ ์ค์ ์คํ
9. SPOT ๋์, ํ๋ธ๋ฆฟ UI ๊ฐฑ์ , robot/spot-01/state 1Hz publish
10. HA ๋์๋ณด๋์ ๋ชจ๋ ๊ฒ์ด ๋ผ์ด๋ธ๋ก ํ์
1. ์ฌ์ฉ์ "๊ณต์์์ ์ฐ์ฑ
๊ฐ์ด ํ๊ธฐ" ์ ํ
2. ํ๋ธ๋ฆฟ โ mqtt:tablet/t01/intent { option:"walk_with_me" }
3. LangGraph ๊ตฌ๋
์ฝ๋ฐฑ โ assemble_context (ํ zone=park)
4. Claude โ robot_command(spot-01, capability="follow", params={target:"sess_A", max_distance:2.0})
5. mcp-robot-adapter: safety_interlock ๊ฒ์ฆ (proximity, zone polygon) โ SPOT SDK ํธ์ถ
6. 1Hz robot/spot-01/state ๋ฐํ โ ํ๋ธ๋ฆฟ ์งํ๋ ํ์
services:
mosquitto:
image: eclipse-mosquitto:2.0
ports: ["1883:1883"]
volumes: ["./mosquitto:/mosquitto/config"]
frigate:
image: ghcr.io/blakeblackshear/frigate:stable
devices: ["/dev/dri:/dev/dri"] # ๋๋ NVIDIA GPU
volumes:
- ./frigate.yml:/config/config.yml
- frigate_media:/media/frigate
ports: ["5000:5000"]
home-assistant:
image: ghcr.io/home-assistant/home-assistant:stable
volumes: ["./ha-config:/config"]
network_mode: host
bridge:
build: ./services/bridge # frigate.events โ vision.* ๋ณํ
depends_on: [mosquitto]
mcp-robot-adapter:
build: ./services/mcp-robot-adapter
devices: ["/dev/spot:/dev/spot"] # ํ์ ์
environment:
- SPOT_HOSTNAME=...
- SPOT_USERNAME=...
mcp-tablet-gateway:
build: ./services/mcp-tablet-gateway
langgraph-orchestrator:
build: ./services/orchestrator
extra_hosts:
- "host.docker.internal:host-gateway" # HA(host network) ๋๋ฌ
environment:
- ANTHROPIC_API_KEY=...
- HA_URL=http://host.docker.internal:8123
- HA_TOKEN=...
depends_on: [mosquitto, mcp-robot-adapter, mcp-tablet-gateway]OSS ๋ฐฑ๋ณธ์ ์ฑํํ๋ฉด ์์ฒด ๊ตฌํ ํ์ ์์ญ์ ๋ค์ 4๊ฐ๋ก ์ถ์๋๋ค.
| ๋ชจ๋ | ์ถ์ ๊ท๋ชจ | ํต์ฌ ์ฑ ์ |
|---|---|---|
services/bridge |
~200 LoC(Lines of Code, ์ฝ๋ ๋ผ์ธ ์) | Frigate ์ด๋ฒคํธ โ ๋๋ฉ์ธ ํ ํฝ ๋ณํ, ReID ์ธ์ ๋งค์นญ |
services/mcp-robot-adapter |
~1.5K LoC | SPOT/๋ฏธ์ ๋ก๋ด SDK ๋ํ, ์์ ์ธํฐ๋ก, MCP ๋๊ตฌ ๋ ธ์ถ |
services/mcp-tablet-gateway |
~500 LoC | ํ๋ธ๋ฆฟ WSS(WebSocket Secure, ๋ณด์ ์น์์ผ), ์ต์ push, intent ์์ |
services/orchestrator |
~800 LoC | LangGraph ๊ทธ๋ํ ์ ์, Claude ํธ์ถ, prompt/playbook |
apps/tablet |
~3K LoC | React Native UI, ์ธํฐ๋์ |
์์ฒด ์ธํ๋ผ(MQTT, ์ํ ์ ์ฅ, ๋์๋ณด๋, NVR(Network Video Recorder, ๋คํธ์ํฌ ๋น๋์ค ๋ ์ฝ๋), ๊ฐ์ฒด ํ์ง)๋ ๋ชจ๋ OSS๊ฐ ์ ๊ณต.
-
MQTT QoS(Quality of Service, ์๋น์ค ํ์ง ๋ฑ๊ธ): ๋ช
๋ น(
robot/.../command)์ QoS 1 ๋๋ 2, ํ ๋ ๋ฉํธ๋ฆฌ(robot/.../state)๋ 0 ๋๋ 1. -
HA recorder retention: ๊ธฐ๋ณธ 10์ผ์ด์ง๋ง ์ด ์์คํ
์ ํต๊ณ ๋ณด์กด์ด ๋ชฉ์ ์๋๋ฏ๋ก 2~3์ผ๋ก ์ถ์ (
recorder.purge_keep_days: 3). -
MCP ๊ถํ:
mcp-robot-adapter๋ mTLS(mutual TLS, ์ํธ ์ธ์ฆ TLS) + ํ ํฐ. LangGraph๋ง ํธ์ถ ๊ฐ๋ฅํ๊ฒ ACL(Access Control List, ์ ๊ทผ ์ ์ด ๋ชฉ๋ก). -
Frigate ReID ํ๊ณ: ๋จ์ผ ์นด๋ฉ๋ผ ์ถ์ ์ ๊ฐํ์ง๋ง cross-camera๋ ๋ณด๊ฐ ํ์ โ
services/bridge์์ ์๋ฒ ๋ฉ ๋งค์นญ ๋ณด๊ฐ ๋๋ BoxMOT ์ฌ์ด๋์นด. - HA ์๋ํ vs LangGraph ์ฑ ์ ๊ฒฝ๊ณ: ๋จ์ ๋ฃฐ(์: ๋ก๋ด ๋ฐฐํฐ๋ฆฌ < 20% ์ ์๋ฆผ)์ HA ์๋ํ๋ก, ์ปจํ ์คํธ ์์กด ์์ฌ๊ฒฐ์ ์ LangGraph๋ก. ๊ฐ์ ๊ฒฐ์ ์ ๋ ๊ณณ์์ ํ๋ฉด ์ถฉ๋๋๋ค.
- prompt cache: zone playbook + system prompt๋ stable โ Anthropic prompt cache๋ก ๋น์ฉยท์ง์ฐ ์ ๊ฐ.
-
Frigate native (
frigate/{cam}/{zone}ON/OFF): ๋จ์ zone ์ ์ ํ์์ฉ. HA๊ฐbinary_sensor.<zone>_occupied๋ก ์๋ ํก์. ์ด์์ ๋์๋ณด๋ ์๋จ "ํ์ฌ ์ ์ zone" ์นด๋์ฉ์ผ๋ก๋ง ์ฌ์ฉ. -
bridge
vision/zone/enter|exit: session_id ๋งค์นญ๋ ๋๋ฉ์ธ ์ด๋ฒคํธ. orchestrator ํธ๋ฆฌ๊ฑฐยท์ธ์ zone history ์ถ์ ์ฉ. - ๋ ๊ฒฝ๋ก๊ฐ ์ ๋ณด๊ฐ ๊ฒน์น๋ ๋ฏํ์ง๋ง ์๋น์๊ฐ ๋ค๋ฅด๋ฏ๋ก ์๋๋ ์ค๋ณต. orchestrator๊ฐ Frigate native ํ ํฝ์ ์ง์ ๊ตฌ๋ ํ์ง ์๊ฒ ACL๋ก ์ฐจ๋จ.
- ์๋น์ค ์ด๋ฆ์
orchestrator(๋๋ ํฐ๋ฆฌservices/orchestrator). - ๋ด๋ถ์์ LangGraph๋ฅผ ์ฌ์ฉํ์ง๋ง "LangGraph ์๋น์ค"๋ผ ๋ถ๋ฅด์ง ์์ โ LangGraph๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌยท๊ตฌํ ๋๊ตฌ.
- ๋ฌธ์ยท๋ก๊ทธยท๋ฉํธ๋ฆญ์์ ์ผ๊ด๋๊ฒ
orchestrator๋ก ํ๊ธฐ.
- MCP ํ์ค transport 3์ข : stdio, SSE, HTTP.
-
๋ณธ ์์คํ
์ SSE over HTTP ์ฑํ:
- ์ปจํ ์ด๋ ๋ถ๋ฆฌ๋์ด ์์ด stdio ๋ถ์ ํฉ.
- SSE๋ ์๋ฐฉํฅ long-lived ์คํธ๋ฆผ์ผ๋ก ๋๊ตฌ ํธ์ถ + ์งํ ์๋ฆผ์ ์ ํฉ.
- HA REST์ ๋์ผ ๋ผ์ฐํ (์ญํ๋ก์ ๋จ์ํ).
-
mcp-robot-adapterยทmcp-tablet-gateway๋ ๋ค:8000/mcp์๋ํฌ์ธํธ ๋ ธ์ถ. orchestrator๊ฐ ํด๋ผ์ด์ธํธ๋ก ์ฐ๊ฒฐ. - ์ธ์ฆ: opaque bearer token (๊ฐ ์๋น์ค์
.env.secret์ ๋ฐ๊ธ), ์ํธ mTLS๋ ๋ฏธ์ ์ฉ(LAN ๋ด๋ถ).
-
enter hold:
800msโ NFR 1.5s ์ด๋ด ๋ณด์ฅ. -
exit grace:
3000msโ ์ฌ์ฉ์๊ฐ ์ ๊น ๊ฐ๋ ค์ ธ๋ exit ์ ๋ฐ๋. -
dedup window: ๋์ผ (session_id, zone_id) enter๋
1000ms๋ด ์ฌ๋ฐํ ์ฐจ๋จ. - ์๊ณ๊ฐ์
config/bridge.yaml์์ ์ด์ ์ค ์กฐ์ ๊ฐ๋ฅ (์ฌ๊ธฐ๋ ํ์).
-
๋ง๋ฃ ์ฒ๋ฆฌ์:
mcp-tablet-gateway๊ฐ ๋ด๋น. -
ttl_s๋ง๋ฃ ์ ๊ฒ์ดํธ์จ์ด๊ฐ ํ๋ธ๋ฆฟ์{type: "dismiss_options", message_id}์๋ push โ ํ๋ธ๋ฆฟ UI๊ฐ ์ต์ ์์ญ ๋น์. - ๋ง๋ฃ ํ ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ฒ์ดํธ์จ์ด์์ "stale_intent" ์๋ต์ผ๋ก ๊ฑฐ๋ถ + Loki ๋ก๊ทธ ํ ์ค.
- ๋ง๋ฃ ์ ์
show_options(๊ฐ์ tablet_id)๊ฐ ๋์ฐฉํ๋ฉด ์ด์ message_id๋ ์ฆ์ ํ๊ธฐ.
SYSTEM_DESIGN.md ์ปดํฌ๋ํธ โ OSS ๋งคํ:
| ์ค๊ณ ๋ฌธ์ ์ปดํฌ๋ํธ | OSS ์ฑํ |
|---|---|
| ยง3.1 Central Context Server | HA recorder (SQLite) + entity state |
| ยง3.2 Vision Tracking | Frigate + bridge + ReID ๋ณด๊ฐ |
| ยง3.3 Robot Adapter |
์์ฒด ๊ตฌํ (mcp-robot-adapter) |
| ยง3.4 LLM Orchestrator | LangGraph + Claude Agent SDK |
| ยง3.5 Tablet Application | ์์ฒด ๊ตฌํ (HA WebSocket API ํ์ฉ) |
| ยง3.6 Monitoring & Dashboard | HA Lovelace + Prometheus/Grafana(๋ฉํธ๋ฆญ) |
| ยง6.2 ๋ฉ์์ง (MQTT) | Mosquitto (๋๋ HA ๋ด์ฅ) |