Files
STM32F4-Base/docs/UI设计/页面扩充详细方案.md
T
wandering1 d64bbdc57f feat(wave): 故障录波数据面、接线与单通道回放页(软件闭环交付)
数据面(App/algo,纯逻辑,双端编译)
- wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask
  唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃
  兜底——下一次真故障无波可录比丢一段更贵
- wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带
  锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度
- 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式
- 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样
  纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉
- test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条
  自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合
- 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定)

接线(任务层)
- adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图),
  段头按最低有效位精确记账,不经浮点回算
- adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实;
  台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行
- defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到
  关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms
  补一条告警并记历史峰值(喂狗余量可核)
- Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留
  超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的,
  半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进
- Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到
  上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数;
  段文件字节布局零改动,三份合成段哈希逐字节不变
- protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体
  零改动

人机(160×160 单色屏)
- 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器
  得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30
- 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与
  垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、
  单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据
- ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、
  手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口
- 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有
  60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形
- 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变
- 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏
  22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、
  仪器用已在跑的单轮工作耗时探针,不新加埋点

台账与文档
- 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936
  字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名;
  散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建
  重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置)
- 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据);
  §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径
  风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」
- 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项
- 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划
  索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动)
- 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件
  形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记
  「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场
- 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行
  227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退

为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与
体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账
同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道

编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串
生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
2026-10-29 19:23:15 +08:00

