Skip to content

DEVELOPMENT

playground edited this page May 31, 2026 · 2 revisions

๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ฐ€์ด๋“œ

๋กœ์ปฌ์—์„œ SPOTยท์‹ค ์นด๋ฉ”๋ผยท์‹ค ํƒœ๋ธ”๋ฆฟ ์—†์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑยท๊ฒ€์ฆํ•˜๋Š” ์ ˆ์ฐจ. CI ํ†ต๊ณผ + ์‚ฌ๋ฌด์‹ค ๋ฏธ๋Ÿฌ ํ™˜๊ฒฝ ๊ฒ€์ฆ์„ ๊ฑฐ์ณ ์ „์‹œ์žฅ์— ๋ฐฐํฌํ•œ๋‹ค.


0. Phase 0 โ€” ํ™˜๊ฒฝ ์ค€๋น„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

SPOTยท์‹ค ์นด๋ฉ”๋ผยท์‹ค ํƒœ๋ธ”๋ฆฟ ์—†์ด ๋กœ์ปฌ์—์„œ ํ’€ ์Šคํƒ์„ ์ฒ˜์Œ ๋„์šฐ๊ธฐ๊นŒ์ง€ ์ˆœ์„œ๋Œ€๋กœ ์ ๊ฒ€ํ•˜๋Š” ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ฒดํฌ๋ฆฌ์ŠคํŠธ. ๊ฐ ํ•ญ๋ชฉ์˜ ์ƒ์„ธ ์ ˆ์ฐจ๋Š” ์•„๋ž˜ ํ•ด๋‹น ์„น์…˜์„ ๋”ฐ๋ฅธ๋‹ค. ์ด ์ฒดํฌ๋ฆฌ์ŠคํŠธ์˜ ์ข…์ฐฉ์  = ยง6 Phase 0 ๊ฒŒ์ดํŠธ(mosquitto + bridge + fake-frigate โ†’ vision/zone/enter ๋ฐœํ–‰ ํ™•์ธ).

ํ‘œ๊ธฐ: [ ] ๊ฐ–์ถ”๊ฑฐ๋‚˜ ๋งŒ๋“ค ๊ฒƒ ยท [?] ํ™•์ธยท์ ๊ฒ€ํ•  ๊ฒƒ.

0.1 ์‚ฌ์ „ ๋„๊ตฌ ์„ค์น˜ (โ†’ ยง1)

  • Docker 24.0+ + Compose v2 (docker compose version)
  • Python 3.12 + uv, Node.js 20 LTS (uv --version / node -v)
  • mosquitto-clients (mosquitto_sub/mosquitto_pub โ€” ๋ฒ„์Šค ๋ผ์ด๋ธŒ ํ…Œ์ผ์šฉ)
  • (์˜ต์…˜) NVIDIA GPU ๋˜๋Š” USB Coral โ€” vision/Frigate ๊ฒ€์ฆ ์‹œ์—๋งŒ

0.2 ๋ ˆํฌ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ (โ†’ ยง2)

  • ๋ชจ๋…ธ๋ ˆํฌ clone + pre-commit install
  • Python ์„œ๋น„์Šค uv sync --dev (orchestratorยทbridgeยทmcp-robot-adapter)
  • Node ์„œ๋น„์Šค npm ci (mcp-tablet-gatewayยทapps/tablet)

0.3 ์‹œํฌ๋ฆฟยทํ™˜๊ฒฝ๋ณ€์ˆ˜ (.env.dev)

  • DEV_MODE=true โ€” tablet-gateway ๊ณ ์ • ํ† ํฐ DEV_TOKEN ํ—ˆ์šฉ (ยง4.3)
  • fake-spot ์ž๊ฒฉ bosdyn:bosdyn ๋ช…์‹œ (ยง8 ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…)
  • ANTHROPIC_API_KEY โ€” Phase 0์—๋Š” ๋ถˆํ•„์š”, Phase 1(orchestrator) ์ง„์ž… ์‹œ ํ•„์š”
  • [?] .env.dev๊ฐ€ .gitignore์— ํฌํ•จ๋๋Š”์ง€ โ€” ์‹œํฌ๋ฆฟ ์ปค๋ฐ‹ ๊ธˆ์ง€

