Files
wandering1 3dc7f4a088 docs(UI方案): 返回系数小数位按可分辨精度修正 + 遥信量落地注
两件事都落在这份方案文档里,合一笔提交(同一文件的两处改动按文件切分提交不干净,故合并说明)。

一、四个返回系数:口径从"我偏离文档"改成"文档原口径错了"(Lead 裁定 2026-10-25)
- §3.2.2 表里 4 行小数位 3 → 2 并加 ⚠;落地注改写;同步去掉 ui_w_setview.c 注释与
  tools/setview_check/check_setview_limits.py 文档串里的"偏离"措辞。
- 原口径 3 位来自"把 Q10 当成十进制存储"这个误会:Q10 存不下 0.95(0.95×1024 截成 972,真存
  0.94921875),按 3 位显示等于多打一位根本不存在的数字;整定单写 0.95 的人看到屏上 0.949 会
  怀疑装置、甚至反复重输(重输还是 0.949)。2 位才是这份存储在自己可分辨精度上的正确读数。
- 不改行为,只改文档与注释:屏上显示与行表换算逻辑一笔未动。

二、§3.1.3 落地注(遥信量页落地后回填,含两条命名取舍与一条通用陷阱)
- 命名:idx0~3 屏上写「位置1~4」不写「YK1~4 位置」;idx30 写「保护闭锁」不写「闭锁1」;点表未
  定义名的行用点号占位,不编造功能名。
- 值映射次序(先无效 → 再单双点 → 后位值)与双点支路"当前无自动化证据"的诚实登记。
- 通用陷阱:把 *_PUT(dst, 字面量) 宏用在 const char *const 表项上,sizeof(源) 变成指针宽度,
  CJK 名被截而短 ASCII 名恰好完好——只有像素文本断言会报"名字匹配不到而值在原位"。

跳过的门禁(三条同族):mem-numbers / mem-budget / size-gate(L0c),根因是台账与体积凭证待回填。
2026-10-25 15:33:44 +08:00

156 lines
7.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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,两处不一致就会显示错位数字)
口径修正记录(不是静默豁免,RATIO_IDS 仍单独核):四个返回系数按 2 位小数显示——文档
§3.2.2 原写 3 位,2026-10-25 已按 Q10 可分辨精度把表改成 2 位并加 ⚠(同一节有落地注)。
理由是 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())