로봇 키트
로보틱스 툴킷: drive, sense, look, listen, speak, emote 도구와 독립형 안전 모니터.
로봇 키트(crates/robot-kit)는 Revka 에이전트를 물리적 로봇의 두뇌로 변환합니다. 이 크레이트는 독립형 Rust 크레이트로, drive, sense, look, listen, speak, emote의 여섯 가지 에이전트 도구와 AI가 요청하는 모든 움직임을 재정의할 수 있는 독립형 SafetyMonitor를 제공합니다. Raspberry Pi 배포 환경에서 오프라인 추론(비전용 Ollama, 청취용 Whisper.cpp, 음성용 Piper)을 사용하도록 설계되었으나, 모든 도구에 mock 모드가 있어 하드웨어 없이도 개발 및 테스트할 수 있습니다.
자연어 에이전트 호출로 제어하는 구현체 로봇(로버, 장난감, 순찰 봇 등)을 만들려면 이 페이지를 참고하세요. 유선 마이크로컨트롤러 GPIO(Pico, Arduino, Nucleo) 및 Aardvark 어댑터에 대해서는 GPIO 도구 및 Aardvark I2C/SPI/GPIO를 참조하세요. Pi에서 Revka 자체를 실행하는 방법은 Raspberry Pi (셀프 호스팅)를 참조하세요.
아키텍처
섹션 제목: “아키텍처”로봇 키트는 에이전트 루프와 하드웨어 사이에 위치합니다. 에이전트가 도구를 호출하면 모든 드라이브 명령은 독립적인 태스크로 실행되는 SafetyMonitor를 통해 전달되며, SafetyMonitor가 로봇의 이동 여부를 최종적으로 결정합니다.
Revka AI Brain ──tool calls──▶ Robot Kit tools drive · look · listen · speak · sense · emote │ ▼ SafetyMonitor (independent task) pre-move check · speed limiting · bump · watchdog · E-stop │ ▼ Hardware: motors · camera · mic · speaker · LIDAR · LEDs크레이트의 핵심 원칙: AI는 이동을 요청할 수 있지만, SafetyMonitor가 이를 허용합니다. 모델이 환각을 일으키더라도 안전이 항상 우선입니다.
이 키트는 독립적으로 사용할 수 있도록 경량 Tool 트레이트를 자체 정의하지만, 도구는 Revka의 에이전트 루프와도 호환됩니다. 이 크레이트는 워크스페이스 멤버이며 코어 런타임에 자동으로 등록되지 않습니다 — Rust에서 직접 연결해야 합니다(에이전트에 연결하기 참조).
빌드 및 피처 플래그
섹션 제목: “빌드 및 피처 플래그”크레이트를 워크스페이스 패키지로 빌드합니다:
cargo build -p revka-robot-kit --release| 피처 | 기본값 | 활성화 내용 |
|---|---|---|
safety | on | SafetyMonitor 백그라운드 태스크 및 SafeDrive 래퍼. 강력히 권장하며 활성화 상태를 유지하세요. |
gpio | off | rppal을 통한 직접 GPIO 제어 (Raspberry Pi / Linux 전용). |
ros2 | off | ROS2 통합 빌드 플래그. |
lidar | off | LIDAR 지원 빌드 플래그. |
vision | off | 카메라 + 비전 모델 빌드 플래그. |
설정 (robot.toml)
섹션 제목: “설정 (robot.toml)”모든 하드웨어 및 제한 설정은 robot.toml에 있습니다. 크레이트에서 템플릿을 복사한 후 로봇에 맞게 수정하세요:
mkdir -p ~/.revkacp crates/robot-kit/robot.toml ~/.revka/최소한의 mock 모드 설정은 다음과 같습니다:
[drive]backend = "mock" # "ros2" | "serial" | "gpio" | "mock"ros2_topic = "/cmd_vel"serial_port = "/dev/ttyACM0"max_speed = 0.5 # m/smax_rotation = 1.0 # rad/s
[camera]device = "/dev/video0" # or "picam" for the Pi Camera Modulewidth = 640height = 480vision_model = "moondream" # "llava" | "moondream" | "none"ollama_url = "http://localhost:11434"
[audio]mic_device = "default"speaker_device = "default"whisper_model = "base" # "tiny" | "base" | "small"whisper_path = "/usr/local/bin/whisper-cpp"piper_path = "/usr/local/bin/piper"piper_voice = "en_US-lessac-medium"
[sensors]lidar_port = "/dev/ttyUSB0"lidar_type = "mock" # "rplidar" | "ydlidar" | "ros2" | "mock"motion_pins = [17, 27] # BCM, PIR sensorsultrasonic_pins = [23, 24] # BCM (trigger, echo); omit to disable
[safety]min_obstacle_distance = 0.3 # meters — robot will not move closer than thisslow_zone_multiplier = 3.0 # start slowing at 0.3 × 3.0 = 0.9 mapproach_speed_limit = 0.3 # fraction of max speed near obstaclesmax_drive_duration = 30 # seconds — watchdog auto-stop windowsensor_timeout_secs = 5 # block movement if sensors go stale this longblind_mode_speed_limit = 0.2 # speed cap when sensors are mock/unavailableestop_pin = 4 # BCM — physical emergency-stop buttonbump_sensor_pins = [5, 6] # BCM — chassis microswitchesbump_reverse_distance = 0.15 # meters to back up after a bumppredict_collisions = true # trajectory prediction (needs LIDAR)confirm_movement = false # require verbal confirmation before each movedrive — 이동
섹션 제목: “drive — 이동”drive는 전방향 이동을 제어합니다. 자연어 동작을 네 가지 모터 백엔드 중 하나에 매핑하고, 요청한 거리와 설정된 속도 제한을 기반으로 이동 시간을 계산합니다.
drive(action="forward", distance=1.0)drive(action="rotate_left", distance=90)drive(action="left", distance=0.5, speed=0.3)drive(action="stop")| 필드 | 타입 | 기본값 | 의미 |
|---|---|---|---|
action | string | — (필수) | forward, backward, left, right (측면 이동), rotate_left, rotate_right, stop, 또는 custom. |
distance | number | 0.5 m / 90° | 직선 이동 시 미터 단위, 회전 시 도(°) 단위. |
speed | number | 0.5 | 속도 배율 0.0–1.0, drive.max_speed에 의해 스케일됨. |
linear_x / linear_y / angular_z | number | 0.0 | action="custom" 시 원시 속도값 (-1.0에서 1.0). |
stop은 항상 즉시 실행되며 차단되지 않습니다. 드라이브 명령은 초당 하나로 속도가 제한되고, 모든 이동 시간은 safety.max_drive_duration으로 제한되어 단일 명령이 무한 실행되지 않습니다.
드라이브 백엔드 (drive.backend):
| 백엔드 | 동작 |
|---|---|
ros2 | ros2 topic pub을 통해 설정된 ros2_topic(기본값 /cmd_vel)으로 geometry_msgs/msg/Twist를 발행합니다. 프로덕션에서는 rclrs(Rust ROS2 바인딩)로 교체하세요. |
serial | drive.serial_port에 연결된 Arduino/모터 컨트롤러로 M <lx> <ly> <az> <ms> 프레임을 전송합니다. |
gpio | rppal을 통한 직접 PWM 제어 (gpio 피처 필요). |
mock | 명령을 로그로 기록하고 성공을 반환합니다. 기본값 — 개발 시 사용하세요. |
sense — 인식
섹션 제목: “sense — 인식”sense는 LIDAR, PIR 모션 센서, 초음파 거리 측정기로부터 로봇의 환경을 읽습니다.
sense(action="scan", direction="forward")sense(action="clear_ahead")sense(action="all")| 필드 | 타입 | 의미 |
|---|---|---|
action | string (필수) | scan (LIDAR), motion (PIR), distance (초음파), clear_ahead (전방 경로 확인), 또는 all (통합 보고서). |
direction | string | scan 시: forward (기본값), left, right, back, 또는 all. |
LIDAR 백엔드는 sensors.lidar_type으로 설정합니다(rplidar, ydlidar, ros2, 또는 mock). 실제 하드웨어를 사용할 수 없는 경우 시뮬레이션 룸 스캔으로 폴백하므로, sense는 mock 모드에서도 항상 유효한 결과를 반환합니다.
look — 비전
섹션 제목: “look — 비전”look은 카메라 프레임을 캡처(ffmpeg, 불가 시 fswebcam으로 폴백)하고 선택적으로 Ollama의 로컬 비전 모델로 내용을 설명합니다.
look(action="describe")look(action="find", prompt="a red ball")look(action="capture")| 필드 | 타입 | 의미 |
|---|---|---|
action | string (필수) | capture (사진만 촬영), describe (사진 + AI 설명), 또는 find (특정 대상 탐색 — prompt 필수). |
prompt | string | describe 시 집중할 대상, find 시 탐색할 대상. |
설명 기능을 비활성화하고 캡처만 사용하려면 camera.vision_model = "none"으로 설정하세요. 프레임은 ~/.revka/captures/에 저장됩니다.
listen — 청취
섹션 제목: “listen — 청취”listen은 마이크로 몇 초간 녹음하고 Whisper.cpp로 음성을 텍스트로 변환합니다.
listen(duration=5)| 필드 | 타입 | 기본값 | 의미 |
|---|---|---|---|
duration | integer | 5 | 녹음 길이(초, 1–30). |
prompt | string | — | 변환을 위한 선택적 컨텍스트 힌트 (예: "The speaker is a child"). |
audio.whisper_path에 whisper.cpp 바이너리가 필요합니다. 모델 크기는 audio.whisper_model로 설정합니다(tiny 가장 빠름 → small 가장 정확).
speak — 음성
섹션 제목: “speak — 음성”speak는 Piper TTS로 텍스트를 음성으로 변환하여 스피커로 재생합니다. 짧은 효과음 재생도 지원합니다.
speak(text="Hello, I am ready.")speak(sound="chime")| 필드 | 타입 | 의미 |
|---|---|---|
text | string | 말할 텍스트. |
emotion | string | 톤: neutral (기본값), excited, sad, 또는 whisper. |
sound | string | 말하는 대신 효과음 재생 (예: beep, chime, laugh, alert). |
audio.piper_path에 piper 바이너리와 audio.piper_voice에 음성 모델이 필요합니다.
emote — 표정
섹션 제목: “emote — 표정”emote는 LED 매트릭스에 얼굴을 표시하고 매칭되는 효과음을 재생하여 로봇을 더욱 생동감 있게 만듭니다.
emote(expression="happy")emote(expression="excited", animation="dance")| 필드 | 타입 | 기본값 | 의미 |
|---|---|---|---|
expression | string (필수) | — | happy, sad, surprised, thinking, sleepy, excited, love, angry, confused, 또는 wink. |
animation | string | — | 선택적: blink, nod, shake, 또는 dance. |
sound | boolean | true | 매칭되는 효과음 재생. |
duration | integer | 3 | 표정 유지 시간(초, 최대 10초). |
LED 백엔드는 FIFO/제어 스크립트가 있을 경우 이를 통해 출력하고, LED 하드웨어가 없을 경우 패턴을 무해하게 로그로 기록합니다.
안전 모니터
섹션 제목: “안전 모니터”SafetyMonitor는 LLM이 호출할 수 있는 도구가 아닙니다 — 모든 이동을 제어하는 독립적인 백그라운드 태스크입니다. SafeDrive 래퍼는 DriveTool 앞에 위치하므로, create_safe_tools(...)로 도구를 빌드하면 모든 drive 호출은 모터가 작동하기 전에 모니터의 사전 승인을 받아야 합니다.
다섯 가지 안전 레이어를 강제합니다:
- 사전 이동 확인 — 이동 전 경로가 깨끗한지 확인합니다.
- 능동 모니터링 — 이동 중 지속적인 센서 폴링; 근접도에 따라 속도가 감소합니다.
- 반응형 정지 — 장애물 또는 범퍼 센서 접촉 시 즉시 정지합니다.
- 워치독 타이머 —
max_drive_duration내에 새 명령이 없으면 자동 정지; 센서 데이터가 오래된 경우에도 이동이 차단됩니다. - 하드웨어 E-stop —
estop_pin의 물리적 버튼이 모든 것을 재정의합니다; 명시적으로 리셋할 때까지 잠긴 상태를 유지합니다.
안전 상태와 이벤트를 관찰할 수 있습니다: 모니터는 can_move 플래그, estop_active 플래그, 현재 최소 장애물 거리, 근접 기반 속도 배율, 차단 이유를 추적합니다. 구독 가능한 tokio::sync::broadcast 채널을 통해 SafetyEvent — ObstacleDetected, EmergencyStop, WatchdogTimeout, BumpDetected, MovementApproved, MovementDenied, Recovered — 를 브로드캐스트합니다.
| 설정 키 | 타입 | 기본값 | 의미 |
|---|---|---|---|
min_obstacle_distance | float (m) | 0.3 | 장애물이 이 거리보다 가까우면 이동하지 않습니다. |
slow_zone_multiplier | float | 3.0 | min_obstacle_distance × multiplier 범위 내에서 감속을 시작합니다. |
approach_speed_limit | float | 0.3 | 감속 구간 내 속도 상한 (최대 속도의 비율). |
max_drive_duration | int (s) | 30 | 워치독 창 — 새 명령 없이 이 시간이 지나면 자동 정지. |
sensor_timeout_secs | int (s) | 5 | 센서 데이터가 이 시간보다 오래된 경우 모든 이동을 차단합니다. |
blind_mode_speed_limit | float | 0.2 | 센서가 mock/사용 불가 상태일 때 속도 상한. |
estop_pin | int (BCM) | 4 | 물리적 비상 정지 버튼 GPIO (null로 비활성화). |
bump_sensor_pins | int[] (BCM) | [5, 6] | 섀시 마이크로스위치; 즉시 정지를 트리거합니다. |
bump_reverse_distance | float (m) | 0.15 | 범퍼 충돌 후 후진 거리. |
predict_collisions | bool | true | 궤적 예측 (LIDAR 필요). |
confirm_movement | bool | false | 각 이동 전 음성 확인 요구. |
에이전트에 연결하기
섹션 제목: “에이전트에 연결하기”먼저 안전 모니터를 빌드하고, SafeDrive로 래핑된 도구 세트를 생성한 후, 에이전트 루프에 도구를 등록합니다:
use revka_robot_kit::{RobotConfig, SafetyMonitor, create_safe_tools};use std::sync::Arc;
// Load configuration (or RobotConfig::default() for mock mode)let config = RobotConfig::load(std::path::Path::new("~/.revka/robot.toml"))?;
// Create the independent safety monitorlet (safety, _events_rx) = SafetyMonitor::new(config.safety.clone());let safety = Arc::new(safety);
// All six tools, with drive wrapped by SafeDrivelet tools = create_safe_tools(&config, safety.clone());// Register `tools` with the Revka agent loop, then drive the safety loop:// tokio::spawn(async move { safety.run(sensor_rx).await; });안전 래퍼 없이 도구를 사용하려면(mock 개발 전용) create_tools(&config)를 대신 사용하세요. 등록 후 에이전트는 자연어를 통해 로봇에 접근합니다:
revka agent -m "Look around, tell me what you see, then move forward 1 meter if the path is clear"Raspberry Pi 배포
섹션 제목: “Raspberry Pi 배포”Pi 5, 모터 컨트롤러, LIDAR, 카메라, 마이크, 스피커, LED 매트릭스, E-stop, 범퍼 센서를 포함한 완전한 로봇 빌드는 crates/robot-kit/PI5_SETUP.md를 따르세요. 이 문서에는 기본 robot.toml에서 사용하는 GPIO 배선 맵이 포함되어 있습니다(E-stop은 GPIO 4, 범퍼 센서는 5/6, 모터 PWM은 12/13, PIR은 17/27, LED 매트릭스는 18, 초음파는 23/24). 소프트웨어 의존성(Ollama, Whisper.cpp, Piper, ALSA, ffmpeg)은 crates/robot-kit/README.md에 나열되어 있습니다.