diff --git a/data/processed/clock001/results.json b/data/processed/clock001/results.json new file mode 100644 index 0000000..bc5ce96 --- /dev/null +++ b/data/processed/clock001/results.json @@ -0,0 +1,1662 @@ +{ + "status": "PARTIAL", + "experiment": "CLOCK-001 INDEPENDENT TX/RX CLOCK READINESS", + "seed": 20260917, + "sample_rate_nominal_hz": 8000000, + "symbol_rate": 500000, + "fec_rate": "2/3", + "packets_per_case": 3, + "payload_sizes": [ + 256, + 512, + 1012 + ], + "offsets_ppm": [ + 0, + -1, + 1, + -5, + 5, + -10, + 10, + -20, + 20, + -50, + 50, + -100, + 100 + ], + "primary_limit_ppm": 50, + "grid": [ + { + "sample_clock_offset_ppm": 0, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.0, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.0001202848978874016, + "final_timing_error_symbols_median": -0.0001202848978874016, + "timing_drift_across_packet_symbols_median": 0.0, + "injected_timing_drift_across_packet_symbols": 0.0, + "maximum_absolute_final_timing_error_symbols": 0.0001202977764478419, + "cfo_estimate_error_hz_median": -1.1068862124136014e-07, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.8397121999878436, + "received_duration_s": 0.028292375, + "receiver_realtime_factor": 29.679805954354965 + }, + { + "sample_clock_offset_ppm": -1, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.1250001250009518, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.0007105024278644123, + "final_timing_error_symbols_median": 0.0023254975721355878, + "timing_drift_across_packet_symbols_median": 0.003036, + "injected_timing_drift_across_packet_symbols": 0.003036, + "maximum_absolute_final_timing_error_symbols": 0.002349132208044408, + "cfo_estimate_error_hz_median": -0.0008018956246522574, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.017520800000056624, + "received_duration_s": 0.028292278292278292, + "receiver_realtime_factor": 0.6192785119337142 + }, + { + "sample_clock_offset_ppm": 1, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -0.12499987498449627, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.0004696450341725722, + "final_timing_error_symbols_median": -0.002566354965827428, + "timing_drift_across_packet_symbols_median": -0.003036, + "injected_timing_drift_across_packet_symbols": -0.003036, + "maximum_absolute_final_timing_error_symbols": 0.002590318990703905, + "cfo_estimate_error_hz_median": 0.0005238959214671352, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.017916299868375063, + "received_duration_s": 0.028292346707653297, + "receiver_realtime_factor": 0.6332560551977319 + }, + { + "sample_clock_offset_ppm": -5, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.6250031250237953, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.003350552009578678, + "final_timing_error_symbols_median": 0.011829447990421321, + "timing_drift_across_packet_symbols_median": 0.015179999999999999, + "injected_timing_drift_across_packet_symbols": 0.015179999999999999, + "maximum_absolute_final_timing_error_symbols": 0.011861775263342117, + "cfo_estimate_error_hz_median": -0.004030897852909998, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.019200999988242984, + "received_duration_s": 0.028292266461332306, + "receiver_realtime_factor": 0.6786660239640198 + }, + { + "sample_clock_offset_ppm": 5, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -0.6249968750198605, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.003108310811512638, + "final_timing_error_symbols_median": -0.012071689188487361, + "timing_drift_across_packet_symbols_median": -0.015179999999999999, + "injected_timing_drift_across_packet_symbols": -0.015179999999999999, + "maximum_absolute_final_timing_error_symbols": 0.012102391238303617, + "cfo_estimate_error_hz_median": 0.0038927718015456803, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.016908299876376987, + "received_duration_s": 0.02829235853820731, + "receiver_realtime_factor": 0.5976277959839664 + }, + { + "sample_clock_offset_ppm": -10, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 1.250012500124285, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.006588812689187762, + "final_timing_error_symbols_median": 0.023771187310812236, + "timing_drift_across_packet_symbols_median": 0.030359999999999998, + "injected_timing_drift_across_packet_symbols": 0.030359999999999998, + "maximum_absolute_final_timing_error_symbols": 0.023834695004540107, + "cfo_estimate_error_hz_median": -0.006443603630148287, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.01689139986410737, + "received_duration_s": 0.028292282922829228, + "receiver_realtime_factor": 0.5970320567690064 + }, + { + "sample_clock_offset_ppm": 10, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -1.2499875001376495, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.006348132576931675, + "final_timing_error_symbols_median": -0.024011867423068323, + "timing_drift_across_packet_symbols_median": -0.030359999999999998, + "injected_timing_drift_across_packet_symbols": -0.030359999999999998, + "maximum_absolute_final_timing_error_symbols": 0.024075431801760472, + "cfo_estimate_error_hz_median": 0.006132770926496223, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.01645309990271926, + "received_duration_s": 0.02829234207657923, + "receiver_realtime_factor": 0.5815389852909827 + }, + { + "sample_clock_offset_ppm": -20, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 2.500050001006457, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.013055876437647385, + "final_timing_error_symbols_median": 0.04766412356235261, + "timing_drift_across_packet_symbols_median": 0.060719999999999996, + "injected_timing_drift_across_packet_symbols": 0.060719999999999996, + "maximum_absolute_final_timing_error_symbols": 0.047755051814368454, + "cfo_estimate_error_hz_median": -0.006300248766615724, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.018176699988543987, + "received_duration_s": 0.028292315846316925, + "receiver_realtime_factor": 0.6424606627212603 + }, + { + "sample_clock_offset_ppm": 20, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -2.4999500009871554, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.012815096072699816, + "final_timing_error_symbols_median": -0.04790490392730018, + "timing_drift_across_packet_symbols_median": -0.060719999999999996, + "injected_timing_drift_across_packet_symbols": -0.060719999999999996, + "maximum_absolute_final_timing_error_symbols": 0.047996033434481444, + "cfo_estimate_error_hz_median": 0.005821352490297471, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.018377100117504597, + "received_duration_s": 0.028292309153816926, + "receiver_realtime_factor": 0.6495440162764281 + }, + { + "sample_clock_offset_ppm": -50, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 6.250312515621772, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.03244822526812641, + "final_timing_error_symbols_median": 0.11935177473187358, + "timing_drift_across_packet_symbols_median": 0.1518, + "injected_timing_drift_across_packet_symbols": 0.1518, + "maximum_absolute_final_timing_error_symbols": 0.1194042152532318, + "cfo_estimate_error_hz_median": -0.0016794133036182046, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.01755540003068745, + "received_duration_s": 0.028292289614480725, + "receiver_realtime_factor": 0.6205012132246145 + }, + { + "sample_clock_offset_ppm": 50, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -6.249687515635742, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.032207171874688356, + "final_timing_error_symbols_median": -0.11959282812531163, + "timing_drift_across_packet_symbols_median": -0.1518, + "injected_timing_drift_across_packet_symbols": -0.1518, + "maximum_absolute_final_timing_error_symbols": 0.11964600212049553, + "cfo_estimate_error_hz_median": 0.0012272698639099744, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.016806199913844466, + "received_duration_s": 0.028292335383230834, + "receiver_realtime_factor": 0.5940195351920534 + }, + { + "sample_clock_offset_ppm": -100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 12.501250125016668, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.06474937094208144, + "final_timing_error_symbols_median": 0.23885062905791854, + "timing_drift_across_packet_symbols_median": 0.3036, + "injected_timing_drift_across_packet_symbols": 0.3036, + "maximum_absolute_final_timing_error_symbols": 0.238853995666244, + "cfo_estimate_error_hz_median": -0.02872907311107653, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.01763760019093752, + "received_duration_s": 0.028292329232923293, + "receiver_realtime_factor": 0.6234057311341107 + }, + { + "sample_clock_offset_ppm": 100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -12.498750124985236, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.0645079320493096, + "final_timing_error_symbols_median": -0.23909206795069038, + "timing_drift_across_packet_symbols_median": -0.3036, + "injected_timing_drift_across_packet_symbols": -0.3036, + "maximum_absolute_final_timing_error_symbols": 0.23909539922423717, + "cfo_estimate_error_hz_median": 0.027265873628373072, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.018008399987593293, + "received_duration_s": 0.028292295770422957, + "receiver_realtime_factor": 0.6365125026870195 + }, + { + "sample_clock_offset_ppm": 0, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.0, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.0001202848978874016, + "final_timing_error_symbols_median": -0.0001202848978874016, + "timing_drift_across_packet_symbols_median": 0.0, + "injected_timing_drift_across_packet_symbols": 0.0, + "maximum_absolute_final_timing_error_symbols": 0.0001202859302793513, + "cfo_estimate_error_hz_median": 8.150090563948534e-07, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.022303700214251876, + "received_duration_s": 0.037508375, + "receiver_realtime_factor": 0.5946325377799458 + }, + { + "sample_clock_offset_ppm": -1, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.1250001250009518, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.0007015730834609712, + "final_timing_error_symbols_median": 0.0038704269165390284, + "timing_drift_across_packet_symbols_median": 0.004572, + "injected_timing_drift_across_packet_symbols": 0.004572, + "maximum_absolute_final_timing_error_symbols": 0.003888341129087959, + "cfo_estimate_error_hz_median": -0.0007460277737802445, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.022407100070267916, + "received_duration_s": 0.03750828750828751, + "receiver_realtime_factor": 0.5973906450758925 + }, + { + "sample_clock_offset_ppm": 1, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -0.12499987498449627, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 11, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.0004606082038662862, + "final_timing_error_symbols_median": -0.0041113917961337135, + "timing_drift_across_packet_symbols_median": -0.004572, + "injected_timing_drift_across_packet_symbols": -0.004572, + "maximum_absolute_final_timing_error_symbols": 0.004129698571152286, + "cfo_estimate_error_hz_median": 0.0004099292953144429, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.021075000055134296, + "received_duration_s": 0.03750833749166251, + "receiver_realtime_factor": 0.5618750780361546 + }, + { + "sample_clock_offset_ppm": -5, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.6250031250237953, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.003318224736657882, + "final_timing_error_symbols_median": 0.019541775263342116, + "timing_drift_across_packet_symbols_median": 0.02286, + "injected_timing_drift_across_packet_symbols": 0.02286, + "maximum_absolute_final_timing_error_symbols": 0.019587448447927598, + "cfo_estimate_error_hz_median": -0.0020655458639792235, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.02366730011999607, + "received_duration_s": 0.037508312541562706, + "receiver_realtime_factor": 0.6309881334642953 + }, + { + "sample_clock_offset_ppm": 5, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -0.6249968750198605, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.003077608761696382, + "final_timing_error_symbols_median": -0.019782391238303616, + "timing_drift_across_packet_symbols_median": -0.02286, + "injected_timing_drift_across_packet_symbols": -0.02286, + "maximum_absolute_final_timing_error_symbols": 0.019828537492648464, + "cfo_estimate_error_hz_median": 0.0015894488198059387, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.023056500125676394, + "received_duration_s": 0.037508312458437706, + "receiver_realtime_factor": 0.614703744702588 + }, + { + "sample_clock_offset_ppm": -10, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 1.250012500124285, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.006524022886878811, + "final_timing_error_symbols_median": 0.039195977113121186, + "timing_drift_across_packet_symbols_median": 0.04572, + "injected_timing_drift_across_packet_symbols": 0.04572, + "maximum_absolute_final_timing_error_symbols": 0.03921932589848322, + "cfo_estimate_error_hz_median": -0.002528543773080427, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.02315999986603856, + "received_duration_s": 0.037508250082500826, + "receiver_realtime_factor": 0.61746415295561 + }, + { + "sample_clock_offset_ppm": 10, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -1.2499875001376495, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.006282197935433942, + "final_timing_error_symbols_median": -0.039437802064566055, + "timing_drift_across_packet_symbols_median": -0.04572, + "injected_timing_drift_across_packet_symbols": -0.04572, + "maximum_absolute_final_timing_error_symbols": 0.039460583237013194, + "cfo_estimate_error_hz_median": 0.0019670058502572196, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.02339330012910068, + "received_duration_s": 0.037508374916250836, + "receiver_realtime_factor": 0.623682049177911 + }, + { + "sample_clock_offset_ppm": -20, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 2.500050001006457, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.013034144400990044, + "final_timing_error_symbols_median": 0.07840585559900995, + "timing_drift_across_packet_symbols_median": 0.09144, + "injected_timing_drift_across_packet_symbols": 0.09144, + "maximum_absolute_final_timing_error_symbols": 0.07847505181436845, + "cfo_estimate_error_hz_median": -0.003790347632368629, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.02175009995698929, + "received_duration_s": 0.0375082501650033, + "receiver_realtime_factor": 0.5798750904483143 + }, + { + "sample_clock_offset_ppm": 20, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -2.4999500009871554, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.012792055046702444, + "final_timing_error_symbols_median": -0.07864794495329755, + "timing_drift_across_packet_symbols_median": -0.09144, + "injected_timing_drift_across_packet_symbols": -0.09144, + "maximum_absolute_final_timing_error_symbols": 0.07871603343448144, + "cfo_estimate_error_hz_median": 0.0033676907774906617, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.022398100001737475, + "received_duration_s": 0.037508374832503355, + "receiver_realtime_factor": 0.5971493060351983 + }, + { + "sample_clock_offset_ppm": -50, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 6.250312515621772, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.03245086220566762, + "final_timing_error_symbols_median": 0.19614913779433238, + "timing_drift_across_packet_symbols_median": 0.2286, + "injected_timing_drift_across_packet_symbols": 0.2286, + "maximum_absolute_final_timing_error_symbols": 0.19620942143919237, + "cfo_estimate_error_hz_median": -0.020700895821461174, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.021463300101459026, + "received_duration_s": 0.03750825041252063, + "receiver_realtime_factor": 0.572228772747405 + }, + { + "sample_clock_offset_ppm": 50, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -6.249687515635742, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 11, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.03220871959989324, + "final_timing_error_symbols_median": -0.19639128040010675, + "timing_drift_across_packet_symbols_median": -0.2286, + "injected_timing_drift_across_packet_symbols": -0.2286, + "maximum_absolute_final_timing_error_symbols": 0.19645065704898443, + "cfo_estimate_error_hz_median": 0.01976830450523792, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.021081000100821257, + "received_duration_s": 0.03750837458127093, + "receiver_realtime_factor": 0.5620344879286675 + }, + { + "sample_clock_offset_ppm": -100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 12.501250125016668, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.0647549864697794, + "final_timing_error_symbols_median": 0.3924450135302206, + "timing_drift_across_packet_symbols_median": 0.4572, + "injected_timing_drift_across_packet_symbols": 0.4572, + "maximum_absolute_final_timing_error_symbols": 0.39245476215927483, + "cfo_estimate_error_hz_median": -0.029640648889721177, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.02181509998627007, + "received_duration_s": 0.03750825082508251, + "receiver_realtime_factor": 0.5816080330699367 + }, + { + "sample_clock_offset_ppm": 100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -12.498750124985236, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 512, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.0645135100439802, + "final_timing_error_symbols_median": -0.3926864899560198, + "timing_drift_across_packet_symbols_median": -0.4572, + "injected_timing_drift_across_packet_symbols": -0.4572, + "maximum_absolute_final_timing_error_symbols": 0.39269640599758276, + "cfo_estimate_error_hz_median": 0.02775763676994636, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.021454399917274714, + "received_duration_s": 0.03750837416258374, + "receiver_realtime_factor": 0.5719895995565818 + }, + { + "sample_clock_offset_ppm": 0, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.0, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 11, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.00012028107448713854, + "final_timing_error_symbols_median": -0.00012028107448713854, + "timing_drift_across_packet_symbols_median": 0.0, + "injected_timing_drift_across_packet_symbols": 0.0, + "maximum_absolute_final_timing_error_symbols": 0.0001202848978874016, + "cfo_estimate_error_hz_median": 4.369539741565404e-07, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.030975400004535913, + "received_duration_s": 0.055508375, + "receiver_realtime_factor": 0.5580311080001156 + }, + { + "sample_clock_offset_ppm": -1, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.1250001250009518, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.0006959209895285312, + "final_timing_error_symbols_median": 0.0068760790104714685, + "timing_drift_across_packet_symbols_median": 0.007572, + "injected_timing_drift_across_packet_symbols": 0.007572, + "maximum_absolute_final_timing_error_symbols": 0.00688257523281197, + "cfo_estimate_error_hz_median": -0.000644826702768525, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.032501199981197715, + "received_duration_s": 0.05550830550830551, + "receiver_realtime_factor": 0.5855195845662173 + }, + { + "sample_clock_offset_ppm": 1, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -0.12499987498449627, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.0004542454444163013, + "final_timing_error_symbols_median": -0.007117754555583698, + "timing_drift_across_packet_symbols_median": -0.007572, + "injected_timing_drift_across_packet_symbols": -0.007572, + "maximum_absolute_final_timing_error_symbols": 0.007123675582551979, + "cfo_estimate_error_hz_median": 0.00020492363680948977, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03267369978129864, + "received_duration_s": 0.055508319491680516, + "receiver_realtime_factor": 0.5886270757340386 + }, + { + "sample_clock_offset_ppm": -5, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 0.6250031250237953, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.003422685991608887, + "final_timing_error_symbols_median": 0.03443731400839111, + "timing_drift_across_packet_symbols_median": 0.03786, + "injected_timing_drift_across_packet_symbols": 0.03786, + "maximum_absolute_final_timing_error_symbols": 0.034541775263342116, + "cfo_estimate_error_hz_median": -0.002638257556384138, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03262909990735352, + "received_duration_s": 0.05550827754138771, + "receiver_realtime_factor": 0.5878240390908335 + }, + { + "sample_clock_offset_ppm": 5, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -0.6249968750198605, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.0031816034534131177, + "final_timing_error_symbols_median": -0.03467839654658688, + "timing_drift_across_packet_symbols_median": -0.03786, + "injected_timing_drift_across_packet_symbols": -0.03786, + "maximum_absolute_final_timing_error_symbols": 0.034782391238303616, + "cfo_estimate_error_hz_median": 0.002297398507955628, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03224770002998412, + "received_duration_s": 0.05550834745826271, + "receiver_realtime_factor": 0.5809522622562577 + }, + { + "sample_clock_offset_ppm": -10, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 1.250012500124285, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 11, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.006525304995459891, + "final_timing_error_symbols_median": 0.0691946950045401, + "timing_drift_across_packet_symbols_median": 0.07572, + "injected_timing_drift_across_packet_symbols": 0.07572, + "maximum_absolute_final_timing_error_symbols": 0.06920749218188459, + "cfo_estimate_error_hz_median": -0.0014880484750994238, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.029740399913862348, + "received_duration_s": 0.05550830508305083, + "receiver_realtime_factor": 0.535782886351243 + }, + { + "sample_clock_offset_ppm": 10, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -1.2499875001376495, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.006284568198239526, + "final_timing_error_symbols_median": -0.06943543180176047, + "timing_drift_across_packet_symbols_median": -0.07572, + "injected_timing_drift_across_packet_symbols": -0.07572, + "maximum_absolute_final_timing_error_symbols": 0.06944914155901526, + "cfo_estimate_error_hz_median": 0.0009202116550217632, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03335119993425906, + "received_duration_s": 0.05550831991680082, + "receiver_realtime_factor": 0.6008324515000242 + }, + { + "sample_clock_offset_ppm": -20, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 2.500050001006457, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.013013613577641081, + "final_timing_error_symbols_median": 0.1384263864223589, + "timing_drift_across_packet_symbols_median": 0.15144, + "injected_timing_drift_across_packet_symbols": 0.15144, + "maximum_absolute_final_timing_error_symbols": 0.13847505181436845, + "cfo_estimate_error_hz_median": -0.0008530164641431526, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03200100013054907, + "received_duration_s": 0.05550836016720334, + "receiver_realtime_factor": 0.576507755483229 + }, + { + "sample_clock_offset_ppm": 20, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -2.4999500009871554, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.012771585443260847, + "final_timing_error_symbols_median": -0.13866841455673914, + "timing_drift_across_packet_symbols_median": -0.15144, + "injected_timing_drift_across_packet_symbols": -0.15144, + "maximum_absolute_final_timing_error_symbols": 0.13871603343448144, + "cfo_estimate_error_hz_median": 0.0003106543373863069, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03151199989952147, + "received_duration_s": 0.055508264834703315, + "receiver_realtime_factor": 0.5676992425066839 + }, + { + "sample_clock_offset_ppm": -50, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 6.250312515621772, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": -0.03247625896256068, + "final_timing_error_symbols_median": 0.3461237410374393, + "timing_drift_across_packet_symbols_median": 0.3786, + "injected_timing_drift_across_packet_symbols": 0.3786, + "maximum_absolute_final_timing_error_symbols": 0.3461491377943324, + "cfo_estimate_error_hz_median": -0.0008270628052375883, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.031876299995929, + "received_duration_s": 0.05550827541377069, + "receiver_realtime_factor": 0.5742621214281325 + }, + { + "sample_clock_offset_ppm": 50, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -6.249687515635742, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.03223447103482613, + "final_timing_error_symbols_median": -0.34636552896517386, + "timing_drift_across_packet_symbols_median": -0.3786, + "injected_timing_drift_across_packet_symbols": -0.3786, + "maximum_absolute_final_timing_error_symbols": 0.34639128040010675, + "cfo_estimate_error_hz_median": 0.0003217930743515396, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03191090002655983, + "received_duration_s": 0.05550834958252087, + "receiver_realtime_factor": 0.5748846843143813 + }, + { + "sample_clock_offset_ppm": -100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 12.501250125016668, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 12, + "detected_packets": 0, + "crc_valid_packets": 0, + "exact_packets": 0, + "packet_success_rate": 0.0, + "exact_sequences": [], + "missing_sequences": [ + 0, + 1, + 2 + ], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": null, + "final_timing_error_symbols_median": null, + "timing_drift_across_packet_symbols_median": null, + "injected_timing_drift_across_packet_symbols": 0.7572, + "maximum_absolute_final_timing_error_symbols": null, + "cfo_estimate_error_hz_median": null, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.037264199927449226, + "received_duration_s": 0.055508300830083006, + "receiver_realtime_factor": 0.6713266190856576 + }, + { + "sample_clock_offset_ppm": 100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -12.498750124985236, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 3, + "correlation_candidates": 13, + "detected_packets": 0, + "crc_valid_packets": 0, + "exact_packets": 0, + "packet_success_rate": 0.0, + "exact_sequences": [], + "missing_sequences": [ + 0, + 1, + 2 + ], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": null, + "final_timing_error_symbols_median": null, + "timing_drift_across_packet_symbols_median": null, + "injected_timing_drift_across_packet_symbols": -0.7572, + "maximum_absolute_final_timing_error_symbols": null, + "cfo_estimate_error_hz_median": null, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.03416480007581413, + "received_duration_s": 0.055508324167583244, + "receiver_realtime_factor": 0.6154896691290548 + } + ], + "combined": [ + { + "sample_clock_offset_ppm": 5, + "cfo_hz": 20, + "effective_cfo_hz_at_nominal_rx_rate": 19.374903125484707, + "initial_phase_degrees": 15, + "ebn0_db": 9, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 5, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.0030668110666738357, + "final_timing_error_symbols_median": -0.012113188933326163, + "timing_drift_across_packet_symbols_median": -0.015179999999999999, + "injected_timing_drift_across_packet_symbols": -0.015179999999999999, + "maximum_absolute_final_timing_error_symbols": 0.012201059466881353, + "cfo_estimate_error_hz_median": -0.997791176333795, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.013744600117206573, + "received_duration_s": 0.02829235853820731, + "receiver_realtime_factor": 0.4858060913742921 + }, + { + "sample_clock_offset_ppm": 5, + "cfo_hz": 20, + "effective_cfo_hz_at_nominal_rx_rate": 19.374903125484707, + "initial_phase_degrees": 15, + "ebn0_db": 12, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 5, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.002459860083035892, + "final_timing_error_symbols_median": -0.012720139916964107, + "timing_drift_across_packet_symbols_median": -0.015179999999999999, + "injected_timing_drift_across_packet_symbols": -0.015179999999999999, + "maximum_absolute_final_timing_error_symbols": 0.01394447967458759, + "cfo_estimate_error_hz_median": 0.3757850511965586, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.01364679983817041, + "received_duration_s": 0.02829235853820731, + "receiver_realtime_factor": 0.4823493177403764 + }, + { + "sample_clock_offset_ppm": 10, + "cfo_hz": 100, + "effective_cfo_hz_at_nominal_rx_rate": 98.74901250987023, + "initial_phase_degrees": 90, + "ebn0_db": 9, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 5, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.007915003169078005, + "final_timing_error_symbols_median": -0.022444996830921993, + "timing_drift_across_packet_symbols_median": -0.030359999999999998, + "injected_timing_drift_across_packet_symbols": -0.030359999999999998, + "maximum_absolute_final_timing_error_symbols": 0.02738125245858042, + "cfo_estimate_error_hz_median": -0.6650643363015121, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.013825700152665377, + "received_duration_s": 0.02829234207657923, + "receiver_realtime_factor": 0.488672875340019 + }, + { + "sample_clock_offset_ppm": 10, + "cfo_hz": 100, + "effective_cfo_hz_at_nominal_rx_rate": 98.74901250987023, + "initial_phase_degrees": 90, + "ebn0_db": 12, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 5, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.00557470597414067, + "final_timing_error_symbols_median": -0.024785294025859328, + "timing_drift_across_packet_symbols_median": -0.030359999999999998, + "injected_timing_drift_across_packet_symbols": -0.030359999999999998, + "maximum_absolute_final_timing_error_symbols": 0.02935591165467427, + "cfo_estimate_error_hz_median": 0.48465531552886887, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.014139400096610188, + "received_duration_s": 0.02829234207657923, + "receiver_realtime_factor": 0.49976067935057833 + }, + { + "sample_clock_offset_ppm": 20, + "cfo_hz": 100, + "effective_cfo_hz_at_nominal_rx_rate": 97.49805003900838, + "initial_phase_degrees": -45, + "ebn0_db": 9, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 5, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.01883429428744421, + "final_timing_error_symbols_median": -0.04188570571255579, + "timing_drift_across_packet_symbols_median": -0.060719999999999996, + "injected_timing_drift_across_packet_symbols": -0.060719999999999996, + "maximum_absolute_final_timing_error_symbols": 0.049179322647790014, + "cfo_estimate_error_hz_median": -0.4022989255894487, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.013649400090798736, + "received_duration_s": 0.028292309153816926, + "receiver_realtime_factor": 0.4824420663789223 + }, + { + "sample_clock_offset_ppm": 20, + "cfo_hz": 100, + "effective_cfo_hz_at_nominal_rx_rate": 97.49805003900838, + "initial_phase_degrees": -45, + "ebn0_db": 12, + "payload_bytes": 256, + "tx_packets": 3, + "correlation_candidates": 5, + "detected_packets": 3, + "crc_valid_packets": 3, + "exact_packets": 3, + "packet_success_rate": 1.0, + "exact_sequences": [ + 0, + 1, + 2 + ], + "missing_sequences": [], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": 0.012147076622133213, + "final_timing_error_symbols_median": -0.048572923377866783, + "timing_drift_across_packet_symbols_median": -0.060719999999999996, + "injected_timing_drift_across_packet_symbols": -0.060719999999999996, + "maximum_absolute_final_timing_error_symbols": 0.0523186275391333, + "cfo_estimate_error_hz_median": -1.148855663408625, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.014022699790075421, + "received_duration_s": 0.028292309153816926, + "receiver_realtime_factor": 0.49563645419814073 + } + ], + "single_long_stress": [ + { + "sample_clock_offset_ppm": -100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": 12.501250125016668, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 1, + "correlation_candidates": 1, + "detected_packets": 0, + "crc_valid_packets": 0, + "exact_packets": 0, + "packet_success_rate": 0.0, + "exact_sequences": [], + "missing_sequences": [ + 0 + ], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": null, + "final_timing_error_symbols_median": null, + "timing_drift_across_packet_symbols_median": null, + "injected_timing_drift_across_packet_symbols": 0.7572, + "maximum_absolute_final_timing_error_symbols": null, + "cfo_estimate_error_hz_median": null, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.008426700020208955, + "received_duration_s": 0.01518001800180018, + "receiver_realtime_factor": 0.5551179200979631 + }, + { + "sample_clock_offset_ppm": 100, + "cfo_hz": 0.0, + "effective_cfo_hz_at_nominal_rx_rate": -12.498750124985236, + "initial_phase_degrees": 0.0, + "ebn0_db": null, + "payload_bytes": 1012, + "tx_packets": 1, + "correlation_candidates": 1, + "detected_packets": 0, + "crc_valid_packets": 0, + "exact_packets": 0, + "packet_success_rate": 0.0, + "exact_sequences": [], + "missing_sequences": [ + 0 + ], + "duplicate_sequences": [], + "unexpected_or_wrong_sequences": [], + "initial_timing_error_symbols_median": null, + "final_timing_error_symbols_median": null, + "timing_drift_across_packet_symbols_median": null, + "injected_timing_drift_across_packet_symbols": -0.7572, + "maximum_absolute_final_timing_error_symbols": null, + "cfo_estimate_error_hz_median": null, + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": true, + "continuous_timing_tracking_used": false, + "receiver_time_s": 0.008934899931773543, + "received_duration_s": 0.01518010698930107, + "receiver_realtime_factor": 0.588592685023291 + } + ], + "primary_grid_all_exact": true, + "stress_grid_all_exact": false, + "combined_all_exact": true, + "benchmark_all_exact": true, + "maximum_symmetric_ppm_with_all_exact_by_payload": { + "256": 100, + "512": 100, + "1012": 50 + }, + "maximum_symmetric_ppm_with_all_payloads_exact": 50, + "per_packet_reacquisition_sufficient_for_primary_grid": true, + "continuous_tracking_required_for_primary_grid": false, + "intra_packet_timing_correction_needed_to_cover_long_packet_stress": true, + "benchmark": { + "description": "VIDEO-005 32 long packets; independent clocks +20 ppm, CFO 20 Hz, phase 15 deg, Eb/N0 9 dB", + "iq_duration_s": 0.4857639097218056, + "warm_receiver_times_s": [ + 0.2721281999256462, + 0.261731400154531, + 0.2624587998725474 + ], + "warm_realtime_factor": 0.5403011516908618, + "exact_packets_each_run": [ + 32, + 32, + 32 + ], + "tx_packets": 32 + }, + "rf_tx": 0, + "sdr_hardware": "NOT_ACCESSED", + "hardware_status": "NOT_RUN" +} diff --git a/data/processed/rf001/offline_roundtrip.json b/data/processed/rf001/offline_roundtrip.json new file mode 100644 index 0000000..ff0f12f --- /dev/null +++ b/data/processed/rf001/offline_roundtrip.json @@ -0,0 +1,1348 @@ +{ + "rf001_status": "PREPARED_NOT_RUN", + "test_id": "RF001_QPSK_500K_R23_256B_100P_GAP5MS", + "modulation": "QPSK", + "symbol_rate": 500000, + "inner_fec": "2/3", + "payload_bytes": 256, + "inter_packet_gap_ms": 5, + "tx_packets": 100, + "iq_format": "c64le", + "iq_sample_count": 8833828, + "iq_sha256": "7cd81395efaa88b623b7e4e5a02e3a80b52185007397ea56ceaacc13353974b2", + "detected_preambles": 557, + "rejected_candidates": 457, + "decoded_packets": 100, + "crc_valid_packets": 100, + "packet_sequences": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99 + ], + "duplicates": 0, + "missing_packets": [], + "exact_packets": 100, + "packet_success_rate": 1.0, + "raw_ber": 0.0, + "raw_ber_scope": "ALL_EXPECTED_PACKETS", + "raw_bit_errors_on_crc_valid_packets": 0, + "raw_bits_compared_on_crc_valid_packets": 322500, + "raw_ber_on_crc_valid_packets": 0.0, + "raw_ber_replay_failures": 0, + "post_fec_bit_errors": 0, + "post_fec_bits_compared": 214400, + "post_fec_error_rate": 0.0, + "post_fec_error_rate_on_compared_packets": 0.0, + "post_fec_uncomparable_packets": 0, + "cfo_median_hz": -5.779635023821704e-08, + "cfo_p95_absolute_hz": 1.0706373568243788e-06, + "phase_error_rad": null, + "timing_offset_symbols": null, + "phase_and_timing_estimates": [ + { + "sequence_number": 0, + "packet_sha256": "e50f93a6389e4911db17eb822d583f06fae05df6708c64019de08c6ec6834960", + "payload_sha256": "4e95e78189b1156739263a383aa26e568b369a24737b78f169a02d7182b66f0b", + "start_sample": 223.9980754416338, + "preamble_score": 0.9999534804857549, + "cfo_hz": 4.369539741565404e-07, + "phase_rad": -5.35778814821809e-09, + "timing_phase_symbols": 0.9998797151021126, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00027463028018630286 + }, + { + "sequence_number": 1, + "packet_sha256": "35c7fbd31f2452da87e40be898553a29d05ae1303ab7e9928d514ca7f94f19e9", + "payload_sha256": "8d2104b86fe512059111192d7ff0bc089f1931a57756c8b930fa03c238352e75", + "start_sample": 88960.9980756568, + "preamble_score": 0.9964055608418225, + "cfo_hz": -4.1585811430236864e-07, + "phase_rad": 3.0434178880297242, + "timing_phase_symbols": 0.062379728549785796, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003367398322383227 + }, + { + "sequence_number": 2, + "packet_sha256": "d672a74d80a3e16265bb4fe531579c907dcff464ae036e4cf129c3e22de8ec80", + "payload_sha256": "a1c20c33df93076fcb95cbfc1e5c6eeeb7c0b35733a4e28a8c7330c994850aca", + "start_sample": 177697.99807523558, + "preamble_score": 0.9853599251365788, + "cfo_hz": -1.1068862124136014e-07, + "phase_rad": -0.1963495405063104, + "timing_phase_symbols": 0.12487970222355216, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0002126313959375951 + }, + { + "sequence_number": 3, + "packet_sha256": "7083d4c56b3cda44f9d34be4588809519dd65a4072dbfcc6d563470a2a527547", + "payload_sha256": "d3ffdd0789e7bb3f7dba60b96a61175414912253001475d16ec6f837eb8b17a5", + "start_sample": 266434.99807517155, + "preamble_score": 0.9656523338185405, + "cfo_hz": -4.473674829402695e-07, + "phase_rad": 2.8470683465838924, + "timing_phase_symbols": 0.18737969822177547, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003351642994881687 + }, + { + "sequence_number": 4, + "packet_sha256": "8e74c8ac395bfca64ad1a2b72f755ff909626735d375bdc75d12750dae79334f", + "payload_sha256": "03f62ee09971f903b3610e9b73a8484021e76d09a5758a4aafc0e951ca1a4399", + "start_sample": 355171.99807572213, + "preamble_score": 0.9355140624945403, + "cfo_hz": 4.0586201483579405e-07, + "phase_rad": -0.39269908709778867, + "timing_phase_symbols": 0.249879732633417, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003033432671531153 + }, + { + "sequence_number": 5, + "packet_sha256": "46d3e3d8b40beadb6a881c821c460e44a4cdc6e43ae2c94649677f7b26087617", + "payload_sha256": "a7915faacc162ae02f5b962eafb6ee221d4320e60a9a29f9aaafaa23358e76b7", + "start_sample": 443908.9980755144, + "preamble_score": 0.9656287555152533, + "cfo_hz": 3.455130344857617e-07, + "phase_rad": 2.6507187997073554, + "timing_phase_symbols": 0.31237971964947064, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0002783362762480548 + }, + { + "sequence_number": 6, + "packet_sha256": "3c72b11fc893d6b28ec966f0677897ffdb33da26c820fe29a5528be0535c8638", + "payload_sha256": "9484962525e16284e0a5efb2d1e3672c5ee688f7edce17c25d0eb4d2884eee5d", + "start_sample": 532645.9980753463, + "preamble_score": 0.9853526566737129, + "cfo_hz": -1.786572046673675e-07, + "phase_rad": -0.589048626596313, + "timing_phase_symbols": 0.37487970914662583, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003362094110325385 + }, + { + "sequence_number": 7, + "packet_sha256": "ed6a563a919e2ada004520042696bb2b18af80eb81afcb9c35ba0b4ce96b712e", + "payload_sha256": "b9476ffd787f7d88612de8763244274f5b89b0b3744dbac7d5286992de7be12c", + "start_sample": 621382.9980759135, + "preamble_score": 0.996404445429844, + "cfo_hz": 3.156352611478282e-07, + "phase_rad": 2.454369262307951, + "timing_phase_symbols": 0.4373797445950913, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00025197589631829426 + }, + { + "sequence_number": 8, + "packet_sha256": "cc377708eae5a9da1fd7238f124b1a8658769ec8b19f405b9f3d902eece165f2", + "payload_sha256": "1e8ce60401efc25916c78b94fd5ee1d5ad9f61fd4ca0f79377815bf7e2e8cdc0", + "start_sample": 710119.9980758594, + "preamble_score": 0.9999534914996233, + "cfo_hz": 1.965111839713364e-07, + "phase_rad": -0.7853981677423116, + "timing_phase_symbols": 0.49987974121177103, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00036675604034606317 + }, + { + "sequence_number": 9, + "packet_sha256": "a11f702f42659771e907d969c53f6ae953ee8e7576ee0e5d29a1e9a95caeeccc", + "payload_sha256": "1dd003103ed9065ad42ad177c6cdcb6abe95e1b6d85703a4b82a550d9fe1a5f8", + "start_sample": 798856.9980753093, + "preamble_score": 0.9964055585207345, + "cfo_hz": -6.005718177430313e-08, + "phase_rad": 2.2580197191477485, + "timing_phase_symbols": 0.5623797068328713, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00047342052412802565 + }, + { + "sequence_number": 10, + "packet_sha256": "cebbd0b3f5ba39659a8f784542f97b68d47ce271a7349e616ee453933bdf5fd0", + "payload_sha256": "09106da81d922fef36a31ba4632ec1bce31061f3b6da3322e6be0e1c4806f0c4", + "start_sample": 887593.9980752576, + "preamble_score": 0.9853599281871901, + "cfo_hz": -3.9233030520806904e-08, + "phase_rad": -0.981747697780494, + "timing_phase_symbols": 0.6248797036023461, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0003277726354850433 + }, + { + "sequence_number": 11, + "packet_sha256": "93d38f1a1f5d1b103e970d606424847838b2e8c3bcedbf538e963e369f6d05f5", + "payload_sha256": "ff2de9370b5817127586092275a38c8878693962ba91b7ff5836334e835020b8", + "start_sample": 976330.9980753273, + "preamble_score": 0.9656523433938926, + "cfo_hz": -1.037850328763135e-06, + "phase_rad": 2.061670189078677, + "timing_phase_symbols": 0.6873797079533688, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00020303610676875714 + }, + { + "sequence_number": 12, + "packet_sha256": "c1043bf2927f0c94afe3e5166e264eb38660dea6f3e62f715bd9eee7dc0f716c", + "payload_sha256": "194d4eea8b148f8097e18882d97d1089b7e46896fe5bb98befa37ebb3c50aa29", + "start_sample": 1065067.9980752072, + "preamble_score": 0.9355140622221757, + "cfo_hz": 5.077251009308196e-08, + "phase_rad": -1.1780972431350132, + "timing_phase_symbols": 0.7498797004518565, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00033053738710719157 + }, + { + "sequence_number": 13, + "packet_sha256": "55feddabdea9a8e154e787b587a1f1ce09b0a85d4b0cddd79bb09e721b7d66e1", + "payload_sha256": "074c679bb903935c6bf5cc52c3ced97408376b1c7a0cc15eb72b514063bf4810", + "start_sample": 1153804.9980755537, + "preamble_score": 0.9656287022220583, + "cfo_hz": -1.5379604552529043e-07, + "phase_rad": 1.865320642215999, + "timing_phase_symbols": 0.8123797221051063, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0004156811646769086 + }, + { + "sequence_number": 14, + "packet_sha256": "6963cf0fa929c33bbf44fd44044d7cd70ca20ec3ceb5ac17f35b06baf6b290b3", + "payload_sha256": "257814be75560af99a9c4a8f63c01ab7c6ed12a13487cfd1d0f8c1c872ff9bb6", + "start_sample": 1242541.9980754908, + "preamble_score": 0.9853526297458299, + "cfo_hz": -5.020471719577815e-07, + "phase_rad": -1.3744467850755726, + "timing_phase_symbols": 0.8748797181760892, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00027510048410202867 + }, + { + "sequence_number": 15, + "packet_sha256": "84537c730059fb93ce34ded7ca5b71f74db0055931132568233325fa6710cce8", + "payload_sha256": "9d2c9736b139bea03b80a21f2b6d7c8055e167ecdbea0e69f812b6cb3dd37fe4", + "start_sample": 1331278.9980754221, + "preamble_score": 0.9964044412971949, + "cfo_hz": 5.524384303357385e-07, + "phase_rad": 1.6689710947730996, + "timing_phase_symbols": 0.9373797138832742, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0003284642968157846 + }, + { + "sequence_number": 16, + "packet_sha256": "2c6781c0fa023af965f994cf246d6138dd878235c96e0f1ed641bb9b631102bf", + "payload_sha256": "a6ee2a88458190abfbe8ebdfcc93d9346442ed8041c47d032d75eeae81d1a5e0", + "start_sample": 1420015.9980755865, + "preamble_score": 0.9999534700212876, + "cfo_hz": 7.887281367137925e-07, + "phase_rad": -1.5707963330984573, + "timing_phase_symbols": 0.9998797241569264, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0005716145506350678 + }, + { + "sequence_number": 17, + "packet_sha256": "fac6f86486df9d66931360844789e8d0f350b9ec0140e925b52eaec011252e27", + "payload_sha256": "421f076061c433b5515bc5eb42e06ed42355a46e5099c6caf9f364433f41b140", + "start_sample": 1508752.998075397, + "preamble_score": 0.9964055642449243, + "cfo_hz": -3.330197356625023e-07, + "phase_rad": 1.4726215613382831, + "timing_phase_symbols": 0.06237971231166739, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0004164848828314122 + }, + { + "sequence_number": 18, + "packet_sha256": "8c20ec5bd30a1a8091e6b0b173d01ee0018b52017d32c62742434fa032c5fb98", + "payload_sha256": "e93c614ff590771cba9d220c81f44c8ebdba3f7ed6406768648872b889050fb6", + "start_sample": 1597489.9980751746, + "preamble_score": 0.9853598814205502, + "cfo_hz": 2.7692680104419307e-07, + "phase_rad": -1.7671458698768352, + "timing_phase_symbols": 0.12487969841458835, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0003353980717476159 + }, + { + "sequence_number": 19, + "packet_sha256": "f34f48c6a7327425f7fc42d244e65b57fc13dd5caf0079b37a94e76719a5e16f", + "payload_sha256": "d14f3bfce872f3ae12d5b10c0861ff438c1dedefd652cb7762c94cdef48b052f", + "start_sample": 1686226.998075396, + "preamble_score": 0.9656523141120604, + "cfo_hz": -1.9211367685852336e-07, + "phase_rad": 1.2762720216379888, + "timing_phase_symbols": 0.18737971225345973, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00042975475140237534 + }, + { + "sequence_number": 20, + "packet_sha256": "4c44637f18540a4bc7eb765e0b331abcfd683ab21136d24522f91298e5329a38", + "payload_sha256": "54fa05ab31402bd66ccb9fd7bf130c167d2d11546bcb34530923fa14103f07f1", + "start_sample": 1774963.9980754138, + "preamble_score": 0.9355140631700823, + "cfo_hz": -9.388215259079348e-08, + "phase_rad": -1.963495410635133, + "timing_phase_symbols": 0.2498797133594053, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00046027663818240874 + }, + { + "sequence_number": 21, + "packet_sha256": "da583f81b26a0035fb829ec1707a33dddcca2662ddae9ca22d22bacaa4580cb6", + "payload_sha256": "dcf67daa5a735ca45f4609f9981a00bab143f2fd5f7d1d5ea56240e1650ffade", + "start_sample": 1863700.9980753055, + "preamble_score": 0.9656287228345416, + "cfo_hz": -3.567660595285917e-07, + "phase_rad": 1.0799224806096879, + "timing_phase_symbols": 0.3123797065927647, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0002837981996695765 + }, + { + "sequence_number": 22, + "packet_sha256": "677b77e784fa91edc4c2896f9ca574bd9f3e71fc18e355082674200a7166d9c5", + "payload_sha256": "f0578ad0a65f0f9df231fd508d9f6f27fe0043e2720d1ba20a9927c730f0601f", + "start_sample": 1952437.998075446, + "preamble_score": 0.9853526716662373, + "cfo_hz": -2.0352765912543873e-07, + "phase_rad": -2.1598449516427096, + "timing_phase_symbols": 0.3748797153821215, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00038612283625672487 + }, + { + "sequence_number": 23, + "packet_sha256": "c91339dfc311df65afbbb88b0f5f7ca06c1cbffb0e2b330bb3a4b4202586b308", + "payload_sha256": "12246760d0aa098ffe08470487f15d832ef6731dbce3ed9c4b1ba11db94f15c2", + "start_sample": 2041174.998075519, + "preamble_score": 0.9964044361136578, + "cfo_hz": -7.141369216809288e-08, + "phase_rad": 0.8835729323986149, + "timing_phase_symbols": 0.43737971993687097, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00020981215565658411 + }, + { + "sequence_number": 24, + "packet_sha256": "8bc0d37edd1b0e0fceefe84150fa240133221e75b4c7dd632a8211aaab27a2f2", + "payload_sha256": "2d7d070265ae85f0014490a9b53046c143df8947f99161b6770619c34f6a69dc", + "start_sample": 2129911.9980755234, + "preamble_score": 0.9999534831717128, + "cfo_hz": 1.0562308094243798e-06, + "phase_rad": -2.3561944993056345, + "timing_phase_symbols": 0.49987972021335736, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00038565438233869134 + }, + { + "sequence_number": 25, + "packet_sha256": "9d2b1fe0c9d1722093944105bfe64d99109a595a6e4ce5504030f23f10f10d09", + "payload_sha256": "74a152e7fa829dfc448669927bf714c5b610947f7225bbc8bf5d491ed2a300ad", + "start_sample": 2218648.9980751625, + "preamble_score": 0.9964055761894921, + "cfo_hz": 8.544645087201054e-07, + "phase_rad": 0.6872233889862716, + "timing_phase_symbols": 0.5623796976578888, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00032207797268828884 + }, + { + "sequence_number": 26, + "packet_sha256": "6461326859169e27329f26c064a87445d33ceb7846368ae3d7b683d9aaec912b", + "payload_sha256": "7fb9a08bbd3fb7c8004bcb54ac76e016c37f5686f23c4f14bd3b6f6db7bfaa85", + "start_sample": 2307385.998075126, + "preamble_score": 0.9853599413973353, + "cfo_hz": -6.471943997122194e-07, + "phase_rad": -2.5525440275587625, + "timing_phase_symbols": 0.62487969538779, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0002901496244269332 + }, + { + "sequence_number": 27, + "packet_sha256": "627b69cd08eb2c539cee8b470ba7c007d78cfa9eb5464b91274ef6749a3bf24e", + "payload_sha256": "18d0572fc2bb366b04198f0c0f451ea79c06f93e8b729a6194db3ce950e35d0f", + "start_sample": 2396122.998075539, + "preamble_score": 0.9656523718508626, + "cfo_hz": 3.33616615619946e-07, + "phase_rad": 0.4908738493023602, + "timing_phase_symbols": 0.6873797211737838, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00029161699344175716 + }, + { + "sequence_number": 28, + "packet_sha256": "64511c3982b1c77ec76d75e9036fbfb91fc378ea3275e420c66de5acba93a0c0", + "payload_sha256": "6ae0006e493efe63ba7f482832b32e59c1556eae2e845402e36685ea9d64f2eb", + "start_sample": 2484859.9980753153, + "preamble_score": 0.9355140574455318, + "cfo_hz": -1.2714627379235893e-07, + "phase_rad": -2.74889357187696, + "timing_phase_symbols": 0.7498797072039451, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0002667892233150363 + }, + { + "sequence_number": 29, + "packet_sha256": "5c363e0729272ef745715362825755ac4d5bf64e9ce6245a116226ab7a3860fb", + "payload_sha256": "c8810ded095de421272cb2ac06d03a4dae07603c92aa54ed6f95c9e92b94aa4d", + "start_sample": 2573596.998074954, + "preamble_score": 0.9656287076185661, + "cfo_hz": 2.2215639847282674e-07, + "phase_rad": 0.29452431023901604, + "timing_phase_symbols": 0.8123796846193727, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0002083990597485854 + }, + { + "sequence_number": 30, + "packet_sha256": "69f0ce4e421548ffe79b5262eec9cf8c1f0e01e338e4a5c47c344c095203cc7e", + "payload_sha256": "b738ff110f5bc52700094b698f2f814b59f62edd1553073634818cf41634a7b9", + "start_sample": 2662333.998075357, + "preamble_score": 0.9853526626506701, + "cfo_hz": 5.015587330599453e-07, + "phase_rad": -2.945243115847439, + "timing_phase_symbols": 0.8748797098232899, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00027846556248962154 + }, + { + "sequence_number": 31, + "packet_sha256": "826f2108ecd64fe1c4f7b750fcd6f0c0d586d9092e7a608b050ec9d298039291", + "payload_sha256": "f70565bb9395029c857ea85ebb9fbeca8c55f37cff2de52a43aa5dcaee49fce4", + "start_sample": 2751070.9980756734, + "preamble_score": 0.9964044082489474, + "cfo_hz": 1.0223193564683887e-07, + "phase_rad": 0.0981747701327405, + "timing_phase_symbols": 0.9373797295847908, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003588071867504616 + }, + { + "sequence_number": 32, + "packet_sha256": "35a4f770aeb60a0b269fd70b48c0856230a2661fc6f85ace9e3fe7ffeaaedd1b", + "payload_sha256": "a6110fb9975df074ec37b653a288fbcc3356eb60460e9b7c77fab99cf519d54e", + "start_sample": 2839807.998075264, + "preamble_score": 0.9999534764410928, + "cfo_hz": -1.093456359187774e-07, + "phase_rad": 3.1415926519539985, + "timing_phase_symbols": 0.9998797040025238, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00030354985143570353 + }, + { + "sequence_number": 33, + "packet_sha256": "7530f6f6480163162bb5c799f114b068ee3fa6e840d837b5d45f307dc78842b2", + "payload_sha256": "e6ee539b6824c69e0cfac0f166d8ca08bdfe1b72a4dc47bbdaf3c1bfdfd20af4", + "start_sample": 2928544.998075335, + "preamble_score": 0.9964055673374336, + "cfo_hz": -6.612594430846697e-07, + "phase_rad": -0.09817476351296023, + "timing_phase_symbols": 0.062379708426306024, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003886386003125331 + }, + { + "sequence_number": 34, + "packet_sha256": "6a990db6b516d4c264b965d54851dbde6782a56b5b810996ffd04a6f8b58a200", + "payload_sha256": "3320f4036487dba8c882d2b2a3b0fc03375f803e73ee8ca4a170699bb2b49c73", + "start_sample": 3017281.9980753814, + "preamble_score": 0.9853598855223934, + "cfo_hz": 6.670393621359362e-07, + "phase_rad": 2.945243103935153, + "timing_phase_symbols": 0.12487971133668907, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00027167735480996137 + }, + { + "sequence_number": 35, + "packet_sha256": "4f247fe687e7a91ca3a7362e47098ffee42e351e0e346bed46c9c684ceb8935d", + "payload_sha256": "049538d4d23c057af2aa77989ff2260b72fd67920e864f6e7c7761500a777ae5", + "start_sample": 3106018.99807532, + "preamble_score": 0.9656523265090453, + "cfo_hz": -6.393964822490675e-08, + "phase_rad": -0.2945243124950861, + "timing_phase_symbols": 0.18737970749498345, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0002983808089217487 + }, + { + "sequence_number": 36, + "packet_sha256": "60d3b3d234ac875609f83497e1137c05579663a2d9d1ea0911309f5cfdfca12a", + "payload_sha256": "72102a33749d838277ea1e8243ebdf4ae30b6dbda739c334fdf11cf20d408724", + "start_sample": 3194755.9980753153, + "preamble_score": 0.9355140520608137, + "cfo_hz": -6.4362168590155095e-09, + "phase_rad": 2.748893569979471, + "timing_phase_symbols": 0.24987970720394515, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0002330541156081467 + }, + { + "sequence_number": 37, + "packet_sha256": "2e5e79705c683ed6cec2b0705124a3a51d7c918cd5477334173378eed5ad72c9", + "payload_sha256": "fb8457ec7bcda3c70b61181c95c2d8a844e4b341f8d6d4f9533f594344952bd7", + "start_sample": 3283492.998075558, + "preamble_score": 0.9656287413508351, + "cfo_hz": 8.568902846927148e-09, + "phase_rad": -0.49087385291945923, + "timing_phase_symbols": 0.3123797223670408, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0002934883880340207 + }, + { + "sequence_number": 38, + "packet_sha256": "deb2bdd76ae1bdaab5cf45693cc913bf860720b8a6369ea6dab984398635c142", + "payload_sha256": "e21bd69eaa967ab61212dc57e211d76733099b2534d76591710eef5c4bbb68c0", + "start_sample": 3372229.998075396, + "preamble_score": 0.9853526782771873, + "cfo_hz": -7.217990130115105e-07, + "phase_rad": 2.5525440315813976, + "timing_phase_symbols": 0.3748797122389078, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00030427496303242604 + }, + { + "sequence_number": 39, + "packet_sha256": "9377bc52bb374a1f148e30b8a583b2504f5cd9754d79a488219ae6594add0d49", + "payload_sha256": "e440830eb1dcb5e952ffd38a6c4a8aa7a455c73f483fd96487ca3b1b5814f74a", + "start_sample": 3460966.998075504, + "preamble_score": 0.9964044374018721, + "cfo_hz": -8.970438748580408e-08, + "phase_rad": -0.6872233868053313, + "timing_phase_symbols": 0.4373797189909965, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00044961974108064044 + }, + { + "sequence_number": 40, + "packet_sha256": "50a5d4f1af820acc6611ad05cca7e2a58743ffb75e2e21c5e537bcb4f01d1ca1", + "payload_sha256": "ab22ef291546afad3c096faf4c11a917ba530b71063a74899e748e291ddc1888", + "start_sample": 3549703.9980759122, + "preamble_score": 0.999953498632545, + "cfo_hz": -4.257684546872286e-07, + "phase_rad": 2.356194493811307, + "timing_phase_symbols": 0.4998797445150558, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00039666310486674653 + }, + { + "sequence_number": 41, + "packet_sha256": "21192e0633a549a6863c1412050379a37f784c2850fe3b0fd9a6189e6fa04716", + "payload_sha256": "3a77bddd7867d943b8c70673f63e0b7dc45af645d82f27779ca3b716d4688772", + "start_sample": 3638440.9980755183, + "preamble_score": 0.9964055683942243, + "cfo_hz": -8.907102003590029e-07, + "phase_rad": -0.8835729243270245, + "timing_phase_symbols": 0.5623797198932152, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0003999188744313706 + }, + { + "sequence_number": 42, + "packet_sha256": "799d9e8af80186dd693bea85869ae8e05bbdbacb188fdbdcd355ee568563152e", + "payload_sha256": "349255af17303f3ad7de96079f17001db1120bdc1d015414ab1189450ca23867", + "start_sample": 3727177.9980752, + "preamble_score": 0.9853599468059603, + "cfo_hz": 1.9927335384063975e-07, + "phase_rad": 2.159844951052697, + "timing_phase_symbols": 0.6248796999861952, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.000311625496190698 + }, + { + "sequence_number": 43, + "packet_sha256": "ece74d595de7a24e04d7c17afbdeef079337f316d6da6f9f0a7936a3a0ed2a51", + "payload_sha256": "70eb1217481f5a17dad4ba973f99ae149bf7d591b2739a2fe7a44b33946fc2de", + "start_sample": 3815914.9980752654, + "preamble_score": 0.9656523297273055, + "cfo_hz": 4.6578442277073977e-07, + "phase_rad": -1.0799224730906165, + "timing_phase_symbols": 0.6873797040898353, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00025043146470674754 + }, + { + "sequence_number": 44, + "packet_sha256": "8ffcae35363d9f1ca2f6bfc02c0833cff9fad1e75312d587db44db6d290a316e", + "payload_sha256": "64127d2496375c8c508b39c0a80bff78eb26ed00c57d462de27b75dfb852f23f", + "start_sample": 3904651.9980753027, + "preamble_score": 0.9355140410645076, + "cfo_hz": 1.3007052129854324e-06, + "phase_rad": 1.9634953980058794, + "timing_phase_symbols": 0.7498797064181417, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0004302428809562517 + }, + { + "sequence_number": 45, + "packet_sha256": "9a893918e5441504c77c49107975c34671e6ab572dedfa64b52d554b3b220057", + "payload_sha256": "94e697b1bb63efb563fe8f23a7ac717bcbe55ec12b612b5946fa95a34804d2ad", + "start_sample": 3993388.9980754717, + "preamble_score": 0.9656287101294347, + "cfo_hz": -1.7246821153840324e-07, + "phase_rad": -1.2762720121623623, + "timing_phase_symbols": 0.8123797169828322, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0003179850929762594 + }, + { + "sequence_number": 46, + "packet_sha256": "ba04e7b50f0a2c3dce78694aa977d6e4f6a17842761c94b90395aa8dc136b57b", + "payload_sha256": "f0b9ffe40da709a93f71477c7e7ef7e5c0c7960a81249e73032e45b389b23a37", + "start_sample": 4082125.9980753516, + "preamble_score": 0.9853526289294899, + "cfo_hz": -4.505450660685816e-07, + "phase_rad": 1.7671458693221933, + "timing_phase_symbols": 0.8748797094740439, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0003309185445489074 + }, + { + "sequence_number": 47, + "packet_sha256": "3da0bafcd46fc6a917505c5615c830dd27e4d3c58e97603d089c79282fc9f9c0", + "payload_sha256": "2177b36f9aac5bfbef359e662f9c947c98f800526ffe9f99665912c303f22abb", + "start_sample": 4170862.998075893, + "preamble_score": 0.9964044258946141, + "cfo_hz": -3.617626742396583e-07, + "phase_rad": -1.47262154953605, + "timing_phase_symbols": 0.9373797433217987, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0003658131783001924 + }, + { + "sequence_number": 48, + "packet_sha256": "2d2424d3024c65353da8065ba8a32e8f8a6a18633267a8b01e32cbe734e2d9dc", + "payload_sha256": "3aa5a3818488ea9cbf9fdcf655aaaa22dcf01d7b00ffcc5e127a9a2fd9332cd8", + "start_sample": 4259599.99807536, + "preamble_score": 0.9999534717092378, + "cfo_hz": 4.4059872956289485e-07, + "phase_rad": 1.570796322419641, + "timing_phase_symbols": 0.9998797100270167, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.000400677593435847 + }, + { + "sequence_number": 49, + "packet_sha256": "471f68e3c998b8a65917b5e5ea978aa9e1fb1a8ba9586c3e10ba69ef67f5f373", + "payload_sha256": "365bef881aa8d57ddce1849485d33938f6ea85b9f75383c99eaf4fd95b4233b9", + "start_sample": 4348336.998075546, + "preamble_score": 0.9964055628024971, + "cfo_hz": 7.065937226980043e-09, + "phase_rad": -1.668971093896282, + "timing_phase_symbols": 0.06237972161034122, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0004195598749151084 + }, + { + "sequence_number": 50, + "packet_sha256": "58610a2248c28fb815df0acba42d2de1d474f5bba54fb634572771e22bb841a5", + "payload_sha256": "47043687b94a7c4abbe6fd405bcf92cc843cd0185bd1246b50e3678ded1331c3", + "start_sample": 4437073.99807565, + "preamble_score": 0.9853599007685145, + "cfo_hz": 2.7564427015354693e-07, + "phase_rad": 1.3744467829732518, + "timing_phase_symbols": 0.12487972812959924, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0002741455713023924 + }, + { + "sequence_number": 51, + "packet_sha256": "e91154fb19ca789296e09a1884243dda26a20ff6e964a99350b63a56f9900d6d", + "payload_sha256": "1cc173ed5a928de1a49017d88f233ed64c4212edeffb618dd99b03c7a06f9e69", + "start_sample": 4525810.998075301, + "preamble_score": 0.9656523333861875, + "cfo_hz": -4.612775725704621e-07, + "phase_rad": -1.8653206370514048, + "timing_phase_symbols": 0.1873797063017264, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00039751168205853223 + }, + { + "sequence_number": 52, + "packet_sha256": "4fabd2ece3ba7b35ce77ab185e5689d9a69ec25e97d25431ba06c4fe41ce3ca1", + "payload_sha256": "d073a047015bc48b7249996ac967cbe573e70d0a383685ecb5fd65ae0fa7f5d0", + "start_sample": 4614547.998075285, + "preamble_score": 0.9355140797571867, + "cfo_hz": -9.682789903316685e-08, + "phase_rad": 1.1780972492875688, + "timing_phase_symbols": 0.24987970531219617, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00021161457991862985 + }, + { + "sequence_number": 53, + "packet_sha256": "5a9fff30607546c10719fc8b2373751936d85c9cbb825351d40ff192c35e67c3", + "payload_sha256": "9bd5a56c09781c5358e2e0aad364a470667f519550f18eb38c7de982c1080280", + "start_sample": 4703284.998075266, + "preamble_score": 0.9656287320396739, + "cfo_hz": 7.10571699177797e-07, + "phase_rad": -2.0616701851042745, + "timing_phase_symbols": 0.31237970414804295, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0003095879401767602 + }, + { + "sequence_number": 54, + "packet_sha256": "291d6753b048005cc93ff7916df1c702ceffe6ad34dd20824f9fc93c802839cc", + "payload_sha256": "80673128b291980917d288d4909f2024f331bada7884dedcb38a3b25e115a4e2", + "start_sample": 4792021.998075615, + "preamble_score": 0.985352683036673, + "cfo_hz": -2.111718336242389e-07, + "phase_rad": 0.9817477031937565, + "timing_phase_symbols": 0.37487972591770813, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0003043728777321544 + }, + { + "sequence_number": 55, + "packet_sha256": "038b17c9da0844d003d4780cd61217e904a51e113bf0705862c30285bf4c216d", + "payload_sha256": "86a03ba0f472f0627123e22e11014f42b2f318cee7055b439e5ebca516061370", + "start_sample": 4880758.998075494, + "preamble_score": 0.9964044638503415, + "cfo_hz": 4.6674938220602107e-07, + "phase_rad": -2.258019717954112, + "timing_phase_symbols": 0.4373797183507122, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0002287220301861987 + }, + { + "sequence_number": 56, + "packet_sha256": "203e9373cc6fa3d52ffcc4a9e3ce9675bf86f2e154bcba9217a302355819c6b1", + "payload_sha256": "1497fa9c6aae68d2a41256f9257c13264d50bd32dcc79b76c3be0fbff53d14f8", + "start_sample": 4969495.998075847, + "preamble_score": 0.9999534668158402, + "cfo_hz": 7.123440920807353e-07, + "phase_rad": 0.7853981563232216, + "timing_phase_symbols": 0.4998797404114157, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00026256773215521336 + }, + { + "sequence_number": 57, + "packet_sha256": "1af6cc447cc333df9af40721d7c126efb1fcb065bef62ba42f00ba2e812b320d", + "payload_sha256": "acd390d05a2a51bdb14c74c3a26588e544af4167d3907b49a86897e8c5d82f22", + "start_sample": 5058232.998075466, + "preamble_score": 0.9964055742839393, + "cfo_hz": 5.978664273729809e-07, + "phase_rad": -2.4543692668446617, + "timing_phase_symbols": 0.5623797166044824, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00030062995064175657 + }, + { + "sequence_number": 58, + "packet_sha256": "8c500c11f4f412bd2eaa86862398c4ccf9fc499c32a8717e5931df5b8c9e7836", + "payload_sha256": "5366d7d4670582403cbd0e2f6fa12974384278b03ad2899b2c5e4b92ab32a564", + "start_sample": 5146969.998075468, + "preamble_score": 0.9853599390138585, + "cfo_hz": -1.2365573617871509e-07, + "phase_rad": 0.5890486240341104, + "timing_phase_symbols": 0.6248797167208977, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00021857587284986548 + }, + { + "sequence_number": 59, + "packet_sha256": "1ae3de9c1f2a4ea0a97dce9df11929f82caf63e17e404498cb08b678acfaafaa", + "payload_sha256": "99a4565ae1db6cb094cbddc0adaa442bd9b0fa04e02115ac5f82b1ba0cb021f3", + "start_sample": 5235706.998075391, + "preamble_score": 0.9656523353242816, + "cfo_hz": 2.397123449711764e-07, + "phase_rad": -2.650718803584578, + "timing_phase_symbols": 0.6873797119478695, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003192981555218041 + }, + { + "sequence_number": 60, + "packet_sha256": "662b48a8e61ada3671dfff3af459c5ee979849856be2602b4b5f85828b87e3f8", + "payload_sha256": "1785424f2d9427a9c13bbdcaca2975f4ea2893daf6f3642deac0b7929acd7d4d", + "start_sample": 5324443.998075463, + "preamble_score": 0.9355140517540891, + "cfo_hz": -3.177953556597676e-07, + "phase_rad": 0.3926990826964663, + "timing_phase_symbols": 0.7498797164298594, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00047147510814509456 + }, + { + "sequence_number": 61, + "packet_sha256": "73e2f50f4cf77ef91e0b374b68aa07bb7a7cc9601cfa5d0e19475bedda4a9847", + "payload_sha256": "1fb826298448fd4183522f7ccd3f45e9889ab78314ef78749cf9d679501d2efb", + "start_sample": 5413180.998075056, + "preamble_score": 0.9656286931012454, + "cfo_hz": 2.1311350454063803e-07, + "phase_rad": -2.8470683431004367, + "timing_phase_symbols": 0.8123796909931116, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0002568562723818086 + }, + { + "sequence_number": 62, + "packet_sha256": "32de517b56537ead8cb46eabdfcfa41bb3924528c5bc31dd1f04b9c8865246ee", + "payload_sha256": "86b46d9e32a85309e34183b5fadb8e64030dc804c6ba54d685e32b2e367ec362", + "start_sample": 5501917.998075499, + "preamble_score": 0.985352648983978, + "cfo_hz": 3.059341511162315e-08, + "phase_rad": 0.19634953686685455, + "timing_phase_symbols": 0.8748797186999582, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003835502590467282 + }, + { + "sequence_number": 63, + "packet_sha256": "964a64272e72c8377f10baf43845d1023e2f66363d10ea14861005e3496201d0", + "payload_sha256": "19cb46c3f3fbf8d804aa64f66031f4cb9c551dd198083394568ba12e7f4140e1", + "start_sample": 5590654.998075541, + "preamble_score": 0.9964044292964961, + "cfo_hz": -3.3470802889769553e-07, + "phase_rad": -3.043417879907161, + "timing_phase_symbols": 0.9373797213193029, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00029255838827481204 + }, + { + "sequence_number": 64, + "packet_sha256": "2a9f1cd3eb0a6fd78a79ea5b7ee20eb90266967ad60519ffd52d379f27968cc5", + "payload_sha256": "cce7d5957bf9573b07cd537ad1882e4f9ca8e40a06e7ee651b45eb1083ac062e", + "start_sample": 5679391.998075336, + "preamble_score": 0.9999534777639434, + "cfo_hz": -5.553551870213095e-08, + "phase_rad": -1.9693706767258801e-10, + "timing_phase_symbols": 0.9998797085136175, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00026521068440448316 + }, + { + "sequence_number": 65, + "packet_sha256": "9fb0676a2a05c48791b14dcac0fe694462123d07aa1f0539a25ae2c85edcb467", + "payload_sha256": "a3022b2ccdddfb3ae31666802fc50862d328b90fa3f1fd919a7ec282a803d01b", + "start_sample": 5768128.998075686, + "preamble_score": 0.9964055797030827, + "cfo_hz": -1.9093555072245578e-07, + "phase_rad": 3.043417885331109, + "timing_phase_symbols": 0.06237973039969802, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003848342750761074 + }, + { + "sequence_number": 66, + "packet_sha256": "4db24f6fcccde301f1a7f3c7f93a22f809b6304ecada8ff08964bf47f97c7d44", + "payload_sha256": "d700f2d05d13f0397b8314a413b3bdeefdb29b19fe266ed6fadd7847c8d370a8", + "start_sample": 5856865.998075404, + "preamble_score": 0.9853598914916654, + "cfo_hz": -4.3513487014848015e-07, + "phase_rad": -0.19634953924345702, + "timing_phase_symbols": 0.12487971276277676, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003300237861217708 + }, + { + "sequence_number": 67, + "packet_sha256": "47eb548881c5c2bb7f4568ec803d62744041363a8df836ac24f212dd043b194d", + "payload_sha256": "a7c6780dfa088e82142e99839a3794d068e7dbed9c1c984ab89e1c18341aa165", + "start_sample": 5945602.9980755085, + "preamble_score": 0.9656523331063722, + "cfo_hz": 3.4633470810164743e-07, + "phase_rad": 2.8470683406296904, + "timing_phase_symbols": 0.18737971928203478, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0004192777948953412 + }, + { + "sequence_number": 68, + "packet_sha256": "16d123eac8487f32dfbef2836b9ca28bf0bbbc9ec9cbde9473be3ad49dad8561", + "payload_sha256": "4e94ad2174f8379eb311155969ef7619b862d6db93c0ce2b6b0b8222641869e0", + "start_sample": 6034339.998075469, + "preamble_score": 0.9355140417506126, + "cfo_hz": 8.510877730458883e-07, + "phase_rad": -0.3926990915599774, + "timing_phase_symbols": 0.24987971683731303, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003096002342060578 + }, + { + "sequence_number": 69, + "packet_sha256": "e806ca6e9be15f5e5d3e9ab21ddfe5a81cba66c5dc40283e3543bbbb1de8ad23", + "payload_sha256": "19375b8ab0a37f5eff619977273c547c0698ed9264d91b725568bb4dd7ccb6c4", + "start_sample": 6123076.998075198, + "preamble_score": 0.9656287183113985, + "cfo_hz": 4.925691400723952e-07, + "phase_rad": 2.650718801762018, + "timing_phase_symbols": 0.3123796998988837, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0002043448401128733 + }, + { + "sequence_number": 70, + "packet_sha256": "c542d6426d41d9245715b262c3353c05c3e2459f6e57b92b197d0e325b6f1a39", + "payload_sha256": "d60d45967a1dcf5188222ed928adb9afa9599b1c036db00c3b7c1ff2290edf30", + "start_sample": 6211813.998075597, + "preamble_score": 0.9853526760899664, + "cfo_hz": 7.033808510673781e-07, + "phase_rad": -0.5890486287646531, + "timing_phase_symbols": 0.37487972481176257, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00028920693446085393 + }, + { + "sequence_number": 71, + "packet_sha256": "1a40abde7d1ca18c86ccb8cad98005adb259a7262d04f655fb6b59f713339a90", + "payload_sha256": "408ab0b95a563333d7705a01201fba72079d78a840ac2c0aebc85a807fafc760", + "start_sample": 6300550.998075573, + "preamble_score": 0.9964044275173102, + "cfo_hz": 9.07673262266893e-07, + "phase_rad": 2.4543692498683383, + "timing_phase_symbols": 0.4373797232983634, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003154141744544809 + }, + { + "sequence_number": 72, + "packet_sha256": "b96ec2a35c3f04a1eb1d11416b6bba6c464eb9223823bc90286e93496995aef2", + "payload_sha256": "9960aee518000cf96d71300a043bb7826e611314dbfb41b544c64aa62eae8de1", + "start_sample": 6389287.998075888, + "preamble_score": 0.9999534801942348, + "cfo_hz": -1.4164899593847618e-06, + "phase_rad": -0.7853981568547805, + "timing_phase_symbols": 0.4998797429725528, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003393068630733403 + }, + { + "sequence_number": 73, + "packet_sha256": "4cd1b3313cd7f2270e083c16fe77f4ce5b843cc57834382e30d8fb3350eb6ac8", + "payload_sha256": "a0b867b3f9d3e09113a663acc1310c9245d49c985d624d6407f8553cc0f0a8eb", + "start_sample": 6478024.998075609, + "preamble_score": 0.9964055699523506, + "cfo_hz": -3.099470220729283e-07, + "phase_rad": 2.258019724350109, + "timing_phase_symbols": 0.5623797255684622, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00035729082046438904 + }, + { + "sequence_number": 74, + "packet_sha256": "1d3512901cb78072a773198320330ac528009c6aff7fb4f42fe10eeb2134a498", + "payload_sha256": "6d8f21d468de22c1939ae0f72374cee7e505528e49c052fb5c2295574195c595", + "start_sample": 6566761.998075387, + "preamble_score": 0.9853599365152191, + "cfo_hz": -6.106484597610234e-07, + "phase_rad": -0.9817476995741863, + "timing_phase_symbols": 0.6248797117150389, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00032627314848225885 + }, + { + "sequence_number": 75, + "packet_sha256": "d423db049f9e9ef07dd9c358c3315239e745ab0f8bf6dee1c93ee027b63550f4", + "payload_sha256": "918f46919ed53f70bd031a739886dc8b93b212e054388076076bb81624d9a4cb", + "start_sample": 6655498.998075513, + "preamble_score": 0.9656523434029951, + "cfo_hz": 2.4732434452311823e-07, + "phase_rad": 2.0616701816279948, + "timing_phase_symbols": 0.6873797195730731, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.000287909302056616 + }, + { + "sequence_number": 76, + "packet_sha256": "2ed7dbda8de3ad2b55fb5631aa0d6adb52e0e7a2e87b3ccbf4c4d14b93737224", + "payload_sha256": "c898050384f25ea8e8a06121ca9c870b21ffdf5d7d66941ea80f1f43cc7d6c48", + "start_sample": 6744235.998075347, + "preamble_score": 0.9355140563209334, + "cfo_hz": -1.4862781697837864e-06, + "phase_rad": -1.1780972328938686, + "timing_phase_symbols": 0.7498797092121094, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0002163372363636784 + }, + { + "sequence_number": 77, + "packet_sha256": "ea584a2d123b4969df4bc52dd721f9983fa5a34c9e2a98a0b857a99ee7e7bae3", + "payload_sha256": "420551db636aa35fa85dae39b8166056a028338da82fdaeab3d2e2f90fb692f9", + "start_sample": 6832972.998074692, + "preamble_score": 0.9656287247218528, + "cfo_hz": -7.000780785897525e-08, + "phase_rad": 1.8653206483028006, + "timing_phase_symbols": 0.8123796682339162, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0004764548771722145 + }, + { + "sequence_number": 78, + "packet_sha256": "44f213f517d5ecf2b419426c14eaae5c03176485db73b78b29a145ad5c952a66", + "payload_sha256": "ff37d42b153f57cd46808c831894c180ec8710827cbe5ddd84891a349e78584e", + "start_sample": 6921709.998075333, + "preamble_score": 0.9853526383878213, + "cfo_hz": 2.9443635219860285e-07, + "phase_rad": -1.3744467905176394, + "timing_phase_symbols": 0.8748797083389945, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0003319844271457144 + }, + { + "sequence_number": 79, + "packet_sha256": "a7a4fff4bf520ebe57db314995036ce76eeb09a778ccedd5eec4fa4179782fc0", + "payload_sha256": "795aa97056eb0c8b5e4134ba9204581c06a3f73eb72a26cfd52cdfba5c2d3ebd", + "start_sample": 7010446.998075243, + "preamble_score": 0.996404432139867, + "cfo_hz": -1.0659386081669425e-06, + "phase_rad": 1.6689711063882693, + "timing_phase_symbols": 0.9373797026928514, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00033909501247044463 + }, + { + "sequence_number": 80, + "packet_sha256": "c04ae816948e9f645dde14c688bf7a881fd761c12666169bf3cf944397d2ae1f", + "payload_sha256": "772225bc6d1c9d7da39b90cf545409d21b8a267ec3881abb8b4cf737dc8b5586", + "start_sample": 7099183.998075609, + "preamble_score": 0.9999535014965605, + "cfo_hz": 4.0158602400985114e-08, + "phase_rad": -1.5707963304335086, + "timing_phase_symbols": 0.9998797255684622, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00029992508414527184 + }, + { + "sequence_number": 81, + "packet_sha256": "19d35293afa0ff3705dd9fe7dff134aec57655a2cfacfc10b3b56792488cc379", + "payload_sha256": "5d7b128857e4eea0290eb5748b26da0ff30136e2d2b0cbf6527b08cb84b576ee", + "start_sample": 7187920.998075315, + "preamble_score": 0.9964055476486727, + "cfo_hz": -1.1599135813156762e-06, + "phase_rad": 1.4726215717134155, + "timing_phase_symbols": 0.062379707174841315, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0002841315122503849 + }, + { + "sequence_number": 82, + "packet_sha256": "7f5da368cdb8d93f01a743046842a94b779408bf03be46ea749b7d3d875efb68", + "payload_sha256": "6f13fedc3925d328f7aa448715f4f88bf7f8397430f015474d1ec0b8c2524f19", + "start_sample": 7276657.998075226, + "preamble_score": 0.9853599016507467, + "cfo_hz": 9.215809446692395e-07, + "phase_rad": -1.767145875807084, + "timing_phase_symbols": 0.12487970164511353, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00025842721776741904 + }, + { + "sequence_number": 83, + "packet_sha256": "bc5c2b1a99bb0f03281a6a8977fb91497c1fcb08f824fe37f3a5dc58272e135f", + "payload_sha256": "db4fae9f025383ee9fcb272bf591391be9a7511480a6155bd16368b2a7526da8", + "start_sample": 7365394.998075327, + "preamble_score": 0.9656523369229836, + "cfo_hz": -8.481806292570243e-07, + "phase_rad": 1.2762720235427094, + "timing_phase_symbols": 0.1873797079315409, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00039192620729342815 + }, + { + "sequence_number": 84, + "packet_sha256": "19e7ac7b9331adb5b9e58a91612203767498150463300b360e0ba603b20ca104", + "payload_sha256": "c7263f589c0b9a8bfed09664ba80c2c98bef86e4e190ecadf5a4764be50af600", + "start_sample": 7454131.998075553, + "preamble_score": 0.9355140409713402, + "cfo_hz": -5.793515567365643e-07, + "phase_rad": -1.9634954048636062, + "timing_phase_symbols": 0.2498797220760025, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.0002594919076774104 + }, + { + "sequence_number": 85, + "packet_sha256": "9714f48963007f0802169f73e765a3d19d958d130ce82f022919aa1f29cf97e3", + "payload_sha256": "8813e4ed36a33b415487386e94ee7e07bc33825b7516e662cf51f5b43f2f25a6", + "start_sample": 7542868.998075086, + "preamble_score": 0.9656287178040833, + "cfo_hz": -5.078731171027991e-07, + "phase_rad": 1.079922483722954, + "timing_phase_symbols": 0.31237969285575673, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.0004262762272171403 + }, + { + "sequence_number": 86, + "packet_sha256": "a76f078847dc4afb6bb6f7c55ae2c7d04770e2ea8d5ecf11cb9a1a72e2374efc", + "payload_sha256": "ae35ed2b855402a457dc0ebf13e9c6915a1964a87a9a6f58b678ada2549e36a0", + "start_sample": 7631605.998075369, + "preamble_score": 0.9853526661755722, + "cfo_hz": 6.607061539589068e-07, + "phase_rad": -2.159844960121425, + "timing_phase_symbols": 0.37487971055088565, + "qpsk_quadrant": 3, + "tracking_error_rms_rad": 0.00031277310962819944 + }, + { + "sequence_number": 87, + "packet_sha256": "5531ebb6a8138ee6b9f549be25ee2cf47c0c321bfe05eebb7232e611b6124fff", + "payload_sha256": "1e508aacf860bfa4fa124c366386be546cebd62a375f14e771be08999506fe85", + "start_sample": 7720342.998075589, + "preamble_score": 0.9964044520226404, + "cfo_hz": -3.898249997122725e-07, + "phase_rad": 0.8835729340194961, + "timing_phase_symbols": 0.4373797242878936, + "qpsk_quadrant": 1, + "tracking_error_rms_rad": 0.00038195461006633387 + }, + { + "sequence_number": 88, + "packet_sha256": "a23c2b138284b533fbbc7ff2350056c82878d92572e7b96ccdc60e28977858de", + "payload_sha256": "96942711e476e7bdb2aa57c3845c4594825f4dd8b8fd8f3203c87acccdc6d527", + "start_sample": 7809079.998075797, + "preamble_score": 0.9999534795665775, + "cfo_hz": 5.076478042947447e-07, + "phase_rad": -2.356194495983843, + "timing_phase_symbols": 0.49987973732640967, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003667601279676407 + }, + { + "sequence_number": 89, + "packet_sha256": "6f003c9766914ee11f6d4bd5cb8f80f05451e06f4ba21e589e7ee288ee0ec87f", + "payload_sha256": "e356c8ef03d32d28983c088d6c0bad625ca577351dd6e067763b212e817d75c4", + "start_sample": 7897816.99807522, + "preamble_score": 0.9964055733646907, + "cfo_hz": -3.8520359480764025e-07, + "phase_rad": 0.6872233996163549, + "timing_phase_symbols": 0.5623797012376599, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00031393325171452646 + }, + { + "sequence_number": 90, + "packet_sha256": "5b2a1a43ebf039b998af09037edf1054d913dfa4d364fa502ac5e89279055fa7", + "payload_sha256": "bd6da495c09b3b533bd4e8c6f0ac5751c0b26174172fc5085e22b48f3b53a577", + "start_sample": 7986553.998075364, + "preamble_score": 0.9853599559684919, + "cfo_hz": 1.1685401283317561e-06, + "phase_rad": -2.5525440374612107, + "timing_phase_symbols": 0.6248797102598473, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003502185674387156 + }, + { + "sequence_number": 91, + "packet_sha256": "90a075a8c1804a32a42c5d0ebce27bc7dc3e23feb7e9d8cdffe3f3486ec5cf0f", + "payload_sha256": "648b2da1112dcfeb229519fde8ef8034449937e7b42602588c223855382925be", + "start_sample": 8075290.998075713, + "preamble_score": 0.9656523433224695, + "cfo_hz": -2.6474482634808936e-07, + "phase_rad": 0.4908738581407373, + "timing_phase_symbols": 0.6873797320877202, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00026280632238819245 + }, + { + "sequence_number": 92, + "packet_sha256": "2567a20ec8033be6286594ce3a542566866544bc083ad278f9340ca2b48aae24", + "payload_sha256": "669afb8bb74b9dbdf96708d2a48ee1fce635ae22294e06ed4500586e57213762", + "start_sample": 8164027.998075497, + "preamble_score": 0.9355140624718461, + "cfo_hz": 2.2223964512095423e-07, + "phase_rad": -2.748893569511431, + "timing_phase_symbols": 0.7498797185835429, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00046899552253353764 + }, + { + "sequence_number": 93, + "packet_sha256": "9192777c9fecb9f5ed4f065b7d5bfafcc41f6f76ac5ce742ccedc5b2a6f61de3", + "payload_sha256": "a2f6f9d77355b3a04c32d069af37031a06f2c484d58b51849bb55b3cdec572e5", + "start_sample": 8252764.9980751155, + "preamble_score": 0.9656286849311061, + "cfo_hz": 7.133652886149805e-07, + "phase_rad": 0.29452430940598084, + "timing_phase_symbols": 0.8123796947184019, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0002541655662089155 + }, + { + "sequence_number": 94, + "packet_sha256": "b54786e36fd680a81066bddf3289f17aa95b57d46bf006cf08c1e07a7aaa5a27", + "payload_sha256": "055d60647878f18901ec2a1e009363e3d5517fccbbe92fa2cd6b6feeadf58391", + "start_sample": 8341501.998075377, + "preamble_score": 0.98535262462279, + "cfo_hz": -6.822171125501059e-07, + "phase_rad": -2.945243109407717, + "timing_phase_symbols": 0.8748797110747546, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003323667522591207 + }, + { + "sequence_number": 95, + "packet_sha256": "f37eb95b8e0344bedc4ad85436cbe4db62593dde31fdb3daf0284e49a2ac32c1", + "payload_sha256": "d10f469fbad708685b16c1ddcceca68a84f77a802b3ad04b0d394e45f754d0a2", + "start_sample": 8430238.998075563, + "preamble_score": 0.9964044210994923, + "cfo_hz": 1.9551369920095391e-07, + "phase_rad": 0.09817477210653362, + "timing_phase_symbols": 0.9373797227162868, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.00023864111076948155 + }, + { + "sequence_number": 96, + "packet_sha256": "fe3ce30a3d7649061f208a86c8ede5aac568803d079d8da1a0a5a11a962dd358", + "payload_sha256": "12a224b34c176ef7fed560197a39ad3bc448eee1467733f84250b29f3a06ce03", + "start_sample": 8518975.998075683, + "preamble_score": 0.999953469241609, + "cfo_hz": -7.280895289819939e-07, + "phase_rad": -3.1415926491855304, + "timing_phase_symbols": 0.9998797301668674, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.0003872435584524988 + }, + { + "sequence_number": 97, + "packet_sha256": "a60f4e0dfe9b40ae18e03deb331022e53292368803bed492bc3069dbc564bb38", + "payload_sha256": "db488bd0b7c4c38e5fce4a309d323955bd870a5b9947c753fd9065a0ce42b210", + "start_sample": 8607712.998075392, + "preamble_score": 0.9964055636820677, + "cfo_hz": 5.59538310705268e-08, + "phase_rad": -0.09817476848735947, + "timing_phase_symbols": 0.06237971200607717, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0005004537885106286 + }, + { + "sequence_number": 98, + "packet_sha256": "88e79568d2845aa646c91d1e211ee97e10c3a7cb43696607c6d56c7da0c5bb8b", + "payload_sha256": "3f12c8bc119739279484078bc5e17c39a7c36e4078ae55fc6a25aba0b7457348", + "start_sample": 8696449.998075671, + "preamble_score": 0.9853599004353394, + "cfo_hz": -5.006699283071385e-08, + "phase_rad": 2.9452431081365336, + "timing_phase_symbols": 0.12487972946837544, + "qpsk_quadrant": 2, + "tracking_error_rms_rad": 0.00028318503474169594 + }, + { + "sequence_number": 99, + "packet_sha256": "ea7ce09526a21d432e7ae7a9007a9e69d69197f1a6b2c2668cd66c95c1e42688", + "payload_sha256": "c91c2b855495e75e3751b1e4c3abc5525db4d7500b1845d0e9907d8f60ccb2dc", + "start_sample": 8785186.9980751, + "preamble_score": 0.965652332316708, + "cfo_hz": -8.933741070095995e-07, + "phase_rad": -0.2945243031733345, + "timing_phase_symbols": 0.18737969372887164, + "qpsk_quadrant": 0, + "tracking_error_rms_rad": 0.0003428261520286745 + } + ], + "tx_underruns": "NOT_RUN", + "rx_overruns": "NOT_RUN", + "rx_clipping_rail_codes": "NOT_APPLICABLE", + "common_10mhz_clock": "NOT_RUN", + "sdr_hardware": "NOT_ACCESSED", + "rf_tx": 0, + "unexpected_sequences": [], + "same_bytes_as_tx_waveform": true +} diff --git a/docs/rf/CLOCK_001.md b/docs/rf/CLOCK_001.md new file mode 100644 index 0000000..88e6645 --- /dev/null +++ b/docs/rf/CLOCK_001.md @@ -0,0 +1,166 @@ +# CLOCK-001: независимые такты перед RF-001 + +17 сентября 2026 года. Статус **PARTIAL**: программный приёмник прошёл основную +сетку до ±50 ppm, но потерял длинные пакеты при ±100 ppm. Последняя точка +служит только стресс-тестом. Частоты реальных генераторов Auron HackRF Plus +не измерялись. SDR не открывался, `RF TX = 0`. Данные каждого запуска +сохранены в `data/processed/clock001/results.json`. + +## Модель и вход приёмника + +TX строит QPSK/FEC 2/3/RRC при номинальных 8 Мвыб/с и 500 ксимв/с. Для +приёмника с независимой частотой `Fs_RX = Fs_TX · (1 + ε)`, где +`ε = ppm · 10⁻⁶`, программа берёт комплексную TX-волну в моменты +`n / (1 + ε)` по её исходной сетке. Дробные отсчёты получаются линейной +интерполяцией. Поэтому ошибка момента **накапливается по времени**, а не +подменяется одним постоянным сдвигом. Интерполяция не описывает аналоговый +тракт АЦП, шум генераторов или аппаратное квантование. + +CFO добавляется отдельно как поворот с частотой в герцах физического времени +RX; начальная фаза и AWGN задаются отдельными параметрами. Даже при нулевом +внешнем CFO цифровая несущая 125 кГц из-за изменения частоты выборки даёт +остаток `(125000 + CFO) / (1 + ε) − 125000` Гц относительно номинального +цифрового смесителя. Отчёт хранит и заданный CFO, и этот ожидаемый остаток; +один не выдаётся за другой. Частотный тест проверяет оба эффекта раздельно. + +Программа `tools/clock001.py` строит по три пакета каждого размера с паузой +5 мс и seed `20260917`. Нагрузка 256 байт совпадает с RF-001; для 512 и +1012 байт та же SHA-последовательность продлена. **1012 байт** заполняют +допустимый PHY-пакет 1024 байта с 12 байтами заголовка и CRC. Во всех +39 точках ppm-сетки приёмник получает только полученный IQ и номинальный +радиорежим. TX-границы, исходные пакеты, заданный ppm/CFO и число пакетов +используются только после декодирования для оценки. + +```powershell +python -B -X utf8 tools/clock001.py ` + --numba-packages C:\Users\user\Desktop\projects\SDR_Rover\.lab053_cpu_runtime\packages +``` + +Параметр с локальным путём к Numba нужен на этом ПК; в обычном окружении с +зависимостями проекта он не требуется. Команда отказывается перезаписывать +готовый отчёт. + +## Строгие результаты + +В таблице каждая ячейка означает число **точных пакетов из трёх** после +PHY/FEC/CRC. CFO, фаза и AWGN в этой сетке равны нулю/отсутствуют. + +| Payload | 0 | ±1 ppm | ±5 ppm | ±10 ppm | ±20 ppm | ±50 ppm | ±100 ppm, стресс | +|---|---:|---:|---:|---:|---:|---:|---:| +| 256 байт | 3/3 | 3/3 в обе стороны | 3/3 | 3/3 | 3/3 | 3/3 | 3/3 | +| 512 байт | 3/3 | 3/3 | 3/3 | 3/3 | 3/3 | 3/3 | 3/3 | +| 1012 байт | 3/3 | 3/3 | 3/3 | 3/3 | 3/3 | 3/3 | **0/3** | + +Основная сетка 0, ±1, ±5, ±10, ±20 и ±50 ppm: **99/99**. Наибольшая +симметричная проверенная точка со 100% точных пакетов для всех размеров: +**±50 ppm**. Для 256 и 512 байт стресс ±100 ppm тоже дал 3/3 в обе стороны. +Одиночный пакет 1012 байт при −100 и +100 ppm дал 0/1; в обоих случаях +найден один корреляционный кандидат, но CRC-пакета нет. Значит, новый поиск +преамбулы перед следующим пакетом не устраняет эту потерю внутри текущего +длинного пакета. При ±100 ppm модель накапливает по нему ±0,7572 символа. +Контроль с отдельной кубической интерполяцией повторил для одиночного +1012-байтного пакета 1/1 при ±50 и 0/1 при ±100 ppm. Вывод не зависит +только от выбора линейного интерполятора в основном опыте. + +Текущий приёмник оценивает начало **каждого** пакета заново, затем выбирает +его символы с постоянным шагом 16 отсчётов. Для 1012 байт при −50 ppm +медианная ошибка в начале/расчётная ошибка в конце составили +−0,0325 / +0,3461 символа; при +50 ppm +0,0322 / −0,3464. Разность по +модели равна ±0,3786 символа. Начальную ошибку считаем по началу принятого +CRC-пакета и известной **после приёма** TX-шкале. Конечная ошибка +экстраполирована из заданного ppm при фиксированном шаге приёмника; это не +независимое измерение последнего символа. Для потерянного пакета эти оценки +равны `null`, а введённый моделью дрейф указан отдельно. + +В JSON `correlation_candidates` считает кандидатов поиска. Поле +`detected_packets` равно числу CRC-valid пакетов: нынешний API не выдаёт +отдельное число пакетов, распознанных до CRC. Поля точности и потерь +считаются отдельно по TX-ведомости после приёма. + +Отдельно проверены шесть сочетаний для **256 байт**, каждое с тремя пакетами: + +| ppm | CFO | Фаза | Eb/N0 | Точные пакеты | +|---:|---:|---:|---:|---:| +| +5 | +20 Гц | +15° | 9 и 12 дБ | 3/3 при каждом уровне | +| +10 | +100 Гц | +90° | 9 и 12 дБ | 3/3 при каждом уровне | +| +20 | +100 Гц | −45° | 9 и 12 дБ | 3/3 при каждом уровне | + +Итого **18/18**. Шум задан прежней программной AWGN-моделью VIDEO-005; +это не оценка реального RF-канала. + +## Компенсация и скорость + +Для основной сетки повторной оценки момента по преамбуле каждого пакета +хватило. **Непрерывное слежение в рабочий приёмник не добавлялось.** Для +расширения диапазона до ±100 ppm на пакете 1012 байт потребовалась бы +коррекция внутри пакета, например отдельное исследование детектора ошибки +момента [Gardner для BPSK/QPSK](https://doi.org/10.1109/TCOM.1986.1096561) +с интерполяцией и петлёй. [Документация GNU Radio](https://www.gnuradio.org/doc/doxygen/classgr_1_1digital_1_1symbol__sync__cc.html) +показывает такой класс синхронизатора и его параметры. Перед включением +понадобятся строгие CRC-тесты на всей сетке, проверка без истинного ppm и +новый замер скорости. Старый BPSK-детектор сюда не переносился. + +На каноническом потоке VIDEO-005 из 32 длинных пакетов с добавленными ++20 ppm, +20 Гц CFO, фазой +15° и Eb/N0 9 дБ получено **32/32** при каждом +из трёх прогретых запусков. Времена приёмника: 0,272 / 0,262 / 0,262 с; +длительность IQ 0,485764 с; медианный **RADIO RECEIVER REALTIME FACTOR = +0,540**. Построение канала и ресемплирование в это время не входят. Для +сравнения, сохранённый ориентир VIDEO-005 равен 0,578 на другом входе; +разница не доказывает ускорения. Первый запуск ppm-сетки включал JIT и не +использован для оценки скорости. Порог 0,67 на этом ПК выполнен. + +## Code quality audit + +Рабочий `receive_iq_stream()` и его защитные проверки не менялись. Новый +пересчёт временной шкалы, сравнение с эталоном и запись JSON остаются вне +пути приёма. Отдельный `cProfile` того же 32-пакетного IQ занял 0,293 с; +его время включает накладные расходы профилировщика и не подставляется в +фактор 0,540. Основные этапы: + +| Этап | Время, мс | Доля профиля | Вызовы | Основные массивы и копии | +|---|---:|---:|---:|---| +| Цифровое смещение несущей | 16,4 | 5,6% | 1 | Осциллятор и baseband | +| Согласованный RRC | 62,1 | 21,2% | 1 | Выход и внутренние FFT-буферы | +| Поиск преамбул | 47,6 | 16,2% | 1 | Две корреляции и массивы энергии | +| Восстановление момента | 9,9 | 3,4% | 32 | Индексы и символы каждого кандидата | +| Оценка CFO и фазы | 12,2 | 4,2% | 32 | Исправленные символы | +| PHY-заголовок | 0,8 | 0,3% | 32 | Короткие массивы заголовка | +| Слежение за фазой тела | 78,4 | 26,8% | 32 | Копия символов пакета и блоки решений | +| Строгий PHY/FEC/CRC | 54,8 | 18,7% | 32 | Мягкие биты и рабочие массивы декодера | + +Число аллокаций NumPy не измерялось; в таблице перечислены видимые по коду +массивы. Resampling создаёт один выходной complex64 IQ и временные массивы +до миллиона отсчётов за блок; в измеренное время приёмника он не входит. Проверки +размеров и конечности IQ оставлены как защита входа; диапазон ppm и длина +пакета проверяются при создании опыта. Проверка эталона находится после +приёмника. Ruff не нашёл неиспользуемых импортов, временных compatibility +wrappers и лабораторных ветвей в новом коде нет. Канонический генератор +32-пакетного VIDEO-005 входа также строит сигнал с нарушениями канала, +который CLOCK-001 не использует; это один офлайн-вызов, не приёмный hot path. +Удалять или переписывать его здесь нет оснований. Поведение VIDEO-005 +проверено его неизменённым шлюзом: 178 тестов прошли. + +| Предмет | Место | Почему проверен | Решение | +|---|---|---|---| +| Дополнительный IQ от канонического генератора | `tools/clock001.py`, `run()` | Создаётся, но не участвует в CLOCK-входе | **KEEP**: однократная офлайн-цена за неизменный эталон VIDEO-005 | +| Длинная оркестрация опыта | `tools/clock001.py`, `run()` | Собирает сетку, сочетания и замер | **KEEP**: не исполняется в рабочем приёмнике; шаги явно разделены | +| Проверка конечности всего TX IQ | `tools/clock001.py`, `sample_independent_clock()` | Один проход по большому массиву | **KEEP**: защита от повреждённого входа до интерполяции | + +В CLOCK-001 нет внешнего erasure FEC и видеотранспорта: это опыт с PHY-пакетами, +поэтому их время нельзя включать в фактор радиоприёмника. + +## Следующий аппаратный план + +План RF-001 разделён на **RF-001A** с проводной общей опорой 10 МГц как +лабораторным baseline и **RF-001B** с независимыми тактами. В B провод +CLKOUT → CLKIN отключён. Остальные параметры и TX-волна должны совпасть; +оба режима сохраняют исходный RX IQ и полные наблюдения счётчиков. В B +нужны последовательности оценок CFO и моментов пакетов, чтобы после +приёма оценить их изменение во времени и фактическую разницу частот +дискретизации. Моделируемые ppm не считаются характеристикой плат. + +**Готовность A и B: да, только к подготовке контролируемого опыта.** +Фактические частоты, соединения, gain и RF-условия ещё не проверены. Для +каждого аппаратного сеанса потребуется отдельный preflight и прямое +разрешение. CLOCK-001 не открывала SDR, не меняла аппаратную конфигурацию +и не выполняла RF TX/RX. Коммита и push для этого этапа нет. diff --git a/docs/rf/RF_001_PLAN.md b/docs/rf/RF_001_PLAN.md new file mode 100644 index 0000000..eb32ef6 --- /dev/null +++ b/docs/rf/RF_001_PLAN.md @@ -0,0 +1,185 @@ +# RF-001 — подготовка первого QPSK-пакетного опыта + +**Статус: PREPARED / NOT_RUN.** Это программная подготовка после коммита +VIDEO-005 `6df5f8a`. SDR не открывался; приёма и передачи через него не было. +`RF TX = 0`. Первый аппаратный сеанс требует отдельной постановки и прямого +разрешения. + +## Цель и граница + +Будущий опыт проверит известные PHY-пакеты через два Auron HackRF Plus. +Утверждён один начальный режим: QPSK, **500 000 символов/с**, внутренний FEC +**2/3**, прикладная нагрузка **256 байт**, пауза **5 мс**, **100 пакетов** с +номерами 0–99 и seed `20260917`. H.264, камера, видеотранспорт, внешний +erasure FEC и адаптивное переключение не участвуют. До первого аппаратного +результата нагрузку не увеличивать, а паузу не уменьшать. + +```text +TX: payload(seed, sequence) → build_frame(QPSK, 2/3) → shape_packet(RRC) + → IQ-файл → [будущий аппаратный IQSink] +RX: [будущий аппаратный IQSource] → исходный raw IQ-файл + → receive_iq_stream() VIDEO-005 → CRC-valid пакеты + → отдельное сравнение с TX-ведомостью и отчёт +``` + +PHY-приёмник не получает TX-границы, payload, истинную фазу, CFO, момент +выборки или ожидаемое число пакетов. SHA и ошибки сравниваются после строгого +декодирования. `receive_iq_stream()` для RF-001 не менялся. Исторический +`build_frame()` ставит в пакет тип 3; здесь содержимое — тестовые байты, +видеосборщик не вызывается. + +## Файловые инструменты + +`tools/qpsk_rf_tx.py` создаёт весь IQ без аппаратных импортов в новом +каталоге `data/raw/rf001/offline_qpsk_100_packets/`. Существующие файлы он +не перезаписывает. Нагрузка каждого номера получается из SHA-256 по seed, +номеру и счётчику блока, без зависимости от версии генератора NumPy. + +```powershell +python -B -X utf8 tools/qpsk_rf_tx.py +``` + +`tx_waveform.c64le` — сырые little-endian complex64, 8 байт на комплексный +отсчёт, без заголовка. `tx_metadata.json` содержит `test_id`, UTC-время, +режим, номинальный цифровой сдвиг несущей 125 кГц, seed, номера и SHA-256 +payload/пакетов, паузу, dtype, число/длительность отсчётов, SHA IQ и +TX-границы. Последние остаются только в ведомости. Форма каждого пакета +включает по 64 защитных отсчёта на концах; между массивами вставлены +39 872 нулевых отсчёта. Итого пауза вместе с защитными участками — ровно +**40 000 отсчётов = 5 мс**. Это цифровая форма без аппаратной калибровки +амплитуды и преобразования к формату TX устройства. + +`tools/qpsk_rf_rx.py` читает `c64le` и вызывает существующий приёмник +VIDEO-005. Необязательная TX-ведомость читается **после** вызова приёмника. +Для будущего сохранённого аппаратного файла предусмотрен вход +`--iq-format cs8`: чередующиеся знаковые байты I/Q преобразуются в +complex64 для анализа, исходный файл не меняется; отдельно считаются +коды насыщения −128/127. Это файловый конвертер, не аппаратный адаптер. + +```powershell +python -B -X utf8 tools/qpsk_rf_rx.py ` + --iq-file data/raw/rf001/offline_qpsk_100_packets/tx_waveform.c64le ` + --truth-metadata data/raw/rf001/offline_qpsk_100_packets/tx_metadata.json ` + --report data/processed/rf001/offline_roundtrip.json ` + --numba-packages C:\Users\user\Desktop\projects\SDR_Rover\.lab053_cpu_runtime\packages +``` + +Путь `--numba-packages` относится к текущему ПК; на втором ноутбуке Numba +берётся из его окружения. Без TX-ведомости инструмент сообщает кандидатов +преамбулы и CRC-пакеты, номера, повторы и оценки CFO/фазы/такта, но не +объявляет точность или потери относительно неизвестного ожидаемого списка. + +## Программный круг на 100 пакетах + +| Показатель | Результат | +|---|---:| +| IQ | 8 833 828 отсчётов; 1,1042285 с; 70 670 624 байта | +| SHA-256 IQ | `7cd81395efaa88b623b7e4e5a02e3a80b52185007397ea56ceaacc13353974b2` | +| Кандидаты корреляционного поиска / отвергнутые | 557 / 457 | +| CRC-valid / точные пакеты | 100 / 100 | +| Пропущенные / дубли | 0 / 0 | +| Raw BER по всем 100 принятым пакетам | 0 / 322 500 кодированных битов | +| Ошибки после FEC | 0 в сопоставленных пакетах | + +**557 — кандидаты корреляции, а не 557 принятых преамбул.** Только 100 +прошли PHY/FEC/CRC. Raw BER получен отдельным повтором штатных PHY-функций +после первого приёма. При потере пакетов полный raw BER неизвестен: API +VIDEO-005 не отдаёт мягкие решения отвергнутых кандидатов. Тогда отчёт +покажет BER на CRC-valid пакетах отдельно, а `raw_ber` оставит `null`. +Условный BER нельзя выдавать за BER всего канала. +Полный машинный отчёт сохранён в `data/processed/rf001/offline_roundtrip.json`; +исходный IQ и TX-ведомость лежат в игнорируемом Git `data/raw/rf001/`. + +## Будущий захват и аппаратная граница + +Следующий этап сначала сохраняет **исходные** байты аппаратного RX, затем +анализирует их офлайн. Небольшая граница `IQSource` создаёт raw IQ-файл и +`capture_metadata.json`; файловый анализатор читает сохранённый файл. +`IQSink` сможет принять заранее проверенный TX-файл только после отдельного +допуска. PHY-модули не знают о HackRF API; собственный libusb-слой не нужен. + +Предлагаемый файловый контракт capture: `capture.cs8` без заголовка, пары +знаковых байтов `I0,Q0,I1,Q1,…`; исходный файл не нормализовать и не +перезаписывать. `capture_metadata.json` создаётся рядом. Неизвестные поля +остаются `null` или `NOT_RUN`, а не заполняются нулями: + +```json +{ + "schema_version": 1, + "status": "NOT_RUN", + "raw_iq_file": "capture.cs8", + "iq_format": "cs8_interleaved_signed_iq", + "sample_rate_hz": 8000000, + "sample_count": null, + "raw_iq_sha256": null, + "capture_started_utc": null, + "center_frequency_hz": null, + "nominal_digital_carrier_offset_hz": 125000, + "clock_mode": "NOT_RUN", + "clock_link": "NOT_RUN", + "estimated_sample_clock_offset_ppm": null, + "cfo_series_hz": null, + "timing_drift_series_symbols": null, + "receiver_serial": null, + "gain_settings": null, + "common_clock": "NOT_RUN", + "tx_underrun_observations": [], + "rx_overrun_observations": [], + "rx_clipping_rail_codes": null, + "counter_observation_complete": false, + "capture_validity": "NOT_RUN", + "command_log": [] +} +``` + +В metadata захвата потребуются время, путь/формат/SHA raw IQ, частота +дискретизации, центр RF, номинальный цифровой сдвиг, число отсчётов, +серийные номера, gain, состояние общей опоры, журнал команд и **все** +наблюдения underrun/overrun/clipping по ходу сеанса. Неполный или нечистый +счётчик делает результат непригодным для PASS; конечный ноль не отменяет +промежуточную потерю. Сейчас аппаратного источника/стока и фактического +capture metadata нет — это будущая отдельно разрешённая работа. +Дрейф частоты дискретизации не моделировался в VIDEO-005 и остаётся отдельным +риском для реального захвата. + +RF-001 делится на два будущих режима с отдельным разрешением на каждый выход +в эфир. **RF-001A** использует проводную общую опору 10 МГц только как +лабораторный baseline: TCXO → MASTER → MASTER CLKOUT → SLAVE CLKIN. Предыдущие +лабораторные проверки фиксировали MASTER и SLAVE `reg29 = 0x26`, `SLAVE CLKIN = +DETECTED`. Это **исторические данные**, а не свежая проверка подключения. +**RF-001B** использует независимые такты: провод CLKOUT → CLKIN снят. Именно +этот вариант соответствует архитектуре ровера и НСУ без проводной опоры. + +Для сравнения A/B одинаковыми остаются RF-частота, 500 ксимв/с, FEC 2/3, +payload 256 байт, пауза 5 мс, 100 пакетов, TX-уровень, RX-усиления, +геометрия, антенны и TX-волна. Оба сеанса сначала сохраняют исходный RX IQ и +полный журнал счётчиков. В RF-001B по принятой преамбуле каждого пакета и +известным TX-номерам нужно оценить CFO по времени и наклон ошибки момента +выборки, затем разницу частот дискретизации. Эти оценки делаются **после** +строгого PHY/FEC/CRC; потери и пропуски не подменяются известными границами +передатчика. Программная сетка CLOCK-001 проверила до ±50 ppm, но фактический +ppm плат ещё не измерен. Частота RF, gain, кабели, антенны и уровень сигнала +для нового опыта не утверждены здесь; они войдут в отдельный аппаратный +preflight. + +## Форма отчёта и следующий критерий + +Будущий отчёт содержит `RF-001 STATUS`, режим, число кандидатов преамбулы, +CRC-valid и точных пакетов, долю точных, raw BER с областью расчёта, +ошибки после FEC, дубли/пропуски, медиану CFO и P95 |CFO|, оценки фазы/такта, +TX underruns, RX overruns, RX clipping, общую опору +`DETECTED / NOT_DETECTED`, SHA capture и причины отказа. Сейчас аппаратные +поля — `NOT_RUN`, а не нули. + +Для будущего короткого OTA smoke test, если захват и счётчики чисты: +не менее 99 точных из 100 — GOOD; 95–98 — DIAGNOSTIC/PARTIAL; менее 95 — +не переходить к видео. Это предварительная классификация, не +эксплуатационная норма. Программные 100/100 не доказывают RF PASS. + +Только после отдельного аппаратного PASS: сравнение пауз 5 → 2 → 1 → 0,5 +→ 0 мс при 256 байтах. После выбора паузы — отдельно 256 → 512 → +максимально допустимый рабочий payload. Здесь матрица не выполняется. + +**RF-001 HARDWARE STATUS = NOT_RUN. SDR HARDWARE = NOT_ACCESSED. RF TX = 0.** +Следующий шаг — отдельное согласование preflight и одного контролируемого +сеанса; автоматического перехода к эфиру нет. diff --git a/docs/rf/SECOND_LAPTOP_RF_SETUP.md b/docs/rf/SECOND_LAPTOP_RF_SETUP.md new file mode 100644 index 0000000..464531a --- /dev/null +++ b/docs/rf/SECOND_LAPTOP_RF_SETUP.md @@ -0,0 +1,48 @@ +# Второй ноутбук для будущего RF-001 + +Это список подготовки Windows 10/11. В текущей задаче второй ноутбук, +USB-драйвер и HackRF не проверялись. Никакой пункт ниже не запускается +автоматически. + +## Программы и проект + +1. Зафиксировать выпуск Windows и имеющийся USB-драйвер для каждого Auron + HackRF Plus. Сначала инвентаризация; Zadig не запускать без отдельного + решения. Прошивку устройства не обновлять автоматически. +2. Установить Git и VS Code. Установить **Python 3.13.1**, как на основном + ПК при подготовке RF-001; проверить `python --version` и `git --version`. +3. Получить согласованный коммит и ветку `video-development`, сверить HEAD. + Создать отдельное виртуальное окружение Python и установить зависимости + из `requirements-video.txt`; для разработки также + `requirements-video-dev.txt`. Проверить импорты NumPy, SciPy и Numba, + затем запустить целевые программные тесты. +4. Установить совместимые HackRF host tools/libhackrf, но до отдельного + аппаратного разрешения не открывать устройство. Позже, во время + согласованной инвентаризации, проверить `hackrf_info` и сведения + о каждом устройстве, не выполняя TX/RX. Не заменять драйвер только ради + ошибки команды без проверки текущего состояния USB. +5. Сверить SHA-256 программного `tx_waveform.c64le` с TX-ведомостью RF-001. + Локально повторить файловый RX: 100/100 точных пакетов до аппаратного + этапа. + +## Перед будущим аппаратным сеансом + +- Зафиксировать серийные номера MASTER/SLAVE, версии firmware и host tools, + идентификаторы USB и состояние драйверов. Само наличие `hackrf_info` не + означает исправную общую опору или готовность к передаче. +- Сверить физическую схему TCXO 10 МГц → MASTER → MASTER CLKOUT → SLAVE + CLKIN для лабораторного RF-001A; не создавать петлю через SLAVE CLKOUT. + Для отдельного RF-001B провод CLKOUT → CLKIN отключается при неизменных + прочих параметрах. Исторические проверки + `reg29 = 0x26` на обеих платах и `SLAVE CLKIN = DETECTED` нужно подтвердить + в новом preflight, если он будет отдельно разрешён. +- Записать согласованные RF-частоту, усиления, антенны/кабели, геометрию, + формат TX и лимиты уровня. Цифровой файл complex64 не является готовым + аппаратным потоком до отдельного преобразования и проверки уровня. +- Настроить сохранение исходного RX IQ и metadata **до** офлайн-декодирования. + Сохранить полный журнал команд и значения underrun/overrun/clipping, + включая промежуточные наблюдения. Неполный захват не считать PASS. + +Команды `python --version`, `git --version` и `hackrf_info` приведены как +будущий чеклист. В рамках программной подготовки RF-001 аппаратные команды +не выполнялись, RF TX = 0. diff --git a/pyrightconfig.json b/pyrightconfig.json index 9c09c24..4c146a0 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -19,8 +19,14 @@ "tools/video005_video.py", "tools/video005_benchmark.py", "tools/video_gate.py", + "tools/qpsk_rf_common.py", + "tools/qpsk_rf_tx.py", + "tools/qpsk_rf_rx.py", + "tools/clock001.py", "tests/test_video005_iq.py", - "tests/test_video005_video.py" + "tests/test_video005_video.py", + "tests/test_rf001_preparation.py", + "tests/test_clock001.py" ], "typeCheckingMode": "basic", "pythonVersion": "3.13", diff --git a/tests/test_clock001.py b/tests/test_clock001.py new file mode 100644 index 0000000..9308c6d --- /dev/null +++ b/tests/test_clock001.py @@ -0,0 +1,97 @@ +"""CLOCK-001: отдельная частота АЦП, строгий приём и область метрик.""" + +from __future__ import annotations + +import math + +import numpy as np +import pytest +from scipy.ndimage import map_coordinates + +from protocol.video003_modem import SAMPLE_RATE_HZ +from protocol.video005_receiver import IqStreamResult, receive_iq_stream +from tools import clock001 +from tools.qpsk_rf_common import SEED, payload_for_sequence + + +def test_sampling_rate_changes_time_base_and_keeps_cfo_independent() -> None: + tone_hz = 125_000 + cfo_hz = 100 + indexes = np.arange(200_000) + transmitted = np.exp(2j * np.pi * tone_hz * indexes / SAMPLE_RATE_HZ).astype(np.complex64) + for ppm in (-50, 0, 50): + received = clock001.sample_independent_clock( + transmitted, ppm, cfo_hz=cfo_hz, initial_phase_degrees=45, + ) + scale = 1 + ppm * 1e-6 + assert len(received) == math.floor((len(transmitted) - 1) * scale) + 1 + measured_hz = (np.angle(np.vdot(received[:-1].astype(np.complex128), + received[1:].astype(np.complex128))) + * SAMPLE_RATE_HZ / (2 * np.pi)) + assert measured_hz == pytest.approx((tone_hz + cfo_hz) / scale, abs=0.2) + assert np.angle(received[0]) == pytest.approx(np.pi / 4, abs=1e-6) + assert clock001.payload_for_case(256, 7) == payload_for_sequence(SEED, 7) + + +@pytest.mark.parametrize("ppm", (-50, 50)) +def test_long_packets_reacquire_and_decode_at_primary_limit(ppm: int) -> None: + fixture = clock001.build_fixture(1012) + received_iq = clock001.sample_independent_clock(fixture.iq, ppm) + result = receive_iq_stream(received_iq, decoder_backend="legacy") + summary = clock001.summarize_case( + result, fixture, sample_clock_offset_ppm=ppm, cfo_hz=0, + initial_phase_degrees=0, ebn0_db=None, receiver_time_s=1.0, + received_samples=len(received_iq), + ) + assert summary["exact_packets"] == 3 + assert summary["exact_sequences"] == [0, 1, 2] + assert summary["reacquisition_per_packet"] is True + assert summary["injected_timing_drift_across_packet_symbols"] == pytest.approx(-ppm * 1e-6 * 7572) + + +@pytest.mark.parametrize("ppm", (-100, 100)) +def test_long_packet_stress_failure_is_not_declared_exact(ppm: int) -> None: + fixture = clock001.build_fixture(1012, packet_count=1) + received_iq = clock001.sample_independent_clock(fixture.iq, ppm) + result = receive_iq_stream(received_iq, decoder_backend="legacy") + summary = clock001.summarize_case( + result, fixture, sample_clock_offset_ppm=ppm, cfo_hz=0, + initial_phase_degrees=0, ebn0_db=None, receiver_time_s=1.0, + received_samples=len(received_iq), + ) + assert summary["exact_packets"] == 0 + assert summary["missing_sequences"] == [0] + assert summary["initial_timing_error_symbols_median"] is None + assert summary["final_timing_error_symbols_median"] is None + assert abs(summary["injected_timing_drift_across_packet_symbols"]) > 0.75 + + +def test_cubic_resampling_confirms_long_packet_stress_boundary() -> None: + """Проверяет вывод вторым интерполятором, без TX-подсказок приёмнику.""" + fixture = clock001.build_fixture(1012, packet_count=1) + for ppm, expected_packets in ((-100, 0), (-50, 1), (50, 1), (100, 0)): + scale = 1 + ppm * 1e-6 + count = math.floor((len(fixture.iq) - 1) * scale) + 1 + positions = np.arange(count, dtype=np.float64) / scale + received_iq = ( + map_coordinates(fixture.iq.real, [positions], order=3, mode="nearest") + + 1j * map_coordinates(fixture.iq.imag, [positions], order=3, mode="nearest") + ).astype(np.complex64) + result = receive_iq_stream(received_iq, decoder_backend="legacy") + assert len(result.packets) == expected_packets + + +def test_reference_is_used_only_after_receiver(monkeypatch: pytest.MonkeyPatch) -> None: + fixture = clock001.build_fixture(256, packet_count=1) + calls = [] + + def fake_receiver(iq: np.ndarray) -> IqStreamResult: + calls.append(iq) + return IqStreamResult((), 0, 0, {}) + + monkeypatch.setattr(clock001, "receive_iq_stream", fake_receiver) + summary = clock001.evaluate_case(fixture, ppm=20) + assert len(calls) == 1 + assert isinstance(calls[0], np.ndarray) + assert summary["exact_packets"] == 0 + assert summary["missing_sequences"] == [0] diff --git a/tests/test_rf001_preparation.py b/tests/test_rf001_preparation.py new file mode 100644 index 0000000..0935043 --- /dev/null +++ b/tests/test_rf001_preparation.py @@ -0,0 +1,113 @@ +"""RF-001: воспроизводимость файлового IQ без аппаратуры и передачи.""" + +from __future__ import annotations + +import json +from itertools import pairwise +from pathlib import Path + +import numpy as np +import pytest + +from protocol.video003_modem import build_frame +from protocol.video005_receiver import IqStreamResult, ReceivedIqPacket +from tools import qpsk_rf_rx +from tools.qpsk_rf_common import ( + GAP_SAMPLES, PACKET_COUNT, PAYLOAD_BYTES, SEED, payload_for_sequence, + read_iq_file, sha256_file, +) +from tools.qpsk_rf_tx import build_dry_run, write_dry_run + + +def test_payload_and_gap_are_fixed_and_reproducible() -> None: + first = payload_for_sequence(SEED, 0) + assert len(first) == PAYLOAD_BYTES + assert first == payload_for_sequence(SEED, 0) + assert first != payload_for_sequence(SEED, 1) + iq, metadata = build_dry_run() + assert iq.dtype == np.dtype(" left for left, right in pairwise(starts)) + assert iq.size == metadata["waveform_sample_count"] + with pytest.raises(ValueError): + payload_for_sequence(-1, 0) + + +def test_full_100_packet_file_roundtrip(tmp_path: Path) -> None: + output_directory = tmp_path / "rf001" + iq_path, metadata_path = write_dry_run(output_directory) + metadata = json.loads(metadata_path.read_text(encoding="utf-8")) + assert iq_path.stat().st_size == metadata["waveform_sample_count"] * 8 + assert sha256_file(iq_path) == metadata["iq_sha256"] + with pytest.raises(FileExistsError): + write_dry_run(output_directory) + report = qpsk_rf_rx.decode_file(iq_path, truth_metadata_path=metadata_path) + assert report["decoded_packets"] == PACKET_COUNT + assert report["crc_valid_packets"] == PACKET_COUNT + assert report["exact_packets"] == PACKET_COUNT + assert report["packet_sequences"] == list(range(PACKET_COUNT)) + assert report["duplicates"] == 0 + assert report["missing_packets"] == [] + assert report["raw_ber"] == 0 + assert report["raw_bits_compared_on_crc_valid_packets"] > 0 + assert report["raw_ber_replay_failures"] == 0 + assert report["post_fec_bit_errors"] == 0 + assert report["same_bytes_as_tx_waveform"] + assert report["rf_tx"] == 0 + assert report["sdr_hardware"] == "NOT_ACCESSED" + + +def test_missing_packets_do_not_get_invented_raw_ber() -> None: + frame = build_frame(payload_for_sequence(SEED, 0), "QPSK", "2/3", sequence_number=0) + packet = ReceivedIqPacket(frame.packet, payload_for_sequence(SEED, 0), + 0.0, 0.0, 0.0, 0.0, 0, 1.0, 0.0) + reception = IqStreamResult((packet,), 1, 0, {}) + _, metadata = build_dry_run() + report = qpsk_rf_rx.summarize_result( + reception, iq_sample_count=100, iq_sha256="different", iq_format="c64le", + clipping_codes=None, truth=metadata, raw_bits=(0, 3210, 0), + ) + assert report["exact_packets"] == 1 + assert len(report["missing_packets"]) == 99 + assert report["raw_ber"] is None + assert report["raw_ber_on_crc_valid_packets"] == 0 + assert report["raw_ber_scope"].startswith("CRC_VALID_PACKETS_ONLY") + assert report["post_fec_error_rate"] is None + assert report["post_fec_error_rate_on_compared_packets"] == 0 + + +def test_truth_file_is_read_only_after_receiver(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + iq_path = tmp_path / "input.c64le" + np.zeros(32, dtype=" IqStreamResult: + assert iq.size == 32 + called.append(True) + return IqStreamResult((), 0, 0, {}) + + monkeypatch.setattr(qpsk_rf_rx, "receive_iq_stream", fake_receiver) + with pytest.raises(json.JSONDecodeError): + qpsk_rf_rx.decode_file(iq_path, truth_metadata_path=truth_path) + assert called == [True] + + +def test_iq_file_format_and_clipping_validation(tmp_path: Path) -> None: + invalid = tmp_path / "bad.c64le" + invalid.write_bytes(b"123") + with pytest.raises(ValueError, match="c64le"): + read_iq_file(invalid) + raw = tmp_path / "capture.cs8" + raw.write_bytes(bytes([127, 0, 128, 10])) + iq, clipping = read_iq_file(raw, "cs8") + assert iq.dtype == np.complex64 + assert iq.size == 2 + assert clipping == 2 diff --git a/tools/clock001.py b/tools/clock001.py new file mode 100644 index 0000000..7dee159 --- /dev/null +++ b/tools/clock001.py @@ -0,0 +1,354 @@ +"""CLOCK-001: программная проверка QPSK при независимых тактах TX и RX.""" + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +import hashlib +import json +import math +from pathlib import Path +import statistics +import sys +import time + +import numpy as np + +if __package__ in (None, ""): + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from protocol.packet import parse_packet +from protocol.video003_modem import SAMPLE_RATE_HZ, build_frame +from protocol.video005_iq_waveform import ( + GUARD_SAMPLES, + NOMINAL_CARRIER_HZ, + SYMBOL_RATE, + rrc_taps, + shape_packet, +) +from protocol.video005_receiver import IqStreamResult, receive_iq_stream +from tools.qpsk_rf_common import GAP_SAMPLES, SEED + + +ROOT = Path(__file__).resolve().parents[1] +OUTPUT = ROOT / "data/processed/clock001/results.json" +PAYLOAD_SIZES = (256, 512, 1012) +OFFSETS_PPM = (0, -1, 1, -5, 5, -10, 10, -20, 20, -50, 50, -100, 100) +PRIMARY_LIMIT_PPM = 50 +PACKETS_PER_CASE = 3 + + +@dataclass(frozen=True) +class ClockFixture: + iq: np.ndarray + expected_packets: dict[int, bytes] + starts: dict[int, int] + symbol_counts: dict[int, int] + payload_bytes: int + + +def payload_for_case(payload_bytes: int, sequence_number: int) -> bytes: + """Расширяет фиксированную RF-001 SHA-последовательность до длины пакета.""" + if not 0 <= sequence_number <= 0xFFFF or not 1 <= payload_bytes <= 1012: + raise ValueError("неверный номер или размер пакета") + prefix = SEED.to_bytes(8, "big") + sequence_number.to_bytes(2, "big") + blocks = math.ceil(payload_bytes / hashlib.sha256().digest_size) + return b"".join( + hashlib.sha256(prefix + block.to_bytes(2, "big")).digest() + for block in range(blocks) + )[:payload_bytes] + + +def build_fixture(payload_bytes: int, packet_count: int = PACKETS_PER_CASE) -> ClockFixture: + """Собирает поток с RF-001 паузой; границы сохраняет только для оценки.""" + if packet_count < 1 or packet_count > 100: + raise ValueError("число пакетов должно быть от 1 до 100") + separator = GAP_SAMPLES - 2 * GUARD_SAMPLES + if separator < 0: + raise AssertionError("пауза короче защитных интервалов") + waveforms = [] + expected_packets = {} + starts = {} + symbol_counts = {} + position = 0 + for sequence_number in range(packet_count): + frame = build_frame(payload_for_case(payload_bytes, sequence_number), + "QPSK", "2/3", sequence_number=sequence_number) + waveform = shape_packet(frame) + starts[sequence_number] = position + expected_packets[sequence_number] = frame.packet + symbol_counts[sequence_number] = len(frame.symbols) + waveforms.append(waveform) + position += len(waveform) + (separator if sequence_number < packet_count - 1 else 0) + iq = np.zeros(position, dtype=np.complex64) + for sequence_number, waveform in enumerate(waveforms): + start = starts[sequence_number] + iq[start:start + len(waveform)] = waveform + return ClockFixture(iq, expected_packets, starts, symbol_counts, payload_bytes) + + +def sample_independent_clock( + tx_iq: np.ndarray, + sample_clock_offset_ppm: float, + *, + cfo_hz: float = 0.0, + initial_phase_degrees: float = 0.0, + ebn0_db: float | None = None, + seed: int = SEED, +) -> np.ndarray: + """Выбирает TX-волну в моменты АЦП Fs_RX=Fs_TX·(1+ppm·1e-6). + + Дробная интерполяция меняет временную шкалу по всему потоку. CFO задаётся + отдельно в герцах физического времени RX; затем добавляется AWGN. + """ + if (tx_iq.ndim != 1 or not np.iscomplexobj(tx_iq) or tx_iq.size < 2 + or not np.all(np.isfinite(tx_iq))): + raise ValueError("нужен конечный одномерный комплексный TX IQ") + if not all(math.isfinite(value) for value in ( + sample_clock_offset_ppm, cfo_hz, initial_phase_degrees)): + raise ValueError("параметры такта и несущей должны быть конечными") + if abs(sample_clock_offset_ppm) > 1000: + raise ValueError("модель ограничена диапазоном ±1000 ppm") + if ebn0_db is not None and not math.isfinite(ebn0_db): + raise ValueError("Eb/N0 должно быть конечным") + scale = 1 + sample_clock_offset_ppm * 1e-6 + output_count = math.floor((tx_iq.size - 1) * scale) + 1 + received = np.empty(output_count, dtype=np.complex64) + # Обрабатываем длинные потоки частями, сохраняя единую временную шкалу. + for start in range(0, output_count, 1_000_000): + stop = min(start + 1_000_000, output_count) + positions = np.arange(start, stop, dtype=np.float64) / scale + lower = np.floor(positions).astype(np.int64) + fraction = positions - lower + upper = np.minimum(lower + 1, tx_iq.size - 1) + part = (tx_iq[lower] * (1 - fraction) + + tx_iq[upper] * fraction).astype(np.complex64) + if cfo_hz or initial_phase_degrees: + physical_time = np.arange(start, stop, dtype=np.float64) / (SAMPLE_RATE_HZ * scale) + phase = math.radians(initial_phase_degrees) + 2 * math.pi * cfo_hz * physical_time + part *= np.exp(1j * phase).astype(np.complex64) + received[start:stop] = part + if ebn0_db is not None: + sigma = math.sqrt(1 / (2 * (2 / 3) * 10 ** (ebn0_db / 10)) / 2) + random_generator = np.random.default_rng(seed) + for start in range(0, output_count, 1_000_000): + stop = min(start + 1_000_000, output_count) + received[start:stop] += (sigma * ( + random_generator.standard_normal(stop - start) + + 1j * random_generator.standard_normal(stop - start) + )).astype(np.complex64) + return received + + +def _median(values: list[float]) -> float | None: + return float(statistics.median(values)) if values else None + + +def summarize_case( + result: IqStreamResult, + fixture: ClockFixture, + *, + sample_clock_offset_ppm: float, + cfo_hz: float, + initial_phase_degrees: float, + ebn0_db: float | None, + receiver_time_s: float, + received_samples: int, +) -> dict: + """Сравнивает строгий выход приёмника с эталоном после декодирования.""" + scale = 1 + sample_clock_offset_ppm * 1e-6 + tx_filter_delay = (len(rrc_taps()) - 1) / 2 + rx_filter_delay = tx_filter_delay + samples_per_symbol = SAMPLE_RATE_HZ // SYMBOL_RATE + effective_cfo_hz = (NOMINAL_CARRIER_HZ + cfo_hz) / scale - NOMINAL_CARRIER_HZ + exact_sequences: set[int] = set() + seen: set[int] = set() + duplicates: list[int] = [] + unexpected: list[int] = [] + initial_errors = [] + final_errors = [] + drift_values = [] + cfo_errors = [] + for packet in result.packets: + sequence_number = parse_packet(packet.packet).sequence_number + if sequence_number in seen: + duplicates.append(sequence_number) + seen.add(sequence_number) + expected = fixture.expected_packets.get(sequence_number) + if expected is None or packet.packet != expected: + unexpected.append(sequence_number) + continue + exact_sequences.add(sequence_number) + tx_preamble = fixture.starts[sequence_number] + GUARD_SAMPLES + tx_filter_delay + ideal_rx_start = tx_preamble * scale + rx_filter_delay + initial_error = (packet.start_sample - ideal_rx_start) / samples_per_symbol + symbol_intervals = fixture.symbol_counts[sequence_number] - 1 + final_error = initial_error - symbol_intervals * sample_clock_offset_ppm * 1e-6 + initial_errors.append(initial_error) + final_errors.append(final_error) + drift_values.append(final_error - initial_error) + cfo_errors.append(packet.carrier_offset_hz - effective_cfo_hz) + packet_count = len(fixture.expected_packets) + modeled_drift = -(next(iter(fixture.symbol_counts.values())) - 1) * sample_clock_offset_ppm * 1e-6 + # Приёмник выдаёт только CRC-valid пакеты. Отдельной стадии "пакет до CRC" + # в его API нет; число корреляционных кандидатов указано отдельно. + return { + "sample_clock_offset_ppm": sample_clock_offset_ppm, + "cfo_hz": cfo_hz, + "effective_cfo_hz_at_nominal_rx_rate": effective_cfo_hz, + "initial_phase_degrees": initial_phase_degrees, + "ebn0_db": ebn0_db, + "payload_bytes": fixture.payload_bytes, + "tx_packets": packet_count, + "correlation_candidates": result.candidate_count, + "detected_packets": len(result.packets), + "crc_valid_packets": len(result.packets), + "exact_packets": len(exact_sequences), + "packet_success_rate": len(exact_sequences) / packet_count, + "exact_sequences": sorted(exact_sequences), + "missing_sequences": sorted(fixture.expected_packets.keys() - exact_sequences), + "duplicate_sequences": duplicates, + "unexpected_or_wrong_sequences": unexpected, + "initial_timing_error_symbols_median": _median(initial_errors), + "final_timing_error_symbols_median": _median(final_errors), + "timing_drift_across_packet_symbols_median": _median(drift_values), + "injected_timing_drift_across_packet_symbols": modeled_drift, + "maximum_absolute_final_timing_error_symbols": ( + max(map(abs, final_errors)) if final_errors else None + ), + "cfo_estimate_error_hz_median": _median(cfo_errors), + "timing_error_method": "accepted-packet start versus injected time base; final error extrapolated with fixed 16-sample step", + "reacquisition_per_packet": True, + "continuous_timing_tracking_used": False, + "receiver_time_s": receiver_time_s, + "received_duration_s": received_samples / (SAMPLE_RATE_HZ * scale), + "receiver_realtime_factor": receiver_time_s / (received_samples / (SAMPLE_RATE_HZ * scale)), + } + + +def evaluate_case(fixture: ClockFixture, *, ppm: float, cfo_hz: float = 0.0, + phase_degrees: float = 0.0, ebn0_db: float | None = None, + seed: int = SEED) -> dict: + rx_iq = sample_independent_clock( + fixture.iq, ppm, cfo_hz=cfo_hz, initial_phase_degrees=phase_degrees, + ebn0_db=ebn0_db, seed=seed, + ) + started = time.perf_counter() + result = receive_iq_stream(rx_iq) + elapsed = time.perf_counter() - started + return summarize_case(result, fixture, sample_clock_offset_ppm=ppm, + cfo_hz=cfo_hz, initial_phase_degrees=phase_degrees, + ebn0_db=ebn0_db, receiver_time_s=elapsed, + received_samples=len(rx_iq)) + + +def run() -> dict: + """Снимает отдельную ppm-сетку, короткие комбинации и прогретую скорость.""" + fixtures = {size: build_fixture(size) for size in PAYLOAD_SIZES} + grid = [evaluate_case(fixtures[size], ppm=ppm) + for size in PAYLOAD_SIZES for ppm in OFFSETS_PPM] + combinations = [ + (5, 20, 15, 9), (5, 20, 15, 12), + (10, 100, 90, 9), (10, 100, 90, 12), + (20, 100, -45, 9), (20, 100, -45, 12), + ] + combined = [evaluate_case(fixtures[256], ppm=ppm, cfo_hz=cfo, + phase_degrees=phase, ebn0_db=ebn0, + seed=SEED + index) + for index, (ppm, cfo, phase, ebn0) in enumerate(combinations)] + single_long_fixture = build_fixture(1012, packet_count=1) + single_long_stress = [evaluate_case(single_long_fixture, ppm=ppm) + for ppm in (-100, 100)] + from tools.video005_benchmark import build_radio_fixture + + benchmark_packets, clean_benchmark_iq, _ = build_radio_fixture() + benchmark_iq = sample_independent_clock(clean_benchmark_iq, 20, cfo_hz=20, + initial_phase_degrees=15, ebn0_db=9) + benchmark_times = [] + benchmark_exact = [] + for _ in range(3): + started = time.perf_counter() + received = receive_iq_stream(benchmark_iq) + benchmark_times.append(time.perf_counter() - started) + observed = {parse_packet(packet.packet).sequence_number: packet.packet + for packet in received.packets} + benchmark_exact.append(sum(observed.get(index) == packet + for index, packet in enumerate(benchmark_packets))) + duration = len(benchmark_iq) / (SAMPLE_RATE_HZ * (1 + 20e-6)) + benchmark_all_exact = all(count == len(benchmark_packets) for count in benchmark_exact) + primary = [case for case in grid if abs(case["sample_clock_offset_ppm"]) <= PRIMARY_LIMIT_PPM] + primary_all_exact = all(case["exact_packets"] == case["tx_packets"] for case in primary) + stress = [case for case in grid if abs(case["sample_clock_offset_ppm"]) > PRIMARY_LIMIT_PPM] + stress_all_exact = all(case["exact_packets"] == case["tx_packets"] for case in stress) + all_combined_exact = all(case["exact_packets"] == case["tx_packets"] for case in combined) + symmetric_limits = {} + for size in PAYLOAD_SIZES: + maximum = 0 + for offset in (1, 5, 10, 20, 50, 100): + pair = [case for case in grid if case["payload_bytes"] == size + and abs(case["sample_clock_offset_ppm"]) == offset] + if len(pair) != 2 or any(case["exact_packets"] != case["tx_packets"] for case in pair): + break + maximum = offset + symmetric_limits[str(size)] = maximum + return { + "status": ("PASS" if primary_all_exact and stress_all_exact + and all_combined_exact and benchmark_all_exact else "PARTIAL"), + "experiment": "CLOCK-001 INDEPENDENT TX/RX CLOCK READINESS", + "seed": SEED, + "sample_rate_nominal_hz": SAMPLE_RATE_HZ, + "symbol_rate": SYMBOL_RATE, + "fec_rate": "2/3", + "packets_per_case": PACKETS_PER_CASE, + "payload_sizes": PAYLOAD_SIZES, + "offsets_ppm": OFFSETS_PPM, + "primary_limit_ppm": PRIMARY_LIMIT_PPM, + "grid": grid, + "combined": combined, + "single_long_stress": single_long_stress, + "primary_grid_all_exact": primary_all_exact, + "stress_grid_all_exact": stress_all_exact, + "combined_all_exact": all_combined_exact, + "benchmark_all_exact": benchmark_all_exact, + "maximum_symmetric_ppm_with_all_exact_by_payload": symmetric_limits, + "maximum_symmetric_ppm_with_all_payloads_exact": min(symmetric_limits.values()), + "per_packet_reacquisition_sufficient_for_primary_grid": primary_all_exact, + "continuous_tracking_required_for_primary_grid": not primary_all_exact, + "intra_packet_timing_correction_needed_to_cover_long_packet_stress": not stress_all_exact, + "benchmark": { + "description": "VIDEO-005 32 long packets; independent clocks +20 ppm, CFO 20 Hz, phase 15 deg, Eb/N0 9 dB", + "iq_duration_s": duration, + "warm_receiver_times_s": benchmark_times, + "warm_realtime_factor": statistics.median(benchmark_times) / duration, + "exact_packets_each_run": benchmark_exact, + "tx_packets": len(benchmark_packets), + }, + "rf_tx": 0, + "sdr_hardware": "NOT_ACCESSED", + "hardware_status": "NOT_RUN", + } + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--numba-packages", type=Path) + parser.add_argument("--report", type=Path, default=OUTPUT) + args = parser.parse_args() + if args.numba_packages is not None: + if not args.numba_packages.is_dir(): + parser.error("каталог Numba недоступен") + sys.path.insert(0, str(args.numba_packages.resolve())) + if args.report.exists(): + parser.error("отчёт уже существует; выберите новый путь") + report = run() + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8") + print(f"CLOCK-001: {report['status']}; primary_all_exact={report['primary_grid_all_exact']}; " + f"combined_all_exact={report['combined_all_exact']}; " + f"realtime_factor={report['benchmark']['warm_realtime_factor']:.3f}; " + "RF TX=0") + + +if __name__ == "__main__": + main() diff --git a/tools/qpsk_rf_common.py b/tools/qpsk_rf_common.py new file mode 100644 index 0000000..0a5dd7c --- /dev/null +++ b/tools/qpsk_rf_common.py @@ -0,0 +1,69 @@ +"""Формат файлов и воспроизводимые пакеты для подготовки RF-001 без SDR.""" + +from __future__ import annotations + +import hashlib +from pathlib import Path + +import numpy as np + +from protocol.video003_modem import SAMPLE_RATE_HZ +from protocol.video005_iq_waveform import NOMINAL_CARRIER_HZ, SYMBOL_RATE + + +TEST_ID = "RF001_QPSK_500K_R23_256B_100P_GAP5MS" +SEED = 20260917 +PAYLOAD_BYTES = 256 +PACKET_COUNT = 100 +GAP_MS = 5 +GAP_SAMPLES = SAMPLE_RATE_HZ * GAP_MS // 1000 +IQ_FORMAT = "c64le" + + +def payload_for_sequence(seed: int, sequence_number: int) -> bytes: + """Создаёт 256 байт через SHA-256, не завися от версии генератора NumPy.""" + if not 0 <= seed < 2**64 or not 0 <= sequence_number <= 0xFFFF: + raise ValueError("seed или номер пакета вне допустимого диапазона") + prefix = seed.to_bytes(8, "big") + sequence_number.to_bytes(2, "big") + return b"".join( + hashlib.sha256(prefix + block.to_bytes(2, "big")).digest() + for block in range(PAYLOAD_BYTES // hashlib.sha256().digest_size) + ) + + +def sha256_file(path: Path) -> str: + """Хеширует файл потоково, не копируя весь IQ в память.""" + digest = hashlib.sha256() + with path.open("rb") as stream: + while chunk := stream.read(1 << 20): + digest.update(chunk) + return digest.hexdigest() + + +def read_iq_file(path: Path, iq_format: str = IQ_FORMAT) -> tuple[np.ndarray, int | None]: + """Читает файловый IQ; `cs8` сохраняет исходный файл без изменений.""" + size = path.stat().st_size + if iq_format == "c64le": + if size == 0 or size % 8: + raise ValueError("c64le должен содержать целое ненулевое число отсчётов") + return np.memmap(path, dtype=" dict[str, int | float | str]: + """Параметры номинального режима, которые разрешено знать приёмнику.""" + return { + "modulation": "QPSK", + "symbol_rate": SYMBOL_RATE, + "sample_rate": SAMPLE_RATE_HZ, + "fec_rate": "2/3", + "digital_carrier_offset_hz": NOMINAL_CARRIER_HZ, + } diff --git a/tools/qpsk_rf_rx.py b/tools/qpsk_rf_rx.py new file mode 100644 index 0000000..546309d --- /dev/null +++ b/tools/qpsk_rf_rx.py @@ -0,0 +1,302 @@ +"""RF-001: файловый IQ → существующий VIDEO-005 receiver → отчёт после CRC.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +import statistics +import sys +from typing import Any + +import numpy as np +from scipy.signal import oaconvolve + +if __package__ in (None, ""): + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from protocol.packet import build_packet, parse_packet +from protocol import punctured_fec +from protocol.video003_modem import ( + PHY_HEADER_SYMBOLS, PREAMBLE_SYMBOLS, SAMPLE_RATE_HZ, receive_frame, +) +from protocol.video005_carrier import ( + correct_pilot_estimate, estimate_from_preamble, track_qpsk_body, +) +from protocol.video005_iq_waveform import NOMINAL_CARRIER_HZ, SYMBOL_RATE, rrc_taps +from protocol.video005_receiver import IqStreamResult, receive_iq_stream +from protocol.video005_timing import sample_symbols +from tools.qpsk_rf_common import ( + GAP_MS, PACKET_COUNT, PAYLOAD_BYTES, SEED, TEST_ID, + payload_for_sequence, read_iq_file, sha256_file, +) + + +def _bit_errors(observed: bytes, expected: bytes) -> tuple[int, int]: + """Считает ошибки только для сопоставленных пакетов равной длины.""" + if len(observed) != len(expected): + return 0, 0 + return sum((left ^ right).bit_count() for left, right in zip( + observed, expected, strict=True + )), len(expected) * 8 + + +def _raw_ber_after_decode( + iq: np.ndarray, + reception: IqStreamResult, + seed: int, + decoder_backend: str, +) -> tuple[int, int, int]: + """Повторяет штатные PHY-функции для BER только после строгого первого приёма. + + Метрика относится к CRC-valid кандидатам. Границы отвергнутых кандидатов + VIDEO-005 не возвращает; их биты здесь не выдумываются. + """ + period = round(SAMPLE_RATE_HZ / NOMINAL_CARRIER_HZ) + cycle = np.exp(-2j * np.pi * np.arange(period) / period).astype(np.complex64) + baseband = np.asarray(iq, dtype=np.complex64) * np.resize(cycle, iq.size) + matched = oaconvolve(baseband, rrc_taps().astype(np.float32), mode="full") + if decoder_backend == "numba": + from protocol.video004_viterbi import decode_soft as fec_decoder + else: + fec_decoder = punctured_fec.decode_soft + bit_errors = 0 + bits_compared = 0 + replay_failures = 0 + for packet in reception.packets: + sequence_number = parse_packet(packet.packet).sequence_number + coded_count = punctured_fec.coded_bit_count(len(packet.packet) * 8, "2/3") + symbol_count = PREAMBLE_SYMBOLS + PHY_HEADER_SYMBOLS + (coded_count + 1) // 2 + symbols = sample_symbols(matched, packet.start_sample, 16, symbol_count) + if len(symbols) != symbol_count: + replay_failures += 1 + continue + estimate = estimate_from_preamble(symbols, SYMBOL_RATE) + corrected = correct_pilot_estimate(symbols, estimate) + tracked, _ = track_qpsk_body(corrected) + replay = receive_frame(tracked, "QPSK", "2/3", SYMBOL_RATE, + maximum_start=0, fec_decoder=fec_decoder) + if replay.packet != packet.packet or replay.coded_soft is None: + replay_failures += 1 + continue + from protocol.video003_modem import build_frame + expected_bits = build_frame(payload_for_sequence(seed, sequence_number), + "QPSK", "2/3", sequence_number).coded_bits + observed_bits = (replay.coded_soft >= 0).astype(np.uint8) + if len(observed_bits) != len(expected_bits): + replay_failures += 1 + continue + bit_errors += int(np.count_nonzero(observed_bits != expected_bits)) + bits_compared += len(expected_bits) + return bit_errors, bits_compared, replay_failures + + +def summarize_result( + reception: IqStreamResult, + *, + iq_sample_count: int, + iq_sha256: str, + iq_format: str, + clipping_codes: int | None, + truth: dict[str, Any] | None, + raw_bits: tuple[int, int, int] | None = None, +) -> dict[str, Any]: + """Выполняет сравнение с эталоном только после завершения IQ-приёма.""" + rows = [] + observed_by_sequence: dict[int, bytes] = {} + for packet in reception.packets: + parsed = parse_packet(packet.packet) + observed_by_sequence.setdefault(parsed.sequence_number, packet.packet) + rows.append({ + "sequence_number": parsed.sequence_number, + "packet_sha256": hashlib.sha256(packet.packet).hexdigest(), + "payload_sha256": hashlib.sha256(parsed.payload).hexdigest(), + "start_sample": packet.start_sample, + "preamble_score": packet.preamble_score, + "cfo_hz": packet.carrier_offset_hz, + "phase_rad": packet.initial_phase_rad, + "timing_phase_symbols": packet.timing_phase_symbols, + "qpsk_quadrant": packet.quadrant, + "tracking_error_rms_rad": packet.tracking_error_rms_rad, + }) + sequences = [row["sequence_number"] for row in rows] + unique_sequences = set(sequences) + cfo = [row["cfo_hz"] for row in rows] + report: dict[str, Any] = { + "rf001_status": "PREPARED_NOT_RUN", + "test_id": truth.get("test_id") if truth is not None else None, + "modulation": "QPSK", + "symbol_rate": 500_000, + "inner_fec": "2/3", + "payload_bytes": 256, + "inter_packet_gap_ms": 5, + "tx_packets": truth.get("packet_count") if truth is not None else None, + "iq_format": iq_format, + "iq_sample_count": iq_sample_count, + "iq_sha256": iq_sha256, + "detected_preambles": reception.candidate_count, + "rejected_candidates": reception.rejected_candidates, + "decoded_packets": len(rows), + "crc_valid_packets": len(rows), + "packet_sequences": sequences, + "duplicates": len(sequences) - len(unique_sequences), + "missing_packets": None, + "exact_packets": None, + "packet_success_rate": None, + "raw_ber": None, + "raw_ber_scope": "UNAVAILABLE_FOR_REJECTED_CANDIDATES_FROM_VIDEO005_API", + "raw_bit_errors_on_crc_valid_packets": None, + "raw_bits_compared_on_crc_valid_packets": None, + "raw_ber_on_crc_valid_packets": None, + "raw_ber_replay_failures": None, + "post_fec_bit_errors": None, + "post_fec_bits_compared": None, + "post_fec_error_rate": None, + "post_fec_error_rate_on_compared_packets": None, + "post_fec_uncomparable_packets": None, + "cfo_median_hz": statistics.median(cfo) if cfo else None, + "cfo_p95_absolute_hz": float(np.percentile(np.abs(cfo), 95)) if cfo else None, + "phase_error_rad": None, + "timing_offset_symbols": None, + "phase_and_timing_estimates": rows, + "tx_underruns": "NOT_RUN", + "rx_overruns": "NOT_RUN", + "rx_clipping_rail_codes": clipping_codes if clipping_codes is not None else "NOT_APPLICABLE", + "common_10mhz_clock": "NOT_RUN", + "sdr_hardware": "NOT_ACCESSED", + "rf_tx": 0, + } + if truth is None: + return report + expected_rows = truth["expected_packet_hashes"] + expected = {int(item["sequence_number"]): item["packet_sha256"] + for item in expected_rows} + if (len(expected) != len(expected_rows) or len(expected) != truth["packet_count"] + or set(expected) != set(range(PACKET_COUNT))): + raise ValueError("ведомость содержит повторные или недостающие номера") + exact = { + row["sequence_number"] for row in rows + if expected.get(row["sequence_number"]) == row["packet_sha256"] + } + missing = sorted(expected.keys() - unique_sequences) + seed = int(truth["seed"]) + if (truth["packet_count"] != PACKET_COUNT + or truth["application_payload_bytes"] != PAYLOAD_BYTES + or truth["seed"] != SEED): + raise ValueError("ведомость не соответствует каноническому RF-001") + for sequence_number, digest in expected.items(): + reference = build_packet(payload_for_sequence(seed, sequence_number), + message_type=3, sequence_number=sequence_number) + if hashlib.sha256(reference).hexdigest() != digest: + raise ValueError("эталонный SHA пакета не совпадает с seed") + errors = 0 + compared = 0 + uncomparable = 0 + for sequence_number, observed in observed_by_sequence.items(): + if sequence_number not in expected: + continue + reference = build_packet(payload_for_sequence(seed, sequence_number), + message_type=3, sequence_number=sequence_number) + packet_errors, packet_bits = _bit_errors(observed, reference) + if packet_bits == 0: + uncomparable += 1 + errors += packet_errors + compared += packet_bits + complete_post_fec = (unique_sequences == expected.keys() + and len(sequences) == truth["packet_count"] + and uncomparable == 0) + report.update({ + "missing_packets": missing, + "unexpected_sequences": sorted(unique_sequences - expected.keys()), + "exact_packets": len(exact), + "packet_success_rate": len(exact) / truth["packet_count"], + "post_fec_bit_errors": errors, + "post_fec_bits_compared": compared, + "post_fec_error_rate": errors / compared if compared and complete_post_fec else None, + "post_fec_error_rate_on_compared_packets": errors / compared if compared else None, + "post_fec_uncomparable_packets": uncomparable, + "same_bytes_as_tx_waveform": iq_sha256 == truth.get("iq_sha256"), + }) + if raw_bits is not None: + raw_errors, raw_count, replay_failures = raw_bits + conditional_ber = raw_errors / raw_count if raw_count else None + complete = (unique_sequences == expected.keys() + and len(sequences) == truth["packet_count"] + and replay_failures == 0) + report.update({ + "raw_bit_errors_on_crc_valid_packets": raw_errors, + "raw_bits_compared_on_crc_valid_packets": raw_count, + "raw_ber_on_crc_valid_packets": conditional_ber, + "raw_ber_replay_failures": replay_failures, + "raw_ber": conditional_ber if complete else None, + "raw_ber_scope": ("ALL_EXPECTED_PACKETS" if complete else + "CRC_VALID_PACKETS_ONLY; FULL_RAW_BER_UNAVAILABLE"), + }) + return report + + +def decode_file( + iq_path: Path, + *, + iq_format: str = "c64le", + truth_metadata_path: Path | None = None, + decoder_backend: str = "numba", +) -> dict[str, Any]: + """Открывает только файл; truth читается после вызова рабочего приёмника.""" + iq, clipping_codes = read_iq_file(iq_path, iq_format) + reception = receive_iq_stream(iq, decoder_backend=decoder_backend) + truth = (json.loads(truth_metadata_path.read_text(encoding="utf-8")) + if truth_metadata_path is not None else None) + if truth is not None and ( + truth.get("schema_version") != 1 + or truth.get("test_id") != TEST_ID + or truth.get("modulation") != "QPSK" + or truth.get("symbol_rate") != SYMBOL_RATE + or truth.get("sample_rate") != SAMPLE_RATE_HZ + or truth.get("fec_rate") != "2/3" + or truth.get("digital_carrier_offset_hz") != NOMINAL_CARRIER_HZ + or truth.get("packet_count") != PACKET_COUNT + or truth.get("application_payload_bytes") != PAYLOAD_BYTES + or truth.get("inter_packet_gap_ms") != GAP_MS + or truth.get("seed") != SEED + ): + raise ValueError("TX-ведомость не соответствует режиму RF-001") + raw_bits = (_raw_ber_after_decode(iq, reception, int(truth["seed"]), decoder_backend) + if truth is not None else None) + return summarize_result( + reception, + iq_sample_count=int(iq.size), + iq_sha256=sha256_file(iq_path), + iq_format=iq_format, + clipping_codes=clipping_codes, + truth=truth, + raw_bits=raw_bits, + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--iq-file", required=True, type=Path) + parser.add_argument("--iq-format", choices=("c64le", "cs8"), default="c64le") + parser.add_argument("--truth-metadata", type=Path) + parser.add_argument("--report", required=True, type=Path) + parser.add_argument("--numba-packages", type=Path) + arguments = parser.parse_args() + if arguments.report.exists(): + parser.error("отчёт уже существует; перезапись запрещена") + if arguments.numba_packages is not None: + if not arguments.numba_packages.is_dir(): + parser.error("каталог пакетов Numba отсутствует") + sys.path.insert(0, str(arguments.numba_packages.resolve())) + result = decode_file(arguments.iq_file, iq_format=arguments.iq_format, + truth_metadata_path=arguments.truth_metadata) + arguments.report.parent.mkdir(parents=True, exist_ok=True) + arguments.report.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8") + print(f"RF-001 FILE RX: CRC={result['crc_valid_packets']}; " + f"exact={result['exact_packets']}; RF TX=0") + + +if __name__ == "__main__": + main() diff --git a/tools/qpsk_rf_tx.py b/tools/qpsk_rf_tx.py new file mode 100644 index 0000000..dea3178 --- /dev/null +++ b/tools/qpsk_rf_tx.py @@ -0,0 +1,107 @@ +"""RF-001: построение 100 QPSK-пакетов и IQ только в режиме dry-run.""" + +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +import hashlib +import json +from pathlib import Path +import sys + +import numpy as np + +if __package__ in (None, ""): + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from protocol.video003_modem import SAMPLE_RATE_HZ, build_frame +from protocol.video005_iq_waveform import GUARD_SAMPLES, shape_packet +from tools.qpsk_rf_common import ( + GAP_MS, + GAP_SAMPLES, + IQ_FORMAT, + PACKET_COUNT, + PAYLOAD_BYTES, + SEED, + TEST_ID, + payload_for_sequence, + radio_mode, + sha256_file, +) + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_OUTPUT = ROOT / "data/raw/rf001/offline_qpsk_100_packets" + + +def build_dry_run() -> tuple[np.ndarray, dict]: + """Собирает ровно утверждённый режим; границы остаются только в TX-ведомости.""" + separator = GAP_SAMPLES - 2 * GUARD_SAMPLES + if separator < 0: + raise AssertionError("пауза короче двух защитных интервалов RRC") + waveforms = [] + expected = [] + starts = [] + position = 0 + for sequence_number in range(PACKET_COUNT): + payload = payload_for_sequence(SEED, sequence_number) + frame = build_frame(payload, "QPSK", "2/3", sequence_number=sequence_number) + waveform = shape_packet(frame) + starts.append(position) + waveforms.append(waveform) + expected.append({ + "sequence_number": sequence_number, + "payload_sha256": hashlib.sha256(payload).hexdigest(), + "packet_sha256": hashlib.sha256(frame.packet).hexdigest(), + }) + position += waveform.size + (separator if sequence_number < PACKET_COUNT - 1 else 0) + iq = np.zeros(position, dtype=" tuple[Path, Path]: + """Создаёт новый каталог; существующие IQ и метаданные не перезаписывает.""" + output_directory.mkdir(parents=True, exist_ok=False) + iq, metadata = build_dry_run() + iq_path = output_directory / "tx_waveform.c64le" + metadata_path = output_directory / "tx_metadata.json" + iq.tofile(iq_path) + metadata["iq_sha256"] = sha256_file(iq_path) + metadata_path.write_text(json.dumps(metadata, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8") + return iq_path, metadata_path + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT) + arguments = parser.parse_args() + iq_path, metadata_path = write_dry_run(arguments.output_dir) + print(f"RF-001 DRY-RUN: {iq_path}; metadata={metadata_path}; RF TX=0") + + +if __name__ == "__main__": + main()