Files
STM32F4-Base/Drivers/BSP/UI/ui_data.c
T
wandering1 48a8ef0564 feat(UI): 遥测死区与系数 2 页——注入表第四张、三态用脏位、WAIT 与 OK 各出一张黄金
这两页是「UI补齐」里唯一带**持久化语义**的一档,所以整笔的设计重心不在画面上,而在**页面如何诚实表达
「已受理」与「已落盘」**——这一点由 setpage-scout 在开工前的只读查实钉下来:cfg_file_save() 全仓唯一调用
者是 run_par_store_save(),而 default_task.c:246 **每轮无条件**调它、函数首行 s_dirty==0 就直接返回
⇒
落盘动力本来就在 defaultTask,不在 104 会话、也不在事件位。因此本笔走 A2(UI 不当授权写者),不需要
新事件位、不需要新同步口。

页面三态判据 = run_par_dirty_peek():ENTER / WAIT(脏位仍在 = 已受理未落盘)/ OK(脏位归零 = 整档已外科
保存成功)/ 写被拒 = ERR。两条限制写在页面注释里没藏:脏位是**整档全局**的(一趟落 [run_par] 9 键 +
[adc_cal] 两段,归零只证明「这一档落盘了」,不证明「我这一项单独落盘」);他人随后再改会重新置脏 ⇒ 沿用
clear/clock 页的结果锁存,把「我这次动作的结果」与「档位此刻的状态」分开。

**为什么黄金必须出两张**(这条是本轮最有价值的防假证据规则):模拟器假件**保留脏位**、全仓唯一的
s_yt_dirty=0 写点是 sim_yt_pump()(setter 只置不清),于是 WAIT 与 OK 各出一帧——两组同键序、唯一差别是
EXTRA 里的 --yt-nopump。若假件同步清脏位,真机的 WAIT 态永远不会被像素回归覆盖,黄金就成了「只钉住最
好看那一帧」。TC-UIDATA-028o 另钉住 ui_data 层零缓存。

形状:新增 ui_data 第四张注入表(八字段整表校验、拒残表),实现落在装配层 App/app/ui_data_feed.c,
页面零 App include(分层方向 + 模拟器不链 run_par 两条老理由都成立);共用视图模型 ui_w_ytview 承载两页。
合法域一律由执行侧出口给(run_par_db_range / RUN_PAR_K_MAX_UV),页面不抄第二真源——系数页 v1 只用粗守
并在注释写明两层守卫互不掩盖,µV/码 ×[0.5,2.0] 那条真带的出口器归 task-43,**没有**在页面里抄常量。

取舍与遗留(如实):系数行 d=0 整 µV(1e9 装不下两位小数,注释写了「要小数改哪、不收紧假上限」);b 覆盖
无口未做;徽标只画系数页;行名因「率」缺字改为 Hz死区(font-charset 机器先抓到的,不是我目测判的)。

实测:@build 0 错误 0 警告(Code=233,900 / RW-data=1,536);ui_data 单测 277/277、run_par
67/67 零回归;
内容页黄金 47 页全过、**存量 40 张逐字节零漂移**(git 里只有 7 张新增);menu_rows / 池水位 / font-charset
/
bom / mojibake / nested-comment / cppcheck / coding-standard / layer-deps /
bsp-err 全 0 新增;编号五项式
refs 8,778 / dec 7,622 / und **1,156**(上限 1,166)/ 比值 86.83%(基线 86.0% 之上)/ 错误差值
0,注释零裸形
字母+数字。generated-fresh 与 registration-check 本地 PASS;新增 6 只源文件按规矩**双端注册同笔**(叶子
+
registry 19→21 + uvprojx + CMakeLists),uvprojx 这次属「真的增删源文件」才入库。

移交三条不混进本笔:① 全量 --selftest 现 exit 1 是 task-41 硬闸在模拟器上也拒 reset/clear(既有红,快检档
不受影响),归台架道定夺;② 内容页 40→47 的全仓文档计数改写由 Lead 派;③ run_par_deadband_set 的限定
条件语措辞(「iec104Task 上下文」已被装配层裁定覆盖)归算法层下一笔。
2026-10-27 05:07:18 +08:00