656 lines
43 KiB
Markdown
Raw 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.
# 页面扩充详细方案(27 个空叶子 + 3 页回炉)
> **定位**:`docs/UI设计/页面补齐规划.md` 回答"怎么归并、分几批";本文回答**每一页显示什么、数据从哪来、要改哪些代码**——可直接照着开工的粒度。
> **数据通道**:`docs/UI设计/UI_数据供给设计说明.md`(快照推送 + 写路径 + 记录 reader 注入)。
> **数据真实性**:`docs/问题记录/采集与数据处理短板评测.md`(标定、遥测链、运行参数段的缺口)。
> **统计口径**:本文所有节点/叶子数字均由 `ui_menu_tree.txt` 脚本统计 + `Drivers/BSP/UI/ui_page_registry.c` 交叉核对,不引用旧文档结论。
---
## 1. 核对结论
### 1.1 统计口径与结果
| 项 | 数 | 来源 |
|---|---|---|
| 菜单节点总数 | **42** | `ui_menu_tab.h` 的 `UI_MENU_TAB_COUNT` |
| 叶子节点 | **33** | 无子项的节点 |
| 已绑定内容页 | **6** | `ui_page_registry.c` 的 `g_ui_page_map[]` |
| 密码页 | 1 | 非节点绑定(`@password=password`) |
| **空叶子** | **27** | 33 − 6 |
已绑定 6 页:`装置信息`(node 1)、`交流量`(3)、`直流量`(4)、`网口设置`(30)、`自检记录`(39)、`运行记录`(40)。
> ⚠ **本表 = 2026-10-25 立项时快照,按历史冻结不回改。** 2026-10-28 复扫现值:**已绑 28 叶 / 未绑 5 叶**;**2026-10-29 再复扫:已绑 30 叶 / 未绑 4 叶**(手动录波与录波回放两叶已绑)(判据两处互证:`ui_menu_tree.txt` 剔掉文件头注释里的示例行后 `#page=` 实数 = 30,与生成物 `ui_page_registry.h` 的 `UI_PAGE_MAP_COUNT` 一致);未绑 5 叶 = 遥信类型 / 遥信防抖 / 双点遥信 / 手动录波 / SNTP 设置。现状口径见 `docs/任务规划/UI补齐.md` §1 与 `docs/产品需求/产品需求设计文档.md` §8.2 / §8.3。
### 1.2 27 个空叶子清单(按菜单顺序)
| # | 叶子 | 节点 | 密码 | 父节点 |
|---|---|---|---|---|
| 1 | 遥信量 | 5 | — | 实时数据 |
| 2~5 | 参数 / 定值 / 控制字 / 软压板 | 8~11 | ✓ | 参数定值 → 整定 |
| 6~9 | 参数 / 定值 / 控制字 / 软压板 | 13~16 | — | 参数定值 → 查看 |
| 10~14 | 遥测死区 / 遥测系数 / 遥信类型 / 遥信防抖 / 双点遥信 | 18~22 | ✓ | 三遥设置 |
| 15~18 | 时钟设置 / 强制复归 / 手动录波 / 清除记录 | 24~27 | ✓ | 装置维护 |
| 19~20 | 通讯设置 / SNTP设置 | 29、31 | ✓ | 通讯参数(网口设置 30 已有页) |
| 21~27 | SOE记录 / 事故记录 / 操作记录 / 保护告警 / 保护启动 / 遥控记录 / 运行报告 | 33~38、41 | — | 记录查询 |
> 记录查询下的 9 个叶子是**平级**的(`SOE记录` 不是分组节点),因此 SOE 页与其余 8 页同级,不需要为它单独加一层菜单。
### 1.3 已建 6 页的健康度
| 页 | 状态 | 证据 |
|---|---|---|
| 网口设置 | ✅ 真数据 | `net_cfg_get()` + 保存交接 `net_cfg_save_request/state` |
| 自检记录 | ✅ 真数据 | `ui_w_records` → `syslog_count/get_record` |
| 运行记录 | ✅ 真数据 | 同上 |
| 装置信息 | ⚠ 静态字面量 | `s_rows[]` 6 行写死(`SV0.010` / `HW0.010` / `4454` / `2024.08.27`) |
| 交流量 | ⚠ 静态字面量 | `s_rows[]` 24 行全是 Flash 字面量,**不会变**(第 1 页数值取自参考图实测,看着像真值) |
| 直流量 | ⚠ 静态字面量 | `s_rows[]` 3 行写死(`ZL01/ZL02/Pa1`) |
**结论**:真正接通的只有 3 页(网口 + 2 个记录页)。所以"要动"的页数是 **27 个空叶子 + 3 页回炉 = 30 页次**。
### 1.4 与既有规划的关系
`docs/UI设计/页面补齐规划.md` 已把 27 个空叶子归并为 **7 个页面模块 + 1 个共享组件 + 2 项框架能力 + 1 个数据层**。本文沿用该归并,但**新增三项**(核对后才发现必需):
| 新增项 | 为什么规划里没有 |
|---|---|
| `ui_w_soe`(二进制 SOE 解码组件) | 规划只写了"需新组件",没定接口 |
| `ui_page_action`(确认操作页) | 强制复归 / 清除记录 / 手动录波三处共用,规划里散落在"操作页" |
| `run_par`(运行参数模型) | 三遥设置 5 页的地基;规划里当成"数据现成",实际整段不存在 |
---
## 2. 数据可用性:V0.1 到底能显示什么
这一节决定每页的"天花板"。**页面写得再漂亮,没有数据源就是假值 2.0。**
### 2.1 现有真值清单(可被页面消费)
| 数据 | API | 状态 | 说明 |
|---|---|---|---|
| 相量 Ua | `phasor_get(1, &p)` → `p.fAmSQ` | ✅ 真(**码值口径**) | 系数表只含 √2/N,未折算工程量 |
| 相量 U0(UN 通道) | `phasor_get(2, &p)` | ✅ 真(码值) | 零序电压基波 |
| 相量 Ia | `phasor_get(0, &p)` | ✅ 真(码值) | 过流/过负荷判据源 |
| 相量 Ia-2 次 | `phasor_get(3, &p)` | ✅ 真(码值) | 涌流制动用,页面可显示 |
| 相量 Ia-5 次 | `phasor_get(4, &p)` | ✅ 真(码值) | 选线用 |
| 频率 | `freq_get(&f)` + `freq_valid()` | 🔶 有条件 | 等周波未闭环(`docs/算法/继电保护功能复现规划.md` 「等周波采样(软件测频 + 调频闭环)」),104 点 `0x401D` 仍标 IV |
| 温度 | `adc_input_raw(ADC_SEM_TEMP, &raw)` | 🔶 仅码值 | 无换算 API |
| 遥信 6 点 | `iec104_pts_yx_read(idx, &v)` | ✅ 真 | YX15/16/17/18/23/31(`YX_VALID_MASK`) |
| 遥信 27 点 | 同上 | ❌ 恒无效 | 无硬件来源(开入未配置) |
| 定值 40 项 | `prot_set_get_f/get_ctl/checksum/locked/bad_mask` | ✅ 真 | Q10 定点 |
| 时钟 | `sys_clock_get_str(buf, len)` | ✅ 真 | 墙钟(可能慢 8 h,见台架记录) |
| 网口 | `net_cfg_get()` | ✅ 真 | IP/掩码/网关/MAC |
| 日志 | `syslog_count/get_record` | ✅ 真 | 仅运行、自检 2 类 |
| SOE | **无读取 API**(只有 `soe_store_post/drain`) | ❌ 需新增 | `/log/soe.log` 二进制 64 B |
| 元件状态 | `prot_elm_state/active_mask/name` | ✅ 真 | 当前态,无历史 |
| 功率/电度 | — | ❌ 禁止 | 双转换器无共同采样触发:跨转换器不同步,V0.1 不得算功率 |
| 直流通道 | — | ❌ 无硬件 | `ADC_SEM` 只有 8 电气 + 温度 |
| 开入/开出 | — | ❌ 无代码 | 固件完全没有开入开出实现 |
### 2.2 三档可用性分类(27 个空叶子的分档)
| 档 | 含义 | 页数 | 叶子 |
|---|---|---|---|
| **A 类** | 数据现成,只差页面 | **17** | 参数/定值/控制字/软压板 ×2(8)、遥信量、时钟设置、强制复归、清除记录、事故/操作/告警/启动/遥控记录(5) |
| **B 类** | 需新数据层或新 API | **8** | 遥测死区/遥测系数/遥信类型/遥信防抖/双点遥信(5)、SOE记录、运行报告、通讯设置 |
| **C 类** | 功能本身不存在 | **2** | 手动录波(无录波)、SNTP设置(无 SNTP 客户端) |
> A 类 17 页里有 5 页(事故/操作/告警/启动/遥控)**页面是 S 但写入方是 M**——`sys_log` 只有 2 个 ID,5 类新记录要先有写入点。这是本方案最容易低估的地方。
>
> C 类的 2 页与"直流量无通道"是同一个性质的问题:不是页面没做,是**被测对象不存在**。页面做得再好也只能显示"未配置"。
### 2.3 对页面设计的硬约束
1. **未标定必须可见**:`eng=0` 时页面要显式标"未标定",否则码值被当成伏特(现场误读)。
2. **无源点显示 `--`**,不显示 `0.000`。
3. **不在 UI 层算业务量**:功率、电度、方和根一律等 App 侧给出。
4. **中文名 ≤ 7 字**:左列 x=7、值列 x=101 → 名称可用宽度约 94 px,12 px 字宽 ≈ 7 个汉字。
5. **日志描述 ≤ 9 汉字**:`SYSLOG_DESC_MAX=28` 字节,UTF-8 汉字 3 B/字。
---
## 3. 逐页方案
### 3.1 实时数据(3 页)
#### 3.1.1 交流量(回炉,24 行 → 7 行)
**为什么不是 24 行**:参考布局的 24 行是"双回线 + 三相 + 功率 + 极值"的产物。V0.1 只有 8 个电气通道,其中 5 个(UB/UC/IB/IC/IN)在 104 点表里明确标 `IV`(削顶),相量实例只有 5 个(IA-F1 / UA-F1 / UN-F1 / IA-F2 / IA-F5)。**照抄 24 行等于 17 行 `--`**。
| 行 | 显示名 | 数据源 | 小数位 | 单位 |
|---|---|---|---|---|
| 1 | `Ua` | `phasor_get(1)` → `fAmSQ` | 3 | — |
| 2 | `U0` | `phasor_get(2)` | 3 | — |
| 3 | `Ia` | `phasor_get(0)` | 3 | — |
| 4 | `Ia2` | `phasor_get(3)`(二次谐波) | 3 | — |
| 5 | `Ia5` | `phasor_get(4)`(五次谐波) | 3 | — |
| 6 | `Hz` | `freq_get` + `freq_valid` | 2 | Hz |
| 7 | `标定` | 快照 `eng` → `未标定`/`二次值`/`一次值` | — | — |
- **第 7 行是诚实性行**:它不是测量值,是"上面 6 行的口径说明"。标定落地后自动变"二次值",无需改页面。
- 单页 7 行(8 行/页),不分页。
- 数据来源:`ui_data_rt_t` 的 `yc[0..5]`(下标语义见 `docs/UI设计/UI_数据供给设计说明.md` §9.4 一致性测试)。
- 规模:**S**(改行表 + `refresh` 钩子,约 80 行)。
**待拍板**:是否接受"7 行"而非参考的"24 行"。若产品要求保留 24 行骨架(便于将来扩通道),则改成 24 行固定表、当前 17 行显 `--`。
【2026-10-25 落地】按 7 行落了(`Drivers/BSP/UI/ui_page_ac.c`):品质位 IV/NT/RAW/SB/BL/OV 一律显 `--`,
第 7 行走快照 `eng` 真值语义(口径见 `docs/UI设计/UI_数据供给设计说明.md` §4.1)。24 行骨架并
没有"便于将来扩通道"的价值——真扩通道要连采样源一起动,那时页面改行表就是一行表的事。
#### 3.1.2 直流量(回炉,3 行 → 2 行)
V0.1 **无直流采样通道**(`ADC_SEM` 只有 8 路电气 + 温度),点表 `0x401E/0x401F` 也注明"FTU 无"。
| 行 | 显示名 | 数据源 | 说明 |
|---|---|---|---|
| 1 | `温度` | `adc_input_raw(ADC_SEM_TEMP)` | 码值,未标定 |
| 2 | `直流通道` | 常量 | 值列显示 `未配置` |
- 规模:**S**。
- **待拍板**(三选一):① 按上表做(推荐,保留菜单语义与点表一致);② 菜单改名"温度量"(改产品语义);③ 删节点(与参考/点表不一致)。
- 【2026-10-25 落地】按方案①落了(`Drivers/BSP/UI/ui_page_dc.c`)。温度值列写成 `码2048`:那个"码"字把
"这是 12 位原始码、不是温度"写进屏上而不是写进注释(通道本身带 `UI_DATA_Q_RAW`,页面不挂单位)。
#### 3.1.3 遥信量(新建)
33 点表,但只有 6 点有硬件来源(`YX_VALID_MASK`:YX15/16/17/18/23/31)。
| 方案 | 行表 | 优点 | 缺点 |
|---|---|---|---|
| **A 固定 33 行(推荐)** | 33 行固定名表,无效点值显 `--` | 与点表一一对应,现场可对点;开入落地后**页面零改动**自动变真 | 5 页里 4 页多 `--` |
| B 只显有效点 | `count_cb` 返回有效点数 | 紧凑 | 看不出缺哪些点;开入落地后行数变化,`ui-golden` 需重做 |
推荐 A。行表(节选,全 33 项按点表):
| 行 | 名 | 有效 | 值 |
|---|---|---|---|
| 1~4 | `YK1~4 位置` | ❌ | `--`(双点,无硬件) |
| 5~14 | `YX5~14` | ❌ | `--` |
| 15 | `故障总` | ✅ | `动作`/`复归` |
| 16 | `过流Ⅰ段` | ✅ | 同上 |
| 17 | `过流Ⅱ段` | ✅ | 同上 |
| 18 | `过流Ⅲ段` | ✅ | 同上 |
| 19 | `零序Ⅰ段` | ❌ | `--` |
| 20 | `零序Ⅱ段` | ❌ | `--` |
| 21 | `接地故障` | ❌ | `--` |
| 22 | `重载` | ❌ | `--`(定值源缺失,见 `docs/问题记录/采集与数据处理短板评测.md` §4.3 运行参数段) |
| 23 | `过载` | ✅ | `动作`/`复归` |
| 24~30 | `外部遥信1~7` | ❌ | `--` |
| 31~33 | `闭锁1~3` | 31 ✅ | 其余 `--` |
- 名表放页面侧(Flash const),**不进快照**——显示文案属表现层(`docs/UI设计/UI_数据供给设计说明.md` §8)。
- 双点遥信 4 点用 `iec104_pts_yx_is_dp()` 决定是否显示"分/合",当前恒无效。
- 规模:**M**(33 行名表 + `refresh` + 值映射,约 150 行)。
> **落地注(2026-10-25 道E task-15 批 4)**:已按方案 A 落地为 `Drivers/BSP/UI/ui_page_yx.c`
> (菜单 `遥信量 #page=yx`,8 行/页 = 5 页,黄金参考钉了第 1、2 两页)。四处要点:
> 1. **两处命名不照抄本表简写**:idx0~3 本表写「YK1~4 位置」,屏上只写「位置1~4」——遥信页
> 出现 YK 前缀会被读成遥控对象(那是另一页的事),与遥控的对应关系留在代码注释里;idx30
> 本表写「闭锁1」,屏上写「保护闭锁」(写侧 `FEED_YX_BIT_LOCK` 的语义即此,用编号反而丢信息)。
> idx4~13 与 idx31~32 点表未定义功能名,屏上如实写点号占位(`YX5`…、`YX32`),**不编造功能名**。
> 2. **值映射判定次序是「先无效、再单双点、最后位值」**:无效位上的位值不代表任何事实,排在
> 后面就会把垃圾位画成「复归」。单/双点由快照 `yx_dp` 决定,不在页面复制点表事实(理由见
> `docs/UI设计/UI_数据供给设计说明.md` §4.1 的 `yx_dp` 条目)。
> 3. 本表「双点遥信用 `is_dp()` 决定是否显示分/合」落地为 **合位/分位** 两词,但该支路 V0.1 恒
> 无效(无开入硬件),像素与主机单测都覆盖不到 → 已登记为开入落地后的台架判据,不假装测过。
> 4. 落地时踩到一条对所有名表页通用的陷阱:把既有的 `*_PUT(dst, 字面量)` 宏用在
> `const char *const` 表项上,`sizeof(源)` 就成了**指针宽度**,CJK 名被截成第一个汉字而短
> ASCII 名恰好完好——编译器 / cppcheck / 编码钩子全不响,只有像素文本断言报出「某行名字
> 匹配不到、值却在原位」。长度参数必须取**目标缓冲容量**(`UI_BUF_STRLEN`)。
### 3.2 参数定值(8 叶子 → 2 个模块)
8 个叶子(整定 4 + 查看 4)共用**一份** `ui_w_setlist` 组件 + **4 张视图表**。查看与整定的差别只是 `edit_capable` 与权限。
> 【2026-10-26 落地】查看 4 页(道E task-15 批 3)与**整定 4 页(道F task-2)都已落**:
八个叶子绑的是**同一批模块**(`view_param` / `view_set` / `view_word` / `view_plate`),
两态共用**同一份页面描述符**——差别不写在描述符里,而在 `pg_init` 按 `ui_content_page_is_editable()`
选 `ui_w_setview_attach()`(只读)还是 `ui_w_setview_attach_edit()`(可写)。
下方 §3.2.6 那张按两种描述符写的表已照此更正。
#### 3.2.1 40 项四视图划分
| 视图 | 项数 | 判据 | 页数(8 行/页) |
|---|---|---|---|
| 参数 | 8 | PT/CT 额定 + 4 个返回系数 | 1 |
| 定值 | 18 | 数值型(电流/电压/频率/延时) | 3 |
| 控制字 | 4 | `*_TRIP`(0 告警 / 1 跳闸) | 1 |
| 软压板 | 10 | `*_EN` + `PS_GLOBAL_EN` | 2 |
| **合计** | **40** | 与 `PS_ID_COUNT` 相等 | 7 |
> 划分只改**显示分组**,不改枚举序、不改校验和覆盖序(`prot_set` 的枚举序 = 数组序 = 校验和序,只增不删不改序)。
#### 3.2.2 参数视图(8 项)
| 行 | 显示名 | 枚举 | 单位 | 小数位 |
|---|---|---|---|---|
| 1 | `PT一次` | `PS_PT_PRI` | V | 0 |
| 2 | `PT二次` | `PS_PT_SEC` | V | 0 |
| 3 | `CT一次` | `PS_CT_PRI` | A | 0 |
| 4 | `CT二次` | `PS_CT_SEC` | A | 0 |
| 5 | `过量返回` | `PS_RET_OVER` | — | 2 ⚠ |
| 6 | `欠量返回` | `PS_RET_UNDER` | — | 2 ⚠ |
| 7 | `过频返回` | `PS_RET_OF` | — | 2 ⚠ |
| 8 | `低频返回` | `PS_RET_UF` | — | 2 ⚠ |
> 【2026-10-25 落地注·四张视图表(道E task-15 批 3)】四个查看页已接(`ui_page_view_param` /
> `_set` / `_word` / `_plate`,共用 `Drivers/BSP/UI/ui_w_setview.c` 的四张行表 + `ui_w_setlist`
> 组件)。三处口径落地时要说清楚,都写进了代码注释与提交正文,不留在这儿等人猜:
> 1. **返回系数按 2 位小数显示(本表原写 3 位,已按可分辨精度修正,⚠ 标在表里)**。这不是
> 口径让步而是原口径本身错了:3 位来自"把 Q10 当成十进制存储"这个误会。Q10 存不下 0.95
> (0.95×1024 截成 972 → 真存 0.94921875),按 3 位显示等于多打一位根本不存在的数字;
> 整定单上写 0.95 的人看到屏上 0.949 会怀疑装置、甚至反复重输(重输还是 0.949)。2 位才是
> 这份存储在自身可分辨精度上的正确读数。上下限、枚举序、单位口径由
> `tools/setview_check/check_setview_limits.py` 连 meta 一起核(已挂 `setview-check` 钩子)。
> 2. **单位挂在值串尾**(`10.00A` / `500ms`),不塞进显示名:值列只有数字会让 `60000` 既是 ms
> 又是 A;A/V/ms/Hz 全 ASCII,不占中文字库、不触发 font-charset。为此给 `ui_w_setlist_row_t`
> 加了 `unit` 字段(NULL = 不挂;只挂在非编辑态——编辑中是逐位输入串,挂单位会算错位光标)。
> 3. **上下限不自己拟定**:行表 min/max = `g_prot_set_meta` 的 Q10 上下限按该行小数位换算的结果,
> 事后由上述脚本核验(改了 meta 不改行表就会红)。
#### 3.2.3 定值视图(18 项)
| 行 | 显示名 | 枚举 | 单位 | 小数位 |
|---|---|---|---|---|
| 1 | `Ⅰ段电流` | `PS_OC1_CUR` | A | 2 |
| 2 | `Ⅰ段延时` | `PS_OC1_DLY` | ms | 0 |
| 3 | `Ⅱ段电流` | `PS_OC2_CUR` | A | 2 |
| 4 | `Ⅱ段延时` | `PS_OC2_DLY` | ms | 0 |
| 5 | `Ⅲ段电流` | `PS_OC3_CUR` | A | 2 |
| 6 | `Ⅲ段延时` | `PS_OC3_DLY` | ms | 0 |
| 7 | `过负荷电流` | `PS_OL_CUR` | A | 2 |
| 8 | `过负荷延时` | `PS_OL_DLY` | ms | 0 |
| 9 | `过压定值` | `PS_OV_VOL` | V | 1 |
| 10 | `过压延时` | `PS_OV_DLY` | ms | 0 |
| 11 | `低压定值` | `PS_UV_VOL` | V | 1 |
| 12 | `低压延时` | `PS_UV_DLY` | ms | 0 |
| 13 | `零序定值` | `PS_3U0_VOL` | V | 1 |
| 14 | `零序延时` | `PS_3U0_DLY` | ms | 0 |
| 15 | `过频定值` | `PS_OF_HZ` | Hz | 2 |
| 16 | `过频延时` | `PS_OF_DLY` | ms | 0 |
| 17 | `低频定值` | `PS_UF_HZ` | Hz | 2 |
| 18 | `低频延时` | `PS_UF_DLY` | ms | 0 |
#### 3.2.4 控制字视图(4 项)
| 行 | 显示名 | 枚举 | 值 |
|---|---|---|---|
| 1 | `Ⅰ段跳闸` | `PS_OC1_TRIP` | `告警`/`跳闸` |
| 2 | `Ⅱ段跳闸` | `PS_OC2_TRIP` | 同上 |
| 3 | `Ⅲ段跳闸` | `PS_OC3_TRIP` | 同上 |
| 4 | `过负荷跳闸` | `PS_OL_TRIP` | 同上 |
#### 3.2.5 软压板视图(10 项)
| 行 | 显示名 | 枚举 | 值 |
|---|---|---|---|
| 1 | `Ⅰ段投入` | `PS_OC1_EN` | `投入`/`退出` |
| 2 | `Ⅱ段投入` | `PS_OC2_EN` | 同上 |
| 3 | `Ⅲ段投入` | `PS_OC3_EN` | 同上 |
| 4 | `过负荷投入` | `PS_OL_EN` | 同上 |
| 5 | `过压投入` | `PS_OV_EN` | 同上 |
| 6 | `低压投入` | `PS_UV_EN` | 同上 |
| 7 | `零序投入` | `PS_3U0_EN` | 同上 |
| 8 | `过频投入` | `PS_OF_EN` | 同上 |
| 9 | `低频投入` | `PS_UF_EN` | 同上 |
| 10 | `保护总投入` | `PS_GLOBAL_EN` | 同上 |
#### 3.2.6 查看与整定的差别
| 项 | 查看(4 页) | 整定(4 页) |
|---|---|---|
| 权限 | 无密码 → `UI_PERM_VIEW` | 密码 → 按所输密码授 `VIEW`/`MODIFY`(123 = 修改、000 = 查看) |
| `edit_capable` | 描述符置 1,但 `is_editable()` 为假 → 走只读 attach | 同一份描述符,`is_editable()` 为真 → 走 `attach_edit` |
| 组件回调 | `commit = state = NULL` → **不画保存行**、ENTER 不消费(绑了 `key_cb` 也等价于没绑) | 绑 `set/commit/state` → 画保存行、放开编辑 |
| 页脚 | 只显示页码 | 额外一行"保存"(挂在**末行之后**,不是固定第 8 行) |
| 提交 | — | 只提交**改动位图**内的 id(一次封存、一次校验和),经 `ui_data_param_request()` 投递,App 侧 `drain` 落定 → 见 `docs/UI设计/UI_数据供给设计说明.md` §6.2 |
| 越界反显来源 | `par.bad_mask`(装载侧,且 `bad_valid=1` 才参与) | 同上 **+** `ui_data_param_err_mask()`(本次提交被整批预检挡下的项) |
| 闭锁 | 无写通路,不需要表达 | `par.locked=1` 或投递回 `ERR_LOCKED` → 保存行显 `LOCK` **且 ENTER 一律不消费**(禁编辑,不是只换个词) |
| 未标定 | 不画(查看页像素恒零变化) | 标题右侧「未标定」徽标(`par.cal_block`);与 `LOCK` 是两件事,两源两通道两词 |
> 保存行占掉一个可见行 → 整定页行数 = 视图项数 + 1,分页随之增加:参数 9 行 2 页、定值 19 行 3 页、控制字 5 行 1 页、软压板 11 行 2 页(合计 8 页)。这是"保存必须整表封签"带来的必然代价,不是设计缺陷。
> 【2026-10-26 落地实测 + 两条必修缺陷】上述分页与黄金参考逐页对上(`sparam_p2` /
> `sset` / `sword_cycle` / `splate` 四条用例的页脚分别是
> 「共 2 页 / 共 3 页 / 共 1 页 / 共 2 页」)。**「整表封签」指的是一次保存只封存一次、
> 只重算一次校验和,不是「把 40 项全写一遍」**——实际只写用户改过的项:Q10 存不下 0.95 这类
> 返回系数(真存 972/1024),从显示值回提全表会把用户没动的项顺手改掉。
>
> 这一批暴露了两条「只有第一个消费者才会暴露」的缺陷(都已修;写路径口径见
> `docs/UI设计/UI_数据供给设计说明.md` §6.2/§6.3):
> ① `ui_w_setlist` 的二态行 ENTER **不回绕**——落在最后一项时写出 opts 之外的
> 下标,屏上看着「按了没反应」,叠层里却已是越界值,随保存投出去必被预检判 `ERR_RANGE`;
> 控制字 4 行与软压板 10 行的出厂值**全是**最后一项(跳闸 / 投入),所以是必现而不是边角。
> 本方案落地之前,没有任何页面按过这条键(查看页 `commit=NULL` 走不到)。
> ② 编辑态的「参数帧已就位」原先只在**绘制路径**里被置位,而按键门读它 → 无头回归里开页后
> 第一个 ENTER 永远被挡(真机开页 10 ms 内就重绘,症状只在测试侧出现)。修法是让
> 「数据就位」与「画过没有」解耦:判就位之前先按 seq 做一次 sync(帧未变时只是一次比较)。
>
> 另记一条生成器侧的事实(不在本道修,已登记为待办「生成器幂等性检查」):菜单树重跑时
> `ui_menu_tab.h` 有一行注释跟着变了 —— 说明 HEAD 上生成器的模板文字与已提交的
> 生成物本来就**不同步**(改模板那笔没重跑生成器)。生成物是产物,以生成器输出为准即可,
> 但这条滞后不补,任何人都可能在不知情的情况下把它 revert 掉。
#### 3.2.7 编辑交互(复用网口设置页的模型)
| 键 | 正常模式 | 编辑模式 |
|---|---|---|
| ↑↓ | 移选(框架内置) | 当前位 +1 / −1(按项步进) |
| ←→ | ← 返回上级(框架) | 移动数字位光标 |
| ENTER | 进编辑 / 触发保存行(**闭锁态 `LOCK` 下一律不消费**) | 确认本项(越界则拒退并打提示,值不进叠层) |
| ESC | 关页(框架恒可拦截除外) | 关页(未保存的调整留在 RAM,不落盘) |
- 权限不足时 ENTER 一律忽略(`ui_content_page_is_editable()` 判);**权限够但装置写不进去时同样忽略**(`state()` 回 `LOCK` 或帧里 `locked=1`)——前者是「这人不能写」,后者是「这台装置现在写不进」,两件事都必须落在键路上,只改屏上词等于让用户白改一轮。
- 编辑中「←」是**移动光标**而不是关页:描述符要置 `key_intercept = 1u`,框架才先把 LEFT 交页面(`simulator/tests/` 的 `sparam_edit` / `sparam_oob` 两条用例走的就是这条路)。
- 越界项在保存失败后由 `bad_mask` 高亮(`draw_cb` 画反显)。
- 规模:`ui_w_setlist` **M**(约 250 行),`ui_w_editor` **M**(约 180 行)。
### 3.3 三遥设置(5 页,阻塞于 `run_par`)
**现状**:死区是**编译期常量**——`App/protocol/iec104/iec104_pts.c` 的 `k_yc[].deadband`(第 244 行 `if (d > k_yc[i].deadband)`)。其余四项(系数/类型/防抖/双点)在固件里**完全没有对应结构**。
**所以这 5 页不是页面工作,是一个参数模型工作。**
| 页 | 项数 | 数据模型 | 消费方 |
|---|---|---|---|
| 遥测死区 | 9 | `run_par.deadband[9]`(float,工程量) | `iec104_pts_cov_check`(替换 `k_yc[].deadband`) |
| 遥测系数 | 9 | `run_par.gain[9]`(Q10 微调,1.000 基准) | `iec104_pts_yc_read`(乘在 fAmSQ 上) |
| 遥信类型 | 33 | `run_par.yx_type[33]`(单点/双点/常闭) | `iec104_pts` 的 `M_SP`/`M_DP` 选择 |
| 遥信防抖 | 33 | `run_par.yx_debounce[33]`(ms) | 开入采集(尚未实现,**当前无消费方**) |
| 双点遥信 | 4 | `run_par.dp_map[4]`(虚端子映射) | 开入采集(同上) |
**关键判断**:
- 死区/系数两项**有消费方**(104 层),做完立刻生效 → 值得先做。
- 遥信类型/防抖/双点三项的消费方是**开入采集**,而开入采集在固件里不存在 → 现在做页面 = 配置无处生效,属于"存了不用的参数"。**建议随开入量采集一起做**,不在本批。
- 因此本批只做 **2 页(死区、系数)**,其余 3 页等开入。
**`run_par` 模块要点**(与 `prot_set` 并列,不合并——运行参数是"可随时改的运行旋钮",定值有 sum32 封存/闭锁语义,混在一起会搅乱闭锁判断):
```c
/* App/algo/run_par.h —— 运行参数(无封存语义,可随时改) */
#define RUN_PAR_YC_N 9u
#define RUN_PAR_YX_N 33u
int run_par_init(void); /* 出厂默认 */
float run_par_deadband(uint8_t yc_idx);
int run_par_set_deadband(uint8_t yc_idx, float v); /* 范围校验 */
/* 落盘走 cfg_file 新段 "runpar";键名 ≤15 字符(CFG_FILE_MAX_NAME_LEN) */
```
- 规模:`run_par` **M**(表驱动 + 单测),2 个页面 **M**。
- 104 侧改动:`k_yc[].deadband` 改为查 `run_par`(1 行 + 去常量表字段),`yc_read` 乘增益(3 行)。
### 3.4 装置维护(4 页)
| 页 | 形态 | 数据/接口 | 规模 | 阻塞 |
|---|---|---|---|---|
| 时钟设置 | 表单(6 字段:年月日时分秒) | `sys_clock_get_time()` 预填 → `sys_clock_set_unix()` 提交 | **M** | 无 |
| 强制复归 | 操作页(确认框) | `prot_elm_request_reset()` | **S** | 无 |
| 手动录波 | 操作页(确认框) | **无录波模块** | — | ⛔ 等录波 |
| 清除记录 | 操作页(选记录类型 + 确认) | 需新增 `syslog_clear(id)` + `soe_clear()` | **M**(API S + 页 M) | 无(API 自建) |
- **时钟设置**:字段级编辑(年/月/日/时/分/秒),提交后立即 `sys_clock_set_unix()`;时钟无效时(`clock_ok=0`)允许设置。注意 RTC 可能慢 8 h(台架记录),页面不修正,只如实写。
- **强制复归**:调 `prot_elm_request_reset()`(任意上下文安全,单字标志),页脚回"已请求复归"。
- **手动录波**:**本批不建页**。菜单节点保留空叶子(现状行为 = 进空白终点框)。录波落地后再补。
- **清除记录**:需要 `sys_log` 加删除 API(按 ID 删文件 + 重建 + 计数归零),以及 `soe_store` 加清空 API。页面列出 9 类记录供选择,二次确认。
### 3.5 通讯参数(3 页)
| 页 | 形态 | 现状 | 结论 |
|---|---|---|---|
| 网口设置 | 已有真页 | `net_cfg` 全链闭环 | ✅ 不动 |
| 通讯设置 | 只读信息页 | **无配置模型**(104 端口 2404、CA=1 都是 `#define`) | 做只读页,值经快照下发 |
| SNTP设置 | — | **无 SNTP 客户端** | ⛔ 本批不建 |
**通讯设置 4 行**(值必须在 App 侧取、经快照下发,页面不得自己 include 协议头):
| 行 | 名 | 值来源 |
|---|---|---|
| 1 | `104端口` | `IEC104_PORT`(App 侧常量) |
| 2 | `公共地址` | `iec104_serv` 的 CA(当前固定 1) |
| 3 | `显示端口` | RemoDispBus 端口常量 |
| 4 | `FTP用户` | FTP 配置(`admin`,鉴权启用后) |
- 需要在 `ui_data_rt_t` **尾部追加**一个 `comm` 子结构(`port`/`ca`/`disp_port` + 8 B 用户名),按 `docs/UI设计/UI_数据供给设计说明.md` §4.3 的扩展规则升 `UI_DATA_VER`。
- 规模:**S**(快照 +4 字段,页面 60 行)。
- **SNTP设置**:等 SNTP 客户端(`App/` 下无任何 SNTP 代码)。现在建页只能存"没人读的参数"。
### 3.6 记录查询(9 页)
9 页 = **2 个组件 + 5 个新日志 ID + 1 个二进制解码器 + 1 个摘要页**。
| 页 | 组件 | 数据源 | 页面规模 | 写入方规模 |
|---|---|---|---|---|
| 自检记录 | `ui_w_records` | `SYSLOG_SELFTEST` | ✅ 已有 | — |
| 运行记录 | `ui_w_records` | `SYSLOG_RUN` | ✅ 已有 | — |
| 事故记录 | `ui_w_records` | **新** `SYSLOG_ACCIDENT` | S | **M** |
| 操作记录 | `ui_w_records` | **新** `SYSLOG_OPER` | S | **M** |
| 保护告警 | `ui_w_records` | **新** `SYSLOG_ALARM` | S | **M** |
| 保护启动 | `ui_w_records` | **新** `SYSLOG_START` | S | **M** |
| 遥控记录 | `ui_w_records` | **新** `SYSLOG_YK` | S | **M** |
| SOE记录 | **新** `ui_w_soe` | `/log/soe.log`(二进制 64 B) | **M** | 已有(`soe_store_post`) |
| 运行报告 | 专用摘要页 | 最近一条 SOE 动作事件 | S | 无 |
**5 个新日志 ID 的写入点**(这是本批真正的成本):
| 记录 | 写入点 | 判据 |
|---|---|---|
| 事故记录 | `App/task/protect_task.c` 的 `s_log_active_edge()` | `asserted` 上升沿 **且** 该元件 `*_TRIP` 控制字 = 1 |
| 保护告警 | 同上 | `asserted` 上升沿 **且** `*_TRIP` = 0 |
| 保护启动 | 同上(新增 `picked` 边沿检测) | `picked` 上升沿 |
| 操作记录 | 定值保存成功 / 强制复归 / 时钟设置 / 清除记录(4 处) | 动作成功后写一条 |
| 遥控记录 | `iec104_pts_yk_exec()` 返回处 | 每次执行结果(成功/失败/选择态) |
> **本表只是页面侧的需求清单**(记录页要消费哪几类),**不是记录层的登记表**:
> 5 个 ID 的定名、记录格式、条数与字节上限、写入时机、以及是否真的落地,一律以
> `App/log` 侧为准,权威口径 `docs/子系统设计/运行与自检记录设计说明.md`;
> 实施进度由记录侧任务回报后回填本文(本道不代记,免得同一件事在两份文档里各说一遍——
> 同一事实只在一处详述,见 `docs/文档规范.md`)。
**`ui_w_soe` 要点**:
```c
/* Drivers/BSP/UI/ui_w_soe.h —— 与 ui_w_records 同构,但数据是二进制 */
typedef struct {
uint8_t cache_valid;
uint32_t last_rec;
char ts[20];
char line[24]; /* "过流Ⅰ段 动作" */
} ui_w_soe_t;
uint16_t ui_w_soe_count(ui_w_soe_t *inst);
void ui_w_soe_refresh(ui_w_soe_t *inst, uint16_t row, char *name, char *value);
```
- 数据经注入的 reader 取(不 include `soe_store.h`),与 `ui_rec_reader_t` 同一模式但返回 `soe_rec_t`。
【2026-10-25 落地】页已接:`Drivers/BSP/UI/ui_page_soe.c`(菜单树 `SOE记录 #page=soe`,
`tools/menu_gen/gen_menu.py` 重生成 + 双端注册自动化),reader 在两端适配层绑定
(真机 `App/task/ui_task.c` / 模拟器 `simulator/main.c` 的固定 3 条假记录)。
只读 API **不带类型掩码**(Lead 裁定 2026-10-25):5 个记录页怎么分组是界面语义,不进
日志存储层;环容量小,页渲染全扫无成本问题,真变大了再加带 mask 的优化 API(YAGNI)。
显示名(含类型 7 未标定闭锁 / 8 标定复归)留在 UI 侧——与"屏上的话归 UI、数据归 App"一致。
- 需要 `soe_store` 新增只读 API:`soe_count()` / `soe_read(idx, &rec)`(FatFs `f_open` + `f_lseek`,与 `syslog_get_record` 同构)。
【2026-10-25 落地】实到为 `App/log/soe_store.c` 的 `soe_store_count()` / `soe_store_read()`,照
`syslog_get_record` 那套**窗口 + 代数 + try 锁**纪律实现(8 条 / 512 B 一窗,命中即零 I/O)。
可见性规则钉在 `App/log/soe_store.h` 头注:条数只数整条 64 B 槽、写者在卷锁区间内自增代数、
单条版本/校验和不过一律 `-1`——**读者不得看到半成品记录**;`count` 与 `read` 之间新落一条只
容忍"本轮少看一条"(200 ms 重绘自然追上),不容忍读到半条。
- 每条记录 2 行(时间戳 / "元件+类型"),与文本记录页一致 → 用户学习成本为零。
**运行报告(最小可用版)**:3 行——`最近动作`(元件名)、`动作时间`、`当前状态`。数据取最后一条 `SOE_T_ELM_ASSERT`。完整版(故障前后有效值 + 波形摘要)依赖录波,不在本批。
**记录容量**:新增 5 类日志需各自定上限(参考现有:运行 1024 条/64 KB、自检 512 条/32 KB)。按"事故/告警/启动各 512 条 + 操作/遥控各 256 条"计 ≈ **128 KB**(64 B/条),NAND 余量需按 `docs/子系统设计/运行与自检记录设计说明.md` 的容量口径核对。
### 3.7 装置信息(回炉)
现在 6 行全是字面量,其中"软件校验 4454"是参考项目的值,与本机无关。
| 行 | 名 | 值来源 |
|---|---|---|
| 1 | `终端型号` | 编译期常量 `APP_MODEL`(`F30`) |
| 2 | `软件版本` | 编译期常量 `APP_SW_VER` |
| 3 | `硬件版本` | 编译期常量 `APP_HW_VER` |
| 4 | `程序日期` | `__DATE__`(编译期) |
| 5 | `软件校验` | 编译期常量(构建脚本生成,或先显示 `--`) |
| 6 | `MAC地址` | `net_cfg_get_mac()` |
| 7 | `IP地址` | `net_cfg_get()` + `net_cfg_fmt_ipv4()` |
| 8 | `运行时长` | 由 `ui_data_feed` 算开机 tick 差值(页面只拿秒数,不碰 RTOS) |
- 新增 `App/app/app_ver.h`(4 个宏 + `__DATE__`),页面经快照取值。
- 规模:**S**。
- 说明:第 5 行"软件校验"若没有构建期 CRC 生成步骤,**先显示 `--` 而不是抄参考值**。
- 【2026-10-25 落地】8 行全接真值(`Drivers/BSP/UI/ui_page_device_info.c`):型号/软件/硬件/校验/日期/
运行时长取装置信息帧,MAC/IP 取 `net_cfg` RAM 副本(与网口设置页同一个 `net_cfg_fmt_ipv4`,
不另起第二套格式化)。两处顺带修正的口径:① 标签去全角冒号、值列左对齐 x=56——17 字符 MAC
要 102 px,参考图的 x=86 容不下;② 页脚从参考遗留的固定"第 10 页"改成真页码"第 1 页,共 1 页"
(屏上不出现与本机无关的数字,Lead 裁定 2026-10-25,像素基线随之单独提交)。
---
## 4. 组件与模块
### 4.1 已有 vs 待建
| 组件 | 状态 | 用途 |
|---|---|---|
| `ui_content_page` | ✅ 已有 | 页面框架(4 钩子 + 权限) |
| `ui_menu` / `ui_menu_view` / `ui_menu_tab` | ✅ 已有 | 导航 + 显示 + 生成表 |
| `ui_w_records` | ✅ 已有(需改为 reader 注入) | 文本记录页 |
| `ui_data` | ⛔ 待建(步骤1) | 快照存储 |
| `ui_data_feed` | ⛔ 待建(步骤2) | 快照组装 |
| `ui_w_setlist` | ⛔ 待建 | 定值四视图共用 |
| `ui_w_editor` | ⛔ 待建 | 数值编辑器 |
| `ui_w_soe` | ⛔ 待建 | SOE 二进制解码 |
| `ui_page_action` | ⛔ 待建 | 确认操作页(复归/清除/录波) |
| `run_par` | ⛔ 待建 | 运行参数模型 |
### 4.2 六个待建件的接口要点
| 组件 | 关键接口 | 关键约束 |
|---|---|---|
| `ui_data` | `publish_rt/par`、`rt/par`、`rt_seq`、`param_request/state` | 单写者同上下文;不 include 任何 App 头 |
| `ui_data_feed` | `ui_data_feed_tick()` | 唯一填充者;不在绘制路径调用 |
| `ui_w_setlist` | `bind(view_tbl, count, commit_cb)` | 视图表含 `{name, enum_id, dot, unit}`;不直接调 `prot_set` |
| `ui_w_editor` | `edit_begin(row)` / `edit_key(key)` / `edit_commit()` | 只改 RAM 副本;提交由页面统一发起 |
| `ui_w_soe` | 同 `ui_w_records` | reader 注入;不 include `soe_store.h` |
| `ui_page_action` | `bind(title, prompt, ok_cb)` | 二次确认;`ok_cb` 只投递请求,不阻塞 |
---
## 5. 菜单树改动
**本批不改菜单树**——27 个空叶子全在现有 42 节点里,`#page=` 注记逐个补上即可(改 `ui_menu_tree.txt` 后重跑 `python tools/menu_gen/gen_menu.py`)。
> 【2026-10-26 已改一笔】整定 4 叶(参数 / 定值 / 控制字 / 软压板)已补 `#page=`,
> 绑的是与查看列**同一批模块**(一个模块被两个叶子绑定是生成器允许的,`validate_pages()` 按
> 集合去重判孤儿页)。重跑生成器后 `UI_PAGE_MAP_COUNT` 12 → 16,
> `uvprojx` 与 `CMakeLists.txt` 未变(没新增文件)。
> 生成器幂等性的一条滞后见 §3.2.6 落地注末段。
**唯一建议改动**(可选,需产品拍板):补参考项目的"厂家设置"分支。参考侧该分支有 18 个叶子(元件配置、恢复默认 ×5、交流显示、装置调试 ×8、版本信息)。若采纳,精简为 3 个叶子 + 2 个分组:
| 建议叶子 | 理由 |
|---|---|
| 恢复默认 → 全部恢复 / 仅定值 / 仅软压板 | 现场救砖刚需 |
| 装置调试 → 交流虚遥测 / 虚拟遥信 | 无标准源时的唯一验证手段 |
| 装置调试 → 动作出口 | 出口测试 |
(版本信息不单列,并入装置信息。)
→ 42 + 9 = **51 节点**(`厂家设置` + `恢复默认` + `装置调试` 三个分组节点 + 6 个叶子)。未超 255 上限,但 `ui-golden` 的全部菜单屏参考图需重拍。
---
## 6. 分批与依赖
### 6.1 批 0:前置能力(不产出可见页面,但决定后面是否可信)
| 步 | 内容 | 验证 |
|---|---|---|
| 0-1 | `ui_data` + 主机单测 | `test/host/ui_data` 全绿 |
| 0-2 | `ui_data_feed` + `ui_task` 接线 | 首帧串口锚点;`@build` 0/0 |
| 0-3 | `ui_w_editor`(数值编辑器) | 模拟器可改值 |
| 0-4 | `ui_page_action`(确认操作页) | 模拟器确认/取消 |
### 6.2 批 1~4
| 批 | 页面 | 页数 | 依赖 |
|---|---|---|---|
| **1** | 交流量(回炉)、直流量(回炉)、装置信息(回炉)、参数/定值/控制字/软压板**查看**(4)、遥信量 | **8** | 批 0 |
| **2** | 整定 4 页、时钟设置、强制复归、清除记录、通讯设置 | **8** | 批 1 + 写路径 |
| **3** | 事故/操作/告警/启动/遥控记录(5)、SOE记录、运行报告、遥测死区、遥测系数 | **9** | 批 2 + 5 个写入方 + `run_par` + `soe` 读 API |
| **4**(可选) | 遥信类型/遥信防抖/双点遥信、手动录波、SNTP设置 | **5** | 开入采集 / 录波 / SNTP 客户端 |
### 6.3 依赖关系
```
批0 ui_data ──┬─→ 批1 实时数据 3 页 + 查看 4 页
├─→ 批2 整定 4 页(写路径)
└─→ 批3 记录 5 页(reader 注入)+ run_par 2 页
开入采集 ─────────→ 批4 遥信类型/防抖/双点
录波 ─────────────→ 批4 手动录波 + 运行报告完整版
SNTP 客户端 ──────→ 批4 SNTP设置
```
**关键路径**:批 0 → 批 1 → 批 2。批 3 的 5 个记录页可以并行(写入方在 App 侧,页面在 BSP 侧,互不阻塞)。
---
## 7. 预算
| 项 | 量 | 说明 |
|---|---|---|
| Flash(页面代码) | ≈ 12 KB | 8 个新页面文件(`yx`/`set_ro`/`set_rw`/`runpar`/`clock`/`comm`/`soe`/`report`)× ~1.2 KB + 3 个回炉页 |
| Flash(组件) | ≈ 8 KB | 6 个组件(`ui_data`/`ui_data_feed`/`ui_w_setlist`/`ui_w_editor`/`ui_w_soe`/`ui_page_action`)× ~1.4 KB |
| Flash(字库) | ≈ **+10 KB** | 新增约 25 字(段/投/延/零/频/荷/闸/跳/低/过/Ⅰ/Ⅱ/Ⅲ 等),12 px 1 bpp ≈ 370 B/字 |
| ROM 总量 | 268 KB → ≈ 298 KB | 1 MB Flash,余量充足 |
| RAM(`ui_data`) | ≈ 380 B | 见 `docs/UI设计/UI_数据供给设计说明.md` §10 |
| RAM(页面缓存) | ≈ 130 B/数据页 | 6 个数据页 ≈ 800 B |
| LVGL 池 | 0 增量 | 页面全自绘,无子对象 |
| 记录存储 | +160 KB NAND | 5 类新日志,需核 NAND 余量 |
**字库是唯一需要提前处理的硬依赖**:新增中文名不在 `ui_cn_chars.txt` 里,必须重跑 `tools/font_gen/make_font.py`,否则屏上显示方块。
---
## 8. 验收判据
| # | 判据 | 方法 |
|---|---|---|
| 1 | 27 个空叶子全部有页(除批 4 延后的 5 个) | 遍历菜单树,进入每个叶子不出现空白框 |
| 2 | 3 个静态页全部接真数据 | 改变输入信号 → 页面值变 |
| 3 | 未标定态可见 | `eng=0` 时交流量第 7 行显示"未标定" |
| 4 | 无源点显示 `--` | 遥信量无效点、直流量直流通道 |
| 5 | 定值整定闭环 | 改 → 保存 → 重启 → 值保持 + 校验和自检通过 + 主站 C_RD 一致。**2026-10-26 半收口**:模拟器侧「改 → 保存 → `OK` → 回首屏新值已在帧里」与「越界被拒、值不落盘」已由像素 + 文本断言覆盖(`sparam_save` / `sparam_landed` / `sparam_oob`);**重启保持与 C_RD 一致只能上板**(模拟器无 NAND)。另注意 40 项里 **13 项 `comm_addr = 0`**(投退、返回系数、过频/低频段、全局软压板)主站不可寻址,不能拿 C_RD 当它们的判据 |
| 6 | 分层门禁无新增 | `check_layer_deps.py` 退出码 0,基线 6 → 3。**现状:3**(记录 reader 已削 3 条;写路径走注入式 ops 函数表,未新增 include,见 `docs/UI设计/UI_数据供给设计说明.md` §6.2) |
| 7 | 像素回归稳定 | `ui-golden` 连续两次零差异。**已验(2026-10-26)**:27 页连跑两次逐字节一致——**该下限此后已扩至 66 张**(新增 clock / reset / clear 系列 13 张,其后 47→53→60→66,2026-10-29 录波 UI 道 +6 张),66 张上的双次逐字节一致**已验(2026-10-29:任务树重建模拟器后同一批 66 张连跑三次逐字节一致,页面池水位 5,448 B;菜单侧 `check_menu_rows.py` 8 状态连跑三次亦逐字节一致,5,368 B;三次之间未用更新模式写过任何参考图,故这是同口径的传递性证据)** |
| 8 | 每个新页有 golden 参考 | `simulator/tests/ref/` 新增对应 `.bin`。**整定 4 页已有 9 张**(含保存/越界/落定/回绕各态);参考图入库,改它 = 改判据,须逐页过目 |
---
## 9. 待拍板
| # | 问题 | 选项 | 建议 |
|---|---|---|---|
| 1 | 交流量 7 行 vs 参考 24 行骨架 | 7 行紧凑 / 24 行留位 | **7 行**(不显示假 `--`) |
| 2 | 直流量页处置 | 温度+未配置 / 改名温度量 / 删节点 | **温度+未配置** |
| 3 | 遥信量 33 行固定 vs 只显有效点 | 固定 33 / 动态 6 | **固定 33**(可对点,零维护) |
| 4 | 三遥设置 5 页是否全做 | 全做 / 只做死区+系数 | **只做 2 页**(另 3 页配置无处生效) |
| 5 | 是否补厂家设置分支 | 补 6 节点 / 不补 | 看送检要求 |
| 6 | 5 类记录是否都在本批 | 全做 / 先事故+操作 | 全做(写入点集中在一处) |
---
## 附:本文与既有文档的分工
| 文档 | 回答什么 |
|---|---|
| `docs/UI设计/页面补齐规划.md` | 27 页怎么归并、分几批、内存预算 |
| **本文** | 每一页显示哪些行、数据从哪来、要改哪些代码 |
| `docs/UI设计/UI_数据供给设计说明.md` | 数据怎么从 App 到 UI(通道/契约/并发/写路径) |
| `docs/UI设计/内容页架构.md` | 页面框架本身(描述符/钩子/权限) |
| `docs/问题记录/采集与数据处理短板评测.md` | 为什么有些数据现在没有(标定/遥测链/运行参数/录波) |