feat: add AS status feedback for both HMI sizes with verified v2.7 release
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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')
|
||||
Reference in New Issue
Block a user