# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Wedding Phone is a Raspberry Pi-based rotary phone system for weddings and events. Guests pick up a vintage rotary phone handset to hear a custom greeting, then record voice messages. The system features a Flask web interface for managing greetings and recordings, GPIO hookswitch detection, optional USB backup with CRC32 verification, and HiFiBerry DAC+ADC audio support. ## Development Commands ### Running the System ```bash # Start the wedding phone system (preferred method) make start # Or using UV directly uv run --no-project python rotary_phone_web.py # Or using Python directly python3 rotary_phone_web.py ``` ### Testing ```bash # Run complete audio test (speaker + mic + dial tone) make test # Or using UV uv run --no-project python test_complete.py ``` ### Dependencies ```bash # Install/sync dependencies using UV (preferred) make sync # Or manually with UV uv pip install flask numpy pyaudio RPi.GPIO # System dependencies (Raspberry Pi) sudo apt-get install -y python3-pyaudio portaudio19-dev ``` ### Service Management ```bash # Install as systemd service ./install_service.sh # Service control sudo systemctl start wedding-phone sudo systemctl stop wedding-phone sudo systemctl restart wedding-phone sudo systemctl status wedding-phone # View logs sudo journalctl -u wedding-phone -f ``` ### Cleanup ```bash # Clean temporary files and generated templates make clean ``` ## Architecture ### Single-File Application Design The entire Flask application lives in `rotary_phone_web.py` (~2000 lines). This is intentional - the system is designed for deployment on Raspberry Pi devices where simplicity matters more than strict separation of concerns. **Key architectural components:** 1. **Configuration System** (lines 22-82) - `config.json`: System configuration (GPIO pins, audio device, paths, backup settings) - `user_config.json`: Runtime user settings (active greeting, volume, button sound) - Configuration loaded at startup and used throughout application - User settings persisted on changes (volume adjustments, greeting selection) 2. **Backup System with CRC32 Verification** (lines 84-218) - `calculate_crc32()`: Compute CRC32 checksum for file verification - `backup_file_to_usb()`: Copy files to multiple USB drives with verification - `get_usb_backup_status()`: Check USB drive mount status and writability - Automatic backup on recording/upload if `backup_on_write` enabled - Corrupted backups automatically deleted 3. **RotaryPhone Class** (lines 220-550+) - Central state machine managing phone lifecycle - GPIO event-driven architecture with immediate hook detection - Three states: `on_hook`, `off_hook`, `recording` - Audio playback with volume control (numpy-based amplitude scaling) - Recording with minimum duration requirements (1 second) - Extra button support for playing sounds during recording 4. **Flask Web Application** (lines 550+) - REST API for status, recordings, greetings, volume control - Template auto-generation (creates `templates/index.html` on first run) - File upload handling with werkzeug secure_filename - Audio streaming endpoints for browser playback ### Phone State Machine Flow ``` 1. ON_HOOK (waiting) → GPIO detects hookswitch pressed (handset lifted) 2. OFF_HOOK (playing greeting) → Optional greeting delay (0-10 seconds) → Play active greeting with volume control → GPIO continuously checked for hang-up 3. RECORDING (recording audio) → Record audio from microphone → Extra button can trigger sounds (non-blocking) → Stop on hang-up or max duration (300s) → Save if ≥1 second, backup to USB if enabled → Return to ON_HOOK ``` ### Audio Architecture - **PyAudio Instance**: Single shared instance in `RotaryPhone.audio` - **Streams**: Created per-operation (playback/recording) with configured device index - **Volume Control**: Applied in real-time using numpy array manipulation (int16 scaling) - **Extra Button Sounds**: Separate PyAudio instance to prevent blocking main recording - **Hook Detection**: Direct GPIO reads during playback/recording loops for immediate response ### HTML Template Generation and Versioning The Flask app generates `templates/index.html` programmatically with automatic version tracking: **Version System:** - `TEMPLATE_VERSION` constant (line ~84) tracks template changes - Version embedded as HTML comment: `` - `check_template_version()` compares embedded version with current code version - Template auto-regenerates on version mismatch **How It Works:** 1. On startup, `check_template_version()` reads existing template 2. If version matches `TEMPLATE_VERSION`, template is up-to-date 3. If version mismatches or template missing, regeneration occurs automatically 4. User sees: "Template up-to-date (v1.3.0)" or "Template version mismatch - regenerating" **When to Update:** - Increment `TEMPLATE_VERSION` whenever HTML/CSS/JS changes are made - Format: "X.Y.Z" (major.minor.patch) - Add comment describing the change **Manual Regeneration:** If needed, delete `templates/` directory and restart - not required with version system ## Configuration ### config.json Structure All system configuration is in `config.json` (copy from `config.example.json`): - **gpio**: Pin assignments and pressed states (LOW/HIGH) for hookswitch and extra button - **audio**: Device index, sample rate, chunk size, max recording duration - **paths**: Base directory and subdirectories for recordings/sounds - **backup**: USB paths, CRC verification, auto-backup settings - **web**: Port and upload size limits - **system**: Default values for volume, greeting, button sound, greeting delay ### Finding Audio Device Index ```bash python3 -c "import pyaudio; p=pyaudio.PyAudio(); [print(f'{i}: {p.get_device_info_by_index(i)[\"name\"]}') for i in range(p.get_device_count())]" ``` Update `config.json` → `audio.device_index` with the correct index. ### GPIO Configuration - **hook_pin**: BCM GPIO pin number for hookswitch - **hook_pressed_state**: "LOW" if switch pulls to ground when pressed, "HIGH" if pulls to 3.3V - **extra_button_enabled**: true/false to enable optional button feature - Pin mode: BCM (not BOARD) - Pull-up resistors enabled on inputs ## Common Development Tasks ### Adding New API Endpoints Add Flask routes in `rotary_phone_web.py`. Follow existing patterns: - Status endpoints: Return JSON with `jsonify()` - File operations: Use `secure_filename()` for uploads - Audio streaming: Use `send_file()` with mimetype - Error handling: Return appropriate HTTP status codes ### Modifying Phone Behavior Edit the `RotaryPhone` class methods: - `play_sound_file()`: Audio playback logic with volume/hook checking - `record_audio()`: Recording loop with GPIO monitoring - `phone_monitor_thread()`: Main state machine loop ### Adding Configuration Options 1. Add to `config.example.json` with documentation 2. Load in `load_system_config()` or `RotaryPhone.load_config()` 3. Access via `SYS_CONFIG` (system) or `self.config` (runtime user settings) 4. Update README.md configuration section ### USB Backup Modifications - `backup_file_to_usb()`: Handles individual file backup - CRC verification logic prevents silent corruption - Backup called after recording saves and greeting uploads - Test with `/api/backup/test` endpoint ### Download All Recordings The `/download_all` endpoint creates a ZIP archive of all recordings in memory: - Uses `zipfile` and `io.BytesIO()` for in-memory ZIP creation - Filename includes timestamp: `wedding_recordings_YYYYMMDD_HHMMSS.zip` - Only includes `.wav` files from recordings directory - Returns 404 if no recordings exist - Web UI shows "Download All as ZIP" button when recordings are present ## Testing ### Audio Testing The `test_complete.py` script validates: 1. Speaker output (440Hz test tone) 2. Dial tone generation (350Hz + 440Hz) 3. Microphone recording (5-second test) Run before deployment to verify HiFiBerry configuration. ### Manual Testing Checklist 1. Pick up phone → greeting plays 2. Hang up during greeting → playback stops immediately 3. Wait for greeting → recording starts 4. Speak → recording captures audio 5. Hang up → recording saves and backs up 6. Web interface → upload/play/delete functions work 7. Extra button (if enabled) → sound plays during recording only ## Important Patterns ### Immediate GPIO Response The system uses direct `GPIO.input()` calls inside playback and recording loops rather than callbacks. This ensures immediate response when the handset is hung up: ```python while data: if GPIO.input(HOOK_PIN) != HOOK_PRESSED: print("Handset hung up, stopping playback immediately") break # Continue processing ``` ### Volume Control Implementation Volume is applied to PCM audio data using numpy: ```python volume = self.get_volume() / 100.0 # 0.0 to 1.0 audio_data = np.frombuffer(data, dtype=np.int16) audio_data = (audio_data * volume).astype(np.int16) data = audio_data.tobytes() ``` ### File Integrity with CRC32 All file writes (recordings, uploads) can be verified with CRC32: ```python source_crc = calculate_crc32(source_file) # ... copy file ... dest_crc = calculate_crc32(dest_file) if dest_crc != source_crc: # Corrupted, delete backup ``` ## Deployment Notes ### Raspberry Pi Setup 1. Install Raspberry Pi OS (Bullseye or newer) 2. Configure HiFiBerry DAC+ADC (`./configure_hifiberry.sh`) 3. Wire GPIO hookswitch and optional button 4. Create `config.json` from example 5. Test audio with `make test` 6. Install as service with `./install_service.sh` ### USB Backup Setup USB drives must be mounted with user write permissions: ```bash # Automated setup (recommended) sudo ./setup_usb.sh # Or manual mount sudo mount -o uid=$(id -u),gid=$(id -g) /dev/sda1 /media/usb0 ``` ### HiFiBerry Configuration The system expects HiFiBerry DAC+ADC Pro or compatible. Configuration includes: - Disable onboard audio in `/boot/config.txt` - Enable HiFiBerry overlay - Set correct audio device index in `config.json` See `AUDIO_FIX.md` for detailed troubleshooting. ### Production Web Server The application uses **waitress**, a production-ready WSGI server for Python web apps. This eliminates the Flask development server warning and provides: - Thread pooling (4 worker threads by default) - Better performance and stability - Production-safe error handling - No "development server" warnings The server configuration is in `rotary_phone_web.py` line ~2000: ```python serve(app, host='0.0.0.0', port=WEB_PORT, threads=4) ``` To adjust thread count or other waitress settings, modify the `serve()` call parameters. ## Project Structure ``` wedding-phone/ ├── rotary_phone_web.py # Main application (Flask + GPIO + Audio) ├── test_complete.py # Audio testing script ├── config.json # Configuration (copy from config.example.json) ├── config.example.json # Configuration template ├── Makefile # Build commands ├── pyproject.toml # Python package metadata ├── wedding-phone.service # Systemd service template ├── install_service.sh # Service installer ├── setup_usb.sh # USB setup script ├── configure_hifiberry.sh # HiFiBerry configuration ├── README.md # User documentation ├── CHANGELOG.md # Version history ├── AUDIO_FIX.md # Audio troubleshooting └── templates/ # Auto-generated on first run └── index.html # Web interface (auto-generated) ``` Runtime data (auto-created): ``` rotary_phone_data/ # Configurable via config.json ├── recordings/ # Guest voice recordings ├── sounds/ # Greeting WAV files └── user_config.json # Runtime settings (volume, active greeting) ``` ## Dependencies **Core:** - flask >= 2.3.0 (web framework) - numpy >= 1.21.0 (audio processing) - pyaudio >= 0.2.13 (audio I/O) - RPi.GPIO >= 0.7.1 (GPIO control) - waitress >= 2.1.0 (production WSGI server) **System:** - portaudio19-dev (PyAudio backend) - Python 3.8+ (required for Flask 2.3+) **Development:** - UV package manager (recommended, faster than pip) - Make (command shortcuts) ## Version Information Current version: 1.2.0 See CHANGELOG.md for complete version history and upgrade notes.