Files
STM32F4-Base/Drivers/BSP/UI/ui_page_runreport.c
T
super_agent_bot 416e92f109
CI / 静态门禁 (pre-commit) (push) Failing after 3m10s
CI / 主机侧单测 (MSVC) (push) Failing after 44s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 47s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s
docs(ui): Issue #53 台架轮复核销项——六页真机判据上板复核完成(RESTARTED 标不可验)
台架轮(2026-11-06,实机 192.168.31.100 生产固件,boot 187–193,
ST-LINK + RemoDispBus 7003 按键注入/抓帧 + FTP 取证,全程持 bench 锁):

- 判据1 真记录翻页与保持:oper 73 条 19 页 / yk 34 条 9 页 / start 2 条
  1 页实机翻页连续,页数与盘上条数交叉吻合;跨 188–193 六次重启
  (IWDG/NRST/系统复位,RAM 每次全清)YK/START/WDEV md5 逐字节不变,
  RUN/SOE/OPER/SELFTEST 纯追加、旧记录区零改写。
- 判据2 清除记录后对应页归零:oper 73→0、start 2→0、yk 34→0(页显
  「无记录」+ NAND 对应文件消失);告警行空类清除幂等;RUN/SOE/
  SELFTEST 清除前后逐字节一致(逐行独立清除证实);OPER 由后续写入
  自动重建且恰含三条清类留痕、清 oper 自身不留痕(留痕契约证实)。
- 判据3 上电一次 boot_count +1:187→193 六连号无跳变,电池域 SRAM
  保持+回写正常;本机无市电开关,以系统复位代理完整上电(覆盖
  SRAM 存储语义),真·拔电 POR 计数差额留人工一眼补验。
- 判据4 拔 RTC 电池落 RESTARTED:台架不可验(用户裁定 2026-11-05),
  销项不挂账;seq_state 四态判定归主机单测 test/host/boot_stats/ 覆盖。
- 判据5 IWDG 复位落「独立看门狗」:SWD 热插拔现场收窄看门狗窗口
  (PR=0/RLR=2,窗口 ≈0.37ms ≪ 100ms 喂狗节拍)触发真实计数器溢出
  → rst_cause=独立看门狗;咬后 wd_platform_start 自愈回 PR=4/RLR=1249。
  调试停机(DBGMCU_APB1_FZ 实读 0)与 NRST 硬脉冲两条不咬路径的
  归因已记台架台账(Issue #53 评论)。
- 判据6 主站接入一次 conn_up +1:75s 静默窗(前后帧逐字节相同)后
  单次 STARTDT 连接恰好 +1,发送帧数 +1、接收字节 +6B 交叉印证。

销项:六页文件头「台架待补」段改为「台架复核已完成」(模拟器口径
保留);模拟器差距清单 §2.3 开机账本行更新复核口径;登记簿 §7
「UI 六页真机判据」行删除(工单授权),交叉引用同步(任务规划
README、产品需求 FR-SOE-3)。清除动作是判据 2 本体,清前全量
NAND 记录已备份(PC 侧),留证于 Issue #53 台架轮评论。

门禁:check_doc_links --all 213 文档过;主机侧单测 48 runner
4257 断言全过;ARMCC 全量构建 0 Error 0 Warning
(Code=254820 RO=30896 RW=2072 ZI=150104);ui-golden 菜单 8 状态
+ 内容页 74 张黄金参考一致(本机按部署文档第 3 步补装 SDL2 SDK)。
附带:内存数字两文档随 .map 同步——RAM 台账受管区 3 行 +
RAM 使用分析快照 3 行(mem-budget / mem-numbers 门禁随本票
Drivers/BSP staged 触发的 HEAD 既有漂移修正:#73/#79 合入后未跑
--write;285,716 B 与体积凭证一致,全部 PASS/参考,无红线变动)。
体积凭证指纹陈旧同为 HEAD 既有(488f6fb 起),本票纯注释/文档
不动体积面,不越权刷新。
2026-11-06 03:49:18 +08:00

280 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**——
* 值列再加字或换字号必切边;要扩文案必须先减字或改标签列宽度,别直接加字。
* 台架复核已完成(2026-11-06,Issue #53 台架轮、实机 boot 187–193,证据贴 Issue #53 评论):
* 上电一次 boot_count +1 真机复核——链条 187→193 六连击无跳号,SRAM 保持 +
* 写回正常(本机无市电开关,用 ST-LINK 系统复位代理完整上电,电池域存储语义
* 全覆盖);IWDG 复位后 rst_cause 落「独立看门狗」经 SWD 缩窗真溢出复核;
* 主站接入一次 conn_up +1 经静默窗 +1 精确归因复核;拔 RTC 电池落 RESTARTED
* 一项台架不可验(用户裁定 2026-11-05,销项不挂账)。模拟器无 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,
};