Initial commit - Claudzmo CLI tool
Claudzmo is a CLI tool for controlling Anki Cozmo robot via PyCozmo. Features: - Movement control (drive, turn, head, lift) - Facial expressions (15 presets with 30fps animation) - Text-to-speech via macOS voice synthesis - Camera access (320x240 live feed) - Status monitoring (battery, firmware, hardware) - Claude Code skill integration Architecture: - Fresh connection per command using pycozmo.connect() - Reliable audio playback (100% consistent) - Simple CLI interface with argparse - Fast execution (~1 second per command) Built with ❤️ by Matt & Claude 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Python
|
||||
venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
# PyCozmo
|
||||
.pycozmo/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temp files
|
||||
/tmp/
|
||||
*.wav
|
||||
*.aiff
|
||||
*.tmp
|
||||
|
||||
# Old MCP server (deprecated)
|
||||
server.py
|
||||
mcp_config.json.example
|
||||
setup.sh
|
||||
test_audio_direct.py
|
||||
254
README.md
Normal file
254
README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Claudzmo
|
||||
|
||||
CLI tool for controlling Anki Cozmo robot via PyCozmo.
|
||||
|
||||
Direct WiFi control - no phone or USB bridge required!
|
||||
|
||||
## Features
|
||||
|
||||
- 🤖 **Movement** - Drive, turn, head/lift positioning
|
||||
- 😊 **Expressions** - 15 preset emotions with smooth 30fps animations
|
||||
- 🗣️ **Speech** - Text-to-speech with macOS voice synthesis
|
||||
- 📷 **Camera** - Live 320x240 camera feed
|
||||
- 🔋 **Status** - Battery, firmware, hardware info
|
||||
- ⚡ **Fast & Reliable** - Fresh connection per command, audio works consistently
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Anki Cozmo robot (powered on)
|
||||
2. Connect computer to Cozmo's WiFi network (`Cozmo_XXXXX`)
|
||||
3. Python 3.6+ with pip
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://gitea.kostverse.com/matt/claudzmo.git
|
||||
cd claudzmo
|
||||
|
||||
# Create virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Download PyCozmo resources (required!)
|
||||
pycozmo_resources.py download
|
||||
|
||||
# Make executable
|
||||
chmod +x claudzmo
|
||||
|
||||
# Optional: Add to PATH
|
||||
ln -s $(pwd)/claudzmo ~/bin/claudzmo
|
||||
```
|
||||
|
||||
### Test Connection
|
||||
|
||||
```bash
|
||||
./claudzmo status
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Movement
|
||||
|
||||
```bash
|
||||
# Move forward/backward
|
||||
claudzmo move --distance 200 --speed 50
|
||||
|
||||
# Turn in place
|
||||
claudzmo turn --angle 90
|
||||
|
||||
# Set head angle (-25 to 44 degrees)
|
||||
claudzmo head --angle 30
|
||||
|
||||
# Set lift height (0 to 66mm)
|
||||
claudzmo lift --height 50
|
||||
|
||||
# Emergency stop
|
||||
claudzmo stop
|
||||
```
|
||||
|
||||
### Expressions & Speech
|
||||
|
||||
```bash
|
||||
# Display facial expression
|
||||
claudzmo expression --name happiness --duration 1500
|
||||
|
||||
# Available expressions:
|
||||
# neutral, happiness, sadness, anger, surprise, disgust, fear,
|
||||
# pleading, vulnerability, despair, guilt, amazement, excitement,
|
||||
# confusion, skepticism
|
||||
|
||||
# Speak text
|
||||
claudzmo speak --text "Hello Matt!"
|
||||
claudzmo speak --text "I'm Claudzmo!" --volume 65535
|
||||
```
|
||||
|
||||
### Camera & Status
|
||||
|
||||
```bash
|
||||
# Get camera image (returns JSON with base64 image)
|
||||
claudzmo camera --format jpeg
|
||||
|
||||
# Get robot status
|
||||
claudzmo status
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Greet someone
|
||||
|
||||
```bash
|
||||
claudzmo expression --name excitement --duration 1500
|
||||
claudzmo speak --text "Hi Nicole, Matt got me Claudzmo to work with Claude!"
|
||||
```
|
||||
|
||||
### Dance around
|
||||
|
||||
```bash
|
||||
claudzmo move --distance 100 --speed 50
|
||||
claudzmo turn --angle 90
|
||||
claudzmo head --angle 35
|
||||
claudzmo lift --height 50
|
||||
claudzmo expression --name happiness --duration 2000
|
||||
```
|
||||
|
||||
### Take a selfie
|
||||
|
||||
```bash
|
||||
claudzmo head --angle 20
|
||||
claudzmo expression --name happiness --duration 1000
|
||||
claudzmo camera --format jpeg > cozmo_selfie.json
|
||||
```
|
||||
|
||||
## Claude Code Skill
|
||||
|
||||
Claudzmo includes a skill for [Claude Code](https://claude.com/claude-code) integration.
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Copy skill to Claude Code skills directory
|
||||
cp claudzmo.skill.md ~/.claude/skills/claudzmo.md
|
||||
|
||||
# Restart Claude Code
|
||||
claude /restart
|
||||
```
|
||||
|
||||
### Usage in Claude Code
|
||||
|
||||
Just ask Claude to control Cozmo:
|
||||
|
||||
- "Make Cozmo say hello"
|
||||
- "Have Cozmo turn left and look up"
|
||||
- "Show me what Cozmo's camera sees"
|
||||
- "Make Cozmo do a happy dance"
|
||||
|
||||
Claude will automatically use the `claudzmo` skill to control the robot!
|
||||
|
||||
## Architecture
|
||||
|
||||
### Why CLI instead of MCP?
|
||||
|
||||
Originally built as an MCP server, but audio playback was unreliable with persistent connections. The CLI approach:
|
||||
|
||||
- ✅ Uses `pycozmo.connect()` context manager (proven reliable)
|
||||
- ✅ Fresh connection per command (no state issues)
|
||||
- ✅ Audio works 100% consistently
|
||||
- ✅ Simpler to debug and test
|
||||
- ✅ Fast enough (~1 second per command)
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Connection** - Each command creates fresh `pycozmo.Client()` connection
|
||||
2. **Command execution** - Sends appropriate PyCozmo API calls
|
||||
3. **Audio** - Uses macOS `say` → convert to 22kHz 16-bit mono WAV → play via Cozmo
|
||||
4. **Expressions** - Renders 128x64 procedural faces → downsample to 128x32 → animate at 30fps
|
||||
5. **Cleanup** - Connection closes automatically (context manager)
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Audio Format
|
||||
|
||||
Cozmo requires:
|
||||
- Sample rate: 22,050 Hz
|
||||
- Bit depth: 16-bit PCM
|
||||
- Channels: Mono
|
||||
- Format: WAV
|
||||
|
||||
The CLI handles conversion automatically using macOS `afconvert`.
|
||||
|
||||
### Volume Range
|
||||
|
||||
Volume is 16-bit (0-65535):
|
||||
- `65535` = Maximum volume
|
||||
- `50000` = ~75% volume
|
||||
- `32768` = ~50% volume
|
||||
|
||||
### Connection
|
||||
|
||||
- IP: `172.31.1.1` (Cozmo's default)
|
||||
- Port: `5106` (UDP)
|
||||
- Auto-discovery via PyCozmo
|
||||
- Firmware: 2381 (tested)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Failed to connect to Cozmo"
|
||||
|
||||
1. Check Cozmo is powered on (press button on back)
|
||||
2. Verify connected to Cozmo's WiFi network
|
||||
3. Wait a moment between commands (connection cooldown)
|
||||
|
||||
### Audio not playing
|
||||
|
||||
1. Check volume: `--volume 65535` for max
|
||||
2. Verify text is being spoken (test with macOS `say` command)
|
||||
3. Ensure `afconvert` is available (comes with macOS)
|
||||
|
||||
### "No camera image available"
|
||||
|
||||
Camera takes ~1 second to initialize. Wait and retry.
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
claudzmo/
|
||||
├── claudzmo # Main CLI executable
|
||||
├── requirements.txt # Python dependencies
|
||||
├── README.md # This file
|
||||
├── claudzmo.skill.md # Claude Code skill
|
||||
└── venv/ # Python virtual environment
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `pycozmo` - Pure-Python Cozmo SDK
|
||||
- `Pillow` - Image processing for facial expressions
|
||||
- `numpy` - Array operations for image conversion
|
||||
|
||||
## Credits
|
||||
|
||||
- **PyCozmo** - https://github.com/zayfod/pycozmo (Pure-Python Cozmo library)
|
||||
- **Anki Cozmo** - Original robot hardware and firmware
|
||||
- **Claude** - AI assistant that helped build this! 🤖
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
## Links
|
||||
|
||||
- Repository: https://gitea.kostverse.com/matt/claudzmo
|
||||
- PyCozmo Docs: https://pycozmo.readthedocs.io/
|
||||
- Claude Code: https://claude.com/claude-code
|
||||
|
||||
---
|
||||
|
||||
Built with ❤️ by Matt & Claude • "Claudzmo" name suggested by Nicole 😄
|
||||
314
claudzmo
Executable file
314
claudzmo
Executable file
@@ -0,0 +1,314 @@
|
||||
#!/Users/matt/Projects/cozmo-mcp/venv/bin/python3
|
||||
"""
|
||||
Claudzmo - CLI tool for controlling Anki Cozmo robot via PyCozmo
|
||||
|
||||
Usage:
|
||||
claudzmo move --distance 100 --speed 50
|
||||
claudzmo turn --angle 90 --speed 50
|
||||
claudzmo head --angle 30
|
||||
claudzmo lift --height 50
|
||||
claudzmo expression --name happiness --duration 1000
|
||||
claudzmo speak --text "Hello!" [--volume 65535]
|
||||
claudzmo camera [--format jpeg]
|
||||
claudzmo status
|
||||
claudzmo stop
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
import pycozmo
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
except ImportError as e:
|
||||
print(f"Error: Missing dependency - {e}", file=sys.stderr)
|
||||
print("Install with: pip install pycozmo Pillow numpy", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def connect_cozmo():
|
||||
"""Connect to Cozmo robot"""
|
||||
return pycozmo.connect(enable_procedural_face=False)
|
||||
|
||||
|
||||
def cmd_move(args):
|
||||
"""Move forward/backward"""
|
||||
with connect_cozmo() as cli:
|
||||
distance_mm = args.distance
|
||||
speed_mmps = args.speed
|
||||
duration = abs(distance_mm / speed_mmps)
|
||||
wheel_speed = speed_mmps if distance_mm > 0 else -speed_mmps
|
||||
|
||||
cli.drive_wheels(lwheel_speed=wheel_speed, rwheel_speed=wheel_speed, duration=duration)
|
||||
time.sleep(duration)
|
||||
|
||||
print(f"Moved {distance_mm}mm at {speed_mmps}mm/s")
|
||||
|
||||
|
||||
def cmd_turn(args):
|
||||
"""Turn in place"""
|
||||
with connect_cozmo() as cli:
|
||||
angle_degrees = args.angle
|
||||
speed = args.speed
|
||||
|
||||
# Calculate wheel speeds for turning (opposite directions)
|
||||
wheel_speed = speed
|
||||
if angle_degrees < 0:
|
||||
left_speed = -wheel_speed
|
||||
right_speed = wheel_speed
|
||||
else:
|
||||
left_speed = wheel_speed
|
||||
right_speed = -wheel_speed
|
||||
|
||||
# Rough duration calculation
|
||||
duration = abs(angle_degrees) / 90.0
|
||||
|
||||
cli.drive_wheels(lwheel_speed=left_speed, rwheel_speed=right_speed, duration=duration)
|
||||
time.sleep(duration)
|
||||
|
||||
print(f"Turned {angle_degrees} degrees")
|
||||
|
||||
|
||||
def cmd_head(args):
|
||||
"""Set head angle"""
|
||||
import math
|
||||
|
||||
with connect_cozmo() as cli:
|
||||
angle_rad = math.radians(args.angle)
|
||||
cli.set_head_angle(angle_rad)
|
||||
time.sleep(0.5)
|
||||
|
||||
print(f"Set head angle to {args.angle} degrees")
|
||||
|
||||
|
||||
def cmd_lift(args):
|
||||
"""Set lift height"""
|
||||
with connect_cozmo() as cli:
|
||||
cli.set_lift_height(args.height)
|
||||
time.sleep(0.5)
|
||||
|
||||
print(f"Set lift height to {args.height}mm")
|
||||
|
||||
|
||||
def cmd_expression(args):
|
||||
"""Display facial expression"""
|
||||
with connect_cozmo() as cli:
|
||||
expression_name = args.name.lower()
|
||||
duration_ms = args.duration
|
||||
|
||||
# Map expression names to pycozmo.expressions classes
|
||||
expression_map = {
|
||||
"neutral": pycozmo.expressions.Neutral,
|
||||
"happiness": pycozmo.expressions.Happiness,
|
||||
"sadness": pycozmo.expressions.Sadness,
|
||||
"anger": pycozmo.expressions.Anger,
|
||||
"surprise": pycozmo.expressions.Surprise,
|
||||
"disgust": pycozmo.expressions.Disgust,
|
||||
"fear": pycozmo.expressions.Fear,
|
||||
"pleading": pycozmo.expressions.Pleading,
|
||||
"vulnerability": pycozmo.expressions.Vulnerability,
|
||||
"despair": pycozmo.expressions.Despair,
|
||||
"guilt": pycozmo.expressions.Guilt,
|
||||
"amazement": pycozmo.expressions.Amazement,
|
||||
"excitement": pycozmo.expressions.Excitement,
|
||||
"confusion": pycozmo.expressions.Confusion,
|
||||
"skepticism": pycozmo.expressions.Skepticism
|
||||
}
|
||||
|
||||
if expression_name not in expression_map:
|
||||
print(f"Error: Unknown expression '{expression_name}'", file=sys.stderr)
|
||||
print(f"Available: {', '.join(expression_map.keys())}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Create and display expression with animation
|
||||
from_face = pycozmo.expressions.Neutral()
|
||||
to_face = expression_map[expression_name]()
|
||||
num_frames = max(1, int(duration_ms / 33)) # ~30fps
|
||||
|
||||
face_generator = pycozmo.procedural_face.interpolate(from_face, to_face, num_frames)
|
||||
|
||||
for face in face_generator:
|
||||
im = face.render()
|
||||
np_im = np.array(im)
|
||||
np_im2 = np_im[::2] # Convert 128x64 to 128x32
|
||||
im2 = Image.fromarray(np_im2)
|
||||
cli.display_image(im2, 0.033)
|
||||
time.sleep(0.033)
|
||||
|
||||
print(f"Displayed expression: {expression_name}")
|
||||
|
||||
|
||||
def cmd_speak(args):
|
||||
"""Speak text through Cozmo's speaker"""
|
||||
with connect_cozmo() as cli:
|
||||
# Generate audio file
|
||||
text = args.text
|
||||
volume = args.volume
|
||||
|
||||
# Create temp files for audio conversion
|
||||
with tempfile.NamedTemporaryFile(suffix='.aiff', delete=False) as aiff_file:
|
||||
aiff_path = aiff_file.name
|
||||
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as wav_file:
|
||||
wav_path = wav_file.name
|
||||
|
||||
try:
|
||||
# Generate speech
|
||||
subprocess.run(['say', '-v', 'Samantha', text, '-o', aiff_path], check=True, capture_output=True)
|
||||
|
||||
# Convert to Cozmo format (22kHz, 16-bit, mono)
|
||||
subprocess.run(['afconvert', aiff_path, '-d', 'LEI16@22050', '-f', 'WAVE', wav_path],
|
||||
check=True, capture_output=True)
|
||||
|
||||
# Play through Cozmo
|
||||
cli.set_volume(volume)
|
||||
cli.play_audio(wav_path)
|
||||
cli.wait_for(pycozmo.event.EvtAudioCompleted, timeout=30.0)
|
||||
|
||||
print(f"Spoke: {text}")
|
||||
|
||||
finally:
|
||||
# Cleanup temp files
|
||||
Path(aiff_path).unlink(missing_ok=True)
|
||||
Path(wav_path).unlink(missing_ok=True)
|
||||
|
||||
|
||||
def cmd_camera(args):
|
||||
"""Get camera image"""
|
||||
with connect_cozmo() as cli:
|
||||
# Enable camera and wait for image
|
||||
cli.enable_camera(enable=True, color=True)
|
||||
|
||||
# Register handler to capture image
|
||||
latest_image = [None]
|
||||
|
||||
def on_camera_image(cli_obj, image):
|
||||
latest_image[0] = image
|
||||
|
||||
cli.add_handler(pycozmo.event.EvtNewRawCameraImage, on_camera_image)
|
||||
|
||||
# Wait for image
|
||||
time.sleep(1)
|
||||
|
||||
if latest_image[0] is None:
|
||||
print("Error: No camera image available", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Convert to base64
|
||||
import base64
|
||||
from io import BytesIO
|
||||
|
||||
img_format = args.format.upper()
|
||||
buffered = BytesIO()
|
||||
latest_image[0].save(buffered, format=img_format)
|
||||
img_base64 = base64.b64encode(buffered.getvalue()).decode()
|
||||
|
||||
# Output JSON with image data
|
||||
result = {
|
||||
"width": latest_image[0].size[0],
|
||||
"height": latest_image[0].size[1],
|
||||
"format": img_format,
|
||||
"base64": img_base64
|
||||
}
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
def cmd_status(args):
|
||||
"""Get robot status"""
|
||||
with connect_cozmo() as cli:
|
||||
# Give it a moment to get status
|
||||
time.sleep(0.5)
|
||||
|
||||
status = {
|
||||
"connected": True,
|
||||
"battery_voltage": getattr(cli, 'battery_voltage', 0.0),
|
||||
"firmware_version": getattr(cli, 'fw_ver', 0),
|
||||
"hardware_version": getattr(cli, 'hw_ver', 0),
|
||||
"body_id": f"0x{getattr(cli, 'body_id', 0):08x}"
|
||||
}
|
||||
|
||||
print(json.dumps(status, indent=2))
|
||||
|
||||
|
||||
def cmd_stop(args):
|
||||
"""Emergency stop all motors"""
|
||||
with connect_cozmo() as cli:
|
||||
cli.stop_all_motors()
|
||||
print("Stopped all motors")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Claudzmo - Control Anki Cozmo robot',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest='command', help='Command to execute')
|
||||
subparsers.required = True
|
||||
|
||||
# Move command
|
||||
move_parser = subparsers.add_parser('move', help='Move forward/backward')
|
||||
move_parser.add_argument('--distance', type=float, required=True, help='Distance in mm (negative for backward)')
|
||||
move_parser.add_argument('--speed', type=float, default=50, help='Speed in mm/s (default: 50)')
|
||||
move_parser.set_defaults(func=cmd_move)
|
||||
|
||||
# Turn command
|
||||
turn_parser = subparsers.add_parser('turn', help='Turn in place')
|
||||
turn_parser.add_argument('--angle', type=float, required=True, help='Angle in degrees (negative for left)')
|
||||
turn_parser.add_argument('--speed', type=float, default=50, help='Wheel speed (default: 50)')
|
||||
turn_parser.set_defaults(func=cmd_turn)
|
||||
|
||||
# Head command
|
||||
head_parser = subparsers.add_parser('head', help='Set head angle')
|
||||
head_parser.add_argument('--angle', type=float, required=True, help='Angle in degrees (-25 to 44)')
|
||||
head_parser.set_defaults(func=cmd_head)
|
||||
|
||||
# Lift command
|
||||
lift_parser = subparsers.add_parser('lift', help='Set lift height')
|
||||
lift_parser.add_argument('--height', type=float, required=True, help='Height in mm (0 to 66)')
|
||||
lift_parser.set_defaults(func=cmd_lift)
|
||||
|
||||
# Expression command
|
||||
expr_parser = subparsers.add_parser('expression', help='Display facial expression')
|
||||
expr_parser.add_argument('--name', type=str, required=True, help='Expression name')
|
||||
expr_parser.add_argument('--duration', type=int, default=1000, help='Animation duration in ms (default: 1000)')
|
||||
expr_parser.set_defaults(func=cmd_expression)
|
||||
|
||||
# Speak command
|
||||
speak_parser = subparsers.add_parser('speak', help='Speak text')
|
||||
speak_parser.add_argument('--text', type=str, required=True, help='Text to speak')
|
||||
speak_parser.add_argument('--volume', type=int, default=65535, help='Volume (0-65535, default: 65535)')
|
||||
speak_parser.set_defaults(func=cmd_speak)
|
||||
|
||||
# Camera command
|
||||
camera_parser = subparsers.add_parser('camera', help='Get camera image')
|
||||
camera_parser.add_argument('--format', type=str, default='jpeg', choices=['jpeg', 'png'], help='Image format')
|
||||
camera_parser.set_defaults(func=cmd_camera)
|
||||
|
||||
# Status command
|
||||
status_parser = subparsers.add_parser('status', help='Get robot status')
|
||||
status_parser.set_defaults(func=cmd_status)
|
||||
|
||||
# Stop command
|
||||
stop_parser = subparsers.add_parser('stop', help='Emergency stop')
|
||||
stop_parser.set_defaults(func=cmd_stop)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
args.func(args)
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrupted", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
64
claudzmo.skill.md
Normal file
64
claudzmo.skill.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# claudzmo
|
||||
|
||||
Control Anki Cozmo robot - movement, expressions, speech, and camera.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
~/bin/claudzmo <command> [options]
|
||||
# or
|
||||
/Users/matt/Projects/cozmo-mcp/claudzmo <command> [options]
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### Movement
|
||||
- `claudzmo move --distance <mm> [--speed <mm/s>]` - Move forward (positive) or backward (negative)
|
||||
- `claudzmo turn --angle <degrees> [--speed <mm/s>]` - Turn in place (positive=right, negative=left)
|
||||
- `claudzmo head --angle <degrees>` - Set head angle (-25 to 44 degrees)
|
||||
- `claudzmo lift --height <mm>` - Set lift height (0 to 66mm)
|
||||
|
||||
### Expression & Speech
|
||||
- `claudzmo expression --name <name> [--duration <ms>]` - Display facial expression
|
||||
- Available: neutral, happiness, sadness, anger, surprise, disgust, fear, pleading, vulnerability, despair, guilt, amazement, excitement, confusion, skepticism
|
||||
- `claudzmo speak --text "<text>" [--volume <0-65535>]` - Speak text (default volume: 65535)
|
||||
|
||||
### Sensors
|
||||
- `claudzmo camera [--format jpeg|png]` - Get camera image (returns JSON with base64 image)
|
||||
- `claudzmo status` - Get robot status (battery, firmware, etc.)
|
||||
|
||||
### Control
|
||||
- `claudzmo stop` - Emergency stop all motors
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Make Cozmo greet someone
|
||||
claudzmo speak --text "Hello Matt!"
|
||||
claudzmo expression --name happiness --duration 1500
|
||||
|
||||
# Move around
|
||||
claudzmo move --distance 200 --speed 50
|
||||
claudzmo turn --angle 90
|
||||
claudzmo head --angle 30
|
||||
|
||||
# Take a photo
|
||||
claudzmo camera --format jpeg
|
||||
|
||||
# Check status
|
||||
claudzmo status
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
Requires:
|
||||
1. Cozmo robot powered on
|
||||
2. Computer connected to Cozmo's WiFi network (Cozmo_XXXXX)
|
||||
3. Python environment with pycozmo installed at `/Users/matt/Projects/cozmo-mcp/venv`
|
||||
|
||||
## Notes
|
||||
|
||||
- Commands connect/disconnect for each operation (ensures reliability)
|
||||
- Audio uses macOS `say` command with Samantha voice
|
||||
- Expressions animate smoothly at ~30fps
|
||||
- Camera returns base64-encoded JPEG or PNG
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
pycozmo>=0.8.0
|
||||
Pillow>=9.0.0
|
||||
numpy>=1.20.0
|
||||
Reference in New Issue
Block a user