/* * 模块名称:UI Page clear (清除记录内容页) * 模块功能:装置维护 → 清除记录 叶子(node 27,#P 受密码保护)的内容页—— * 七行清单(运行 / 自检 / 事故 / 保护告警 / 保护启动 / 操作 / 遥控记录), * 值列 = 该类记录当前条数(0 条显"无记录"),逐行独立清除; * 行内两段式确认:第一次 ENTER 该行值列变"确认?",第二次 ENTER 才投递, * 之后该行按清除状态显 WAIT / OK / ERR,直到光标移开才收回; * 条数只走 ui_w_records 的**同一枚注入 reader**(裸计数入口 * ui_w_records_count_of),删除只发请求(执行侧 = syslog_request_clear), * 真反馈 = 状态机 + 下一帧条数归零,不是"按了就算成功"; * 只读进入(查看密码)整页不投递; * 本文件零 App include、零文件系统触点(既不调 MCU 专属的 * syslog_clear_apply(),也绝不自己 f_unlink / 碰卷锁——defaultTask 是 * 记录唯一写者,uiTask 在 IWDG required_mask 内,给它一条最坏数百 ms * 的存储路径就是拿误复位当代价) * 为什么不做"一键清全部":七类记录分属不同事件面(运行/自检 vs 事故/告警/启动 vs * 操作/遥控审计),一次点击清光七类 = 一次误操作毁掉全部审计面; * 逐行清已完全覆盖判据(删后重建、计数归零、不存在时幂等) * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) * 作者:DTU 项目组 * 创建日期:2026-10-26 * 修改记录: * v1.0 2026-10-26 DTU 项目组 创建(道F task-3;判据 docs/任务规划/UI补齐.md * §2 第 3 行「清除记录…不允许只做没有实际效果的按钮」; * 删除 API 与请求/落地分离 = task-4,口径真源 * App/log/README.md 与 App/log/sys_log.h) */ /* 头文件包含区 */ #include #include "ui_page_clear.h" #include "ui_content_page.h" #include "ui_data.h" #include "ui_tokens.h" #include "ui_w_records.h" /* 私有宏定义区 */ #define CL_ROW_N UI_W_REC_LOG_N /* 行数 = 记录源个数(两端映射表长度同源) */ #define CL_NONE 0xFFu /* "无行"哨兵(行号上限 7,绝不撞) */ #define CL_TXT_NONE "无记录" /* 0 条(用字已在子集,与两条记录页同一词) */ #define CL_TXT_CONFIRM "确认?" /* 第一段确认('?' 是 ASCII) */ /* 结果词沿用全 ASCII 一排:WAIT 是正常态(MCU 端删除在 defaultTask 的 * syslog_drain() 尾部落地),模拟器端同步删除永不出现 WAIT —— 两端都要能显示 */ #define CL_TXT_WAIT "WAIT" #define CL_TXT_OK "OK" #define CL_TXT_ERR "ERR" /* 私有函数声明区 */ static const char *cl_name_of(uint16_t a_row); static uint8_t cl_id_of(uint16_t a_row); /* 静态变量定义区 */ /* 行名表(Flash);顺序 = UI_W_REC_LOG_* 编号序,与两端映射表同一套下标 */ static const char *const k_cl_name[CL_ROW_N] = { "运行记录", "自检记录", "事故记录", "保护告警", "保护启动", "操作记录", "遥控记录" }; static const uint8_t k_cl_id[CL_ROW_N] = { UI_W_REC_LOG_RUN, UI_W_REC_LOG_SELFTEST, UI_W_REC_LOG_ACCIDENT, UI_W_REC_LOG_ALARM, UI_W_REC_LOG_START, UI_W_REC_LOG_OPER, UI_W_REC_LOG_YK }; static uint8_t s_confirm; /* 待确认行(第一次 ENTER 已按下),CL_NONE = 无 */ static uint8_t s_note; /* 已投递、要显示清除结果的行,CL_NONE = 无 */ static uint8_t s_err; /* 投递被拒的行,CL_NONE = 无 */ static uint16_t s_sel_seen; /* 上一次绘制时的选中行(移开光标即收回三个锁存) */ /* 函数定义区 */ /* * 函数功能:取行名 * 入口参数:row - 行号 uint16_t * 返回值:名(Flash 字面量;越界回空串,不越表读) const char * * 限定条件:无 */ static const char *cl_name_of(uint16_t a_row) { if (a_row >= CL_ROW_N) { return ""; } return k_cl_name[a_row]; } /* * 函数功能:取行对应的记录源编号 * 入口参数:row - 行号 uint16_t * 返回值:UI_W_REC_LOG_*;越界回 0(首号,调用方已在越界时不调本函数) uint8_t * 限定条件:无 * 函数说明:编号不透明——本页不知道也不该知道"3 号是 alarm.log",映射在两端 * 适配层(App/task/ui_task.c 与 simulator/main.c),加一类记录时 * 本表加一行、两端各加一条映射,屏上自动多一行 */ static uint8_t cl_id_of(uint16_t a_row) { if (a_row >= CL_ROW_N) { return (uint8_t)UI_W_REC_LOG_RUN; } return k_cl_id[a_row]; } /* * 函数功能:开页复位(框架 init_cb) * 入口参数:无 * 返回值:无 * 限定条件:框架开页链内(此时本次授予权限已装载) * 函数说明:三个锁存全部归无——清除状态是**按 id 的全局单值**(syslog 侧 * 上电以来一直是 OK),开页直读会把"昨天清过操作记录"显示成"这次 * 清成功了"(契约 §6.3 第 3 条同一件事:状态是全局的,页面必须锁存) */ static void pg_init(void) { s_confirm = CL_NONE; s_note = CL_NONE; s_err = CL_NONE; s_sel_seen = ui_content_page_selected_row(); } /* * 函数功能:逐行取数(框架 refresh;值列按"结果 > 确认 > 条数"优先级) * 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h * 返回值:无 * 限定条件:框架绘制链内 * 函数说明:已投递那一行的词来自 ui_data_maint_clear_state()(执行侧直通, * FAIL 也照实显 ERR:原文件一个字节没少,可以再发一次请求); * 没投递的行每帧现算条数——清 run.log 之后 sys_log 会往**操作记录** * 补一条留痕,所以"操作记录"那行会从 0 变成 1,这是真的,页面不缓存 * "我以为归零了"(清 oper.log 自己时刻意不补,App/log/README.md 已载) */ static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) { uint8_t id; uint16_t n; (void)memset(a_value, 0, UI_BUF_STRLEN); id = cl_id_of(a_row); (void)memcpy(a_name, cl_name_of(a_row), strlen(cl_name_of(a_row)) + 1u); if ((uint8_t)a_row == s_note) { int st = ui_data_maint_clear_state(id); if (st == UI_DATA_CLEAR_ST_OK) { (void)memcpy(a_value, CL_TXT_OK, sizeof(CL_TXT_OK)); } else if (st == UI_DATA_CLEAR_ST_WAIT) { (void)memcpy(a_value, CL_TXT_WAIT, sizeof(CL_TXT_WAIT)); } else { (void)memcpy(a_value, CL_TXT_ERR, sizeof(CL_TXT_ERR)); } return; } if ((uint8_t)a_row == s_err) { (void)memcpy(a_value, CL_TXT_ERR, sizeof(CL_TXT_ERR)); return; } if ((uint8_t)a_row == s_confirm) { (void)memcpy(a_value, CL_TXT_CONFIRM, sizeof(CL_TXT_CONFIRM)); return; } n = ui_w_records_count_of(id); /* 现算:不留"我以为归零了"的缓存 */ if (n == 0u) { (void)memcpy(a_value, CL_TXT_NONE, sizeof(CL_TXT_NONE)); return; } (void)lv_snprintf(a_value, UI_BUF_STRLEN, "%u", (unsigned int)n); } /* * 函数功能:自绘(框架 draw_cb)——顺带承担"光标移开就收回锁存"的观测 * 入口参数:a_layer / a_clip - 契约见 ui_content_page.h * 返回值:无 * 限定条件:框架绘制链内 * 函数说明:为什么在 draw 里看选中行:↑↓ 移选是框架内置的(本页 key_cb 对 * 非 ENTER 键一律交回框架,看不到它们),而每次移选框架都会 * invalidate 并重绘——draw 是唯一必然发生、且总在状态变化之后的观测点。 * 语义 = "用户已经看过程序结果 / 已经放弃这次确认",本页不再有 * "上一次的结果挂在另一行上"的显示 */ static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) { uint16_t sel = ui_content_page_selected_row(); (void)a_layer; (void)a_clip; if (sel != s_sel_seen) { s_sel_seen = sel; s_confirm = CL_NONE; s_note = CL_NONE; s_err = CL_NONE; } } /* * 函数功能:按键处理(行内两段式确认) * 入口参数:a_key - LVGL 键值 lv_key_t * 返回值:true = 本页已消费;false = 交回框架(↑↓ 移选、←/ESC 关页 = 取消) bool * 限定条件:框架按键链内;描述符 edit_capable=1 * 函数说明:只读进入一律交回框架(本页没有只读可做的动作,也没有保存行可显 * LOCK——清单页没有态标位,加了反而像"可以按"); * 第一次 ENTER 只是把该行标成待确认(不做任何投递),第二次才投; * 投递被拒(含维护通道未注入)时置 s_err,光标一动就收回 */ static bool pg_key(lv_key_t a_key) { uint16_t sel; if (a_key != LV_KEY_ENTER) { return false; } if (ui_content_page_is_editable() == false) { return false; /* 只读:清除是修改类动作,一条都不投 */ } sel = ui_content_page_selected_row(); if (sel >= CL_ROW_N) { return true; /* 越界防御:不投任何编号 */ } if ((uint8_t)sel != s_confirm) { s_confirm = (uint8_t)sel; /* 第一段:只标记,不投递 */ s_note = CL_NONE; s_err = CL_NONE; ui_content_page_invalidate(); return true; } s_confirm = CL_NONE; s_err = CL_NONE; if (ui_data_maint_clear_req(cl_id_of(sel)) == 0) { s_note = (uint8_t)sel; /* 已受理:接下来按状态机显 WAIT/OK/ERR */ } else { s_err = (uint8_t)sel; } ui_content_page_invalidate(); return true; } /* * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=clear 绑定引用) * 七行 1 页(rows_per_page = UI_LIST_ROWS(8) > 7 ⇒ 不翻页);key_intercept=0: * 本页没有"编辑到一半"的态要保,← 与 ESC 一律关页 = 取消 */ const content_page_def_t ui_page_clear_desc = { .id = "clear", .title = "清除记录", .selectable = 1u, .edit_capable = 1u, .top_y = UI_LIST_TOP, .pitch = UI_ROW_PITCH, .band_h = UI_ROW_BAND_H, .text_dy = 0, .left_x = UI_LIST_LEFT_X, .right_x = UI_LIST_RIGHT_X, .rows_per_page = UI_LIST_ROWS, .row_count = CL_ROW_N, .init_cb = pg_init, .refresh = pg_refresh, .draw_cb = pg_draw, .key_cb = pg_key, };