Files
STM32F4-Base/Drivers/BSP/UI/ui_w_records.h
T
wandering1 52e28885cb feat(UI): 装置维护 3 页落地(时钟 / 强制复归 / 清除记录),内容页黄金 27→40
三个薄页面模块 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() 待删(只有测试在读
的自检口),排在本笔之后单独一笔。
2026-10-26 22:43:54 +08:00

143 lines
8.2 KiB
C
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.
/*
* 模块名称: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 */