Leroy is an AI birdwatcher built for Raspberry Pi 5 with AI Kit (Hailo).
- Raspberry Pi 5
- Raspberry Pi AI Kit (Hailo-8L accelerator)
- Raspberry Pi HQ Camera
Before running the install script, ensure these interfaces are enabled:
Required Interfaces:
- Camera Interface: Required for HQ Camera access
- SSH: Required for remote access and service management
- PCIe: Required for AI Kit (automatically configured by install script)
The install script will automatically enable Camera and SSH interfaces. If you prefer to enable them manually:
# Using raspi-config (recommended)
sudo raspi-config
# Navigate to: Interface Options → Camera → Enable
# Navigate to: Interface Options → SSH → Enable
# Or enable via command line
sudo raspi-config nonint do_camera 0
sudo raspi-config nonint do_ssh 0Note: After enabling Camera interface, a reboot may be required.
git clone <repository-url> project-leroy
cd project-leroy./install-pi5.shThis will:
- Enable required interfaces (Camera, SSH)
- Set up Python virtual environment
- Install system dependencies (Hailo SDK, rpicam-apps, nginx)
- Install Python packages
- Configure PCIe for AI Kit
- Configure systemd service
- Set up cron jobs
- Configure and start nginx
- Create storage directories
- Download HEF models (optional)
Note: All directories are created automatically when needed:
storage/detected/{date}/{visitation_id}/- Created byphoto.pywhen saving photos/var/www/html/classified/{date}/{visitation_id}/- Created byclassify.pywhen moving filesstorage/active_learning/*- Created byactive_learning.pyon initialization
CRITICAL: The service requires at least one detection model to run.
The Hailo Model Zoo provides pre-compiled HEF files - no compilation needed!
./download_models.shThis downloads pre-compiled HEF models directly from the Model Zoo.
If downloads fail, check:
-
Raspberry Pi AI Kit Documentation (may have example models):
-
Hailo Model Explorer (find and download models):
-
Model Zoo Repository (browse for HEF files):
- https://github.com/hailo-ai/hailo_model_zoo
- Look for:
hailo_models/<model>/hef/<model>.hef
Model Priority:
- Detection: YOLOv5s (preferred) or SSD MobileNet v2 (fallback) - REQUIRED
- Classification: MobileNet v2 (trained on 964 iNaturalist bird species) - Optional
Verify Models:
ls -lh all_models/*.hefAll HEF files should show non-zero file sizes. If any are 0 bytes, remove them and re-download:
./fix_empty_models.shThe detection service runs as a systemd service. After installation, the service is enabled but not started automatically.
# Start the service
sudo systemctl start leroy.service
# Check service status
sudo systemctl status leroy.service
# View live logs
sudo journalctl -u leroy.service -f
# View recent logs (last boot)
sudo journalctl -u leroy.service -bThe service is automatically enabled during installation. To verify:
sudo systemctl is-enabled leroy.service# Stop the service
sudo systemctl stop leroy.service
# Restart the service
sudo systemctl restart leroy.service
# Disable auto-start on boot (if needed)
sudo systemctl disable leroy.service- Auto-updates: Pulls latest code from git when it starts (via
run.sh) - Auto-restart: Restarts automatically if it crashes (
Restart=on-abort) - Auto-launch browser: Launches browser with web app (if enabled, no duplicate windows)
- Custom port: Web interface runs on port 8080 (configurable)
- Logs: Output logged to systemd journal and
storage/results.log
Create or edit leroy.env to customize settings:
# Web Server Configuration
LEROY_WEB_PORT=8080 # Custom port (default: 8080)
LEROY_WEB_HOST=localhost # Host (default: localhost)
# Browser Auto-Launch
LEROY_AUTO_LAUNCH_BROWSER=true # Enable/disable (default: true)
# Camera Resolution Configuration
LEROY_DETECTION_WIDTH=1280 # Detection resolution width (default: 1280)
LEROY_DETECTION_HEIGHT=960 # Detection resolution height (default: 960)
LEROY_PHOTO_WIDTH=4056 # Photo resolution width (default: 4056)
LEROY_PHOTO_HEIGHT=3040 # Photo resolution height (default: 3040)Security Note: Using port 8080 instead of 80/443 reduces exposure to automated scanners while remaining accessible on local network.
For testing or debugging:
# Activate virtual environment
source venv/bin/activate
# Run detection script
python3 leroy.py
# Or with custom model/labels
python3 leroy.py --model all_models/yolov5s.hef --labels all_models/coco_labels.txtDefault Model: Automatically uses yolov5s.hef if available, otherwise falls back to ssd_mobilenet_v2_coco.hef.
- Detection: Configurable resolution (default: 1280x960), resized to 500px for inference
- Photos: Configurable high-resolution (default: 4056x3040) captured when birds are detected
- Classification: Runs periodically via cron job
- Storage: UUID-based filenames with JSON metadata for full scientific visitation schema support
- Camera Resolution: Configurable via
leroy.env(LEROY_DETECTION_WIDTH/HEIGHT, LEROY_PHOTO_WIDTH/HEIGHT)
See .cursor/rules/architecture.mdc for detailed system architecture.
The web interface is a lightweight vanilla JavaScript app (no build step required).
On Raspberry Pi: Nginx runs directly on the host (installed by install-pi5.sh). Access at http://your-pi-ip:8080.
Local Development: Use Docker for preview:
make web-preview
# Or: docker-compose -f docker-compose.nginx.yml upSee web/README.md for web interface details.
Run tests using Docker (includes all dependencies):
# Option 1: Use test runner script (recommended)
./run_tests.sh # Run all tests
./run_tests.sh tests.test_visitation_processing # Run specific test
# Option 2: Use Makefile
make docker-pi5-test # Run all tests
make docker-pi5-test-file TEST=tests.test_visitation_processing # Run specific testNote: Tests focus on business logic. Hardware-dependent code (camera, Hailo) is not tested.
The system can learn to identify new bird species not in the base model (964 iNaturalist species).
Workflow:
- Automatic Collection: System collects low-confidence bird classifications
- Human Labeling: Review and label unknown bird photos in
storage/active_learning/ - Retraining: Fine-tune model on new species data
- Deployment: Update model and labels
Quick Start:
# After collecting and labeling unknown bird photos:
python3 retrain_model.py \
--new_species_dir storage/active_learning/labeled/painted-bunting \
--new_species_name painted-bunting \
--new_class_id 965The system can optionally post to Bluesky with daily summaries.
Setup:
# Set environment variables in leroy.env
export BLUESKY_ENABLED=true
export [email protected]
export BLUESKY_APP_PASSWORD=your-app-passwordPosting Rules:
- One post per day - Single daily summary
- Evening posting - 7:00 PM - 9:00 PM
- 5 best photos - Varying species, high clarity
- Only posts if authenticated, otherwise silently ignores
See .cursor/rules/social-media-posting.mdc for complete posting rules.
-
Check service status:
sudo systemctl status leroy.service
-
Check logs for errors:
sudo journalctl -u leroy.service -n 50
-
Common issues:
- Camera not found: Ensure HQ Camera is connected and accessible
- Hailo SDK not found: Verify AI Kit is properly installed
- Models missing: Run
./download_models.shto download required models - Virtual environment missing: Re-run
./install-pi5.sh - Driver version mismatch (error 76): See "Hailo Driver Version Mismatch" below
If you see HAILO_INVALID_DRIVER_VERSION(76) or "Driver version is different from library version":
Recommended Fix (Automated):
sudo ./fix_hailo_version.shThis script will:
- Detect the version mismatch
- Remove all Hailo packages completely
- Remove kernel modules
- Reinstall hailo-all
- Prompt for reboot
Manual Fix:
# Remove all Hailo packages
sudo apt-get remove --purge -y hailo-all hailort hailo-platform-python3
# Remove kernel modules
sudo find /lib/modules/ -name "hailo*.ko*" -delete
sudo depmod -a
# Update and reinstall
sudo apt-get update
sudo apt-get install -y hailo-all
# REBOOT (required!)
sudo reboot
# After reboot, verify
sudo hailortcli fw-control identify
# Should NOT show version mismatchOr run the install script again:
./install-pi5.sh
# It will detect and fix the version mismatch automaticallyWhy this happens: After system updates, the Hailo driver and library can get out of sync. The driver loads at boot, so a reboot is required after reinstalling. The kernel modules must be removed for a clean reinstall.
Repository Unavailable (404 Error):
If you see 404 Not Found when updating packages, the Hailo repository may be:
- Temporarily unavailable
- Not supporting your OS version (trixie/sid)
- Repository URL has changed
If packages were removed but repository unavailable:
-
Check if packages are in Raspberry Pi's repository:
apt-cache search hailo
-
Check official Raspberry Pi AI Kit guide for latest repository info: https://www.raspberrypi.com/documentation/accessories/ai-kit.html
-
Once repository is available, restore packages:
sudo ./restore_hailo_from_repo.sh
-
Or manually:
sudo apt-get update sudo apt-get install -y hailo-all sudo reboot
Note: If you're on Debian 13 (trixie), the Hailo repository may not support it yet. You may need to:
- Wait for Hailo to add support
- Use a different OS version (bookworm/bullseye)
- Check Hailo Developer Zone for alternative installation methods
Check logs to identify the crash cause:
sudo journalctl -u leroy.service -fCommon causes:
- Camera initialization failure
- Hailo model loading error
- Missing dependencies
- Driver version mismatch (see above)
Quick Diagnostic Script:
./diagnose_camera.shThis script checks:
- Camera interface status
- Device detection (
/dev/video0) - Camera permissions
- OpenCV access
- Project Leroy camera manager
Manual Camera Tests:
-
Check camera interface is enabled:
grep start_x /boot/firmware/config.txt # Should show: start_x=1 -
Check camera device exists:
ls -l /dev/video* # Should show /dev/video0 with video group
-
Test with v4l2-utils:
sudo apt-get install v4l-utils v4l2-ctl --device=/dev/video0 --all
-
Test with rpicam (Raspberry Pi official tools):
sudo apt-get install rpicam-apps rpicam-hello # 5 second preview rpicam-still -o test.jpg # Capture test image
-
Test with OpenCV (Python) - What Project Leroy uses:
# Quick test script python3 test_camera_opencv.py # Or manual test python3 << 'EOF' import cv2 cap = cv2.VideoCapture(0) if cap.isOpened(): ret, frame = cap.read() if ret: print(f"Camera working! Frame: {frame.shape[1]}x{frame.shape[0]}") else: print("Camera opened but can't read frames") cap.release() else: print("Failed to open camera") EOF
-
Check user permissions:
groups # Should include 'video' # If not: sudo usermod -aG video $USER # Then logout and login again
-
Test with Project Leroy's camera manager:
python3 << 'EOF' from camera_manager import CameraManager camera = CameraManager(camera_idx=0) if camera.initialize(): print("Camera manager initialized successfully") camera.release() else: print("Camera manager failed to initialize") EOF
Common Camera Issues:
- Camera not detected: Check cable connection, try different camera port
- Permission denied: Add user to video group:
sudo usermod -aG video $USER - Interface not enabled: Run
sudo raspi-config→ Interface Options → Camera → Enable - Wrong camera index: Try different indices (0, 1, 2) with
--camera_idxargument
Photos are stored in:
- Detected (raw):
storage/detected/{date}/{visitation_id}/ - Classified:
/var/www/html/classified/{date}/{visitation_id}/ - Web interface: Visit
http://your-pi-ip:8080/
Classification runs automatically via cron job (hourly). Check cron logs:
grep CRON /var/log/syslog- iNaturalist Integration: Planned feature to submit visitations to iNaturalist. Data format is already compatible - one observation per species per visitation.
- Architecture:
.cursor/rules/architecture.mdc- Detailed system architecture - Web Interface:
web/README.md- Web app details