Files
STM32F4-Base/Drivers/BSP/UI/ui_page_runreport.c
T
wandering1 94b0d79026 feat(ui)+chore(docs): UI 补齐清单清空;数据可信销项与「卡」号清退
本笔含两组收尾工作(同一工作区内交错在 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 字),随该笔入库。
2026-10-28 07:23:16 +08:00

277 lines
12 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 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,
};