0.4 MQTT broker (mosquitto) ๊ธฐ๋™ (โ†’ ยง3)

  • docker-compose.dev.yml๋กœ mosquitto ๊ธฐ๋™
  • passwd ํŒŒ์ผ ๊ถŒํ•œยทuid (chown 1883:1883 passwd, ยง8)
  • ACL ๋กœ๋“œ โ€” ๊ฐ ์„œ๋น„์Šค ๊ณ„์ •์ด ํ•„์š”ํ•œ ํ† ํ”ฝ๋งŒ ์‚ฌ์šฉํ•˜๋„๋ก (SERVICES.md ยง1)
  • [?] mosquitto_sub -h localhost -t '#' -v ๋กœ ์—ฐ๊ฒฐ ํ™•์ธ

0.5 Home Assistant ๊ธฐ๋™ (โ†’ ยง3)

  • HA ์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋™ + configuration.yaml ๊ธฐ๋ณธ๊ฐ’ ๋กœ๋“œ
  • HA์˜ MQTT integration์ด mosquitto์— ์—ฐ๊ฒฐ๋๋Š”์ง€ (Settings โ†’ Devices & Services)
  • recorder(SQLite) ๋™์ž‘ ํ™•์ธ โ€” HA๊ฐ€ ์ƒํƒœ ์ €์žฅ ๊ณ„์ธต์ž„
  • [?] http://localhost:8123 Developer Tools โ†’ States ์ ‘๊ทผ ๊ฐ€๋Šฅ

0.6 Mock ํ•˜๋‹ˆ์Šค (โ†’ ยง4)

  • ROBOT_ADAPTER=mock ์œผ๋กœ mcp-robot-adapter ๊ธฐ๋™ โ€” MockAdapter (ยง4.1)
  • tests/tools/fake-frigate.py ์‹คํ–‰ ๊ฐ€๋Šฅ ํ™•์ธ (ยง4.2)
  • fake-spot gRPC ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ โ€” Phase 1 ์ง„์ž… ์‹œ (ยง3 compose)
  • tests/tools/fake-tablet.html WSS ์—ฐ๊ฒฐ โ€” Phase 3 ์ง„์ž… ์‹œ (ยง4.3)

0.7 Phase 0 ๊ฒŒ์ดํŠธ ํ†ต๊ณผ (โ†’ ยง6)

  • tests/integration/run-phase-0.sh โ€” mosquitto + bridge + fake-frigate ๋ถ€ํŒ…
  • [?] fake-frigate frigate/events ๋ฐœํ–‰ โ†’ bridge๊ฐ€ vision/zone/enter๋กœ ๋ณ€ํ™˜ยท๋ฐœํ–‰ํ•˜๋Š”์ง€ ๋ผ์ด๋ธŒ ํ…Œ์ผ ํ™•์ธ
  • [?] CloudEvents envelopeยทtraceparent๊ฐ€ ๋ฉ”์‹œ์ง€์— ์ •์ƒ ๋ถ€์ฐฉ๋๋Š”์ง€ (MQTT_SCHEMA.md ยง1)

์™„๋ฃŒ ๊ธฐ์ค€: ๋กœ์ปฌ์—์„œ mosquittoยทHAยทbridge๊ฐ€ ๋–  ์žˆ๊ณ , fake-frigate ์ด๋ฒคํŠธ ํ•œ ๊ฑด์ด vision/zone/enter๋กœ ๋ณ€ํ™˜๋˜์–ด ๋ฒ„์Šค์— ํ๋ฅด๋Š” ๊ฒƒ์„ mosquitto_sub๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


