A Raspberry Pi thin client that turns a Pi + USB webcam + speaker into an AI companion with vision, voice, memory, and personality. All the heavy lifting (LLM, TTS, STT, face recognition) happens server-side β the Pi just streams audio/video and plays responses.
[Demo video coming soon]
- π€ Listens β Streams mic audio to server for speech-to-text (Groq Whisper)
- π· Sees β Sends camera frames for vision analysis (Claude) + face recognition
- π Speaks β Plays TTS audio responses through USB or Bluetooth speaker
- π§ Remembers β Server-side memory system (short/medium/long-term) with relationship graphs
- π€ Recognizes faces β Knows who's talking to it, greets people by name
- ποΈ Recognizes voices β Speaker verification via resemblyzer d-vectors
- π΄ Sleeps/Wakes β Goes idle after 60s, wakes on noise detection
- π§ Skills β Timer, weather, web search, calculator, and 60+ tools via server
βββββββββββββββββββ ββββββββββββββββββββββββββββ
β Raspberry Pi β WSS β Droid Server β
β βββββββββΊβ β
β USB Webcam β β Claude API (LLM) β
β USB Mic β β Groq Whisper (STT) β
β USB Speaker β β Edge TTS (speech) β
β BT Speaker (opt)β β Resemblyzer (voice ID) β
β β β SQLite (memory) β
β ~50MB RAM used β β Face recognition β
βββββββββββββββββββ ββββββββββββββββββββββββββββ
The Pi is intentionally dumb. It captures audio/video, sends it over WebSocket, and plays back audio. This means:
- Any Pi 3B+ or newer works (no GPU needed)
- All API keys stay on the server
- Multiple Pis can connect to the same server
- Browser clients also work (same server, different frontend)
| Part | Notes | ~Cost |
|---|---|---|
| Raspberry Pi 3B+ or newer | 2.4GHz WiFi only on 3B | $35 |
| USB webcam with mic | Logitech BRIO (recommended) or C270 | $20-70 |
| USB speaker | Any USB audio device | $10 |
| MicroSD card | 16GB+ with Raspberry Pi OS | $8 |
| Power supply | 5V 2.5A+ (3A if using USB speaker) | $10 |
| Part | Notes | ~Cost |
|---|---|---|
| Bluetooth speaker | Pairs via PulseAudio, voice-command switchable | $15+ |
| PCA9685 servo board | Pan/tilt head tracking (I2C) | $5 |
| Pan/tilt camera mount | SG90 servos + bracket | $10 |
| Powered USB hub | Prevents undervoltage with multiple USB devices | $10 |
| 3D printed body | STL files in the main droid repo | $5 filament |
Face tracking uses Haar cascade detection to find faces in the camera frame and smoothly pans/tilts the head to follow. Only runs when PCA9685 is detected β no CPU overhead on GPIO fallback.
- Raspberry Pi 3 Model B (Debian 13 Trixie, aarch64)
- Logitech BRIO Ultra HD Webcam (video + mic, recommended)
- Logitech C270 webcam (video + mic, budget option)
- HONKYOB USB speaker (80x30x45mm pill)
- X-GO Bluetooth speaker
Flash Raspberry Pi OS Lite (64-bit) to your SD card. Boot, connect to WiFi, enable SSH.
sudo apt update && sudo apt install -y \
python3-pip python3-opencv python3-pyaudio \
portaudio19-dev ffmpeg bluetooth bluez \
pulseaudio pulseaudio-module-bluetooth
pip3 install websocketsCreate ~/.asoundrc for your USB devices. Card names survive USB reordering across reboots (card numbers don't):
# Find your card names
arecord -l # capture devices
aplay -l # playback devices# ~/.asoundrc β asymmetric: playbackβspeaker, captureβwebcam mic
pcm.!default {
type asym
playback.pcm "plughw:UACDemoV10" # your USB speaker card name
capture.pcm "plughw:BRIO" # your webcam mic card name
}
ctl.!default {
type hw
card UACDemoV10
}
cp config.example.json config.jsonEdit config.json:
{
"server": "wss://droid.turkeycode.ai/ws/device",
"token": "your-device-auth-token",
"camera_index": 0,
"volume": 250,
"rms_threshold": 200,
"idle_timeout": 60
}| Key | Description | Default |
|---|---|---|
server |
WebSocket URL to your droid server | required |
token |
Device auth JWT from server | required |
camera_index |
OpenCV camera index | 0 |
volume |
Playback volume (0-1000, 100 = 1x) | 250 (2.5x) |
rms_threshold |
Noise level to wake from sleep | 200 |
idle_timeout |
Seconds before sleeping | 60 |
python3 droid-client.pyCreate /etc/systemd/system/droid.service:
[Unit]
Description=Droid Client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/home/your-user/droid
ExecStart=/usr/bin/python3 droid-client.py
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
Environment=PULSE_SERVER=unix:/run/user/1000/pulse/native
Environment=HOME=/home/your-user
[Install]
WantedBy=multi-user.targetsudo systemctl enable droid
sudo systemctl start droid
sudo journalctl -u droid -f # watch logsNote:
XDG_RUNTIME_DIR,DBUS_SESSION_BUS_ADDRESS,PULSE_SERVER, andHOMEare all required. WithoutHOME, PyAudio can't load~/.asoundrcand won't find ALSA devices. WithoutPULSE_SERVER, PulseAudio connections fail.
The droid sleeps after idle_timeout seconds of no activity:
- Awake: Camera captures frames, mic streams to STT, vision analyzes scenes
- Sleeping: Camera off (light off), mic listens for noise only (RMS detection)
- Wake triggers: Noise above
rms_threshold, any speech detected
| Command | Action |
|---|---|
mute / go to sleep / be quiet |
Mute mic + camera |
unmute / wake up / come back |
Resume everything |
camera off / stop looking |
Camera only off |
camera on / start looking |
Camera only on |
use bluetooth / party mode |
Switch to BT speaker |
use regular speaker / bluetooth off |
Switch to USB speaker |
volume [0-10] |
Set volume (0=silent, 10=max) |
turn it up / louder |
+1 step |
turn it down / quieter |
-1 step |
learn my voice |
Enroll your voice (3 samples) |
- Only sends frames during active conversation (speech within last 2 min)
- Frame diffing: skips API call if scene is >75% similar to last analyzed frame
- Idle room = zero vision API cost
- Background thread checks every 10s that PyAudio callbacks are firing
- Auto-restarts the audio stream if ALSA crashes silently
- No more "mic died and nobody noticed"
wifi-manager.py handles network connectivity:
- Monitors WiFi every 30s
- If disconnected for >60s: starts AP mode (
Droid-Setup/droid1234) - Web portal at
192.168.4.1for network configuration - Install as separate service with
droid-wifi.service
Pair a BT speaker, then switch with voice commands:
# Manual pairing
bluetoothctl
> scan on
> pair XX:XX:XX:XX:XX:XX
> trust XX:XX:XX:XX:XX:XX
> connect XX:XX:XX:XX:XX:XXAudio routes through PulseAudio when BT is active. Persistent pacat process with silence feeder keeps the A2DP connection alive between speech chunks.
| File | Description |
|---|---|
droid-client.py |
Main client β camera, mic, speaker, WebSocket, sleep/wake, face tracking |
servo.py |
Pan/tilt servo control (PCA9685 or GPIO PWM fallback) |
haarcascade_frontalface_default.xml |
OpenCV face detection model (for face tracking) |
wifi-manager.py |
WiFi monitoring + AP fallback with config portal |
config.example.json |
Example configuration |
droid-wifi.service |
Systemd service for WiFi manager |
setup-wifi.sh |
Quick WiFi setup script |
This is just the Pi client. You need an account on the droid server for it to work.
- Sign up at droid.turkeycode.ai
- Create a droid in your dashboard
- Generate a device token from the dashboard β Hardware section
- Put the token in your
config.json - Run the client β it connects via WebSocket and you're live
- Claude API calls (LLM + vision)
- Groq Whisper (speech-to-text)
- Edge TTS (text-to-speech)
- Resemblyzer (voice identification)
- Face recognition
- Memory (SQLite β conversations, extractions, relationship graph)
- Skills engine (60+ tools)
- Dashboard (web UI for managing droids, faces, voices, skills, billing)
| Plan | Price | Tokens/mo | Vision/day |
|---|---|---|---|
| Trial | Free (7 days) | 500K | 100 |
| Starter | $9.99/mo | 2M | 500 |
| Pro | $24.99/mo | 8M | 2,000 |
| Ultra | $49.99/mo | 30M | 5,000 |
| BYOK | $4.99/mo | Unlimited | Unlimited |
BYOK (Bring Your Own Key): Use your own Claude API key. $4.99/mo covers hosting only.
- Pi 3B only sees 2.4GHz WiFi. If using iPhone hotspot, enable "Maximize Compatibility."
- Use ALSA card names, not numbers in
.asoundrcβ numbers change when USB devices reorder on reboot. - Powered USB hub recommended if running webcam + speaker from Pi USB (prevents undervoltage).
- PCA9685 uses I2C β enable with
sudo raspi-configβ Interface Options β I2C. - Volume scale: 0-1000 internally. User-facing 0-10.
volume 5= 500 = 5x amplification. - Brio mic captures mono at 16kHz β stereo downmix was unreliable, mono is cleaner for STT.
- Energy threshold may need tuning per mic. Default 1500 works for Brio; C270 may need 2500.
MIT