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), 已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
This commit is contained in:
+228
-2
@@ -10,11 +10,23 @@
|
||||
* 发布者,保护收在本模块内部(双槽 + 每槽 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.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 原样入存(本层不解释含义)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -39,15 +51,42 @@ 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),页面即按"无首帧"渲染
|
||||
* 函数功能:快照层初始化——三通道清零(valid=0、seq=0)+ 写路径队列复位,
|
||||
* 页面即按"无首帧/未投递"渲染
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:内核启动前可调(纯内存操作,零 OS 依赖);可重复调用(幂等)
|
||||
* 函数说明:静态存储上电本已清零,本函数的意义是运行期复位(例如"恢复出厂后
|
||||
* 重开 UI"这类后续路径),以及让宿主侧测试能随时造出"首帧之前"的状态
|
||||
* 重开 UI"这类后续路径),以及让宿主侧测试能随时造出"首帧之前"的状态。
|
||||
* 写路径一并丢弃在队请求是**有意**的:旧请求带的是旧配置下的编辑叠层,
|
||||
* 复位后还把它落进 prot_set,等于拿陈旧草稿覆盖新装载的定值
|
||||
*/
|
||||
void ui_data_init(void) {
|
||||
(void)memset(&s_rt, 0, sizeof(s_rt));
|
||||
@@ -56,6 +95,11 @@ void ui_data_init(void) {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -194,3 +238,185 @@ uint32_t ui_data_par_seq(void) {
|
||||
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;
|
||||
}
|
||||
|
||||
+151
-1
@@ -25,6 +25,13 @@
|
||||
* 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
|
||||
@@ -41,7 +48,9 @@ extern "C" {
|
||||
* (禁止删除或重排字段——页面按名字取,重排是静默错位;
|
||||
* 口径 docs/UI设计/UI_数据供给设计说明.md §4.3)
|
||||
*/
|
||||
#define UI_DATA_VER 1u
|
||||
/* v2(2026-10-26,道F task-1):ui_data_par_t 尾追加 cal_block——版本号是页面与
|
||||
* 写侧唯一的对齐凭据,追加字段不升版 = 老页面按新帧长 memcpy 时静默错位 */
|
||||
#define UI_DATA_VER 2u
|
||||
|
||||
/* ── 通道数量 ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -157,6 +166,16 @@ typedef struct {
|
||||
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;
|
||||
|
||||
/* ── 通道三:装置信息(静态身份 + 通讯参数 + 运行时长)───────────────────── */
|
||||
@@ -276,6 +295,137 @@ uint32_t ui_data_par_seq(void);
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -53,7 +53,7 @@ uint8_t ui_menu_tab_children(uint8_t node, uint8_t out[], uint8_t max);
|
||||
* 入口参数:node - 节点下标 uint8_t 0 - 表长-1
|
||||
* 返回值:true - 有子项(显示为菜单,带 ▶);false - 无子项(叶子,可进内容页)
|
||||
* 限定条件:纯查询,无副作用;node 越界返回 false
|
||||
* 函数说明:这是**谓词**(bool 合法),不是成败码——err-type 检查里白名单放行的形态。
|
||||
* 函数说明:这是**谓词**(bool 合法),不是成败码——bsp-err 的 err-type 检查里白名单放行的形态。
|
||||
*/
|
||||
bool ui_menu_tab_has_children(uint8_t node);
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT] = {
|
||||
{ 3u, &ui_page_ac_desc }, /* node 3 交流量 */
|
||||
{ 4u, &ui_page_dc_desc }, /* node 4 直流量 */
|
||||
{ 5u, &ui_page_yx_desc }, /* node 5 遥信量 */
|
||||
{ 8u, &ui_page_view_param_desc }, /* node 8 参数 */
|
||||
{ 9u, &ui_page_view_set_desc }, /* node 9 定值 */
|
||||
{ 10u, &ui_page_view_word_desc }, /* node 10 控制字 */
|
||||
{ 11u, &ui_page_view_plate_desc }, /* node 11 软压板 */
|
||||
{ 13u, &ui_page_view_param_desc }, /* node 13 参数 */
|
||||
{ 14u, &ui_page_view_set_desc }, /* node 14 定值 */
|
||||
{ 15u, &ui_page_view_word_desc }, /* node 15 控制字 */
|
||||
|
||||
@@ -25,7 +25,7 @@ typedef struct {
|
||||
const content_page_def_t *desc; /* 页面描述符(Flash const) */
|
||||
} ui_page_entry_t;
|
||||
|
||||
#define UI_PAGE_MAP_COUNT 12u /* 绑定了内容页的叶子数 */
|
||||
#define UI_PAGE_MAP_COUNT 16u /* 绑定了内容页的叶子数 */
|
||||
|
||||
extern const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT];
|
||||
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
* 模块名称:UI Page view_param (参数查看内容页)
|
||||
* 模块功能:参数定值 → 查看 → 参数 的内容页——8 行数值表(PT/CT 一次二次额定 + 4 个返回系数),每页 8 行、1 页;
|
||||
* 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显;
|
||||
* 本文件零 App include、零写路径(查看页没有"保存"这件事)
|
||||
* 本文件零 App include;写方向只有一个出口:整定态的"保存"经
|
||||
* ui_data_param_request() 投递(执行与落盘在 App 侧,本层不碰 prot_set/FatFs)
|
||||
* 两态一页(道F task-2:整定 4 叶绑定本描述符):同一份描述符既服务"查看"叶子
|
||||
* (只读——不绑 commit → 无保存行、按键全交回框架 → 像素与绑定前逐位一致),
|
||||
* 也服务"整定"叶子(123 授权 → 编辑叠层 + 保存行 + 未标定徽标)。
|
||||
* 分派点只有 pg_init 一处(按 ui_content_page_is_editable() 判),
|
||||
* 两态共用行表/取数/反显,不存在"查看一套边界、整定另一套边界"
|
||||
* 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型),
|
||||
* 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号
|
||||
* 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致
|
||||
@@ -11,6 +17,11 @@
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 3)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2:接整定态——pg_init 按权限分派
|
||||
* attach/attach_edit、新增 pg_key 转发按键、
|
||||
* pg_draw 追加"未标定"徽标、描述符补
|
||||
* edit_capable/key_intercept/key_cb 三项
|
||||
* (只读分支行为逐字不变)
|
||||
*/
|
||||
/* 头文件包含区 */
|
||||
#include "ui_page_view_param.h"
|
||||
@@ -32,11 +43,21 @@ static ui_w_setlist_t s_sl;
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内调用(uiTask)
|
||||
* 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 函数说明:1. 每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 2. **只读 / 整定两态在这里分派一次**,之后所有钩子只看已绑好的回调
|
||||
* (组件用 commit==NULL 判视图类型,不再回头看权限)
|
||||
* 3. 时序依据(为什么 init_cb 里问 is_editable() 拿到的就是本次进入的权限):
|
||||
* 框架 open() 先装 s_perm 与 s_open、最后才调 init_cb
|
||||
* (ui_content_page.c 的 open:s_perm=perm → s_open=desc → init_cb),
|
||||
* 所以这里读到的是"本次进入"的真值,不是上一次的残留
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_PARAM);
|
||||
if (ui_content_page_is_editable()) {
|
||||
ui_w_setview_attach_edit(&s_sl, UI_W_SETVIEW_PARAM); /* 整定:编辑 + 保存行 + 徽标 */
|
||||
} else {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_PARAM); /* 查看:与绑定前完全同一条路径 */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -60,26 +81,49 @@ static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自绘层(越界行反显;框架内置区块画完后调用)
|
||||
* 函数功能:自绘层(越界行反显 + 未标定徽标;框架内置区块画完后调用)
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链内
|
||||
* 函数说明:徽标函数自己判"是不是整定视图 + 帧在不在 + cal_block 置没置",
|
||||
* 页面这里无条件调它——四页少写四个 if,也就少四次写漏的机会;
|
||||
* 查看页三个条件第一条就不成立,一个像素都不落(vparam~vplate
|
||||
* 四张黄金参考仍逐位一致)
|
||||
*/
|
||||
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_w_setview_draw_cal_badge(a_layer, a_clip);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按键转发(框架 key_cb)
|
||||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:true - 本页已消费(框架跳过内置);false - 交回框架(↑↓ 移选、← 关页)
|
||||
* 限定条件:框架按键链内;描述符须 edit_capable=1 + key_intercept=1
|
||||
* 函数说明:两态行为的不同**不写在这里**——ui_w_setview_key() 先看视图类型
|
||||
* (查看页未绑 commit → 直接交回框架,等于本页没有 key_cb),
|
||||
* 再看参数帧在不在。所以这里必须原样透传返回值,不得就地"或"上什么
|
||||
*/
|
||||
static bool pg_key(lv_key_t a_key) {
|
||||
return (ui_w_setview_key(&s_sl, a_key) != 0u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_param 绑定引用)
|
||||
* edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互,
|
||||
* ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在
|
||||
* 查看页等价于"根本没有 ENTER 这条路")
|
||||
* edit_capable=1 + key_cb + key_intercept=1:同一份描述符两态共用。
|
||||
* · 整定叶子(123 授权 UI_PERM_MODIFY)→ is_editable() 真 → attach_edit +
|
||||
* 编辑叠层 + 保存行;key_intercept=1 是**必需**的:编辑中的 ←→ 移位光标
|
||||
* 要先给页面,否则框架拿 ← 当"关页",编辑到一半就跳出去
|
||||
* · 查看叶子(无 #P,恒 UI_PERM_VIEW)→ is_editable() 假 → 仍走 attach,
|
||||
* ui_w_setview_key() 第一条门就把键全部交回框架,↑↓ 移选与 ← 关页全走内置
|
||||
* ——即"绑了 key_cb 的查看页"与"没有 key_cb 的查看页"行为逐字等价
|
||||
* (§3.2.7:"权限不足 ENTER 一律忽略")
|
||||
*/
|
||||
const content_page_def_t ui_page_view_param_desc = {
|
||||
.id = "view_param",
|
||||
.title = "参数",
|
||||
.selectable = 1u,
|
||||
.edit_capable = 0u,
|
||||
.edit_capable = 1u, /* 两态页:能不能真编辑还要看本次进入的权限(is_editable) */
|
||||
.top_y = UI_LIST_TOP,
|
||||
.pitch = UI_ROW_PITCH,
|
||||
.band_h = UI_ROW_BAND_H,
|
||||
@@ -87,8 +131,10 @@ const content_page_def_t ui_page_view_param_desc = {
|
||||
.left_x = UI_LIST_LEFT_X,
|
||||
.right_x = UI_LIST_RIGHT_X,
|
||||
.rows_per_page = UI_LIST_ROWS,
|
||||
.key_intercept = 1u, /* ← 先交 key_cb:编辑中是"移位光标",非编辑中才轮到关页 */
|
||||
.init_cb = pg_init,
|
||||
.count_cb = pg_count,
|
||||
.refresh = pg_refresh,
|
||||
.draw_cb = pg_draw,
|
||||
.key_cb = pg_key,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
* 模块名称:UI Page view_plate (软压板查看内容页)
|
||||
* 模块功能:参数定值 → 查看 → 软压板 的内容页——10 行二态表(9 个段投退 + 保护总投:投入/退出),每页 8 行、2 页;
|
||||
* 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显;
|
||||
* 本文件零 App include、零写路径(查看页没有"保存"这件事)
|
||||
* 本文件零 App include;写方向只有一个出口:整定态的"保存"经
|
||||
* ui_data_param_request() 投递(执行与落盘在 App 侧,本层不碰 prot_set/FatFs)
|
||||
* 两态一页(道F task-2:整定 4 叶绑定本描述符):同一份描述符既服务"查看"叶子
|
||||
* (只读——不绑 commit → 无保存行、按键全交回框架 → 像素与绑定前逐位一致),
|
||||
* 也服务"整定"叶子(123 授权 → 编辑叠层 + 保存行 + 未标定徽标)。
|
||||
* 分派点只有 pg_init 一处(按 ui_content_page_is_editable() 判),
|
||||
* 两态共用行表/取数/反显,不存在"查看一套边界、整定另一套边界"
|
||||
* 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型),
|
||||
* 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号
|
||||
* 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致
|
||||
@@ -11,6 +17,11 @@
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 3)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2:接整定态——pg_init 按权限分派
|
||||
* attach/attach_edit、新增 pg_key 转发按键、
|
||||
* pg_draw 追加"未标定"徽标、描述符补
|
||||
* edit_capable/key_intercept/key_cb 三项
|
||||
* (只读分支行为逐字不变)
|
||||
*/
|
||||
/* 头文件包含区 */
|
||||
#include "ui_page_view_plate.h"
|
||||
@@ -32,11 +43,21 @@ static ui_w_setlist_t s_sl;
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内调用(uiTask)
|
||||
* 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 函数说明:1. 每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 2. **只读 / 整定两态在这里分派一次**,之后所有钩子只看已绑好的回调
|
||||
* (组件用 commit==NULL 判视图类型,不再回头看权限)
|
||||
* 3. 时序依据(为什么 init_cb 里问 is_editable() 拿到的就是本次进入的权限):
|
||||
* 框架 open() 先装 s_perm 与 s_open、最后才调 init_cb
|
||||
* (ui_content_page.c 的 open:s_perm=perm → s_open=desc → init_cb),
|
||||
* 所以这里读到的是"本次进入"的真值,不是上一次的残留
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_PLATE);
|
||||
if (ui_content_page_is_editable()) {
|
||||
ui_w_setview_attach_edit(&s_sl, UI_W_SETVIEW_PLATE); /* 整定:编辑 + 保存行 + 徽标 */
|
||||
} else {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_PLATE); /* 查看:与绑定前完全同一条路径 */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -60,26 +81,49 @@ static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自绘层(越界行反显;框架内置区块画完后调用)
|
||||
* 函数功能:自绘层(越界行反显 + 未标定徽标;框架内置区块画完后调用)
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链内
|
||||
* 函数说明:徽标函数自己判"是不是整定视图 + 帧在不在 + cal_block 置没置",
|
||||
* 页面这里无条件调它——四页少写四个 if,也就少四次写漏的机会;
|
||||
* 查看页三个条件第一条就不成立,一个像素都不落(vparam~vplate
|
||||
* 四张黄金参考仍逐位一致)
|
||||
*/
|
||||
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_w_setview_draw_cal_badge(a_layer, a_clip);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按键转发(框架 key_cb)
|
||||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:true - 本页已消费(框架跳过内置);false - 交回框架(↑↓ 移选、← 关页)
|
||||
* 限定条件:框架按键链内;描述符须 edit_capable=1 + key_intercept=1
|
||||
* 函数说明:两态行为的不同**不写在这里**——ui_w_setview_key() 先看视图类型
|
||||
* (查看页未绑 commit → 直接交回框架,等于本页没有 key_cb),
|
||||
* 再看参数帧在不在。所以这里必须原样透传返回值,不得就地"或"上什么
|
||||
*/
|
||||
static bool pg_key(lv_key_t a_key) {
|
||||
return (ui_w_setview_key(&s_sl, a_key) != 0u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_plate 绑定引用)
|
||||
* edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互,
|
||||
* ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在
|
||||
* 查看页等价于"根本没有 ENTER 这条路")
|
||||
* edit_capable=1 + key_cb + key_intercept=1:同一份描述符两态共用。
|
||||
* · 整定叶子(123 授权 UI_PERM_MODIFY)→ is_editable() 真 → attach_edit +
|
||||
* 编辑叠层 + 保存行;key_intercept=1 是**必需**的:编辑中的 ←→ 移位光标
|
||||
* 要先给页面,否则框架拿 ← 当"关页",编辑到一半就跳出去
|
||||
* · 查看叶子(无 #P,恒 UI_PERM_VIEW)→ is_editable() 假 → 仍走 attach,
|
||||
* ui_w_setview_key() 第一条门就把键全部交回框架,↑↓ 移选与 ← 关页全走内置
|
||||
* ——即"绑了 key_cb 的查看页"与"没有 key_cb 的查看页"行为逐字等价
|
||||
* (§3.2.7:"权限不足 ENTER 一律忽略")
|
||||
*/
|
||||
const content_page_def_t ui_page_view_plate_desc = {
|
||||
.id = "view_plate",
|
||||
.title = "软压板",
|
||||
.selectable = 1u,
|
||||
.edit_capable = 0u,
|
||||
.edit_capable = 1u, /* 两态页:能不能真编辑还要看本次进入的权限(is_editable) */
|
||||
.top_y = UI_LIST_TOP,
|
||||
.pitch = UI_ROW_PITCH,
|
||||
.band_h = UI_ROW_BAND_H,
|
||||
@@ -87,8 +131,10 @@ const content_page_def_t ui_page_view_plate_desc = {
|
||||
.left_x = UI_LIST_LEFT_X,
|
||||
.right_x = UI_LIST_RIGHT_X,
|
||||
.rows_per_page = UI_LIST_ROWS,
|
||||
.key_intercept = 1u, /* ← 先交 key_cb:编辑中是"移位光标",非编辑中才轮到关页 */
|
||||
.init_cb = pg_init,
|
||||
.count_cb = pg_count,
|
||||
.refresh = pg_refresh,
|
||||
.draw_cb = pg_draw,
|
||||
.key_cb = pg_key,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
* 模块名称:UI Page view_set (定值查看内容页)
|
||||
* 模块功能:参数定值 → 查看 → 定值 的内容页——18 行数值表(三段过流/过负荷/过压/低压/零序/过频/低频 定值与延时),每页 8 行、3 页;
|
||||
* 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显;
|
||||
* 本文件零 App include、零写路径(查看页没有"保存"这件事)
|
||||
* 本文件零 App include;写方向只有一个出口:整定态的"保存"经
|
||||
* ui_data_param_request() 投递(执行与落盘在 App 侧,本层不碰 prot_set/FatFs)
|
||||
* 两态一页(道F task-2:整定 4 叶绑定本描述符):同一份描述符既服务"查看"叶子
|
||||
* (只读——不绑 commit → 无保存行、按键全交回框架 → 像素与绑定前逐位一致),
|
||||
* 也服务"整定"叶子(123 授权 → 编辑叠层 + 保存行 + 未标定徽标)。
|
||||
* 分派点只有 pg_init 一处(按 ui_content_page_is_editable() 判),
|
||||
* 两态共用行表/取数/反显,不存在"查看一套边界、整定另一套边界"
|
||||
* 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型),
|
||||
* 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号
|
||||
* 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致
|
||||
@@ -11,6 +17,11 @@
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 3)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2:接整定态——pg_init 按权限分派
|
||||
* attach/attach_edit、新增 pg_key 转发按键、
|
||||
* pg_draw 追加"未标定"徽标、描述符补
|
||||
* edit_capable/key_intercept/key_cb 三项
|
||||
* (只读分支行为逐字不变)
|
||||
*/
|
||||
/* 头文件包含区 */
|
||||
#include "ui_page_view_set.h"
|
||||
@@ -32,11 +43,21 @@ static ui_w_setlist_t s_sl;
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内调用(uiTask)
|
||||
* 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 函数说明:1. 每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 2. **只读 / 整定两态在这里分派一次**,之后所有钩子只看已绑好的回调
|
||||
* (组件用 commit==NULL 判视图类型,不再回头看权限)
|
||||
* 3. 时序依据(为什么 init_cb 里问 is_editable() 拿到的就是本次进入的权限):
|
||||
* 框架 open() 先装 s_perm 与 s_open、最后才调 init_cb
|
||||
* (ui_content_page.c 的 open:s_perm=perm → s_open=desc → init_cb),
|
||||
* 所以这里读到的是"本次进入"的真值,不是上一次的残留
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_SET);
|
||||
if (ui_content_page_is_editable()) {
|
||||
ui_w_setview_attach_edit(&s_sl, UI_W_SETVIEW_SET); /* 整定:编辑 + 保存行 + 徽标 */
|
||||
} else {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_SET); /* 查看:与绑定前完全同一条路径 */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -60,26 +81,49 @@ static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自绘层(越界行反显;框架内置区块画完后调用)
|
||||
* 函数功能:自绘层(越界行反显 + 未标定徽标;框架内置区块画完后调用)
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链内
|
||||
* 函数说明:徽标函数自己判"是不是整定视图 + 帧在不在 + cal_block 置没置",
|
||||
* 页面这里无条件调它——四页少写四个 if,也就少四次写漏的机会;
|
||||
* 查看页三个条件第一条就不成立,一个像素都不落(vparam~vplate
|
||||
* 四张黄金参考仍逐位一致)
|
||||
*/
|
||||
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_w_setview_draw_cal_badge(a_layer, a_clip);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按键转发(框架 key_cb)
|
||||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:true - 本页已消费(框架跳过内置);false - 交回框架(↑↓ 移选、← 关页)
|
||||
* 限定条件:框架按键链内;描述符须 edit_capable=1 + key_intercept=1
|
||||
* 函数说明:两态行为的不同**不写在这里**——ui_w_setview_key() 先看视图类型
|
||||
* (查看页未绑 commit → 直接交回框架,等于本页没有 key_cb),
|
||||
* 再看参数帧在不在。所以这里必须原样透传返回值,不得就地"或"上什么
|
||||
*/
|
||||
static bool pg_key(lv_key_t a_key) {
|
||||
return (ui_w_setview_key(&s_sl, a_key) != 0u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_set 绑定引用)
|
||||
* edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互,
|
||||
* ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在
|
||||
* 查看页等价于"根本没有 ENTER 这条路")
|
||||
* edit_capable=1 + key_cb + key_intercept=1:同一份描述符两态共用。
|
||||
* · 整定叶子(123 授权 UI_PERM_MODIFY)→ is_editable() 真 → attach_edit +
|
||||
* 编辑叠层 + 保存行;key_intercept=1 是**必需**的:编辑中的 ←→ 移位光标
|
||||
* 要先给页面,否则框架拿 ← 当"关页",编辑到一半就跳出去
|
||||
* · 查看叶子(无 #P,恒 UI_PERM_VIEW)→ is_editable() 假 → 仍走 attach,
|
||||
* ui_w_setview_key() 第一条门就把键全部交回框架,↑↓ 移选与 ← 关页全走内置
|
||||
* ——即"绑了 key_cb 的查看页"与"没有 key_cb 的查看页"行为逐字等价
|
||||
* (§3.2.7:"权限不足 ENTER 一律忽略")
|
||||
*/
|
||||
const content_page_def_t ui_page_view_set_desc = {
|
||||
.id = "view_set",
|
||||
.title = "定值",
|
||||
.selectable = 1u,
|
||||
.edit_capable = 0u,
|
||||
.edit_capable = 1u, /* 两态页:能不能真编辑还要看本次进入的权限(is_editable) */
|
||||
.top_y = UI_LIST_TOP,
|
||||
.pitch = UI_ROW_PITCH,
|
||||
.band_h = UI_ROW_BAND_H,
|
||||
@@ -87,8 +131,10 @@ const content_page_def_t ui_page_view_set_desc = {
|
||||
.left_x = UI_LIST_LEFT_X,
|
||||
.right_x = UI_LIST_RIGHT_X,
|
||||
.rows_per_page = UI_LIST_ROWS,
|
||||
.key_intercept = 1u, /* ← 先交 key_cb:编辑中是"移位光标",非编辑中才轮到关页 */
|
||||
.init_cb = pg_init,
|
||||
.count_cb = pg_count,
|
||||
.refresh = pg_refresh,
|
||||
.draw_cb = pg_draw,
|
||||
.key_cb = pg_key,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
* 模块名称:UI Page view_word (控制字查看内容页)
|
||||
* 模块功能:参数定值 → 查看 → 控制字 的内容页——4 行二态表(*_TRIP:告警/跳闸),每页 8 行、1 页;
|
||||
* 数据 = 参数快照(Q10)按行表小数位换算,越界项由 bad 回调反显;
|
||||
* 本文件零 App include、零写路径(查看页没有"保存"这件事)
|
||||
* 本文件零 App include;写方向只有一个出口:整定态的"保存"经
|
||||
* ui_data_param_request() 投递(执行与落盘在 App 侧,本层不碰 prot_set/FatFs)
|
||||
* 两态一页(道F task-2:整定 4 叶绑定本描述符):同一份描述符既服务"查看"叶子
|
||||
* (只读——不绑 commit → 无保存行、按键全交回框架 → 像素与绑定前逐位一致),
|
||||
* 也服务"整定"叶子(123 授权 → 编辑叠层 + 保存行 + 未标定徽标)。
|
||||
* 分派点只有 pg_init 一处(按 ui_content_page_is_editable() 判),
|
||||
* 两态共用行表/取数/反显,不存在"查看一套边界、整定另一套边界"
|
||||
* 为什么这么薄:行表与取数在 ui_w_setview(四个查看页共用一份视图模型),
|
||||
* 渲染与反显在 ui_w_setlist(与整定页同一组件)——本文件只把视图编号
|
||||
* 和标准骨架几何令牌接进框架的跳板,改一次口径四处一致
|
||||
@@ -11,6 +17,11 @@
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 3)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2:接整定态——pg_init 按权限分派
|
||||
* attach/attach_edit、新增 pg_key 转发按键、
|
||||
* pg_draw 追加"未标定"徽标、描述符补
|
||||
* edit_capable/key_intercept/key_cb 三项
|
||||
* (只读分支行为逐字不变)
|
||||
*/
|
||||
/* 头文件包含区 */
|
||||
#include "ui_page_view_word.h"
|
||||
@@ -32,11 +43,21 @@ static ui_w_setlist_t s_sl;
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内调用(uiTask)
|
||||
* 函数说明:每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 函数说明:1. 每次开页都重新 attach——组件实例内残留的编辑态/游标会跨页带过来,
|
||||
* 开页复位比"信任上一次已经清干净"可靠
|
||||
* 2. **只读 / 整定两态在这里分派一次**,之后所有钩子只看已绑好的回调
|
||||
* (组件用 commit==NULL 判视图类型,不再回头看权限)
|
||||
* 3. 时序依据(为什么 init_cb 里问 is_editable() 拿到的就是本次进入的权限):
|
||||
* 框架 open() 先装 s_perm 与 s_open、最后才调 init_cb
|
||||
* (ui_content_page.c 的 open:s_perm=perm → s_open=desc → init_cb),
|
||||
* 所以这里读到的是"本次进入"的真值,不是上一次的残留
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_WORD);
|
||||
if (ui_content_page_is_editable()) {
|
||||
ui_w_setview_attach_edit(&s_sl, UI_W_SETVIEW_WORD); /* 整定:编辑 + 保存行 + 徽标 */
|
||||
} else {
|
||||
ui_w_setview_attach(&s_sl, UI_W_SETVIEW_WORD); /* 查看:与绑定前完全同一条路径 */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -60,26 +81,49 @@ static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自绘层(越界行反显;框架内置区块画完后调用)
|
||||
* 函数功能:自绘层(越界行反显 + 未标定徽标;框架内置区块画完后调用)
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链内
|
||||
* 函数说明:徽标函数自己判"是不是整定视图 + 帧在不在 + cal_block 置没置",
|
||||
* 页面这里无条件调它——四页少写四个 if,也就少四次写漏的机会;
|
||||
* 查看页三个条件第一条就不成立,一个像素都不落(vparam~vplate
|
||||
* 四张黄金参考仍逐位一致)
|
||||
*/
|
||||
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_w_setview_draw_cal_badge(a_layer, a_clip);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按键转发(框架 key_cb)
|
||||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:true - 本页已消费(框架跳过内置);false - 交回框架(↑↓ 移选、← 关页)
|
||||
* 限定条件:框架按键链内;描述符须 edit_capable=1 + key_intercept=1
|
||||
* 函数说明:两态行为的不同**不写在这里**——ui_w_setview_key() 先看视图类型
|
||||
* (查看页未绑 commit → 直接交回框架,等于本页没有 key_cb),
|
||||
* 再看参数帧在不在。所以这里必须原样透传返回值,不得就地"或"上什么
|
||||
*/
|
||||
static bool pg_key(lv_key_t a_key) {
|
||||
return (ui_w_setview_key(&s_sl, a_key) != 0u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=view_word 绑定引用)
|
||||
* edit_capable=0 + 无 key_cb + key_intercept=0:查看页不接受任何写交互,
|
||||
* ↑↓ 移选与 ← 关页全走框架内置(§3.2.7 表里"权限不足 ENTER 一律忽略"在
|
||||
* 查看页等价于"根本没有 ENTER 这条路")
|
||||
* edit_capable=1 + key_cb + key_intercept=1:同一份描述符两态共用。
|
||||
* · 整定叶子(123 授权 UI_PERM_MODIFY)→ is_editable() 真 → attach_edit +
|
||||
* 编辑叠层 + 保存行;key_intercept=1 是**必需**的:编辑中的 ←→ 移位光标
|
||||
* 要先给页面,否则框架拿 ← 当"关页",编辑到一半就跳出去
|
||||
* · 查看叶子(无 #P,恒 UI_PERM_VIEW)→ is_editable() 假 → 仍走 attach,
|
||||
* ui_w_setview_key() 第一条门就把键全部交回框架,↑↓ 移选与 ← 关页全走内置
|
||||
* ——即"绑了 key_cb 的查看页"与"没有 key_cb 的查看页"行为逐字等价
|
||||
* (§3.2.7:"权限不足 ENTER 一律忽略")
|
||||
*/
|
||||
const content_page_def_t ui_page_view_word_desc = {
|
||||
.id = "view_word",
|
||||
.title = "控制字",
|
||||
.selectable = 1u,
|
||||
.edit_capable = 0u,
|
||||
.edit_capable = 1u, /* 两态页:能不能真编辑还要看本次进入的权限(is_editable) */
|
||||
.top_y = UI_LIST_TOP,
|
||||
.pitch = UI_ROW_PITCH,
|
||||
.band_h = UI_ROW_BAND_H,
|
||||
@@ -87,8 +131,10 @@ const content_page_def_t ui_page_view_word_desc = {
|
||||
.left_x = UI_LIST_LEFT_X,
|
||||
.right_x = UI_LIST_RIGHT_X,
|
||||
.rows_per_page = UI_LIST_ROWS,
|
||||
.key_intercept = 1u, /* ← 先交 key_cb:编辑中是"移位光标",非编辑中才轮到关页 */
|
||||
.init_cb = pg_init,
|
||||
.count_cb = pg_count,
|
||||
.refresh = pg_refresh,
|
||||
.draw_cb = pg_draw,
|
||||
.key_cb = pg_key,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道B task-5 三组件之一)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2:① 保存行新增 LOCK 词 + 闭锁态
|
||||
* ENTER 不消费(禁编辑);② 二态行 ENTER **取模回绕**
|
||||
* (原实现落在最后一项时写出 opts 之外的下标:屏上
|
||||
* 看着没变、叠层已是越界值,保存必被预检挡回;
|
||||
* task-2 是本页第一个消费者,此前无人按过这条键);
|
||||
* 查看视图(commit/state 未绑)路径逐字未变
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -172,6 +178,11 @@ void ui_w_setlist_refresh(ui_w_setlist_t *a_inst, uint16_t a_row,
|
||||
case UI_W_SETLIST_ST_FAIL:
|
||||
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", "ERR");
|
||||
break;
|
||||
case UI_W_SETLIST_ST_LOCK:
|
||||
/* 定值闭锁:写不进去这件事必须写在保存行上(页脚/徽标都不算,
|
||||
* 用户按保存之前看到的就是这一格);词用 ASCII 与 ENTER/WAIT 同排 */
|
||||
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", "LOCK");
|
||||
break;
|
||||
default:
|
||||
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%s", "ENTER");
|
||||
break;
|
||||
@@ -211,6 +222,16 @@ uint8_t ui_w_setlist_key(ui_w_setlist_t *a_inst, lv_key_t a_key) {
|
||||
if (ui_content_page_is_editable() == false) {
|
||||
return 0u; /* 只读权限 ENTER 一律忽略(框架内置也无 ENTER 语义) */
|
||||
}
|
||||
/* 定值闭锁(state 回调回 LOCK)→ ENTER 一律不消费:不进数值编辑、不切二态、
|
||||
* 不投保存。权限门(上一条)说的是"这人不能写",这一条说的是"这台装置现在
|
||||
* 写不进"——两件事都必须落在键路上,只靠屏显词会让用户白改一轮。
|
||||
* ⚠ state() 回调可能带收口副作用(视图模型用它锁存落定结果),这里调它
|
||||
* 是幂等的:同一次落定只会被消费一次(见 ui_w_setview sv_state 的 s_pending)
|
||||
* ⚠ 已在编辑中的那一格不强行走这条路(编辑分支在上面已返回),其值只落在
|
||||
* RAM 工作副本,投递口已被本门堵住,落不了盘 */
|
||||
if ((a_inst->state != NULL) && (a_inst->state() == UI_W_SETLIST_ST_LOCK)) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
if (sel < a_inst->count) {
|
||||
const ui_w_setlist_row_t *r = &a_inst->rows[sel];
|
||||
@@ -222,14 +243,24 @@ uint8_t ui_w_setlist_key(ui_w_setlist_t *a_inst, lv_key_t a_key) {
|
||||
ui_content_page_invalidate();
|
||||
return 1u;
|
||||
}
|
||||
/* 二态行:循环切换(控制字 告警<->跳闸、软压板 投入<->退出) */
|
||||
/* 二态行:循环切换(控制字 告警<->跳闸、软压板 投入<->退出)
|
||||
* ⚠ **必须取模回绕**(道F task-2 实测坑):原写法 nxt = cur + 1 不回绕,
|
||||
* 落在最后一项时写出 opts 之外的下标 n——显示侧按 [0, n-1] 钳位,
|
||||
* 于是"按 ENTER 屏幕上什么也没变",而叠层里存的已是越界下标,
|
||||
* 随保存投出去就被 App 侧整批预检判成 ERR_RANGE:用户按了两下切换,
|
||||
* 结果是整页保存被"越界"挡回。本组件在 task-2 之前没有整定页消费者,
|
||||
* 这条只有接上写路径才会暴露(二态行 = 控制字 4 行 + 软压板 10 行,
|
||||
* 初始值全为"跳闸/投入"= 最后一项,所以是**必现**而不是边角)
|
||||
*/
|
||||
{
|
||||
int32_t cur = a_inst->get(r->id);
|
||||
uint8_t n = sl_opts_count(r->opts);
|
||||
int32_t nxt = cur + 1;
|
||||
int32_t nxt;
|
||||
|
||||
if ((cur < 0) || (cur >= (int32_t)n)) {
|
||||
nxt = 0;
|
||||
nxt = 0; /* 值区里是 opts 之外的脏值:回指第一项,不把脏值往下传 */
|
||||
} else {
|
||||
nxt = (int32_t)(((uint32_t)cur + 1u) % (uint32_t)n);
|
||||
}
|
||||
a_inst->set(r->id, nxt);
|
||||
ui_content_page_invalidate();
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
* §3.2 四视图表/§3.2.6 查看与整定差别/§3.2.7 编辑
|
||||
* 交互/§4.2 bind 要点;保存行 ENTER/WAIT/OK/ERR
|
||||
* 沿用 ui_page_net_cfg 交接显示先例)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2(整定 4 页):态枚举尾追加 LOCK
|
||||
* (值列显 LOCK + ENTER 不消费 = 闭锁禁编辑);
|
||||
* 查看视图不绑 commit/state,走不到新分支,
|
||||
* 四张查看页像素零变化(黄金用例 vparam~vplate 为证)
|
||||
*/
|
||||
#ifndef UI_W_SETLIST_H
|
||||
#define UI_W_SETLIST_H
|
||||
@@ -37,12 +41,20 @@ extern "C" {
|
||||
#include "ui_content_page.h" /* lvgl(lv_key_t/lv_layer_t)+ 框架类型 */
|
||||
#include "ui_w_editor.h"
|
||||
|
||||
/* 保存交接显示态(值列 ENTER/WAIT/OK/ERR,与 net_cfg 保存行同字面) */
|
||||
/* 保存交接显示态(值列 ENTER/WAIT/OK/ERR/LOCK,与 net_cfg 保存行同字面)
|
||||
* ⚠ 新增态一律**尾追加**:前四个值按序被各页与先例使用,中间插入会把后续态的
|
||||
* 数值整体挪位——数值挪位在 C 里不报错,只会让屏上的词换成另一个词 */
|
||||
typedef enum {
|
||||
UI_W_SETLIST_ST_IDLE = 0, /* 空闲:保存行显示 ENTER */
|
||||
UI_W_SETLIST_ST_WAIT, /* 请求已投递、属主任务处理中:WAIT */
|
||||
UI_W_SETLIST_ST_OK, /* 上轮提交成功:OK */
|
||||
UI_W_SETLIST_ST_FAIL /* 上轮提交失败:ERR */
|
||||
UI_W_SETLIST_ST_FAIL, /* 上轮提交失败:ERR */
|
||||
UI_W_SETLIST_ST_LOCK /* 定值闭锁(= par.locked,校验和自检失败):值列 LOCK
|
||||
* 且**禁编辑**(组件见 LOCK 即不消费 ENTER)。
|
||||
* ⚠ 与「未标定」是两件事、屏上两条通道:未标定
|
||||
* (= par.cal_block)仍可改定值、只是判据停走,
|
||||
* 用标题右侧徽标表达;LOCK 才是"写不进去"。
|
||||
* 三词三源 → docs/任务规划/数据可信.md §4.3 */
|
||||
} ui_w_setlist_st_t;
|
||||
|
||||
/* 行种类 */
|
||||
@@ -174,6 +186,11 @@ void ui_w_setlist_refresh(ui_w_setlist_t *a_inst, uint16_t a_row,
|
||||
* 整表提交口径见 页面补齐规划.md §3.2)
|
||||
* 3. 只读权限 ENTER 一律不消费;ESC 恒关页(框架,未提交编辑丢弃,
|
||||
* 已经 ENTER 提交到工作副本的调整保留、不落盘——net_cfg 先例)
|
||||
* 4. 整定视图 state()==LOCK(定值闭锁)→ ENTER 同样一律不消费:
|
||||
* 不进数值编辑、不切二态、不投保存——**为什么闭锁不由组件解释**,
|
||||
* 它只按 state 回调给的事实收住写交互(闭锁 vs 未标定的分工见
|
||||
* ui_w_setlist_st_t 的 LOCK 注);已在编辑中的那一格不强制打断,
|
||||
* 但其值只落在工作副本,投递口已被 LOCK 堵住,落不了盘
|
||||
*/
|
||||
uint8_t ui_w_setlist_key(ui_w_setlist_t *a_inst, lv_key_t a_key);
|
||||
|
||||
|
||||
+402
-31
@@ -1,12 +1,20 @@
|
||||
/*
|
||||
* 模块名称:UI 定值查看视图模型(ui_w_setview)
|
||||
* 模块功能:参数 / 定值 / 控制字 / 软压板四张视图表与它们的取数、换算、越界反显——
|
||||
* 供四个查看页薄跳板共用;声明与分层理由见 ui_w_setview.h
|
||||
* 四个查看页与四个整定页**共用同一批表**(attach = 只读,attach_edit =
|
||||
* 可写:另绑 set/commit/state + 编辑叠层);声明与分层理由见 ui_w_setview.h
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(共用源码)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-25
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 3)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2:整定侧落地——编辑叠层(改动位图 +
|
||||
* 工作值,只提交改动 id)、sv_commit 经
|
||||
* ui_data_param_request() 投递、sv_state 锁存落定
|
||||
* 并映射 LOCK、bad 回调加"本次提交挡下项"第二来源、
|
||||
* 统一按键门 ui_w_setview_key()、标题"未标定"徽标;
|
||||
* 新增 .bss ≈190 B(叠层 160 B + 位图/态),
|
||||
* 查看路径与四张行表逐字未变
|
||||
*/
|
||||
/* 头文件包含区 */
|
||||
#include <string.h>
|
||||
@@ -113,6 +121,21 @@ static const uint8_t s_dec[UI_DATA_SET_N] = {
|
||||
/* 无参数帧时值列的占位串(与全仓"读不到就 --"同一口径) */
|
||||
#define SV_NO_DATA "--"
|
||||
|
||||
/* 参数帧/落定结果轮询节拍(ms):见 sv_poll_cb 的"为什么必须有"。
|
||||
* 100 ms 是"用户看不出延迟"与"每拍一次比较"的折中,比 rt 帧的 200 ms 更细一档,
|
||||
* 这样保存落定与主站写值最迟 100 ms 上屏 */
|
||||
#define SV_POLL_MS 100u
|
||||
|
||||
/* 轮询定时器(LVGL 池对象,首张定值页 attach 时建、只建不删;NULL 初始化是
|
||||
* coding-standard CODE-RULE-15 对本层唯一文件作用域指针的要求) */
|
||||
static lv_timer_t *s_poll_timer = NULL;
|
||||
|
||||
/* 未标定徽标(整定页标题右侧):三字已在字库里(交流量页标定行、SOE type7 在用),
|
||||
* 故本次不新增字面字符;词与 par.cal_block 的屏上口径一致,别换成"标定失败"
|
||||
* 之类没登记过的说法(font-charset 与像素断言都按这个词钉) */
|
||||
#define SV_CAL_BADGE "未标定"
|
||||
#define SV_BADGE_RIGHT_X 141 /* 文字右边界:让开"浅 V"右腿(y7~17 占 x≈147~158) */
|
||||
|
||||
/* 参数帧缓存(四个查看页共用一份:同屏只开一页,无并发读者) */
|
||||
static ui_data_par_t s_par;
|
||||
static uint32_t s_par_seq = 0xFFFFFFFFu; /* 与任何合法发布序号都不同 */
|
||||
@@ -122,6 +145,22 @@ static uint8_t s_par_ok;
|
||||
static const ui_w_setlist_row_t *s_cur_rows;
|
||||
static uint16_t s_cur_n; /* 当前视图行数(bad 回调的边界) */
|
||||
|
||||
/*
|
||||
* 整定视图状态(道F task-2)
|
||||
* ⚠ 与 s_cur_rows 同一假设:**同屏只开一页**,故这些状态是模块级单份而非按实例存
|
||||
* (查看页四份缓存同一条理由)。将来要并排开两页,得连 s_cur_rows 一起收进实例
|
||||
*
|
||||
* RAM 台账:s_wq 160 B + 三个位图 24 B + 态与标志 ≈ **190 B .bss**
|
||||
*(落 RW_IRAM1;口径 docs/内存管理/RAM使用分析与优化方案.md,回报里已单独列给 Lead)
|
||||
*/
|
||||
static int32_t s_wq[UI_DATA_SET_N]; /* 工作值(工程量 × 10^dec,下标 = id) */
|
||||
static uint64_t s_wch; /* 改动位图:bit=1 → 该行本次要提交 */
|
||||
static uint64_t s_commit_mask; /* 在飞请求覆盖的位(落定后按它清叠层) */
|
||||
static uint64_t s_bad_mask; /* 本次提交被挡下的越界项(bad 回调第二个来源) */
|
||||
static ui_w_setlist_st_t s_disp; /* 保存行显示态(锁存,见 sv_state 的注) */
|
||||
static uint8_t s_pending; /* 1 = 已投递未落定 */
|
||||
static uint8_t s_edit; /* 1 = 当前 attach 是整定视图(徽标/按键门都看它) */
|
||||
|
||||
/* 行表长度(编译期算,别手写数字——手写数字与表长不一致是最难查的错) */
|
||||
#define SV_ROW_N(tbl) ((uint16_t)(sizeof(tbl) / sizeof((tbl)[0])))
|
||||
|
||||
@@ -162,6 +201,44 @@ static int32_t sv_q10_to_scaled(int32_t a_q10, uint8_t a_dec) {
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:显示值(工程量 × 10^dec)→ Q10 定点(整定提交方向,sv_q10_to_scaled 的逆)
|
||||
* 入口参数:a_scaled - 显示域整数值 int32_t
|
||||
* a_dec - 该行小数位 0..3 uint8_t
|
||||
* 返回值:Q10 定点值(工程量 × 1024) int32_t
|
||||
* 限定条件:a_dec <= 3;a_scaled 非负(组件编辑器契约 min>=0,本工程定值全非负)
|
||||
* 函数说明:q10 = (scaled×1024 + den/2) / den,四舍五入,**纯 32 位整数**——
|
||||
* 不用 float、更不用 __aeabi_ldiv(64 位除),理由与 sv_q10_to_scaled 同一条
|
||||
* (MicroLIB 代价,模拟器照不出来)。溢出界已核:最大显示值 PT一次额定
|
||||
* 500000(dec=0)→ 500000×1024 = 512,000,000 < int32 满
|
||||
* ⚠ 往返精度:这条换算与正向量程不是逐位可逆的(Q10 存 972 → 显示 95 →
|
||||
* 再写回 973,差 1 个 LSB)。所以**只提交改动位图内的 id**:用户没动的
|
||||
* 行根本不进这个函数,也就不会被"顺手改掉"(docs/UI设计/UI_数据供给设计
|
||||
* 说明.md §6.2 的 mask 语义;越界值不落盘这条判据也建在它上面)
|
||||
*/
|
||||
static int32_t sv_scaled_to_q10(int32_t a_scaled, uint8_t a_dec) {
|
||||
static const int32_t p10[4] = { 1, 10, 100, 1000 };
|
||||
int32_t den = p10[a_dec];
|
||||
|
||||
return (a_scaled * 1024 + den / 2) / den;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:判断两区域相交(与本目录各绘制文件同实现:static 局部)
|
||||
* 入口参数:a - 区域 1 const lv_area_t *
|
||||
* b - 区域 2 const lv_area_t *
|
||||
* 返回值:true = 相交 bool
|
||||
* 限定条件:无
|
||||
*/
|
||||
static bool sv_areas_intersect(const lv_area_t *a, const lv_area_t *b) {
|
||||
int32_t x1 = (a->x1 > b->x1) ? a->x1 : b->x1;
|
||||
int32_t y1 = (a->y1 > b->y1) ? a->y1 : b->y1;
|
||||
int32_t x2 = (a->x2 < b->x2) ? a->x2 : b->x2;
|
||||
int32_t y2 = (a->y2 < b->y2) ? a->y2 : b->y2;
|
||||
|
||||
return (x1 <= x2 && y1 <= y2);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读某定值项的显示值(组件 get 回调)
|
||||
* 入口参数:a_id - 行表登记的 id(= 枚举序) uint8_t
|
||||
@@ -191,6 +268,144 @@ static void sv_set_unused(uint8_t a_id, int32_t a_val) {
|
||||
(void)a_val;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读某定值项的**编辑态**显示值(整定视图的 get 回调)
|
||||
* 入口参数:a_id - 行表登记的 id(= prot_set 枚举序) uint8_t
|
||||
* 返回值:改过 → 叠层工作值;未改 → 参数帧换算值 int32_t
|
||||
* 限定条件:整定视图(attach_edit)专用;帧未就位时由页面值列打 "--",走不到这里
|
||||
* 函数说明:叠层优先——用户改到一半的数必须以叠层为准,否则每帧重绘都会把
|
||||
* 未提交的编辑冲回帧值("改完光标一动就跳回原值"就是这种实现写反了);
|
||||
* 未改的行反过来一律跟随帧,主站 C_SE 写进来的新值 ≤200 ms 自己上屏
|
||||
*/
|
||||
static int32_t sv_get_edit(uint8_t a_id) {
|
||||
if ((a_id < (uint8_t)UI_DATA_SET_N) && ((s_wch & ((uint64_t)1u << a_id)) != 0u)) {
|
||||
return s_wq[a_id];
|
||||
}
|
||||
return sv_get(a_id);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:写某定值项的编辑叠层(整定视图的 set 回调;不落任何数据源)
|
||||
* 入口参数:a_id - 行 id uint8_t
|
||||
* a_val - 新显示值(工程量 × 10^dec) int32_t
|
||||
* 返回值:无
|
||||
* 限定条件:整定视图专用;组件只在 ENTER 提交/二态切换时调
|
||||
* 函数说明:只置位不改帧——真正的写入发生在保存行 ENTER → sv_commit → 快照层队列
|
||||
* → App 侧 prot_set_set_sealed()。顺手清掉该行的"上次越界"反显:
|
||||
* 用户已经改过的行还挂着上一次失败的 redflag,是在说一件已经不成立的事
|
||||
*/
|
||||
static void sv_set_edit(uint8_t a_id, int32_t a_val) {
|
||||
if (a_id >= (uint8_t)UI_DATA_SET_N) {
|
||||
return;
|
||||
}
|
||||
s_wq[a_id] = a_val;
|
||||
s_wch |= ((uint64_t)1u << a_id);
|
||||
s_bad_mask &= ~((uint64_t)1u << a_id);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:提交本次改动(整定视图的 commit 回调——只投递,不执行、不等待)
|
||||
* 入口参数:无
|
||||
* 返回值:无(结果经 sv_state 回读,组件保存行显示)
|
||||
* 限定条件:uiTask 按键链内(保存行 ENTER);整定视图专用
|
||||
* 函数说明:请求形状 = 改动位图 + 按位换算的 Q10(ui_data_param_req_t,168 B 栈帧)。
|
||||
* 本函数**不**判断越界、不判闭锁、更不碰 FatFs:那三件事分别属于
|
||||
* prot_set 的 meta、prot_set 的自检态与 defaultTask(契约 §6.1)。
|
||||
* 队列满(-1)时叠层原样保留:用户直接再按一次保存即可,不必重输
|
||||
*/
|
||||
static void sv_commit(void) {
|
||||
ui_data_param_req_t req;
|
||||
uint64_t bit = 1u;
|
||||
uint8_t i;
|
||||
int ret;
|
||||
|
||||
(void)memset(&req, 0, sizeof(req));
|
||||
req.mask = s_wch;
|
||||
for (i = 0u; i < (uint8_t)UI_DATA_SET_N; i++) {
|
||||
if ((s_wch & bit) != 0u) {
|
||||
req.q10[i] = sv_scaled_to_q10(s_wq[i], s_dec[i]);
|
||||
}
|
||||
bit <<= 1u;
|
||||
}
|
||||
s_bad_mask = 0u; /* 新一轮提交:上一次的越界认定作废 */
|
||||
ret = ui_data_param_request(&req);
|
||||
if (ret == 0) {
|
||||
s_commit_mask = s_wch;
|
||||
s_pending = 1u;
|
||||
s_disp = UI_W_SETLIST_ST_WAIT;
|
||||
} else {
|
||||
s_pending = 0u;
|
||||
s_commit_mask = 0u;
|
||||
s_disp = UI_W_SETLIST_ST_FAIL; /* 忙/参数无效 → 页脚口径"忙,请重试" */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:回读保存行显示态(整定视图的 state 回调;顺带收口一次落定)
|
||||
* 入口参数:无
|
||||
* 返回值:ui_w_setlist_st_t(IDLE/WAIT/OK/FAIL/LOCK)
|
||||
* 限定条件:uiTask(组件绘制保存行时调用;ui_w_setlist_key 的闭锁门也调它)
|
||||
* 函数说明:为什么要把落定"锁存"进 s_disp 而不是每次直读快照层状态——
|
||||
* 快照层的 ui_data_param_state() 是**全局单值**,落定后一直停在那儿;
|
||||
* 直读会让用户移动一下光标(触发整页重绘)就看到别的页留下的旧结果,
|
||||
* 甚至把本次
|
||||
* OK 显示成 ENTER。这里改成事件锁存:本页投过一次才显示那次的答案,
|
||||
* 开页 attach_edit 复位成 ENTER。
|
||||
* 优先级(从高到低,逐条都有各自的"为什么"):
|
||||
* ① 帧里 locked=1 → LOCK:装置现在写不进去,比任何历史结果都该先说;
|
||||
* ② 在飞请求落定 → WAIT/OK/FAIL/LOCK(ERR_LOCKED 也映射成 LOCK:
|
||||
* 帧可能还没刷到 locked 位,但执行侧刚说过的"闭锁"是当下一手事实);
|
||||
* ③ 否则回显锁存值(ENTER/OK/ERR 等)
|
||||
*/
|
||||
static ui_w_setlist_st_t sv_state(void) {
|
||||
ui_w_setview_sync();
|
||||
|
||||
if ((s_par_ok != 0u) && (s_par.locked != 0u)) {
|
||||
return UI_W_SETLIST_ST_LOCK;
|
||||
}
|
||||
if (s_pending != 0u) {
|
||||
int p = ui_data_param_state();
|
||||
|
||||
if (p == UI_DATA_PARAM_ST_WAIT) {
|
||||
s_disp = UI_W_SETLIST_ST_WAIT;
|
||||
} else {
|
||||
s_pending = 0u; /* 一次落定只消费一次 */
|
||||
if (p == UI_DATA_PARAM_ST_OK) {
|
||||
s_wch &= ~s_commit_mask; /* 这批已进值区:叠层让位给新帧 */
|
||||
s_bad_mask = 0u;
|
||||
s_disp = UI_W_SETLIST_ST_OK;
|
||||
} else {
|
||||
s_bad_mask = (p == UI_DATA_PARAM_ST_ERR_RANGE)
|
||||
? ui_data_param_err_mask() : 0u;
|
||||
s_disp = (p == UI_DATA_PARAM_ST_ERR_LOCKED)
|
||||
? UI_W_SETLIST_ST_LOCK : UI_W_SETLIST_ST_FAIL;
|
||||
}
|
||||
s_commit_mask = 0u;
|
||||
}
|
||||
}
|
||||
return s_disp;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:复位整定侧状态(attach / attach_edit 开页时都调)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:uiTask(开页 attach 链内,先于组件 reset)
|
||||
* 函数说明:**每次开页都清稿**是刻意的:改动位图与工作值只在本页这一次打开里有意义,
|
||||
* 带进下一次打开会出现"没改任何东西却显示着上次的编辑值"(更糟的是下次
|
||||
* 保存会把那份陈旧草稿投出去)。ESC 关页天然弃稿(不投递),与本函数
|
||||
* 合起来正好是 net_cfg 那套"改到一半退出 = 什么都没发生"的语义
|
||||
*/
|
||||
static void sv_view_reset(void) {
|
||||
(void)memset(s_wq, 0, sizeof(s_wq));
|
||||
s_wch = 0u;
|
||||
s_commit_mask = 0u;
|
||||
s_bad_mask = 0u;
|
||||
s_disp = UI_W_SETLIST_ST_IDLE;
|
||||
s_pending = 0u;
|
||||
s_edit = 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:某数据行是否越界(组件 bad 回调 → 反显高亮)
|
||||
* 入口参数:a_row - 数据行下标(不含保存行) uint16_t
|
||||
@@ -205,7 +420,7 @@ 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)) {
|
||||
if (s_cur_rows == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (a_row >= s_cur_n) {
|
||||
@@ -216,9 +431,74 @@ static bool sv_bad(uint16_t a_row) {
|
||||
return false;
|
||||
}
|
||||
bit = (uint64_t)1u << r->id;
|
||||
/*
|
||||
* 两个来源,任一命中即反显(整定批次加了①,查看页②逐字不变):
|
||||
* ① s_bad_mask —— **本次提交**被 App 侧整批预检挡下的项(ui_data_param_err_mask()
|
||||
* 经 sv_state 收进来,只在 ERR_RANGE 那一轮非 0;用户再改该行、或重新提交
|
||||
* 即清)。这是"越界行高亮"判据的屏上落点:整批一字未写,但必须说清是哪几行
|
||||
* 把人挡住的,否则用户只能逐行猜。
|
||||
* ② s_par.bad_mask —— prot_set 整表装载/解析留下的越界位图,且必须 bad_valid=1
|
||||
* 才参与(bad_valid=0 时位图无意义,把"没数据"画成"这行有问题"是假告警)
|
||||
*/
|
||||
if ((s_bad_mask & bit) != 0u) {
|
||||
return true;
|
||||
}
|
||||
if ((s_par_ok == 0u) || (s_par.bad_valid == 0u)) {
|
||||
return false;
|
||||
}
|
||||
return (s_par.bad_mask & bit) != 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:参数帧/落定结果轮询(LVGL 定时器,100 ms 一拍)
|
||||
* 入口参数:a_timer - 定时器句柄(未使用) lv_timer_t *
|
||||
* 返回值:无
|
||||
* 限定条件:uiTask 上下文(LVGL 定时器在 lv_timer_handler 内跑,与页面同一线程)
|
||||
* 函数说明:**为什么必须有这条定时器**——LVGL 只重绘被 invalidate 的区域,而本页
|
||||
* 的数据是**异步**变的:
|
||||
* ① 参数帧由 App 侧节拍发布(保存落定后校验和变、主站 C_SE 写进来、
|
||||
* 标定完成让 cal_block 翻转),页面自己无从察觉;
|
||||
* ② 整定的落定结果更隐蔽:ERR_RANGE / ERR_LOCKED **不改校验和**,
|
||||
* 也就不会发布新帧——只有 ui_data_param_state() 变了。
|
||||
* 没有这条轮询,用户按完"保存"屏上会一直停在 WAIT,越界行的反显也要
|
||||
* 等下一次按键才出现(判据"越界行高亮"在真机上就落不了地)。
|
||||
* 代价:一拍一次 uint32 比较 + 一次 bool 判断,无变化时零重绘、零拷贝;
|
||||
* 页面没打开时直接返回(菜单屏/待机屏一像素都不动)。
|
||||
* 与 net_cfg 的差别:net_cfg 的保存行状态至今只在按键触发的重绘里更新
|
||||
* (先例的既有局限),本页不能沿用——整定的 OK/ERR/LOCK 是用户判断
|
||||
* "这台装置到底吃了我改的值没有"的唯一凭据
|
||||
*/
|
||||
static void sv_poll_cb(lv_timer_t *a_timer) {
|
||||
(void)a_timer;
|
||||
|
||||
if (ui_content_page_is_open() == false) {
|
||||
return;
|
||||
}
|
||||
if (ui_data_par_seq() != s_par_seq) {
|
||||
ui_content_page_invalidate(); /* 新参数帧:整页重绘(refresh 里同步序号) */
|
||||
return;
|
||||
}
|
||||
if ((s_pending != 0u) && (ui_data_param_state() != UI_DATA_PARAM_ST_WAIT)) {
|
||||
ui_content_page_invalidate(); /* 落定但不改校验和(ERR_RANGE/ERR_LOCKED) */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:确保轮询定时器已建(首张定值相关页面 attach 时建一次,之后不再建)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:uiTask(页面 open 链内)
|
||||
* 函数说明:只建不删(同 ui_standby_init 的 sb_idle_timer_cb 先例:模块生命周期 =
|
||||
* UI 生命周期);回调自带"没开页就返回"的门,停在菜单屏时它只是一次比较。
|
||||
* 建失败 = LVGL 池耗尽,此时页面退回"按键才重绘"的旧行为——不另外报错,
|
||||
* 因为那条错误在屏上没有任何可显示的通道(池耗尽本身早已是更大的问题)
|
||||
*/
|
||||
static void sv_poll_ensure(void) {
|
||||
if (s_poll_timer == NULL) {
|
||||
s_poll_timer = lv_timer_create(sv_poll_cb, SV_POLL_MS, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按发布序号取参数帧快照(接各查看页 refresh 前调用)
|
||||
* 入口参数:无
|
||||
@@ -348,34 +628,125 @@ void ui_w_setview_attach(ui_w_setlist_t *a_inst, ui_w_setview_t a_view) {
|
||||
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_setview_table()(本模块唯一的行表出口):attach 与 attach_edit
|
||||
* 共用同一张表,才会出现"查看合法、整定也合法"——两处各自 switch 就是两份口径 */
|
||||
a_inst->rows = ui_w_setview_table(a_view, &a_inst->count);
|
||||
s_cur_rows = a_inst->rows;
|
||||
s_cur_n = a_inst->count;
|
||||
a_inst->get = sv_get;
|
||||
a_inst->set = sv_set_unused;
|
||||
/* NULL = 查看视图:无保存行、禁编辑(§3.2.6);state 同 NULL → 保存行也没得显示 */
|
||||
a_inst->commit = NULL;
|
||||
a_inst->state = NULL;
|
||||
a_inst->bad = sv_bad;
|
||||
/* 整定侧状态一律复位:查看页既不该看见上一次的改动位图,也不该在下次开整定页
|
||||
* 时把它带过去(s_edit=0 同时关掉"未标定"徽标——查看页像素零变化的硬条件) */
|
||||
sv_view_reset();
|
||||
sv_poll_ensure(); /* 帧是异步变的:页面必须能自己发现新帧/落定(见 sv_poll_cb) */
|
||||
ui_w_setlist_reset(a_inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:把某个**整定**视图绑到定值表实例(行表与 attach 同一批,另绑写回调)
|
||||
* 入口参数:a_inst - 页面持有的实例(NULL 直接返回) ui_w_setlist_t *
|
||||
* a_view - 视图编号 ui_w_setview_t
|
||||
* 返回值:无
|
||||
* 限定条件:页面 init_cb 内、且 ui_content_page_is_editable() 为真时才调
|
||||
* (只读权限进来调这个等于给只读用户开了保存行)
|
||||
* 函数说明:与 attach 的差别只有 get/set/commit/state 四个钩子 + s_edit 标志,
|
||||
* 行表、行数、bad 回调、越界反显全部复用——**同一张行表**是这一版
|
||||
* 设计的关键:上下限的镜像只有一份,漂移由 setview-check 钩子拦
|
||||
*/
|
||||
void ui_w_setview_attach_edit(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));
|
||||
a_inst->rows = ui_w_setview_table(a_view, &a_inst->count);
|
||||
s_cur_rows = a_inst->rows;
|
||||
s_cur_n = a_inst->count;
|
||||
a_inst->get = sv_get_edit;
|
||||
a_inst->set = sv_set_edit;
|
||||
a_inst->commit = sv_commit; /* 非 NULL = 整定视图:组件画保存行并放开编辑 */
|
||||
a_inst->state = sv_state;
|
||||
a_inst->bad = sv_bad;
|
||||
sv_view_reset();
|
||||
s_edit = 1u;
|
||||
sv_poll_ensure(); /* 同 attach:整定页更要看到"落定"这一刻 */
|
||||
ui_w_setlist_reset(a_inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:两态统一按键转发(页面 key_cb 一行转过来)
|
||||
* 入口参数:a_inst - 已 attach 的实例 ui_w_setlist_t *
|
||||
* a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:1 - 本页已消费(框架跳过内置);0 - 交回框架(↑↓ 移选、← 关页)
|
||||
* (键分发标志非错误通道,取 uint8_t——BSP err-type 约定)
|
||||
* 限定条件:uiTask 按键链内(框架 key_cb;描述符须 edit_capable=1 + key_intercept=1)
|
||||
* 函数说明:两道门都在本函数里,四页共用一条规矩(写在页面里就会长出不一致的四个版本):
|
||||
* 1. 只读视图(commit==NULL)直接交回框架:查看页绑了 key_cb 也**没有
|
||||
* 任何行为变化**——这是"绑 key_cb 不改像素"的依据,vparam~vplate
|
||||
* 四张黄金参考仍逐位一致就是它的机器证明
|
||||
* 2. 参数帧未就位时不接受写交互:值列此刻是 "--",拿 "--" 进编辑器等于
|
||||
* 把"还不知道"当成 0 让人改,改完还能投出去("无数据不许打 0"的同一条
|
||||
* 诚实性纪律,见 ui_w_setview_refresh 的注释)
|
||||
* ⚠ 判"就位"之前必须先 ui_w_setview_sync():s_par_ok 原本**只在绘制路径**
|
||||
* 里被置位,而"画过没有"是 LVGL 的事、与按键不同步。模拟器无头回归
|
||||
* 两次注键之间根本不重绘 → 开页后的第一个 ENTER 永远被这道门挡掉;
|
||||
* 真机上开页 10 ms 内会重绘,于是这条坑只在测试侧咬人("代码没错、
|
||||
* 时序有错"最难查)。sync 在帧没变时只是一次 seq 比较,多调零代价
|
||||
*/
|
||||
uint8_t ui_w_setview_key(ui_w_setlist_t *a_inst, lv_key_t a_key) {
|
||||
if ((a_inst == NULL) || (a_inst->commit == NULL)) {
|
||||
return 0u;
|
||||
}
|
||||
ui_w_setview_sync();
|
||||
if (ui_w_setview_ready() == 0u) {
|
||||
return 0u;
|
||||
}
|
||||
return ui_w_setlist_key(a_inst, a_key);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:整定页标题右侧画"未标定"徽标(未标定闭锁的屏上表达)
|
||||
* 入口参数:a_layer - 绘制层 lv_layer_t *
|
||||
* a_clip - 重绘区 const lv_area_t *
|
||||
* 返回值:无
|
||||
* 限定条件:页面 draw_cb 内(框架内置区块之后);三条件全满足才落墨:整定视图 +
|
||||
* 帧已就位 + par.cal_block 非 0
|
||||
* 函数说明:几何为什么这样取:标题带只有 y=2..17 这一条,而"浅 V"右腿在 y7~17
|
||||
* 占掉 x≈147..158(ui_deco.c 的点集),所以徽标右边界取 141、左边界按
|
||||
* 实宽左推(3 个汉字 = 36 px → x≈105..141),既不压装饰也不压居中的标题
|
||||
* (最长标题"软压板/控制字" 3 字到 x≈98 为止)。字色取 UI_COL_FG,
|
||||
* 与标题同一极性;不做反显——反显在 1bpp 下表示"选中",徽标不是可选中项
|
||||
*/
|
||||
void ui_w_setview_draw_cal_badge(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||||
lv_area_t area;
|
||||
lv_draw_label_dsc_t ld;
|
||||
const char *txt = SV_CAL_BADGE;
|
||||
size_t len;
|
||||
|
||||
if ((s_edit == 0u) || (s_par_ok == 0u) || (s_par.cal_block == 0u)) {
|
||||
return;
|
||||
}
|
||||
len = strlen(txt);
|
||||
area.x2 = UI_SCREEN_W - 1;
|
||||
area.y1 = UI_TITLE_Y;
|
||||
area.y2 = UI_TITLE_Y + UI_TEXT_BAND_H;
|
||||
/* 右对齐:先用与框架标题同一套测宽(lv_text_get_width + strlen,
|
||||
* 这里 length 必须传 strlen——该版本传 -1/0 要么死循环要么返回 0) */
|
||||
area.x1 = SV_BADGE_RIGHT_X
|
||||
- (int32_t)lv_text_get_width(txt, (uint32_t)len, LV_FONT_DEFAULT, 0);
|
||||
|
||||
if (!sv_areas_intersect(&area, a_clip)) {
|
||||
return;
|
||||
}
|
||||
lv_draw_label_dsc_init(&ld);
|
||||
ld.base.layer = a_layer;
|
||||
ld.text = txt;
|
||||
ld.text_static = 1u; /* Flash 字面量:不拷内存(与框架标题同法) */
|
||||
ld.color = UI_COL_FG;
|
||||
ld.ofs_x = 0;
|
||||
ld.ofs_y = 0;
|
||||
lv_draw_label(a_layer, &ld, &area);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
* 模块名称:UI 定值查看视图模型(ui_w_setview)
|
||||
* 模块功能:参数 / 定值 / 控制字 / 软压板**四张视图表**与它们的取数、越界反显——
|
||||
* 把"40 项定值怎么分组上屏、Q10 怎么换算成带小数位的显示值"收在一处,
|
||||
* 供四个查看页薄跳板(ui_page_view_*)共用;整定页(下一批)复用同一批表,
|
||||
* 只是再绑 set/commit/state 三个回调
|
||||
* 供四个查看页薄跳板(ui_page_view_*)共用;**同一批表**再经
|
||||
* ui_w_setview_attach_edit() 供整定页复用(道F task-2 已落:查看与整定
|
||||
* 只差绑不绑 set/commit/state)。行表只有一份,界内界外就不会两套口径
|
||||
* 写方向:整定页的"保存"经 Drivers/BSP/UI/ui_data.h 的 param_request 投递
|
||||
* (位图 + Q10 批量请求,队列深度 4),执行与落盘在 App 侧——本模块
|
||||
* 仍然零 App include:越界与否、写不写得进去都由快照层回读
|
||||
* 分层:本模块只读 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 的主机单测
|
||||
@@ -17,6 +21,10 @@
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 王建锋 创建(道E task-15 批 3;规格 =
|
||||
* docs/UI设计/页面扩充详细方案.md §3.2.1~§3.2.3/§3.2.6)
|
||||
* v1.1 2026-10-26 DTU 项目组 道F task-2:新增整定侧三件事——attach_edit
|
||||
* (改动位图叠层 + 只提交改动 id + 保存态锁存)、
|
||||
* key 转发(两态统一门)、标题"未标定"徽标;
|
||||
* 四张行表与查看路径逐字未变(像素零变化)
|
||||
*/
|
||||
#ifndef UI_W_SETVIEW_H
|
||||
#define UI_W_SETVIEW_H
|
||||
@@ -52,6 +60,58 @@ typedef enum {
|
||||
*/
|
||||
void ui_w_setview_attach(ui_w_setlist_t *a_inst, ui_w_setview_t a_view);
|
||||
|
||||
/*
|
||||
* 函数功能:把一个**整定**视图绑到定值表实例(行表与 attach 同一批,另绑
|
||||
* set/commit/state 三个回调 —— 组件据此画保存行并放开编辑)
|
||||
* 入口参数:a_inst - 页面持有的实例 ui_w_setlist_t *
|
||||
* a_view - 视图编号 ui_w_setview_t
|
||||
* 返回值:无
|
||||
* 限定条件:页面 init_cb 内调用,且**只在 ui_content_page_is_editable() 为真时**调
|
||||
* (只读权限走 attach,本函数根本不该被绑上)
|
||||
* 函数说明:整定与查看的唯一差别就是绑不绑这三个回调(§3.2.6 口径)。三个新增点:
|
||||
* 1. 编辑叠层:改动位图 s_wch + 工作值表 s_wq(工程量 × 10^dec),
|
||||
* get 优先读叠层、未改的行跟随参数帧 —— 用户改到一半退出页面即弃稿
|
||||
* (ESC 关页不投递,同 net_cfg);开页 attach 一律清稿,不带上次的改动
|
||||
* 2. 提交只投**改动位图内的 id**(绝不从显示值回提全表):Q10 存不下
|
||||
* 0.95 这类返回系数(真存 972/1024),按显示域往返会把用户没动的项
|
||||
* 顺手改掉——"越界值不落盘"与"改哪条只落哪条"两条判据都靠这个 mask
|
||||
* 3. 保存行显示态由 sv_state() 锁存(见 .c 内 s_disp 的注),
|
||||
* 闭锁 → LOCK(组件据此禁 ENTER),未标定 → 标题右侧徽标(下面那个)
|
||||
*/
|
||||
void ui_w_setview_attach_edit(ui_w_setlist_t *a_inst, ui_w_setview_t a_view);
|
||||
|
||||
/*
|
||||
* 函数功能:整定/查看两态统一的按键转发(页面 key_cb 一行转过来即可)
|
||||
* 入口参数:a_inst - 已 attach 的实例 ui_w_setlist_t *
|
||||
* a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:1 - 本页已消费(框架跳过内置);0 - 交回框架(↑↓ 移选、← 关页)
|
||||
* (键分发标志非错误通道,取 uint8_t——BSP 公开 API 的 err-type 约定)
|
||||
* 限定条件:uiTask 按键链内(框架 key_cb)
|
||||
* 函数说明:门在这里而不是散在四个页面文件里,为的是"四页同一条规矩":
|
||||
* 1. 只读视图(未绑 commit)一律交回框架 —— 查看页按键行为逐字不变,
|
||||
* 这是"绑了 key_cb 也不会动像素"的依据(黄金用例 vparam~vplate 仍绿)
|
||||
* 2. 参数帧未就位时**不接受任何写交互**:值列此刻打的是 "--",
|
||||
* 拿 "--" 去 begin 编辑等于把"还不知道"当成 0 让人改,改完还能投出去
|
||||
*/
|
||||
uint8_t ui_w_setview_key(ui_w_setlist_t *a_inst, lv_key_t a_key);
|
||||
|
||||
/*
|
||||
* 函数功能:整定页标题右侧画"未标定"徽标(未标定闭锁的屏上表达)
|
||||
* 入口参数:a_layer - 绘制层 lv_layer_t *
|
||||
* a_clip - 重绘区 const lv_area_t *
|
||||
* 返回值:无
|
||||
* 限定条件:页面 draw_cb 内调用(框架内置区块之后);三个条件全满足才落墨:
|
||||
* 当前是整定视图(attach_edit)+ 参数帧已就位 + par.cal_block 非 0
|
||||
* 函数说明:为什么值得占标题带一个角:整定页的存在就是"让用户改保护定值",
|
||||
* 而产品构建下 UA/UN/IA 未标定 → 判据恒停走 → **改完也不会动作**。
|
||||
* 不写这件事,页面就是在暗示"改了就算数"(UI补齐.md §4 末段"保护闭锁
|
||||
* 必须可见";口径 docs/任务规划/数据可信.md §4.3)。
|
||||
* ⚠ 与保存行的 LOCK 是两件事:LOCK = 定值闭锁(写不进去、禁编辑),
|
||||
* 徽标 = 未标定(仍可改,只是判据停走)——合成一个词就等于互相冒充。
|
||||
* ⚠ 查看页不画(s_edit=0):那四页是既有黄金参考,像素零变化是硬约束
|
||||
*/
|
||||
void ui_w_setview_draw_cal_badge(lv_layer_t *a_layer, const lv_area_t *a_clip);
|
||||
|
||||
/*
|
||||
* 函数功能:按发布序号取参数帧快照(页面 refresh 前调用)
|
||||
* 入口参数:无
|
||||
|
||||
Reference in New Issue
Block a user