Files
STM32F4-Base/Drivers/BSP/UI/ui_page_device_info.c
T
wandering1 a98d4e06e8 UI 其余五个实时页接入框架自动失效(按内容变化节拍分档取值)
- 描述符各加一行(机制 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 全补回。
2026-11-13 14:51:49 +08:00

272 lines
11 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 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,
};