Robot Kit
The robotics toolkit: drive, sense, look, listen, speak, emote tools and the independent safety monitor.
The Robot Kit (crates/robot-kit) turns a Revka agent into the brain of a physical robot. It is a standalone Rust crate that exposes six agent tools — drive, sense, look, listen, speak, and emote — plus an independent SafetyMonitor that can override any movement the AI requests. It is designed for Raspberry Pi deployment with offline inference (Ollama for vision, Whisper.cpp for hearing, Piper for speech), but every tool has a mock mode so you can develop and test with no hardware attached.
Use this page when you want to build an embodied robot — a rover, a toy, a patrol bot — controlled by natural-language agent calls. For tethered microcontroller GPIO (Pico, Arduino, Nucleo) and the Aardvark adapter, see GPIO tools and Aardvark I2C/SPI/GPIO instead. For running Revka itself on a Pi, see Raspberry Pi (self-hosted).
Architecture
Section titled “Architecture”The Robot Kit sits between the agent loop and the hardware. The agent calls tools; every drive command is funnelled through the SafetyMonitor, which runs as an independent task and has final say on whether the robot moves.
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 · LEDsThe guiding principle, from the crate itself: the AI can REQUEST movement, but the SafetyMonitor ALLOWS it. Safety always wins, even if the model hallucinates.
The kit defines its own lightweight Tool trait so it can be used standalone, but the tools are compatible with Revka’s agent loop. The crate is a workspace member and is not auto-registered in the core runtime — you wire it in from Rust (see Wiring it into an agent).
Build & feature flags
Section titled “Build & feature flags”Build the crate as a workspace package:
cargo build -p revka-robot-kit --release| Feature | Default | What it enables |
|---|---|---|
safety | on | The SafetyMonitor background task and the SafeDrive wrapper. Strongly recommended; keep it enabled. |
gpio | off | Direct GPIO control via rppal (Raspberry Pi / Linux only). |
ros2 | off | ROS2 integration build flag. |
lidar | off | LIDAR support build flag. |
vision | off | Camera + vision-model build flag. |
Configuration (robot.toml)
Section titled “Configuration (robot.toml)”All hardware and limits live in robot.toml. Copy the template from the crate and edit it for your robot:
mkdir -p ~/.revkacp crates/robot-kit/robot.toml ~/.revka/A minimal, mock-mode config looks like this:
[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 moveThe tools
Section titled “The tools”drive — movement
Section titled “drive — movement”drive controls omni-directional movement. It maps a natural-language action to one of four motor backends and computes the move duration from the requested distance and your configured speed limits.
drive(action="forward", distance=1.0)drive(action="rotate_left", distance=90)drive(action="left", distance=0.5, speed=0.3)drive(action="stop")| Field | Type | Default | Meaning |
|---|---|---|---|
action | string | — (required) | forward, backward, left, right (strafe), rotate_left, rotate_right, stop, or custom. |
distance | number | 0.5 m / 90° | Meters for linear moves, degrees for rotation. |
speed | number | 0.5 | Speed multiplier 0.0–1.0, scaled by drive.max_speed. |
linear_x / linear_y / angular_z | number | 0.0 | For action="custom": raw velocities (-1.0 to 1.0). |
stop always executes immediately and is never blocked. Drive commands are rate-limited to one per second, and every move duration is clamped to safety.max_drive_duration so a single command can never run away.
Drive backends (drive.backend):
| Backend | Behaviour |
|---|---|
ros2 | Publishes geometry_msgs/msg/Twist to the configured ros2_topic (default /cmd_vel) by shelling out to ros2 topic pub. For production, swap in rclrs (Rust ROS2 bindings). |
serial | Sends M <lx> <ly> <az> <ms> frames to an Arduino/motor controller on drive.serial_port. |
gpio | Direct PWM via rppal (requires the gpio feature). |
mock | Logs the command and returns success. The default — use it for development. |
sense — perception
Section titled “sense — perception”sense reads the robot’s environment from LIDAR, PIR motion sensors, and an ultrasonic range finder.
sense(action="scan", direction="forward")sense(action="clear_ahead")sense(action="all")| Field | Type | Meaning |
|---|---|---|
action | string (required) | scan (LIDAR), motion (PIR), distance (ultrasonic), clear_ahead (forward path check), or all (combined report). |
direction | string | For scan: forward (default), left, right, back, or all. |
LIDAR backend is set by sensors.lidar_type (rplidar, ydlidar, ros2, or mock). When real hardware is unavailable the kit falls back to a simulated room scan, so sense always returns something usable in mock mode.
look — vision
Section titled “look — vision”look captures a camera frame (via ffmpeg, falling back to fswebcam) and optionally describes it with a local vision model through Ollama.
look(action="describe")look(action="find", prompt="a red ball")look(action="capture")| Field | Type | Meaning |
|---|---|---|
action | string (required) | capture (photo only), describe (photo + AI description), or find (look for a specific thing — prompt required). |
prompt | string | What to focus on for describe, or what to search for with find. |
Set camera.vision_model = "none" to disable descriptions and capture only. Frames are saved under ~/.revka/captures/.
listen — hearing
Section titled “listen — hearing”listen records from the microphone for a few seconds and transcribes it with Whisper.cpp.
listen(duration=5)| Field | Type | Default | Meaning |
|---|---|---|---|
duration | integer | 5 | Recording length in seconds (1–30). |
prompt | string | — | Optional context hint for transcription, e.g. "The speaker is a child". |
Requires the whisper.cpp binary at audio.whisper_path. Model size is set by audio.whisper_model (tiny fastest → small most accurate).
speak — voice
Section titled “speak — voice”speak converts text to speech with Piper TTS and plays it through the speaker. It can also play short sound effects.
speak(text="Hello, I am ready.")speak(sound="chime")| Field | Type | Meaning |
|---|---|---|
text | string | The text to say. |
emotion | string | Tone: neutral (default), excited, sad, or whisper. |
sound | string | Play a sound effect instead of speaking (e.g. beep, chime, laugh, alert). |
Requires the piper binary at audio.piper_path and a voice model in audio.piper_voice.
emote — expression
Section titled “emote — expression”emote shows a face on an LED matrix and plays a matching sound, making the robot more engaging.
emote(expression="happy")emote(expression="excited", animation="dance")| Field | Type | Default | Meaning |
|---|---|---|---|
expression | string (required) | — | happy, sad, surprised, thinking, sleepy, excited, love, angry, confused, or wink. |
animation | string | — | Optional: blink, nod, shake, or dance. |
sound | boolean | true | Play the matching sound effect. |
duration | integer | 3 | Seconds to hold the expression (capped at 10). |
The LED backend writes to a FIFO/control script if present, and logs the pattern harmlessly when no LED hardware is connected.
The safety monitor
Section titled “The safety monitor”The SafetyMonitor is not a tool the LLM can call — it is an independent background task that gates every movement. The SafeDrive wrapper sits in front of DriveTool, so when you build tools with create_safe_tools(...), every drive call must be pre-cleared by the monitor before the motors turn.
It enforces five safety layers:
- Pre-move checks — verify the path is clear before any movement.
- Active monitoring — continuous sensor polling while moving; speed is scaled down by proximity.
- Reactive stops — instant halt on obstacle or bump-sensor contact.
- Watchdog timer — auto-stop if no new command arrives within
max_drive_duration; movement is also blocked if sensor data goes stale. - Hardware E-stop — a physical button on
estop_pinoverrides everything; it stays latched until explicitly reset.
Safety state and events are observable: the monitor tracks a can_move flag, an estop_active flag, the current minimum obstacle distance, a proximity-based speed multiplier, and a block reason. It broadcasts SafetyEvents — ObstacleDetected, EmergencyStop, WatchdogTimeout, BumpDetected, MovementApproved, MovementDenied, and Recovered — over a tokio::sync::broadcast channel you can subscribe to.
| Config key | Type | Default | Meaning |
|---|---|---|---|
min_obstacle_distance | float (m) | 0.3 | Robot will not move if an obstacle is closer than this. |
slow_zone_multiplier | float | 3.0 | Start slowing within min_obstacle_distance × multiplier. |
approach_speed_limit | float | 0.3 | Speed cap (fraction of max) inside the slow zone. |
max_drive_duration | int (s) | 30 | Watchdog window — auto-stop after this without a new command. |
sensor_timeout_secs | int (s) | 5 | Block all movement if sensor data is older than this. |
blind_mode_speed_limit | float | 0.2 | Speed cap when sensors are in mock/unavailable mode. |
estop_pin | int (BCM) | 4 | GPIO for the physical emergency-stop button (null to disable). |
bump_sensor_pins | int[] (BCM) | [5, 6] | Chassis microswitches; trigger an immediate stop. |
bump_reverse_distance | float (m) | 0.15 | How far to back up after a bump. |
predict_collisions | bool | true | Trajectory prediction (needs LIDAR). |
confirm_movement | bool | false | Require verbal confirmation before each move. |
Wiring it into an agent
Section titled “Wiring it into an agent”Build the safety monitor first, then create the tool set wrapped with SafeDrive, and register the tools with your agent loop:
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; });Use create_tools(&config) instead if you deliberately want the tools without the safety wrapper (mock development only). Once registered, the agent reaches the robot through natural language:
revka agent -m "Look around, tell me what you see, then move forward 1 meter if the path is clear"Raspberry Pi deployment
Section titled “Raspberry Pi deployment”For a full robot build — Pi 5, motor controller, LIDAR, camera, mic, speaker, LED matrix, E-stop, and bump sensors — follow crates/robot-kit/PI5_SETUP.md, which includes the GPIO wiring map used by the default robot.toml (E-stop on GPIO 4, bump sensors on 5/6, motor PWM on 12/13, PIR on 17/27, LED matrix on 18, ultrasonic on 23/24). Software dependencies (Ollama, Whisper.cpp, Piper, ALSA, ffmpeg) are listed in crates/robot-kit/README.md.