416e92f109
台架轮(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 起),本票纯注释/文档 不动体积面,不越权刷新。
280 lines
12 KiB
C
280 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**——
|
||
* 值列再加字或换字号必切边;要扩文案必须先减字或改标签列宽度,别直接加字。
|
||
* 台架复核已完成(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,
|
||
};
|