-
Notifications
You must be signed in to change notification settings - Fork 0
DEVELOPMENT
๋ก์ปฌ์์ SPOTยท์ค ์นด๋ฉ๋ผยท์ค ํ๋ธ๋ฆฟ ์์ด ์ฝ๋๋ฅผ ์์ฑยท๊ฒ์ฆํ๋ ์ ์ฐจ. CI ํต๊ณผ + ์ฌ๋ฌด์ค ๋ฏธ๋ฌ ํ๊ฒฝ ๊ฒ์ฆ์ ๊ฑฐ์ณ ์ ์์ฅ์ ๋ฐฐํฌํ๋ค.
SPOTยท์ค ์นด๋ฉ๋ผยท์ค ํ๋ธ๋ฆฟ ์์ด ๋ก์ปฌ์์ ํ ์คํ์ ์ฒ์ ๋์ฐ๊ธฐ๊น์ง ์์๋๋ก ์ ๊ฒํ๋ ๋ถํธ์คํธ๋ฉ ์ฒดํฌ๋ฆฌ์คํธ. ๊ฐ ํญ๋ชฉ์ ์์ธ ์ ์ฐจ๋ ์๋ ํด๋น ์น์ ์ ๋ฐ๋ฅธ๋ค. ์ด ์ฒดํฌ๋ฆฌ์คํธ์ ์ข ์ฐฉ์ = ยง6 Phase 0 ๊ฒ์ดํธ(
mosquitto + bridge + fake-frigate โ vision/zone/enter ๋ฐํ ํ์ธ).ํ๊ธฐ:
[ ]๊ฐ์ถ๊ฑฐ๋ ๋ง๋ค ๊ฒ ยท[?]ํ์ธยท์ ๊ฒํ ๊ฒ.
- 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 ๊ฒ์ฆ ์์๋ง
- ๋ชจ๋
ธ๋ ํฌ clone +
pre-commit install - Python ์๋น์ค
uv sync --dev(orchestratorยทbridgeยทmcp-robot-adapter) - Node ์๋น์ค
npm ci(mcp-tablet-gatewayยทapps/tablet)
-
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์ ํฌํจ๋๋์ง โ ์ํฌ๋ฆฟ ์ปค๋ฐ ๊ธ์ง
-
docker-compose.dev.yml๋ก mosquitto ๊ธฐ๋ - passwd ํ์ผ ๊ถํยทuid (
chown 1883:1883 passwd, ยง8) - ACL ๋ก๋ โ ๊ฐ ์๋น์ค ๊ณ์ ์ด ํ์ํ ํ ํฝ๋ง ์ฌ์ฉํ๋๋ก (SERVICES.md ยง1)
- [?]
mosquitto_sub -h localhost -t '#' -v๋ก ์ฐ๊ฒฐ ํ์ธ
- HA ์ปจํ
์ด๋ ๊ธฐ๋ +
configuration.yaml๊ธฐ๋ณธ๊ฐ ๋ก๋ - HA์ MQTT integration์ด mosquitto์ ์ฐ๊ฒฐ๋๋์ง (Settings โ Devices & Services)
- recorder(SQLite) ๋์ ํ์ธ โ HA๊ฐ ์ํ ์ ์ฅ ๊ณ์ธต์
- [?]
http://localhost:8123Developer Tools โ States ์ ๊ทผ ๊ฐ๋ฅ
-
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.htmlWSS ์ฐ๊ฒฐ โ Phase 3 ์ง์ ์ (ยง4.3)
-
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๋ก ํ์ธํ ์ ์๋ค.
| ํญ๋ชฉ | ๊ถ์ฅ ์ฌ์ |
|---|---|
| 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 |
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 ../..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 --buildmcp-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"}์ค์ 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๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ ๊ฐ๋จ 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๋ก ๋ก๊ทธ์ธ ํ์ฉ.
cd services/orchestrator
uv run pytest -v --cov=src
# ๊ฐ๋๋ ์ผ fuzz ํ
์คํธ
uv run pytest tests/test_guardrail.py::test_reject_invalid_velocity -vcd services/mcp-tablet-gateway
npm testcd apps/tablet
npm test
npm run e2e:ios # detox iOS๊ฐ 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.
| ๊ฒ์ฌ | ๋๊ตฌ | ๊ธฐ์ค |
|---|---|---|
| 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 ๋ฑ | ํต๊ณผ |
| ์ฆ์ | ์์ธ / ํด๊ฒฐ |
|---|---|
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์ ๋ช
์ |
์ ์์ฅ ์ง์ ์ ๊ฒ์ฆํ ๋ฏธ๋ ํ๊ฒฝ:
- ์นด๋ฉ๋ผ 1๋ (ํ์ด๋งยทํ ์คํธ์ฉ)
- SPOT 1๋ (์ฌ๋ฌด์ค ํ์ง)
- ํ๋ธ๋ฆฟ 1๋
- mosquitto/HA/orchestrator/bridge/robot-adapter/tablet-gateway ํ ์คํ
์ ์์ฅ ํ๊ฒฝ๊ณผ์ ์ฐจ์ด๋ฅผ tests/integration/MIRROR_DIFFS.md์ ๊ธฐ๋ก โ ์ฐจ์ด๊ฐ ์ํ์ ๋ง๋ค์ง ์๋์ง ๊ฒํ .
| ์ฉ๋ | ๋๊ตฌ |
|---|---|
| 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 |
- Google ADK ๊ฒํ (ํ๋ ์์ํฌยท๋ชจ๋ธ ๋น๊ต)
- ํ๋ฅด์๋ + ์ํฉ ์นด๋ ๋ชจ๋ธ (2์ฐจ ์๋๋ฆฌ์ค ๊ตฌ๋ ๋ฐฉ์)
- Mac Studio ๋ก์ปฌ LLM + Claude Opus ํ์ด๋ธ๋ฆฌ๋ ๋ผ์ฐํ ์ ๋ต
- ๊ฐ๋ฐ ์ผ์ ยทํ ์ผ ์ ๊ฒํ (๋จ๊ณ๋ณ ์งํ ์ ๊ฒ)
- ๋ก๋ด Adapter ๊ฐ๋ฐ ์จ๋ณด๋ฉ (HAยทMQTT ๊ณ์ ๋ฑ๋ก, ํ ํฝ ์ ์ฉ, ํ ์คํธ ์ก์์ , ์ํ ํ์ธ)