feat(ui)+chore(docs): UI 补齐清单清空;数据可信销项与「卡」号清退

本笔含两组收尾工作(同一工作区内交错在 6 个共享文档/门禁配置里,
拆开会产生"文档声称的页数与树里黄金数不一致"的中间提交,故合为一笔):

一、UI 补齐(清单清空)
- 新建 6 个内容页模块:ui_page_accident / oper / alarm / start / yk
  复用 ui_w_records 共享组件(分页、空态"无记录"、按实例读盘缓存),
  记录层与五个写入点已由道F task-10 / 道G task-12 落地,本批只补页面;
  ui_page_runreport 为 13 行 2 页只读统计,四个数逐个挂范围标签
  (自电池周期 / 本次开机),boot_ready=0 显 "--" 不显 0。
- 数据面:ui_data_info_t 尾部追加 9 字段,ui_data_feed 用
  boot_stats_get() + iec104_serv_get_stats() 纯搬运;模拟器填确定值假数据
  (差异登记 docs/测试说明/模拟器差距清单.md §2.3 两行)。
- 接线:菜单树 6 叶绑 #page=,绑定叶 22→28、UI_PAGE_MAP_COUNT=28;
  字库 185→218 字 / 280→313 字形;双端注册 uvprojx +30、CMakeLists +6。
- 回归:内容页黄金 53→60(含 runreport_p2 真拍第 2 页),文本断言
  425 正向 / 36 反向;存量 53 张逐字节零漂移;记录 5 页各用独立空目录
  (模拟器把操作留痕写进 cwd 的 sim_oper.log,共用 tmp 会让空态拍不到)。

二、数据可信销项 + 「数据可信」任务卡号整族清退
- 删除已闭环的"已知电压注入验证"行与闭环结论块(证据在
  docs/硬件平台/ADC通道映射说明.md §8.1 与
  docs/测试说明/ADC前端注入验证流程.md §5~§7),§5 标识列改语义短语。
- 卡 0/0b/1~12 共 14 个号全仓 288 站点 / 56 文件清退成语义短语,
  代码注释、测试、门禁配置与文档一并改;换算表入 docs/编号规范.md §9.3。
  这一族是中文前缀,不进 check_ids 语料,刻意不入 id_aliases.json。

验证(实测)
- @build 0 错 0 警:Code=236456 RO=28124 RW=1876 ZI=137084;
  RAM +464 B(RW +304 / ZI +160),RW_IRAM1 62.2% / RW_CCM 87.9%,
  体积同码凭证与 RAM 台账已回填。
- check_menu_rows 8 状态 rc=0(池 5368/5448 常量未动);
  check_content_pages 60 页 rc=0;check_font_charset / registration /
  coding-standard / bom-drift / mojibake / doc-links --all / layer-deps /
  nested-comment 全 rc=0;check_ids --strict 无错误;主机单测
  ui_data 295 / boot_stats 21 / sys_log_mcu 141 / soe 32 / soe_store 27。

未做(不是遗漏)
- 六页真机判据(真记录翻页、掉电保持、清除后归零、boot_count 自增、
  拔 RTC 电池落 RESTARTED、IWDG 复位落 rst_cause、主站连一次 conn_up+1)
  已登记 docs/任务规划/登记簿.md §7;模拟器结构上照不到。
- docs/产品需求/产品需求设计文档.md 属另一条在飞改动,本笔不含;
  其中我已同步 6 处现状数字(28 叶 / 60 页 / 218 字),随该笔入库。
