52e28885cb
三个薄页面模块 ui_page_clock / ui_page_reset / ui_page_clear,页面注册由生成器 自动写双端(裁定 A-1:「三叶各绑一页」与「零新增 .c」不可兼得,选薄页面加生成器, 不写手写 node switch——那种分发在增删叶子时会静默换功能,且没有任何门禁照得到)。 数据侧是注入式函数表:UI 不 include App 层的头。两条硬理由都实测过——分层方向 (layer-deps)之外,模拟器根本不链 sys_clock,ALLOW 豁免挡得住 layer-deps, 挡不住模拟器链接期。面板对时的唯一真判据是回读 RTC:sys_clock_set() 恒返 0 (RTC 写失败只打日志),所以真机侧写 1 后回读 1,最坏约 200 ms 落在一次「保存」 按键上,IWDG 800 ms 窗有余;上界与依据都写进注释。 「无时钟」徽标位置有两个像素级实测坑:放标题带右侧会被 4 字标题连成「时钟设置 无时钟」一句话;放页脚带左侧 x=7 会压住页脚——页脚从 x=6 起排,不是居中。 最终落在清单末行与页脚之间的空带 y=124。clear_done 那张顺带给出像素证据: 清 run.log 之后「操作记录」那一行从 2 变 3,页面上没缓存、没撒谎。 ui_w_records 的 UI_W_REC_LOG_* 顺序必须等于 syslog_id_t 顺序(7 类,只准尾追加), 已加编译期顺序守卫;执行侧 7 条映射表同样带编译期守卫。 测试与真值:内容页黄金 27 → 40 页(+13),文本断言 +68、反向断言 ABSENT +19; 27 张旧参考 git diff 为空 = 逐字节未变。往返池 5448 B 与菜单常驻堆 5368 B 均未变; 字库未扩(源码在用 171 并 KEEP_EXTRA 39 = 清单 183),font-charset 与 cn-chars、 cn-font 字节一致。@build 0 错误 0 警告;uiTask 静态最坏链 1,336 B / 16,384 (余 91%)与基线相同,页面钩子没进最坏链。 一条测试学结论单独留痕:反向断言不能当「位置不重叠」的证据——本脚本的字形匹配 把 y 带判得比字面宽(徽标在 125..136、页脚在 144..155,仍被 (140,160) 判命中)。 该防线改由正向断言「页脚未被徽标压住」承担(压字那一版实测过不了它)。 两条自我更正一并记档(作者自报,按真值而非原口径): ① 先前报「文案零新增汉字」是错的——观测行拟用「动作元件」,「元」不在子集, cn-chars 与 font-charset 如实红;最终改「保护动作 N」才让「零新增」成为事实。 顺序是:先按机器红,再按真值写结论。 ② 先前报「6 个文件 BOM 逐个核过」被 bom-guard 证伪:丢 BOM 发生在上一次编译之后, 正是 AGENTS 那句「历史 0 警告不代表安全」的活例;此后一律以退出码为准。 未覆盖项不假装:台架「N>0 → N=0」在产品构建的未标定闭锁期拿不到(闭锁期 N 恒 0), 判据显式退化为「页面受理 + 落一条操作记录」,写在 rs_watch 注释里。 生成物入库例外照用:uvprojx 只加 3 个 ui_page_ 的 FilePath(真增删源文件), CMakeLists 同批;两处都由 gen_menu.py 自己写,没手写。 本笔先走了一次真钩子:20 条钩子实跑通过(ui-golden 菜单 8 状态加内容页 40 页、 generated-fresh、registration-check、font-charset、ui-data 单测、cppcheck、 coding-standard、bom、mojibake、id-check、doc-links 等),只有 mem-numbers 与 mem-budget 两条红——磁盘上的 .map 出自 20:39 那次构建、含三条在飞道的代码, 与 HEAD 台账必然不同码。按仓库既有约定(这两条等台账回填时允许 --no-verify 并 正文留证)落这一笔;回填由我统一在收尾一笔做。 三条在飞道(task-26 崩溃证据路径、task-16 与 task-17 谓词收敛和卷锁观测、 task-36 台架工具)的未暂存改动被 autostash 暂时收走,钩子看到的是 HEAD 加本笔; 过程中曾把 comm 道未跟踪的 iec104_comm.c 临时移出(它不在 stash 覆盖面内), 那次移出反而让 ui-golden 撞见 CMake 缓存里的悬空引用而红——已改用「先落 comm 那笔、再落本笔」的顺序消掉这个中间态。ui_data_maint_ready() 待删(只有测试在读 的自检口),排在本笔之后单独一笔。
143 lines
8.2 KiB
C
143 lines
8.2 KiB
C
/*
|
||
* 模块名称:UI 记录页共享组件(运行记录 / 自检记录两页的公共实现)
|
||
* 模块功能:把"定长记录 -> 每记录 2 行(偶数行时间戳、奇数行描述)+ 同记录读盘缓存
|
||
* + 空态单条伪行"这套逻辑收成一份,供多个日志页按实例复用;
|
||
* 数据经注入式 reader 回调取(UI_数据供给设计说明.md §7.2 契约),
|
||
* 本头**不含任何 App 头**——记录属主 sys_log 留在 App 侧,启动时由
|
||
* App/task/ui_task.c(MCU)与 simulator/main.c(PC)各注入一份薄包装
|
||
* 适用范围:Drivers/BSP/UI(仅 UI 层;分层方向由 check_layer_deps 门禁守护)
|
||
* 说明:缓存必须按实例存放而非文件级静态——两页共用代码但各有独立实例,
|
||
* 若共享缓存,从 A 页切到 B 页会命中上一记录号而显示另一页的陈旧行(静默错数据)。
|
||
* 作者:王建锋
|
||
* 创建日期:2026-10-20
|
||
* 修改记录:
|
||
* v1.0 2026-10-20 王建锋 创建(W6:合并 ui_page_runlog / ui_page_selftest 的重复实现)
|
||
* v2.0 2026-10-25 王建锋 记录 reader 注入(道B task-6):删 #include "sys_log.h",
|
||
* 改 ui_rec_reader_t 函数表 + ui_w_records_bind();
|
||
* log_id 换不透明编号(UI_W_REC_LOG_*);缓冲容量宏本地化
|
||
* (与 sys_log 契约下限同宽,逐字节一致)
|
||
*/
|
||
|
||
#ifndef UI_W_RECORDS_H
|
||
#define UI_W_RECORDS_H
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
#include <stdint.h>
|
||
|
||
/* 记录源不透明编号:UI 侧只认"N 号日志",App 侧适配层映射到 syslog_id_t
|
||
* (映射表见 App/task/ui_task.c 与 simulator/main.c 的绑定处;新增记录页 =
|
||
* 这里加编号 + 两端适配层各加一条映射)
|
||
* ⚠ **顺序契约(Lead 裁定 2026-10-26,task-6 起 7 条)**:本表顺序必须逐字等于
|
||
* App/log/sys_log.h 的 syslog_id_t 枚举序(RUN/SELFTEST/ACCIDENT/ALARM/START/
|
||
* OPER/YK),且**只准尾部追加**——枚举序就是两端 s_paths[]/s_caps[] 的下标,
|
||
* 中间插值会安静地把事故写进告警文件(记录侧已有 TC-SYSLOG-130/131/131b 那条
|
||
* 机械防线,UI 侧对应的那条 = test/host/ui_data 的 TC-UIDATA-024 逐项核对 +
|
||
* 两端适配层里的编译期数组守卫;编号"不透明"指的是**页面不得依赖**这个相等,
|
||
* 不是允许两者各数各的:适配层始终显式映射,从不把本编号强转成 syslog_id_t)。
|
||
* 追加之外的任何改动(换序/删号/复用号)都会让已落盘的记录被读成别的类别。 */
|
||
#define UI_W_REC_LOG_RUN 0u /* 0 号:运行记录(MCU /log/run.log) */
|
||
#define UI_W_REC_LOG_SELFTEST 1u /* 1 号:自检记录(MCU /log/selftest.log) */
|
||
#define UI_W_REC_LOG_ACCIDENT 2u /* 2 号:事故记录(/log/accid.log,跳闸型动作) */
|
||
#define UI_W_REC_LOG_ALARM 3u /* 3 号:保护告警(/log/alarm.log,告警型动作) */
|
||
#define UI_W_REC_LOG_START 4u /* 4 号:保护启动(/log/start.log,picked 上升沿) */
|
||
#define UI_W_REC_LOG_OPER 5u /* 5 号:操作记录(/log/oper.log,人工/远端操作留痕) */
|
||
#define UI_W_REC_LOG_YK 6u /* 6 号:遥控记录(/log/yk.log,104 选择/执行/拒绝) */
|
||
#define UI_W_REC_LOG_N 7u /* 编号个数(上界;两端映射表的数组长度,不是合法编号) */
|
||
|
||
/* 读取缓冲容量(UI 侧定义;必须 >= sys_log 契约下限:时间戳 19+NUL、描述 28+NUL。
|
||
* sys_log 侧 SYSLOG_TS_LEN/SYSLOG_DESC_MAX 变化时这里同步加宽——容量不足时
|
||
* reader 按契约返回负码、页面退化空行,不会读越界) */
|
||
#define UI_W_REC_TS_LEN 20u /* "YYYY-MM-DD HH:MM:SS" 19 + NUL */
|
||
#define UI_W_REC_DESC_LEN 29u /* 描述 28 + NUL */
|
||
|
||
/*
|
||
* 注入式记录 reader(只读函数表;契约 = UI_数据供给设计说明.md §7.2)
|
||
* 实现体在 App / 模拟器侧,uiTask 上下文同步调用——记录页绘制路径上的读盘
|
||
* 时序与合并前直调 syslog_* 完全一致,无新增并发面
|
||
*/
|
||
typedef struct {
|
||
/*
|
||
* 函数功能:取该日志当前记录条数
|
||
* 入口参数:log_id - 不透明记录源编号(UI_W_REC_LOG_*) uint8_t
|
||
* 返回值:记录条数(0 = 空态,页面显示伪行"无记录") uint16_t
|
||
*/
|
||
uint16_t (*count)(uint8_t a_log_id);
|
||
/*
|
||
* 函数功能:读第 idx 条记录(时间戳 + 描述两字段)
|
||
* 入口参数:log_id - 记录源编号 uint8_t
|
||
* idx - 记录序号(0 起) uint32_t
|
||
* ts - 时间戳缓冲 char *
|
||
* ts_len - ts 容量(>= UI_W_REC_TS_LEN) uint32_t
|
||
* desc - 描述缓冲 char *
|
||
* desc_len - desc 容量(>= UI_W_REC_DESC_LEN)uint32_t
|
||
* 返回值:0 = 成功;-1 = 序号/编号越界;-2 = 读失败
|
||
* (与 sys_log 契约同值,适配层直通不改码) int
|
||
*/
|
||
int (*get)(uint8_t a_log_id, uint32_t a_idx,
|
||
char *a_ts, uint32_t a_ts_len, char *a_desc, uint32_t a_desc_len);
|
||
} ui_rec_reader_t;
|
||
|
||
/*
|
||
* 记录页实例:记录源编号 + 该实例自己的读盘缓存
|
||
* 用法:页面文件里 static ui_w_records_t s_rec = { UI_W_REC_LOG_xxx };
|
||
* 其余字段自动清零,即"缓存初始无效",与合并前两页的行为一致。
|
||
*/
|
||
typedef struct {
|
||
uint8_t log_id; /* 记录源编号(UI_W_REC_LOG_*,透传给 reader) */
|
||
uint8_t cache_valid; /* 缓存有效标志(0 = 无缓存) */
|
||
uint32_t last_rec; /* 已缓存的记录序号 */
|
||
char ts[UI_W_REC_TS_LEN]; /* 时间戳缓存(含 NUL) */
|
||
char desc[UI_W_REC_DESC_LEN];/* 描述缓存(含 NUL) */
|
||
} ui_w_records_t;
|
||
|
||
/*
|
||
* 函数功能:注入记录 reader(启动时一次;NULL = 撤销注入,页面回到空态)
|
||
* 入口参数:reader - 只读函数表(生命周期由注入方保证,组件只存指针)
|
||
* const ui_rec_reader_t *
|
||
* 返回值:无
|
||
* 限定条件:uiTask 启动期注入(ui_menu_init 之前,UI_数据供给设计说明.md §7.2);
|
||
* 未注入时组件按"空态"渲染(伪行"无记录"),不崩不读野
|
||
* 函数说明:模块级单例——sys_log 读 API 本就是全局的,两端各只有一份实现;
|
||
* 两页共享函数表、独享各自的读盘缓存(缓存才是必须按实例存的那半)
|
||
*/
|
||
void ui_w_records_bind(const ui_rec_reader_t *a_reader);
|
||
|
||
/*
|
||
* 函数功能:按记录源编号查条数(不经行缓存的裸计数;清除记录页逐行显示用)
|
||
* 入口参数:log_id - 记录源编号(UI_W_REC_LOG_*) uint8_t
|
||
* 返回值:条数;未注入 reader 或编号未知一律 0,即空态,调用方按"无记录"显示
|
||
* uint16_t
|
||
* 限定条件:uiTask 上下文(绘制/按键链)
|
||
* 函数说明:reader 是本组件私有的注入表,不开放只读入口的话,想按行显示条数的
|
||
* 页面只能自己去注第二份 reader——那正好把 task-6 刚削掉的分层反向边
|
||
* 接回来(口径 docs/UI设计/UI_数据供给设计说明.md §7.2/§7.3);
|
||
* 本函数不带行缓存(缓存属于"某条记录的字段",与条数无关)
|
||
*/
|
||
uint16_t ui_w_records_count_of(uint8_t a_log_id);
|
||
|
||
/*
|
||
* 函数功能:总行数(动态;每条记录 2 行,空日志 = 单条伪行"无记录")
|
||
* 入口参数:a_inst - 记录页实例 ui_w_records_t *
|
||
* 返回值:总行数 uint16_t
|
||
*/
|
||
uint16_t ui_w_records_count(ui_w_records_t *a_inst);
|
||
|
||
/*
|
||
* 函数功能:逐行取数(偶数行 = 时间戳、奇数行 = 描述;值列恒空)
|
||
* 入口参数:a_inst - 记录页实例 ui_w_records_t *
|
||
* a_row - 全局行下标 uint16_t
|
||
* a_name - 名输出缓冲(框架栈缓冲 UI_BUF_STRLEN=32,含 NUL) char *
|
||
* a_value- 值输出缓冲(同上;本组件恒写空串) char *
|
||
* 限定条件:页面打开中(框架绘制/按键上下文,uiTask)
|
||
*/
|
||
void ui_w_records_refresh(ui_w_records_t *a_inst, uint16_t a_row, char *a_name, char *a_value);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* UI_W_RECORDS_H */
|