import os,json,re,struct,subprocess,threading,time from pathlib import Path R=Path('D:/YMR/AMI');O=Path((R/'tmp/as-flash-evidence-path.txt').read_text()) DLL=R/'tmp/can1-test-20261002/pcan-basic/x64';os.environ['PATH']=str(DLL)+os.pathsep+os.environ['PATH'];dh=os.add_dll_directory(str(DLL)) import can,serial CLI='D:/STM32CubeCLT_1.19.0/STM32CubeProgrammer/bin/STM32_Programmer_CLI.exe' NM='D:/STM32CubeCLT_1.19.0/GNU-tools-for-STM32/bin/arm-none-eabi-nm.exe' nm=subprocess.check_output([NM,'-n',str(R/'firmware/build/Release/AMI_Controller.elf')]).decode().replace('\r','') S={n:int(a,16) for a,k,n in re.findall(r'^(\w+)\s+(\w)\s+(\w+)$',nm,re.M)} names=['vcu_display_state','vcu_status_last_rx_ms','vcu_status_received','can_rx_error_count','uart_rx_error_count','task_sequence','task_head','task_tail','task_queue','parser_pos','screen_tx_busy'] base=min(S[n] for n in names)&~3 length=((max(S[n]+4 for n in names)-base+3)//4)*4 def memory(name): p=subprocess.run([CLI,'-c','port=SWD','sn=37FF71064E573436188C1543','mode=HOTPLUG','freq=1000','-r32',hex(base),str(length)],capture_output=True,timeout=15) (O/(name+'-ram.log')).write_bytes(p.stdout+p.stderr);assert p.returncode==0 mem={} for a,words in re.findall(r'0x([0-9A-Fa-f]+)\s*:\s*((?:[0-9A-Fa-f]{8}\s*)+)',p.stdout.decode(errors='replace')): a=int(a,16) for word in words.split(): for i,b in enumerate(struct.pack('=10 and bus.status()==0,('CAN1 500K baseline heartbeat missing',len(heartbeat),bus.status()) print('CAN1 500K baseline:',len(heartbeat),'heartbeats, status 0',flush=True) def reader(): try: while not stop.is_set():uart.extend(port.read(512)) except Exception as e:errors.append(repr(e)) def sender(): try: while not stop.is_set(): msg=payload if msg:bus.send(msg,timeout=.15);tx.append(dict(id=hex(msg.arbitration_id),data=msg.data.hex(' '),monotonic=time.monotonic())) stop.wait(.08) except Exception as e:errors.append(repr(e)) def receiver(): try: while not stop.is_set(): m=bus.recv(.1) if m:frames.append(dict(id=hex(m.arbitration_id),data=m.data.hex(' '),timestamp=m.timestamp)) except Exception as e:errors.append(repr(e)) ts=[threading.Thread(target=fn) for fn in (reader,sender,receiver)] for t in ts:t.start() try: for state in range(6): start=len(uart);set_payload([2,1,1,15,state,state,0,2]) record('as_'+str(state),[2,1,1,state],.65) expected='as_state='+str(state) assert expected in commands(start),('physical UART state missing',state,commands(start)) checks[-1]['physical_uart']=expected for flags in range(16): set_payload([2,1,1,flags,255,1,0,2]) record('valid_'+str(flags),[2 if flags&1 else 7,1 if flags&2 else 0,1 if flags&4 else 0,1 if flags&8 else 6]) set_payload([2,1,1,15,0,4,0,2]);record('before_legacy',[2,1,1,4]) start=len(uart);set_payload([2,1,1,7,0,0,0,1]);record('legacy_clears_as',[2,1,1,6],.65) assert 'as_state=6' in commands(start) for label,data in [('bad_as',[2,1,1,15,0,6,0,2]),('bad_flag',[2,1,1,31,0,1,0,2]),('reserved',[2,1,1,15,0,1,1,2]),('bad_version',[2,1,1,15,0,1,0,3])]: set_payload([2,1,1,15,0,1,0,2]);record(label+'_baseline',[2,1,1,1]) payload=None;time.sleep(.1);before=memory(label+'_stopped') start=len(uart);set_payload(data);after=record(label+'_expired',[7,0,0,6],.8) assert after['timestamp']==before['timestamp'] assert 'as_state=6' in commands(start) set_payload([2,1,1,15,0,1,0,2]);record('before_timeout',[2,1,1,1]) start=len(uart);payload=can.Message(arbitration_id=0x6a0,is_extended_id=False,data=bytes.fromhex('A1 01 00 00 00 00 00 00')) record('other_can_cannot_renew',[7,0,0,6],.8) assert all(c in commands(start) for c in ['current_task=7','lock_state=0','ebs_state=0','as_state=6']) set_payload([2,1,1,15,0,1,0,2]);record('recovery',[2,1,1,1],.65) before=memory('before_uart_request');port.write(bytes.fromhex('AA 55 01 05 0D 0A'));port.flush() snap=record('physical_uart_task_request',[2,1,1,1],.65) requests=[m for m in frames if m['id']=='0x300' and bytes.fromhex(m['data'])[1]==5] assert requests and snap['task_sequence']!=before['task_sequence'],('UART RX to CAN request missing',requests,before,snap) checks[-1]['can_request']=requests[-1] print('UART COM6 -> MCU -> real CAN 0x300 verified',flush=True) payload=None;record('final_unknown',[7,0,0,6],.8) finally: payload=None;stop.set() for t in ts:t.join(timeout=2) (O/'hardware-uart.bin').write_bytes(uart) (O/'hardware-uart-commands.json').write_text(json.dumps(commands(),indent=2)) (O/'hardware-can.json').write_text(json.dumps(dict(received=frames,sent=tx),indent=2)) result=dict(result='passed' if len(checks)==37 and not errors else 'partial',scope='CAN1 500K -> actual MCU RAM -> physical USART2 COM6; physical UART task RX -> CAN1 0x300; physical screen not tested',checks=checks,errors=errors) (O/'hardware-results.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') print('Physical checks:',len(checks),'completed')