Breaking Changes: - Configuration now via config.json instead of editing Python code - Remove all hardcoded paths (no more /home/berwn) - Separate system config (config.json) from runtime config (user_config.json) Features: - config.example.json with all configurable options - GPIO pin and state configuration - Audio device index configuration - Customizable paths (relative or absolute) - Web port and upload size settings - No code editing required for deployment Configuration Structure: - gpio: hook_pin, hook_pressed_state - audio: device_index, chunk_size, channels, sample_rate, max_record_seconds - paths: base_dir, recordings_dir, sounds_dir - web: port, max_upload_size_mb - system: active_greeting, default_volume Script automatically: - Checks for config.json on startup - Provides helpful error if missing - Uses relative paths by default - Loads test_complete.py config from same file Updated Documentation: - Complete configuration guide in README - Setup instructions without hardcoded paths - Troubleshooting for config errors - Device index discovery command 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
302 lines
8.7 KiB
Python
302 lines
8.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Complete HiFiBerry Test - Verify speaker and microphone work
|
|
"""
|
|
|
|
import pyaudio
|
|
import wave
|
|
import numpy as np
|
|
import time
|
|
import os
|
|
import json
|
|
import sys
|
|
|
|
# Load configuration
|
|
def load_config():
|
|
"""Load audio device configuration"""
|
|
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')
|
|
|
|
if not os.path.exists(config_path):
|
|
print("Config file not found. Using default device index 1")
|
|
return {"audio": {"device_index": 1, "sample_rate": 44100}}
|
|
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
return json.load(f)
|
|
except:
|
|
print("Error reading config. Using defaults.")
|
|
return {"audio": {"device_index": 1, "sample_rate": 44100}}
|
|
|
|
CONFIG = load_config()
|
|
HIFIBERRY_INDEX = CONFIG['audio']['device_index']
|
|
SAMPLE_RATE = CONFIG['audio']['sample_rate']
|
|
|
|
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()
|