/* * 模块名称:UI 数据快照层(ui_data) * 模块功能:App 层 → UI 层唯一数据通道的"存侧"——只持有最新一帧实时量(rt)/ * 参数(par)/装置信息(info) 快照 + 发布序号,给页面只读访问; * 不取数、不格式化、不判断业务含义(职责边界 = * docs/UI设计/UI_数据供给设计说明.md §3.2) * 适用范围:Drivers/BSP/UI(表现层叶子:不 include 任何 App 头、不 include 任何 * 页面头、不碰 LVGL——分层方向由 scripts/check_layer_deps.py 守护, * BSP → App 反向 include 一律拦提交) * 并发模型:写侧 = App/app/ui_data_feed.c(唯一发布者);读侧 = ui_page_* 的绘制/ * 按键钩子。两侧同在 uiTask 上下文串行执行 → 无锁、无双缓冲、无 seqlock * (判据与"将来出现第二发布者"时的扩展路径见 * docs/UI设计/UI_数据供给设计说明.md §5.1) * 页面用法:行缓存按"发布序号变过才整帧拷"门控,勿逐行整帧拷贝 * (docs/UI设计/UI_数据供给设计说明.md §5.4): * static ui_data_rt_t s_rt; * static uint32_t s_seq; * if (ui_data_rt_seq() != s_seq) { * (void)ui_data_rt(&s_rt); * s_seq = ui_data_rt_seq(); * } * 作者:王建锋 * 创建日期:2026-10-25 * 修改记录: * v1.0 2026-10-25 王建锋 创建(道E task-14:契约 §4 快照层落地;相对契约 * 两通道新增第三条 info,划分理由见本头"通道划分"注 * 与契约 §4.4) */ #ifndef UI_DATA_H #define UI_DATA_H #ifdef __cplusplus extern "C" { #endif #include /* * 契约版本:结构体字段只允许尾部追加,追加即升此值并在写侧填默认值 * (禁止删除或重排字段——页面按名字取,重排是静默错位; * 口径 docs/UI设计/UI_数据供给设计说明.md §4.3) */ #define UI_DATA_VER 1u /* ── 通道数量 ─────────────────────────────────────────────────────────────── */ /* 遥测语义通道数:下标序 = adc_semantic_t(UA/UB/UC/UN/IA/IB/IC/IN/TEMP)。 * BSP 侧不得 include App 头,故"两数相等"的编译期守卫落在写侧 * App/app/ui_data_feed.c,语义逐项对齐的断言落在 test/host/ui_data/ * (docs/UI设计/UI_数据供给设计说明.md §9.4 一致性测试) */ #define UI_DATA_YC_N 9u /* 遥信点数:与 104 点表同序(bit i = 第 i 点),页面名表按点表逐行登记 */ #define UI_DATA_YX_N 33u /* 定值项数:与 prot_set 枚举同序(校验和覆盖序),四视图划分不改序 */ #define UI_DATA_SET_N 40u /* 相量实例槽上限:谐波(Ia 二次/五次)不能塞进"语义通道"下标空间——同一通道 * 的二次/五次谐波会与基波撞位,故实时帧另开一条按**相量实例序**索引的槽位, * 供交流量页显示 Ia2/Ia5;实际发布槽数由 ph_n 给出,超出上限的实例本帧不发布 * (写侧口径见 App/app/ui_data_feed.c 的 fill_rt 函数)。上限取 8 = 现用 5 实例 * (IA/UA/UN 基波 + IA 二次/五次)+ 遥测谐波轨留位,每槽 8 B */ #define UI_DATA_PH_MAX 8u /* ── 量纲口径 eng(诚实性字段:数值 val 的单位由它决定)───────────────────── */ /* * 真值语义(现场口径 = docs/任务规划/数据可信.md §2.6 的承诺边界 + §4.2 三态 * 可用性;契约文档原先写的"0 = 原始码值(未标定)"是标定落地前的旧口径, * 已按本表更正,勿再照抄旧表述): * ENG_PIN 引脚电压 V —— 现状:adc_cal 的 ROM 默认系数是「数据可信」卡 1 定死 * 的引脚侧 LSB(TPAFE 152.59 uV/码、STM32 805.66 uV/码),采样环 * 折算出来的就是 ADC 引脚电压,不是端子二次值 * ENG_SEC 二次值 —— 电气 8 路 adc_cal_flags() 的 VALID 全置位(拿到端子变比 * 并完成两点标定)后由写侧升档,页面自动按二次值显示,零改动 * ENG_PRI 一次值 —— 预留(再乘 PT/CT 变比),当前写侧不会产出 */ #define UI_DATA_ENG_PIN 0u #define UI_DATA_ENG_SEC 1u #define UI_DATA_ENG_PRI 2u /* ── 品质位(与 104 QDS 语义对齐;页面只消费自己关心的位)─────────────────── */ #define UI_DATA_Q_IV 0x01u /* 无效:该通道无源/不可用 → 页面显示 "--" */ #define UI_DATA_Q_NT 0x02u /* 未就绪:尚未攒满一周波/未收敛 */ #define UI_DATA_Q_OV 0x04u /* 越限:超过量程/硬件削顶(V0.1 STM32 侧单极性) */ #define UI_DATA_Q_SB 0x08u /* 取代值(虚拟注入/替身值,页面应带角标) */ #define UI_DATA_Q_BL 0x10u /* 闭锁:该量被业务闭锁(未标定闭锁/采样闭锁) */ /* 原始码值域:val 不是工程量而是转换器码值——TEMP 不参与线性标定 * (docs/任务规划/数据可信.md §4.2 的"TEMP 透传码值"决策),有此位即页面 * 不得给 val 挂 V/A 单位,也不得与工程量互比 */ #define UI_DATA_Q_RAW 0x20u /* ── 字符串容量(结构体内定长数组,跨层零指针;容量宏本地化)──────────────── */ #define UI_DATA_CLOCK_LEN 20u /* "YYYY-MM-DD HH:MM:SS" 19 + NUL */ #define UI_DATA_MODEL_LEN 8u /* 终端型号("F30") */ #define UI_DATA_VERSTR_LEN 16u /* 软件/硬件版本串 */ #define UI_DATA_DATE_LEN 12u /* 程序日期(编译期 __DATE__,留 NUL 余量) */ #define UI_DATA_SUM_LEN 8u /* 软件校验(无构建期 CRC 步骤时如实为 "--") */ #define UI_DATA_FUSER_LEN 16u /* FTP 用户名;与 Drivers/BSP/NETCFG/ftp_cfg.h 的 * FTP_CFG_USER_MAX 同宽,写侧受该上限约束 */ /* ── 通道一:实时量(写侧节拍 200 ms)────────────────────────────────────── */ /* 单路遥测:数值 + 品质 */ typedef struct { float val; /* 数值;单位口径由 ui_data_rt_t.eng 决定,RAW 位另说 */ uint8_t qds; /* 品质位组合(UI_DATA_Q_*) */ uint8_t rsv[3]; /* 对齐填充(显式命名,勿留匿名空洞) */ } ui_data_yc_t; /* 实时快照:遥测 9 路 + 遥信位图 + 频率 + 墙钟 */ typedef struct { uint32_t seq; /* 发布序号(本层盖章,单调递增) */ uint32_t tick_ms; /* 发布时刻(tick 域,差值法可比) */ uint8_t valid; /* 0 = 尚无首帧(页面全显 "--") */ uint8_t eng; /* 量纲口径(UI_DATA_ENG_*) */ uint8_t rsv[2]; /* 预留(对齐填充) */ ui_data_yc_t yc[UI_DATA_YC_N]; /* 下标 = adc_semantic_t 语义序(基波有效值) */ uint8_t ph_n; /* 本帧发布的相量实例槽数(0..UI_DATA_PH_MAX) */ 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 该点有硬件来源 */ uint8_t freq_valid; /* 1 = 测频读数新鲜(幅值门 OK 且已收敛) */ uint8_t rsv2[3]; /* 预留(对齐填充) */ float freq_hz; /* 测得频率(Hz);freq_valid=0 时不可消费 */ uint8_t clock_ok; /* 0 = 墙钟无效(RTC 异常/未对时) */ uint8_t rsv3[3]; /* 预留(对齐填充) */ char clock[UI_DATA_CLOCK_LEN]; /* "YYYY-MM-DD HH:MM:SS";无效时空串 */ } ui_data_rt_t; /* ── 通道二:参数(写侧节拍 = 定值校验和变化才发)────────────────────────── */ /* * 参数快照:40 项定值 Q10 原值 + 封存态 * 存 Q10 而非 float 的理由:定值是整表校验和覆盖的定点值,快照存浮点会引入 * "显示值与校验和口径不一致"的二义性(Q10 → 显示小数位换算属表现层,见 * docs/UI设计/UI_数据供给设计说明.md §8) */ typedef struct { uint32_t seq; /* 发布序号(本层盖章) */ uint32_t checksum; /* 与 prot_set_checksum() 同值:页面据此判"参数变过" */ uint8_t valid; /* 0 = 尚无首帧 */ uint8_t locked; /* 1 = 定值闭锁(校验和自检失败等)→ 页面禁编辑 */ uint8_t bad_valid; /* 1 = bad_mask 有意义(整表装载/提交失败之后) */ uint8_t rsv; /* 预留 */ uint64_t bad_mask; /* 40 位:置 1 = 该定值项越界(页面反显该行) */ int32_t q10[UI_DATA_SET_N]; /* Q10 原值,下标 = prot_set_id_t 枚举序 */ } ui_data_par_t; /* ── 通道三:装置信息(静态身份 + 通讯参数 + 运行时长)───────────────────── */ /* * 装置信息快照(通道划分理由见本头文件头,契约侧同一条登记在 * docs/UI设计/UI_数据供给设计说明.md §4.4——同一事实只在一处详述) * 写侧随 rt 同节拍发布(内容基本不变,代价是一次约 90 B 拷贝) */ typedef struct { uint32_t seq; /* 发布序号(本层盖章) */ uint8_t valid; /* 0 = 尚无首帧 */ uint8_t rsv[3]; /* 预留(对齐填充) */ uint32_t uptime_s; /* 开机运行时长(秒;写侧按 tick 差算) */ uint16_t port_104; /* IEC 60870-5-104 上行监听端口 */ uint16_t port_disp; /* 远程显示(RemoDispBus)端口 */ char model[UI_DATA_MODEL_LEN]; /* 终端型号 */ char sw_ver[UI_DATA_VERSTR_LEN]; /* 软件版本 */ char hw_ver[UI_DATA_VERSTR_LEN]; /* 硬件版本 */ char build_date[UI_DATA_DATE_LEN]; /* 程序日期(编译期) */ char sw_sum[UI_DATA_SUM_LEN]; /* 软件校验;无构建期 CRC 步骤 = "--" */ char ftp_user[UI_DATA_FUSER_LEN]; /* FTP 登录用户名(只显名,不显口令) */ } ui_data_info_t; /* ── 初始化 ───────────────────────────────────────────────────────────────── */ /* * 函数功能:快照层初始化——三通道清零(valid=0、seq=0),页面即按"无首帧"渲染 * 入口参数:无 * 返回值:无 * 限定条件:内核启动前可调(纯内存操作,零 OS 依赖);可重复调用(幂等) * 函数说明:静态存储上电本已清零,本函数的意义是运行期复位(例如"恢复出厂后 * 重开 UI"这类后续路径),以及让宿主侧测试能随时造出"首帧之前"的状态 */ void ui_data_init(void); /* ── 发布(写侧唯一入口;单写者同上下文,无锁)───────────────────────────── */ /* * 函数功能:发布一帧实时量快照(覆盖存量并给 seq 盖章 +1) * 入口参数:a_snap - 源帧(本层整帧拷入自有存储,调用方可复用其栈/静态空间) * const ui_data_rt_t * * 返回值:0 - 已发布;-1 - 参数无效(NULL,存量不动) int * 限定条件:唯一发布者 = App/app/ui_data_feed.c,uiTask 上下文 * 函数说明:seq 由本层盖章(写侧不必填);其余字段原样入存,本层不做任何业务 * 判断与格式化 */ int ui_data_publish_rt(const ui_data_rt_t *a_snap); /* * 函数功能:发布一帧参数快照(覆盖存量并给 seq 盖章 +1) * 入口参数:a_snap - 源帧 const ui_data_par_t * * 返回值:0 - 已发布;-1 - 参数无效(NULL,存量不动) int * 限定条件:同上;发布节拍 = 定值校验和变化才发 * (docs/UI设计/UI_数据供给设计说明.md §5.2) */ int ui_data_publish_par(const ui_data_par_t *a_snap); /* * 函数功能:发布一帧装置信息快照(覆盖存量并给 seq 盖章 +1) * 入口参数:a_snap - 源帧 const ui_data_info_t * * 返回值:0 - 已发布;-1 - 参数无效(NULL,存量不动) int * 限定条件:同上 */ int ui_data_publish_info(const ui_data_info_t *a_snap); /* ── 只读访问(页面侧)───────────────────────────────────────────────────── */ /* * 函数功能:读最新一帧实时量快照(整帧拷给页面私有缓存) * 入口参数:a_out - 输出缓冲(非 NULL) ui_data_rt_t * * 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动,页面显示 "--"); * -2 - 参数无效(NULL) int * 限定条件:页面钩子上下文(uiTask);勿在逐行 refresh 里无条件整帧拷贝, * 先比 ui_data_rt_seq()(用法见本头文件头) */ int ui_data_rt(ui_data_rt_t *a_out); /* * 函数功能:读最新一帧参数快照 * 入口参数:a_out - 输出缓冲(非 NULL) ui_data_par_t * * 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动);-2 - 参数无效 int * 限定条件:同上;定值页同样用 ui_data_par_seq() 门控页面私有缓存 */ int ui_data_par(ui_data_par_t *a_out); /* * 函数功能:读最新一帧装置信息快照 * 入口参数:a_out - 输出缓冲(非 NULL) ui_data_info_t * * 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动);-2 - 参数无效 int * 限定条件:同上 */ int ui_data_info(ui_data_info_t *a_out); /* * 函数功能:查实时帧发布序号(页面缓存失效判据) * 入口参数:无 * 返回值:当前 seq;从未发布 = 0 uint32_t * 限定条件:无(只读单字,任意上下文安全) * 函数说明:轻量查询——只读一个 uint32,可逐行调用(契约 §5.4 的门控就靠它) */ uint32_t ui_data_rt_seq(void); /* * 函数功能:查参数帧发布序号 * 入口参数:无 * 返回值:当前 seq;从未发布 = 0 uint32_t * 限定条件:无 */ uint32_t ui_data_par_seq(void); /* * 函数功能:查装置信息帧发布序号 * 入口参数:无 * 返回值:当前 seq;从未发布 = 0 uint32_t * 限定条件:无 */ uint32_t ui_data_info_seq(void); #ifdef __cplusplus } #endif #endif /* UI_DATA_H */