음성으로 산업용 협동로봇팔을 제어하는 통합 시스템. ROS 2 + LLM 에이전트 + 음성 파이프라인을 모노레포로 통합한 Phase 1 MVP 구현체입니다.
- Phase 1 MVP (2026-04-30) — UR5 실기 + 마이크 + LLM 에이전트 + TTS 통합 시연 통과. 12 발화 시나리오 전부 정합 (실기 픽플 3회 / multi-turn 회상 / ask_clarification / 자연 응답 매칭 / 한국어 숫자 정규화).
- Phase 1.5-α (2026-05-01) — 시연 자동화. 6 Terminal → 1 Terminal 단일 launch 통합. yaml-driven 4-tier launch 우선순위 (CLI > env > PC IP auto-detect > sample fallback). LLM warm-up 1160ms.
- Phase 1.5-β (2026-05-02) — Docker 컨테이너화 통과. 동료 PC
git pull직후 즉시 진입 가능. 환경 의존성 0.
사용자: "안녕"
로봇: "안녕하세요. UR5 로봇 팔 준비 되었습니다."
사용자: "1번에서 5번으로 옮겨줘"
로봇: "1번 위치에서 5번 위치로 옮기는 거 맞을까요?"
사용자: "그렇게 해줘"
로봇: (UR5 픽-플레이스 진행) "1번에서 5번으로 옮겼습니다."
사용자: "9번 위치도 있어?"
로봇: "1번부터 8번 위치까지만 사용할 수 있어요. 다른 위치를 알려주세요."
사용자: "아까 어디로 옮겼지?"
로봇: "1번에서 5번으로 옮겼습니다."
사용자: "멈춰"
로봇: (즉시 정지)
LLM이 multi-turn context를 유지하면서 자연 응답("그렇게 해줘" / "어 그렇게 하면 돼")을 yes로, 취소 응답("아 미안 다르게 해줘")을 no로 매칭합니다. 8 슬롯 외 위치 입력 시 명확화 질문이 자동 발동합니다.
# 1. NVIDIA Driver (580.x 권장) — nvidia-smi 통과 검증
nvidia-smi
# 2. Docker + nvidia-container-toolkit
sudo apt install -y docker.io docker-compose-plugin nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
sudo usermod -aG docker $USER
newgrp docker
docker compose version # v2.x 통과 검증git clone https://lab.ssafy.com/s14-final/S14P31S303.git ~/workspaces/voice_robot
cd ~/workspaces/voice_robot
git checkout develop && git pull
cp .env.example .env
# .env 열어 GMS_KEY 채우기 (정지용이 별도 공유)hostname -I # 본인 PC IP 확인
ls omni_kit/config/network/ # 사용자별 yaml 목록| 사용자 | PC IP | 매칭 yaml |
|---|---|---|
| 정지용 | 192.168.1.101 |
jjy.yaml ✅ |
| 손동완 | 192.168.1.100 |
lab.yaml ✅ |
| 김경한 | 192.168.1.102 |
kimkh.yaml ⏳ (합의 후 추가 예정) |
본인 IP 매칭 yaml이 없으면 두 가지 선택지:
# A. 기존 yaml로 강제 매칭 (env override)
echo "VOICE_ROBOT_PROFILE=lab" >> .env
# B. 신규 yaml 추가
cd omni_kit/config/network
cp sample.yaml <본인이름>.yaml
# pc.ip_cidr / pc.ethernet_interface 본인 환경값 채움PolyScope 펜던트에서 본인 PC IP를 등록해야 외부 제어가 활성화됩니다.
- PolyScope 메인 화면 → Installation 탭
- 좌측 메뉴 → URCaps → External Control
- Host IP 필드에 본인 PC IP 입력 (예:
192.168.1.101) - Host port =
50002(default 그대로) - 우상단 Save Installation — 변경 영구 저장
- Program 탭으로 이동
- 기존 program이 외부 제어 노드를 포함하면 그대로 / 없으면 Add → URCaps → External Control 추가
- Save Program
검증 — 컨테이너 안 launch terminal에 다음 로그가 출력되어야 정합:
[ur_ros2_control_node-1] [INFO] Robot connected to reverse interface. Ready to receive control commands.
함정 인지:
- Host IP가 본인 PC IP와 일치하지 않으면 sentinel이 무한 대기합니다 (
waiting for /io_and_status_controller/robot_program_running). - Save를 누르지 않으면 펜던트 재부팅 시 옛 IP로 복귀합니다.
ping -c 3 192.168.1.11 # UR5
ping -c 3 192.168.1.1 # OnRobot Compute Box둘 다 0% loss여야 정합.
docker compose -f docker-compose.voice.yml build최종 개발 이미지는 voice_robot:dev 하나를 사용한다. voice/RealSense/RViz 흐름 모두 repo를 컨테이너 안 /workspace에 mount하고 ROS workspace는 /workspace/omni_kit로 맞춘다.
소요 분해:
- Layer 1 (apt: ROS 2 Humble + RViz + ffmpeg + 빌드 도구) — 약 5~7분
- Layer 2 (pip: faster-whisper + edge-tts + anthropic 등) — 약 2~3분
- Layer 2b (RealSense DDS-enabled pyrealsense2 + debug tools) — 장비/네트워크 상태에 따라 수분 이상
- Layer 3 (vcs import + 외부 패키지 패치 + rosdep) — 약 3~5분
- Layer 4 (외부 패키지 colcon build — UR Driver / OnRobot) — 약 3~5분
호스트에서 직전에 colcon build를 시도한 잔재(다른 절대경로) 또는 다른 환경에서 만든 build artifact가 남아있으면 컨테이너 안 빌드와 충돌합니다. 처음 진입 시 호스트 shell에서 한 번 정리합니다.
# 호스트 shell (컨테이너 진입 전, 첫 진입 시점만)
rm -rf omni_kit/build omni_kit/install omni_kit/log
⚠️ 호스트 shell에서colcon build직접 실행 금지. 본 컨테이너는 호스트의 ROS 2 / CUDA 환경 의존성 0이 의도이며, 빌드는 반드시 컨테이너 안에서만 실행해야 절대경로(/workspace/omni_kit/...)가 정합합니다.
colcon build 박은 후 마지막 line 검증:
Summary: 26 packages finished [Xm Ys]
Failed / Aborted 박힌 시점 = launch 박지 마. 부분 빌드(특히 Ctrl+C 박은 시점)가 박힌 시점 Python ament 패키지(stt_node / tts_node / llm_agent / ur5_voice_app / ur5_waypoint_controller)의 setup.py develop 흐름 통과 X = egg-link metadata 누락 → launch 진입 시 PackageNotFoundError: No package metadata was found for <pkg> 함정. stderr 본문 검토 박은 후 별 분석.
launch 박기 직전 펜던트 PolyScope 박은 거 검증:
- External Control 프로그램이 PolyScope에 박혀있는 거 검증 (Program 탭 — 본인이 직전에 Add → URCaps → External Control 박은 거)
- External Control 프로그램 안 Host IP = 본인 PC IP 박혀있는 거 검증 (예:
192.168.1.100) - ▶ Play 박은 후 PolyScope 화면에
Robot connected to reverse interface. Ready to receive control commands.박힌 거 검증
박지 마 박힌 시점 launch 박힌 후 [ur_ros2_control_node-1] FATAL Could not get configuration package within timeout 박힘 — Dashboard(29999) 진입은 통과해도 RTDE(30004) 진입은 펜던트 ▶ Play 박혀야만 통과.
# 컨테이너 기동
docker compose -f docker-compose.voice.yml up -d
docker compose -f docker-compose.voice.yml exec voice_robot bash
# 컨테이너 안 — 자동으로 cwd /workspace/omni_kit 진입 + ROS 2 환경 source
colcon build --symlink-install # 첫 진입 또는 코드 변경 시 (~3~5분, 26 packages)
source install/setup.bash
ros2 launch ur5_voice_app phase1_mvp.launch.pylaunch가 sentinel wait 상태에 진입하면 5분 안에 펜던트 ▶ Play를 누르세요. 5분을 넘기면 펜던트의 External Control이 자체 stop됩니다 (idle timeout).
펜던트 ▶ Play 후 다음이 자동 진행됩니다:
- sentinel exit
- voice 노드 chain 진입 (STT / LLM / TTS)
- LLM warm-up 호출 (~1.2초)
- Greeting 발화 — "안녕하세요. UR5 로봇 팔 준비 되었습니다."
docker compose -f docker-compose.voice.yml exec voice_robot bash
ros2 run stt_node audio_stub --mic발화 패턴: Enter → 발화 → Enter (push-to-talk).
# 호스트 shell
docker compose -f docker-compose.voice.yml downGPU 없는 EC2 / 동료 PC 는 별도 compose 파일로 진입:
docker compose -f docker-compose.voice.cpu.yml up -d --buildCPU 변형은 BASE_IMAGE=ubuntu:22.04 로 build (CUDA base 우회) 하고, stt_node 는 STT_* env override 로 device=cpu / compute_type=int8 / beam_size=1 / cpu_threads=4 / condition_on_previous_text=false / without_timestamps=true 가 declare-time 박힘 (코드 변경 0건). 상세 흐름·env 표·지연 측정 흐름은 README_DOCKER.md §2.9 참조.
| 구성 요소 | 모델 | 비고 |
|---|---|---|
| 로봇팔 | Universal Robots UR5 (CB3) | PolyScope 3.15.8 |
| 그리퍼 | OnRobot 2FG7 | 외부 그립 |
| 그리퍼 컨트롤러 | OnRobot Compute Box | Modbus TCP (port 502) |
| 비전 (Phase 3 R&D) | OnRobot Eyes (RealSense D415 기반) | Phase 1 미사용 (eye-in-hand 마운트만 유지) |
| 제어 PC | Ubuntu 22.04 + RTX 4070 (8GB) | NVIDIA Driver 580.x, swap 16GB 권장 |
| 장비/PC | IP | 비고 |
|---|---|---|
| UR5 | 192.168.1.11 |
2026-05-01 lab 재구성 (.102 → .11) |
| OnRobot Compute Box | 192.168.1.1 |
변경 없음 |
| 정지용 PC | 192.168.1.101 |
jjy.yaml |
| 손동완 PC | 192.168.1.100 |
lab.yaml |
| 김경한 PC | 192.168.1.102 |
kimkh.yaml (합의 후 추가) |
| 영역 | 기술 |
|---|---|
| OS | Ubuntu 22.04 LTS |
| ROS | ROS 2 Humble |
| Robot driver | ur_rtde 1.6.3 + URScript + Universal_Robots_ROS2_Driver |
| Gripper | pymodbus + OnRobot Compute Box Modbus TCP |
| STT | faster-whisper 1.2.1 (large-v3-turbo / CUDA float16) |
| LLM | Claude Sonnet 4.5 (SSAFY GMS gateway 경유) |
| TTS | edge-tts (ko-KR-SunHiNeural) |
| VAD | faster-whisper 내장 VAD (Phase 1.5+ Silero VAD 도입 예정) |
| Container | Docker 28.x + Compose v2 + nvidia-container-toolkit |
| Base image | nvidia/cuda:12.6.3-cudnn-runtime-ubuntu22.04 |
CUDA 12 base는 faster-whisper의 CTranslate2 4.7.1 wheel이 cuBLAS 12 + cuDNN 9에 의존하기 때문입니다. host NVIDIA Driver 580.x는 CUDA 12와 13 모두 호환되므로 host 환경 추가 셋업은 불필요합니다.
voice_robot/ # 모노레포 root (저장소명은 S14P31S303)
├── README.md # 이 문서
├── docker-compose.voice.yml # Phase 1.5-β voice 컨테이너 (standalone)
├── docker-compose.yml # 웹 트랙 stack (postgres / redis / mosquitto / backend / ai-agent / sensevoice / nginx) — 별도 트랙
├── Dockerfile # voice 컨테이너 image 빌드 — 4-layer (apt / pip / vcs / colcon)
├── .env.example # 환경 변수 템플릿
│
├── omni_kit/ # ROS 2 워크스페이스
│ ├── dependencies.repos # 외부 ROS 2 패키지 (UR Driver / OnRobot ROS 2 / ros2_control)
│ ├── requirements.txt # Python 의존성
│ ├── config/
│ │ └── network/ # 사용자별 yaml (PC IP / robot IP / gripper IP 매핑)
│ │ ├── sample.yaml # 신규 합류 템플릿
│ │ ├── jjy.yaml # 정지용 (192.168.1.101)
│ │ └── lab.yaml # 손동완 (192.168.1.100)
│ └── src/
│ ├── interfaces/
│ │ └── voice_robot_interfaces/ # msg / srv / action 정의
│ ├── core/
│ │ ├── robot_arm_base/ # RobotArm / GripperDriver 추상
│ │ ├── waypoint_store/ # YAML 기반 위치 저장소
│ │ ├── safety_guard/ # workspace bound / vel·acc 제한
│ │ └── scenario_runner/ # 시나리오 액션 서버
│ ├── voice/
│ │ ├── audio_gateway/ # 마이크 → /audio/utterance
│ │ ├── stt_node/ # faster-whisper STT
│ │ ├── llm_agent/ # Claude Sonnet 4.5 + tool use 라우팅
│ │ └── tts_node/ # edge-tts → 스피커
│ ├── robots/
│ │ └── ur5/ # UR5 driver / gripper / waypoint controller
│ ├── vision/ # Phase 3 R&D (object_detector / realsense_bringup)
│ ├── apps/
│ │ └── ur5_voice_app/ # phase1_mvp.launch.py (통합 진입점)
│ └── external/ # vcs import target (UR Driver 등 — gitignore)
│
├── web/ # 웹 트랙 (Phase 1.5+ 통합 예정)
│ ├── docs/ # 웹 트랙 문서
│ └── sensevoice/ # sensevoice STT 백엔드 (Phase 1.5+ Hybrid 흐름)
│
├── mosquitto/ # MQTT broker config (웹 트랙 stack 의존)
│ └── config/
│
├── docs/ # 다이어그램 / 부가 문서
└── scripts/
└── setup/
├── docker_entrypoint.sh # 컨테이너 진입 시 ROS 2 3-layer overlay
├── post_vcs_import.sh # 외부 패키지 자동 patch (UR Driver RTDE recipe vs OnRobot URCap IO 충돌)
└── voice_stack_cleanup.sh # 시연 cleanup (vsclean)
[마이크 (audio_stub --mic)]
│ /audio/utterance (PCM 16kHz)
▼
[stt_node — faster-whisper large-v3-turbo (GPU)]
│ /voice/transcript (한국어 텍스트)
▼
[llm_agent — Claude Sonnet 4.5 + tool use (multi-turn 누적)]
│ tool_use blocks: speak / confirm_with_user / ask_clarification / pick_and_place / emergency_stop
▼
[RealToolClient]
│ /ur5_command (slot key — "15" = pick_1 → place_1)
▼
[ur5_waypoint_controller (slot_runner)]
│ trajectory_msgs/JointTrajectory
▼
[ur_robot_driver — UR Driver passthrough_trajectory_controller]
│ RTDE / URScript (TCP 30001~30004)
▼
[UR5 CB3 + OnRobot 2FG7]
(병렬)
[llm_agent] ──► /tts/text ──► [tts_node — edge-tts] ──► sounddevice ──► 스피커
| 함정 | 회피 |
|---|---|
| 펜던트 External Control idle timeout | 펜던트 ▶ Play 후 5분 안에 첫 발화 (5분 넘기면 펜던트가 자체 stop). idle 후 복구는 펜던트 ▶ Play 다시 누르면 됨 |
| PC IP yaml 매칭 실패 | .env에 VOICE_ROBOT_PROFILE=<name> 명시 또는 신규 yaml 추가. 매칭 없으면 sample.yaml로 fallback (실기 IP와 어긋날 수 있음) |
| host PulseAudio 미가동 | Ubuntu desktop session에서는 자동 가동. 헤드리스 SSH 진입 시 export XDG_RUNTIME_DIR=/run/user/$(id -u) 후 docker compose up |
docker compose exec bash 진입 시 ROS 2 환경 누락 |
자동 해결 — Dockerfile의 ~/.bashrc가 3-layer overlay (ROS 2 base / image-baked external / host bind-mount workspace) 자동 source. cwd /workspace/omni_kit 자동 진입 |
다른 PC에서 clone 직후 빌드 실패 (절대경로 mismatch / tesla_ws·gae_ws 등 옛 workspace not found) |
호스트 shell의 직전 colcon build 잔재 또는 호스트 ~/.bashrc의 옛 ROS workspace source가 원인. 첫 진입 시 호스트 shell에서 rm -rf omni_kit/build omni_kit/install omni_kit/log 후 컨테이너 안에서 빌드. 호스트에서 colcon build 직접 실행 금지 |
Python PackageNotFoundError (모든 노드) |
colcon build 박은 후 빌드 통과 검증 박지 마 박은 시점 (Failed / Aborted 또는 Ctrl+C 부분 빌드). Summary: 26 packages finished 검증 박은 후 launch |
UR Driver Could not get configuration package within timeout |
펜던트 External Control 프로그램 ▶ Play 박지 마 박힘 또는 program 안 Host IP가 본인 PC IP 박지 마 박힘. 펜던트 사전 검증(Host IP / ▶ Play / Robot connected... 로그) 박은 후 launch |
| GPU 없는 동료 PC | 본 컨테이너는 nvidia/cuda base + faster-whisper GPU 의존 = GPU 필수. GPU 없는 환경은 Phase 1.5+ sensevoice 백엔드 통합 후 진입 가능 |
| 트랙 | 담당 | 영역 |
|---|---|---|
| voice | 정지용 | omni_kit/src/voice/ (audio_gateway / stt_node / llm_agent / tts_node) + 통합 책임 |
| robots | 손동완 | omni_kit/src/robots/ur5/ (driver / gripper / waypoint controller) |
| vision | 김경한 | omni_kit/src/vision/ (object_detector / realsense_bringup — Phase 3 R&D 진입 흐름, Phase 1 시연 범위 외) |
| web | 웹 개발자 2명 | web/ (안드로이드 클라이언트 / 백엔드 / sensevoice STT / 모니터링) |
영역 자율성 원칙: 영역 외 코드는 직접 수정하지 않고 통보 후 담당자가 직접 갱신합니다.
- feature 브랜치 push만 허용. develop 직접 push는 금지.
- force push 금지 (
--force-with-lease만 허용, 단 main / develop에는 금지). --no-verify(hook 우회) 금지.- MR 통과 흐름: feature push → GitLab MR 생성 → reviewer 승인 → develop 머지 (Squash 미사용) → 로컬 cleanup (
git checkout develop && git pull && git branch -d <feature>).
Docker 흐름이 표준이지만, host에 ROS 2 Humble + CUDA 12 + dependencies가 사전 설치된 환경에서는 host에서 직접 빌드도 가능합니다.
cd ~/workspaces/voice_robot/omni_kit
vcs import src/external < dependencies.repos
bash ../scripts/setup/post_vcs_import.sh
rosdep install --from-paths src --ignore-src -r -y --rosdistro humble
colcon build --symlink-install
source install/setup.bash
ros2 launch ur5_voice_app phase1_mvp.launch.pySSAFY 캡스톤 산학 협력 프로젝트 (S14P31S303). 시연·교육·포트폴리오 목적.