#!/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()