a98d4e06e8
- 描述符各加一行(机制 task-2 已建,本轮零框架代码改动):dc 300ms(电气量与 ac 同账);yx 500ms(遥信变位按 1s 内上屏到位,比 300 档少 40% 稳态重绘); clock 1000ms(快变项只有墙钟秒尾);device_info / runreport 1000ms 并把新鲜度 源换到通道二 .auto_seq = ui_data_info_seq(info 与 rt 同拍 200ms 发布) ——各页取值账目在该页描述符注释 - docs/UI设计:供给说明 §5.5 改六页全接终态(用户裁定 2026-11-13);内容页架构 字段表/内存表/变更记录同步;自动化测试流程补 rtlive 覆盖行;页面扩充方案 追加 74 张现势注(带日期史实行冻结不动) - .dsh/skills/lvgl-simulator:开关表加 --rtlive 行、黄金参考计数 66→74 同步 验证:模拟器 0错0警;菜单 8 状态 + 内容页 74 张 + rtlive PASS(lead 独立复跑 RC=0,池水位 5368/5448B 均不漂移——timer 开页创建关页销毁、分配归还); Keil 0错0警且 Code/RO/RW/ZI 与 task-2 基线逐字节一致(只改常量值);BOM 全补回。
272 lines
11 KiB
C
272 lines
11 KiB
C
/*
|
||
* 模块名称:UI Page Device Info (装置信息内容页)
|
||
* 模块功能:装置信息叶子节点(node 1)内容页——8 行只读信息:终端型号/软件版本/硬件版本/
|
||
* 软件校验/程序日期/运行时长取自 ui_data 装置信息帧(App 层装配,本文件零 App
|
||
* include),MAC/IP 取自 net_cfg RAM 副本(同层 BSP→BSP,与网口设置页同一来源、
|
||
* 同一格式化函数);向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
|
||
* 几何为何改:原值列 x=86 是参考图"标签带全角冒号 + 短值"的产物;本页要容 17 字符 MAC
|
||
* (102 px)与 15 字符 IP,故标签去冒号(4 汉字 = 48 px,右缘 52)、值列左对齐
|
||
* x=56(右缘 158 ≤ 160);8 行按 13 px 行距排到 129,不与页脚(143)相撞
|
||
* 页脚:显示真实页码"第 1 页,共 1 页"——原固定值"第 10 页"是参考项目遗留的假页码,
|
||
* 屏上不该出现与本机无关的数字(Lead 裁定 2026-10-25);副标题属产品口径,本批不动
|
||
* 软件校验行:构建期 CRC 步骤未落地前值为 "--"(写侧 App/app/ui_data_feed.c 的 sw_sum),
|
||
* 不抄参考项目的"4454"
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-09
|
||
* 修改记录:
|
||
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
|
||
* v2.0 2026-10-25 王建锋 道E task-15 回炉:6 行 Flash 字面量(含参考项目遗留值)→
|
||
* 8 行真值;行几何、页码口径与越界读防御一并处理
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_page_device_info.h"
|
||
|
||
#include "net_cfg.h"
|
||
#include "ui_data.h"
|
||
#include "ui_tokens.h"
|
||
|
||
/* 私有宏定义区 */
|
||
|
||
#define DI_ROW_N 8u
|
||
#define DI_INVALID "--"
|
||
#define DI_VAL_X 56 /* 值列 x:17 字符 MAC(102 px)右缘 158 落在屏内 */
|
||
#define DI_LABEL_X 4 /* 标签列 x:4 汉字(48 px)右缘 52,不压值列 */
|
||
#define DI_ROW_TOP 38 /* 首行槽顶:让开副标题(y=25) */
|
||
#define DI_ROW_PITCH 13
|
||
#define DI_SEC_PER_DAY 86400u
|
||
#define DI_SEC_PER_HOUR 3600u
|
||
#define DI_SEC_PER_MIN 60u
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
/* 行名表(Flash;无全角冒号,理由见文件头几何说明) */
|
||
static const char *const s_names[DI_ROW_N] = {
|
||
"终端型号", "软件版本", "硬件版本", "软件校验",
|
||
"程序日期", "MAC地址", "IP地址", "运行时长",
|
||
};
|
||
|
||
/* 帧缓存:发布序号变化才整帧拷一次(同实时量页口径,UI_数据供给设计说明.md §5.4) */
|
||
static ui_data_info_t s_info;
|
||
static uint32_t s_seq;
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:把常量串安全搬进显示缓冲(替代 strcpy,规范 7.5)
|
||
* 入口参数:a_dst - 目标缓冲(框架 UI_BUF_STRLEN) char *
|
||
* a_lit - 源字面量(长度由宏用 sizeof 算好) const char *
|
||
* a_len - 需拷贝字节数(不含 NUL) uint32_t
|
||
* 返回值:无
|
||
* 限定条件:a_dst/a_lit 非 NULL;a_len 超缓冲按缓冲截断
|
||
* 函数说明:代码规范 7.5 禁 strcpy;长度来自调用点 sizeof,写串与算长同一处
|
||
*/
|
||
static void di_put(char *a_dst, const char *a_lit, uint32_t a_len) {
|
||
if (a_len > ((uint32_t)UI_BUF_STRLEN - 1u)) {
|
||
a_len = (uint32_t)UI_BUF_STRLEN - 1u;
|
||
}
|
||
(void)memcpy(a_dst, a_lit, a_len);
|
||
a_dst[a_len] = (char)0;
|
||
}
|
||
|
||
#define DI_PUT(a_dst, a_lit) di_put((a_dst), (a_lit), (uint32_t)(sizeof(a_lit) - 1u))
|
||
|
||
/*
|
||
* 函数功能:取用最新装置信息帧(只在发布序号变化时整帧拷)
|
||
* 入口参数:无
|
||
* 返回值:1 - 有可用帧;0 - 首帧未到或本帧无效 uint8_t
|
||
* 限定条件:uiTask 绘制上下文
|
||
*/
|
||
static uint8_t di_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;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:开页复位缓存(强制下一次绘制重取最新帧)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
*/
|
||
static void di_init(void) {
|
||
s_seq = 0u;
|
||
(void)memset(&s_info, 0, sizeof(s_info));
|
||
}
|
||
|
||
/*
|
||
* 函数功能:把快照里的定长字符域安全搬进显示缓冲
|
||
* 入口参数:a_dst - 目标缓冲(框架 UI_BUF_STRLEN) char *
|
||
* a_src - 快照字段首地址 const char *
|
||
* a_cap - 字段容量(不含 NUL 的字节数,取 UI_DATA_*_LEN) uint32_t
|
||
* 返回值:1 - 拷到非空串;0 - 字段全零(视为无数据,调用方显示 "--") uint8_t
|
||
* 限定条件:a_dst/a_src 非 NULL
|
||
* 函数说明:契约里这些字段是"定长域、写侧不保证补 NUL",所以**按容量扫**而不是 strlen——
|
||
* 靠 strlen 读一个没封口的定长域就是越界读,模拟器(后面紧跟别的常量)大概率
|
||
* 侥幸、板端 Flash 布局一变就翻车,且这类 bug 从不报错只多显示几个字符
|
||
*/
|
||
static uint8_t di_copy_field(char *a_dst, const char *a_src, uint32_t a_cap) {
|
||
uint32_t len = 0u;
|
||
|
||
while ((len < a_cap) && (a_src[len] != (char)0)) {
|
||
len++;
|
||
}
|
||
if (len == 0u) {
|
||
return 0u;
|
||
}
|
||
if (len > ((uint32_t)UI_BUF_STRLEN - 1u)) {
|
||
len = (uint32_t)UI_BUF_STRLEN - 1u; /* 超长截断:宁可少显示几个字符也不越界 */
|
||
}
|
||
(void)memcpy(a_dst, a_src, len);
|
||
a_dst[len] = (char)0;
|
||
return 1u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:把开机秒格式化成运行时长
|
||
* 入口参数:a_sec - 开机秒 uint32_t
|
||
* a_buf - 输出缓冲 char *
|
||
* a_len - 缓冲长度(含 NUL) uint32_t
|
||
* 返回值:无
|
||
* 限定条件:a_buf 非 NULL
|
||
* 函数说明:不足一天给"时:分:秒",过一天给"N天(ASCII d) 时:分"(丢秒)——串长压进值列
|
||
* 104 px;"天"字故意不写中文:它不在 183 字子集字体里,写上去屏上是空白方块,
|
||
* 而为一个运行时才出现的格式字符扩字库要付 27 B RO + 一次字体重生成的代价
|
||
* (font-charset 门禁这次就把它拦下了,改 ASCII 而不是补字库);
|
||
* "1234567 s" 这种写法人眼算不出量级,反而是把换算推给读表的人
|
||
*/
|
||
static void di_fmt_uptime(uint32_t a_sec, char *a_buf, uint32_t a_len) {
|
||
uint32_t day = a_sec / DI_SEC_PER_DAY;
|
||
uint32_t rem = a_sec % DI_SEC_PER_DAY;
|
||
|
||
if (day == 0u) {
|
||
(void)lv_snprintf(a_buf, a_len, "%02u:%02u:%02u",
|
||
(unsigned)(rem / DI_SEC_PER_HOUR),
|
||
(unsigned)((rem / DI_SEC_PER_MIN) % DI_SEC_PER_MIN),
|
||
(unsigned)(rem % DI_SEC_PER_MIN));
|
||
} else {
|
||
(void)lv_snprintf(a_buf, a_len, "%ud %02u:%02u", (unsigned)day,
|
||
(unsigned)(rem / DI_SEC_PER_HOUR),
|
||
(unsigned)((rem / DI_SEC_PER_MIN) % DI_SEC_PER_MIN));
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:逐行取数(快照 6 行 + net_cfg 2 行)
|
||
* 入口参数:a_row - 全局行下标 0..7 uint16_t
|
||
* a_name - 名输出缓冲(框架 UI_BUF_STRLEN) char *
|
||
* a_value - 值输出缓冲 char *
|
||
* 返回值:无
|
||
* 限定条件:uiTask 绘制上下文;net_cfg RAM 副本已由 defaultTask 载入(本函数只读副本,
|
||
* 不碰配置引擎——配置引擎属主是 defaultTask)
|
||
* 函数说明:MAC/IP 两行不依赖装置信息帧:网络配置在自己的通路上已经就绪,快照还没发
|
||
* 也应该能读到本机地址(否则首帧前整屏都是 "--",白丢两行真值)
|
||
*/
|
||
static void di_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||
const char *nm = (a_row < DI_ROW_N) ? s_names[a_row] : "";
|
||
const char *src = NULL;
|
||
uint32_t cap = 0u;
|
||
uint32_t n;
|
||
|
||
n = (uint32_t)strlen(nm) + 1u;
|
||
if (n > (uint32_t)UI_BUF_STRLEN) {
|
||
n = (uint32_t)UI_BUF_STRLEN;
|
||
}
|
||
(void)memcpy(a_name, nm, n);
|
||
a_value[0] = (char)0;
|
||
|
||
if ((a_row == 5u) || (a_row == 6u)) {
|
||
if (a_row == 5u) {
|
||
uint8_t mac[6];
|
||
|
||
net_cfg_get_mac(mac);
|
||
(void)lv_snprintf(a_value, (size_t)UI_BUF_STRLEN, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||
(unsigned)mac[0], (unsigned)mac[1], (unsigned)mac[2],
|
||
(unsigned)mac[3], (unsigned)mac[4], (unsigned)mac[5]);
|
||
} else {
|
||
net_cfg_fmt_ipv4(net_cfg_get()->ip, a_value, (size_t)UI_BUF_STRLEN);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (di_frame_ready() == 0u) {
|
||
DI_PUT(a_value, DI_INVALID);
|
||
return;
|
||
}
|
||
|
||
switch (a_row) {
|
||
case 0u:
|
||
src = s_info.model;
|
||
cap = UI_DATA_MODEL_LEN;
|
||
break;
|
||
case 1u:
|
||
src = s_info.sw_ver;
|
||
cap = UI_DATA_VERSTR_LEN;
|
||
break;
|
||
case 2u:
|
||
src = s_info.hw_ver;
|
||
cap = UI_DATA_VERSTR_LEN;
|
||
break;
|
||
case 3u:
|
||
src = s_info.sw_sum;
|
||
cap = UI_DATA_SUM_LEN;
|
||
break;
|
||
case 4u:
|
||
src = s_info.build_date;
|
||
cap = UI_DATA_DATE_LEN;
|
||
break;
|
||
case 7u:
|
||
di_fmt_uptime(s_info.uptime_s, a_value, (uint32_t)UI_BUF_STRLEN);
|
||
return;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if ((src == NULL) || (di_copy_field(a_value, src, cap) == 0u)) {
|
||
DI_PUT(a_value, DI_INVALID);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=device_info 绑定引用):
|
||
* 只读信息页(非 selectable → 无选中带、无分页),页脚取真实页码 1/1
|
||
*/
|
||
const content_page_def_t ui_page_device_info_desc = {
|
||
.id = "device_info",
|
||
.title = "装置信息",
|
||
.subtitle = "馈线自动化终端",
|
||
.subtitle_y = 25,
|
||
.selectable = 0u,
|
||
.top_y = DI_ROW_TOP,
|
||
.pitch = DI_ROW_PITCH,
|
||
.band_h = 0,
|
||
.text_dy = 0,
|
||
.left_x = DI_LABEL_X,
|
||
.right_x = DI_VAL_X,
|
||
.rows_per_page = 0u,
|
||
.row_count = DI_ROW_N,
|
||
.rows = NULL,
|
||
.fixed_cur_page = 1u,
|
||
.fixed_total_pages = 1u,
|
||
.init_cb = di_init,
|
||
.refresh = di_refresh,
|
||
/* 自动失效轮询——通道二(新鲜度源换 info_seq;机制见 ui_content_page.h 的
|
||
* auto_ms/auto_seq 注,口径见供给说明 §5.5):info 与 rt 同拍发布(每 200ms
|
||
* 两帧连发,见 App/app/ui_data_feed.c 的 ui_data_feed_tick;App 侧节拍值,
|
||
* 本文件不 include App 头,口径引用文档);本页可见内容近静态(型号/版本/
|
||
* 身份),只有 IP、端口等网络事件级变化——取 1000ms 大档:事件上屏最坏
|
||
* 200 + 1000 = 1200ms,对配置生效核对足够;seq 恒随帧走,周期即稳态重绘频率,
|
||
* 近静态页没必要快 */
|
||
.auto_ms = 1000u,
|
||
.auto_seq = ui_data_info_seq,
|
||
};
|