- 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>
348 lines
12 KiB
Markdown
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.
|