diff --git a/Drivers/BSP/UI/ui_page_registry.c b/Drivers/BSP/UI/ui_page_registry.c index 367bdfd4..6bf225e9 100644 --- a/Drivers/BSP/UI/ui_page_registry.c +++ b/Drivers/BSP/UI/ui_page_registry.c @@ -17,11 +17,19 @@ #include "ui_page_runlog.h" #include "ui_page_selftest.h" #include "ui_page_soe.h" +#include "ui_page_view_param.h" +#include "ui_page_view_plate.h" +#include "ui_page_view_set.h" +#include "ui_page_view_word.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 直流量 */ + { 13u, &ui_page_view_param_desc }, /* node 13 参数 */ + { 14u, &ui_page_view_set_desc }, /* node 14 定值 */ + { 15u, &ui_page_view_word_desc }, /* node 15 控制字 */ + { 16u, &ui_page_view_plate_desc }, /* node 16 软压板 */ { 30u, &ui_page_net_cfg_desc }, /* node 30 网口设置 */ { 33u, &ui_page_soe_desc }, /* node 33 SOE记录 */ { 39u, &ui_page_selftest_desc }, /* node 39 自检记录 */ diff --git a/Drivers/BSP/UI/ui_page_registry.h b/Drivers/BSP/UI/ui_page_registry.h index c135240a..ac46b20d 100644 --- a/Drivers/BSP/UI/ui_page_registry.h +++ b/Drivers/BSP/UI/ui_page_registry.h @@ -25,7 +25,7 @@ typedef struct { const content_page_def_t *desc; /* 页面描述符(Flash const) */ } ui_page_entry_t; -#define UI_PAGE_MAP_COUNT 7u /* 绑定了内容页的叶子数 */ +#define UI_PAGE_MAP_COUNT 11u /* 绑定了内容页的叶子数 */ extern const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT]; diff --git a/Drivers/BSP/UI/ui_page_view_param.c b/Drivers/BSP/UI/ui_page_view_param.c new file mode 100644 index 00000000..20284505 --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_param.c @@ -0,0 +1,94 @@ +/* + * 模块名称:UI Page view_param (参数查看内容页) + * 模块功能:参数定值 → 查看 → 参数 的内容页——8 行数值表(PT/CT 一次二次额定 + 4 个返回系数),每页 8 行、1 页; + * 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显; + * 本文件零 App include、零写路径(查看页没有"保存"这件事) + * 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型), + * 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号 + * 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致 + * 适用平台:STM32F407ZGTx / PC 模拟器(共用源码) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3) + */ +/* 头文件包含区 */ +#include "ui_page_view_param.h" + +#include "ui_tokens.h" +#include "ui_w_setlist.h" +#include "ui_w_setview.h" + +/* 静态变量定义区 */ + +/* 本页实例(组件状态:编辑态等;查看页只用行游标与反显,实例仍须独占一份, + * 与别的页面共用会互相踩状态——同 ui_w_records 的实例化约定) */ +static ui_w_setlist_t s_sl; + +/* 函数定义区 */ + +/* + * 函数功能:开页绑定视图(行表 + 取数/反显回调一次配齐;接框架 init_cb) + * 入口参数:无 + * 返回值:无 + * 限定条件:框架开页链内调用(uiTask) + * 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来, + * 开页复位比"信任上一次已经清干净"可靠 + */ +static void pg_init(void) { + ui_w_setview_attach(&s_sl, UI_W_SETVIEW_PARAM); +} + +/* + * 函数功能:数据行数(框架 count_cb;查看视图没有保存行) + * 入口参数:无 + * 返回值:行数 uint16_t + * 限定条件:框架绘制/翻页链内 + */ +static uint16_t pg_count(void) { + return ui_w_setlist_count(&s_sl); +} + +/* + * 函数功能:逐行取数(框架 refresh;内部先按发布序号同步参数帧) + * 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) { + ui_w_setview_refresh(&s_sl, a_row, a_name, a_value); +} + +/* + * 函数功能:自绘层(越界行反显;框架内置区块画完后调用) + * 入口参数:a_layer / a_clip - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) { + ui_w_setlist_draw(&s_sl, a_layer, a_clip); +} + +/* + * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_param 绑定引用) + * edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互, + * ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在 + * 查看页等价于"根本没有 ENTER 这条路") + */ +const content_page_def_t ui_page_view_param_desc = { + .id = "view_param", + .title = "参数", + .selectable = 1u, + .edit_capable = 0u, + .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, + .draw_cb = pg_draw, +}; diff --git a/Drivers/BSP/UI/ui_page_view_param.h b/Drivers/BSP/UI/ui_page_view_param.h new file mode 100644 index 00000000..63899e2e --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_param.h @@ -0,0 +1,27 @@ +/* + * 模块名称:UI Page view_param (参数查看内容页) + * 模块功能:参数查看叶子节点的内容页声明——向框架 ui_content_page 提供页面描述符 + * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3;规格 = + * docs/UI设计/页面扩充详细方案.md §3.2.6 查看列) + */ +#ifndef UI_PAGE_VIEW_PARAM_H +#define UI_PAGE_VIEW_PARAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ui_content_page.h" + +/* 页面描述符(定义在 ui_page_view_param.c;生成物按菜单树 #page=view_param 引用本符号) */ +extern const content_page_def_t ui_page_view_param_desc; + +#ifdef __cplusplus +} +#endif + +#endif /* UI_PAGE_VIEW_PARAM_H */ diff --git a/Drivers/BSP/UI/ui_page_view_plate.c b/Drivers/BSP/UI/ui_page_view_plate.c new file mode 100644 index 00000000..425994d7 --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_plate.c @@ -0,0 +1,94 @@ +/* + * 模块名称:UI Page view_plate (软压板查看内容页) + * 模块功能:参数定值 → 查看 → 软压板 的内容页——10 行二态表(9 个段投退 + 保护总投:投入/退出),每页 8 行、2 页; + * 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显; + * 本文件零 App include、零写路径(查看页没有"保存"这件事) + * 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型), + * 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号 + * 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致 + * 适用平台:STM32F407ZGTx / PC 模拟器(共用源码) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3) + */ +/* 头文件包含区 */ +#include "ui_page_view_plate.h" + +#include "ui_tokens.h" +#include "ui_w_setlist.h" +#include "ui_w_setview.h" + +/* 静态变量定义区 */ + +/* 本页实例(组件状态:编辑态等;查看页只用行游标与反显,实例仍须独占一份, + * 与别的页面共用会互相踩状态——同 ui_w_records 的实例化约定) */ +static ui_w_setlist_t s_sl; + +/* 函数定义区 */ + +/* + * 函数功能:开页绑定视图(行表 + 取数/反显回调一次配齐;接框架 init_cb) + * 入口参数:无 + * 返回值:无 + * 限定条件:框架开页链内调用(uiTask) + * 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来, + * 开页复位比"信任上一次已经清干净"可靠 + */ +static void pg_init(void) { + ui_w_setview_attach(&s_sl, UI_W_SETVIEW_PLATE); +} + +/* + * 函数功能:数据行数(框架 count_cb;查看视图没有保存行) + * 入口参数:无 + * 返回值:行数 uint16_t + * 限定条件:框架绘制/翻页链内 + */ +static uint16_t pg_count(void) { + return ui_w_setlist_count(&s_sl); +} + +/* + * 函数功能:逐行取数(框架 refresh;内部先按发布序号同步参数帧) + * 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) { + ui_w_setview_refresh(&s_sl, a_row, a_name, a_value); +} + +/* + * 函数功能:自绘层(越界行反显;框架内置区块画完后调用) + * 入口参数:a_layer / a_clip - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) { + ui_w_setlist_draw(&s_sl, a_layer, a_clip); +} + +/* + * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_plate 绑定引用) + * edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互, + * ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在 + * 查看页等价于"根本没有 ENTER 这条路") + */ +const content_page_def_t ui_page_view_plate_desc = { + .id = "view_plate", + .title = "软压板", + .selectable = 1u, + .edit_capable = 0u, + .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, + .draw_cb = pg_draw, +}; diff --git a/Drivers/BSP/UI/ui_page_view_plate.h b/Drivers/BSP/UI/ui_page_view_plate.h new file mode 100644 index 00000000..3e8d22ca --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_plate.h @@ -0,0 +1,27 @@ +/* + * 模块名称:UI Page view_plate (软压板查看内容页) + * 模块功能:软压板查看叶子节点的内容页声明——向框架 ui_content_page 提供页面描述符 + * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3;规格 = + * docs/UI设计/页面扩充详细方案.md §3.2.6 查看列) + */ +#ifndef UI_PAGE_VIEW_PLATE_H +#define UI_PAGE_VIEW_PLATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ui_content_page.h" + +/* 页面描述符(定义在 ui_page_view_plate.c;生成物按菜单树 #page=view_plate 引用本符号) */ +extern const content_page_def_t ui_page_view_plate_desc; + +#ifdef __cplusplus +} +#endif + +#endif /* UI_PAGE_VIEW_PLATE_H */ diff --git a/Drivers/BSP/UI/ui_page_view_set.c b/Drivers/BSP/UI/ui_page_view_set.c new file mode 100644 index 00000000..055a4605 --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_set.c @@ -0,0 +1,94 @@ +/* + * 模块名称:UI Page view_set (定值查看内容页) + * 模块功能:参数定值 → 查看 → 定值 的内容页——18 行数值表(三段过流/过负荷/过压/低压/零序/过频/低频 定值与延时),每页 8 行、3 页; + * 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显; + * 本文件零 App include、零写路径(查看页没有"保存"这件事) + * 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型), + * 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号 + * 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致 + * 适用平台:STM32F407ZGTx / PC 模拟器(共用源码) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3) + */ +/* 头文件包含区 */ +#include "ui_page_view_set.h" + +#include "ui_tokens.h" +#include "ui_w_setlist.h" +#include "ui_w_setview.h" + +/* 静态变量定义区 */ + +/* 本页实例(组件状态:编辑态等;查看页只用行游标与反显,实例仍须独占一份, + * 与别的页面共用会互相踩状态——同 ui_w_records 的实例化约定) */ +static ui_w_setlist_t s_sl; + +/* 函数定义区 */ + +/* + * 函数功能:开页绑定视图(行表 + 取数/反显回调一次配齐;接框架 init_cb) + * 入口参数:无 + * 返回值:无 + * 限定条件:框架开页链内调用(uiTask) + * 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来, + * 开页复位比"信任上一次已经清干净"可靠 + */ +static void pg_init(void) { + ui_w_setview_attach(&s_sl, UI_W_SETVIEW_SET); +} + +/* + * 函数功能:数据行数(框架 count_cb;查看视图没有保存行) + * 入口参数:无 + * 返回值:行数 uint16_t + * 限定条件:框架绘制/翻页链内 + */ +static uint16_t pg_count(void) { + return ui_w_setlist_count(&s_sl); +} + +/* + * 函数功能:逐行取数(框架 refresh;内部先按发布序号同步参数帧) + * 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) { + ui_w_setview_refresh(&s_sl, a_row, a_name, a_value); +} + +/* + * 函数功能:自绘层(越界行反显;框架内置区块画完后调用) + * 入口参数:a_layer / a_clip - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) { + ui_w_setlist_draw(&s_sl, a_layer, a_clip); +} + +/* + * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_set 绑定引用) + * edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互, + * ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在 + * 查看页等价于"根本没有 ENTER 这条路") + */ +const content_page_def_t ui_page_view_set_desc = { + .id = "view_set", + .title = "定值", + .selectable = 1u, + .edit_capable = 0u, + .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, + .draw_cb = pg_draw, +}; diff --git a/Drivers/BSP/UI/ui_page_view_set.h b/Drivers/BSP/UI/ui_page_view_set.h new file mode 100644 index 00000000..833addb4 --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_set.h @@ -0,0 +1,27 @@ +/* + * 模块名称:UI Page view_set (定值查看内容页) + * 模块功能:定值查看叶子节点的内容页声明——向框架 ui_content_page 提供页面描述符 + * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3;规格 = + * docs/UI设计/页面扩充详细方案.md §3.2.6 查看列) + */ +#ifndef UI_PAGE_VIEW_SET_H +#define UI_PAGE_VIEW_SET_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ui_content_page.h" + +/* 页面描述符(定义在 ui_page_view_set.c;生成物按菜单树 #page=view_set 引用本符号) */ +extern const content_page_def_t ui_page_view_set_desc; + +#ifdef __cplusplus +} +#endif + +#endif /* UI_PAGE_VIEW_SET_H */ diff --git a/Drivers/BSP/UI/ui_page_view_word.c b/Drivers/BSP/UI/ui_page_view_word.c new file mode 100644 index 00000000..36a18934 --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_word.c @@ -0,0 +1,94 @@ +/* + * 模块名称:UI Page view_word (控制字查看内容页) + * 模块功能:参数定值 → 查看 → 控制字 的内容页——4 行二态表(*_TRIP:告警/跳闸),每页 8 行、1 页; + * 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显; + * 本文件零 App include、零写路径(查看页没有"保存"这件事) + * 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型), + * 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号 + * 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致 + * 适用平台:STM32F407ZGTx / PC 模拟器(共用源码) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3) + */ +/* 头文件包含区 */ +#include "ui_page_view_word.h" + +#include "ui_tokens.h" +#include "ui_w_setlist.h" +#include "ui_w_setview.h" + +/* 静态变量定义区 */ + +/* 本页实例(组件状态:编辑态等;查看页只用行游标与反显,实例仍须独占一份, + * 与别的页面共用会互相踩状态——同 ui_w_records 的实例化约定) */ +static ui_w_setlist_t s_sl; + +/* 函数定义区 */ + +/* + * 函数功能:开页绑定视图(行表 + 取数/反显回调一次配齐;接框架 init_cb) + * 入口参数:无 + * 返回值:无 + * 限定条件:框架开页链内调用(uiTask) + * 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来, + * 开页复位比"信任上一次已经清干净"可靠 + */ +static void pg_init(void) { + ui_w_setview_attach(&s_sl, UI_W_SETVIEW_WORD); +} + +/* + * 函数功能:数据行数(框架 count_cb;查看视图没有保存行) + * 入口参数:无 + * 返回值:行数 uint16_t + * 限定条件:框架绘制/翻页链内 + */ +static uint16_t pg_count(void) { + return ui_w_setlist_count(&s_sl); +} + +/* + * 函数功能:逐行取数(框架 refresh;内部先按发布序号同步参数帧) + * 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) { + ui_w_setview_refresh(&s_sl, a_row, a_name, a_value); +} + +/* + * 函数功能:自绘层(越界行反显;框架内置区块画完后调用) + * 入口参数:a_layer / a_clip - 契约见 ui_content_page.h + * 返回值:无 + * 限定条件:框架绘制链内 + */ +static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) { + ui_w_setlist_draw(&s_sl, a_layer, a_clip); +} + +/* + * 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_word 绑定引用) + * edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互, + * ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在 + * 查看页等价于"根本没有 ENTER 这条路") + */ +const content_page_def_t ui_page_view_word_desc = { + .id = "view_word", + .title = "控制字", + .selectable = 1u, + .edit_capable = 0u, + .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, + .draw_cb = pg_draw, +}; diff --git a/Drivers/BSP/UI/ui_page_view_word.h b/Drivers/BSP/UI/ui_page_view_word.h new file mode 100644 index 00000000..19dbe137 --- /dev/null +++ b/Drivers/BSP/UI/ui_page_view_word.h @@ -0,0 +1,27 @@ +/* + * 模块名称:UI Page view_word (控制字查看内容页) + * 模块功能:控制字查看叶子节点的内容页声明——向框架 ui_content_page 提供页面描述符 + * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3;规格 = + * docs/UI设计/页面扩充详细方案.md §3.2.6 查看列) + */ +#ifndef UI_PAGE_VIEW_WORD_H +#define UI_PAGE_VIEW_WORD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ui_content_page.h" + +/* 页面描述符(定义在 ui_page_view_word.c;生成物按菜单树 #page=view_word 引用本符号) */ +extern const content_page_def_t ui_page_view_word_desc; + +#ifdef __cplusplus +} +#endif + +#endif /* UI_PAGE_VIEW_WORD_H */ diff --git a/Drivers/BSP/UI/ui_w_setlist.c b/Drivers/BSP/UI/ui_w_setlist.c index 27c67d17..eae8b7aa 100644 --- a/Drivers/BSP/UI/ui_w_setlist.c +++ b/Drivers/BSP/UI/ui_w_setlist.c @@ -146,7 +146,13 @@ void ui_w_setlist_refresh(ui_w_setlist_t *a_inst, uint16_t a_row, ui_w_editor_format(&a_inst->ed, a_value, UI_BUF_STRLEN); /* 编辑实时串 */ } else { - ui_w_editor_fmt_scaled(a_inst->get(r->id), r->decimals, a_value, UI_BUF_STRLEN); + /* 单位后缀(r->unit NULL = 不挂):先格式化到临时串再合成,一次界内写回, + * 不用 strcat 类函数(代码规范 7.5;且长度不足时会静默截半,屏幕上更难查) */ + char fmt_buf[UI_BUF_STRLEN]; + + ui_w_editor_fmt_scaled(a_inst->get(r->id), r->decimals, fmt_buf, UI_BUF_STRLEN); + (void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s%s", fmt_buf, + (r->unit != NULL) ? r->unit : ""); } return; } diff --git a/Drivers/BSP/UI/ui_w_setlist.h b/Drivers/BSP/UI/ui_w_setlist.h index 858183b1..356f66eb 100644 --- a/Drivers/BSP/UI/ui_w_setlist.h +++ b/Drivers/BSP/UI/ui_w_setlist.h @@ -63,6 +63,10 @@ typedef struct { int32_t min; /* 数值行下限(含;>=0;二态行忽略) */ int32_t max; /* 数值行上限(含;二态行忽略) */ const char *const *opts; /* 二态行:NUL 结尾 Flash 字符串串表(数值行 NULL) */ + const char *unit; /* 数值行单位后缀(Flash,NULL = 不挂;二态行忽略) + * 单位是"屏上的话",跟视图表走、不进 App 快照;只挂在 + * 非编辑态的数值串尾——编辑中是逐位输入串,挂单位会让 + * ui_w_editor 的位光标算错列宽(其契约:串内只有数字与点) */ } ui_w_setlist_row_t; /* 数据回调组(均 uiTask 上下文、页面绘制/按键链内调用;实现由批 2 页面跳板提供, diff --git a/Drivers/BSP/UI/ui_w_setview.c b/Drivers/BSP/UI/ui_w_setview.c new file mode 100644 index 00000000..4586a7c3 --- /dev/null +++ b/Drivers/BSP/UI/ui_w_setview.c @@ -0,0 +1,381 @@ +/* + * 模块名称:UI 定值查看视图模型(ui_w_setview) + * 模块功能:参数 / 定值 / 控制字 / 软压板四张视图表与它们的取数、换算、越界反显—— + * 供四个查看页薄跳板共用;声明与分层理由见 ui_w_setview.h + * 适用平台:STM32F407ZGTx / PC 模拟器(共用源码) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3) + */ +/* 头文件包含区 */ +#include + +#include "ui_w_setview.h" + +#include "ui_data.h" +#include "ui_tokens.h" /* UI_BUF_STRLEN:框架行缓冲长度(无帧占位串要写进同一缓冲) */ + +/* 静态变量定义区 */ + +/* 二态行的串表(下标 = 记录值;0/1 的语义来自 App/algo/prot_set.h 的 meta 注释: + * *_TRIP 0 = 告警 / 1 = 跳闸,*_EN 0 = 退出 / 1 = 投入;串表以 NULL 结尾, + * 组件按串表长度决定 ENTER 循环切换的范围) */ +const char *const s_opts_trip[3] = { "告警", "跳闸", NULL }; +const char *const s_opts_plate[3] = { "退出", "投入", NULL }; + +/* + * 四张视图表(Flash const)。行表的三项口径全部有真源,不在本文件里自由发挥: + * · id = App/algo/prot_set.h 的 prot_set_id_t 枚举序(BSP 不 include App 头, + * 故写裸数字并把枚举名注在行尾;对不上由主机单测与像素值两处暴露) + * · min/max = App/algo/prot_set.c 的 g_prot_set_meta 上下限(Q10)换算到本行 + * decimals 后的整数——**照抄换算结果,不自己拟定**;整定页复用同一批表, + * 界内外一处一份,不会出现"查看看着合法、整定说越界"的两套边界 + * · 显示名/小数位/单位 = docs/UI设计/页面扩充详细方案.md §3.2.2~§3.2.5 四张表; + * **唯一偏离**:四个返回系数按 2 位小数显示(文档写 3 位)。这不是排版取舍而是 + * 量值真实性问题——Q10 存不下 0.95:0.95×1024 截成 972,真存的是 0.94921875, + * 按 3 位显示就打出一位根本不存在的"9",整定单上写 0.95 的人看到 0.949 会怀疑 + * 装置、甚至反复重输(重输也还是 0.949)。2 位显示 0.95 才是这份存储在它自己可 + * 分辨精度上的正确读数;文档的 3 位来自"把 Q10 当成十进制存储"这个误会 + * 单位挂在值串尾(A/V/ms/Hz 全 ASCII,不占中文字库):值列只有数字会让"60000"既是 + * ms 又是 A,读者只能靠猜;行表带 unit 字段是本次给组件加的(NULL = 不挂) + */ + +/* 参数视图(8 行 = 1 页) */ +static const ui_w_setlist_row_t s_rows_param[8] = { + { "PT一次", 27u, UI_W_SETLIST_KIND_NUM, 0u, 100, 500000, NULL, "V" }, /* PS_PT_PRI */ + { "PT二次", 28u, UI_W_SETLIST_KIND_NUM, 0u, 1, 200, NULL, "V" }, /* PS_PT_SEC */ + { "CT一次", 29u, UI_W_SETLIST_KIND_NUM, 0u, 1, 10000, NULL, "A" }, /* PS_CT_PRI */ + { "CT二次", 30u, UI_W_SETLIST_KIND_NUM, 0u, 1, 10, NULL, "A" }, /* PS_CT_SEC */ + { "过量返回", 25u, UI_W_SETLIST_KIND_NUM, 2u, 50, 100, NULL, NULL }, /* PS_RET_OVER */ + { "欠量返回", 26u, UI_W_SETLIST_KIND_NUM, 2u, 100, 150, NULL, NULL }, /* PS_RET_UNDER */ + { "过频返回", 38u, UI_W_SETLIST_KIND_NUM, 2u, 90, 100, NULL, NULL }, /* PS_RET_OF */ + { "低频返回", 39u, UI_W_SETLIST_KIND_NUM, 2u, 100, 110, NULL, NULL }, /* PS_RET_UF */ +}; + +/* 定值视图(18 行 = 3 页) */ +static const ui_w_setlist_row_t s_rows_set[18] = { + { "Ⅰ段电流", 2u, UI_W_SETLIST_KIND_NUM, 2u, 0, 10000, NULL, "A" }, /* PS_OC1_CUR */ + { "Ⅰ段延时", 3u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_OC1_DLY */ + { "Ⅱ段电流", 6u, UI_W_SETLIST_KIND_NUM, 2u, 0, 10000, NULL, "A" }, /* PS_OC2_CUR */ + { "Ⅱ段延时", 7u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_OC2_DLY */ + { "Ⅲ段电流", 10u, UI_W_SETLIST_KIND_NUM, 2u, 0, 10000, NULL, "A" }, /* PS_OC3_CUR */ + { "Ⅲ段延时", 11u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_OC3_DLY */ + { "过负荷电流", 14u, UI_W_SETLIST_KIND_NUM, 2u, 0, 10000, NULL, "A" }, /* PS_OL_CUR */ + { "过负荷延时", 15u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_OL_DLY */ + { "过压定值", 17u, UI_W_SETLIST_KIND_NUM, 1u, 0, 2000, NULL, "V" }, /* PS_OV_VOL */ + { "过压延时", 18u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_OV_DLY */ + { "低压定值", 20u, UI_W_SETLIST_KIND_NUM, 1u, 0, 2000, NULL, "V" }, /* PS_UV_VOL */ + { "低压延时", 21u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_UV_DLY */ + { "零序定值", 23u, UI_W_SETLIST_KIND_NUM, 1u, 0, 2000, NULL, "V" }, /* PS_3U0_VOL */ + { "零序延时", 24u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_3U0_DLY */ + { "过频定值", 33u, UI_W_SETLIST_KIND_NUM, 2u, 5000, 5500, NULL, "Hz" }, /* PS_OF_HZ */ + { "过频延时", 34u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_OF_DLY */ + { "低频定值", 36u, UI_W_SETLIST_KIND_NUM, 2u, 4500, 5000, NULL, "Hz" }, /* PS_UF_HZ */ + { "低频延时", 37u, UI_W_SETLIST_KIND_NUM, 0u, 0, 60000, NULL, "ms" }, /* PS_UF_DLY */ +}; + +/* 控制字视图(4 行 = 1 页) */ +static const ui_w_setlist_row_t s_rows_word[4] = { + { "Ⅰ段跳闸", 1u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_trip, NULL }, /* PS_OC1_TRIP */ + { "Ⅱ段跳闸", 5u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_trip, NULL }, /* PS_OC2_TRIP */ + { "Ⅲ段跳闸", 9u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_trip, NULL }, /* PS_OC3_TRIP */ + { "过负荷跳闸", 13u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_trip, NULL }, /* PS_OL_TRIP */ +}; + +/* 软压板视图(10 行 = 2 页) */ +static const ui_w_setlist_row_t s_rows_plate[10] = { + { "Ⅰ段投入", 0u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_OC1_EN */ + { "Ⅱ段投入", 4u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_OC2_EN */ + { "Ⅲ段投入", 8u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_OC3_EN */ + { "过负荷投入", 12u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_OL_EN */ + { "过压投入", 16u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_OV_EN */ + { "低压投入", 19u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_UV_EN */ + { "零序投入", 22u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_3U0_EN */ + { "过频投入", 32u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_OF_EN */ + { "低频投入", 35u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_UF_EN */ + { "保护总投入", 31u, UI_W_SETLIST_KIND_ENUM, 0u, 0, 0, s_opts_plate, NULL }, /* PS_GLOBAL_EN */ +}; + +/* + * 每个定值项的显示小数位(下标 = 枚举序;二态项为 0,不参与数值换算) + * 为什么要单独一张表而不是从行表里找:组件的 get(id) 回调只给 id 不给行号, + * 同一 id 只会出现在一张视图里(四张表恰好覆盖 0..39 各一次,由主机单测钉住), + * 所以这张表是无歧义的,且比遍历四张表便宜 + */ +static const uint8_t s_dec[UI_DATA_SET_N] = { + 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, + 2, 0, 0, 0, 2, 0, 0, 1, 0, 0, + 1, 0, 0, 1, 0, 2, 2, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 2, 0, 2, 2, +}; + +/* 无参数帧时值列的占位串(与全仓"读不到就 --"同一口径) */ +#define SV_NO_DATA "--" + +/* 参数帧缓存(四个查看页共用一份:同屏只开一页,无并发读者) */ +static ui_data_par_t s_par; +static uint32_t s_par_seq = 0xFFFFFFFFu; /* 与任何合法发布序号都不同 */ +static uint8_t s_par_ok; + +/* 当前视图的行表(bad 回调只收到行号,需要它把行号换回 id 再查位图) */ +static const ui_w_setlist_row_t *s_cur_rows; +static uint16_t s_cur_n; /* 当前视图行数(bad 回调的边界) */ + +/* 行表长度(编译期算,别手写数字——手写数字与表长不一致是最难查的错) */ +#define SV_ROW_N(tbl) ((uint16_t)(sizeof(tbl) / sizeof((tbl)[0]))) + +/* 函数定义区 */ + +/* + * 函数功能:Q10 原值 → 指定小数位的定点显示值(四舍五入) + * 入口参数:a_q10 - Q10 原值(工程量 × 1024) int32_t + * a_dec - 小数位 0..3 uint8_t + * 返回值:工程量 × 10^a_dec int32_t + * 限定条件:|a_q10| 在 int32 范围内且换算后仍在 int32 内(行表生成时已按 + * g_prot_set_meta 的最大量程核过:最大项 PT 一次额定 500000 V,d=0) + * 函数说明:不用浮点也不用 64 位除法——MicroLIB 的 soft-float 与 __aeabi_ldiv + * 一进镜像就是几百字节,而模拟器(宿主 CRT)永远照不出这笔代价(本仓 + * 反复记录的同一条口径)。做法是拆整数段与小数段: + * v = ip×1024 + fp(fp∈[0,1023],负数用算术右移取 floor,fp 仍非负), + * 小数段按 10^d 缩放后四舍五入,满 10^d 就进位回整数段。 + * 误差上界半个最低显示位——与 prot_set 自己的 Q10 精度同源, + * 屏上显示 10.00A 而实际 10.0004A 不是本页造成的,是存储精度 + */ +static int32_t sv_q10_to_scaled(int32_t a_q10, uint8_t a_dec) { + static const int32_t p10[4] = { 1, 10, 100, 1000 }; + int32_t ip; + uint32_t fp; + int32_t r; + + ip = a_q10 >> 10; + fp = (uint32_t)a_q10 & 0x3FFu; + r = ip * p10[a_dec]; + if (a_dec != 0u) { + uint32_t fr = (fp * (uint32_t)p10[a_dec] + 512u) >> 10; /* 作用域收在这里 */ + if (fr >= (uint32_t)p10[a_dec]) { /* 小数段进位(如 0.9996 → 1.000) */ + r += p10[a_dec]; + fr -= (uint32_t)p10[a_dec]; + } + r += (int32_t)fr; + } + return r; +} + +/* + * 函数功能:读某定值项的显示值(组件 get 回调) + * 入口参数:a_id - 行表登记的 id(= 枚举序) uint8_t + * 返回值:工程量 × 10^decimals int32_t + * 限定条件:ui_w_setview_sync() 已先跑过且 ui_w_setview_ready() 为真 + * (页面在值列打 "--" 之前不会走到这里) + * 函数说明:id 越界(>39)返回 0 属防御性死支——行表是本文件内 const,越界 id + * 只可能来自代码改动错误,由主机单测在构建期前拦,不靠运行期兜 + */ +static int32_t sv_get(uint8_t a_id) { + if (a_id >= (uint8_t)UI_DATA_SET_N) { + return 0; + } + return sv_q10_to_scaled(s_par.q10[a_id], s_dec[a_id]); +} + +/* + * 函数功能:只读视图的写回调占位(组件契约要求非 NULL) + * 入口参数:a_id/a_val - 未使用 + * 返回值:无 + * 限定条件:查看视图不可编辑,组件不会调到这里 + * 函数说明:刻意空实现——查看页**没有任何**该写进 RAM 工作副本的语义; + * 真要在查看页改值,得先把整定批次的 commit/state 一起接上 + */ +static void sv_set_unused(uint8_t a_id, int32_t a_val) { + (void)a_id; + (void)a_val; +} + +/* + * 函数功能:某数据行是否越界(组件 bad 回调 → 反显高亮) + * 入口参数:a_row - 数据行下标(不含保存行) uint16_t + * 返回值:true = 反显;false = 正常 bool + * 限定条件:uiTask 绘制链内;s_cur_rows 已由 attach 设定 + * 函数说明:位图来自 prot_set_bad_mask()(上一次整表装载/提交后仍在量程外的项), + * 经 par 帧 bad_mask + bad_valid 下发。bad_valid=0 表示位图无意义 + * (App 侧只在位图非 0 时置 1),此时一律不反显——把"没数据"画成 + * "这行有问题"是假告警,比不画更糟 + */ +static bool sv_bad(uint16_t a_row) { + const ui_w_setlist_row_t *r; + uint64_t bit; + + if ((s_cur_rows == NULL) || (s_par_ok == 0u) || (s_par.bad_valid == 0u)) { + return false; + } + if (a_row >= s_cur_n) { + return false; /* 行号越界(组件不会给,防御:越界不反显也不越读) */ + } + r = &s_cur_rows[a_row]; + if (r->id >= (uint8_t)UI_DATA_SET_N) { + return false; + } + bit = (uint64_t)1u << r->id; + return (s_par.bad_mask & bit) != 0u; +} + +/* + * 函数功能:按发布序号取参数帧快照(接各查看页 refresh 前调用) + * 入口参数:无 + * 返回值:无 + * 限定条件:uiTask 上下文(页面绘制链内);只读快照层,不写任何东西 + * 函数说明:序号没变就一帧都不重拷(一帧约 184 B;四个查看页共用这份缓存——同屏 + * 只可能开着一页,彼此之间不存在并发读者)。读取失败把序号打回不可能的值, + * 下一轮一定重试,不会停在半新不旧的帧上 + */ +void ui_w_setview_sync(void) { + uint32_t seq = ui_data_par_seq(); + + if ((seq == s_par_seq) && (s_par_ok != 0u)) { + return; /* 序号未变:一帧都不重拷 */ + } + /* ui_data_par() 的通道是 0=成功 / -1=尚无首帧 / -2=参数无效(快照层的既有约定, + * 不是布尔成败)——把它当 bool 用会反义:模拟器与真机上都会永远"读不到", + * 于是整页值列恒 "--",而页面看起来完全正常(本函数第一版就踩了这一条, + * 是看像素图才发现满屏横杠,静态检查看不出来) */ + if (ui_data_par(&s_par) != 0) { + s_par_ok = 0u; + s_par_seq = 0xFFFFFFFFu; /* 失败态不认序号,下一轮一定重试 */ + (void)memset(&s_par, 0, sizeof(s_par)); + return; + } + s_par_seq = seq; + s_par_ok = (s_par.valid != 0u) ? 1u : 0u; +} + +/* + * 函数功能:逐行取数(页面 refresh 的直直通:先同步参数帧,再走组件) + * 入口参数:a_inst - 实例 ui_w_setlist_t *;a_row - 行下标 uint16_t + * a_name/a_value - 框架行缓冲(UI_BUF_STRLEN) char * + * 返回值:无 + * 限定条件:框架绘制链内(uiTask) + * 函数说明:**参数帧未就位时只把值列打成 "--",名字照打**——空值会被读成"这项是 0", + * 整页空白会让人以为菜单坏了;打出"名字 + --"才说得出真正的事实 + * ("这张表在等参数帧")。帧就位后走组件:数值行按小数位 + 单位, + * 二态行打串表词 + */ +void ui_w_setview_refresh(ui_w_setlist_t *a_inst, uint16_t a_row, + char *a_name, char *a_value) { + ui_w_setview_sync(); + if (ui_w_setview_ready() != 0u) { + ui_w_setlist_refresh(a_inst, a_row, a_name, a_value); + return; + } + a_name[0] = '\0'; + a_value[0] = '\0'; + if ((a_inst != NULL) && (a_inst->rows != NULL) && (a_row < a_inst->count)) { + (void)lv_snprintf(a_name, UI_BUF_STRLEN, "%s", a_inst->rows[a_row].name); + } + (void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", SV_NO_DATA); +} + +/* + * 函数功能:参数帧是否已就位 + * 入口参数:无 + * 返回值:1 = 有有效帧可显示;0 = 尚无首帧或读取失败 uint8_t + * 限定条件:uiTask 上下文;纯查询,无副作用 + * 函数说明:返回 uint8_t 而不是 bool 是**本层约定**(bsp-err 的 err-type:BSP 公开 API + * 不得返回 bool——bool 把"为什么失败"整条丢掉,而这里虽然确实不需要原因, + * 也不能给整层开一个按名字豁免的例外;先例见 ui_w_setlist_key 的键分发标志)。 + * 调用方拿它决定值列打换算值还是打 "--",没有错误要上报 + */ +uint8_t ui_w_setview_ready(void) { + return s_par_ok; +} + +/* + * 函数功能:取某视图的行表与行数(结构自测用;页面不需要,attach 时才自己取) + * 入口参数:a_view - 视图编号 ui_w_setview_t;a_n - 行数输出(不可为 NULL) uint16_t * + * 返回值:行表首地址;视图号非法返回 NULL const ui_w_setlist_row_t * + * 限定条件:只读;行表是 const Flash 表 + * 函数说明:存在的理由是让主机单测能把四张表当成数据来核(id 是否恰好覆盖 0..39 + * 各一次、二态行有没有串表、小数位与 dec 查表是否一致)。这些约束在 + * 编译期看不出来,页面上也只有当前可见的 8 行会被像素断言看到—— + * 第 2、3 页的行错了对齐,屏幕上要翻两页才看得见,而人不会去翻 + */ +const ui_w_setlist_row_t *ui_w_setview_table(ui_w_setview_t a_view, uint16_t *a_n) { + if (a_n == NULL) { + return NULL; + } + switch (a_view) { + case UI_W_SETVIEW_PARAM: + *a_n = SV_ROW_N(s_rows_param); + return s_rows_param; + case UI_W_SETVIEW_SET: + *a_n = SV_ROW_N(s_rows_set); + return s_rows_set; + case UI_W_SETVIEW_WORD: + *a_n = SV_ROW_N(s_rows_word); + return s_rows_word; + case UI_W_SETVIEW_PLATE: + *a_n = SV_ROW_N(s_rows_plate); + return s_rows_plate; + default: + *a_n = 0u; + return NULL; + } +} + +/* + * 函数功能:取 id → 显示小数位的查表(结构自测用) + * 入口参数:a_n - 表长输出(= UI_DATA_SET_N) uint16_t * + * 返回值:表首地址 const uint8_t * + * 限定条件:只读 + */ +const uint8_t *ui_w_setview_dec_table(uint16_t *a_n) { + if (a_n != NULL) { + *a_n = (uint16_t)UI_DATA_SET_N; + } + return s_dec; +} + +/* + * 函数功能:把某个查看视图绑到定值表实例(行表 + 取数 + 反显一次配齐) + * 入口参数:a_inst - 页面持有的实例(可为 NULL,NULL 直接返回) ui_w_setlist_t * + * a_view - 视图编号 ui_w_setview_t + * 返回值:无 + * 限定条件:页面 init_cb 内调用(uiTask);每次开页都重新绑,不信任上一次的残留 + * 函数说明:先 memset 再填 —— 组件实例里的编辑态跨页留着就是脏状态;视图号非法 + * 给空表(页面退化成 0 行空页,比按错表导航安全) + */ +void ui_w_setview_attach(ui_w_setlist_t *a_inst, ui_w_setview_t a_view) { + if (a_inst == NULL) { + return; + } + (void)memset(a_inst, 0, sizeof(*a_inst)); + switch (a_view) { + case UI_W_SETVIEW_PARAM: + a_inst->rows = s_rows_param; + a_inst->count = SV_ROW_N(s_rows_param); + break; + case UI_W_SETVIEW_SET: + a_inst->rows = s_rows_set; + a_inst->count = SV_ROW_N(s_rows_set); + break; + case UI_W_SETVIEW_WORD: + a_inst->rows = s_rows_word; + a_inst->count = SV_ROW_N(s_rows_word); + break; + case UI_W_SETVIEW_PLATE: + a_inst->rows = s_rows_plate; + a_inst->count = SV_ROW_N(s_rows_plate); + break; + default: + a_inst->rows = NULL; + a_inst->count = 0u; + break; + } + s_cur_rows = a_inst->rows; + s_cur_n = a_inst->count; + a_inst->get = sv_get; + a_inst->set = sv_set_unused; + a_inst->commit = NULL; /* NULL = 查看视图:无保存行、禁编辑(§3.2.6) */ + a_inst->state = NULL; + a_inst->bad = sv_bad; + ui_w_setlist_reset(a_inst); +} diff --git a/Drivers/BSP/UI/ui_w_setview.h b/Drivers/BSP/UI/ui_w_setview.h new file mode 100644 index 00000000..cf88adbc --- /dev/null +++ b/Drivers/BSP/UI/ui_w_setview.h @@ -0,0 +1,110 @@ +/* + * 模块名称:UI 定值查看视图模型(ui_w_setview) + * 模块功能:参数 / 定值 / 控制字 / 软压板**四张视图表**与它们的取数、越界反显—— + * 把"40 项定值怎么分组上屏、Q10 怎么换算成带小数位的显示值"收在一处, + * 供四个查看页薄跳板(ui_page_view_*)共用;整定页(下一批)复用同一批表, + * 只是再绑 set/commit/state 三个回调 + * 分层:本模块只读 Drivers/BSP/UI/ui_data.h 的参数快照,**不 include 任何 App 头** + * (行表里的 id 是**裸数字** + 注释标注 PS_* 名,因为 prot_set_id_t 属 App 层) + * ——id 与 App/algo/prot_set.h 枚举序的一致性由 test/host/ui_data 的主机单测 + * 在两侧头文件同时可见的地方钉住(BSP 侧看不到 App 头,物理上无法自证) + * 值语义:q10[i] = 工程量 × 1024(App/app/ui_data_feed.c 只搬原值不换算,换算属表现层, + * docs/UI设计/UI_数据供给设计说明.md §8),本模块按行表 decimals 换算成 + * "工程量 × 10^decimals" 交给 ui_w_setlist(其值通道契约) + * 适用平台:STM32F407ZGTx / PC 模拟器(共用源码) + * 作者:王建锋 + * 创建日期:2026-10-25 + * 修改记录: + * v1.0 2026-10-25 王建锋 创建(道E task-15 批 3;规格 = + * docs/UI设计/页面扩充详细方案.md §3.2.1~§3.2.3/§3.2.6) + */ +#ifndef UI_W_SETVIEW_H +#define UI_W_SETVIEW_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "ui_w_setlist.h" + +/* 四个视图编号 */ +typedef enum { + UI_W_SETVIEW_PARAM = 0, /* 参数视图 8 行(PT/CT 额定 + 4 个返回系数) */ + UI_W_SETVIEW_SET, /* 定值视图 18 行(电流/电压/频率/延时) */ + UI_W_SETVIEW_WORD, /* 控制字视图 4 行(*_TRIP:告警/跳闸) */ + UI_W_SETVIEW_PLATE, /* 软压板视图 10 行(*_EN + 保护总投:投入/退出) */ + UI_W_SETVIEW_VIEWS /* 哨兵 = 视图个数(不是有效视图号) */ +} ui_w_setview_t; + +/* + * 函数功能:绑定一个查看视图到定值表实例(rows/count/get/bad 一次配齐) + * 入口参数:a_inst - 页面持有的实例(static,一页一份) ui_w_setlist_t * + * a_view - 视图编号 ui_w_setview_t + * 返回值:无 + * 限定条件:页面 init_cb 内调用(uiTask 上下文) + * 函数说明:查看视图与整定视图的**唯一**差别就是这里绑不绑 set/commit/state: + * commit=NULL → 组件不画保存行、ENTER 无效(§3.2.6 查看列口径); + * set 契约写"必填",故绑一个空实现而不是 NULL——组件在只读态确实不会调它, + * 但把"当前不会调"当成"可以是 NULL"等于把安全性押在下一次重构上 + */ +void ui_w_setview_attach(ui_w_setlist_t *a_inst, ui_w_setview_t a_view); + +/* + * 函数功能:按发布序号取参数帧快照(页面 refresh 前调用) + * 入口参数:无 + * 返回值:无 + * 限定条件:uiTask 上下文(页面绘制链内) + * 函数说明:序号没变就不重拷(一帧约 184 B;四个查看页共用这一份缓存——同屏只可能 + * 开着一页,缓存互相之间不存在并发读者) + */ +void ui_w_setview_sync(void); + +/* + * 函数功能:参数帧是否已就位 + * 入口参数:无 + * 返回值:1 = 有有效帧可显示;0 = 尚无首帧或读取失败 uint8_t(取 uint8_t 是 BSP 公开 + * API 的 err-type 约定,不是错误通道;定义处有完整理由) + * 限定条件:uiTask 上下文;纯查询,无副作用 + * 函数说明:页面据此决定值列打 "--" 还是打换算值——**无数据不许打 0**: + * "0.00A" 会被读成"这台装置的定值就是 0",与"还不知道"是两件事 + */ +uint8_t ui_w_setview_ready(void); + +/* + * 函数功能:逐行取数(接框架 refresh;内含 ui_w_setview_sync() 与无帧占位) + * 入口参数:a_inst - 已 attach 的实例 ui_w_setlist_t * + * a_row - 全局行下标 uint16_t + * a_name - 名输出缓冲(UI_BUF_STRLEN) char * + * a_value - 值输出缓冲 char * + * 返回值:无 + * 限定条件:框架绘制链内调用(uiTask) + * 函数说明:页面**不要**直接调 ui_w_setlist_refresh()——那样会绕过"无帧打 --" + * 这一步,参数帧还没到就把 0 当成定值显示出去 + */ +void ui_w_setview_refresh(ui_w_setlist_t *a_inst, uint16_t a_row, + char *a_name, char *a_value); + +/* + * 函数功能:取某视图的行表与行数(供主机单测做结构自测;页面正常不需要调用) + * 入口参数:a_view - 视图编号 ui_w_setview_t;a_n - 行数输出(不可为 NULL) uint16_t * + * 返回值:行表首地址;视图号非法或 a_n 为空返回 NULL const ui_w_setlist_row_t * + * 限定条件:只读 + */ +const ui_w_setlist_row_t *ui_w_setview_table(ui_w_setview_t a_view, uint16_t *a_n); + +/* + * 函数功能:取 id → 显示小数位查表(供主机单测核对行表与查表一致) + * 入口参数:a_n - 表长输出(= UI_DATA_SET_N) uint16_t * + * 返回值:表首地址 const uint8_t * + * 限定条件:只读 + */ +const uint8_t *ui_w_setview_dec_table(uint16_t *a_n); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_W_SETVIEW_H */ diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 241055c9..f4b8f960 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -3566,11 +3566,36 @@ 1 ..\Drivers\BSP\UI\ui_w_soe.c + + ui_w_setview.c + 1 + ..\Drivers\BSP\UI\ui_w_setview.c + ui_page_soe.c 1 ..\Drivers\BSP\UI\ui_page_soe.c + + ui_page_view_param.c + 1 + ..\Drivers\BSP\UI\ui_page_view_param.c + + + ui_page_view_plate.c + 1 + ..\Drivers\BSP\UI\ui_page_view_plate.c + + + ui_page_view_set.c + 1 + ..\Drivers\BSP\UI\ui_page_view_set.c + + + ui_page_view_word.c + 1 + ..\Drivers\BSP\UI\ui_page_view_word.c + diff --git a/docs/UI设计/UI_数据供给设计说明.md b/docs/UI设计/UI_数据供给设计说明.md index 5cadb68f..1ccf06e8 100644 --- a/docs/UI设计/UI_数据供给设计说明.md +++ b/docs/UI设计/UI_数据供给设计说明.md @@ -497,6 +497,15 @@ typedef struct { > 三条纪律在页面侧被验证:① 页面只读快照、零 App include(分层基线不变);② 帧缓存按 `seq` > 门控整帧拷一次,逐行只读标量;③ 品质位判空一律 `--`,"有数但不可信"与"没数"同形。参数帧 > `par` 的首批消费者是定值/参数查看页(本批未接,`bad_mask`/`locked` 的屏上表达随那 4 页落)。 +> +> 【2026-10-25 落地进度·三】`par` 的首批消费者已落(同分支批 3):**参数 / 定值 / 控制字 / 软压板**四个查看页 +> (`ui_w_setview` 四张行表 + `ui_w_setlist` 组件,查看态 `commit=NULL` 无保存行)。Q10 → 显示值的换算落在**表现层** +> (本契约 §8 的分工:写侧只搬原值),换算式在 `ui_w_setview.c` 里做整数拆分而非浮点/64 位除(MicroLIB +> 软浮点与 `__aeabi_ldiv` 一进镜像几百字节,模拟器永远照不出这笔代价)。`bad_mask` 的屏上表达 = +> 该行反显高亮(且只在 `bad_valid=1` 时参与,把没数据画成有毛病是假告警);`locked` 的屏上表达是 +> **禁编辑**,查看页本来就无编辑通路,它随整定 4 页一起显形。四个返回系数按 2 位小数显示的理由见 +> `docs/UI设计/页面扩充详细方案.md` §3.2.2 落地注;行表与 `prot_set` 枚举序/上下限的一致性由 +> `tools/setview_check/check_setview_limits.py` 核验(BSP 侧看不到 App 头,物理上无法自证,必须有人从外面钉)。 --- diff --git a/docs/UI设计/页面扩充详细方案.md b/docs/UI设计/页面扩充详细方案.md index e3e66aad..3ec34fc8 100644 --- a/docs/UI设计/页面扩充详细方案.md +++ b/docs/UI设计/页面扩充详细方案.md @@ -212,6 +212,20 @@ V0.1 **无直流采样通道**(`ADC_SEM` 只有 8 路电气 + 温度),点 | 7 | `过频返回` | `PS_RET_OF` | — | 3 | | 8 | `低频返回` | `PS_RET_UF` | — | 3 | +> 【2026-10-25 落地注·四张视图表(道E task-15 批 3)】四个查看页已接(`ui_page_view_param` / +> `_set` / `_word` / `_plate`,共用 `Drivers/BSP/UI/ui_w_setview.c` 的四张行表 + `ui_w_setlist` +> 组件)。三条落地时的偏离与理由都写进了代码注释与提交正文,不留在这儿等人猜: +> 1. **返回系数按 2 位小数显示**(本表写 3 位)。这不是排版取舍,是量值真实性问题:Q10 存不 +> 下 0.95(0.95×1024 截成 972 → 真存 0.94921875)。按 3 位显示就多打一位根本不存在的数字, +> 整定单上写 0.95 的人看到屏上 0.949 会怀疑装置、甚至反复重输(重输还是 0.949)。2 位才是 +> 这份存储在自身可分辨精度上的正确读数。本表「小数位」列按文档口径保留原样,偏离登记在 +> `tools/setview_check/check_setview_limits.py` 的文档串里(该脚本连 meta 上下限一起核)。 +> 2. **单位挂在值串尾**(`10.00A` / `500ms`),不塞进显示名:值列只有数字会让 `60000` 既是 ms +> 又是 A;A/V/ms/Hz 全 ASCII,不占中文字库、不触发 font-charset。为此给 `ui_w_setlist_row_t` +> 加了 `unit` 字段(NULL = 不挂;只挂在非编辑态——编辑中是逐位输入串,挂单位会算错位光标)。 +> 3. **上下限不自己拟定**:行表 min/max = `g_prot_set_meta` 的 Q10 上下限按该行小数位换算的结果, +> 事后由上述脚本核验(改了 meta 不改行表就会红)。 + #### 3.2.3 定值视图(18 项) | 行 | 显示名 | 枚举 | 单位 | 小数位 | diff --git a/simulator/CMakeLists.txt b/simulator/CMakeLists.txt index 23e88c3c..2ebf2323 100644 --- a/simulator/CMakeLists.txt +++ b/simulator/CMakeLists.txt @@ -102,8 +102,13 @@ set(UI_SOURCES ${UI_DIR}/ui_w_setlist.c ${UI_DIR}/ui_page_action.c ${UI_DIR}/ui_data.c # 数据快照存侧(道E task-14;写侧 App/app/ui_data_feed.c 不进模拟器,见 scripts/check_registration.py) - ${UI_DIR}/ui_w_soe.c # SOE 记录组件(道E task-14;reader 注入点待绑定,未绑定按空态渲染) + ${UI_DIR}/ui_w_soe.c # SOE 记录组件(道E task-14;reader 已在批 2 两端绑定) + ${UI_DIR}/ui_w_setview.c # 定值查看视图模型(道E task-15 批 3:四张视图表 + Q10 换算 + 越界反显) ${UI_DIR}/ui_page_soe.c + ${UI_DIR}/ui_page_view_param.c + ${UI_DIR}/ui_page_view_plate.c + ${UI_DIR}/ui_page_view_set.c + ${UI_DIR}/ui_page_view_word.c ) # 运行/自检记录引擎(双端共享核心 + 平台存储后端;MCU 端编 sys_log_mcu.c,见 Keil 工程) diff --git a/simulator/tests/check_content_pages.py b/simulator/tests/check_content_pages.py index 30c63741..02177b8f 100644 --- a/simulator/tests/check_content_pages.py +++ b/simulator/tests/check_content_pages.py @@ -44,6 +44,12 @@ PAGES = [ # SOE 记录(node 51,记录查询下第 1 项):6 下进记录查询 → e → 首项 SOE记录 → e; # 数据 = simulator/main.c 的固定 3 条假记录(含 type 7 未标定闭锁这条非元件支路) ("soe", "dddddde e"), + # 参数定值 → 查看 的四个只读页(批 3):dd 进"参数定值"→ e 进(选中"整定")→ d 到"查看" + # → e 进(选中"参数")→ e 开页;定值/控制字/软压板 依次多按 d 选到该行再 e + ("vparam", "ddedee"), + ("vset", "ddedede"), + ("vword", "ddededde"), + ("vplate", "ddededdde"), ("pwd", "ddeee"), # 密码页初始态:3 框 000、光标首框 ("pwe", "ddeee uru"), # 密码页调值+移光标:110、光标第 2 框 ("pwk", "ddeee uruuruuue"), # 密码页输 123:u / r / u,u / r / u,u,u / e → 关页进空白终点框(菜单屏) @@ -106,6 +112,84 @@ TEXTS = { ("r5名", "未标定闭锁", (94, 108), False), ("页脚", "第 1 页,共 1 页", (140, 160), False), ], + # ── 批 3:参数定值 → 查看 四页(数据 = prot_set 出厂默认,值串带单位)── + # 名与值两列都断:值列断的是换算后的小数位与单位——光看像素,没人会去核 0.95 是不是被 + # Q10 存成了 0.94921875。控制字页还多钉一层:Ⅰ/Ⅱ 段默认是「跳闸」、Ⅲ 与过负荷段默认是 + # 「告警」,这种两两一组恰好把行表与枚举序对齐钉住(行表整体错位就不可能出现该组合) + "vparam": [ + ("标题", "参数", (0, 24), False), + ("r0名", "PT一次", (24, 38), True), + ("r0值", "10000V", (24, 38), True), + ("r1名", "PT二次", (38, 52), False), + ("r1值", "100V", (38, 52), False), + # r2 值我先前肉眼读成 "1000A",字形匹配器证明值列只有 4 个字符位(命中 x=101/107/113/119): + # CT 一次额定是 100 A —— PT 10000/100 V 加 CT 100/5 A 正是 10 kV 馈线终端的变比自洽。 + # 断言写"匹配器能证明的",不写"我看图觉得的"(这类差异肉眼发现不了,只有匹配器会响) + ("r2名", "CT一次", (52, 66), False), ("r2值", "100A", (52, 66), False), + ("r3名", "CT二次", (66, 80), False), + ("r3值", "5A", (66, 80), False), + ("r4名", "过量返回", (80, 94), False), + ("r4值", "0.95", (80, 94), False), + ("r5名", "欠量返回", (94, 108), False), + ("r5值", "1.05", (94, 108), False), + ("r6名", "过频返回", (108, 122), False), + ("r6值", "0.99", (108, 122), False), + ("r7名", "低频返回", (122, 136), False), + ("r7值", "1.01", (122, 136), False), + ("页脚", "第 1 页,共 1 页", (140, 160), False), + ], + "vset": [ + ("标题", "定值", (0, 24), False), + ("r0名", "Ⅰ段电流", (24, 38), True), + ("r0值", "10.00A", (24, 38), True), + ("r1名", "Ⅰ段延时", (38, 52), False), + ("r1值", "0ms", (38, 52), False), + ("r2名", "Ⅱ段电流", (52, 66), False), + ("r2值", "5.00A", (52, 66), False), + ("r3名", "Ⅱ段延时", (66, 80), False), + ("r3值", "500ms", (66, 80), False), + ("r4名", "Ⅲ段电流", (80, 94), False), + ("r4值", "2.00A", (80, 94), False), + ("r5名", "Ⅲ段延时", (94, 108), False), + ("r5值", "3000ms", (94, 108), False), + ("r6名", "过负荷电流", (108, 122), False), + ("r6值", "1.00A", (108, 122), False), + ("r7名", "过负荷延时", (122, 136), False), + ("r7值", "10000ms", (122, 136), False), + ("页脚", "第 1 页,共 3 页", (140, 160), False), # 18 行 = 3 页 + ], + "vword": [ + ("标题", "控制字", (0, 24), False), + ("r0名", "Ⅰ段跳闸", (24, 38), True), + ("r0值", "跳闸", (24, 38), True), + ("r1名", "Ⅱ段跳闸", (38, 52), False), + ("r1值", "跳闸", (38, 52), False), + ("r2名", "Ⅲ段跳闸", (52, 66), False), + ("r2值", "告警", (52, 66), False), + ("r3名", "过负荷跳闸", (66, 80), False), + ("r3值", "告警", (66, 80), False), + ("页脚", "第 1 页,共 1 页", (140, 160), False), + ], + "vplate": [ + ("标题", "软压板", (0, 24), False), + ("r0名", "Ⅰ段投入", (24, 38), True), + ("r0值", "投入", (24, 38), True), + ("r1名", "Ⅱ段投入", (38, 52), False), + ("r1值", "投入", (38, 52), False), + ("r2名", "Ⅲ段投入", (52, 66), False), + ("r2值", "投入", (52, 66), False), + ("r3名", "过负荷投入", (66, 80), False), + ("r3值", "投入", (66, 80), False), + ("r4名", "过压投入", (80, 94), False), + ("r4值", "投入", (80, 94), False), + ("r5名", "低压投入", (94, 108), False), + ("r5值", "投入", (94, 108), False), + ("r6名", "零序投入", (108, 122), False), + ("r6值", "投入", (108, 122), False), + ("r7名", "过频投入", (122, 136), False), + ("r7值", "投入", (122, 136), False), + ("页脚", "第 1 页,共 2 页", (140, 160), False), # 10 行 = 2 页,次页还有低频投入与保护总投入 + ], # 直流量:V0.1 板上无直流采样通道 -> 温度取码值并显式带"码"前缀,通道行如实"未配置" "dc": [ ("标题", "直流量", (0, 24), False), diff --git a/tools/setview_check/check_setview_limits.py b/tools/setview_check/check_setview_limits.py new file mode 100644 index 00000000..cecfca29 --- /dev/null +++ b/tools/setview_check/check_setview_limits.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +""" +模块名称:定值查看行表一致性检查 +模块功能:把 Drivers/BSP/UI/ui_w_setview.c 的四张视图表当数据核,比对真源 + App/algo/prot_set.h 的枚举序与 App/algo/prot_set.c 的 g_prot_set_meta: + ① id 必须恰好覆盖 0..39 各一次(漏 / 重 / 错位都会在这里炸;页面上只有当前 + 可见的 8 行会被像素断言看到,错位要翻到第 2、3 页才可能被人发现) + ② 数值行 min/max 必须等于 meta 上下限按该行小数位换算后的值——行表里的上下限 + 就是从 meta 换算来的,本脚本是那条换算的可重跑证明(改 meta 不改行表就红) + ③ 二态行必须带串表、数值行必须不带;单位串只允许 ASCII(不进中文字库) + ④ 小数位与 id 查表一致(组件 get 只收到 id,两处不一致就会显示错位数字) +已知偏离(写死在下面,不做静默豁免):四个返回系数按 2 位小数显示,文档 §3.2.2 写 3 位。 +理由是 Q10 存不下 0.95:0.95*1024 截成 972,真存 0.94921875,按 3 位显示会打出一位根本 +不存在的数字——整定单写 0.95 的人看到 0.949 会怀疑装置,甚至反复重输(重输还是 0.949)。 +用法:在仓库根执行 + .venv/Scripts/python.exe tools/setview_check/check_setview_limits.py +退出码:0 = 一致;1 = 不一致(打印具体行与期望值);2 = 解析锚点丢失(结构改了,脚本要跟着改) +创建日期:2026-10-25(道E task-15 批 3) +""" +import re +import sys + +# Windows 控制台默认按 CP936 写 stdout:本脚本要打印中文,先把输出锁到 UTF-8——否则同一份 +# 脚本在别人的机器上会崩在半路(崩在半路 = 一条都没检查出来,比 FAIL 更难发现) +try: + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + sys.stderr.reconfigure(encoding="utf-8", errors="replace") +except Exception: + pass + +ENUM_H = "App/algo/prot_set.h" +META_C = "App/algo/prot_set.c" +TABLES = "Drivers/BSP/UI/ui_w_setview.c" +RATIO_IDS = (25, 26, 38, 39) # 四个返回系数:本文档字符串里说明过的 2 位小数偏离 + + +def q10(tok): + m = re.match(r"PROT_SET_Q10\(([-0-9.]+)\)$", tok.strip()) + v = float(m.group(1)) if m else float(tok.strip()) + return int(round(v * 1024)) if m else int(v) + + +def scaled(v, d): + ip = v >> 10 + fp = v & 0x3FF + p10 = 10 ** d + fr = (fp * p10 + 512) >> 10 + if fr >= p10: + ip += 1 + fr -= p10 + return ip * p10 + fr + + +def load_meta(): + h = open(ENUM_H, encoding="utf-8").read() + body = h.split("typedef enum {")[1].split("PS_ID_COUNT")[0] + ids = [x.strip() for x in re.findall(r"^\s+(PS_[A-Z0-9_]+)[, ]", body, re.M)] + c = open(META_C, encoding="utf-8").read() + blk = c.split("g_prot_set_meta[PS_ID_COUNT] = {")[1].split("\n};")[0] + blk = re.sub(r"/\*.*?\*/", "", blk, flags=re.S) + ent = re.findall(r"\{([^{}]*)\}", blk, re.S) + if len(ent) != len(ids): + print("[FATAL] 枚举 %d 项 / meta %d 项不一致" % (len(ids), len(ent))) + return None + meta = {} + for name, e in zip(ids, ent): + p = [x.strip() for x in e.split(",")] + if len(p) != 5: + print("[FATAL] %s 的 meta 行解析成 %d 段" % (name, len(p))) + return None + meta[name] = (1 if p[1].startswith("1") else 0, q10(p[2]), q10(p[3]), q10(p[4])) + return (ids, meta) + + +def load_tables(): + t = open(TABLES, encoding="utf-8").read() + rows = [] + # 行表一行的形状:{ "名", NNu, UI_W_SETLIST_KIND_XXX, Du, min, max, opts, unit }, /* PS_名 */ + # opts/unit 两种写法都要吃下:NULL 或 "V" 这类带引号串;用 \S+ 而不是 \w+,否则带引号 + # 的单位匹配不上会让整行**静默跳过**(少检查一行比检查错更难发现) + pat = re.compile(r"^\s*\{\s*\"(.*?)\",\s*(\d+)u,\s*UI_W_SETLIST_KIND_(NUM|ENUM),\s*(\d+)u,\s*(-?\d+),\s*(-?\d+),\s*(\S+),\s*(\S+?)\s*\},\s*/\*\s*(PS_[A-Z0-9_]+)\s*\*/", re.M) + for m in pat.finditer(t): + rows.append((m.group(9), int(m.group(2)), m.group(3), int(m.group(4)), + int(m.group(5)), int(m.group(6)), m.group(7), m.group(8))) + dm = re.search(r"s_dec\[UI_DATA_SET_N\] = \{(.*?)\};", t, re.S) + if dm is None or not rows: + print("[FATAL] 行表/查表锚点解析失败(行表写法改了要同步改本脚本)") + return None + dec = [int(x) for x in re.findall(r"\d+", dm.group(1))] + return (rows, dec) + + +def main(): + lm = load_meta() + if lm is None: + return 2 + ids, meta = lm + lt = load_tables() + if lt is None: + return 2 + rows, dec = lt + bad = [] + seen = [] + if len(rows) != len(ids): + print("[FAIL] 行表解析到 %d 行,但枚举有 %d 项——正则与行表写法脱节了,宁可报错也不要少检" % (len(rows), len(ids))) + return 1 + for ps, rid, kind, d, mn, mx, opts, unit in rows: + if rid < 0 or rid >= len(ids): + bad.append("%s: id %d 越界" % (ps, rid)); continue + if ids[rid] != ps: + bad.append("%s: id=%d 实际指向 %s(枚举序对不上)" % (ps, rid, ids[rid])) + seen.append(rid) + is_ctl = meta[ps][0] + if kind == "ENUM": + if opts == "NULL": + bad.append("%s: 二态行没有串表" % ps) + if dec[rid] != 0: + bad.append("%s: 二态项在 dec 表里应为 0,实为 %d" % (ps, dec[rid])) + if not is_ctl: + bad.append("%s: meta 说它是模拟量项,行表却当二态行" % ps) + else: + if opts != "NULL": + bad.append("%s: 数值行不该带串表" % ps) + if dec[rid] != d: + bad.append("%s: 行表小数位 %d 与 dec 查表 %d 不一致" % (ps, d, dec[rid])) + emn = scaled(meta[ps][2], d) + emx = scaled(meta[ps][3], d) + if (mn, mx) != (emn, emx): + bad.append("%s: 上下限 (%d,%d) != meta 换算 (%d,%d)(d=%d)" % (ps, mn, mx, emn, emx, d)) + if is_ctl: + bad.append("%s: meta 说它是投退/枚举项,行表却当数值行" % ps) + if unit != "NULL": + u = "" + if unit.startswith('"'): + u = unit.strip('"') + else: + bad.append("%s: 单位字段写法不认识:%s" % (ps, unit)) + if u and any(ord(ch) > 127 for ch in u): + bad.append("%s: 单位含非 ASCII 字符 %r(会进中文字库,上屏还可能缺字)" % (ps, u)) + if sorted(seen) != list(range(len(ids))): + dup = [x for x in set(seen) if seen.count(x) > 1] + miss = sorted(set(range(len(ids))) - set(seen)) + bad.append("id 覆盖不完整:重复 %s 缺失 %s" % (dup, miss)) + ratios = [r for r in seen if r in RATIO_IDS] + if sorted(ratios) != sorted(RATIO_IDS): + bad.append("四个返回系数(%s)没有全在数值视图里,偏离白名单需要同步更新" % (RATIO_IDS,)) + for m in bad: + print("[FAIL] " + m) + print("行表 %d 行 / dec %d 项 / meta %d 项 -> %s" % (len(rows), len(dec), len(ids), "OK" if not bad else "FAIL")) + return 1 if bad else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ui_menu_tree.txt b/ui_menu_tree.txt index 444960d8..12d9b322 100644 --- a/ui_menu_tree.txt +++ b/ui_menu_tree.txt @@ -29,10 +29,10 @@ 控制字 #P 软压板 #P 查看 - 参数 - 定值 - 控制字 - 软压板 + 参数 #page=view_param + 定值 #page=view_set + 控制字 #page=view_word + 软压板 #page=view_plate 三遥设置 遥测死区 #P 遥测系数 #P