- Add TEMPLATE_VERSION constant (1.3.0) to track UI changes - Create check_template_version() to compare embedded vs current version - Embed version marker as HTML comment in generated template - Auto-regenerate template when version mismatch detected - Show clear status messages: "Template up-to-date" or "regenerating" - Document versioning system in CLAUDE.md with usage guidelines Benefits: - No manual template deletion required when code updates - Users automatically get latest UI features on restart - Clear version tracking for template changes - Prevents stale template issues To update template: increment TEMPLATE_VERSION when HTML/CSS/JS changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
12 KiB
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
# 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
# Run complete audio test (speaker + mic + dial tone)
make test
# Or using UV
uv run --no-project python test_complete.py
Dependencies
# 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
# 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
# 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:
-
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)
-
Backup System with CRC32 Verification (lines 84-218)
calculate_crc32(): Compute CRC32 checksum for file verificationbackup_file_to_usb(): Copy files to multiple USB drives with verificationget_usb_backup_status(): Check USB drive mount status and writability- Automatic backup on recording/upload if
backup_on_writeenabled - Corrupted backups automatically deleted
-
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
-
Flask Web Application (lines 550+)
- REST API for status, recordings, greetings, volume control
- Template auto-generation (creates
templates/index.htmlon 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_VERSIONconstant (line ~84) tracks template changes- Version embedded as HTML comment:
<!-- TEMPLATE_VERSION: 1.3.0 --> check_template_version()compares embedded version with current code version- Template auto-regenerates on version mismatch
How It Works:
- On startup,
check_template_version()reads existing template - If version matches
TEMPLATE_VERSION, template is up-to-date - If version mismatches or template missing, regeneration occurs automatically
- User sees: "Template up-to-date (v1.3.0)" or "Template version mismatch - regenerating"
When to Update:
- Increment
TEMPLATE_VERSIONwhenever 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
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 checkingrecord_audio(): Recording loop with GPIO monitoringphone_monitor_thread(): Main state machine loop
Adding Configuration Options
- Add to
config.example.jsonwith documentation - Load in
load_system_config()orRotaryPhone.load_config() - Access via
SYS_CONFIG(system) orself.config(runtime user settings) - 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/testendpoint
Download All Recordings
The /download_all endpoint creates a ZIP archive of all recordings in memory:
- Uses
zipfileandio.BytesIO()for in-memory ZIP creation - Filename includes timestamp:
wedding_recordings_YYYYMMDD_HHMMSS.zip - Only includes
.wavfiles 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:
- Speaker output (440Hz test tone)
- Dial tone generation (350Hz + 440Hz)
- Microphone recording (5-second test)
Run before deployment to verify HiFiBerry configuration.
Manual Testing Checklist
- Pick up phone → greeting plays
- Hang up during greeting → playback stops immediately
- Wait for greeting → recording starts
- Speak → recording captures audio
- Hang up → recording saves and backs up
- Web interface → upload/play/delete functions work
- 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:
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:
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:
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
- Install Raspberry Pi OS (Bullseye or newer)
- Configure HiFiBerry DAC+ADC (
./configure_hifiberry.sh) - Wire GPIO hookswitch and optional button
- Create
config.jsonfrom example - Test audio with
make test - Install as service with
./install_service.sh
USB Backup Setup
USB drives must be mounted with user write permissions:
# 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.jsonSeeAUDIO_FIX.mdfor 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:
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.