feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐

UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机
OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批
预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block
(口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng
反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定
内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按
is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。

记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在
defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE);
事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身
(一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。
为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核,
丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。

为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费
两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过;
RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同
码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。

实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B;
RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS,
protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→
164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34,
既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、
不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页
连跑两次逐字节一致;字库 diff 空。

顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽
16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前
靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值
全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py
模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。

未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行;
LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND),
已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
This commit is contained in:
2026-10-26 15:51:12 +08:00
parent 0f7b5a87fb
commit 77779d49d6
55 changed files with 4236 additions and 289 deletions
+136 -2
View File
@@ -54,9 +54,30 @@ PAGES = [
("vset", "ddedede"),
("vword", "ddededde"),
("vplate", "ddededdde"),
# ── 道F task-2:整定 4 页(参数定值 → 整定 → *,#P 叶,123=修改密码可编辑)──
# 导航公共前缀:dd 进"参数定值"→ e 进(选中"整定")→ e 进整定子框(选中"参数")
# → e 进密码页;密码 123 = "uruuruuue"(三框各 +1、两次右移、确认,与 netcfg 同串法);
# 000(直接 e)= 查看密码 → 只读。定值/控制字/软压板 在整定子框里多按 d 选到该行再 e。
# 保存行在第 (行数) 行:参数 9 行 = 2 页 → 8×d 落到保存行;定值 19 行 = 3 页。
("sparam_p2", "ddeee uruuruuue dddddddd"), # 参数整定第 2 页:保存行 ENTER
("sparam_ro", "ddeee e"), # 000 只读:无保存行、无徽标
("sparam_edit", "ddeee uruuruuue d e"), # PT二次 进编辑(值列是编辑串,无单位)
("sparam_oob", "ddeee uruuruuue d e l l d e"), # 编到 0(< min=1)→ ENTER 拒退 + 越界
("sparam_save", "ddeee uruuruuue e u e dddddddd e"), # PT一次 +1 → 保存行 ENTER → OK
("sparam_landed", "ddeee uruuruuue e u e dddddddd e u"), # 保存后回首屏:新值 10001V 已在帧里
("sset", "ddeed e uruuruuue"), # 定值整定首屏(18 项 + 保存 = 3 页)
("sword_cycle", "ddeedde uruuruuue e"), # 控制字 二态行 ENTER:跳闸 → 告警
("splate", "ddeeddde uruuruuue"), # 软压板整定首屏(10 项 + 保存 = 2 页)
("pwd", "ddeee"), # 密码页初始态:3 框 000、光标首框
("pwe", "ddeee uru"), # 密码页调值+移光标:110、光标第 2 框
("pwk", "ddeee uruuruuue"), # 密码页输 123:u / r / u,u / r / u,u,u / e → 关页进空白终点框(菜单屏)
# 密码页输 123:u / r / u,u / r / u,u,u / e → 关页并按授权"续做进入"。
# ⚠ **道F task-2 起这张图的落点变了**(黄金参考已 --update 刷新,属有意改判据):
# 整定 4 叶绑定内容页之前,123 确认后走 menu_password_ok 的"无内容页 → 压空白
# 终点框"分支(屏上是 4 框菜单,整定叶本身是个死叶子);绑定之后走
# "有内容页 → ui_content_page_open(page, UI_PERM_MODIFY)"分支,落在**整定-参数页**。
# 也就是说这张图现在同时钉住两件事:密码页授权链正确 + 123 真的能开可编辑整定页。
# 旧图并非"错",是那条分支从此不再可达——留档见 2026-10-26 提交正文。
("pwk", "ddeee uruuruuue"),
("pwc", "ddeee ue"), # 密码页 100 不一致 → 清空重输(回到 000 初始态像素)
# 网口设置(node 30,通讯参数下第 2 项,需密码):
# dddddd = 根框下移 5 至通讯参数;e 进框;d 选中网口设置;e 进密码页
@@ -272,6 +293,79 @@ TEXTS = {
("r1名", "子网掩码", (38, 52), False),
("r4名", "保存", (80, 95), False),
],
# ── 道F task-2:整定 4 页文本级断言 ─────────────────────────────────────────
# 值全部来自 simulator/main.c 的固定帧(prot_set 出厂默认 + cal_block 恒 1),
# 每条断言都是"某个具体事实":徽标在不在、保存行在第几页、越界提示、改值后落没落。
# ⚠ 编辑态的值列**没有单位后缀**(ui_w_setlist_refresh 编辑分支走
# ui_w_editor_format,非编辑分支才拼 unit)——所以 sparam_edit 断 "100" 而不是
# "100V":这条差异本身就是"这一行正在编辑"的像素证据,别把它"顺手统一"掉。
"pwk": [
("标题", "参数", (0, 24), False),
("徽标", "未标定", (0, 24), False), # cal_block=1:整定页必须看得见
("r0名", "PT一次", (24, 38), True), ("r0值", "10000V", (24, 38), True),
("r4名", "过量返回", (80, 94), False), ("r4值", "0.95", (80, 94), False),
("r7名", "低频返回", (122, 136), False), ("r7值", "1.01", (122, 136), False),
("页脚", "第 1 页,共 2 页", (140, 160), False), # 8 项 + 保存行 = 9 行 → 2 页
],
"sparam_p2": [
("徽标", "未标定", (0, 24), False),
("保存名", "保存", (24, 38), True),
("保存值", "ENTER", (24, 38), True), # 从未投递 → IDLE
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
"sparam_ro": [
# 000 查看密码:同一份描述符的只读分支——有行、无保存行、无徽标(见 ABSENT)
("标题", "参数", (0, 24), False),
("r0名", "PT一次", (24, 38), True), ("r0值", "10000V", (24, 38), True),
("r7名", "低频返回", (122, 136), False),
("页脚", "第 1 页,共 1 页", (140, 160), False), # 无保存行 → 8 行正好 1 页
],
"sparam_edit": [
# 编辑态的可见特征:选中带反显(白底黑字)+ 值列**没有单位后缀**(见 ABSENT)
("r1名", "PT二次", (38, 52), True),
("r0名", "PT一次", (24, 38), False), ("r0值", "10000V", (24, 38), False),
("r3名", "CT二次", (66, 80), False), ("r3值", "5A", (66, 80), False),
],
"sparam_oob": [
# PT二次 下限 1:把百位从 1 调到 0 → 0 < min → ENTER 被拒、留在编辑态并打"越界"
("越界提示", "越界", (38, 52), True),
("r1名", "PT二次", (38, 52), True),
("r0值未变", "10000V", (24, 38), False), # 别的行不受影响(编辑只作用选中行)
],
"sparam_save": [
("保存名", "保存", (24, 38), True),
("保存值", "OK", (24, 38), True), # 投递 → drain 落定 → 终态 OK
("页脚", "第 2 页,共 2 页", (140, 160), False),
],
"sparam_landed": [
# 保存后再按一次上移回到首屏:新值必须已在参数帧里(写路径的完整闭环)
("r0值新", "10001V", (24, 38), False),
("r1值旧", "100V", (38, 52), False), # 只提交改动行:没动的行还是旧值
("页脚", "第 1 页,共 2 页", (140, 160), False),
],
"sset": [
("标题", "定值", (0, 24), False),
("徽标", "未标定", (0, 24), False),
("r0名", "Ⅰ段电流", (24, 38), True), ("r0值", "10.00A", (24, 38), True),
("r7名", "过负荷延时", (122, 136), False), ("r7值", "10000ms", (122, 136), False),
("页脚", "第 1 页,共 3 页", (140, 160), False), # 18 项 + 保存行 = 19 行 → 3 页
],
"sword_cycle": [
# 二态行 ENTER:跳闸(1) → **回绕到 告警(0)**;未动的行保持 跳闸(只提交改动 id)
("标题", "控制字", (0, 24), False),
("r0名", "Ⅰ段跳闸", (24, 38), True), ("r0值", "告警", (24, 38), True),
("r1名", "Ⅱ段跳闸", (38, 52), False), ("r1值", "跳闸", (38, 52), False),
("r3名", "过负荷跳闸", (66, 80), False), ("r3值", "告警", (66, 80), False),
("保存名", "保存", (80, 95), False), ("保存值", "ENTER", (80, 95), False),
("页脚", "第 1 页,共 1 页", (140, 160), False),
],
"splate": [
("标题", "软压板", (0, 24), False),
("徽标", "未标定", (0, 24), False),
("r0名", "Ⅰ段投入", (24, 38), True), ("r0值", "投入", (24, 38), True),
("r7名", "过频投入", (122, 136), False), ("r7值", "投入", (122, 136), False),
("页脚", "第 1 页,共 2 页", (140, 160), False), # 10 项 + 保存行 = 11 行 → 2 页
],
"netcfg_save": [
("标题", "网口设置", (0, 24), False),
("r0名", "IP地址", (24, 38), False), ("r0值", "192.168.001.100", (24, 38), False),
@@ -280,6 +374,36 @@ TEXTS = {
],
}
# 反向文本断言表:页名 -> [(名称, 期望**不出现**的串, y 带, 是否反显态)]
# ⚠ inv 必须与"这一行本来会怎么画"一致,否则反向断言恒真、形同没写:
# 选中/编辑行是白底黑字 → inv=True;普通行黑底白字 → inv=False。
# 为什么需要"不该出现"这一侧:像素参考能钉住整张图,但参考图是按当前实现刷出来的
# ——如果"未标定"徽标哪天在两处都画了(整定 attach 判据失效 + 顺手 --update 刷参考),
# 像素比对会"一致"而事实已经错了。反向断言不依赖参考图,钉的是"这条规矩本身":
# · 徽标只属于整定态(可改值但判据停走),不属于只读态,也不属于四个查看页;
# · 只读态没有"保存"这件事(无 commit → 组件不画保存行)。
ABSENT = {
"sparam_ro": [
("只读页无未标定徽标", "未标定", (0, 24), False),
("只读页无保存行", "保存", (0, 140), False),
],
# 查看 4 页:task-2 给同一批描述符绑了 key_cb / edit_capable,像素必须零变化;
# 徽标与保存行都不该出现在它们身上(出现 = 整定 attach 漏判了权限)
"vparam": [("查看页无未标定徽标", "未标定", (0, 24), False),
("查看页无保存行", "保存", (0, 140), False)],
"vset": [("查看页无未标定徽标", "未标定", (0, 24), False),
("查看页无保存行", "保存", (0, 140), False)],
"vword": [("查看页无未标定徽标", "未标定", (0, 24), False),
("查看页无保存行", "保存", (0, 140), False)],
"vplate": [("查看页无未标定徽标", "未标定", (0, 24), False),
("查看页无保存行", "保存", (0, 140), False)],
# 编辑态的值列由 ui_w_editor_format 生成(**不拼单位**),非编辑态才拼:
# 所以"100V 不在这一行"就是"这一行确实在编辑中"的证据。
# 反过来正向断言"100"是没有意义的——它是"100V"甚至"10000V"的子串,
# 字形匹配器只保证 bbox 内干净、不检查词边界(实测在上一行的 10000V 上命中)
"sparam_edit": [("编辑态不打单位后缀", "100V", (38, 52), True)],
}
W = H = 160
STRIDE = W // 8 # I1:20 字节/行
@@ -468,16 +592,26 @@ def main():
run_sim(seq, shot, tmp)
shots[name] = open(shot, "rb").read()
n_pos = 0
for name, _seq in PAGES:
for tname, text, band, inv in TEXTS.get(name, []):
n_pos += 1
if not _match_text(shots[name], text, band, inv):
ok = False
print("[%s] FAIL 文本断言未命中:%s = %r(y 带 %s 反显=%s)"
% (name, tname, text, band, inv))
n_abs = 0
for name, _seq in PAGES:
for tname, text, band, inv in ABSENT.get(name, []):
n_abs += 1
if _match_text(shots[name], text, band, inv):
ok = False
print("[%s] FAIL 反向断言被命中:%s 不该出现却出现了(y 带 %s)"
% (name, tname, band))
if not ok:
print("FAIL: 文本级断言未通过(缺字/错标签);中止像素比对(现场保留在 %s)" % tmp)
return 1
print("文本级断言:%d 条全部命中" % sum(len(TEXTS.get(n, [])) for n, _ in PAGES))
print("文本级断言:%d 条命中 + %d 条反向(不该出现)全部通过" % (n_pos, n_abs))
# 2) 像素黄金参考比对 / 刷新
for name, seq in PAGES:
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.