94b0d79026
本笔含两组收尾工作(同一工作区内交错在 6 个共享文档/门禁配置里, 拆开会产生"文档声称的页数与树里黄金数不一致"的中间提交,故合为一笔): 一、UI 补齐(清单清空) - 新建 6 个内容页模块:ui_page_accident / oper / alarm / start / yk 复用 ui_w_records 共享组件(分页、空态"无记录"、按实例读盘缓存), 记录层与五个写入点已由道F task-10 / 道G task-12 落地,本批只补页面; ui_page_runreport 为 13 行 2 页只读统计,四个数逐个挂范围标签 (自电池周期 / 本次开机),boot_ready=0 显 "--" 不显 0。 - 数据面:ui_data_info_t 尾部追加 9 字段,ui_data_feed 用 boot_stats_get() + iec104_serv_get_stats() 纯搬运;模拟器填确定值假数据 (差异登记 docs/测试说明/模拟器差距清单.md §2.3 两行)。 - 接线:菜单树 6 叶绑 #page=,绑定叶 22→28、UI_PAGE_MAP_COUNT=28; 字库 185→218 字 / 280→313 字形;双端注册 uvprojx +30、CMakeLists +6。 - 回归:内容页黄金 53→60(含 runreport_p2 真拍第 2 页),文本断言 425 正向 / 36 反向;存量 53 张逐字节零漂移;记录 5 页各用独立空目录 (模拟器把操作留痕写进 cwd 的 sim_oper.log,共用 tmp 会让空态拍不到)。 二、数据可信销项 + 「数据可信」任务卡号整族清退 - 删除已闭环的"已知电压注入验证"行与闭环结论块(证据在 docs/硬件平台/ADC通道映射说明.md §8.1 与 docs/测试说明/ADC前端注入验证流程.md §5~§7),§5 标识列改语义短语。 - 卡 0/0b/1~12 共 14 个号全仓 288 站点 / 56 文件清退成语义短语, 代码注释、测试、门禁配置与文档一并改;换算表入 docs/编号规范.md §9.3。 这一族是中文前缀,不进 check_ids 语料,刻意不入 id_aliases.json。 验证(实测) - @build 0 错 0 警:Code=236456 RO=28124 RW=1876 ZI=137084; RAM +464 B(RW +304 / ZI +160),RW_IRAM1 62.2% / RW_CCM 87.9%, 体积同码凭证与 RAM 台账已回填。 - check_menu_rows 8 状态 rc=0(池 5368/5448 常量未动); check_content_pages 60 页 rc=0;check_font_charset / registration / coding-standard / bom-drift / mojibake / doc-links --all / layer-deps / nested-comment 全 rc=0;check_ids --strict 无错误;主机单测 ui_data 295 / boot_stats 21 / sys_log_mcu 141 / soe 32 / soe_store 27。 未做(不是遗漏) - 六页真机判据(真记录翻页、掉电保持、清除后归零、boot_count 自增、 拔 RTC 电池落 RESTARTED、IWDG 复位落 rst_cause、主站连一次 conn_up+1) 已登记 docs/任务规划/登记簿.md §7;模拟器结构上照不到。 - docs/产品需求/产品需求设计文档.md 属另一条在飞改动,本笔不含; 其中我已同步 6 处现状数字(28 叶 / 60 页 / 218 字),随该笔入库。
277 lines
12 KiB
C
277 lines
12 KiB
C
/*
|
||
* 模块名称:UI Page RunReport(运行报告内容页)
|
||
* 模块功能:运行报告叶子节点(node 41,父节点「记录查询」)的内容页数据——13 行只读统计,
|
||
* 分 2 页(UI_LIST_ROWS=8 行/页 → 8 + 5);数据全部经 ui_data 装置信息帧下发
|
||
* (写侧 App/app/ui_data_feed.c 搬 boot_stats_get() 与 iec104_serv_get_stats(),
|
||
* 本文件零 App include,分层方向由 layer-deps 门禁守护)
|
||
* 口径纪律(判据 docs/任务规划/UI补齐.md §2「运行报告」行 + App/util/boot_stats.h 四数台账):
|
||
* ① **四个数逐个挂范围标签**,不许含糊——上电次数=自 RTC 电池周期(换电池即清零,
|
||
* **禁写终身次数**,固件里压根没有跨开机计数器)、复位原因/运行时长/104 统计
|
||
* =本次开机以来(104 四枚跨重连不清零,但**断电即归零**);
|
||
* ② boot_ready==0(账本未初始化)或 seq_state==0(UNKNOWN)时上电次数显示 "--"
|
||
* 而不是 0——把"没有数"洗成"0 次"是最坏的一种误导;
|
||
* ③ write_failed==1 单独占一行("未写回"):后果是账本**少计**,绝不会重复计数
|
||
* (掉电保持数 +1 后先写 SRAM 再发布,写失败仍按内存值显示);
|
||
* ④ 运行时长是 tick 回绕口径(32 位 ms ≈ 49.7 天、uptime_s ≈ 136 年),
|
||
* 显示按"天 + 时时:分分"退化(与装置信息页同一 ASCII 记法,见 di_fmt_uptime),
|
||
* **不做跨开机累计**(用户裁定 2026-10-27)
|
||
* 几何余量(2026-10-28 黄金目检):「自电池周期」5 字 x12px 从 x=101 起、右缘 161,**余量 0**——
|
||
* 值列再加字或换字号必切边;要扩文案必须先减字或改标签列宽度,别直接加字。
|
||
* 台架待补(不在本批,已登记 docs/任务规划/登记簿.md):真机上电一次看 boot_count +1、
|
||
* 拔电换电池后 seq_state 落 RESTARTED、IWDG 复位后 rst_cause 落 IWDG、
|
||
* 主站连一次看 conn_up +1 —— 模拟器无 boot_stats(依赖 SD2506 I2C 与 RCC_CSR)
|
||
* 与 104 协议栈,黄金参考拍的是固定假数据
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:DTU 项目组
|
||
* 创建日期:2026-10-28
|
||
* 修改记录:
|
||
* v1.0 2026-10-28 DTU 项目组 创建(形态照 ui_page_device_info.c 的行表 + 值格式化,
|
||
* 分页与选中带照 ui_page_deadband.c 的列表页几何)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_page_runreport.h"
|
||
|
||
#include "ui_data.h"
|
||
#include "ui_tokens.h"
|
||
|
||
/* 私有宏定义区 */
|
||
|
||
#define RR_ROW_N 13u /* 行数(2 页:8 + 5) */
|
||
#define RR_INVALID "--"
|
||
#define RR_SEC_PER_DAY 86400u
|
||
|
||
/* 复位原因编码(与 App/util/boot_stats.h 的 BOOT_STATS_CAUSE_* 一一对应;
|
||
* 这里刻意写成数字常量并在注释里钉死映射——BSP 不得 include App 头,
|
||
* 编码变更由 test/host/ui_data 的搬运断言与 App/log/README 口径联动发现 */
|
||
#define RR_CAUSE_UNKNOWN 0u
|
||
#define RR_CAUSE_IWDG 1u
|
||
#define RR_CAUSE_WWDG 2u
|
||
#define RR_CAUSE_SFT 3u
|
||
#define RR_CAUSE_POR 4u
|
||
#define RR_CAUSE_PIN 5u
|
||
#define RR_CAUSE_BOR 6u
|
||
|
||
/* 上电次数可信度编码(BOOT_STATS_SEQ_*) */
|
||
#define RR_SEQ_UNKNOWN 0u
|
||
#define RR_SEQ_FRESH 1u
|
||
#define RR_SEQ_CONTINUED 2u
|
||
#define RR_SEQ_RESTARTED 3u
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
/* 行名表(Flash;四数各自挂范围行,口径不许含糊) */
|
||
static const char *const s_names[RR_ROW_N] = {
|
||
"上电次数", "计数范围", "计数可信", "计数写回", "复位原因",
|
||
"复位范围", "运行时长", "时长范围", "主站接入",
|
||
"发送帧数", "发送失败", "接收字节", "统计范围",
|
||
};
|
||
|
||
/* 帧缓存:发布序号变化才整帧拷一次(同装置信息页口径) */
|
||
static ui_data_info_t s_info;
|
||
static uint32_t s_seq;
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:取用最新装置信息帧(只在发布序号变化时整帧拷)
|
||
* 入口参数:无
|
||
* 返 回 值:1 - 有可用帧;0 - 首帧未到或本帧无效 uint8_t
|
||
* 限定条件:uiTask 绘制上下文
|
||
*/
|
||
static uint8_t rr_frame_ready(void) {
|
||
uint32_t now = ui_data_info_seq();
|
||
|
||
if (now != s_seq) {
|
||
s_seq = now;
|
||
if (ui_data_info(&s_info) != 0) {
|
||
(void)memset(&s_info, 0, sizeof(s_info));
|
||
return 0u;
|
||
}
|
||
}
|
||
return (s_info.valid != 0u) ? 1u : 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:开页复位缓存(强制下一次绘制重取最新帧,避免停在上一帧的计数上)
|
||
* 入口参数:无
|
||
* 返 回 值:无 void
|
||
* 限定条件:开页时由框架调用
|
||
*/
|
||
static void rr_init(void) {
|
||
s_seq = 0u;
|
||
(void)memset(&s_info, 0, sizeof(s_info));
|
||
}
|
||
|
||
/*
|
||
* 函数功能:复位原因编码 → 屏上中文(5 字以内,避免挤压值列)
|
||
* 入口参数:cause - 复位原因编码(RR_CAUSE_*) uint8_t
|
||
* 返 回 值:中文标签(Flash 字面量,恒非 NULL) const char *
|
||
* 限定条件:编码越界(含 SD2506 诊断混进来的位)一律落"未知",不猜
|
||
*/
|
||
static const char *rr_cause_str(uint8_t cause) {
|
||
const char *p;
|
||
|
||
switch (cause) {
|
||
case RR_CAUSE_IWDG: p = "独立看门狗"; break;
|
||
case RR_CAUSE_WWDG: p = "窗口看门狗"; break;
|
||
case RR_CAUSE_SFT: p = "软件复位"; break;
|
||
case RR_CAUSE_POR: p = "上电复位"; break;
|
||
case RR_CAUSE_PIN: p = "引脚复位"; break;
|
||
case RR_CAUSE_BOR: p = "掉电复位"; break;
|
||
default: p = "未知"; break;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:上电次数可信度编码 → 屏上中文
|
||
* 入口参数:state - 可信度编码(RR_SEQ_*) uint8_t
|
||
* 返 回 值:中文标签(Flash 字面量,恒非 NULL) const char *
|
||
* 限定条件:编码越界一律"未知"
|
||
* 函数说明:RESTARTED 的屏上话是"掉电重开"——它指的是 RTC 电池域掉过电
|
||
* (计数值本身还在,但跨不掉电那段已经不可信),不是"整机重启过"
|
||
*/
|
||
static const char *rr_seq_str(uint8_t state) {
|
||
const char *p;
|
||
|
||
switch (state) {
|
||
case RR_SEQ_FRESH: p = "首次启用"; break;
|
||
case RR_SEQ_CONTINUED: p = "延续"; break;
|
||
case RR_SEQ_RESTARTED: p = "掉电重开"; break;
|
||
default: p = "未知"; break;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:格式化运行时长(本次开机以来,秒)
|
||
* 入口参数:sec - 秒数 uint32_t
|
||
* a_buf - 输出缓冲 char *
|
||
* a_cap - 缓冲容量 uint32_t
|
||
* 返 回 值:无 void
|
||
* 限定条件:a_buf/a_cap 有效
|
||
* 函数说明:>= 1 天走 "%ud%02u:%02u"(ASCII 小写 d,避免为"天"字扩字模);
|
||
* < 1 天走 "%02u:%02u:%02u"。与装置信息页同一记法,两页不许各写一套
|
||
*/
|
||
static void rr_fmt_uptime(uint32_t sec, char *a_buf, uint32_t a_cap) {
|
||
uint32_t day = sec / RR_SEC_PER_DAY;
|
||
uint32_t rem = sec % RR_SEC_PER_DAY;
|
||
|
||
if (day > 0u) {
|
||
(void)lv_snprintf(a_buf, a_cap, "%luud%02lu:%02lu",
|
||
(unsigned long)day, (unsigned long)(rem / 3600u),
|
||
(unsigned long)((rem % 3600u) / 60u));
|
||
} else {
|
||
(void)lv_snprintf(a_buf, a_cap, "%02lu:%02lu:%02lu",
|
||
(unsigned long)(rem / 3600u),
|
||
(unsigned long)((rem % 3600u) / 60u),
|
||
(unsigned long)(rem % 60u));
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:总行数(固定 12 行,分 2 页)
|
||
* 入口参数:无
|
||
* 返 回 值:行数 uint16_t
|
||
* 限定条件:uiTask 绘制上下文(经框架回调)
|
||
*/
|
||
static uint16_t rr_count(void) {
|
||
return RR_ROW_N;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:逐行取名/值(13 行,见 s_names 表)
|
||
* 入口参数:row - 行下标 uint16_t
|
||
* name - 名输出缓冲 char *
|
||
* value - 值输出缓冲 char *
|
||
* 返 回 值:无 void
|
||
* 限定条件:uiTask 绘制上下文;首帧未到时数值行一律 "--"(不显示 0 冒充有数)
|
||
*/
|
||
static void rr_refresh(uint16_t row, char *name, char *value) {
|
||
const uint32_t cap = (uint32_t)UI_BUF_STRLEN;
|
||
uint8_t ready;
|
||
|
||
if (row >= RR_ROW_N) {
|
||
value[0] = (char)0;
|
||
return;
|
||
}
|
||
(void)lv_snprintf(name, cap, "%s", s_names[row]);
|
||
ready = rr_frame_ready();
|
||
|
||
if (ready == 0u) {
|
||
(void)lv_snprintf(value, cap, "%s", RR_INVALID);
|
||
return;
|
||
}
|
||
|
||
switch (row) {
|
||
case 0u: /* 上电次数 */
|
||
if ((s_info.boot_ready == 0u) || (s_info.boot_seq_state == RR_SEQ_UNKNOWN)) {
|
||
(void)lv_snprintf(value, cap, "%s", RR_INVALID);
|
||
} else {
|
||
(void)lv_snprintf(value, cap, "%lu", (unsigned long)s_info.boot_count);
|
||
}
|
||
break;
|
||
case 1u: /* 计数范围 */
|
||
(void)lv_snprintf(value, cap, "%s", (s_info.boot_ready == 0u) ? "未知" : "自电池周期");
|
||
break;
|
||
case 2u: /* 计数可信(四态;未初始化落"未知") */
|
||
(void)lv_snprintf(value, cap, "%s", rr_seq_str(s_info.boot_seq_state));
|
||
break;
|
||
case 3u: /* 计数写回 */
|
||
(void)lv_snprintf(value, cap, "%s",
|
||
(s_info.boot_write_failed != 0u) ? "未写回" : "正常");
|
||
break;
|
||
case 4u: /* 复位原因 */
|
||
(void)lv_snprintf(value, cap, "%s", rr_cause_str(s_info.rst_cause));
|
||
break;
|
||
case 5u: /* 复位范围 */
|
||
(void)lv_snprintf(value, cap, "%s", "本次开机");
|
||
break;
|
||
case 6u: /* 运行时长 */
|
||
rr_fmt_uptime(s_info.uptime_s, value, cap);
|
||
break;
|
||
case 7u: /* 时长范围 */
|
||
(void)lv_snprintf(value, cap, "%s", "本次开机");
|
||
break;
|
||
case 8u:
|
||
(void)lv_snprintf(value, cap, "%lu", (unsigned long)s_info.iec104_conn_up);
|
||
break;
|
||
case 9u:
|
||
(void)lv_snprintf(value, cap, "%lu", (unsigned long)s_info.iec104_apdu_tx);
|
||
break;
|
||
case 10u:
|
||
(void)lv_snprintf(value, cap, "%lu", (unsigned long)s_info.iec104_tx_fail);
|
||
break;
|
||
case 11u:
|
||
(void)lv_snprintf(value, cap, "%lu", (unsigned long)s_info.iec104_rx_bytes);
|
||
break;
|
||
default: /* 统计范围 */
|
||
(void)lv_snprintf(value, cap, "%s", "本次开机");
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=runreport 绑定引用):
|
||
* 只读列表页——selectable=1 走真分页(13 行 / UI_LIST_ROWS 行 = 2 页,页脚真实页码);
|
||
* band_h=UI_ROW_BAND_H(选中行黑字须配白带);无写入口,故无保存态/无三态
|
||
*/
|
||
const content_page_def_t ui_page_runreport_desc = {
|
||
.id = "runreport",
|
||
.title = "运行报告",
|
||
.selectable = 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,
|
||
.count_cb = rr_count,
|
||
.init_cb = rr_init,
|
||
.refresh = rr_refresh,
|
||
};
|