Add rotary phone web interface with multiple greeting support
Features: - Web interface for managing rotary phone system - Support for multiple greeting messages with selector - Direct audio playback in browser for recordings and greetings - Upload multiple WAV files at once - Set active greeting that plays when phone is picked up - HiFiBerry DAC+ADC Pro audio configuration - GPIO-based handset detection and audio recording - Real-time status monitoring with auto-refresh 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python -m py_compile:*)",
|
||||
"Bash(git add:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
166
AUDIO_FIX.md
Normal file
166
AUDIO_FIX.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# FIX: HiFiBerry Audio Working Now! 🎉
|
||||
|
||||
## The Problem
|
||||
|
||||
Your HiFiBerry is detected as:
|
||||
- **Card 3** (not card 0)
|
||||
- **PyAudio device index 1** (not 0)
|
||||
|
||||
The old configuration was trying to use device 0 (the built-in headphone jack), which is why you heard nothing from your speaker.
|
||||
|
||||
## ✅ SOLUTION - Quick Fix
|
||||
|
||||
### Method 1: Automatic Configuration (Recommended)
|
||||
|
||||
Run this script to auto-detect and configure everything:
|
||||
|
||||
```bash
|
||||
cd /home/berwn
|
||||
chmod +x configure_hifiberry.sh
|
||||
./configure_hifiberry.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Auto-detect your HiFiBerry card number
|
||||
2. Create the correct ~/.asoundrc
|
||||
3. Set volume to 100%
|
||||
4. Test the speaker
|
||||
|
||||
### Method 2: Manual Configuration
|
||||
|
||||
**Step 1: Create correct ALSA config**
|
||||
|
||||
```bash
|
||||
cat > ~/.asoundrc << 'EOF'
|
||||
pcm.!default {
|
||||
type asym
|
||||
playback.pcm "plughw:3,0"
|
||||
capture.pcm "plughw:3,0"
|
||||
}
|
||||
|
||||
ctl.!default {
|
||||
type hw
|
||||
card 3
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
**Step 2: Set volume**
|
||||
|
||||
```bash
|
||||
amixer -c 3 sset Digital 100%
|
||||
amixer -c 3 sset Analogue 100%
|
||||
```
|
||||
|
||||
**Step 3: Test speaker**
|
||||
|
||||
```bash
|
||||
aplay -D plughw:3,0 /usr/share/sounds/alsa/Front_Center.wav
|
||||
# OR
|
||||
speaker-test -D plughw:3,0 -c 1 -t wav
|
||||
```
|
||||
|
||||
### Method 3: Use the Updated Python Script
|
||||
|
||||
The updated `rotary_phone_web.py` now uses device index 1 automatically!
|
||||
|
||||
Just download the new version and run it:
|
||||
|
||||
```bash
|
||||
cd /home/berwn
|
||||
python3 rotary_phone_web.py
|
||||
```
|
||||
|
||||
## 🎵 Test Your Speaker Now
|
||||
|
||||
After configuration, test with:
|
||||
|
||||
```bash
|
||||
# Using ALSA (command line)
|
||||
aplay -D plughw:3,0 /usr/share/sounds/alsa/Front_Center.wav
|
||||
|
||||
# Using speaker-test
|
||||
speaker-test -D plughw:3,0 -c 1 -t sine -f 440 -l 3
|
||||
|
||||
# Using the Python script
|
||||
python3 rotary_phone_web.py
|
||||
# Then pick up the phone handset
|
||||
```
|
||||
|
||||
## 📝 Understanding Your Audio Setup
|
||||
|
||||
```
|
||||
Device 0: bcm2835 Headphones (built-in audio jack)
|
||||
Device 1: HiFiBerry DAC+ADC Pro ← YOUR SPEAKER IS HERE!
|
||||
```
|
||||
|
||||
**In ALSA terms:**
|
||||
- Card 0 = bcm2835 (built-in)
|
||||
- Card 3 = HiFiBerry ← YOUR CARD
|
||||
|
||||
**In PyAudio terms:**
|
||||
- Device index 0 = bcm2835
|
||||
- Device index 1 = HiFiBerry ← YOUR DEVICE
|
||||
|
||||
## ✅ Updated Script Features
|
||||
|
||||
The new `rotary_phone_web.py` includes:
|
||||
|
||||
1. **Automatic device selection**: Uses device index 1 for HiFiBerry
|
||||
2. **Both playback and recording**: Configured for your HiFiBerry
|
||||
3. **Comments for easy changes**: If your device index differs
|
||||
|
||||
If your HiFiBerry shows as a different device index, edit these lines in the script:
|
||||
|
||||
```python
|
||||
# Line ~95 - Playback
|
||||
output_device_index=1, # Change this number if needed
|
||||
|
||||
# Line ~135 - Recording
|
||||
input_device_index=1, # Change this number if needed
|
||||
```
|
||||
|
||||
## 🔧 Optional: Make HiFiBerry Card 0
|
||||
|
||||
If you want HiFiBerry to be card 0 (optional), disable the built-in audio:
|
||||
|
||||
```bash
|
||||
sudo nano /boot/firmware/config.txt
|
||||
# (or /boot/config.txt on older systems)
|
||||
|
||||
# Add or uncomment:
|
||||
dtparam=audio=off
|
||||
|
||||
# Make sure this exists:
|
||||
dtoverlay=hifiberry-dacplusadcpro
|
||||
|
||||
# Save and reboot
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
After reboot, HiFiBerry will be card 0, and you can use the simpler config:
|
||||
|
||||
```bash
|
||||
cat > ~/.asoundrc << 'EOF'
|
||||
pcm.!default {
|
||||
type asym
|
||||
playback.pcm "plughw:0,0"
|
||||
capture.pcm "plughw:0,0"
|
||||
}
|
||||
|
||||
ctl.!default {
|
||||
type hw
|
||||
card 0
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## 🎉 You're All Set!
|
||||
|
||||
Your speaker should now work perfectly with:
|
||||
- ✅ Command-line tools (aplay, speaker-test)
|
||||
- ✅ Python script (rotary_phone_web.py)
|
||||
- ✅ Web interface
|
||||
- ✅ Recording from microphone
|
||||
|
||||
Enjoy your working rotary phone! 📞🎵
|
||||
BIN
__pycache__/rotary_phone_web.cpython-311.pyc
Normal file
BIN
__pycache__/rotary_phone_web.cpython-311.pyc
Normal file
Binary file not shown.
100
configure_hifiberry.sh
Normal file
100
configure_hifiberry.sh
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# Auto-detect HiFiBerry and configure ALSA
|
||||
|
||||
echo "=========================================="
|
||||
echo "HiFiBerry Auto-Configuration"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Find HiFiBerry card number
|
||||
echo "Detecting HiFiBerry..."
|
||||
CARD_INFO=$(aplay -l | grep -i hifiberry)
|
||||
|
||||
if [ -z "$CARD_INFO" ]; then
|
||||
echo "❌ HiFiBerry not found!"
|
||||
echo ""
|
||||
echo "Make sure:"
|
||||
echo "1. HiFiBerry is properly seated on GPIO pins"
|
||||
echo "2. /boot/firmware/config.txt has: dtoverlay=hifiberry-dacplusadcpro"
|
||||
echo "3. You've rebooted after changing config.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ HiFiBerry found:"
|
||||
echo "$CARD_INFO"
|
||||
echo ""
|
||||
|
||||
# Extract card number
|
||||
CARD_NUM=$(echo "$CARD_INFO" | grep -oP 'card \K[0-9]+' | head -1)
|
||||
|
||||
echo "Card number: $CARD_NUM"
|
||||
echo ""
|
||||
|
||||
# Create ALSA config
|
||||
echo "Creating ~/.asoundrc..."
|
||||
cat > ~/.asoundrc << EOF
|
||||
# ALSA Configuration for HiFiBerry DAC+ADC Pro
|
||||
# Auto-generated configuration
|
||||
|
||||
pcm.!default {
|
||||
type asym
|
||||
playback.pcm "plughw:${CARD_NUM},0"
|
||||
capture.pcm "plughw:${CARD_NUM},0"
|
||||
}
|
||||
|
||||
ctl.!default {
|
||||
type hw
|
||||
card ${CARD_NUM}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✓ Created ~/.asoundrc with card ${CARD_NUM}"
|
||||
echo ""
|
||||
|
||||
# Show the config
|
||||
echo "Configuration:"
|
||||
cat ~/.asoundrc
|
||||
echo ""
|
||||
|
||||
# Set volume
|
||||
echo "Setting volume to 100%..."
|
||||
amixer -c ${CARD_NUM} sset Master 100% 2>/dev/null || echo "Master control not available"
|
||||
amixer -c ${CARD_NUM} sset PCM 100% 2>/dev/null || echo "PCM control not available"
|
||||
amixer -c ${CARD_NUM} sset Digital 100% 2>/dev/null || echo "Digital control not available"
|
||||
amixer -c ${CARD_NUM} sset Analogue 100% 2>/dev/null || echo "Analogue control not available"
|
||||
echo ""
|
||||
|
||||
# Test speaker
|
||||
echo "Testing speaker..."
|
||||
echo "🔊 Playing test tone - you should hear a beep!"
|
||||
aplay -D plughw:${CARD_NUM},0 /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null || \
|
||||
speaker-test -D plughw:${CARD_NUM},0 -c 1 -t sine -f 440 -l 2 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Configuration Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Your HiFiBerry is configured as card ${CARD_NUM}"
|
||||
echo ""
|
||||
echo "Test commands:"
|
||||
echo " aplay -D plughw:${CARD_NUM},0 /usr/share/sounds/alsa/Front_Center.wav"
|
||||
echo " speaker-test -D plughw:${CARD_NUM},0 -c 1 -t wav"
|
||||
echo ""
|
||||
echo "For Python script, use device index:"
|
||||
|
||||
# Find PyAudio device index
|
||||
python3 << PYEOF
|
||||
import pyaudio
|
||||
audio = pyaudio.PyAudio()
|
||||
print("")
|
||||
for i in range(audio.get_device_count()):
|
||||
info = audio.get_device_info_by_index(i)
|
||||
if 'hifiberry' in info['name'].lower():
|
||||
print(f" PyAudio device index: {i}")
|
||||
print(f" Device name: {info['name']}")
|
||||
break
|
||||
audio.terminate()
|
||||
PYEOF
|
||||
|
||||
echo ""
|
||||
1228
rotary_phone_web.py
Normal file
1228
rotary_phone_web.py
Normal file
File diff suppressed because it is too large
Load Diff
283
test_complete.py
Normal file
283
test_complete.py
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Complete HiFiBerry Test - Verify speaker and microphone work
|
||||
"""
|
||||
|
||||
import pyaudio
|
||||
import wave
|
||||
import numpy as np
|
||||
import time
|
||||
import os
|
||||
|
||||
# HiFiBerry device index (from your system)
|
||||
HIFIBERRY_INDEX = 1
|
||||
SAMPLE_RATE = 44100
|
||||
|
||||
def test_playback():
|
||||
"""Test speaker output"""
|
||||
print("\n" + "="*60)
|
||||
print("🔊 TESTING SPEAKER PLAYBACK")
|
||||
print("="*60)
|
||||
|
||||
audio = pyaudio.PyAudio()
|
||||
|
||||
try:
|
||||
# Show device info
|
||||
info = audio.get_device_info_by_index(HIFIBERRY_INDEX)
|
||||
print(f"\nUsing device: {info['name']}")
|
||||
print(f"Max output channels: {info['maxOutputChannels']}")
|
||||
|
||||
# Generate test tone (440Hz A note)
|
||||
print("\nGenerating 440Hz test tone...")
|
||||
duration = 3
|
||||
t = np.linspace(0, duration, int(SAMPLE_RATE * duration), False)
|
||||
tone = np.sin(2 * np.pi * 440 * t)
|
||||
tone = (tone * 0.3 * 32767).astype(np.int16) # 30% volume
|
||||
|
||||
# Open stream
|
||||
stream = audio.open(
|
||||
format=pyaudio.paInt16,
|
||||
channels=1,
|
||||
rate=SAMPLE_RATE,
|
||||
output=True,
|
||||
output_device_index=HIFIBERRY_INDEX,
|
||||
frames_per_buffer=1024
|
||||
)
|
||||
|
||||
print("🎵 Playing 3-second tone - LISTEN NOW!")
|
||||
print(" You should hear a clear beep from your speaker...")
|
||||
|
||||
# Play
|
||||
chunk_size = 2048
|
||||
for i in range(0, len(tone.tobytes()), chunk_size):
|
||||
stream.write(tone.tobytes()[i:i + chunk_size])
|
||||
|
||||
stream.stop_stream()
|
||||
stream.close()
|
||||
|
||||
print("✓ Playback completed")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Playback error: {e}")
|
||||
return False
|
||||
finally:
|
||||
audio.terminate()
|
||||
|
||||
def test_recording():
|
||||
"""Test microphone input"""
|
||||
print("\n" + "="*60)
|
||||
print("🎙️ TESTING MICROPHONE RECORDING")
|
||||
print("="*60)
|
||||
|
||||
audio = pyaudio.PyAudio()
|
||||
|
||||
try:
|
||||
# Show device info
|
||||
info = audio.get_device_info_by_index(HIFIBERRY_INDEX)
|
||||
print(f"\nUsing device: {info['name']}")
|
||||
print(f"Max input channels: {info['maxInputChannels']}")
|
||||
|
||||
# Record
|
||||
print("\n🔴 Recording for 5 seconds...")
|
||||
print(" SPEAK NOW or make noise near the microphone...")
|
||||
|
||||
stream = audio.open(
|
||||
format=pyaudio.paInt16,
|
||||
channels=1,
|
||||
rate=SAMPLE_RATE,
|
||||
input=True,
|
||||
input_device_index=HIFIBERRY_INDEX,
|
||||
frames_per_buffer=1024
|
||||
)
|
||||
|
||||
frames = []
|
||||
for i in range(0, int(SAMPLE_RATE / 1024 * 5)):
|
||||
data = stream.read(1024, exception_on_overflow=False)
|
||||
frames.append(data)
|
||||
|
||||
stream.stop_stream()
|
||||
stream.close()
|
||||
|
||||
print("✓ Recording completed")
|
||||
|
||||
# Save to file
|
||||
filename = "/tmp/test_recording.wav"
|
||||
wf = wave.open(filename, 'wb')
|
||||
wf.setnchannels(1)
|
||||
wf.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
|
||||
wf.setframerate(SAMPLE_RATE)
|
||||
wf.writeframes(b''.join(frames))
|
||||
wf.close()
|
||||
|
||||
print(f"✓ Saved to {filename}")
|
||||
|
||||
# Calculate volume level
|
||||
audio_data = np.frombuffer(b''.join(frames), dtype=np.int16)
|
||||
volume = np.abs(audio_data).mean()
|
||||
max_volume = np.abs(audio_data).max()
|
||||
|
||||
print(f"\nRecording analysis:")
|
||||
print(f" Average level: {volume:.0f}")
|
||||
print(f" Peak level: {max_volume}")
|
||||
|
||||
if max_volume > 1000:
|
||||
print(" ✓ Good signal detected!")
|
||||
else:
|
||||
print(" ⚠️ Very low signal - microphone might not be working")
|
||||
|
||||
audio.terminate()
|
||||
|
||||
# Play back
|
||||
print("\n🔊 Playing back your recording...")
|
||||
audio = pyaudio.PyAudio()
|
||||
|
||||
wf = wave.open(filename, 'rb')
|
||||
stream = audio.open(
|
||||
format=audio.get_format_from_width(wf.getsampwidth()),
|
||||
channels=wf.getnchannels(),
|
||||
rate=wf.getframerate(),
|
||||
output=True,
|
||||
output_device_index=HIFIBERRY_INDEX
|
||||
)
|
||||
|
||||
data = wf.readframes(1024)
|
||||
while data:
|
||||
stream.write(data)
|
||||
data = wf.readframes(1024)
|
||||
|
||||
stream.stop_stream()
|
||||
stream.close()
|
||||
wf.close()
|
||||
|
||||
print("✓ Playback of recording completed")
|
||||
print(f"\nYou can listen again with: aplay {filename}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Recording error: {e}")
|
||||
return False
|
||||
finally:
|
||||
audio.terminate()
|
||||
|
||||
def test_dial_tone():
|
||||
"""Test classic dial tone (350Hz + 440Hz)"""
|
||||
print("\n" + "="*60)
|
||||
print("📞 TESTING DIAL TONE")
|
||||
print("="*60)
|
||||
|
||||
audio = pyaudio.PyAudio()
|
||||
|
||||
try:
|
||||
print("\nGenerating classic dial tone (350Hz + 440Hz)...")
|
||||
duration = 3
|
||||
t = np.linspace(0, duration, int(SAMPLE_RATE * duration), False)
|
||||
|
||||
# Generate two frequencies
|
||||
tone1 = np.sin(2 * np.pi * 350 * t)
|
||||
tone2 = np.sin(2 * np.pi * 440 * t)
|
||||
tone = (tone1 + tone2) / 2
|
||||
tone = (tone * 0.3 * 32767).astype(np.int16)
|
||||
|
||||
stream = audio.open(
|
||||
format=pyaudio.paInt16,
|
||||
channels=1,
|
||||
rate=SAMPLE_RATE,
|
||||
output=True,
|
||||
output_device_index=HIFIBERRY_INDEX,
|
||||
frames_per_buffer=1024
|
||||
)
|
||||
|
||||
print("🎵 Playing dial tone - This is what you'll hear when picking up the phone!")
|
||||
|
||||
chunk_size = 2048
|
||||
for i in range(0, len(tone.tobytes()), chunk_size):
|
||||
stream.write(tone.tobytes()[i:i + chunk_size])
|
||||
|
||||
stream.stop_stream()
|
||||
stream.close()
|
||||
|
||||
print("✓ Dial tone playback completed")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Dial tone error: {e}")
|
||||
return False
|
||||
finally:
|
||||
audio.terminate()
|
||||
|
||||
def show_device_info():
|
||||
"""Show all audio devices"""
|
||||
print("\n" + "="*60)
|
||||
print("📋 AUDIO DEVICES ON YOUR SYSTEM")
|
||||
print("="*60)
|
||||
|
||||
audio = pyaudio.PyAudio()
|
||||
|
||||
print("\nAll devices:")
|
||||
for i in range(audio.get_device_count()):
|
||||
info = audio.get_device_info_by_index(i)
|
||||
device_type = []
|
||||
if info['maxOutputChannels'] > 0:
|
||||
device_type.append("OUTPUT")
|
||||
if info['maxInputChannels'] > 0:
|
||||
device_type.append("INPUT")
|
||||
|
||||
marker = " ← USING THIS" if i == HIFIBERRY_INDEX else ""
|
||||
print(f" [{i}] {info['name']}{marker}")
|
||||
print(f" Type: {', '.join(device_type)}")
|
||||
print(f" Sample rate: {info['defaultSampleRate']}")
|
||||
print()
|
||||
|
||||
audio.terminate()
|
||||
|
||||
def main():
|
||||
"""Main test routine"""
|
||||
print("\n" + "="*60)
|
||||
print("🎛️ HIFIBERRY COMPLETE SYSTEM TEST")
|
||||
print("="*60)
|
||||
print(f"\nTesting HiFiBerry at device index: {HIFIBERRY_INDEX}")
|
||||
|
||||
# Show devices
|
||||
show_device_info()
|
||||
|
||||
input("\nPress Enter to start tests...")
|
||||
|
||||
# Test 1: Playback
|
||||
test1 = test_playback()
|
||||
time.sleep(1)
|
||||
|
||||
# Test 2: Dial tone
|
||||
test2 = test_dial_tone()
|
||||
time.sleep(1)
|
||||
|
||||
# Test 3: Recording
|
||||
test3 = test_recording()
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*60)
|
||||
print("📊 TEST SUMMARY")
|
||||
print("="*60)
|
||||
print(f"\n✓ Speaker playback: {'PASS ✓' if test1 else 'FAIL ❌'}")
|
||||
print(f"✓ Dial tone: {'PASS ✓' if test2 else 'FAIL ❌'}")
|
||||
print(f"✓ Microphone recording: {'PASS ✓' if test3 else 'FAIL ❌'}")
|
||||
|
||||
if test1 and test2 and test3:
|
||||
print("\n🎉 ALL TESTS PASSED!")
|
||||
print("\nYour rotary phone system is ready to use!")
|
||||
print("Run: python3 rotary_phone_web.py")
|
||||
else:
|
||||
print("\n⚠️ Some tests failed. Check the output above for details.")
|
||||
|
||||
print("\n" + "="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nTest interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
Reference in New Issue
Block a user