Files
wedding-phone/CLAUDE.md
grabowski 4282b0f7ee Add automatic template versioning system
- 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>
2025-10-27 12:28:34 +07:00

348 lines
12 KiB
Markdown

# 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: `<!-- TEMPLATE_VERSION: 1.3.0 -->`
- `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.