# -*- coding: utf-8 -*- """ 模块名称:定值查看行表一致性检查 模块功能:把 Drivers/BSP/UI/ui_w_setview.c 的四张视图表当数据核,比对真源 App/algo/prot_set.h 的枚举序与 App/algo/prot_set.c 的 g_prot_set_meta: ① id 必须恰好覆盖 0..39 各一次(漏 / 重 / 错位都会在这里炸;页面上只有当前 可见的 8 行会被像素断言看到,错位要翻到第 2、3 页才可能被人发现) ② 数值行 min/max 必须等于 meta 上下限按该行小数位换算后的值——行表里的上下限 就是从 meta 换算来的,本脚本是那条换算的可重跑证明(改 meta 不改行表就红) ③ 二态行必须带串表、数值行必须不带;单位串只允许 ASCII(不进中文字库) ④ 小数位与 id 查表一致(组件 get 只收到 id,两处不一致就会显示错位数字) 已知偏离(写死在下面,不做静默豁免):四个返回系数按 2 位小数显示,文档 §3.2.2 写 3 位。 理由是 Q10 存不下 0.95:0.95*1024 截成 972,真存 0.94921875,按 3 位显示会打出一位根本 不存在的数字——整定单写 0.95 的人看到 0.949 会怀疑装置,甚至反复重输(重输还是 0.949)。 用法:在仓库根执行 .venv/Scripts/python.exe tools/setview_check/check_setview_limits.py 退出码:0 = 一致;1 = 不一致(打印具体行与期望值);2 = 解析锚点丢失(结构改了,脚本要跟着改) 创建日期:2026-10-25(道E task-15 批 3) """ import re import sys # Windows 控制台默认按 CP936 写 stdout:本脚本要打印中文,先把输出锁到 UTF-8——否则同一份 # 脚本在别人的机器上会崩在半路(崩在半路 = 一条都没检查出来,比 FAIL 更难发现) try: sys.stdout.reconfigure(encoding="utf-8", errors="replace") sys.stderr.reconfigure(encoding="utf-8", errors="replace") except Exception: pass ENUM_H = "App/algo/prot_set.h" META_C = "App/algo/prot_set.c" TABLES = "Drivers/BSP/UI/ui_w_setview.c" RATIO_IDS = (25, 26, 38, 39) # 四个返回系数:本文档字符串里说明过的 2 位小数偏离 def q10(tok): m = re.match(r"PROT_SET_Q10\(([-0-9.]+)\)$", tok.strip()) v = float(m.group(1)) if m else float(tok.strip()) return int(round(v * 1024)) if m else int(v) def scaled(v, d): ip = v >> 10 fp = v & 0x3FF p10 = 10 ** d fr = (fp * p10 + 512) >> 10 if fr >= p10: ip += 1 fr -= p10 return ip * p10 + fr def load_meta(): h = open(ENUM_H, encoding="utf-8").read() body = h.split("typedef enum {")[1].split("PS_ID_COUNT")[0] ids = [x.strip() for x in re.findall(r"^\s+(PS_[A-Z0-9_]+)[, ]", body, re.M)] c = open(META_C, encoding="utf-8").read() blk = c.split("g_prot_set_meta[PS_ID_COUNT] = {")[1].split("\n};")[0] blk = re.sub(r"/\*.*?\*/", "", blk, flags=re.S) ent = re.findall(r"\{([^{}]*)\}", blk, re.S) if len(ent) != len(ids): print("[FATAL] 枚举 %d 项 / meta %d 项不一致" % (len(ids), len(ent))) return None meta = {} for name, e in zip(ids, ent): p = [x.strip() for x in e.split(",")] if len(p) != 5: print("[FATAL] %s 的 meta 行解析成 %d 段" % (name, len(p))) return None meta[name] = (1 if p[1].startswith("1") else 0, q10(p[2]), q10(p[3]), q10(p[4])) return (ids, meta) def load_tables(): t = open(TABLES, encoding="utf-8").read() rows = [] # 行表一行的形状:{ "名", NNu, UI_W_SETLIST_KIND_XXX, Du, min, max, opts, unit }, /* PS_名 */ # opts/unit 两种写法都要吃下:NULL 或 "V" 这类带引号串;用 \S+ 而不是 \w+,否则带引号 # 的单位匹配不上会让整行**静默跳过**(少检查一行比检查错更难发现) pat = re.compile(r"^\s*\{\s*\"(.*?)\",\s*(\d+)u,\s*UI_W_SETLIST_KIND_(NUM|ENUM),\s*(\d+)u,\s*(-?\d+),\s*(-?\d+),\s*(\S+),\s*(\S+?)\s*\},\s*/\*\s*(PS_[A-Z0-9_]+)\s*\*/", re.M) for m in pat.finditer(t): rows.append((m.group(9), int(m.group(2)), m.group(3), int(m.group(4)), int(m.group(5)), int(m.group(6)), m.group(7), m.group(8))) dm = re.search(r"s_dec\[UI_DATA_SET_N\] = \{(.*?)\};", t, re.S) if dm is None or not rows: print("[FATAL] 行表/查表锚点解析失败(行表写法改了要同步改本脚本)") return None dec = [int(x) for x in re.findall(r"\d+", dm.group(1))] return (rows, dec) def main(): lm = load_meta() if lm is None: return 2 ids, meta = lm lt = load_tables() if lt is None: return 2 rows, dec = lt bad = [] seen = [] if len(rows) != len(ids): print("[FAIL] 行表解析到 %d 行,但枚举有 %d 项——正则与行表写法脱节了,宁可报错也不要少检" % (len(rows), len(ids))) return 1 for ps, rid, kind, d, mn, mx, opts, unit in rows: if rid < 0 or rid >= len(ids): bad.append("%s: id %d 越界" % (ps, rid)); continue if ids[rid] != ps: bad.append("%s: id=%d 实际指向 %s(枚举序对不上)" % (ps, rid, ids[rid])) seen.append(rid) is_ctl = meta[ps][0] if kind == "ENUM": if opts == "NULL": bad.append("%s: 二态行没有串表" % ps) if dec[rid] != 0: bad.append("%s: 二态项在 dec 表里应为 0,实为 %d" % (ps, dec[rid])) if not is_ctl: bad.append("%s: meta 说它是模拟量项,行表却当二态行" % ps) else: if opts != "NULL": bad.append("%s: 数值行不该带串表" % ps) if dec[rid] != d: bad.append("%s: 行表小数位 %d 与 dec 查表 %d 不一致" % (ps, d, dec[rid])) emn = scaled(meta[ps][2], d) emx = scaled(meta[ps][3], d) if (mn, mx) != (emn, emx): bad.append("%s: 上下限 (%d,%d) != meta 换算 (%d,%d)(d=%d)" % (ps, mn, mx, emn, emx, d)) if is_ctl: bad.append("%s: meta 说它是投退/枚举项,行表却当数值行" % ps) if unit != "NULL": u = "" if unit.startswith('"'): u = unit.strip('"') else: bad.append("%s: 单位字段写法不认识:%s" % (ps, unit)) if u and any(ord(ch) > 127 for ch in u): bad.append("%s: 单位含非 ASCII 字符 %r(会进中文字库,上屏还可能缺字)" % (ps, u)) if sorted(seen) != list(range(len(ids))): dup = [x for x in set(seen) if seen.count(x) > 1] miss = sorted(set(range(len(ids))) - set(seen)) bad.append("id 覆盖不完整:重复 %s 缺失 %s" % (dup, miss)) ratios = [r for r in seen if r in RATIO_IDS] if sorted(ratios) != sorted(RATIO_IDS): bad.append("四个返回系数(%s)没有全在数值视图里,偏离白名单需要同步更新" % (RATIO_IDS,)) for m in bad: print("[FAIL] " + m) print("行表 %d 行 / dec %d 项 / meta %d 项 -> %s" % (len(rows), len(dec), len(ids), "OK" if not bad else "FAIL")) return 1 if bad else 0 if __name__ == "__main__": sys.exit(main())