1. ๊ถŒ์žฅ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ

ํ•ญ๋ชฉ ๊ถŒ์žฅ ์‚ฌ์–‘
OS Ubuntu 22.04 / macOS 14+ / WSL2 Ubuntu
RAM 16GB ์ด์ƒ
CPU 4 ์ฝ”์–ด ์ด์ƒ
GPU(Graphics Processing Unit, ๊ทธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ ์žฅ์น˜) (์˜ต์…˜) Frigate/vision ๊ฒ€์ฆ ์‹œ NVIDIA GPU ๋˜๋Š” USB Coral
Docker 24.0+ + Compose v2
Python 3.12
Node.js 20 LTS

2. ๋ชจ๋…ธ๋ ˆํฌ ํด๋ก  ๋ฐ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ

git clone https://github.com/OWNER/zerone.git
cd zerone

# pre-commit ํ›… ์„ค์น˜
pip install pre-commit
pre-commit install

# Python ์„œ๋น„์Šค dev ์˜์กด์„ฑ
cd services/orchestrator && uv sync --dev && cd ../..
cd services/bridge && uv sync --dev && cd ../..
cd services/mcp-robot-adapter && uv sync --dev && cd ../..

# Node.js ์„œ๋น„์Šค
cd services/mcp-tablet-gateway && npm ci && cd ../..

# ํƒœ๋ธ”๋ฆฟ ์•ฑ
cd apps/tablet && npm ci && cd ../..

3. ๋กœ์ปฌ dev compose

deploy/docker-compose.yml์€ ์šด์˜์šฉ ํ•€(pin) ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๊ฐœ๋ฐœ ์‹œ์—๋Š” deploy/docker-compose.dev.yml overlay๋กœ ๋‹ค์Œ์„ ๋ณ€๊ฒฝ:

  • ์ž์ฒด ์„œ๋น„์Šค๋ฅผ image: ๋Œ€์‹  build: ๋กœ ๋กœ์ปฌ ๋นŒ๋“œ.
  • ์™ธ๋ถ€ ์นด๋ฉ”๋ผยท๋กœ๋ด‡ ์ž๋ฆฌ์— mock ์ปจํ…Œ์ด๋„ˆ ๋ฐฐ์น˜.
  • ํ™˜๊ฒฝ๋ณ€์ˆ˜์— LOG_LEVEL=debug.
  • ํฌํŠธ๋ฅผ ๋ชจ๋‘ localhost์— ๋…ธ์ถœ (๋””๋ฒ„๊น… ํŽธ์˜).
# deploy/docker-compose.dev.yml
services:
  bridge:
    build: ../services/bridge
    image: zerone/bridge:dev
    environment:
      - LOG_LEVEL=debug

  orchestrator:
    build: ../services/orchestrator
    image: zerone/orchestrator:dev
    environment:
      - LOG_LEVEL=debug
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}

  mcp-robot-adapter:
    build: ../services/mcp-robot-adapter
    image: zerone/mcp-robot-adapter:dev
    environment:
      - ROBOT_ADAPTER=mock        # ํ•ต์‹ฌ: ์‹ค SPOT ๋Œ€์‹  mock
      - LOG_LEVEL=debug

  mcp-tablet-gateway:
    build: ../services/mcp-tablet-gateway
    image: zerone/mcp-tablet-gateway:dev

  # mock ์นด๋ฉ”๋ผ (Frigate ๋Œ€์ฒด ๋˜๋Š” RTSP ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
  fake-rtsp:
    image: linuxserver/ffmpeg:latest
    command: >
      -re -stream_loop -1 -i /assets/sample.mp4
      -c copy -f rtsp rtsp://0.0.0.0:8554/live
    volumes: [../tests/fixtures/video:/assets:ro]
    ports: ["8554:8554"]

  # mock SPOT (gRPC ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ)
  fake-spot:
    build: ../tests/mocks/fake-spot
    ports: ["50051:50051"]

