Files
wandering1 94b0d79026 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 字),随该笔入库。
2026-10-28 07:23:16 +08:00

199 lines
7.6 KiB
C
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.
/*
* 模块名称:UI SOE 记录页共享组件实现(ui_w_soe)
* 模块功能:见 Drivers/BSP/UI/ui_w_soe.h——事件类型/元件显示名映射 + 每记录 2 行
* 渲染 + 单条读盘缓存;数据经注入 reader 取,未注入按空态渲染
* 适用范围:Drivers/BSP/UI
* 说明:与 ui_w_records.c 同构(同一份"分页读记录 → 两行/条 + 实例缓存 + 空态
* 伪行"骨架),差别只在记录形态是二进制解码后的视图结构
* 作者:王建锋
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 王建锋 创建(道E task-14)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_w_soe.h"
/* 私有宏定义区 */
/*
* 框架文本缓冲宽度(真源 Drivers/BSP/UI/ui_tokens.h 的 UI_BUF_STRLEN = 32)。
* 这里**有意不 include ui_tokens.h**:该头经 ui_port.h 牵入整条 LVGL 头链,而本组件
* 是纯数据 + 纯文本搬运、一个 LVGL 调用都没有——容量宏本地化之后,
* test/host/ui_data 可以直接把本 .c 编进主机单测核对显示名映射(同
* Drivers/BSP/UI/ui_w_records.h 把 ts/desc 容量宏本地化的同一先例与理由:
* 容量不足只会退化成占位显示,不会越界读)。框架令牌若加宽,本宏同步加宽。
*/
#define SOE_BUF_MAX 32u
#define SOE_INVALID_STR "--" /* 未知编号/越界/读失败的统一占位(ASCII) */
#define SOE_EMPTY_STR "无记录" /* 空态伪行(与文本记录页同字) */
#define SOE_NO_CLOCK_STR "时钟无效" /* 时标行值列:IV 位不显示猜测时间 */
/* 静态变量定义区 */
/* 注入的 SOE reader(模块级单例:SOE 落盘本就是全局一份,两端各只有一份实现;
* NULL = 未注入 → 空态。当前两端均未绑定,理由见头注"说明") */
static const ui_soe_reader_t *s_reader;
/*
* 事件类型显示名(下标 = UI_W_SOE_T_*;表长 = UI_W_SOE_T_COUNT)
* 词长分两档是**版式约束**不是措辞偏好:值列可用宽约 59 px(4 汉字),
* 名列约 94 px(7 汉字)——"动作/返回"是元件行的值列词,其余四类 4 字词
* 落在非元件事件的名列(口径 docs/UI设计/页面扩充详细方案.md §2.3 第 4 条)
*/
static const char *const s_type_name[UI_W_SOE_T_COUNT] = {
SOE_INVALID_STR, /* 0 未知/未映射(含读失败) */
"动作", /* 1 元件动作 */
"返回", /* 2 元件返回 */
"压板退出", /* 3 软压板退出 */
"压板投入", /* 4 软压板投入 */
"采样闭锁", /* 5 采样故障闭锁 */
"采样恢复", /* 6 采样故障恢复 */
"未标定闭锁", /* 7 未标定闭锁(照 App/log/soe_rec.h 与「数据可信」未标定即闭锁的原词,
* 不缩成"标定闭锁"——那个写法可以读成"把标定闭锁了",
* 而这一行要说的是"因未标定而闭锁",一字之差含义相反) */
"标定复归", /* 8 标定复归 */
};
/* 元件显示名(下标 = UI 元件编号 = 保护元件枚举序;越界不入表) */
static const char *const s_elm_name[UI_W_SOE_ELM_N] = {
"过流Ⅰ段", /* 0 */
"过流Ⅱ段", /* 1 */
"过流Ⅲ段", /* 2 */
"过负荷", /* 3 */
"过压", /* 4 */
"欠压", /* 5 */
"零序过压", /* 6 */
"过频", /* 7 */
"低频", /* 8 */
};
/* 函数定义区 */
/*
* 函数功能:注入 SOE reader
*/
void ui_w_soe_bind(const ui_soe_reader_t *a_reader) {
s_reader = a_reader;
}
/*
* 函数功能:把定长源串写入框架文本缓冲
* 入口参数:a_dst - 框架给的名/值缓冲(宽 = SOE_BUF_MAX,与框架 UI_BUF_STRLEN 同值) char *
* a_src - 源串(Flash 字面量或实例缓存) const char *
* src_bytes - 源缓冲总宽(含 NUL,整块拷入以带上终止符) size_t
* 返回值:无
* 限定条件:两者非 NULL;src_bytes 超 SOE_BUF_MAX 即按上限截断拷贝
* 函数说明:不用 strcpy/sprintf(门禁 CODE-RULE-06 禁用不安全函数);本组件
* 只做"整块搬运",不做格式化——格式化属页面行表(表现层)的事
*/
static void soe_put(char *a_dst, const char *a_src, size_t src_bytes) {
if ((a_dst == NULL) || (a_src == NULL)) {
return;
}
if (src_bytes > SOE_BUF_MAX) {
src_bytes = SOE_BUF_MAX;
}
(void)memcpy(a_dst, a_src, src_bytes);
}
/*
* 函数功能:事件类型编号 → 显示名
*/
const char *ui_w_soe_type_name(uint8_t a_type) {
if (a_type >= UI_W_SOE_T_COUNT) {
return SOE_INVALID_STR;
}
return s_type_name[a_type];
}
/*
* 函数功能:元件编号 → 显示名
*/
const char *ui_w_soe_elm_name(uint8_t a_elm) {
if (a_elm >= UI_W_SOE_ELM_N) {
return SOE_INVALID_STR;
}
return s_elm_name[a_elm];
}
/*
* 函数功能:总行数(每条记录 2 行;空态 = 单条伪行"无记录")
*/
uint16_t ui_w_soe_count(ui_w_soe_t *a_inst) {
uint32_t n;
(void)a_inst;
n = (s_reader != NULL) ? (uint32_t)s_reader->count() : 0u;
if (n == 0u) {
return 1u; /* 空态伪行(页脚"共 1 页",避免"共 0 页") */
}
return (uint16_t)(n * 2u);
}
/*
* 函数功能:逐行取数(偶数行 = 时标,奇数行 = 元件 + 事件类型)
*/
void ui_w_soe_refresh(ui_w_soe_t *a_inst, uint16_t a_row, char *a_name, char *a_value) {
uint32_t n;
uint32_t rec;
uint8_t ok;
a_value[0] = '\0';
n = (s_reader != NULL) ? (uint32_t)s_reader->count() : 0u;
if (n == 0u) {
(void)memcpy(a_name, SOE_EMPTY_STR, sizeof(SOE_EMPTY_STR));
return;
}
rec = (uint32_t)a_row / 2u;
ok = 0u;
if (a_inst->cache_valid != 0u && rec == a_inst->last_rec) {
ok = 1u; /* 同一条记录的两行共用一次读盘 */
} else if (s_reader != NULL) {
if ((s_reader->get)(rec, &a_inst->rec) == 0) {
a_inst->last_rec = rec;
a_inst->cache_valid = 1u;
ok = 1u;
} else {
a_inst->cache_valid = 0u; /* 越界/读失败:本行退化为占位,不留脏缓存 */
}
}
if (ok == 0u) {
(void)memcpy(a_name, SOE_INVALID_STR, sizeof(SOE_INVALID_STR));
return;
}
if (((uint16_t)a_row % 2u) == 0u) {
/* 时标行:IV 位或未对时 → 名列 "--"、值列"时钟无效",绝不显示猜测时间 */
if ((a_inst->rec.flags & UI_W_SOE_FLG_IV) != 0u || a_inst->rec.ts[0] == '\0') {
(void)memcpy(a_name, SOE_INVALID_STR, sizeof(SOE_INVALID_STR));
(void)memcpy(a_value, SOE_NO_CLOCK_STR, sizeof(SOE_NO_CLOCK_STR));
} else {
soe_put(a_name, a_inst->rec.ts, sizeof(a_inst->rec.ts));
}
return;
}
/* 内容行:元件事件 = 元件名 + 动作/返回;非元件事件 = 类型名(名列) */
if (a_inst->rec.elm != UI_W_SOE_ELM_NONE) {
const char *elm = ui_w_soe_elm_name(a_inst->rec.elm);
soe_put(a_name, elm, strlen(elm) + 1u);
/* 只有动作/返回两类是元件级事件;其余编号(理论不该出现)仍给类型名 */
if (a_inst->rec.type == UI_W_SOE_T_ELM_ASSERT
|| a_inst->rec.type == UI_W_SOE_T_ELM_RETURN) {
soe_put(a_value, ui_w_soe_type_name(a_inst->rec.type),
strlen(ui_w_soe_type_name(a_inst->rec.type)) + 1u);
}
} else {
const char *tn = ui_w_soe_type_name(a_inst->rec.type);
soe_put(a_name, tn, strlen(tn) + 1u);
}
}