Files
AMI/docs/evidence/2026-10-07/as-status-flash/153558/accept_as_hardware.py
T

109 lines
7.3 KiB
Python

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('<I',int(word,16))):mem[a+i]=b
a+=4
def val(n,l=4):return int.from_bytes(bytes(mem[S[n]+i] for i in range(l)),'little')
return dict(states=list(mem[S['vcu_display_state']+i] for i in range(4)),timestamp=val('vcu_status_last_rx_ms'),received=val('vcu_status_received',1),can_errors=val('can_rx_error_count'),uart_errors=val('uart_rx_error_count'),task_sequence=val('task_sequence',1),task_head=val('task_head',1),task_tail=val('task_tail',1))
frames=[];tx=[];uart=bytearray();checks=[];errors=[];stop=threading.Event();payload=None
def set_payload(data,**kw):
global payload
payload=can.Message(arbitration_id=0x183,is_extended_id=False,data=bytes(data),**kw)
def commands(start=0):
data=bytes(uart[start:])
return [p.decode('ascii') for p in data.split(b'\xff\xff\xff')[:-1] if p and all(32<=b<=126 for b in p)]
def record(name,expected,wait=.4):
time.sleep(wait);snap=memory(name)
assert snap['states']==expected,(name,expected,snap)
assert snap['can_errors']==snap['uart_errors']==0 and bus.status()==0 and not errors,(name,snap,bus.status(),errors)
checks.append(dict(name=name,expected=expected,snapshot=snap,passed=True));print(name,'passed',flush=True)
return snap
with can.Bus(interface='pcan',channel='PCAN_USBBUS1',bitrate=500000) as bus, serial.Serial('COM6',9600,timeout=.05) as port:
assert bus.reset()
reset=subprocess.run([CLI,'-c','port=SWD','sn=37FF71064E573436188C1543','mode=HOTPLUG','freq=1000','-rst'],capture_output=True,timeout=15)
(O/'hardware-reset-active-can.log').write_bytes(reset.stdout+reset.stderr)
assert reset.returncode==0
end=time.monotonic()+3
while time.monotonic()<end:
m=bus.recv(.15)
if m:frames.append(dict(id=hex(m.arbitration_id),data=m.data.hex(' '),timestamp=m.timestamp))
(O/'can-baseline.json').write_text(json.dumps(dict(bitrate=500000,status=bus.status(),frames=frames),indent=2))
heartbeat=[m for m in frames if m['id']=='0x301']
assert len(heartbeat)>=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')