๊ธฐ๋™:

cd deploy
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build

4. Mock ์–ด๋Œ‘ํ„ฐ ๊ฐ€์ด๋“œ

4.1 Mock SPOT ์–ด๋Œ‘ํ„ฐ โ€” MockAdapter

mcp-robot-adapter์˜ ROBOT_ADAPTER=mock ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ํ™œ์„ฑํ™”. ๋‹ค์Œ ๋™์ž‘์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜:

capability ๋™์ž‘
goto 0.5 m/s ๊ฐ€์ •์œผ๋กœ ์œ„์น˜ ๋ณด๊ฐ„, 1Hz robot/{id}/state publish
follow ๊ฐ€์ƒ ์‚ฌ์šฉ์ž ์œ„์น˜๋ฅผ ์ผ์ • ๊ฑฐ๋ฆฌ ์œ ์ง€
stop ์ฆ‰์‹œ stop, current_action=null
look_at yaw ๋ณ€๊ฒฝ, 1์ดˆ ํ›„ completed ack
gesture 2์ดˆ ํ›„ completed
# services/mcp-robot-adapter/src/adapter/adapters/mock.py (์š”์ง€)
class MockAdapter(RobotAdapter):
    def __init__(self, robot_id: str):
        self.robot_id = robot_id
        self.pose = {"x": 0, "y": 0, "yaw": 0, "zone_id": "cafe"}
        self.battery = 100
        self.current_action = None

    async def execute(self, capability, params):
        if capability == "goto":
            asyncio.create_task(self._simulate_goto(params))
        ...
        return {"command_id": ulid.new().str, "status": "accepted"}

4.2 Mock Frigate ์ด๋ฒคํŠธ

์‹ค์ œ RTSP(Real Time Streaming Protocol, ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ ํ”„๋กœํ† ์ฝœ) ์—†์ด frigate/events๋ฅผ ๋ฐœํ–‰ํ•˜๋Š” ์ž‘์€ ์Šคํฌ๋ฆฝํŠธ:

# tests/tools/fake-frigate.py
import json, paho.mqtt.client as mqtt, time, ulid

events = [
    {"type": "new", "after": {"id": ulid.new().str, "camera": "cafe_cam_01",
                              "label": "person", "score": 0.95,
                              "current_zones": ["cafe_zone"],
                              "entered_zones": ["cafe_zone"]}},
    # 5์ดˆ ํ›„ park ์ด๋™
    {"type": "update", "after": {"camera": "park_cam_01", ...}},
]

c = mqtt.Client(); c.connect("localhost", 1883)
for e in events:
    c.publish("frigate/events", json.dumps(e), qos=1)
    time.sleep(5)

์‹คํ–‰:

python tests/tools/fake-frigate.py

4.3 Mock ํƒœ๋ธ”๋ฆฟ ํด๋ผ์ด์–ธํŠธ

๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ๊ฐ„๋‹จ WSS(WebSocket Secure, ๋ณด์•ˆ ์›น์†Œ์ผ“) ํด๋ผ์ด์–ธํŠธ:

<!-- tests/tools/fake-tablet.html -->
<script>
  const ws = new WebSocket("ws://localhost:8080?token=DEV_TOKEN");
  ws.onmessage = (e) => console.log("RX:", JSON.parse(e.data));
  function selectOption(id) {
    ws.send(JSON.stringify({intent_type: "option_select", payload: {option_id: id}}));
  }
</script>
<button onclick="selectOption('drink_recommend')">์ปคํ”ผ ์ถ”์ฒœ</button>

ํŽ˜์–ด๋ง ์šฐํšŒ๋ฅผ ์œ„ํ•ด mcp-tablet-gateway์— DEV_MODE=true ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์‹œ ๊ณ ์ • ํ† ํฐ DEV_TOKEN๋กœ ๋กœ๊ทธ์ธ ํ—ˆ์šฉ.


5. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

Python (orchestrator, bridge, mcp-robot-adapter)

cd services/orchestrator
uv run pytest -v --cov=src

# ๊ฐ€๋“œ๋ ˆ์ผ fuzz ํ…Œ์ŠคํŠธ
uv run pytest tests/test_guardrail.py::test_reject_invalid_velocity -v

Node.js (mcp-tablet-gateway)

cd services/mcp-tablet-gateway
npm test

React Native (tablet-app)

cd apps/tablet
npm test
npm run e2e:ios   # detox iOS

6. ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ โ€” Phase ๊ฒŒ์ดํŠธ

๊ฐ Phase ์ง„์ž… ์ „ ๊ฒ€์ฆํ•  ์‹œ๋‚˜๋ฆฌ์˜ค. ๋ชจ๋‘ mock์œผ๋กœ ๊ฐ€๋Šฅ. (๋กœ์ปฌ ํ™˜๊ฒฝ์„ ์ฒ˜์Œ ์„ธ์šฐ๋Š” ๋‹จ๊ณ„๋ณ„ ์ค€๋น„๋Š” ยง0 ์ฐธ์กฐ.)

Phase ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ
0 mosquitto + bridge + fake-frigate โ†’ vision/zone/enter ๋ฐœํ–‰ ํ™•์ธ
1 + orchestrator + fake-spot โ†’ Claude tool call โ†’ MockAdapter ๋™์ž‘
2 + ์‹ค SPOT (์‚ฌ๋ฌด์‹ค ๋ฏธ๋Ÿฌ) โ€” fake-frigate ์œ ์ง€
3 + tablet-app + fake-tablet HTML โ†’ ์˜ต์…˜ ์„ ํƒ round-trip
4 4 zone ร— 2 robot ๋™์‹œ ์‹œ๋‚˜๋ฆฌ์˜ค (ํ˜ผ์žก ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
5 LLM(Large Language Model, ๋Œ€๊ทœ๋ชจ ์–ธ์–ด ๋ชจ๋ธ) ๋‹จ์ ˆ ์‹œ ํด๋ฐฑ ์‹œ๋‚˜๋ฆฌ์˜ค 4์ข… ๋ชจ๋‘ ๋ฐœ๋™

์ž๋™ํ™”: tests/integration/run-phase-N.sh ์Šคํฌ๋ฆฝํŠธ๋กœ mock ํ™˜๊ฒฝ ๋ถ€ํŒ… + ์‹œ๋‚˜๋ฆฌ์˜ค ์‹คํ–‰ + assert.


7. ์ฝ”๋“œ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ (CI ํ†ต๊ณผ ๊ธฐ์ค€)

๊ฒ€์‚ฌ ๋„๊ตฌ ๊ธฐ์ค€
Python lint ruff check 0 violation
Python type mypy --strict 0 error
Python test pytest + coverage ํ†ต๊ณผ + coverage > 70%
TS lint eslint 0 error
TS type tsc --noEmit 0 error
TS test vitest / jest ํ†ต๊ณผ
์ปจํ…Œ์ด๋„ˆ vuln Trivy CRITICAL/HIGH 0 (๋˜๋Š” ๋ช…์‹œ์  ignore)
pre-commit trailing whitespace, large file ๋“ฑ ํ†ต๊ณผ

8. ์ž์ฃผ ๋ง‰ํžˆ๋Š” ์ง€์  (Troubleshooting)

์ฆ์ƒ ์›์ธ / ํ•ด๊ฒฐ
orchestrator๊ฐ€ host.docker.internal:8123 ์ ‘๊ทผ ์‹คํŒจ extra_hosts ๋ˆ„๋ฝ โ€” compose์— host-gateway ๋งคํ•‘ ํ™•์ธ
mosquitto์— connection refused passwd ํŒŒ์ผ ๊ถŒํ•œยทuid ํ™•์ธ (chown 1883:1883 passwd)
Frigate๊ฐ€ RTSP์—์„œ stream ๋ชป ๋ฐ›์Œ ffprobe rtsp://... ๋กœ ์ปจํ…Œ์ด๋„ˆ ๋ฐ–์—์„œ ๋จผ์ € ๊ฒ€์ฆ
bridge๊ฐ€ vision/zone/enter ๋ฐœํ–‰ ์•ˆ ํ•จ frigate/events ๋ผ์ด๋ธŒ ํ…Œ์ผ ํ™•์ธ โ†’ ReID(Re-identification, ๋ณดํ–‰์ž ์žฌ์‹๋ณ„) registry ๋งค์นญ ์‹คํŒจ ๊ฐ€๋Šฅ (mock ํ† ํฐ ์‚ฌ์šฉ ๊ถŒ์žฅ)
Claude tool call์ด ๋ฌดํ•œ ๋ฐ˜๋ณต guardrail์—์„œ ๊ฑฐ๋ถ€ โ†’ orchestrator๊ฐ€ ๋นˆ ์‘๋‹ต์œผ๋กœ ์žฌ์‹œ๋„ โ†’ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— "๊ฑฐ๋ถ€ ์‹œ ์ข…๋ฃŒ" ๋ช…์‹œ
SPOT ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ธ์ฆ ์‹คํŒจ bosdyn-client๋Š” simulator์˜ bosdyn:bosdyn ๊ธฐ๋ณธ ์ž๊ฒฉ ์‚ฌ์šฉ โ€” .env.dev์— ๋ช…์‹œ

9. ์‚ฌ๋ฌด์‹ค ๋ฏธ๋Ÿฌ ํ™˜๊ฒฝ (Phase 2 ์ง„์ž…)

์ „์‹œ์žฅ ์ง„์ž… ์ „ ๊ฒ€์ฆํ•  ๋ฏธ๋‹ˆ ํ™˜๊ฒฝ:

  • ์นด๋ฉ”๋ผ 1๋Œ€ (ํŽ˜์–ด๋งยทํ…Œ์ŠคํŠธ์šฉ)
  • SPOT 1๋Œ€ (์‚ฌ๋ฌด์‹ค ํ‰์ง€)
  • ํƒœ๋ธ”๋ฆฟ 1๋Œ€
  • mosquitto/HA/orchestrator/bridge/robot-adapter/tablet-gateway ํ’€ ์Šคํƒ

์ „์‹œ์žฅ ํ™˜๊ฒฝ๊ณผ์˜ ์ฐจ์ด๋ฅผ tests/integration/MIRROR_DIFFS.md์— ๊ธฐ๋ก โ†’ ์ฐจ์ด๊ฐ€ ์œ„ํ—˜์„ ๋งŒ๋“ค์ง€ ์•Š๋Š”์ง€ ๊ฒ€ํ† .


10. ๋””๋ฒ„๊น… ๋„๊ตฌ

์šฉ๋„ ๋„๊ตฌ
MQTT(Message Queuing Telemetry Transport, ๋ฉ”์‹œ์ง€ ํ ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ์ „์†ก ํ”„๋กœํ† ์ฝœ) ๋ผ์ด๋ธŒ ํ…Œ์ผ mosquitto_sub -h localhost -t '#' -v
trace_id ์ถ”์  Loki query `{service="orchestrator"}
Claude ํ˜ธ์ถœ ๋ถ„์„ LangSmith (์„ ํƒ) ๋˜๋Š” LOG_LEVEL=debug ์‹œ stdout
HA entity ์ƒํƒœ http://localhost:8123 Developer Tools โ†’ States
์ปจํ…Œ์ด๋„ˆ โ†” ํ˜ธ์ŠคํŠธ ํ†ต์‹  nc -zv host.docker.internal 8123

Clone this wiki locally