183 lines
8.2 KiB
Python
Executable File
183 lines
8.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
import argparse
|
||
import yaml
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
import sys
|
||
|
||
sys.path.insert(0, str(Path(__file__).parent))
|
||
from src.audio_tests import run_artifact_detection_test
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description='Run artifact detection test on audio loopback and radio path')
|
||
parser.add_argument('--serial-number', required=True, help='Serial number (e.g., SN001234)')
|
||
parser.add_argument('--software-version', required=True, help='Software version (git commit hash)')
|
||
parser.add_argument('--comment', default='', help='Comments about this test')
|
||
parser.add_argument('--config', default='config.yaml', help='Path to config file')
|
||
parser.add_argument('--duration', type=float, help='Override recording duration in seconds (default from config)')
|
||
parser.add_argument('--frequency', type=float, help='Override test frequency in Hz (default from config)')
|
||
parser.add_argument('--signal-type', choices=['sine', 'chirp', 'silent'], default='sine',
|
||
help='Signal type: sine (single frequency), chirp (frequency sweep), or silent (no signal)')
|
||
|
||
args = parser.parse_args()
|
||
|
||
with open(args.config, 'r') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
if args.duration:
|
||
config['artifact_detection']['duration'] = args.duration
|
||
if args.frequency:
|
||
config['artifact_detection']['test_frequency'] = args.frequency
|
||
|
||
config['artifact_detection']['signal_type'] = args.signal_type
|
||
|
||
timestamp = datetime.now()
|
||
test_id = timestamp.strftime('%Y%m%d_%H%M%S')
|
||
|
||
results_dir = Path(config['output']['results_dir'])
|
||
|
||
test_output_dir = results_dir / timestamp.strftime('%Y') / timestamp.strftime('%m') / timestamp.strftime('%d') / f"{test_id}_artifact_detection"
|
||
test_output_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
save_plots = config['output'].get('save_plots', False)
|
||
|
||
print("=" * 70)
|
||
print("ARTIFACT DETECTION TEST")
|
||
print("=" * 70)
|
||
print(f"Test ID: {test_id}")
|
||
print(f"Serial Number: {args.serial_number}")
|
||
print(f"Software: {args.software_version}")
|
||
if args.comment:
|
||
print(f"Comment: {args.comment}")
|
||
print(f"Duration: {config['artifact_detection']['duration']} seconds")
|
||
signal_type = config['artifact_detection'].get('signal_type', 'sine')
|
||
if signal_type == 'sine':
|
||
print(f"Signal Type: Sine wave @ {config['artifact_detection']['test_frequency']} Hz")
|
||
elif signal_type == 'chirp':
|
||
print(f"Signal Type: Chirp (100 Hz - 8000 Hz)")
|
||
else:
|
||
print(f"Signal Type: Silent (no playback - noise floor measurement)")
|
||
if save_plots:
|
||
print(f"Plots will be saved to: {test_output_dir}")
|
||
print("-" * 70)
|
||
|
||
print("\nDetection Algorithms:")
|
||
for detector_name, detector_settings in config['artifact_detection']['detectors'].items():
|
||
status = "ENABLED" if detector_settings.get('enabled', False) else "DISABLED"
|
||
print(f" - {detector_name}: {status}")
|
||
if detector_settings.get('enabled', False):
|
||
for param, value in detector_settings.items():
|
||
if param != 'enabled':
|
||
print(f" {param}: {value}")
|
||
|
||
print("\n" + "=" * 70)
|
||
signal_type = config['artifact_detection'].get('signal_type', 'sine')
|
||
if signal_type == 'sine':
|
||
freq = config['artifact_detection']['test_frequency']
|
||
print(f"STARTING TEST - Playing {freq}Hz sine wave and recording both channels...")
|
||
elif signal_type == 'chirp':
|
||
print("STARTING TEST - Playing chirp signal (100-8000Hz) and recording both channels...")
|
||
else:
|
||
print("STARTING TEST - Recording silence (no playback)...")
|
||
print("=" * 70)
|
||
print("\nChannel 1: Loopback path (direct audio interface loopback)")
|
||
print("Channel 2: DUT/Radio path (through beacon and radio transmission)")
|
||
print()
|
||
|
||
try:
|
||
result = run_artifact_detection_test(config, save_plots=save_plots, output_dir=test_output_dir)
|
||
|
||
print("\n" + "=" * 70)
|
||
print("TEST COMPLETE - RESULTS")
|
||
print("=" * 70)
|
||
|
||
signal_type = result.get('signal_type', 'sine')
|
||
if signal_type == 'chirp':
|
||
print(f"\n📊 Signal: Chirp {result['chirp_f0_hz']} Hz → {result['chirp_f1_hz']} Hz")
|
||
elif signal_type == 'silent':
|
||
print(f"\n📊 Signal: Silent (no playback - noise floor measurement)")
|
||
else:
|
||
print(f"\n📊 Test Frequency: {result['test_frequency_hz']} Hz")
|
||
print(f"⏱️ Duration: {result['duration_sec']} seconds")
|
||
|
||
print("\n🔊 CHANNEL 1 (LOOPBACK PATH):")
|
||
print(f" Total Artifacts: {result['channel_1_loopback']['total_artifacts']}")
|
||
print(f" Artifact Rate: {result['channel_1_loopback']['artifact_rate_per_minute']:.2f} per minute")
|
||
if result['channel_1_loopback']['artifacts_by_type']:
|
||
print(" By Type:")
|
||
for artifact_type, count in result['channel_1_loopback']['artifacts_by_type'].items():
|
||
print(f" - {artifact_type}: {count}")
|
||
|
||
# Display frequency accuracy for channel 1
|
||
if 'frequency_accuracy' in result['channel_1_loopback']:
|
||
freq_acc = result['channel_1_loopback']['frequency_accuracy']
|
||
print(f" Frequency Accuracy:")
|
||
print(f" Expected: {freq_acc['expected_freq_hz']:.1f} Hz")
|
||
print(f" Measured: {freq_acc['measured_freq_hz']:.2f} Hz")
|
||
print(f" Error: {freq_acc['error_hz']:+.2f} Hz ({freq_acc['error_percent']:+.3f}%)")
|
||
|
||
print("\n📻 CHANNEL 2 (DUT/RADIO PATH):")
|
||
print(f" Total Artifacts: {result['channel_2_dut']['total_artifacts']}")
|
||
print(f" Artifact Rate: {result['channel_2_dut']['artifact_rate_per_minute']:.2f} per minute")
|
||
if result['channel_2_dut']['artifacts_by_type']:
|
||
print(" By Type:")
|
||
for artifact_type, count in result['channel_2_dut']['artifacts_by_type'].items():
|
||
print(f" - {artifact_type}: {count}")
|
||
|
||
# Display frequency accuracy for channel 2
|
||
if 'frequency_accuracy' in result['channel_2_dut']:
|
||
freq_acc = result['channel_2_dut']['frequency_accuracy']
|
||
print(f" Frequency Accuracy:")
|
||
print(f" Expected: {freq_acc['expected_freq_hz']:.1f} Hz")
|
||
print(f" Measured: {freq_acc['measured_freq_hz']:.2f} Hz")
|
||
print(f" Error: {freq_acc['error_hz']:+.2f} Hz ({freq_acc['error_percent']:+.3f}%)")
|
||
|
||
ch1_count = result['channel_1_loopback']['total_artifacts']
|
||
ch2_count = result['channel_2_dut']['total_artifacts']
|
||
|
||
if ch2_count > ch1_count:
|
||
delta = ch2_count - ch1_count
|
||
print(f"\n⚠️ DEGRADATION DETECTED: {delta} more artifacts in radio path vs loopback")
|
||
elif ch1_count == ch2_count == 0:
|
||
print("\n✅ EXCELLENT: No artifacts detected in either path!")
|
||
else:
|
||
print(f"\nℹ️ Loopback baseline: {ch1_count} artifacts")
|
||
|
||
except Exception as e:
|
||
print(f"\n❌ ERROR: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
result = {
|
||
'error': str(e),
|
||
'test_frequency_hz': config['artifact_detection']['test_frequency'],
|
||
'duration_sec': config['artifact_detection']['duration']
|
||
}
|
||
|
||
output_data = {
|
||
'metadata': {
|
||
'test_id': test_id,
|
||
'timestamp': timestamp.isoformat(),
|
||
'serial_number': args.serial_number,
|
||
'software_version': args.software_version,
|
||
'comment': args.comment
|
||
},
|
||
'artifact_detection_result': result
|
||
}
|
||
|
||
output_file = test_output_dir / f"{test_id}_artifact_detection_results.yaml"
|
||
with open(output_file, 'w') as f:
|
||
yaml.dump(output_data, f, default_flow_style=False, sort_keys=False)
|
||
|
||
print("\n" + "=" * 70)
|
||
print("✅ Results saved to:")
|
||
print(f" YAML: {output_file}")
|
||
if save_plots:
|
||
print(f" Summary plots: {test_output_dir}/")
|
||
print(f" Individual anomaly plots: {test_output_dir}/individual_anomalies/")
|
||
print("=" * 70)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|