Add production WSGI server and CLAUDE.md documentation

- Replace Flask development server with Waitress production WSGI server
- Add waitress>=2.1.0 dependency to pyproject.toml and Makefile
- Configure 4-thread server for better performance and stability
- Create comprehensive CLAUDE.md guide for future development
- Document architecture, deployment, testing, and common patterns
- Update README.md with production-ready feature and dependencies

Eliminates Flask development server warning and provides production-grade
web serving suitable for Raspberry Pi deployment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-27 12:18:19 +07:00
parent 6ff7664f31
commit 72e39f9515
5 changed files with 334 additions and 6 deletions

322
CLAUDE.md Normal file
View File

@@ -0,0 +1,322 @@
# 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
The Flask app generates `templates/index.html` programmatically on first run. The template is defined as a string in the Python code and written to disk. To update the UI:
1. Delete `templates/` directory
2. Restart the application
3. Updated template will be regenerated
## 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
## 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.

View File

@@ -13,7 +13,7 @@ test:
sync:
@echo "Installing/syncing dependencies..."
uv pip install flask numpy pyaudio RPi.GPIO
uv pip install flask numpy pyaudio RPi.GPIO waitress
install: sync
@echo "Dependencies installed!"

View File

@@ -73,6 +73,7 @@ Web interface available at: `http://<raspberry-pi-ip>:8080`
- **USB Backup**: Automatic backup to multiple USB drives with CRC32 verification
- **Data Integrity**: Every file write is verified with CRC checksums
- **HiFiBerry Support**: Optimized for HiFiBerry DAC+ADC Pro audio quality
- **Production-Ready**: Uses Waitress WSGI server for stable, production-grade web serving
- **Real-time Status**: Monitor phone status (on-hook/off-hook/recording)
- **Auto-refresh**: Status updates every 5 seconds
@@ -95,6 +96,7 @@ Web interface available at: `http://<raspberry-pi-ip>:8080`
- numpy>=1.21.0
- pyaudio>=0.2.13
- RPi.GPIO>=0.7.1
- waitress>=2.1.0 (production WSGI server)
## Installation
@@ -127,7 +129,7 @@ sudo apt-get install -y python3-pyaudio portaudio19-dev
# Install Python dependencies with UV
make sync
# Or manually:
# uv pip install flask numpy pyaudio RPi.GPIO
# uv pip install flask numpy pyaudio RPi.GPIO waitress
```
#### Option B: Using pip
@@ -135,7 +137,7 @@ make sync
```bash
sudo apt-get update
sudo apt-get install -y python3-pip python3-pyaudio portaudio19-dev
pip3 install flask numpy RPi.GPIO
pip3 install flask numpy RPi.GPIO waitress
```
### 4. Configure HiFiBerry

View File

@@ -9,6 +9,7 @@ dependencies = [
"numpy>=1.21.0",
"pyaudio>=0.2.13",
"RPi.GPIO>=0.7.1",
"waitress>=2.1.0",
]
[project.scripts]

View File

@@ -14,6 +14,7 @@ import numpy as np
import threading
from flask import Flask, render_template, request, send_file, jsonify, redirect, url_for
from werkzeug.utils import secure_filename
from waitress import serve
import json
import sys
import zlib
@@ -1989,13 +1990,15 @@ def main():
# Get and display local IP
local_ip = get_local_ip()
print("\n" + "="*60)
print(f"🚀 Wedding Phone System Starting...")
print(f"Web Interface Available At:")
print(f" http://{local_ip}:{WEB_PORT}")
print(f" http://localhost:{WEB_PORT}")
print("="*60 + "\n")
# Start Flask web server
app.run(host='0.0.0.0', port=WEB_PORT, debug=False, threaded=True)
# Start production WSGI server (waitress)
print(f"Starting production server on 0.0.0.0:{WEB_PORT}")
serve(app, host='0.0.0.0', port=WEB_PORT, threads=4)
if __name__ == "__main__":
main()