import struct,json,argparse
from pathlib import Path
FIELDS=['magic','version','epoch','state','fault','count','zero_a_q8','zero_b_q8','sense_a_ma','sense_b_ma','reconstructed_c_ma','last_sequence','last_observed_us','flags','limit_ma','adc_reference_uv','drv_reference_uv','peak_a_counts','peak_b_counts','scale_ma_per_count_q16']
def decode(data,epoch_before,epoch_after):
 if len(data)!=80:raise ValueError('requires exactly 80 bytes')
 d=dict(zip(FIELDS,struct.unpack('<8I3i9I',data)))
 if d['magic']!=0x43513833 or d['version']!=1:raise ValueError('not an initialized A83 diagnostic')
 if epoch_before!=epoch_after or epoch_before!=d['epoch'] or not epoch_before or epoch_before&1:raise ValueError('two matching even epochs must bracket capture')
 if d['state'] not in (1,2,3) or d['flags']&~7 or d['count']>256:raise ValueError('invalid state/count/flags')
 if d['state']==3:
  if not 1<=d['fault']<=8 or d['flags']&2:raise ValueError('invalid failure record')
 else:
  if d['fault'] or not 1<=d['limit_ma']<=2000 or not 2800000<=d['adc_reference_uv']<=3600000 or not 3135000<=d['drv_reference_uv']<=3465000:raise ValueError('invalid candidate profile')
  scale=(d['adc_reference_uv']*1000*65536+614250000)//1228500000
  if d['scale_ma_per_count_q16']!=scale:raise ValueError('invalid nominal scale')
  if d['peak_a_counts']>16 or d['peak_b_counts']>16:raise ValueError('invalid accepted zero noise')
  if d['state']==1 and (d['count']>=256 or d['flags']):raise ValueError('zeroing cannot claim convergence')
  if d['state']==2:
   if d['count']!=256 or d['flags'] not in (1,7):raise ValueError('not converged')
   if not (0<d['zero_a_q8']<4095*256 and 0<d['zero_b_q8']<4095*256):raise ValueError('invalid zero')
   if d['flags']&2:
    if d['reconstructed_c_ma']!=-d['sense_a_ma']-d['sense_b_ma'] or any(abs(d[k])>d['limit_ma'] for k in ['sense_a_ma','sense_b_ma','reconstructed_c_ma']):raise ValueError('invalid accepted nominal current')
 return {**d,'software_candidate_valid':d['state']==2 and d['flags']==7,'physical_calibration_verified':False,'gain_accuracy_verified':False,'actual_hardware_source_proven':False,'independent_current_cutoff_qualified':False,'third_phase_independently_measured':False,'motor_run_permission':False,'scope':'nominal low-side sensed currents; direction, CSA cross-correction and real aperture not qualified'}
if __name__=='__main__':
 p=argparse.ArgumentParser();p.add_argument('file');p.add_argument('--epoch-before',type=int,required=True);p.add_argument('--epoch-after',type=int,required=True);a=p.parse_args();print(json.dumps(decode(Path(a.file).read_bytes(),a.epoch_before,a.epoch_after),indent=2))
