ui_w_soe 组件(task-14 落)此前两端都没绑 reader,只能显示"无记录"空态。本批补齐三件事:
App 侧只读遍历、两端 reader 绑定、SOE 记录页与菜单注册。
App/log/soe_store 只读 API(Lead 已放行,本批独占这两个文件)
- soe_store_count() / soe_store_read(idx, soe_rec_t*):与 syslog_count /
syslog_get_record 同构——8 条一窗(512 B)整块读、命中窗口零 I/O、卷锁 try(0 超时)
非阻塞;定长记录逐条 open 等于同一扇区开 4 次,SPI NAND 上这个差距是肉眼可见的卡顿
- 读者不得看到半成品记录,三道保障缺一不可(规则写进 soe_store.h 头注,不只是提交信息):
① 条数只数整条 64 B 槽(写者留下的半条尾槽天然不计入);② 窗口代数:写者每次成功落盘 /
容量重建在**卷锁区间内**自增代数,读者比对填窗时记下的代数,不符即重填(对齐 32 位读写
在 ARM 上原子,跨任务无需临界区,最坏多读一轮);③ 单条硬门槛:soe_rec_parse() 校版本 +
校验和,撕裂/半写一律拒收
- 容忍的竞态只有一个:count 与 read 之间新落一条 → 本轮少看一条,下轮重绘(200 ms)自然
追上;所以边界一律用锁内重取的条数,不信调用方传进来的旧值
- API **不带类型掩码**(Lead 裁定 2026-10-25):5 个记录页怎么分组是界面语义,不进日志存储
层;环容量小、页渲染全扫无成本,真变大了再加带 mask 的优化 API(YAGNI)
⚠ 顺带修掉一颗定时炸弹:菜单生成器与生成物漂移
- 提交里的 Drivers/BSP/UI/ui_menu_tab.h 带着 5 个表查询 API 的规范注释块,而
tools/menu_gen/gen_menu.py 只会输出"声明 + 行尾短注释"——说明历史上有人**手改生成物**
补注释、没回灌生成器。我这次加 #page=soe 后重新生成,等于把那些注释抹掉,bsp-err 立刻
报 5 条 return-doc
- 修法不是把生成物改回去(下次重新生成还会再抹一次),而是把注释块**回灌进生成器**;
回灌后重跑 gen_menu.py 产出的 ui_menu_tab.h 与 HEAD **逐字节一致**(git diff 为空),
漂移关闭。生成物里的手工优化 = 一定会被覆盖的优化,要么进生成器要么别改生成物
时标口径(跨模块陷阱,注释即防线)
- 页面时标取记录内的 CP56(事件自己的时标,不是"现在")。它与运行/自检记录页同口径,靠两条
事实叠出来:sys_clock 的 epoch 域本身就是本地时间(App/util/sys_clock.h 的
sys_clock_set_unix 注明"Unix epoch time (秒,本地时间)"),而 soe_rec.c 的 epoch→CP56
分解不加任何时区偏移。**谁"顺手补 8 小时",SOE 页就会和文本记录页差 8 小时**;要引显式
时区必须两侧一起改
显示名回正:类型 7 用源词"未标定闭锁"
- 组件名字表里原写作"标定闭锁"——缩掉一个"未"字后意思可以读成"把标定闭锁了",与"因未标定
而闭锁"几乎相反;源词见 App/log/soe_rec.h 与"数据可信"卡 5,UI 表现层无权改写事实词
- 模拟器的 3 条假记录刻意含一条 type 7 + ELM_NONE(非元件支路):这是 7/8 两个新类型
**真的接上屏**的第一个像素级证据——只有主机断言时,名字表写错也照样一片绿
- 主机测试的宽度护栏随之按"画到哪一列"分列:动作/返回进值列(4 汉字 = 12 字节),其余类型
名进名列(7 汉字 = 21 字节);原来一刀切 12 字节会把正常名字判成违规
页面与注册
- 新建 Drivers/BSP/UI/ui_page_soe.{c,h}:薄跳板,一条记录两行共用组件内单条读盘缓存,
实例独立(两页共用一份缓存会互相命中对方记录号,task-6 的教训)
- 菜单树 SOE记录 加 #page=soe;重生成 4 个生成物,uvprojx / CMakeLists 两处注册由生成器
自动写入(本批 uvprojx 只含这一条新增,无 UV4 抖动)
- reader 绑定在两端适配层:真机 App/task/ui_task.c(CP56 → UI 视图 + 容量钳位),
模拟器 simulator/main.c(固定 3 条、时标固定串,保证黄金参考可复现)
两处被门禁当场抓住的记录(都是有效拦截,不是噪音)
- id-check 拦下注释里裸引"P2-10":编号引用必须可判定归属,代码注释也一样 → 改为指向
App/log/sys_log_mcu.c 与事实本身,不留裸编号
- 又一次 BOM 铁律现场复现:一次编辑剥掉 ui_w_soe.c 的 BOM,ARMCC 立刻报一串 #8/#7/#20
假语法错(missing closing quote 之类),而 MSVC 侧 0 警告完全照不出来;补回 EF BB BF
即 0/0。本批提交前逐文件比 HEAD 做了 BOM 漂移审计(临时脚本,不入库)——建议把这条
审计固化进 ui-verify skill 或门禁(不在本道写域,已回报 Lead)
验证
- @build:0 错误 0 警告;模拟器:0 警告
- 内容页回归 15 项 PASS:新增 soe 页 9 条文本断言(两条时标、过流Ⅰ段+动作、+返回、
未标定闭锁、页码),其余 11 页逐字节不变;菜单 8 状态 PASS、池 5368 B
- soe 页刷新黄金参考前出图肉眼确认:6 行两两成对、19 字符时标在 94 px 名列内不越屏、
非元件行的类型名落在名列且值列为空
- test/host/ui_data 83/83、test/host/soe 32/32;13 个代码/文档/注册类钩子(含
coding-standard、bsp-err、id-check、cppcheck、registration-check、layer-deps、
font-charset、doc-links)全 Passed
- 全量钩子红项只有同族三条:mem-numbers / mem-budget / size-gate(L0c),root cause 一条:
Flash/RAM 台账与同码凭证回填归单写者 → 本批 --no-verify
体积(相对 b746a1f 台账 Code 217204 / RO 21504 / RW 1256 / ZI 132680)
- 实测 Code 219712、RO 21388、RW 1288、ZI 134272 → Code +2508、RO −116、RW +32、ZI +1592
→ Flash 合计 +2424 B、RAM +1624 B(含本批之前的三页回炉)
- 本批新增里约 1064 B ZI 是 SOE 读窗口(512 B)+ static FIL(528 B),一次性投入、页面打开
才用得到;RAM 再紧张可把窗口降到 4 条(256 B)拿回一半,代价是跨窗记录多一次 I/O
待台架实测:真机 /log/soe.log 有记录时的翻页与尾部行为、卷忙时该行退化显示、SOE 页与文本
记录页时标一致性(跨 8 小时口径的现场核对)、记录增长时窗口代数失效后的重读节拍。
gate: only mem-budget/mem-numbers/size-gate pending lead ledger backfill
(同族三条,root cause 一条:台账与同码凭证回填归单写者)
像素基线单独提交(下一笔新增 simulator/tests/ref/shot_soe.bin):本笔单独 checkout 时
ui-golden 会报"缺参考",属预期拆分;上述钩子结论取自"本笔 + 下一笔"的合并树。
自查:BOM 漂移审计 17 个改动文件全对齐 HEAD;CR = 0;U+FFFD = 0;字面量纯 ASCII。
LVGL PC 模拟器(simulator/)
在 PC 上编译运行与 MCU 同一份 UI 源码(
Drivers/BSP/UI/ui_menu.c+ui_font_cn_12.c), 实时预览 160×160 1bpp 单色菜单界面,无需烧录 MCU。部署细节与完整 FAQ 见
docs/UI设计/LVGL模拟器部署与使用说明.md;本文聚焦本目录的内容与架构。
一、目录内容
simulator/
├── CMakeLists.txt # 构建脚本(CMake + MSVC,x64)
├── build_sim.bat / .ps1 # 一键构建入口(自动探测 VS2019/2022/2026 生成器,x64;.bat 绕过执行策略)
├── main.c # 入口:SDL 初始化 → LVGL → 菜单 → 主循环
├── sim/ # 平台层(PC 专用实现;MCU 端对应实现在各 BSP 目录)
│ ├── sim_port.c / .h # 显示/输入端口(SDL 窗口 + 键盘 + mono 主题),替代 MCU 端 ui_port.c
│ ├── net_socket_sim.c # 网络后端:Winsock 实现 net_* BSD API,替代 MCU 端 net_socket.c(CH395F)
│ ├── ui_net_sink_backend_sim.c # RemoDispBus 平台 backend:取帧/注键(配合共享 ui_net_sink.c)
│ └── cfg_file_store_sim.c # 配置文件存储后端:本地 sim_config.ini(旧档 sim_netcfg.bin 已废弃,不读不删)
├── lv_conf.h # 模拟器专用 LVGL 配置(与 MCU 端 Lib/lv_conf.h 相互独立)
├── i1_to_png.py # 截图转 PNG:把 --screenshot 的 I1(1bpp) 原始位图转成 PNG(见 §3.5)
├── tests/ # 无头回归脚本 + 本地黄金参考(见 §3.6,流程文档 docs/UI设计/UI_自动化测试流程.md)
│ ├── check_menu_rows.py # 菜单 8 状态像素回归 + 池基线门禁
│ ├── check_content_pages.py # 文本断言 + 内容页黄金参考回归 + 往返池门禁(--update 刷参考)
│ ├── check_remo_sink.py # RemoDispBus 远端推流协议回归(起窗口模拟器 + TCP 取帧 + 参考比对)
│ ├── regen_ref_png.py # 由 ref/*.bin 批量重生成目检 PNG(--scale 6 --invert)
│ └── ref/ # 黄金参考 shot_*.bin + 目检 *.png(**本地生成物,不入库**)
├── dbg_log.h # 调试日志 stub:DBG_* 映射到 printf(覆盖 MCU 版头文件)
├── SDL2/
│ └── SDL2-2.32.10/ # SDL2 预编译 SDK(随仓库提供,无需安装)
│ ├── include/ # SDL2 头文件
│ │ └── SDL2/ # ★ 头文件副本(LVGL SDL 驱动写死 #include "SDL2/SDL.h")
│ └── lib/
│ ├── x64/ # SDL2.lib / SDL2main.lib / SDL2.dll(构建使用)
│ └── x86/ # 32 位库(当前未使用)
└── build/ # CMake 生成物(可整体删除重建,勿提交)
└── Debug/lvgl_sim.exe # 可执行文件(SDL2.dll 由 POST_BUILD 自动复制到同目录)
文件职责
| 文件 | 说明 |
|---|---|
main.c |
程序入口。SDL_Init(TIMER|EVENTS) → lv_init() → sim_port_init(160,160) → ui_menu_init()(共享代码)→ 主循环(SDL 事件 + lv_timer_handler + lv_refr_now) |
sim/sim_port.c |
创建 SDL 窗口显示设备(lv_sdl_window_create)、初始化 mono 主题(lv_theme_mono_init,1bpp 必需)、创建键盘输入设备并挂默认输入组。不创建鼠标(纯键盘导航,与 MCU 端 keypad 一致)。sim_port_get_screen_mono() 经 SDL_RenderReadPixels 读回屏幕像素转 1bpp,供推流取帧 |
sim/net_socket_sim.c |
网络后端:Winsock 实现 net_* BSD Socket API(socket/bind/listen/accept/recv/send/close),替代 MCU 端 Drivers/BSP/NET/net_socket.c(CH395F) |
sim/ui_net_sink_backend_sim.c |
RemoDispBus 平台 backend:ui_net_sink_get_lcdmem→sim_port_get_screen_mono、ui_net_sink_inject_key→lv_group_send_data;配合共享 Drivers/BSP/UI/ui_net_sink.c(协议服务端,双端同一份,TCP 7003) |
sim/cfg_file_store_sim.c |
通用配置文件存储后端:本地文件 sim_config.ini(fopen/rename 原子写),替代 MCU 端 App/cfg/cfg_file_store_mcu.c(FatFS);旧档 sim_netcfg.bin 已废弃——不读、不删、不迁移(2026-10-10) |
lv_conf.h |
模拟器专用 LVGL 配置。与 MCU 端 Lib/lv_conf.h 的关键差异见 §3.3 |
dbg_log.h |
MCU 版 dbg_log.h 依赖 sys_clock.h(RTC 时间戳),PC 无法编译。此 stub 把 DBG_INFO/ERROR/DEBUG 映射到 printf;因 simulator/ 在 include 路径最前,优先生效覆盖 MCU 版 |
build_sim.bat / build_sim.ps1 |
一键构建入口:vswhere 探测本机 VS 大版本(16=2019 / 17=2022 / 18=2026,取已装最高)→ 映射 CMake 生成器;build/ 缓存生成器与本机不符(换机/升级 VS)时自动删除重建(带重试,抗文件锁)。构建入口一律走它,文档/命令中勿写死生成器版本;.bat 仅负责绕过 PowerShell 执行策略并转交 .ps1 |
CMakeLists.txt |
收集 LVGL 源码(../Lib/lvgl)+ 共享 UI 源码(../Drivers/BSP/UI)+ 通用配置文件引擎(../App/cfg_file.c、../Drivers/BSP/NETCFG 的 net_cfg;net_cfg_legacy 已于 2026-10-10 删除)+ sim/ 平台层,链接预编译 SDL2,产出 lvgl_sim.exe |
i1_to_png.py |
把 --screenshot 产出的 I1(1bpp) 原始位图转成 PNG,便于离线比对设计稿。详见 §3.5 |
tests/ |
无头回归脚本(菜单 8 状态像素回归、内容页文本断言 + 黄金参考回归);ref/ 为本地生成物、不入库(首次 --update 生成)。详见 §3.6 |
共享代码不复制:
ui_menu.c/ui_font_cn_12.c/ui_net_sink.c直接引用Drivers/BSP/UI/下的原件, MCU 与 PC 两端永远同步。不要在本目录内复制 UI 源码。
二、架构框架
2.1 运行架构
main.c
│
├─ SDL_Init(TIMER | EVENTS) # 仅初始化计时/事件,视频由 LVGL SDL 驱动内部接管
├─ lv_init() # LVGL v9.3.0 内核
├─ sim_port_init(160, 160)
│ ├─ lv_group_create + set_default # 默认输入组
│ ├─ lv_sdl_window_create(160,160) # SDL 窗口 = 显示设备(I1 单色)
│ ├─ lv_theme_mono_init(disp, true, # mono 主题(白字黑底),1bpp 下不初始化 = 黑屏
│ │ LV_FONT_DEFAULT)
│ └─ lv_sdl_keyboard_create # 键盘 → 默认输入组(方向键/Enter/Esc)
├─ ui_menu_init() # ★ 与 MCU 共用(Drivers/BSP/UI/ui_menu.c)
│ # 基元控件页面导航,节点表驱动
└─ 主循环
├─ SDL_PollEvent # SDL_QUIT → 退出
├─ lv_timer_handler() # 驱动 LVGL 定时器/重绘
├─ lv_refr_now(NULL) # 强制整屏刷新(PARTIAL 模式下保证首帧输出)
└─ Sleep(lv_timer_handler 返回值) # 自适应休眠
2.2 渲染链路(1bpp 单色)
LVGL 场景(基元控件页面/label,LV_COLOR_FORMAT_I1)
→ SW 渲染器(仅启用 I1,LV_DRAW_SW_SUPPORT_I1=1)
→ SDL 窗口 blit(LV_SDL_RENDER_MODE_PARTIAL,1bpp 强制要求)
→ 屏幕(160×160 物理像素,Windows 高 DPI 可能放大显示,属正常)
对照 MCU 端链路(ui_port.c):
LVGL 场景(同一份 ui_menu.c)
→ SW 渲染器(I1)
→ flush 回调 memcpy 到内存帧缓冲 g_ui_fbuf(3200 B)
→ RemoDispBus 协议(TCP 7003)按需推给远端客户端显示
两端场景层完全相同,只有"渲染结果去向"不同:模拟器进 SDL 窗口,MCU 进内存帧缓冲再走网络。
2.3 MCU 端 vs 模拟器 对照
| 项 | MCU(Keil MDK) | 模拟器(CMake + MSVC) |
|---|---|---|
| LVGL 编译 | .uvprojx 工程添加 Lib/lvgl 源文件 |
Lib/lvgl/CMakeLists.txt(最小 CMake,仅模拟器用,不影响 Keil 编译) |
| lv_conf.h | Lib/lv_conf.h |
simulator/lv_conf.h(独立,互不影响) |
| 显示端口 | ui_port.c(离屏 framebuffer + flush 回调) |
sim_port.c(SDL 窗口) |
| 输入 | 远端 keypad(ui_port_queue_key,网络回传键码) |
SDL 键盘(lv_sdl_keyboard_create) |
| 主题 | 无主题(LV_USE_THEME_* = 0),控件用内置基础样式 |
mono 主题必须开启(否则 1bpp 黑底黑字) |
| 日志 | Drivers/BSP/dbg_log.h(USART1 + RTC 时间戳) |
simulator/dbg_log.h(printf stub) |
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无(仅编译纯软件部分的配置文件引擎 net_cfg/cfg_file,可离线验证配置加载/保存) |
| 时钟 | lv_tick_inc 由 ui_task 手动喂(10ms 周期) |
SDL 驱动内部 lv_tick_set_cb(SDL_GetTicks),不可再手动喂 |
2.4 关键设计决策
- SDL2:LVGL 官方 PC 端口同款显示方案;使用仓库内预编译 SDK(
SDL2/SDL2-2.32.10/),无需系统安装 - CMake + MSVC:
build_sim.ps1按本机 VS 版本自动选生成器(16=2019 / 17=2022 / 18=2026;不同机器版本不同,勿写死),-A x64;MSVC 下加/utf-8消除中文注释的 C4819 警告 - 共享 UI 源码:
ui_menu.c/ui_font_cn_12.c直接编译Drivers/BSP/UI/原件,改一处两端同时生效 - 纯键盘导航:不创建鼠标/触摸设备,与 MCU 端 keypad 操作语义一致(↑↓←→ / Enter / Esc)
- UI 为主:不含 CH395F 网络 / NAND 存储 / ADC 等硬件业务;通用配置文件引擎(
cfg_file)与net_cfg核心为纯软件,配置加载/保存可在模拟器离线验证(sim_config.ini;旧档sim_netcfg.bin已废弃、不参与)
三、构建与运行
3.1 环境要求
| 依赖 | 版本 | 说明 |
|---|---|---|
| CMake | ≥ 3.15 | 本机验证 4.3.0-rc1 |
| MSVC | VS2019 / 2022 / 2026 任一 | build_sim.ps1 自动探测生成器(16=2019 / 17=2022 / 18=2026,取已装最高),Hostx64/x64 |
| SDL2 | 2.32.10 | 已内置于 SDL2/,无需安装 |
| 操作系统 | Windows | 已验证 win32 |
3.2 构建命令
# 一键构建(仓库根目录执行;自动探测本机 VS2019/2022/2026 生成器,
# build/ 缓存生成器与本机不符时自动删除重建)
& "simulator\build_sim.bat"
# 运行
.\simulator\build\Debug\lvgl_sim.exe
# 或:cmake --build simulator\build --config Debug --target run
# 全量重建
& "simulator\build_sim.bat" -Clean
改
CMakeLists.txt后再跑一次build_sim.bat即可(缓存不匹配会自动重建);遇到奇怪问题加-Clean全量重建。 手动 CMake 等效:cd simulator; cmake -B build -G "Visual Studio <NN> <YYYY>" -A x64; cmake --build build --config Debug,生成器号须与本机 VS 大版本一致(16=2019 / 17=2022 / 18=2026)。
3.3 lv_conf.h 与 MCU 端的关键差异
| 配置 | MCU 端(Lib/lv_conf.h) |
模拟器(本目录) | 原因 |
|---|---|---|---|
LV_USE_SDL |
0 | 1 | SDL 显示/键盘驱动 |
LV_SDL_RENDER_MODE |
— | PARTIAL |
1bpp + SDL 非 PARTIAL 会 #error |
LV_SDL_INCLUDE_PATH |
— | "SDL2/SDL.h" |
对应 include/SDL2/ 头文件副本 |
LV_USE_THEME_MONO |
0 | 1 | 1bpp 无主题 = 黑底黑字不可见 |
LV_DRAW_SW_SUPPORT_I1 |
1 | 1 | 仅启用 I1 渲染,裁掉其余色深 |
LV_MEM_SIZE |
16 KB | 16 KB | 必须一致(lv-conf-sync 比对生效值;2026-10-15 由 40 KB 同步收缩,PC 侧不再"放宽") |
LV_FONT_DEFAULT |
&ui_font_cn_12 |
同 | 共用同一字体 |
| 其余控件/驱动 | 裁剪 | 基本一致(另开 LV_USE_TEXTAREA/KEYBOARD/BUTTONMATRIX) |
模拟器略宽松,便于调试 |
3.4 键盘操作
| 按键 | 功能 |
|---|---|
| ↑ / ↓ | 菜单项上下移动 |
| → | 进入子页 |
| Enter | 确认 / 进入子页 |
| ← / Escape | 返回上级 |
3.5 截图比对设计稿(离线出图)
窗口模式只适合人工目检。要把渲染结果存成 PNG 与设计参考(docs/UI设计/mockups/ 下的原始参考图)做目视比对,用离屏截图 + 转 PNG:
cd simulator
# 1) 离屏渲染首帧,输出 I1(1bpp) 原始位图(已剥离 8 字节调色板,纯像素数据)
.\build\Debug\lvgl_sim.exe --screenshot shot.bin
# 2) 转 PNG(默认 160x160;--scale 放大便于查看;python 统一用仓库 .venv,见 AGENTS.md「Python 环境」)
..\.venv\Scripts\python.exe i1_to_png.py shot.bin # -> shot.png
..\.venv\Scripts\python.exe i1_to_png.py shot.bin -o 预览.png --scale 6
I1 位图格式(i1_to_png.py 据此解析):1 位/像素,每字节 MSB 在左(bit7 = 该行最左像素),bit=1 前景 / bit=0 背景,每行 宽/8 字节、行内无填充。
i1_to_png.py 常用参数:
| 参数 | 说明 |
|---|---|
-o, --output |
输出 PNG 路径(默认与输入同名 .png) |
-W, -H |
宽高(默认 160/160,非默认分辨率时必填) |
--scale N |
最近邻放大 N 倍,便于查看小图 |
--invert |
前/背景互换 |
--fg / --bg |
自定义前/背景色 R,G,B(默认黑白) |
--no-auto-palette |
关闭调色板自动识别(按纯像素解析) |
依赖 Pillow(已含于仓库
.venv,见requirements.txt)。shot.bin/shot*.png均已列入.gitignore,可随时删除重生成。
3.6 无头回归测试(tests/)
UI 改动的自动化门禁脚本(完整流程见 docs/UI设计/UI_自动化测试流程.md)。除 check_remo_sink.py(需 Pillow,.venv 已含)外均为纯标准库、任意 python3 均可;为环境统一,一律用仓库 .venv 运行:
# 仓库根目录执行(前置:本目录已构建出 build/Debug/lvgl_sim.exe)
.venv\Scripts\python.exe simulator/tests/check_menu_rows.py # 菜单 8 状态像素回归 + 池基线门禁
.venv\Scripts\python.exe simulator/tests/check_content_pages.py # 文本断言 + 内容页黄金参考回归 + 往返池门禁
.venv\Scripts\python.exe simulator/tests/check_remo_sink.py # RemoDispBus 远端推流协议回归(起窗口模拟器 + TCP 取帧 + 参考比对)
| 脚本 | 原理 | 失败输出 |
|---|---|---|
check_menu_rows.py |
键序注入(u/d/e/s)跑 8 个菜单状态,断言选中行白带、非选行黑底白字、框体边框、往返状态逐像素一致;MENU-STATE-00 池水位须等于脚本内 MENU_POOL_BASELINE |
未过断言清单 + 保留临时截图 |
check_content_pages.py |
① 文本断言(解析字体字形,在截图内精确匹配期望串,不依赖 ref/)② 11 内容页无头截图与 tests/ref/shot_*.bin 逐字节比对;另校验开页-关页往返池水位 = POOL_AFTER_ROUNDTRIP |
文本未命中项 / 差异像素数 + 双方 ink runs 对照 |
check_remo_sink.py |
启动窗口模拟器(非无头,CREATE_NEW_PROCESS_GROUP 防管道阻塞),经 TCP 7003 发 LCDMEM 取 1bpp 帧;断言首帧=LCDMEM(无 INIT 污染)/位图 3200 字节/非平凡;复用 i1_to_png 出目检 PNG;与无头参考(lvgl_sim --screenshot)逐位比对 <2% 判 MATCH。守护"INIT 污染 / 快照卡死"两个历史缺陷 |
各判据失败原因 + sim_err.txt 尾部 + 保留 PNG(默认不删,--clean 才删) |
- 黄金参考与目检 PNG(
ref/不入库):首次/换机后先生成一次——.venv\Scripts\python.exe simulator/tests/check_content_pages.py --update出ref/*.bin,.venv\Scripts\python.exe simulator/tests/regen_ref_png.py出目检ref/*.png(默认--scale 6 --invert)。 有意改视觉时先肉眼确认新效果正确再--update(会先跑文本断言防误刷)。 - 池水位常量漂移:菜单/页面结构变更导致门禁失败时,确认新值合理后再更新脚本内常量并登记流程文档变更记录;无法解释的漂移 = 内存回归 bug。
- 退出码均为 0=PASS / 1=断言失败 / 2=环境错误(exe 缺失等)。
3.7 配置文件自检(--cfgtest)
配置引擎"加载 → 轮询 → 重载 → 保存"的进程内闭环验证(无需窗口 / LVGL,退出码 0=PASS / 非 0=FAIL);自检期间在当前工作目录临时使用 sim_config.ini,结束时无论成败一律删除(2026-10-15 起,原因见下文 ⚠):
.\build\Debug\lvgl_sim.exe --cfgtest
验证链:net_cfg_load(旧档迁移已于 2026-10-10 删除,只走 INI)→ 第 1 次 poll 建基线 → 伪造"外部编辑"(原子写,等价 FTP 上传/文本编辑器)→ 2 次 poll 沉降(对应真机 4s 沉降)→ 断言 RAM 副本已更新为新值 → 对已存在文件做外科式保存、回读校验注释/值无损。
空目录运行 = 验证出厂模板生成;超限文件(>4096 B)= 判损坏并重建出厂模板;超限文档保存 = 拒绝且不动原文件。窗口模式主循环以与 MCU netTask 相同的 2s 周期调用 cfg_file_poll():外部文件变更沉降后自动重载 [network] 段(模拟器无网络栈,变更回调为空操作,仅日志可见)。
⚠ 为什么必须删掉伪造档:超限文档保存那条断言要求"拒写后原文件不动"(size == 4000),所以连通过路径也会把这份 4000 B 纯注释档留在工作目录。之后每一次运行都会加载它 → 外科式保存要在 4000 B 注释后补写受管段 → 输出必超 CFG_FILE_MAX_LEN → 引擎按设计拒写 → 网口设置页保存行显示 ERR,把 UI 像素回归带偏(2026-10-15 实测踩中)。故 run_cfgtest() 只是外壳,跑完一律删除;simulator/tests/ 两套回归另以空临时目录当工作目录运行做双保险 → docs/UI设计/UI_自动化测试流程.md §五。
四、已知坑(构建/运行)
cannot open include file "SDL2/SDL.h"LVGL SDL 驱动写死#include "SDL2/SDL.h",而原版 SDK 头文件平铺在include/下。 必须把头文件复制一份到include/SDL2/子目录(本仓库已做好):$inc = "simulator\SDL2\SDL2-2.32.10\include" New-Item -ItemType Directory -Path "$inc\SDL2" -Force Copy-Item "$inc\*.h" "$inc\SDL2\"cannot open include file "lv_conf.h"LV_CONF_DIR/SDL2_INCLUDE_DIR两个 CMake 变量必须在add_subdirectory(lvgl)之前定义(见CMakeLists.txt中的顺序注释),勿随意调整。- 运行后窗口全黑,按序排查:
LV_USE_THEME_MONO = 1且sim_port_init中调用了lv_theme_mono_init- 主循环调用了
lv_refr_now(NULL)(PARTIAL 模式保证首帧) - 没有重复设置 tick(SDL 驱动内部已
lv_tick_set_cb,不要再SDL_AddTimer+lv_tick_inc)
- 中文显示空白:字体
ui_font_cn_12是子集字体(当前 183 汉字/符号 + ASCII,共 278 字形),缺字渲染为空白。 补字流程见docs/UI设计/UI_中文字体生成与配置.md(tools/font_gen/两步脚本)。 - 改了 UI 代码但模拟器没变化:确认改的是
Drivers/BSP/UI/ui_menu.c(共享原件)而非本目录副本(本目录无副本),然后重新cmake --build。 - 编码:UI 源码为 UTF-8(带 BOM),MSVC 已加
/utf-8。不要用 PowerShellSet-Content等重写含中文的文件(编码铁律,见根目录AGENTS.md)。 - 键盘输入无响应(方向键/Enter 无效):
main.c主循环不得调用SDL_PollEvent。 LVGL SDL 驱动内部有 5ms 定时器(lv_sdl_window.c的sdl_event_handler)统一轮询分发 SDL 事件; 主循环若先轮询会消费掉事件队列,键盘输入永远到不了 LVGL(现象:画面正常但按键全部无效)。 关窗退出由驱动LV_SDL_DIRECT_EXIT=1自动exit(0),主循环无需处理 SDL_QUIT。
五、边界说明
- 模拟器跑 UI + 配置文件引擎:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务;
cfg_file引擎与net_cfg(纯软件部分)在模拟器编译运行,可离线验证配置加载/保存 - 模拟器含
ui_net_sink(Winsock 版 RemoDispBus 推流服务端,TCP 7003)——远端看图链路 可在模拟器上验证(simulator/tests/check_remo_sink.py);CH395F 真实网络栈仍只在 MCU 上验证 build/为生成物,可随时删除重建(已在.gitignore中忽略)Lib/lvgl/CMakeLists.txt仅被本模拟器引用,不影响 Keil 的 MCU 编译
模拟器跑过 ≠ 真机跑过:本目录与真机的全部已知差异(网络 pending 机制缺失、代次号恒 0、 日志/配置的 mtime 粒度与堆后端差异、IWDG/NAND/RTC 等硬件不可建模)统一登记在
../docs/测试说明/模拟器差距清单.md—— 改动触及sim/任何后端时,必须同步在那里登记一行;改动前先看docs/测试说明/模拟器差距清单.md§3 判断是否必须回真机。