This commit is contained in:
2026-10-28 07:23:16 +08:00
parent 8f7c141a0b
commit 94b0d79026
113 changed files with 1934 additions and 615 deletions
+6
View File
@@ -118,6 +118,12 @@ set(UI_SOURCES
${UI_DIR}/ui_page_calib.c
${UI_DIR}/ui_page_deadband.c
${UI_DIR}/ui_page_comm.c
${UI_DIR}/ui_page_accident.c
${UI_DIR}/ui_page_alarm.c
${UI_DIR}/ui_page_oper.c
${UI_DIR}/ui_page_runreport.c
${UI_DIR}/ui_page_start.c
${UI_DIR}/ui_page_yk.c
)
# 运行/自检记录引擎(双端共享核心 + 平台存储后端;MCU 端编 sys_log_mcu.c,见 Keil 工程)
+3 -3
View File
@@ -53,7 +53,7 @@ simulator/
| `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 状态像素回归 + 内容页 53 页:文本断言 / 反向断言 / 黄金参考逐字节比对);`ref/shot_*.bin` **随仓库入库**(判据本体,改动等于改判据,评审须逐页过目),`ref/*.png` 是本地派生的目检图(`.gitignore` 挡掉)。详见 §3.6 |
| `tests/` | 无头回归脚本(菜单 8 状态像素回归 + 内容页 60 页:文本断言 / 反向断言 / 黄金参考逐字节比对);`ref/shot_*.bin` **随仓库入库**(判据本体,改动等于改判据,评审须逐页过目),`ref/*.png` 是本地派生的目检图(`.gitignore` 挡掉)。详见 §3.6 |
> **共享代码不复制**:`ui_menu.c` / `ui_font_cn_12.c` / `ui_net_sink.c` 直接引用 `Drivers/BSP/UI/` 下的原件,
> MCU 与 PC 两端永远同步。**不要**在本目录内复制 UI 源码。
@@ -222,7 +222,7 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化
| 脚本 | 原理 | 失败输出 |
|------|------|----------|
| `check_menu_rows.py` | 键序注入(u/d/e/s)跑 8 个菜单状态,断言选中行白带、非选行黑底白字、框体边框、往返状态逐像素一致;MENU-STATE-00 池水位须等于脚本内 `MENU_POOL_BASELINE` | 未过断言清单 + 保留临时截图 |
| `check_content_pages.py` | ① 文本断言(解析字体字形,在截图 y 带内精确匹配期望串,不依赖 `ref/`)② **反向断言**(`ABSENT` 表:某串**不该**出现,如查看页/只读态不得出现「未标定」徽标与「保存」行——像素参考会被 `--update` 刷成"自证正确",反向断言不依赖参考图)③ **53 页**无头截图与 `tests/ref/shot_*.bin` **逐字节**比对(含 2026-10-26 新增的整定 9 态:进页 / 保存行 / 只读态 / 编辑态 / 越界 / 保存 OK / 落定后可读 / 二态回绕);另校验开页-关页往返池水位 = `POOL_AFTER_ROUNDTRIP` | 文本未命中项 / 反向断言被命中 / 差异像素数 + 双方 ink runs 对照 |
| `check_content_pages.py` | ① 文本断言(解析字体字形,在截图 y 带内精确匹配期望串,不依赖 `ref/`)② **反向断言**(`ABSENT` 表:某串**不该**出现,如查看页/只读态不得出现「未标定」徽标与「保存」行——像素参考会被 `--update` 刷成"自证正确",反向断言不依赖参考图)③ **60 页**无头截图与 `tests/ref/shot_*.bin` **逐字节**比对(含 2026-10-26 新增的整定 9 态:进页 / 保存行 / 只读态 / 编辑态 / 越界 / 保存 OK / 落定后可读 / 二态回绕);另校验开页-关页往返池水位 = `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/*.bin` **入库**、`ref/*.png` 本地派生不入库):换机或想目检时——
@@ -274,7 +274,7 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化
- `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`)
4. **中文显示空白**:字体 `ui_font_cn_12` 是子集字体(当前 183 汉字/符号 + ASCII,共 278 字形),缺字渲染为空白。
4. **中文显示空白**:字体 `ui_font_cn_12` 是子集字体(当前 218 汉字/符号 + ASCII,共 313 字形;2026-10-28 UI 补齐批 +33 字,旧数 183/278 系未回填、改前实测 185/280),缺字渲染为空白。
补字流程见 `docs/UI设计/UI_中文字体生成与配置.md`(`tools/font_gen/` 两步脚本)。
5. **改了 UI 代码但模拟器没变化**:确认改的是 `Drivers/BSP/UI/ui_menu.c`(共享原件)而非本目录副本(本目录无副本),然后重新 `cmake --build`。
6. **编码**:UI 源码为 UTF-8(带 BOM),MSVC 已加 `/utf-8`。**不要**用 PowerShell `Set-Content` 等重写含中文的文件(编码铁律,见根目录 `AGENTS.md`)。
+13
View File
@@ -670,6 +670,19 @@ static void sim_publish_data_frames(void) {
(void)snprintf(info.build_date, sizeof(info.build_date), "%s", "Oct 25 2026");
(void)snprintf(info.sw_sum, sizeof(info.sw_sum), "%s", "--");
(void)snprintf(info.ftp_user, sizeof(info.ftp_user), "%s", "admin");
/* 运行报告四数:模拟器**没有** boot_stats(依赖 SD2506 I2C 与 RCC_CSR,模拟器不编译
* 该模块)也没有 104 协议栈 → 填确定值假数据,只为像素黄金可复现;真机走
* App/app/ui_data_feed.c 的 boot_stats_get()/iec104_serv_get_stats() 真源。
* 差异已登记 docs/测试说明/模拟器差距清单.md(2026-10-28 UI 补齐「运行报告」) */
info.boot_ready = 1u;
info.boot_count = 7u;
info.boot_seq_state = 2u; /* 延续(自 RTC 电池周期起) */
info.boot_write_failed = 0u;
info.rst_cause = 4u; /* 上电复位(BOOT_STATS_CAUSE_POR) */
info.iec104_conn_up = 3u;
info.iec104_apdu_tx = 128u;
info.iec104_tx_fail = 0u;
info.iec104_rx_bytes = 4096u;
(void)ui_data_publish_info(&info);
}
+1 -1
View File
@@ -11,7 +11,7 @@
| `check_content_pages.py` | **内容页回归**:各内容页渲染比对 |
| `check_remo_sink.py` | **推流(RemoDispBus)回归**:接收端渲染比对 |
| `regen_ref_png.py` | 由 `ref/*.bin` 批量出**目检 PNG**(不产生基准;基准由 `check_content_pages.py --update` 刷) |
| `ref/*.bin` | **像素黄金参考(入库)**:53 页 × 3200 B,160×160 单色打包 |
| `ref/*.bin` | **像素黄金参考(入库)**:60 页 × 3200 B,160×160 单色打包 |
## 使用方式
+123 -2
View File
@@ -131,6 +131,27 @@ PAGES = [
("comm_reject", "ddddd ee uruuruuue e d e"), # CA 编到 0(< min=1)→ ENTER 拒退
("comm_wait", "ddddd ee uruuruuue e u e dd e"), # CA 1→2 提交 → 下移到保存行 ENTER:WAIT(+--comm-nopump)
("comm_ok", "ddddd ee uruuruuue e u e dd e"), # 同键序、pump 正常 → OK(脏位真被清)
# ── 接线道 task-6:记录 5 页 + 运行报告(六叶同一笔绑 #page= 注记)────────────────
# 键序 = 主界面第 7 项「记录查询」(6 下)→ e 进子框(进框后首项 SOE记录 即被选中)
# → 再按 n 下选到目标叶 → e 开页。实测下数与 node 号严格对应:
# 1=事故 34 / 2=操作 35 / 3=保护告警 36 / 4=保护启动 37 / 5=遥控 38 /
# 6=自检 39 / 7=运行 40 / 8=运行报告 41(与 Lead 交接口径同源,逐个键序实测过)。
# 这五页在**模拟器侧没有写入方**(sim_rec_count 对这些编号恒 0)→ 拍到的是空态判据:
# 伪行"无记录"落在第 0 行,而第 0 行正是选中行(白底黑字)→ 文本断言一律 inv=True
# (实测 inv=False 匹不上,这是 ui_content_page 选中带的实现事实,不是 bug)。
("accident", "dddddde d e"),
("oper", "dddddde dd e"),
("alarm", "dddddde ddd e"),
("start", "dddddde dddd e"),
("yk", "dddddde ddddd e"),
# 运行报告(node 41):12 行 / UI_LIST_ROWS=8 → 真 2 页。**四个数逐个挂范围标签**是判据
# (docs/任务规划/UI补齐.md §2「运行报告」行),所以两张都必须拍——首屏钉上电次数/
# 计数范围/复位原因/主站接入 + 范围标签"本次开机",第 2 页钉 104 另外三枚(发送帧数
# 128 / 发送失败 0 / 接收字节 4096)与页脚真实页码 2/2。四枚假数据值互不相同是刻意的:
# 行错位/串行只有"像素 + 逐行文本断言"两把尺子同时才照得出来(Lead 裁定 2026-10-28,
# 因此用例是 53+7=60 张,不是 53+6=59)。
("runreport", "dddddde dddddddd e"),
("runreport_p2", "dddddde dddddddd e dddddddd"), # 页内再 8 下 → 第 2 页(选中落 r0=发送帧数)
]
# 文本级断言表:页名 -> [(名称, 期望串, y 带, 是否反显)]
@@ -590,6 +611,72 @@ TEXTS = {
("r0名", "公共地址", (24, 38), False), ("r0值", "2", (24, 38), False),
("保存名", "保存", (52, 66), True), ("保存值", "OK", (52, 66), True), # 脏位被 pump 清 = 已落盘
],
# ── 接线道 task-6:记录 5 页(空态判据)+ 运行报告两页 ───────────────────────────
# 五个记录页只钉空态:模拟器侧这五个日志文件没有写入方,能钉的正是判据"空记录显示
# 无记录"(App/log/README.md §五类记录)。第 0 行 = 选中行(白底黑字)→ inv=True;
# 页脚钉"第 1 页,共 1 页"同时钉住分页数学(1 条伪行 = 1 页,不是 0 页也不是 2 页)。
"accident": [
("标题", "事故记录", (0, 24), False),
("r0空态", "无记录", (24, 38), True),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
"oper": [
("标题", "操作记录", (0, 24), False),
("r0空态", "无记录", (24, 38), True),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
"alarm": [
("标题", "保护告警", (0, 24), False),
("r0空态", "无记录", (24, 38), True),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
"start": [
("标题", "保护启动", (0, 24), False),
("r0空态", "无记录", (24, 38), True),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
"yk": [
("标题", "遥控记录", (0, 24), False),
("r0空态", "无记录", (24, 38), True),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
# 运行报告首屏 8 行。四个数各自挂范围标签是判据(上电次数=自电池周期、其余=本次开机),
# 所以**标签与值成对断言**:只钉值不钉标签,就等于把"终身次数"这种口径错画放过去。
# 值 = simulator/main.c 的确定假数据(boot_count 7 / rst_cause 4 上电复位 / conn_up 3 /
# uptime_s 0);四枚 104 数互不相同是刻意的,行错位在第 2 页那张里同样看得出来。
# 运行报告首屏 8 行(13 行 = 8 + 5 两页,Lead 2026-10-28 补「计数可信」行使四态可辨)。
# 四个数各自挂范围标签是判据(上电次数=自电池周期、其余=本次开机),所以**标签与值
# 成对断言**:只钉值不钉标签,等于把"终身次数"这种口径错画放过去。「延续」= 模拟器
# 假数据 boot_seq_state=2(CONTINUED),与 UNKNOWN 的 "--" 是两回事。
# 值 = simulator/main.c 的确定假数据(boot_count 7 / rst_cause 4 上电复位 / uptime_s 0)。
"runreport": [
("标题", "运行报告", (0, 24), False),
("r0名", "上电次数", (24, 38), True), ("r0值", "7", (24, 38), True),
("r1名", "计数范围", (38, 52), False), ("r1范围标签", "自电池周期", (38, 52), False),
("r2名", "计数可信", (52, 66), False), ("r2可信度", "延续", (52, 66), False),
("r3名", "计数写回", (66, 80), False), ("r3值", "正常", (66, 80), False),
("r4名", "复位原因", (80, 94), False), ("r4值", "上电复位", (80, 94), False),
("r5名", "复位范围", (94, 108), False), ("r5范围标签", "本次开机", (94, 108), False),
("r6名", "运行时长", (108, 122), False), ("r6值", "00:00:00", (108, 122), False),
("r7名", "时长范围", (122, 136), False), ("r7范围标签", "本次开机", (122, 136), False),
("页脚", "第 1 页,共 2 页", (140, 160), False),
],
# 运行报告第 2 页:104 另外三枚 + 统计范围标签 + 真实页码 2/2(分页数学的像素证据)
# 运行报告第 2 页(13 行 → 第 2 页 5 行):104 四枚 + 统计范围标签 + 真实页码 2/2。
# 选中行随翻页落在 r0(主站接入)→ inv=True;四枚值 3/128/0/4096 互不相同是刻意的,
# 行错位或分页窗口算错只有"像素 + 逐行文本"两把尺子同时才照得出来(Lead 裁定)。
"runreport_p2": [
("标题", "运行报告", (0, 24), False),
("r0名", "主站接入", (24, 38), True), ("r0值", "3", (24, 38), True),
("r1名", "发送帧数", (38, 52), False), ("r1值", "128", (38, 52), False),
("r2名", "发送失败", (52, 66), False), ("r2值", "0", (52, 66), False),
("r3名", "接收字节", (66, 80), False), ("r3值", "4096", (66, 80), False),
("r4名", "统计范围", (80, 94), False), ("r4范围标签", "本次开机", (80, 94), False),
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
}
# 反向文本断言表:页名 -> [(名称, 期望**不出现**的串, y 带, 是否反显态)]
@@ -667,6 +754,26 @@ ABSENT = {
# 反过来正向断言"100"是没有意义的——它是"100V"甚至"10000V"的子串,
# 字形匹配器只保证 bbox 内干净、不检查词边界(实测在上一行的 10000V 上命中)
"sparam_edit": [("编辑态不打单位后缀", "100V", (38, 52), True)],
# ── 接线道 task-6:反向断言钉"分页不串行 / 记录源不串页"(同样不依赖参考图)──
# 运行报告两页互为反证:第 1 页不许出现只有第 2 页才有的行、反之亦然——分页窗口
# 一旦算错(比如 rows_per_page 与页脚页码不同源),只有一张图会露馅,这里两把都上。
# 五个记录页钉"空态页上不许出现别的页的时标串":SOE 页屏上有固定 3 条假记录,时标
# 2026-10-25(ui_w_soe 的显示假数据)。一旦有人图省事把记录页接到那份假数据上,
# 或把两端记录源映射表(simulator/main.c 的 s_sim_log_map 与 App/task/ui_task.c 的
# s_log_map)接错号,让这几页读到 SOE 的数据,屏上就会冒出这个时标——这条不依赖参考图就会响。
"runreport": [
("第 1 页不出现第 2 页的行", "发送帧数", (0, 160), False),
],
"runreport_p2": [
("第 2 页不出现第 1 页的行", "上电次数", (0, 160), False),
],
"accident": [("空态不串 SOE 假记录", "2026-10-25", (0, 160), False)],
"oper": [("空态不串 SOE 假记录", "2026-10-25", (0, 160), False)],
"alarm": [("空态不串 SOE 假记录", "2026-10-25", (0, 160), False)],
"start": [("空态不串 SOE 假记录", "2026-10-25", (0, 160), False)],
"yk": [("空态不串 SOE 假记录", "2026-10-25", (0, 160), False)],
}
W = H = 160
@@ -694,6 +801,16 @@ EXTRA = {
"comm_wait": ["--comm-nopump"],
}
# 接线道 task-6:记录 5 页在**各自独立的空目录**里跑(不共用回归的那个 tmp)。
# 诱因(实测,不是猜):模拟器把"人工操作留痕"镜像成了真记录——simulator/main.c 的
# sim_post_oper() 走 syslog_post(SYSLOG_OPER, ...) 落盘**当前工作目录**的 sim_oper.log;
# 而 60 页原本共用一个 tmp,clock_save / netcfg_save / sparam_save 这些"保存"态跑完之后
# sim_oper.log 已经攒了 3 条(192 B = 3×64 B)→ "操作记录"页读到的是真记录,
# 空态判据(第 0 行"无记录")根本拍不到。**判据本身没错,错在跑它的环境被前面的写路径
# 污染了**,所以给这 5 页各开一个空目录 = 现场"这些日志文件还不存在"的确定态;
# 其余 55 页环境一字不变(对存量 53 张黄金零影响,实测逐字节零漂移)。
FRESH_CWD = ("accident", "oper", "alarm", "start", "yk")
def run_sim(seq, out, cwd, extra=()):
"""在指定工作目录里跑一次无头截图。
@@ -867,8 +984,12 @@ def main():
# 1) 跑全部页面 + 文本级断言预检(先于像素比对/刷新:失败即中止,避免误刷参考)
shots = {}
for name, seq in PAGES:
shot = os.path.join(tmp, "shot_%s.bin" % name)
run_sim(seq, shot, tmp, EXTRA.get(name, ()))
cwd = tmp
if name in FRESH_CWD:
# 记录页的空态判据要求"日志文件此刻不存在",理由见 FRESH_CWD 上方说明
cwd = tempfile.mkdtemp(prefix="fresh_%s_" % name, dir=tmp)
shot = os.path.join(cwd, "shot_%s.bin" % name)
run_sim(seq, shot, cwd, EXTRA.get(name, ()))
shots[name] = open(shot, "rb").read()
n_pos = 0
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.