feat(ui-pages): 遥信量 33 行固定名表页接实时快照(新增 yx_dp)
批 4 主体(票 task-15;规格 docs/UI设计/页面扩充详细方案.md §3.1.3 方案 A)。V0.1 只有 6 点有 硬件来源,其余 27 点在屏上就是 "--"——这是真实状态,不是页面没做完。 页面与数据 - 33 行固定名表(Flash const)+ 动态分页(8 行/页 = 5 页)。两处命名不照抄文档简写:idx0~3 屏上写「位置1~4」而非「YK1~4 位置」(遥信页出现 YK 前缀会被读成遥控对象,对应关系留在注释); idx30 写「保护闭锁」(写侧位号注释的语义即此,用编号反而丢信息)。idx4~13、idx31~32 点表未 定义功能名,如实写点号占位(YX5…、YX32),不编造功能名。 - 值映射次序:先判无效 → 再判单/双点 → 最后看位值。无效位上的位值不代表任何事实,次序颠倒就会 把垃圾位画成「复归」。 - 快照新增 yx_dp 双点位图(+8 B)。不教页面"前 4 点是双点":单双点是 104 点表事实(YX_DP_N / iec104_pts_yx_is_dp),抄进表现层就是第二真相——点表把双点段挪位或改起始下标,屏上不报错, 只会用错词形容同一个点。写侧逐点问 is_dp() 拼位图。 - UI_DATA_YX_N(33) 与 iec104_pts_yx_count() 编译期比不了(那个 #define 在协议层 .c 内),故写侧 留一条一次性 DBG_ERROR(约 90 B Flash,只在真漂移时打一次)。名表行数 = UI_DATA_YX_N 由数组 宽度法编译期守卫钉住(ARMCC C99 无 _Static_assert,本仓惯用写法)。 - 补 include 一条实测坑:DBG_ERROR 是宏,漏 include "dbg_log.h" 时 ARMCC 把它当隐式声明函数放过 编译期,错落在链接期(L6218E Undefined symbol DBG_ERROR)——报错点离根因隔了一整个链接步骤, 已在 include 处写明「DBG_TAG 必须在 include 之前」。 一个只有像素文本断言能抓到的截断 bug - 照抄批 1 的 *_PUT(dst, 源) 宏(长度取 sizeof(源)),但本表源是 const char *const 数组的 **元素(指针)**,sizeof 得 4 → 「故障总」被截成「故」,而 "YX9" 恰好 4 字节看起来完全正常。 编译器 / cppcheck / 编码钩子全不响。定位来自断言:第 2 页「过流Ⅰ段」匹配不到,同一行值列 「复归」却在原位 = 名字被截、值没错位。修法:长度取目标缓冲容量 UI_BUF_STRLEN。同名宏、不同 参数类别(数组 vs 指针),批 1 那处本身是对的,照抄才中招——教训写在宏定义上方。 模拟器与覆盖诚实性 - 固定帧发布 yx_dp = 0x0F,但刻意不把这几位置进 yx_valid:V0.1 无开入硬件,屏上就是 "--"。不用 「让它显示合位/分位」的假数据填满画面,那等于让黄金参考声称本机有开关位置遥信。 - 代价如实登记:合位/分位这条词映射当前**没有任何自动化证据**(像素恒 IV;页面依赖 LVGL 进不了 主机单测)。已写进方案 §3.1.3 落地注与契约落地进度·四,作为开入落地后的台架判据,不假装测过。 测试 - 主机单测 +2 条(TC-UIDATA-009c 双点位图原样透传、不被 valid 掩;009d 点数 33 的独立见证,注明 绊红时要同步改哪四处)→ 85/85。 - 文本断言 +33 条(yx / yxp2 两态,含页码钉住 33/8 = 5 页的分页数学);两页像素基线单独一笔。 - font-charset 本批零新增字:名表用字全部命中既有 183 字子集——「置/故/障/总/外/部」正属批 1 成组并入 KEEP_EXTRA 的那批字,第二次兑现(当时若按 extract 输出重写字符集,这些字早被洗掉, 上屏是空白,而门禁只会说「字不在清单里」)。 体积(同一棵树、构建前后的实测增量) - Code 224272→224688(+416);RO 24040→24640(+600);RW 1324→1328(+4); ZI 134844→135088(+244)→ Flash 约 +1020 B、RAM +248 B。 - RO 增量主要是 33 行名表与名称串,是"把 33 个真点名上屏"的代价,不是可省开销。 跳过的门禁(三条同族,同一根因):mem-numbers / mem-budget / size-gate(L0c)——台账与体积凭证 回填是 Lead 单写者,本分支不做静默处理,数值见上。
This commit is contained in:
@@ -121,7 +121,17 @@ typedef struct {
|
||||
uint8_t rsv4[3]; /* 预留(对齐填充) */
|
||||
ui_data_yc_t ph[UI_DATA_PH_MAX]; /* 下标 = 相量实例序(含谐波),见 UI_DATA_PH_MAX 注 */
|
||||
uint64_t yx_bits; /* 遥信位图:bit i = 第 i 点"合/动作" */
|
||||
uint64_t yx_valid; /* 遥信有效位图:bit i = 1 该点有硬件来源 */
|
||||
uint64_t yx_valid; /* 遥信有效位图: bit i = 1 该点有硬件来源 */
|
||||
uint64_t yx_dp; /* 遥信双点位图: bit i = 1 该点是双点型(M_DP)
|
||||
* 值列打"合位/分位",单点打"动作/复归"。
|
||||
* 为什么下发位图而不让页面自己认定"前 4 点是
|
||||
* 双点":单/双点是**104 点表事实**(见
|
||||
* App/protocol/iec104/iec104_pts.c 的 YX_DP_N 与
|
||||
* iec104_pts_yx_is_dp),抄一份进表现层就是第二
|
||||
* 真相——点表把某点改成双点、或把双点段挪到下标
|
||||
* 中段,屏上不会报错,只会用错词形容同一个点。
|
||||
* V0.1 双点 4 点无开入硬件 → 不在 yx_valid 内,
|
||||
* 值列仍是 "--"(本字段不参与无效判定) */
|
||||
uint8_t freq_valid; /* 1 = 测频读数新鲜(幅值门 OK 且已收敛) */
|
||||
uint8_t rsv2[3]; /* 预留(对齐填充) */
|
||||
float freq_hz; /* 测得频率(Hz);freq_valid=0 时不可消费 */
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
#include "ui_page_view_plate.h"
|
||||
#include "ui_page_view_set.h"
|
||||
#include "ui_page_view_word.h"
|
||||
#include "ui_page_yx.h"
|
||||
|
||||
const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT] = {
|
||||
{ 1u, &ui_page_device_info_desc }, /* node 1 装置信息 */
|
||||
{ 3u, &ui_page_ac_desc }, /* node 3 交流量 */
|
||||
{ 4u, &ui_page_dc_desc }, /* node 4 直流量 */
|
||||
{ 5u, &ui_page_yx_desc }, /* node 5 遥信量 */
|
||||
{ 13u, &ui_page_view_param_desc }, /* node 13 参数 */
|
||||
{ 14u, &ui_page_view_set_desc }, /* node 14 定值 */
|
||||
{ 15u, &ui_page_view_word_desc }, /* node 15 控制字 */
|
||||
|
||||
@@ -25,7 +25,7 @@ typedef struct {
|
||||
const content_page_def_t *desc; /* 页面描述符(Flash const) */
|
||||
} ui_page_entry_t;
|
||||
|
||||
#define UI_PAGE_MAP_COUNT 11u /* 绑定了内容页的叶子数 */
|
||||
#define UI_PAGE_MAP_COUNT 12u /* 绑定了内容页的叶子数 */
|
||||
|
||||
extern const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT];
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 模块名称:UI Page YX (遥信量内容页)
|
||||
* 模块功能:33 点遥信的固定名表页(方案 A)——每行一个遥信点,值列按快照的三个位图
|
||||
* (有效 / 动作 / 单双点)映射成"合位-分位"、"动作-复归"或"--";动态列表页,
|
||||
* 数据只读 Drivers/BSP/UI/ui_data.h 的实时快照,零 App include
|
||||
* 为什么是 33 行固定表(V0.1 只有 6 点有硬件来源,另外 27 行都是 "--"):
|
||||
* ① 与 104 点表一一对应,现场拿这张屏就能与上位机逐点对;② 开入落地后本页
|
||||
* 零改动自动变真;③ 只显有效点的做法看不出"缺哪些点",而且点数一变黄金参考
|
||||
* 就得重做(把判据建立在会漂的东西上等于没有判据)——取舍记录在
|
||||
* docs/UI设计/页面扩充详细方案.md §3.1.3
|
||||
* 名表为什么在页面侧而不在快照里:显示文案属表现层(同文档 §8);点数与单/双点性质
|
||||
* 反过来**必须**由写侧下发(那是点表事实),两者边界见 ui_data.h 的 yx_dp 注释
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 4)
|
||||
*/
|
||||
/* 头文件包含区 */
|
||||
|
||||
#include "ui_page_yx.h"
|
||||
|
||||
#include "ui_data.h"
|
||||
#include "ui_tokens.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
/*
|
||||
* 33 点固定名表(下标 = 遥信位图位号 = 104 点表 IOA - 1,IOA 1~33)
|
||||
*
|
||||
* 两处偏离文档的命名(文档 §3.1.3 用的是区间简写,落到逐行名字时必须选一个,理由写在
|
||||
* 这里而不是留给人猜):
|
||||
* · 文档把 idx0~3 写成「YK1~4 位置」:遥信页上出现"YK"前缀会被读成遥控对象(那是
|
||||
* 另一页的事),屏上只写「位置1~4」,与遥控的对应关系留在本注释;
|
||||
* · 文档把 idx30~32 写成「闭锁1~3」:idx30 在写侧有明确语义来源(保护闭锁,定值校验
|
||||
* 和自检失败),用编号反而把信息丢了;idx31/32 点表未定义功能名,如实写点号占位,
|
||||
* **不编造功能名**。同理 idx4~13(YX5~14)也用点号占位。
|
||||
*
|
||||
* 长度与 UI_DATA_YX_N 的一致性由下面的编译期数组守卫钉住(本仓惯用宽度法,ARMCC C99
|
||||
* 没有 _Static_assert):名表少写/多写一行直接编译失败,不会到了屏上才少一行
|
||||
*/
|
||||
static const char *const s_yx_name[UI_DATA_YX_N] = {
|
||||
"位置1", "位置2", "位置3", "位置4", /* 0~3 双点;V0.1 无开入硬件 */
|
||||
"YX5", "YX6", "YX7", "YX8", "YX9", /* 4~8 点表未定义功能名 */
|
||||
"YX10", "YX11", "YX12", "YX13", "YX14", /* 9~13 (同上,不编名) */
|
||||
"故障总", "过流Ⅰ段", "过流Ⅱ段", "过流Ⅲ段", /* 14~17 YX15~18,有硬件来源 */
|
||||
"零序Ⅰ段", "零序Ⅱ段", /* 18,19 V0.1 无该判据上送 */
|
||||
"接地故障", "重载", "过载", /* 20~22 过载(YX23)有来源 */
|
||||
"外部遥信1", "外部遥信2", "外部遥信3", "外部遥信4", /* 23~26 开入未实现 */
|
||||
"外部遥信5", "外部遥信6", "外部遥信7", /* 27~29 */
|
||||
"保护闭锁", "YX32", "YX33", /* 30~32 闭锁(YX31)有来源 */
|
||||
};
|
||||
|
||||
/* 名表行数与快照点数一致(不一致 = 编译期错误,见上注) */
|
||||
typedef char yx_name_count_check[(sizeof(s_yx_name) / sizeof(s_yx_name[0]) == UI_DATA_YX_N) ? 1 : -1];
|
||||
|
||||
/* 值列词表(全在既有 183 字子集内,font-charset 实测零新增) */
|
||||
#define YX_INVALID "--"
|
||||
#define YX_DP_ON "合位"
|
||||
#define YX_DP_OFF "分位"
|
||||
#define YX_SP_ON "动作"
|
||||
#define YX_SP_OFF "复归"
|
||||
|
||||
/* 帧缓存:只在发布序号变化时整帧拷一次(一帧约 200 B,逐行只读标量) */
|
||||
static ui_data_rt_t s_rt;
|
||||
static uint32_t s_seq = 0xFFFFFFFFu;
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:定长缓冲内拷字面量(代码规范 7.5 禁 strcpy;长度取调用点 sizeof)
|
||||
* 入口参数:a_dst - 目标缓冲(框架行缓冲 UI_BUF_STRLEN) char *
|
||||
* a_src - 源字面量(含 NUL) const char *
|
||||
* len - 目标容量 uint32_t
|
||||
* 返回值:无
|
||||
* 限定条件:源串长度必须小于 len(编译期由字面量 sizeof 保证,不做运行期截断——
|
||||
* 截断只会把"动作"打成"动",比读错更难查)
|
||||
*/
|
||||
static void yx_put(char *a_dst, const char *a_src, uint32_t len) {
|
||||
uint32_t i = 0u;
|
||||
|
||||
if (len == 0u) {
|
||||
return;
|
||||
}
|
||||
while (((i + 1u) < len) && (a_src[i] != '\0')) {
|
||||
a_dst[i] = a_src[i];
|
||||
i++;
|
||||
}
|
||||
a_dst[i] = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* 长度参数**必须取目标缓冲容量**,不能取 sizeof(源):本表的源是 `const char *const`
|
||||
* 表里的**指针**,`sizeof(lit)` 得到的是指针宽度(4/8),于是 "故障总" 会被拷成第一个
|
||||
* 汉字 "故"——ASCII 名("YX9" 恰好 4 字节)看着完全正常,CJK 名集体缺尾巴。这个错
|
||||
* 编译器、cppcheck、编码钩子都不会响,是像素文本断言把它揪出来的(第 2 页第 7 行
|
||||
* "过流Ⅰ段" 匹配不到,值列 "复归" 却在原位 = 名字被截、值没错位)。
|
||||
* 批 1 那几处 *_PUT 宏传的是**字面量数组**(sizeof 就是数组长度),照抄到这里就中招——
|
||||
* 同名宏、不同参数类别,正是"看着像同构其实不同"的那类坑。
|
||||
*/
|
||||
#define YX_PUT(dst, lit) yx_put((dst), (lit), (uint32_t)UI_BUF_STRLEN)
|
||||
|
||||
/*
|
||||
* 函数功能:开页复位帧缓存(接框架 init_cb)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内(uiTask)
|
||||
* 函数说明:序号必须复位——否则重开本页会沿用上次开页的帧号,首帧之前的 "--" 状态
|
||||
* 可能被永久留在屏上(批 1 已踩过一次,同一条纪律再用一遍)
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
s_seq = 0xFFFFFFFFu;
|
||||
(void)memset(&s_rt, 0, sizeof(s_rt));
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按发布序号刷新帧缓存
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:uiTask 绘制链内(refresh 前)
|
||||
* 函数说明:ui_data_rt() 的通道是 0=成功 / -1=尚无首帧(不是 bool 成败);失败时把
|
||||
* 序号打回不可能的值,下一轮一定重试,不会停在半新不旧的帧上
|
||||
*/
|
||||
static void pg_sync(void) {
|
||||
uint32_t seq = ui_data_rt_seq();
|
||||
|
||||
if ((seq == s_seq) && (s_rt.valid != 0u)) {
|
||||
return;
|
||||
}
|
||||
if (ui_data_rt(&s_rt) != 0) {
|
||||
s_seq = 0xFFFFFFFFu;
|
||||
(void)memset(&s_rt, 0, sizeof(s_rt));
|
||||
return;
|
||||
}
|
||||
s_seq = seq;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:总行数(固定 33 点,不随有效性增减)
|
||||
* 入口参数:无
|
||||
* 返回值:行数 uint16_t
|
||||
* 限定条件:框架绘制/翻页链内
|
||||
* 函数说明:故意返回常量——"只显有效点"会让现场无法判断缺了哪些点(方案 B 的缺点),
|
||||
* 且行数变化会让黄金参考随运行态漂移
|
||||
*/
|
||||
static uint16_t pg_count(void) {
|
||||
return (uint16_t)UI_DATA_YX_N;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:逐行取数(名 = 固定名表,值 = 三个位图映射出的词)
|
||||
* 入口参数:a_row - 行下标(0..32) uint16_t
|
||||
* a_name - 名输出缓冲(UI_BUF_STRLEN) char *
|
||||
* a_value - 值输出缓冲 char *
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链内(双缓冲各调一次,本函数无副作用)
|
||||
* 函数说明:判定顺序是"先无效、再单双点、最后位值"——无效位上的位值是真随机垃圾
|
||||
* (位图没更新过就是 0,更新了也不代表该点有效),所以 IV 必须排在最前;
|
||||
* 双点段 V0.1 恒无效,"合位/分位"这条支路当前不可达但必须写:开入一接上
|
||||
* 就走通,而按协议写死的 4 个点本来就是双点(可达性由点表决定,不由本页决定)
|
||||
*/
|
||||
static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||||
uint64_t bit;
|
||||
|
||||
a_value[0] = '\0';
|
||||
if (a_row >= (uint16_t)UI_DATA_YX_N) {
|
||||
a_name[0] = '\0';
|
||||
return; /* 防御:行号越界不越读名表(count 恒 33,走不到) */
|
||||
}
|
||||
pg_sync();
|
||||
YX_PUT(a_name, s_yx_name[a_row]);
|
||||
bit = (uint64_t)1u << (uint8_t)a_row;
|
||||
if ((s_rt.valid == 0u) || ((s_rt.yx_valid & bit) == 0u)) {
|
||||
YX_PUT(a_value, YX_INVALID);
|
||||
return;
|
||||
}
|
||||
if ((s_rt.yx_dp & bit) != 0u) {
|
||||
YX_PUT(a_value, ((s_rt.yx_bits & bit) != 0u) ? YX_DP_ON : YX_DP_OFF);
|
||||
return;
|
||||
}
|
||||
YX_PUT(a_value, ((s_rt.yx_bits & bit) != 0u) ? YX_SP_ON : YX_SP_OFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=yx 绑定引用):
|
||||
* 固定 33 行的动态页(rows=NULL + count_cb/refresh),8 行/页 = 5 页;band_h 必须 >0
|
||||
* (selectable 页选中行是黑字,无白带即不可见,同记录页约定)
|
||||
*/
|
||||
const content_page_def_t ui_page_yx_desc = {
|
||||
.id = "yx",
|
||||
.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,
|
||||
.init_cb = pg_init,
|
||||
.count_cb = pg_count,
|
||||
.refresh = pg_refresh,
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 模块名称:UI Page YX (遥信量内容页)
|
||||
* 模块功能:实时数据 → 遥信量 叶子的内容页声明——向框架 ui_content_page 提供页面描述符
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 4;规格 =
|
||||
* docs/UI设计/页面扩充详细方案.md §3.1.3 方案 A)
|
||||
*/
|
||||
#ifndef UI_PAGE_YX_H
|
||||
#define UI_PAGE_YX_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "ui_content_page.h"
|
||||
|
||||
/* 页面描述符(定义在 ui_page_yx.c;生成物按菜单树 #page=yx 引用本符号) */
|
||||
extern const content_page_def_t ui_page_yx_desc;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* UI_PAGE_YX_H */
|
||||
Reference in New Issue
Block a user