Files
STM32F4-Base/Drivers/BSP/UI/ui_data.h
T
wandering1 77779d49d6 feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐
UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机
OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批
预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block
(口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng
反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定
内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按
is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。

记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在
defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE);
事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身
(一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。
为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核,
丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。

为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费
两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过;
RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同
码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。

实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B;
RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS,
protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→
164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34,
既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、
不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页
连跑两次逐字节一致;字库 diff 空。

顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽
16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前
靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值
全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py
模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。

未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行;
LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND),
已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
2026-10-26 15:51:12 +08:00

434 lines
26 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 数据快照层(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)
* v1.1 2026-10-26 DTU 项目组 道F task-1(docs/任务规划/UI补齐.md §2 第 1 行):
* 新增定值写路径(param_request/param_state/
* param_drain,契约 §6.2 深度 4 环形队列)+ 参数帧尾
* 追加 cal_block(UI_DATA_VER 1→2,整定页"未标定"徽标
* 的数据源);写路径仍不引入任何 App 依赖(执行事实
* 经 ui_data_param_ops_t 由 App 侧注入,同契约 §7.2
* 记录页注入式 reader 的路子)
*/
#ifndef UI_DATA_H
#define UI_DATA_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/*
* 契约版本:结构体字段只允许尾部追加,追加即升此值并在写侧填默认值
* (禁止删除或重排字段——页面按名字取,重排是静默错位;
* 口径 docs/UI设计/UI_数据供给设计说明.md §4.3)
*/
/* v2(2026-10-26,道F task-1):ui_data_par_t 尾追加 cal_block——版本号是页面与
* 写侧唯一的对齐凭据,追加字段不升版 = 老页面按新帧长 memcpy 时静默错位 */
#define UI_DATA_VER 2u
/* ── 通道数量 ─────────────────────────────────────────────────────────────── */
/* 遥测语义通道数:下标序 = 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 该点有硬件来源 */
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 时不可消费 */
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 枚举序 */
/* v2 尾追加:未标定闭锁(整定页"未标定"徽标的数据源)。
* 1 = UA/UN/IA 三路里至少一路的标定 VALID 未置位 → 保护判据当前停走,页面必须
* 让用户看见"改了定值也不会动作";0 = 三路全 VALID(判据在跑)。
* ⚠ 口径**不是** rt.eng:eng 是整帧量纲档(要求电气 8 路全 VALID),V0.1 双
* 转换器下 UB/UC/IB/IC/IN 永不置位 → eng 恒为引脚档,照 eng 画徽标就成了
* "标定做完屏上仍永不解锁"。三路口径的权威实现 = App/task/protect_task.c
* 的 s_cal_locked()(写侧 App/app/ui_data_feed.c 按同一条判据现算,本层只存
* 真值、不判断)。与上面 locked(定值校验和自检闭锁 = 禁编辑)是两件事,
* 屏上不得互认(三词三源见 docs/任务规划/数据可信.md §4.3) */
uint8_t cal_block;
} 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);
/* ── 写路径:定值整定请求(页面只投递,执行在 App 侧)───────────────────── */
/*
* 请求队列深度(契约 docs/UI设计/UI_数据供给设计说明.md §6.2 定死 4)
* 为什么是 4 不是 1:整定页"保存"是用户按键,一条请求要等 App 侧 drain 才落定;
* 深度 1 会让连按两次保存把第二条判成"忙",而 4 格 = 本层 .bss 多
* 4 × sizeof(ui_data_param_req_t)(168 B/格,主机单测 TC-UIDATA-016 钉住尺寸,
* 内存台账 docs/内存管理/RAM使用分析与优化方案.md)。加深 = 纯 RAM 代价,
* 本层不做任何按内存自适应
*/
#define UI_DATA_PARAM_QD 4u
/*
* 一条定值写入请求 = "本次保存改了哪些项 + 它们的 Q10 新值"
*
* 为什么是「位图 + 定长 40 槽」而不是「项数 + 变长 id 数组」:表现层编辑叠层本就
* 是一张改动位图(uint64_t)+ 一张工作值表(int32_t[UI_DATA_SET_N]),本形状与它
* 逐位同构 → 提交侧一次 memcpy、执行侧逐项按位取,两侧都不必再维护第三种表示;
* 定长还让队列退化成 O(1) 静态环形数组(零堆、零变长拷贝、零 malloc 门禁风险)。
*
* ⚠ mask 只有 64 位 → 定值项数硬上限 64;越界位由 ui_data.c 的编译期守卫拦住,
* 将来项数破 64 必须换表示(不会静默丢项)。
* ⚠ **mask 未置位的槽本次不提交**:本层与执行侧都不读它——调用方可以只填 mask 内
* 的槽(整帧 memcpy 入队会把 mask 外的字节一并存下来,但那字节永不被读,不影响
* 任何判定;先 memset 再填也完全可以,两种写法都安全)。
* 绝不允许"从显示值回提全表"——Q10 存不下 0.95 这类返回系数(真存 972/1024),
* 按显示域往返会把用户没动的项顺手改掉
* (docs/任务规划/UI补齐.md §2 第 2 行判据"值不落盘"的前提)。
*/
typedef struct {
uint64_t mask; /* bit i = 1 → 第 i 项随本请求提交 */
int32_t q10[UI_DATA_SET_N]; /* Q10 新值;仅 mask 置位的槽有意义 */
} ui_data_param_req_t;
/*
* 写路径状态(页面保存行显示;语义表 = 契约 §6.3)
* IDLE - 从未投递 → 页面显 ENTER
* WAIT - 已投递未落定(队列里还有请求在路上)→ 页面显 WAIT
* OK - 最近一条已逐项写入并置脏 → 页面显 OK
* ERR_RANGE - 最近一条整批拒绝:任一项越界 → 一字未写 → 页面显 ERR 并高亮
* 越界行(越界项经 ui_data_param_err_mask() 取)
* ERR_LOCKED - 最近一条被定值闭锁拒(写侧自检失败态)→ 页面显 LOCK 并禁编辑
* ERR_BUSY - 最近一次投递被拒:队列满 → 页面显 ERR、页脚"忙,请重试"
* 状态变量的三条写入路径(本层唯二写者 = request 与 drain,同在 uiTask):
* 投递成功 → WAIT;投递被拒(满)→ ERR_BUSY;drain 落定 → 终态,但队列仍有
* 残留时改写回 WAIT。故"满被拒"看得见(ERR_BUSY),随后逐步 drain 又回到 WAIT
*/
#define UI_DATA_PARAM_ST_IDLE 0
#define UI_DATA_PARAM_ST_WAIT 1
#define UI_DATA_PARAM_ST_OK 2
#define UI_DATA_PARAM_ST_ERR_RANGE 3
#define UI_DATA_PARAM_ST_ERR_LOCKED 4
#define UI_DATA_PARAM_ST_ERR_BUSY 5
/*
* 执行侧函数表(由 App 在 ui_data_feed_tick() 每次调用时传入;本层不保存副本)
*
* 为什么用注入而不是直接调 prot_set:分层方向硬约束——Drivers/BSP 不得 include
* App 头(scripts/check_layer_deps.py 拦提交),而"什么算越界、写了要不要置脏"
* 全在 App/algo/prot_set 那侧。同契约 §7.2 记录页的注入式 reader:BSP 声明抽象
* 函数表,App 给实现,主机单测给桩 → 队列与状态机的**生产代码**能在 PC 上跑
* 全表(越界不改值 / 闭锁拒写 / 正常往返三条判据都覆盖到真代码,不是副本)。
* ⚠ 本表不含任何"范围数值"——上下限真源仍是 App/algo/prot_set.c 的
* g_prot_set_meta,本层只按 check() 的返回值决定整批写不写(不新增第二真源)
*/
typedef struct {
uint8_t (*locked)(void); /* 1 = 定值闭锁中 */
int (*check)(uint8_t a_id, int32_t a_q10); /* 0 合法 / -1 越界 */
int (*apply)(uint8_t a_id, int32_t a_q10); /* 0 成功 / -1 失败 */
} ui_data_param_ops_t;
/*
* 函数功能:投递一条定值写入请求(页面"保存"行的唯一出口——只投递,不执行)
* 入口参数:a_req - 请求(mask 与 q10 整条拷入队列,调用方可复用其栈空间)
* const ui_data_param_req_t *
* 返回值:0 - 已入队(状态转 WAIT);mask 为 0 的空请求也返回 0,但按"无改动可
* 保存"直接落终态 OK 且**不占槽位**(保存行 ENTER 时一个都没改
* 是正常操作,报 ERR 会让现场以为设备有问题);
* -1 - 队列满(状态转 ERR_BUSY,在队请求一条不动);
* -2 - 参数无效:NULL、或 mask 含 UI_DATA_SET_N 以上的位
* (状态与队列都不动:调用方的编程错误不该落成屏上的"越界") int
* 限定条件:uiTask 上下文(页面按键钩子);与 drain 同上下文 → 无锁
* 函数说明:本函数**不**校验值域(越界与否是执行侧事实,在 drain 里整批预检),
* 也不碰 prot_set、不碰文件系统(落盘归 defaultTask,AGENTS 模块约定)
*/
int ui_data_param_request(const ui_data_param_req_t *a_req);
/*
* 函数功能:取写路径状态(页面保存行显示)
* 入口参数:无
* 返回值:UI_DATA_PARAM_ST_* 之一 int
* 限定条件:无(只读单字,任意上下文安全)
* 函数说明:ERR_TIMEOUT(契约 §6.3 的"3 s 未取走")本层**不产出**——uiTask 每
* 10 ms 就 drain 一次,真出现"永不落定"说明 App 侧没接线,属装配缺陷;
* 页面若要兜底可自己按投递时刻计时(docs/UI设计/UI_数据供给设计说明.md
* §6.3 已把它列为页面本地可选项)
*/
int ui_data_param_state(void);
/*
* 函数功能:取最近一条请求的越界项位图(页面高亮越界行)
* 入口参数:无
* 返回值:bit i = 1 → 第 i 项越界;最近终态不是 ERR_RANGE 时恒 0 uint64_t
* 限定条件:无(只读单字)
* 函数说明:与 ui_data_par_t.bad_mask 是两条不同来源——bad_mask 是 prot_set 整表
* 装载/解析留下的越界位图(App 侧事实),本函数是**本次提交**里被预检
* 挡下的项;页面在 ERR_RANGE 那一帧用本函数,别把两者混成一份
*/
uint64_t ui_data_param_err_mask(void);
/*
* 函数功能:处理一条定值写入请求(App 装配层节拍入口;出队 → 判定 → 落定)
* 入口参数:a_ops - 执行侧函数表(三个函数指针都不得为 NULL)
* const ui_data_param_ops_t *
* 返回值:1 - 处理并提交了一条(状态已落终态或转 WAIT);0 - 队列空(状态不动);
* -1 - 参数无效(ops 或其函数指针为 NULL,队列与状态都不动) int
* 限定条件:uiTask 上下文,紧跟页面之后调(App/app/ui_data_feed.c 的 tick);
* 每轮最多处理一条——整定是低频人工事件,一轮一条已够,且把最坏耗时
* 钉在 40 次 check + 40 次 apply,不会随队列堆积而抖动
* 函数说明:固定四步(顺序即判据,契约 §6.2)——
* ① 取队首一条;② ops->locked() 非 0 → ERR_LOCKED,**本条丢弃**(闭锁
* 是业务态,用户解闭锁后重新提交;不做"排队等解锁",那会让屏上的 WAIT
* 无限期挂着);③ 按 mask 逐项 ops->check() 整批预检,任一项越界 →
* ERR_RANGE 且**一字不写**(all-or-nothing,与 prot_set_load_raw 同纪律),
* 越界项记入 err_mask;④ 预检全过再逐项 ops->apply(),全成 → OK。
* ④ 中途失败(预检已过仍失败 = 执行侧与预检不一致,理论不可达)不回滚:
* prot_set 无回滚接口,谎称"整批未写"比如实报 ERR_RANGE 更坏,故按实际
* 失败项置 err_mask 后报 ERR_RANGE,并在注释里写明可能部分已生效
*/
int ui_data_param_drain(const ui_data_param_ops_t *a_ops);
#ifdef __cplusplus
}
#endif
#endif /* UI_DATA_H */