feat: I2C-Lab v0.1.0 代码入库——RP2040 PIO 被动监控 probe 固件(pico-sdk C, 250MHz 双核) + Fluent 上位机(事务流/波形快照/CSV/演示模式);release/ 交付物(exe/UF2/BIN/zip)不入库、本地保留待真机验收发布;根 README 工具表与 agent.md 登记;I2C-Inject README 标注后续并入 I2C-Lab;移除误登记的 OBI-1/ui-style-demo 相关内容
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
"""GUI 功能自检(offscreen 无窗口): 事务入表/地址过滤/暂停滚动/波形喂数/
|
||||
CSV 导出池/固件统计行/配置往返
|
||||
====================================================================
|
||||
不依赖硬件与串口, 直接驱动 Window 的事件处理函数; 改 main.py 后建议重跑。
|
||||
|
||||
用法: python selftest_gui.py (全部通过退出码 0)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ['QT_QPA_PLATFORM'] = 'offscreen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
import i2cdec
|
||||
import main as m
|
||||
from i2cdec import Transaction
|
||||
|
||||
fails = []
|
||||
|
||||
|
||||
def check(name, got, want):
|
||||
ok = got == want
|
||||
print(('PASS' if ok else 'FAIL'), name, '->', repr(got)[:120],
|
||||
'' if ok else f'(期望 {want!r})')
|
||||
if not ok:
|
||||
fails.append(name)
|
||||
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
win = m.Window()
|
||||
win.show()
|
||||
|
||||
T = Transaction
|
||||
|
||||
# 1) 事务入表 + 全量池
|
||||
tr1 = T(t0_us=100, t1_us=200, addr7=0x32, read=False,
|
||||
acks=(True, True), data=b'\xff\x60', ended='stop')
|
||||
tr2 = T(t0_us=900, t1_us=1100, addr7=0x50, read=True,
|
||||
acks=(True, False), data=b'\x12', ended='stop')
|
||||
win._paused = False
|
||||
for tr in (tr1, tr2):
|
||||
win._on_trans(tr)
|
||||
check('表格行数', win.table.rowCount(), 2)
|
||||
check('导出池数量', len(win.transactions), 2)
|
||||
check('事务计数', win._trans_total, 2)
|
||||
check('地址列', win.table.item(0, 3).text(), '0x32')
|
||||
check('RW 列', win.table.item(1, 4).text(), 'R')
|
||||
check('ACK 列', win.table.item(0, 5).text(), 'AA')
|
||||
check('数据列', win.table.item(0, 6).text(), 'FF 60')
|
||||
check('收尾列', win.table.item(0, 7).text(), 'STOP')
|
||||
|
||||
# 2) 地址过滤: 只留 0x32; 非法过滤项被忽略
|
||||
win.ed_filter.setText('32 zz')
|
||||
win._on_trans(tr2)
|
||||
check('过滤生效(0x50 不入表)', win.table.rowCount(), 2)
|
||||
check('计数仍累计', win._trans_total, 3)
|
||||
win.ed_filter.setText('')
|
||||
win._on_trans(tr2)
|
||||
check('清过滤后入表', win.table.rowCount(), 3)
|
||||
|
||||
# 3) 地址 NAK 标红 / 未收尾标橙(前景色判断)
|
||||
tr_nak = T(t0_us=1, t1_us=2, addr7=0x10, read=False, acks=(False,),
|
||||
data=b'', ended='stop')
|
||||
tr_cut = T(t0_us=3, t1_us=9, addr7=0x11, read=False, acks=(True,),
|
||||
data=b'', ended='cut')
|
||||
win._on_trans(tr_nak)
|
||||
win._on_trans(tr_cut)
|
||||
r_nak = win.table.rowCount() - 2
|
||||
check('地址 NAK 前景红色',
|
||||
win.table.item(r_nak, 5).foreground().color().name(), '#e0564c')
|
||||
check('cut 收尾前景橙色',
|
||||
win.table.item(r_nak + 1, 7).foreground().color().name(), '#e08a2e')
|
||||
|
||||
# 4) 长事务截断显示 + tooltip 全量
|
||||
tr_long = T(t0_us=1, t1_us=2, addr7=0x2a, read=False, acks=(True,) * 20,
|
||||
data=bytes(range(20)), ended='stop')
|
||||
win._on_trans(tr_long)
|
||||
r = win.table.rowCount() - 1
|
||||
check('长数据显示截断省略', win.table.item(r, 6).text().endswith('…'), True)
|
||||
check('长数据 tooltip 全量',
|
||||
win.table.item(r, 6).toolTip() == tr_long.data_hex(), True)
|
||||
|
||||
# 5) 暂停滚动: 不再 scrollToBottom, 但行照加
|
||||
win._toggle_pause()
|
||||
check('暂停按钮文案', win.btn_pause.text(), '继续滚动')
|
||||
win._on_trans(tr1)
|
||||
check('暂停时行照加', win.table.rowCount() >= 6, True)
|
||||
win._toggle_pause()
|
||||
|
||||
# 6) 表格行数上限: 批量灌 5100 条, 顶行被裁
|
||||
win._clear_table()
|
||||
for i in range(5100):
|
||||
win._on_trans(T(t0_us=i, t1_us=i + 1, addr7=0x01, read=False,
|
||||
acks=(True,), data=b'', ended='stop'))
|
||||
check('表格行数封顶', win.table.rowCount(), m.TABLE_CAP)
|
||||
check('导出池保留全部(<STORE_CAP)', len(win.transactions), win._trans_total)
|
||||
check('导出池未达上限', len(win.transactions) <= m.STORE_CAP, True)
|
||||
|
||||
# 7) 波形喂数 + 注记解码
|
||||
fam = [T(addr7=0x32, read=False, acks=(True, True, True),
|
||||
data=bytes((0xFF, 0x60)), ended='stop')]
|
||||
edges, _refs = i2cdec.encode_edges(fam, khz=100.0, t0_us=1000)
|
||||
win._on_wave(edges, 0)
|
||||
check('波形有数据', win.wave.has_data(), True)
|
||||
check('波形注记解码 1 事务', len(win.wave._trans), 1)
|
||||
check('波形快照标签', win.lb_wave.text(),
|
||||
f'{len(edges)} 边沿, 解码 1 事务')
|
||||
|
||||
# 8) 固件 stats 行解析
|
||||
win._on_info('= stats edges=1234 trans=56 trans_drop=0 susp=0 '
|
||||
'ring_ovf=0 last_edge_ago_us=12 mon=1')
|
||||
check('固件计数显示', win.lb_fw.text().startswith('固件计数: edges=1234'), True)
|
||||
|
||||
# 9) 配置往返
|
||||
win.ed_filter.setText('32 50')
|
||||
win.spin_wave_n.setValue(4096)
|
||||
cfg = win._store_from_ui()
|
||||
check('配置: 过滤', cfg['mon']['addr_filter'], '32 50')
|
||||
check('配置: 波形边沿数', cfg['mon']['wave_n'], 4096)
|
||||
config_path = m.config.CONFIG_FILE
|
||||
try:
|
||||
os.remove(config_path)
|
||||
except OSError:
|
||||
pass
|
||||
m.config.save(cfg)
|
||||
check('配置落盘读回', m.config.load()['mon']['wave_n'], 4096)
|
||||
with open(config_path, 'w', encoding='utf-8') as f: # 损坏文件容错
|
||||
f.write('{not json')
|
||||
check('配置容错(损坏回落)', m.config.load()['mon']['addr_filter'], '')
|
||||
check('配置容错(缺键补齐)', m.config.load()['mon']['wave_n'],
|
||||
m.config.DEFAULTS['mon']['wave_n'])
|
||||
|
||||
# 10) 演示模式一键开停(不开定时器轮询, 仅验证状态翻转)
|
||||
win._toggle_demo()
|
||||
check('演示启动', win._demo_timer.isActive(), True)
|
||||
win._demo_tick()
|
||||
win._drain_events()
|
||||
check('演示 tick 产生事务', win._trans_total > 0, True)
|
||||
win._toggle_demo()
|
||||
check('演示停止', not win._demo_timer.isActive(), True)
|
||||
|
||||
win.close()
|
||||
app.quit()
|
||||
|
||||
print()
|
||||
if fails:
|
||||
print(f'共 {len(fails)} 项失败:', ', '.join(fails))
|
||||
sys.exit(1)
|
||||
print('全部通过')
|
||||
Reference in New Issue
Block a user