"""Bounded equal-symbol-time mixed SF/BW validation, strict channel identity. Ten fixed-frequency controls per profile, RX-only timing, then 100 packets per channel at the measured near-ceiling settling delay. If that fails, test zero added delay once; stop at the first 400/400 pass. No RF retransmissions. """ import argparse from concurrent.futures import ThreadPoolExecutor import json from pathlib import Path import random import secrets import time from profile_switch import exchange, read_packet_response from profile_switch_packets import open_port from profile_switch_dwell import reboot_boards from profile_switch_channels import capacity_levels, reception_validity from profile_switch_rollback import settling_budget, PREAMBLE_US, DWELL_US from profile_stationary_baseline import check_status from profile_preamble_diagnostic import check_word PROFILES = [dict(sf=9+i,bw_khz=62.5*(2**i)) for i in range(4)] def check_plan(reply): if reply.get('mixed_profiles')!=1 or reply.get('symbol_us')!=8192 or reply.get('profiles')!=PROFILES: raise RuntimeError('Different or unsupported mixed modulation plan') def check_scan(status, settle): n=status['switch']['n'] if (not n or status.get('mixed_profiles') is not True or status.get('accept_offchannel') is not False or status.get('rollback')!=0 or status.get('rx_gain_reg')!=0x94 or status.get('channel_step_khz')!=1000 or status.get('settle_us')!=settle or status.get('frequency_repeat') is not True or status.get('modulation_cache') is not True or status['rf_commands']!=2*n or status['modulation_commands']!=n or status['optimized_rx_resumes']!=n or any(status[k] for k in ('failures','rx_mode_errors','cache_errors','rx_errors'))): raise RuntimeError('Mixed scan configuration/command counts/hardware mismatch') if settle and (status['settling']['n']!=n or status['settling']['min_us']=PREAMBLE_US: raise RuntimeError('Near-ceiling control does not fit measured preamble budget') for settle in dict.fromkeys((ceiling,0)): configure_scan(settle) scan=dict(settle_us=settle,complete=False) result['scans'].append(scan);save() def checkpoint(progress): scan.update(progress);save() def probe(channel,pause_ms): s=seq();armed=exchange(rx,f'scanexpect {channel} {s}','listening',2,s,cached=True) if armed.get('channel')!=channel or not armed.get('scanning'): raise RuntimeError('Scan expectation mismatch') time.sleep(pause_ms/1000);sent,received=transmit(channel,s) strict,valid=reception_validity(received,channel) print(f'Mixed +{settle} us ch{channel}: {"received" if valid else "MISSED"}',flush=True) return dict(sequence=s,valid=valid,strict_valid=strict,sent=sent,received=received,armed=armed) capacity_levels(start_scan,probe,lambda:status(settle),checkpoint,100,4,606125,DWELL_US,settle) if scan['stopped']=='channel_limit_without_miss': result.update(complete=True,stopped='first_400_of_400_pass',passed_settle_us=settle);return result.update(complete=True,stopped='both_delay_controls_failed') except Exception as error: result.update(complete=False,stopped='fixture_error',error=str(error));raise finally: close() try: reboot_boards((args.receiver,args.sender)) except Exception as error: result['cleanup_error']=str(error) save() if __name__=='__main__': main()