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() 待删(只有测试在读
的自检口),排在本笔之后单独一笔。
This commit is contained in:
2026-10-26 22:43:54 +08:00
parent 45086cf5f9
commit 52e28885cb
34 changed files with 2854 additions and 175 deletions
+141 -15
View File
@@ -147,14 +147,20 @@ static void sim_time_cb(char *buf, size_t len) {
* 入口参数:log_id - UI 记录源编号(UI_W_REC_LOG_*) uint8_t
* 返回值:记录条数;未知编号返回 0(页面空态) uint16_t
*/
/* 镜像 App/task/ui_task.c 的 s_log_map:两端 UI 编号 → syslog_id_t 表同源同长;
* 顺序契约(== 枚举序、只准尾追加)在真机侧有编译期守卫,宿主侧由
* test/host/ui_data TC-UIDATA-024 与下面的数组长度同时把住 */
static const syslog_id_t s_sim_log_map[UI_W_REC_LOG_N] = {
SYSLOG_RUN, SYSLOG_SELFTEST, SYSLOG_ACCIDENT, SYSLOG_ALARM,
SYSLOG_START, SYSLOG_OPER, SYSLOG_YK
};
typedef char sim_log_map_len_check_t[(UI_W_REC_LOG_N == (uint8_t)SYSLOG_COUNT) ? 1 : -1];
static uint16_t sim_rec_count(uint8_t log_id) {
if (log_id == UI_W_REC_LOG_RUN) {
return (uint16_t)syslog_count(SYSLOG_RUN);
if (log_id >= (uint8_t)UI_W_REC_LOG_N) {
return 0u;
}
if (log_id == UI_W_REC_LOG_SELFTEST) {
return (uint16_t)syslog_count(SYSLOG_SELFTEST);
}
return 0u;
return (uint16_t)syslog_count(s_sim_log_map[log_id]);
}
/*
@@ -164,16 +170,103 @@ static uint16_t sim_rec_count(uint8_t log_id) {
*/
static int sim_rec_get(uint8_t log_id, uint32_t idx, char *ts, uint32_t ts_len,
char *desc, uint32_t desc_len) {
if (log_id == UI_W_REC_LOG_RUN) {
return syslog_get_record(SYSLOG_RUN, idx, ts, (size_t)ts_len, desc, (size_t)desc_len);
if (log_id >= (uint8_t)UI_W_REC_LOG_N) {
return -1;
}
if (log_id == UI_W_REC_LOG_SELFTEST) {
return syslog_get_record(SYSLOG_SELFTEST, idx, ts, (size_t)ts_len, desc, (size_t)desc_len);
}
return -1;
return syslog_get_record(s_sim_log_map[log_id], idx, ts, (size_t)ts_len, desc,
(size_t)desc_len);
}
static const ui_rec_reader_t s_rec_reader = { sim_rec_count, sim_rec_get };
/*
* ======================= 道F task-3:装置维护写通道假件(镜像 ui_task.c)
*
* 形状与真机完全同一张函数表(六件),语义差一处如实记在下面:清除请求在宿主端
* 是同步落地的(simulator/sim/sim_syslog.c:本调用内就删完,状态直接 OK),
* 真机则先 WAIT 再等 defaultTask 落 —— 页面对两种时序都要显示正确,
* 所以清除记录页的值词表里 WAIT 与 OK 都留了位置(黄金参考只覆盖得到 OK)。
*/
static void sim_publish_data_frames(void); /* 前置声明:本文件下方定义 */
static int s_noclock; /* --noclock:把本机装成"墙钟无效" */
static uint32_t s_sim_epoch; /* 面板设过的墙钟(0 = 没设过) */
static uint32_t s_sim_active = 1u; /* 假"动作态元件数":1 → 复归请求后 0 */
static char s_sim_note[32]; /* 最近一条面板留痕(页面上看不到,
但自检/排障时是唯一的取证处) */
/*
* 函数功能:假"设置墙钟"——校验后落内存并立即重发实时帧
* 入口参数:a_epoch - Unix 秒 uint32_t
* 返回值:0 - 已生效;-1 - 越界(与真机同一判据) int
* 限定条件:uiTask 等价上下文(注键链内)
* 函数说明:真机的"回读证据"来自 SD2506 回读 + 快照层 200ms 重发;模拟器没有
* RTC 也没有周期发布(无头模式连 200ms 节拍都不走),这里用"当场重发
* 一帧"补上那半条时间轴——与 sim_param_pump() 同一个理由。不这么做的话,
* 保存成功但屏幕上的时间纹丝不动,黄金参考就会把"页面显示旧值"误当成
* "回读失败",而真机上这个组合根本不可能出现(那就是假故障)
*/
static int sim_maint_clock_set(uint32_t a_epoch) {
if ((a_epoch == 0u) || (a_epoch > (uint32_t)UI_DATA_CLOCK_EPOCH_MAX)) {
return -1;
}
s_sim_epoch = a_epoch;
sim_publish_data_frames();
return 0;
}
/* 函数功能:假"强制复归"——把动作态元件数清零(真机由 protectTask 下一拍清) */
static int sim_maint_reset_req(void) {
s_sim_active = 0u;
return 0;
}
/* 函数功能:假"动作态元件数"(复归页观测行 "保护动作 N") */
static uint32_t sim_maint_active_count(void) {
return s_sim_active;
}
/*
* 函数功能:假"面板留痕"——真投进宿主端的 /log/oper.log(不是只记账)
* 入口参数:a_desc - 描述串 const char *
* 返回值:无
* 限定条件:uiTask 等价上下文
* 函数说明:留痕必须**真的落盘**,否则清除记录页上"清完 oper.log 之后操作记录
* 又从 0 变回 1"这条事实在模拟器里永远看不见,黄金参考也就白拍
*/
static void sim_maint_oper_note(const char *a_desc) {
if (a_desc == NULL) {
return;
}
(void)snprintf(s_sim_note, sizeof(s_sim_note), "%s", a_desc);
syslog_post(SYSLOG_OPER, a_desc); /* 该 API 无格式串形态(本头就是 const char *desc) */
}
/* 函数功能:假"请求清除记录"(宿主端同步落地,返回即已是终态) */
static int sim_maint_clear_req(uint8_t a_log_id) {
if (a_log_id >= (uint8_t)UI_W_REC_LOG_N) {
return -1;
}
return (syslog_request_clear(s_sim_log_map[a_log_id]) == 0) ? 0 : -1;
}
/* 函数功能:假"清除状态"(值直通,与真机同一不改写口径) */
static int sim_maint_clear_state(uint8_t a_log_id) {
if (a_log_id >= (uint8_t)UI_W_REC_LOG_N) {
return UI_DATA_CLEAR_ST_NONE;
}
return syslog_clear_state(s_sim_log_map[a_log_id]);
}
static const ui_data_maint_ops_t s_maint_ops = {
sim_maint_clock_set,
sim_maint_reset_req,
sim_maint_active_count,
sim_maint_oper_note,
sim_maint_clear_req,
sim_maint_clear_state
};
/*
* 函数功能:SOE 记录假源——固定 3 条(镜像 App/task/ui_task.c 真机绑定的形态)
* 入口参数:无
@@ -327,8 +420,24 @@ static void sim_publish_data_frames(void) {
rt.yx_dp = 0x0FULL;
rt.freq_valid = 0u; /* eng=0 期间幅值门恒失效 → 无新鲜读数 */
rt.freq_hz = 0.0f;
rt.clock_ok = 1u;
(void)snprintf(rt.clock, sizeof(rt.clock), "%s", "2026-10-25 08:00:00");
/* 墙钟两态(道F task-3):--noclock 把这台机器装成"RTC 异常/未对时"——只有
* 这样才照得到时钟设置页的 "--" + "无时钟"徽标形态;面板设过时以面板值为准,
* 这就是页面上"回读证据"的唯一来源(真机那边是 SD2506 回读 + 快照层重发) */
rt.clock_ok = (s_noclock == 0) ? 1u : 0u;
if (s_noclock != 0) {
rt.clock[0] = 0; /* 无效时标 = 空串(页面据此显 "--",绝不显 1970) */
} else if (s_sim_epoch != 0u) {
ui_data_clock_t ck;
if (ui_data_clock_from_epoch(s_sim_epoch, &ck) == 0) {
(void)ui_data_clock_format(&ck, rt.clock, sizeof(rt.clock));
} else {
rt.clock[0] = 0;
rt.clock_ok = 0u;
}
} else {
(void)snprintf(rt.clock, sizeof(rt.clock), "%s", "2026-10-25 08:00:00");
}
(void)ui_data_publish_rt(&rt);
/* 定值帧:出厂默认 + 封存(幂等),再按 prot_set 现状发一帧(见上面两个函数) */
@@ -470,6 +579,10 @@ static int run_headless(const char * keys, const char * path, int standby) {
/* task-6 记录 reader 注入(镜像 ui_task.c:先于 ui_menu_init,两端同形) */
ui_w_records_bind(&s_rec_reader);
/* 装置维护写通道(道F task-3):与真机同一张表形状,假件见上方实现 */
if (ui_data_maint_bind(&s_maint_ops) != 0) {
printf("[sim] UI maint bind: FAIL\n");
}
ui_w_soe_bind(&s_soe_reader); /* task-15:SOE 记录页假源(形态镜像真机绑定) */
/* task-14 数据快照:镜像 ui_task.c 的 ui_data_init + 首帧发布(先于建页,
* 页面任何时刻打开都拿得到帧,不必等装配节拍) */
@@ -885,10 +998,19 @@ static int run_logtest(void) {
* 函数功能:模拟器入口(解析模式开关 → 窗口模式或无头自检)
* 入口参数:argc - 参数个数 int
* argv - 参数数组(--screenshot / --keys / --standby / --cfgtest /
* --logtest / --idle) char **
* --logtest / --idle / --noclock) char **
* 返回值:0 - 正常退出或自检 PASS;非 0 - 失败 int
*/
int main(int argc, char **argv) {
/* 形态旗标必须先扫(道F task-3):--keys / --standby 分支命中即 return,
* 放在它们后面的循环根本走不到 —— 这是旗标看似加了、实际只在无参窗口模式
* 生效的那类"看起来做了"的坑,写在这里省一个人重新踩 */
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--noclock") == 0) {
s_noclock = 1; /* 把这台机器装成"墙钟无效"(RTC 异常/未对时) */
}
}
/* 截图/自检模式:--screenshot / --keys / --standby / --cfgtest / --logtest */
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--screenshot") == 0 && (i + 1) < argc) {
@@ -953,6 +1075,10 @@ int main(int argc, char **argv) {
/* 构建菜单 UI(与 MCU 端共用 ui_menu.c) */
/* task-6 记录 reader 注入(镜像 ui_task.c:先于 ui_menu_init,两端同形) */
ui_w_records_bind(&s_rec_reader);
/* 装置维护写通道(道F task-3):与真机同一张表形状,假件见上方实现 */
if (ui_data_maint_bind(&s_maint_ops) != 0) {
printf("[sim] UI maint bind: FAIL\n");
}
ui_w_soe_bind(&s_soe_reader); /* task-15:SOE 记录页假源(形态镜像真机绑定) */
/* task-14 数据快照:镜像 ui_task.c 的 ui_data_init + 首帧发布(先于建页,
* 页面任何时刻打开都拿得到帧,不必等装配节拍) */