1029 lines
44 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)
* 模块功能:三通道快照(实时量 rt / 参数 par / 装置信息 info)的存储、发布盖章与
* 只读访问——本模块是纯内存叶子:不取数、不格式化、不判断业务含义、
* 不碰 LVGL、不 include 任何 App 头(职责边界与依赖方向 =
* Drivers/BSP/UI/ui_data.h 文件头与
* docs/UI设计/UI_数据供给设计说明.md §3.2)
* 并发模型:单写者同上下文——唯一发布者 App/app/ui_data_feed.c 与全部页面钩子都在
* uiTask 内串行执行,故无锁、无双缓冲、无 seqlock;将来真出现第二个
* 发布者,保护收在本模块内部(双槽 + 每槽 seq + 单字节活动下标,照
* App/algo/phasor.c 双区先例),页面代码不受影响
* (判据 docs/UI设计/UI_数据供给设计说明.md §5.1)
* 写路径(定值整定请求队列)同理:生产者是页面"保存"按键钩子、消费者是
* App 侧节拍里的 ui_data_param_drain(),两者同在 uiTask → 环形队列照旧
* 无锁;若将来出现第二个投递上下文,唯一要做的是给 request/drain 两侧
* 各加一把互斥(队列本身是单字 head/cnt 推进,页面侧不会看到半成品)
* 模块功能补充:除三通道快照外,本模块还持有三条与页面相关的东西:
* ① 定值写路径的请求队列与状态字(ui_data_param_request/state/drain)
* ——只做"排队 + 状态机",不判断值域、不碰 prot_set、不碰文件系统
* (值域与写入事实经 ui_data_param_ops_t 由 App 侧注入);
* ② 装置维护写通道(ui_data_maint_*:墙钟设置 / 强制复归 / 清除记录)
* ——常驻注入的 ui_data_maint_ops_t 转发,本层不解释业务含义;
* 为什么这里常驻注入而 ① 每次传表,见 ui_data.h 通道四的函数说明;
* ③ 时钟字段换算(ui_data_clock_*:本地域、无时区偏移、纯整数零依赖)
* ——「无效值拒收」的唯一判据处,也是唯一能在主机单测里跑全表的
* 日历判据(页面 .c 依赖 lvgl.h,编不进主机测试单元)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 王建锋 创建(道E task-14)
* v1.1 2026-10-26 DTU 项目组 道F task-1:新增定值写路径环形队列(深度 4、满返 -1)
* 与状态机(IDLE/WAIT/OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY),
* 执行经注入函数表回调 App 侧;参数帧新增 cal_block
* 字段随 publish 原样入存(本层不解释含义)
* v1.2 2026-10-26 DTU 项目组 道F task-3(装置维护 3 页的前置通道):新增维护
* 写通道 ui_data_maint_*(常驻注入 ui_data_maint_ops_t:
* clock_set / reset_req / active_count / oper_note /
* clear_req / clear_state 六口,未注入一律安全返回)与
* 时钟字段换算 ui_data_clock_*(本地域无时区偏移;
* to_epoch 同时是"无效值拒收"的唯一判据处)。
* 三通道快照结构一个字节都没动 ⇒ UI_DATA_VER 保持 2
* (版本号是页面与写侧唯一的对齐凭据,无字段变更却升版
* = 让老页面以为新帧换了布局)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_data.h"
/* 静态变量定义区 */
/* 三通道存储(上电全零 = "尚无首帧",与 ui_data_init() 后状态一致) */
static ui_data_rt_t s_rt;
static ui_data_par_t s_par;
static ui_data_info_t s_info;
/*
* 发布计数:seq 由本层盖章,与入帧携带的 seq 无关——入帧的该字段被覆盖。
* 读侧"有无首帧"的判据是计数为 0,不是帧内 valid:valid 是"这一帧有没有有效
* 数据"(写侧可能发布一帧全无效的真值),两者语义不同,混用会让"首帧之前"
* 与"首帧但无数据"在页面上显示成同一种态。
*/
static uint32_t s_rt_cnt;
static uint32_t s_par_cnt;
static uint32_t s_info_cnt;
/* ── 写路径:定值整定请求队列(深度 UI_DATA_PARAM_QD 的静态环形数组)──────── */
/*
* mask 合法位集合 = 低 UI_DATA_SET_N 位(40 项 → 0x000000FFFFFFFFFF)。
* 用"全 1 右移"而不是"1 左移 40 位再减 1":前者在 UI_DATA_SET_N == 64 时仍是
* 良定义(移 0 位),后者会退化成立即未定义行为(移位数 == 位宽)。
* 项数一旦超过 64,下面的编译期守卫当场拦下——位图放不下就是表示法该换,
* 绝不能静默把高 24 项变成"投不进去也不报错"
*/
#define UI_DATA_PARAM_MASK_VALID ((uint64_t)(~(uint64_t)0u) >> (64u - UI_DATA_SET_N))
typedef char ui_data_chk_mask_t[((uint32_t)UI_DATA_SET_N <= 64u) ? 1 : -1];
/* 环形队列:head = 出队下标,cnt = 在队条数(0..UI_DATA_PARAM_QD)
* 为什么用 head+cnt 而不是 head+tail:cnt 直接就是"还有几条没落定",
* state() 判 WAIT 不必再做 (tail - head) 取模减法,少一处可算错的地方 */
static ui_data_param_req_t s_pq[UI_DATA_PARAM_QD];
static uint8_t s_pq_head;
static uint8_t s_pq_cnt;
/* 写路径状态字与越界位图(页面只读;int/uint64 单字读写在 M4 上不撕裂) */
static int s_pq_state = UI_DATA_PARAM_ST_IDLE;
static uint64_t s_pq_err_mask;
/* 函数定义区 */
/*
* 函数功能:快照层初始化——三通道清零(valid=0、seq=0)+ 写路径队列复位,
* 页面即按"无首帧/未投递"渲染
* 入口参数:无
* 返回值:无
* 限定条件:内核启动前可调(纯内存操作,零 OS 依赖);可重复调用(幂等)
* 函数说明:静态存储上电本已清零,本函数的意义是运行期复位(例如"恢复出厂后
* 重开 UI"这类后续路径),以及让宿主侧测试能随时造出"首帧之前"的状态。
* 写路径一并丢弃在队请求是**有意**的:旧请求带的是旧配置下的编辑叠层,
* 复位后还把它落进 prot_set,等于拿陈旧草稿覆盖新装载的定值
*/
void ui_data_init(void) {
(void)memset(&s_rt, 0, sizeof(s_rt));
(void)memset(&s_par, 0, sizeof(s_par));
(void)memset(&s_info, 0, sizeof(s_info));
s_rt_cnt = 0u;
s_par_cnt = 0u;
s_info_cnt = 0u;
(void)memset(s_pq, 0, sizeof(s_pq));
s_pq_head = 0u;
s_pq_cnt = 0u;
s_pq_state = UI_DATA_PARAM_ST_IDLE;
s_pq_err_mask = 0u;
}
/*
* 函数功能:发布一帧实时量快照(覆盖存量并给 seq 盖章 +1)
* 入口参数:a_snap - 源帧(本层整帧拷入自有存储,调用方可复用其栈/静态空间)
* const ui_data_rt_t *
* 返回值:0 - 已发布;-1 - 参数无效(NULL,存量不动) int
* 限定条件:唯一发布者 = App/app/ui_data_feed.c,uiTask 上下文
* 函数说明:seq 由本层盖章(入帧 seq 被忽略);其余字段原样入存,本层不做任何
* 业务判断与格式化
*/
int ui_data_publish_rt(const ui_data_rt_t *a_snap) {
if (a_snap == NULL) {
return -1;
}
(void)memcpy(&s_rt, a_snap, sizeof(s_rt));
s_rt_cnt += 1u;
s_rt.seq = s_rt_cnt;
return 0;
}
/*
* 函数功能:发布一帧参数快照(覆盖存量并给 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) {
if (a_snap == NULL) {
return -1;
}
(void)memcpy(&s_par, a_snap, sizeof(s_par));
s_par_cnt += 1u;
s_par.seq = s_par_cnt;
return 0;
}
/*
* 函数功能:发布一帧装置信息快照(覆盖存量并给 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) {
if (a_snap == NULL) {
return -1;
}
(void)memcpy(&s_info, a_snap, sizeof(s_info));
s_info_cnt += 1u;
s_info.seq = s_info_cnt;
return 0;
}
/*
* 函数功能:读最新一帧实时量快照(整帧拷给页面私有缓存)
* 入口参数:a_out - 输出缓冲(非 NULL) ui_data_rt_t *
* 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动,页面显示 "--");
* -2 - 参数无效(NULL) int
* 限定条件:页面钩子上下文(uiTask);勿在逐行 refresh 里无条件整帧拷贝,
* 先比 ui_data_rt_seq()(用法见 Drivers/BSP/UI/ui_data.h 文件头)
*/
int ui_data_rt(ui_data_rt_t *a_out) {
if (a_out == NULL) {
return -2;
}
if (s_rt_cnt == 0u) {
return -1;
}
(void)memcpy(a_out, &s_rt, sizeof(s_rt));
return 0;
}
/*
* 函数功能:读最新一帧参数快照
* 入口参数: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) {
if (a_out == NULL) {
return -2;
}
if (s_par_cnt == 0u) {
return -1;
}
(void)memcpy(a_out, &s_par, sizeof(s_par));
return 0;
}
/*
* 函数功能:读最新一帧装置信息快照
* 入口参数:a_out - 输出缓冲(非 NULL) ui_data_info_t *
* 返回值:0 - 已拷出;-1 - 尚无首帧(输出不动);-2 - 参数无效 int
* 限定条件:同上
*/
int ui_data_info(ui_data_info_t *a_out) {
if (a_out == NULL) {
return -2;
}
if (s_info_cnt == 0u) {
return -1;
}
(void)memcpy(a_out, &s_info, sizeof(s_info));
return 0;
}
/*
* 函数功能:查实时帧发布序号(页面缓存失效判据)
* 入口参数:无
* 返回值:当前 seq;从未发布 = 0 uint32_t
* 限定条件:无(只读单字,任意上下文安全)
* 函数说明:轻量查询——只读一个 uint32,可逐行调用;序号只在发布时 +1,页面
* 据此把"整帧拷贝"从每帧每行降到只在换帧时一次
*/
uint32_t ui_data_rt_seq(void) {
return s_rt.seq;
}
/*
* 函数功能:查参数帧发布序号
* 入口参数:无
* 返回值:当前 seq;从未发布 = 0 uint32_t
* 限定条件:无
*/
uint32_t ui_data_par_seq(void) {
return s_par.seq;
}
/*
* 函数功能:查装置信息帧发布序号
* 入口参数:无
* 返回值:当前 seq;从未发布 = 0 uint32_t
* 限定条件:无
*/
uint32_t ui_data_info_seq(void) {
return s_info.seq;
}
/*
* 函数功能:投递一条定值写入请求(页面"保存"行的唯一出口——只投递,不执行)
* 入口参数:a_req - 请求(本层整条拷入环形队列,调用方可复用其栈/叠层空间)
* const ui_data_param_req_t *
* 返回值:0 - 已入队(状态转 WAIT);或 mask 为 0 的空请求(无改动可保存,
* 状态直接转 OK,不占槽位)
* -1 - 队列满(状态转 ERR_BUSY,在队请求一条不动);
* -2 - 参数无效:NULL 或 mask 含 UI_DATA_SET_N 以上的位
* (状态与队列都不动——把调用方的编程错误落成屏上"错误"会误导现场) int
* 限定条件:uiTask 上下文(页面按键钩子);与 drain 同上下文串行 → 无锁
* 函数说明:本层不校验值域(越界与否属执行侧事实,落在 drain 的整批预检),
* 不 include App 头、不碰 prot_set、不碰 FatFs(UI 直写定值会把校验和
* 打成中间态、直写盘会撞 defaultTask 单写者纪律——契约 §6.1)
* 空请求按成功处理是刻意的:保存行 ENTER 时"一个都没改"是正常操作,
* 报 ERR 会让用户以为设备有问题
*/
int ui_data_param_request(const ui_data_param_req_t *a_req) {
uint8_t slot;
if (a_req == NULL) {
return -2;
}
if ((a_req->mask & ~UI_DATA_PARAM_MASK_VALID) != 0u) {
return -2;
}
if (a_req->mask == 0u) {
s_pq_state = UI_DATA_PARAM_ST_OK;
s_pq_err_mask = 0u;
return 0;
}
if (s_pq_cnt >= (uint8_t)UI_DATA_PARAM_QD) {
s_pq_state = UI_DATA_PARAM_ST_ERR_BUSY;
return -1;
}
slot = (uint8_t)(((uint32_t)s_pq_head + (uint32_t)s_pq_cnt) % UI_DATA_PARAM_QD);
(void)memcpy(&s_pq[slot], a_req, sizeof(s_pq[slot]));
s_pq_cnt += 1u;
s_pq_state = UI_DATA_PARAM_ST_WAIT;
return 0;
}
/*
* 函数功能:取写路径状态(页面保存行显示)
* 入口参数:无
* 返回值:UI_DATA_PARAM_ST_IDLE / _WAIT / _OK / _ERR_RANGE / _ERR_LOCKED /
* _ERR_BUSY 之一 int
* 限定条件:无(只读单字,任意上下文安全)
* 函数说明:本函数就是 s_pq_state 的直读,不做"pending 优先"之类的二次加工——
* 三条写路径(投递成功→WAIT、投递被拒→ERR_BUSY、drain 落定→终态或
* 残留则 WAIT)已把顺序语义收在写侧,读侧再判一次等于两处解释同一件事
*/
int ui_data_param_state(void) {
return s_pq_state;
}
/*
* 函数功能:取最近一条请求的越界项位图(页面高亮越界行)
* 入口参数:无
* 返回值:bit i = 1 → 第 i 项越界;最近终态不是 ERR_RANGE 时恒 0 uint64_t
* 限定条件:无(只读单字)
* 函数说明:与 ui_data_par_t.bad_mask 两条来源不同(本函数 = 本次提交被预检挡下
* 的项;bad_mask = prot_set 整表装载/解析留下的越界位图),页面在
* ERR_RANGE 那一帧用本函数,混用会把上一次装载的越界显示成本次的
*/
uint64_t ui_data_param_err_mask(void) {
return s_pq_err_mask;
}
/*
* 函数功能:整批预检一条请求(位图内置位的项逐个问执行侧 check())
* 入口参数:a_ops - 执行侧函数表(调用方已核非 NULL)
* a_req - 待检请求
* 返回值:越界项位图(bit i = 第 i 项越界;0 = 本批全部在界内) uint64_t
* 限定条件:只调 check(),不写任何东西("预检"与"落值"分成两个函数,
* 正是为了让 all-or-nothing 有地方落脚:整批扫完才决定写不写)
* 函数说明:一次扫全 40 位而不是"遇到第一个越界就返回"——err_mask 要给页面
* **全部**越界行反显,只报第一行等于让用户改完一行再撞下一行
*/
static uint64_t ui_data_param_scan(const ui_data_param_ops_t *a_ops,
const ui_data_param_req_t *a_req) {
uint64_t bad = 0u;
uint64_t bit = 1u;
uint8_t i;
for (i = 0u; i < (uint8_t)UI_DATA_SET_N; i++) {
if ((a_req->mask & bit) != 0u) {
if (a_ops->check(i, a_req->q10[i]) != 0) {
bad |= bit;
}
}
bit <<= 1u;
}
return bad;
}
/*
* 函数功能:逐项落值一条请求(调用前 ui_data_param_scan() 已确认整批在界内)
* 入口参数:a_ops - 执行侧函数表
* a_req - 待写请求
* 返回值:0 - 位图内全部写入;-1 - 某项被执行侧拒绝(*p_bad 输出该项) int
* 限定条件:apply() 的副作用由执行侧定义(App 实现 = prot_set_set_sealed,
* 每项自带置脏);本函数不回滚已写项——prot_set 没有回滚接口
* 函数说明:走到这里还失败 = 执行侧与预检不一致(两个真源漂移),理论不可达;
* 如实报失败项比谎称"整批未写"更诚实,故**可能已有前几项生效**
*/
static int ui_data_param_write(const ui_data_param_ops_t *a_ops,
const ui_data_param_req_t *a_req,
uint64_t *p_bad) {
uint64_t bit = 1u;
uint8_t i;
for (i = 0u; i < (uint8_t)UI_DATA_SET_N; i++) {
if ((a_req->mask & bit) != 0u) {
if (a_ops->apply(i, a_req->q10[i]) != 0) {
*p_bad = bit;
return -1;
}
}
bit <<= 1u;
}
*p_bad = 0u;
return 0;
}
/*
* 函数功能:处理一条定值写入请求(App 装配层节拍入口:出队 → 闭锁 → 预检 → 落定)
* 入口参数:a_ops - 执行侧函数表(locked/check/apply 三者都不得为 NULL)
* const ui_data_param_ops_t *
* 返回值:1 - 处理并提交了一条(状态已转终态,或队列仍有残留则转 WAIT);
* 0 - 队列空(状态与队列都不动);
* -1 - 参数无效(a_ops 或其成员为 NULL,状态与队列都不动) int
* 限定条件:uiTask 上下文(App/app/ui_data_feed.c 的 tick 每轮一次);每轮最多
* 处理一条——整定是低频人工事件,一轮一条已够,且把最坏耗时钉在
* 2 × 40 次回调(预检一遍 + 写入一遍),不随队列堆积抖动
* 函数说明:四步顺序即判据(契约 §6.2),判定本体拆成 scan/write 两个静态函数
* (见其函数说明)——
* ① 取队首;② locked() → ERR_LOCKED 并**丢弃本条**(闭锁是业务态,不做
* "排队等解锁",否则屏上 WAIT 无限期挂着,用户以为设备卡死);
* ③ scan() 任一项越界 → ERR_RANGE 且**一字不写**(all-or-nothing,与
* prot_set_load_raw 同纪律:半张表落进值区就是"看着保存成功、其实混着
* 新旧两套定值");④ write() 全成 → OK。
* ⚠ 顺序不可调:先判闭锁再判越界——闭锁态下 check() 的语义仍成立,但
* 页面要的是"定值闭锁"这个更高优先级的原因(契约 §6.3 表序)
*/
int ui_data_param_drain(const ui_data_param_ops_t *a_ops) {
const ui_data_param_req_t *p_req;
uint64_t bad;
int settled;
if ((a_ops == NULL) || (a_ops->locked == NULL) ||
(a_ops->check == NULL) || (a_ops->apply == NULL)) {
return -1;
}
if (s_pq_cnt == 0u) {
return 0;
}
p_req = &s_pq[s_pq_head];
bad = 0u;
if (a_ops->locked() != 0u) {
settled = UI_DATA_PARAM_ST_ERR_LOCKED;
} else {
settled = UI_DATA_PARAM_ST_OK;
bad = ui_data_param_scan(a_ops, p_req);
if (bad != 0u) {
settled = UI_DATA_PARAM_ST_ERR_RANGE; /* 预检挡下:一字未写 */
} else {
if (ui_data_param_write(a_ops, p_req, &bad) != 0) {
settled = UI_DATA_PARAM_ST_ERR_RANGE; /* 执行侧拒写:见函数说明 */
}
/* 两关都过 → settled 保持 OK */
}
}
s_pq_err_mask = bad;
s_pq_head = (uint8_t)(((uint32_t)s_pq_head + 1u) % UI_DATA_PARAM_QD);
s_pq_cnt -= 1u;
s_pq_state = (s_pq_cnt != 0u) ? UI_DATA_PARAM_ST_WAIT : settled;
return 1;
}
/* ── 通道四:装置维护写请求(时钟 / 强制复归 / 清除记录)──────────────────── */
/*
* 注入的维护执行侧函数表(NULL = 未注入)。
* 只存指针不拷内容:注入方(App/task/ui_task.c 与 simulator/main.c)的两端实现
* 都是文件级 static const 表,生命周期 = 整个运行期,与 ui_w_records_bind() 存的
* reader 指针同一条约定。撤销只可能出现在宿主测试里。
*/
static const ui_data_maint_ops_t *s_maint;
/*
* 函数功能:注入装置维护执行侧函数表(启动期一次)
* 入口参数:ops - 函数表;六个成员指针都不得为 NULL;NULL = 撤销注入
* const ui_data_maint_ops_t *
* 返回值:0 - 已注入(或已撤销);-1 - ops 非空但某成员为 NULL(存量不动) int
* 限定条件:uiTask 启动期,先于 ui_menu_init()(与 ui_w_records_bind 同一处);
* uiTask 之外的上下文不得调用(本层无锁,与快照层同一条并发前提)
* 函数说明:成员逐条校验而不只看表指针——半途接错一条(例如忘了注 clear_state)
* 会让清除记录页在"按下才崩"的位置崩,生成期/启动期就拒掉整表才是省事
*/
int ui_data_maint_bind(const ui_data_maint_ops_t *a_ops) {
if (a_ops == NULL) {
s_maint = NULL; /* 撤销注入:页面随即按"能力不可用"渲染 */
return 0;
}
if ((a_ops->clock_set == NULL) || (a_ops->reset_req == NULL) ||
(a_ops->active_count == NULL) || (a_ops->oper_note == NULL) ||
(a_ops->clear_req == NULL) || (a_ops->clear_state == NULL)) {
return -1;
}
s_maint = a_ops;
return 0;
}
/*
* 函数功能:请求设置墙钟(时钟设置页"保存"行的唯一出口)
* 入口参数:unix_epoch - 已整组校验过的秒(见 ui_data_clock_to_epoch) uint32_t
* 返回值:0 - 执行侧受理;-1 - 未注入或执行侧失败;-2 - 参数无效(0 = 本层保留的
* "未初始化"哨兵,与 sys_clock 的 UINT32_MAX 哨兵不同族,
* 这里只挡 0 这一个明显不该写进 RTC 的值) int
* 限定条件:uiTask 上下文(页面按键钩子)
* 函数说明:本层不判日期合法性——判据只有一份,在 ui_data_clock_to_epoch();
* 也不做回读,回读属执行侧(它才知道 sys_clock_get() 的真值)
*/
int ui_data_maint_clock_set(uint32_t a_unix_epoch) {
if (s_maint == NULL) {
return -1;
}
if (a_unix_epoch == 0u) {
return -2;
}
return s_maint->clock_set(a_unix_epoch);
}
/*
* 函数功能:请求复归全部保护元件(强制复归页的执行出口)
* 入口参数:无
* 返回值:0 - 已受理;-1 - 未注入或执行侧拒收 int
* 限定条件:uiTask 上下文
* 函数说明:本函数**不投**操作记录,页面在返 0 之后自己投("本地面板发起"这个
* 事实只有页面知道;远端 104 那条另有投递点,两边各一条、不得互替);
* 返 -1 时页面必须一条都不投——给一次没发生的操作留审计记录,比少一条
* 记录坏得多
*/
int ui_data_maint_reset_req(void) {
if (s_maint == NULL) {
return -1;
}
return s_maint->reset_req();
}
/*
* 函数功能:取当前动作元件数(复归页"保护动作 N"行的数据源)
* 入口参数:无
* 返回值:元件数;未注入返回 0 uint32_t
* 限定条件:页面绘制路径可直调(一次函数指针转发,零 I/O、零锁)
* 函数说明:返回类型取 uint32_t 与执行侧位图同宽,不在本层压成 uint8_t——
* 元件数将来超 8 时,窄化会在页面里静默截成 0("明明动作了屏上说没有")
*/
uint32_t ui_data_maint_active_count(void) {
if (s_maint == NULL) {
return 0u;
}
return s_maint->active_count();
}
/*
* 函数功能:投一条操作记录留痕
* 入口参数:desc - 描述串(ASCII 常量;NULL = 什么都不做) const char *
* 返回值:0 - 已投递;-1 - 未注入或 desc 为 NULL int
* 限定条件:uiTask 上下文
* 函数说明:投递可能被队列丢弃(执行侧口径)——本层不因此把屏上的"操作成功"
* 翻成失败:记录通道故障不等于操作没发生,混起来只会让现场误判装置
*/
int ui_data_maint_oper_note(const char *a_desc) {
if ((s_maint == NULL) || (a_desc == NULL)) {
return -1;
}
s_maint->oper_note(a_desc);
return 0;
}
/*
* 函数功能:请求清除某类记录(清除记录页的执行出口)
* 入口参数:log_id - UI 侧记录源编号(UI_W_REC_LOG_*) uint8_t
* 返回值:0 - 已受理;-1 - 未注入或执行侧拒绝 int
* 限定条件:uiTask 上下文;"受理"≠"已清除",终态读 ui_data_maint_clear_state()
* 函数说明:不在本层判 log_id 上界——上界的真源是记录侧有几个文件(映射表在
* 适配层),这里再抄一份长度就成了第二真源,记录加一类时必然漏改
*/
int ui_data_maint_clear_req(uint8_t a_log_id) {
if (s_maint == NULL) {
return -1;
}
return s_maint->clear_req(a_log_id);
}
/*
* 函数功能:查某类记录的清除状态
* 入口参数:log_id - 记录源编号 uint8_t
* 返回值:UI_DATA_CLEAR_ST_NONE / _OK / _WAIT / _FAIL;未注入返回 NONE int
* 限定条件:页面绘制路径可直调
* 函数说明:返回值直通执行侧、不做二次加工(与 ui_data_param_state() 同理:
* 两处解释同一件事迟早漂移);WAIT 是正常态,页面不得画成失败
*/
int ui_data_maint_clear_state(uint8_t a_log_id) {
if (s_maint == NULL) {
return UI_DATA_CLEAR_ST_NONE;
}
return s_maint->clear_state(a_log_id);
}
/* ── 时钟字段换算(本地域,无时区偏移)──────────────────────────────────── */
/*
* 私有宏定义区(本小节自带,不与其它小节共用):
* 算法 = Howard Hinnant 的 days_from_civil / civil_from_days(以 0000-03-01
* 为纪元、400 年一周期),与 App/protocol/iec104/iec104_asdu.c 的正反两套实现
* 同族;本工程年域 2000..2038,全程非负,用 uint32 就够,不引 int64 也不引 time.h
*/
#define CL_SECONDS_PER_DAY 86400u
#define CL_SECONDS_OF_DAY_MAX 11647u /* 0x7FFFFFFF % 86400 = 2038-01-19 的 03:14:07 */
#define CL_DAYS_EPOCH_MAX 24855u /* 0x7FFFFFFF / 86400 = 2038-01-19(距 1970-01-01 天数) */
#define CL_DAYS_TO_0000 719468u /* 1970-01-01 距 0000-03-01 纪元的天数(Hinnant 偏移量) */
#define CL_DAYS_PER_ERA 146097u /* 400 年一周期的天数 */
/*
* 函数功能:公历日期 → 距 1970-01-01 的天数(调用方保证字段已判过合法性)
* 入口参数:year - 年(>= 1) uint16_t month - 月 1..12 uint8_t day - 日 1..31 uint8_t
* 返回值:天数(本工程年域内恒非负) uint32_t
* 限定条件:月/日未做合法性判定——**合法性判定在 to_epoch(),不在这里**,
* 换算函数保持"给什么算什么"才能与反向函数严格互逆
* 函数说明:先做 1/2 月归到上一年的 11/12 月(y 相应减一),使 3 月成为岁首、
* 闰日落在周期末端,之后每月天数变成 31 天/30.6 天的规整式子
*/
static uint32_t ui_data_clock_days_from_civil(uint16_t a_year, uint8_t a_month, uint8_t a_day) {
uint32_t y = (a_month <= 2u) ? ((uint32_t)a_year - 1u) : (uint32_t)a_year;
uint32_t era = y / 400u;
uint32_t yoe = y - (era * 400u); /* [0, 399] */
uint32_t mp = (a_month > 2u) ? ((uint32_t)a_month - 3u) : ((uint32_t)a_month + 9u);
uint32_t doy = ((153u * mp + 2u) / 5u) + (uint32_t)a_day - 1u; /* [0, 365] */
uint32_t doe = (yoe * 365u) + (yoe / 4u) - (yoe / 100u) + doy; /* [0, 146096] */
return (era * CL_DAYS_PER_ERA) + doe - CL_DAYS_TO_0000;
}
/*
* 函数功能:判断是否闰年(完整格里历规则)
* 入口参数:year - 年 uint16_t
* 返回值:1 - 闰年;0 - 平年 uint8_t
* 限定条件:与 App/util/sys_clock.c 的 unix_to_sd2506() 内联判据逐字同一条规则
* (4 年 && 非 100 年 || 400 年);2100 不在允许年域内,无需讨论该例外
* 函数说明:取 uint8_t 而不是 bool——本工程布尔判定允许 bool,但本函数参与
* 天数计算,返回宽度明确的整型省掉一次隐式转换(同 ui_w_editor_key()
* 把"分发标志"写成 uint8_t 的口径)
*/
static uint8_t ui_data_clock_is_leap(uint16_t a_year) {
if ((((uint32_t)a_year % 4u) == 0u) && (((uint32_t)a_year % 100u) != 0u)) {
return 1u;
}
if (((uint32_t)a_year % 400u) == 0u) {
return 1u;
}
return 0u;
}
/*
* 函数功能:取某年某月的天数(含闰年 2 月)
* 入口参数:year - 年 uint16_t month - 月 1..12 uint8_t
* 返回值:28 / 29 / 30 / 31;月份为 0 或 > 12 时返回 0(调用方以 0 判非法,
* 不开第二条错误码) uint8_t
* 限定条件:无(纯查表 + 闰年判定,零依赖)
* 函数说明:表序 = 1 月起(下标 0 是 1 月),与 sys_clock.c:145 那份 mdays[]
* 的"0 月起"排法**故意不同**——本表按屏上月的直觉排(下标 = 月 - 1),
* 少一次心算 -1 就少一次错位;两侧同一张表的等价性由主机单测逐月核对
*/
uint8_t ui_data_clock_days_in_month(uint16_t a_year, uint8_t a_month) {
static const uint8_t k_mdays[12] = {
31u, 28u, 31u, 30u, 31u, 30u, 31u, 31u, 30u, 31u, 30u, 31u
};
if ((a_month < 1u) || (a_month > 12u)) {
return 0u;
}
if ((a_month == 2u) && (ui_data_clock_is_leap(a_year) != 0u)) {
return 29u;
}
return k_mdays[a_month - 1u];
}
/*
* 函数功能:日历字段 → 本地域 epoch 秒,同时充当这组字段的合法性判据
* 入口参数:p_in - 六个字段 const ui_data_clock_t *
* p_out - 秒 uint32_t *
* 返回值:0 - 合法并已输出;-1 - 参数为 NULL;-2 - 字段组合非法(任一段越界,
* 含 2 月 30 日、非闰年 2 月 29 日、月 13、时 24、分/秒 60);
* -3 - 字段各自合法但换算结果越界(> UI_DATA_CLOCK_EPOCH_MAX) int
* 限定条件:年段先判(2000..2038),因此天数计算全程在非负 uint32 域内
* 函数说明:三段边界分开返码,是为了让页面能给出现场听得懂的话:"2 月 30 日"
* 是输入错(-2,光标回到出问题那一行),而"2038 年之后"是能力边界
* (-3,说这一版装置的墙钟就到这儿)——两种情况混成一个 ERR,
* 用户会反复试一个永远试不出的日期
* ⚠ 全程不含时区偏移(口径见 ui_data.h 本节注释与
* App/task/ui_task.c 的 SOE 时标注记):加 28800 的后果是"屏上输
* 08:00、存进去 16:00",且模拟器里往返照样自洽、照不出来
*/
int ui_data_clock_to_epoch(const ui_data_clock_t *p_in, uint32_t *p_out) {
uint32_t days;
uint32_t sod;
uint32_t dmax;
if ((p_in == NULL) || (p_out == NULL)) {
return -1;
}
if ((p_in->year < (uint16_t)UI_DATA_CLOCK_YEAR_MIN) ||
(p_in->year > (uint16_t)UI_DATA_CLOCK_YEAR_MAX)) {
return -2;
}
if ((p_in->month < 1u) || (p_in->month > 12u)) {
return -2;
}
dmax = (uint32_t)ui_data_clock_days_in_month(p_in->year, p_in->month);
if ((p_in->day < 1u) || ((uint32_t)p_in->day > dmax)) {
return -2;
}
if (p_in->hour > 23u) {
return -2;
}
if ((p_in->minute > 59u) || (p_in->second > 59u)) {
return -2;
}
days = ui_data_clock_days_from_civil(p_in->year, p_in->month, p_in->day);
sod = ((uint32_t)p_in->hour * 3600u) + ((uint32_t)p_in->minute * 60u) +
(uint32_t)p_in->second;
if ((days > CL_DAYS_EPOCH_MAX) || ((days == CL_DAYS_EPOCH_MAX) && (sod > CL_SECONDS_OF_DAY_MAX))) {
return -3; /* 2038-01-19 03:14:07 之后:uint32 秒域到头,不是用户输错 */
}
*p_out = (days * CL_SECONDS_PER_DAY) + sod;
return 0;
}
/*
* 函数功能:本地域 epoch 秒 → 日历字段(ui_data_clock_to_epoch 的反函数)
* 入口参数:unix_epoch - 秒 uint32_t p_out - 输出 ui_data_clock_t *
* 返回值:0 - 已输出;-1 - p_out 为 NULL;-2 - 超出允许域(0 哨兵、年 < 2000、
* 或 > UI_DATA_CLOCK_EPOCH_MAX) int
* 限定条件:与 to_epoch 严格互逆(主机单测按往返相等钉,见 test/host/ui_data);
* 刻意把 0 也判越界:0 = 1970-01-01,落在 RTC 年域(2000..2099)之外,
* 而它又恰好是"墙钟无效"在记录侧的占位起点,绝不允许被当真实时间显示
*/
int ui_data_clock_from_epoch(uint32_t a_unix_epoch, ui_data_clock_t *p_out) {
uint32_t days;
uint32_t sod;
uint32_t z;
uint32_t era;
uint32_t doe;
uint32_t yoe;
uint32_t doy;
uint32_t mp;
uint32_t y;
uint32_t m;
if (p_out == NULL) {
return -1;
}
if ((a_unix_epoch == 0u) || (a_unix_epoch > UI_DATA_CLOCK_EPOCH_MAX)) {
return -2;
}
days = a_unix_epoch / CL_SECONDS_PER_DAY;
sod = a_unix_epoch % CL_SECONDS_PER_DAY;
z = days + CL_DAYS_TO_0000; /* 移到 0000-03-01 纪元 */
era = z / CL_DAYS_PER_ERA;
doe = z - (era * CL_DAYS_PER_ERA); /* [0, 146096] */
yoe = (doe - (doe / 1460u) + (doe / 36524u) - (doe / 146096u)) / 365u; /* [0, 399] */
doy = doe - ((365u * yoe) + (yoe / 4u) - (yoe / 100u));
mp = (5u * doy + 2u) / 153u;
/* 3 月纪元月序转公历月序:mp < 10 落在 3..12 月,否则落在 1/2 月并归上一年 */
y = yoe + (era * 400u);
if (mp < 10u) {
m = mp + 3u;
} else {
m = mp - 9u;
y += 1u; /* 1/2 月属于上一年 */
}
p_out->year = (uint16_t)y;
p_out->month = (uint8_t)m;
p_out->day = (uint8_t)(doy - ((153u * mp + 2u) / 5u) + 1u);
p_out->hour = (uint8_t)(sod / 3600u);
p_out->minute = (uint8_t)((sod / 60u) % 60u);
p_out->second = (uint8_t)(sod % 60u);
if ((p_out->year < (uint16_t)UI_DATA_CLOCK_YEAR_MIN) ||
(p_out->year > (uint16_t)UI_DATA_CLOCK_YEAR_MAX)) {
return -2;
}
return 0;
}
/*
* 函数功能:解析定长时标串为日历字段
* 入口参数:ts - "YYYY-MM-DD HH:MM:SS"(快照 rt.clock 的形状) const char *
* p_out - 输出 ui_data_clock_t *
* 返回值:0 - 成功;-1 - 参数为 NULL;-2 - 串型不对(不是 19 字符、分隔位不是
* '-'/' '/':'、出现非数字)**或**型对但值非法
* (组合再过 ui_data_clock_to_epoch() 把关) int
* 限定条件:空串(墙钟无效时快照里的取值,ui_data.h:149)走 -2;
* 本函数**不猜**:"1970-01-01 08:00:00"这类记录侧占位串在这里同样返 -2
* (年 1970 出域),页面据此显示"无时钟"而不是把占位串画成真实时间
* 函数说明:手写十进制累加而不用 sscanf——本模块是零 LVGL、零 App 的纯内存叶子,
* 拉进 stdio 就等于给主机单测加依赖、给 MCU 端加一份 printf 家族
*/
int ui_data_clock_parse(const char *a_ts, ui_data_clock_t *p_out) {
ui_data_clock_t t;
uint32_t i;
uint32_t acc;
const char *p;
if ((a_ts == NULL) || (p_out == NULL)) {
return -1;
}
(void)memset(&t, 0, sizeof(t));
/* 五段数字:年 4 位、月/日/时/分/秒各 2 位;段间分隔位逐位核对 */
p = a_ts;
acc = 0u;
for (i = 0u; i < 4u; i++) {
if ((p[i] < '0') || (p[i] > '9')) {
return -2;
}
acc = (acc * 10u) + (uint32_t)(p[i] - '0');
}
t.year = (uint16_t)acc;
if (p[4] != '-') {
return -2;
}
p += 5u;
{
uint8_t *const fields[5] = { &t.month, &t.day, &t.hour, &t.minute, &t.second };
const char seps[4] = { '-', ' ', ':', ':' };
for (i = 0u; i < 5u; i++) {
if (((p[0] < '0') || (p[0] > '9')) || ((p[1] < '0') || (p[1] > '9'))) {
return -2;
}
*fields[i] = (uint8_t)(((uint32_t)(p[0] - '0') * 10u) + (uint32_t)(p[1] - '0'));
p += 2u;
if (i < 4u) {
if (*p != seps[i]) {
return -2;
}
p++;
}
}
}
if (*p != '\0') {
return -2; /* 型对但尾部还有字符:不是本工程认的时标串 */
}
{
uint32_t probe;
if (ui_data_clock_to_epoch(&t, &probe) != 0) {
return -2;
}
}
*p_out = t;
return 0;
}
/*
* 函数功能:日历字段 → 定长时标串(与 ui_data_clock_parse 同串型)
* 入口参数:p_in - 字段 const ui_data_clock_t *
* buf - 输出缓冲 char *
* len - 缓冲容量(含 NUL,至少 UI_DATA_CLOCK_TS_LEN + 1 = 20) uint32_t
* 返回值:0 - 已写出;-1 - 参数无效或容量不足(**不写半个串**,避免调用方
* 把截断串当完整时标显示) int
* 限定条件:不判字段合法性(调用方刚从 parse/to_epoch 那条链出来,再判一遍
* 就是第二条判据)
*/
int ui_data_clock_format(const ui_data_clock_t *p_in, char *a_buf, uint32_t a_len) {
uint8_t i;
char *p;
uint32_t v;
if ((p_in == NULL) || (a_buf == NULL) ||
(a_len < (UI_DATA_CLOCK_TS_LEN + 1u))) {
return -1;
}
p = a_buf;
v = (uint32_t)p_in->year;
for (i = 0u; i < 4u; i++) { /* 年份 4 位补零 */
p[3u - i] = (char)('0' + (uint32_t)(v % 10u));
v /= 10u;
}
p[4] = '-';
p += 5;
{
const uint8_t fields[5] = { p_in->month, p_in->day, p_in->hour,
p_in->minute, p_in->second };
const char seps[4] = { '-', ' ', ':', ':' };
for (i = 0u; i < 5u; i++) {
p[0] = (char)('0' + ((uint32_t)fields[i] / 10u));
p[1] = (char)('0' + ((uint32_t)fields[i] % 10u));
p += 2;
if (i < 4u) {
*p = seps[i];
p++;
}
}
}
*p = '\0';
return 0;
}
/* ── 通道五:遥测整定注入(死区 / 系数)──────────────────────────────────── */
/*
* 注入的遥测整定执行侧函数表(NULL = 未注入)。
* 与 s_maint 同一条生命周期约定:注入方(App/app/ui_data_feed.c 与
* simulator/main.c)给的都文件级 static const 表,本层只存指针不拷内容;
* 撤销窗口只在宿主测试里存在。
*
* ⚠ 本层**不持有任何遥测整定的值**:九条死区与八路系数的真源在
* App/algo/run_par.c 与 App/algo/adc_cal.c,落盘动力在 defaultTask 的每轮
* 无条件 run_par_store_save()(首行 if (s_dirty == 0u) return 0;,道F task-7
* 查实结论:落盘链不靠 104 会话、也不靠事件位)。本层若自己存一份,就正是
* 卡片判据"不新增第二份真源"要拦的那件事。页面能问的只有两样:现状(*_row)
* 与"我这次动作落没落"(pending)。
*/
static const ui_data_yt_ops_t *s_yt;
/*
* 函数功能:注入遥测整定执行侧函数表(启动期一次)
* 入口参数:ops - 函数表;八个成员指针都不得为 NULL;NULL = 撤销注入
* 返回值:0 - 已注入(或已撤销);-1 - ops 非空但某成员为 NULL(存量不动) int
* 限定条件:uiTask 启动期,先于 ui_menu_init();本层无锁,与快照层同一并发前提
* 函数说明:整表校验的理由与 ui_data_maint_bind() 逐字相同——半途接错一条会让
* 页面在"按下才崩"的位置崩,装配期就拒掉残表才是省事。残表被拒后**存量
* 不动**(TC-UIDATA-027 钉住这条:好表在用时被残表覆盖会静默把已通的口
* 打断)
*/
int ui_data_yt_bind(const ui_data_yt_ops_t *a_ops) {
if (a_ops == NULL) {
s_yt = NULL; /* 撤销注入:页面随即按"整定能力不可用"渲染 */
return 0;
}
if ((a_ops->db_n == NULL) || (a_ops->db_row == NULL) ||
(a_ops->db_set == NULL) || (a_ops->cal_n == NULL) ||
(a_ops->cal_row == NULL) || (a_ops->cal_k_set == NULL) ||
(a_ops->cal_blocked == NULL) || (a_ops->pending == NULL)) {
return -1;
}
s_yt = a_ops;
return 0;
}
/*
* 函数功能:死区点数
* 返回值:点数;未注入返回 0(页面据此渲染空表,而不是拿编译期常量当"真源说
* 有几行"——行数归执行侧,见 ui_data.h 通道五表注) uint8_t
* 限定条件:页面绘制路径可直调(一次函数指针转发,零 I/O、零锁)
*/
uint8_t ui_data_yt_db_n(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->db_n();
}
/*
* 函数功能:读第 idx 行死区现状(工程量 + 粗守域 + 量纲 + 显示小数位)
* 入口参数:idx - 0..8(0..7 = 电气语义通道序,8 = 频率) uint8_t
* a_out - 行现状 ui_data_yt_row_t *
* 返回值:0 - 已写出;-1 - 未注入 / 参数空 / 执行侧无此行(页面这行退化显示,
* 不猜、不拿上一行数值顶替) int
* 限定条件:页面绘制与按键链内(uiTask)
* 函数说明:出参在本层不预清——执行侧契约是"返 0 才整份写",页面返非 0 时
* 不得读该结构,这条口径由 TC-UIDATA-028 钉
*/
int ui_data_yt_db_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
if ((s_yt == NULL) || (a_out == NULL)) {
return -1;
}
return s_yt->db_row(a_idx, a_out);
}
/*
* 函数功能:写第 idx 行死区(工程量)——两页唯一的死区写出口
* 返回值:0 - 副本已写且已置脏;-1 - 未注入或执行侧拒(副本未动、不置脏) int
* 限定条件:uiTask 按键链内(编辑提交)
* 函数说明:未注入与执行侧拒绝**同码返回**是刻意的:两条腿屏上都是"值没变 +
* 保存行 ERR",页面不需要、也不该分辨"是没接上还是被拒"——那是装配期
* 故障与工况故障的区分,归日志不归屏幕
*/
int ui_data_yt_db_set(uint8_t a_idx, float a_eng) {
if (s_yt == NULL) {
return -1;
}
return s_yt->db_set(a_idx, a_eng);
}
/* 函数功能:系数覆盖点数;未注入返回 0(理由同 ui_data_yt_db_n) uint8_t */
uint8_t ui_data_yt_cal_n(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->cal_n();
}
/* 函数功能:读第 idx 路系数现状;返回值与不猜口径同 ui_data_yt_db_row int */
int ui_data_yt_cal_row(uint8_t a_idx, ui_data_yt_row_t *a_out) {
if ((s_yt == NULL) || (a_out == NULL)) {
return -1;
}
return s_yt->cal_row(a_idx, a_out);
}
/*
* 函数功能:写第 idx 路系数 k(µV/码)——两页唯一的系数写出口
* 返回值:0 - 已写并置脏;-1 - 未注入或拒(副本未动、不置脏) int
* 限定条件:uiTask 按键链内
* 函数说明:粗守(上限 RUN_PAR_K_MAX_UV)与真带(执行段 ×[0.5,2.0])两层守卫
* **互不掩盖**:屏上输入框只能挡误写,真带裁在 adc_cal;本层不加第三层
*/
int ui_data_yt_cal_k_set(uint8_t a_idx, float a_uv) {
if (s_yt == NULL) {
return -1;
}
return s_yt->cal_k_set(a_idx, a_uv);
}
/*
* 函数功能:未标定闭锁徽标源
* 返回值:1 - 闭锁中;0 - 正常**或未注入** uint8_t
* 限定条件:页面绘制路径可直调
* 函数说明:未注入按 0 是"宁可不画"而不是"报个徽标保平安"——徽标是断言,
* 没有真源时的断言就是假断言;此时页面上写值本来也全被拒(写口返 -1),
* 信息不缺这条
*/
uint8_t ui_data_yt_cal_blocked(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->cal_blocked();
}
/*
* 函数功能:整档落盘脏位探查(保存行三态的唯一判据源)
* 返回值:1 - 有已整定未落盘改动;0 - 干净或未注入 uint8_t
* 限定条件:页面绘制路径可直调(每帧一次转发,零 I/O)
* 函数说明:本层**不加任何边沿记忆**——"我这次动作的结果"要锁存是页面的事
* (脏位整档全局、他人再改会重新置脏,两条口径见 ui_data.h pending
* 成员注);在这里做锁存会把两页的状态耦合成一份
*/
uint8_t ui_data_yt_pending(void) {
if (s_yt == NULL) {
return 0u;
}
return s_yt->pending();
}