feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐
UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机 OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批 预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block (口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng 反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定 内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按 is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。 记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在 defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE); 事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身 (一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。 为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核, 丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。 为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费 两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过; RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同 码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。 实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B; RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS, protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→ 164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34, 既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、 不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页 连跑两次逐字节一致;字库 diff 空。 顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽 16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前 靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值 全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py 模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。 未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行; LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND), 已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
This commit is contained in:
+217
-11
@@ -37,6 +37,7 @@
|
||||
* lvgl_sim.exe --logtest
|
||||
* 验证链:post N 条 → count/get 回读断言 + 宿主文件字节级校验
|
||||
* (64B 定长、ISO 时间戳、epoch 字段、NUL 填充)
|
||||
* → 「清除记录」:计数归零 + 文件真没了 + 只动自己那份 + 幂等 + 删后还能继续写
|
||||
*
|
||||
* 待机屏截图模式(无窗口,强制进入待机后截图):
|
||||
* lvgl_sim.exe --standby shot.bin
|
||||
@@ -232,9 +233,53 @@ static const ui_soe_reader_t s_soe_reader = { sim_soe_count, sim_soe_get };
|
||||
* 3. 定值取 prot_set 出厂默认(App/algo/prot_set.c 是纯算法,双端可编),
|
||||
* 不手抄 40 个数字——抄一次就是一份会漂移的第二真相
|
||||
*/
|
||||
/* 最近一次发布的参数帧校验和(sim_param_pump 的"值变了才重发"门,与真机同纪律) */
|
||||
static uint32_t s_par_pub_sum;
|
||||
|
||||
/*
|
||||
* 函数功能:按 prot_set 现状填一帧参数快照(模拟器版 feed_fill_par)
|
||||
* 入口参数:p_par - 目标帧(调用方已清零;本函数只填有真源的几个字段)
|
||||
* 返回值:无
|
||||
* 限定条件:prot_set_init() 已跑过(值区有效)
|
||||
* 函数说明:40 项值全部现取 prot_set,不手抄数字(抄一次就是一份会漂移的第二真相)。
|
||||
* cal_block 固定给 1:**真机现在就是**"UA/UN/IA 未标定 → 判据停走"
|
||||
* (产品构建下 [adc_cal] 的 valid 位没写,用户裁定保持闭锁),模拟器要
|
||||
* 与本机一致才配当像素基准——rt.eng 也按同一条口径钉在引脚档。
|
||||
* 标定链在模拟器里没有数据源,所以这里给常量而不是"猜一个会变的值";
|
||||
* 要拍"已标定"态的图,改这一处即可(黄金参考随之要一起刷)
|
||||
*/
|
||||
static void sim_fill_par_frame(ui_data_par_t *p_par) {
|
||||
uint8_t i;
|
||||
|
||||
p_par->valid = 1u;
|
||||
p_par->checksum = prot_set_checksum();
|
||||
p_par->locked = prot_set_locked();
|
||||
p_par->bad_mask = 0u; /* 装载成功:无越界项 */
|
||||
p_par->cal_block = 1u; /* 与真机同口径:未标定(判据停走) */
|
||||
for (i = 0u; i < UI_DATA_SET_N; i++) {
|
||||
p_par->q10[i] = prot_set_get((prot_set_id_t)i);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:发布一帧参数快照并记下其校验和(写路径落定后重发用)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:prot_set_init() 之后
|
||||
* 函数说明:与 App/app/ui_data_feed.c 同一条纪律——值变了才重发(这里比校验和,
|
||||
* 模拟器没有自检闭锁与标定链,那两个源恒不变);页面按发布序号失效缓存
|
||||
*/
|
||||
static void sim_publish_par_frame(void) {
|
||||
ui_data_par_t par;
|
||||
|
||||
(void)memset(&par, 0, sizeof(par));
|
||||
sim_fill_par_frame(&par);
|
||||
(void)ui_data_publish_par(&par);
|
||||
s_par_pub_sum = par.checksum;
|
||||
}
|
||||
|
||||
static void sim_publish_data_frames(void) {
|
||||
ui_data_rt_t rt;
|
||||
ui_data_par_t par;
|
||||
ui_data_info_t info;
|
||||
uint8_t i;
|
||||
|
||||
@@ -286,16 +331,9 @@ static void sim_publish_data_frames(void) {
|
||||
(void)snprintf(rt.clock, sizeof(rt.clock), "%s", "2026-10-25 08:00:00");
|
||||
(void)ui_data_publish_rt(&rt);
|
||||
|
||||
(void)memset(&par, 0, sizeof(par));
|
||||
(void)prot_set_init(); /* 出厂默认 + 封存(幂等) */
|
||||
par.valid = 1u;
|
||||
par.checksum = prot_set_checksum();
|
||||
par.locked = prot_set_locked();
|
||||
par.bad_mask = 0u; /* 装载成功:无越界项 */
|
||||
for (i = 0u; i < UI_DATA_SET_N; i++) {
|
||||
par.q10[i] = prot_set_get((prot_set_id_t)i);
|
||||
}
|
||||
(void)ui_data_publish_par(&par);
|
||||
/* 定值帧:出厂默认 + 封存(幂等),再按 prot_set 现状发一帧(见上面两个函数) */
|
||||
(void)prot_set_init();
|
||||
sim_publish_par_frame();
|
||||
|
||||
(void)memset(&info, 0, sizeof(info));
|
||||
info.valid = 1u;
|
||||
@@ -311,6 +349,86 @@ static void sim_publish_data_frames(void) {
|
||||
(void)ui_data_publish_info(&info);
|
||||
}
|
||||
|
||||
/* ── 定值写路径镜像(道F task-2:整定 4 页的"保存"落定)──────────────────── */
|
||||
|
||||
/*
|
||||
* 函数功能:写路径判定"定值闭锁中吗"(模拟器版,镜像 App/app/ui_data_feed.c)
|
||||
* 入口参数:无
|
||||
* 返回值:prot_set 的闭锁态(模拟器里恒 0:不跑 L5-1 自检) uint8_t
|
||||
* 限定条件:主循环 / 无头注键链(单线程)
|
||||
*/
|
||||
static uint8_t sim_param_locked(void) {
|
||||
return prot_set_locked();
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:写路径整批预检——单项是否落在 meta 上下限内
|
||||
* 入口参数:a_id - 定值项编号;a_q10 - Q10 定点值
|
||||
* 返回值:0 - 合法;-1 - 编号越界或值越界 int
|
||||
* 限定条件:只读 g_prot_set_meta
|
||||
* 函数说明:与真机走的是**同一份**上下限真源(App/algo/prot_set.c),
|
||||
* 所以"模拟器里越界被拒"与"真机上越界被拒"是同一个判定,不是两份规矩
|
||||
*/
|
||||
static int sim_param_check(uint8_t a_id, int32_t a_q10) {
|
||||
if (a_id >= (uint8_t)PS_ID_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
if ((a_q10 < g_prot_set_meta[a_id].min) || (a_q10 > g_prot_set_meta[a_id].max)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:写路径落值——单项原子写入 + 重封 + 置脏(模拟器无文件系统,落盘到此为止)
|
||||
* 入口参数:a_id - 定值项编号;a_q10 - Q10 定点值
|
||||
* 返回值:0 - 已写入;-1 - 拒写 int
|
||||
* 限定条件:BSP 侧已整批预检通过
|
||||
* 函数说明:真机在这里之后还有 defaultTask 扫脏写 /prot_set.bin 一段——模拟器没有
|
||||
* NAND,跨复位保持这条只能在台架验(差距已登记在案,别把模拟器跑绿
|
||||
* 当成"保存能落盘"的证据 → docs/测试说明/模拟器差距清单.md)
|
||||
*/
|
||||
static int sim_param_apply(uint8_t a_id, int32_t a_q10) {
|
||||
if (a_id >= (uint8_t)PS_ID_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
return prot_set_set_sealed((prot_set_id_t)a_id, a_q10);
|
||||
}
|
||||
|
||||
/* 注入给 ui_data_param_drain() 的执行侧实现(与真机 s_param_ops 同构) */
|
||||
static const ui_data_param_ops_t s_param_ops = {
|
||||
sim_param_locked,
|
||||
sim_param_check,
|
||||
sim_param_apply
|
||||
};
|
||||
|
||||
/*
|
||||
* 函数功能:落定页面投递的定值整定请求(模拟器版装配节拍)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:主循环每轮一次(窗口模式)/ 注键结束后同步一次(无头模式)——
|
||||
* 两处都必须排在按键之后、渲染/截图之前,否则保存行停在 WAIT
|
||||
* 函数说明:真机这段在 App/app/ui_data_feed.c 的 tick 里(模拟器不链接装配器),
|
||||
* 这里只做同样的三件事:出队落定 → 值变了就重发参数帧 → 让页面看见。
|
||||
* drain 每轮最多处理一条,故用 while 把在队的清空(无头模式后面没有
|
||||
* 下一个 tick,必须当场排空才能拍到终态)。
|
||||
* 16 次上界是纯防御:队列深度只有 4,正常永不触及
|
||||
*/
|
||||
static void sim_param_pump(void) {
|
||||
uint32_t guard = 0u;
|
||||
|
||||
while (ui_data_param_drain(&s_param_ops) == 1) {
|
||||
if (prot_set_checksum() != s_par_pub_sum) {
|
||||
sim_publish_par_frame();
|
||||
ui_content_page_invalidate(); /* 让页面重绘:保存行 OK + 新值同帧 */
|
||||
}
|
||||
guard++;
|
||||
if (guard >= 16u) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:无窗口离屏渲染(可注入键序列)并导出末帧像素
|
||||
* 入口参数:keys - 键序列(u=上 d=下 l=左 r=右 e=确认 s=返回;空 = 不注键) const char *
|
||||
@@ -382,6 +500,11 @@ static int run_headless(const char * keys, const char * path, int standby) {
|
||||
net_cfg_save_run();
|
||||
}
|
||||
|
||||
/* 同上理由落定"整定保存"请求:真机由 ui_data_feed_tick() 每 10 ms 排一条,
|
||||
* 无头模式没有那个节拍,不在此当场排空,截图就会停在 WAIT(终态拍不到,
|
||||
* OK/ERR/LOCK 三条判据的像素证据也就全都不成立) */
|
||||
sim_param_pump();
|
||||
|
||||
/* --standby:注键后强制进入待机屏(截图前) */
|
||||
if (standby != 0) {
|
||||
ui_standby_force_enter();
|
||||
@@ -588,6 +711,10 @@ static int run_cfgtest(void) {
|
||||
* 返回值:0 - PASS;非 0 - FAIL int
|
||||
* 说明:验证链 清理旧档 → post N 条 → count/get 回读断言 + 宿主文件字节级校验
|
||||
* (64B 定长、ISO 时间戳格式、epoch 字段 ≈ now、NUL 填充)
|
||||
* → 「清除记录」(syslog_request_clear / syslog_clear_state)。宿主后端与 MCU 端
|
||||
* 判据相同、时序不同:模拟器单线程,请求在调用内就落地,所以本端断言的是
|
||||
* 「立刻 OK」——读到 WAIT 就说明两端语义漂了。完整分支矩阵(卷占用、unlink
|
||||
* 错误码、幂等、读窗口失效)在 test/host/sys_log_mcu/ 那侧用假卷注入去测。
|
||||
*/
|
||||
static int run_logtest(void) {
|
||||
uint32_t ep;
|
||||
@@ -675,6 +802,81 @@ static int run_logtest(void) {
|
||||
}
|
||||
|
||||
printf("[logtest] OK count/get/epoch/bytes\n");
|
||||
/* ── 「清除记录」:宿主后端必须与 MCU 端共用同一组判据 ──
|
||||
* 分支矩阵(卷占用 / unlink 错误码 / 幂等 / 读窗口失效)由假卷那边测,
|
||||
* 这里只测「同一份头契约在宿主上真对得上」——宿主没有卷锁,也就没有顺延。 */
|
||||
|
||||
/* 从未请求过的 id 必须是 NONE:UI 据此把「没操作过」和「已清除」分开显示 */
|
||||
if (syslog_clear_state(SYSLOG_SELFTEST) != SYSLOG_CLEAR_NONE) {
|
||||
printf("[logtest] FAIL selftest clear state = %d (expect NONE before any ask)\n",
|
||||
syslog_clear_state(SYSLOG_SELFTEST));
|
||||
return 1;
|
||||
}
|
||||
/* 越界 id:请求被拒、状态仍 NONE,且不得牵连合法 id */
|
||||
if ((syslog_request_clear((syslog_id_t)SYSLOG_COUNT) == 0)
|
||||
|| (syslog_clear_state((syslog_id_t)SYSLOG_COUNT) != SYSLOG_CLEAR_NONE)
|
||||
|| (syslog_clear_state(SYSLOG_SELFTEST) != SYSLOG_CLEAR_NONE)) {
|
||||
printf("[logtest] FAIL out-of-range clear was accepted\n");
|
||||
return 1;
|
||||
}
|
||||
if (syslog_request_clear(SYSLOG_RUN) != 0) {
|
||||
printf("[logtest] FAIL request_clear(run)\n");
|
||||
return 1;
|
||||
}
|
||||
if (syslog_count(SYSLOG_RUN) != 0u) {
|
||||
printf("[logtest] FAIL run count after clear = %u (expect 0)\n",
|
||||
(unsigned)syslog_count(SYSLOG_RUN));
|
||||
return 1;
|
||||
}
|
||||
/* 同步落地的终态;本端读到 WAIT 就是两端语义漂移 */
|
||||
if (syslog_clear_state(SYSLOG_RUN) != SYSLOG_CLEAR_OK) {
|
||||
printf("[logtest] FAIL run clear state = %d (expect OK on host)\n",
|
||||
syslog_clear_state(SYSLOG_RUN));
|
||||
return 1;
|
||||
}
|
||||
/* 文件必须真没了(不是「截成 0 字节」),而且不许牵连邻居 */
|
||||
{
|
||||
FILE *gone = fopen("sim_run.log", "rb");
|
||||
FILE *neigh = fopen("sim_selftest.log", "rb");
|
||||
|
||||
if (gone != NULL) {
|
||||
(void)fclose(gone);
|
||||
printf("[logtest] FAIL sim_run.log still there after clear\n");
|
||||
return 1;
|
||||
}
|
||||
if (neigh == NULL) {
|
||||
printf("[logtest] FAIL sim_selftest.log lost: clear touched the wrong file\n");
|
||||
return 1;
|
||||
}
|
||||
(void)fclose(neigh);
|
||||
}
|
||||
/* 幂等:文件已不在,再清一次仍算成功(UI 上重复点击不该报故障) */
|
||||
if ((syslog_request_clear(SYSLOG_RUN) != 0)
|
||||
|| (syslog_clear_state(SYSLOG_RUN) != SYSLOG_CLEAR_OK)) {
|
||||
printf("[logtest] FAIL second clear is not idempotent\n");
|
||||
return 1;
|
||||
}
|
||||
/* 删后重建可继续写:清除不能把日志通道本身弄坏(MCU 端那条 FA_WRITE 陷阱由
|
||||
* test/host/sys_log_mcu/ 的 TC-SYSLOG-112 钉,本端同判据同断言形状) */
|
||||
syslog_post(SYSLOG_RUN, "after clear");
|
||||
if (syslog_count(SYSLOG_RUN) != 1u) {
|
||||
printf("[logtest] FAIL run count after re-post = %u (expect 1)\n",
|
||||
(unsigned)syslog_count(SYSLOG_RUN));
|
||||
return 1;
|
||||
}
|
||||
if ((syslog_get_record(SYSLOG_RUN, 0u, ts, sizeof(ts), desc, sizeof(desc)) != 0)
|
||||
|| (strcmp(desc, "after clear") != 0)) {
|
||||
printf("[logtest] FAIL get_record after clear: desc mismatch\n");
|
||||
return 1;
|
||||
}
|
||||
/* 两份记录各清各的:清 selftest 不得带走 run 里刚写的那一条 */
|
||||
if ((syslog_request_clear(SYSLOG_SELFTEST) != 0)
|
||||
|| (syslog_count(SYSLOG_SELFTEST) != 0u)
|
||||
|| (syslog_count(SYSLOG_RUN) != 1u)) {
|
||||
printf("[logtest] FAIL selftest clear (run must keep its 1 record)\n");
|
||||
return 1;
|
||||
}
|
||||
printf("[logtest] OK clear: zeroed + file gone + isolated + idempotent + rebuild\n");
|
||||
printf("[logtest] PASS\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -797,6 +999,10 @@ int main(int argc, char **argv) {
|
||||
* 无请求 = 空操作);"立即应用"回调未注册 → 空操作 */
|
||||
net_cfg_save_run();
|
||||
|
||||
/* 落定定值整定请求(镜像真机 ui_data_feed_tick 里的 ui_data_param_drain:
|
||||
* 出队 → prot_set 落值 → 重发参数帧);队列空时只是一次计数判断 */
|
||||
sim_param_pump();
|
||||
|
||||
/* 配置文件轮询(2s 周期:外部文件变更 4s 沉降后自动重载) */
|
||||
uint32_t now_ms = (uint32_t)SDL_GetTicks();
|
||||
if ((uint32_t)(now_ms - cfg_last_poll) >= 2000u) {
|
||||
|
||||
Reference in New Issue
Block a user