Files
STM32F4-Base/Drivers/BSP/UI/ui_w_soe.c
T
wandering1 201d713226 feat(ui-data): ui_data 三通道快照 + ui_data_feed 装配 + SOE 显示名映射
按 docs/UI设计/UI_数据供给设计说明.md 的 D 方案把 App→UI 数据通道落地:
App 组帧、BSP 存帧、页面只读;UI 侧零 App include,薄包装只在两端适配层。
本笔是纯增层、不接任何页面 → 零像素变化(菜单 8 状态 + 内容页 11 页黄金
参考逐字节不变,simulator/tests/ref 无 diff,未刷基线)。

新增
- Drivers/BSP/UI/ui_data.{c,h}:实时量 rt / 参数 par / 装置信息 info 三通道存储
  + 发布序号盖章 + 只读访问。零 App include、零 LVGL include(分层叶子);
  读写同在 uiTask 上下文串行 → 无锁、无双缓冲、无 seqlock(契约 §5.1)。
- App/app/ui_data_feed.{c,h}:快照唯一填充者。rt/info 每 200 ms 一帧,par 只在
  prot_set_checksum() 变化时重发;首轮立即发布,免启动初期闪一下 "--"。
- Drivers/BSP/UI/ui_w_soe.{c,h}:SOE 二进制记录的显示名映射与注入式 reader
  契约。未标定闭锁=7 / 标定复归=8 已进表——编号按 App/log/soe_rec.h 与
  App/algo/prot_elm.h 现场数过(类型 1..8 + 哨兵 9、元件 0..8、ELM_NONE=0xFF、
  FLG_IV=0x80),不采信文档与消息转述。reader 备好但两端未绑定:落盘侧还
  没有只读 API(Lead 已裁该 API 归下一工作项),未注入按"无记录"空态渲染。

量纲诚实性(契约 §4.1 同步改成真值口径,不再抄旧码值表述)
- eng 不写"0 = 原始码值":标定层 ROM 系数是引脚侧 LSB,采样环折算出来的就是
  ADC 引脚电压(承诺边界 docs/任务规划/数据可信.md §2.6);电气 8 路
  adc_cal_flags() 的 VALID 全置位才升 eng=1 二次值——整帧共用一个 eng,只要
  还有一路是引脚电压就不能贴"二次值"标签,宁可整体保守。
- TEMP 不参与线性标定 → 恒带新增品质位 UI_DATA_Q_RAW=0x20,页面不挂单位。
- UB/UC/IB/IC/IN 无基波相量实例(V0.1 削顶)→ Q_IV|Q_OV;未满一周波 → Q_NT。
- 谐波另开 ph[] 相量实例槽带:yc 的语义通道下标容不下同通道的二次/五次谐波。

接线与双端注册
- App/task/ui_task.c:ui_menu_init 之前 ui_data_init + 首帧发布;主循环每轮
  ui_data_feed_tick() 排在 ui_port_step 之前(本轮渲染用本轮刚发布的帧)。
- simulator/main.c:发布固定合成帧(无头与窗口两个初始化点,先于建页);
  定值不手抄 40 个数,改为链入 App/algo/prot_set.c 取出厂默认(抄表等于造
  第二真相);build_date 用固定串而非 __DATE__,否则跨天重建会把装置信息页
  黄金参考刷掉,而黄金参考是判据本身。
- 注册:uvprojx 加 3 条(App 组 ui_data_feed.c + UI 组 ui_data.c/ui_w_soe.c)与
  simulator/CMakeLists.txt;ui_data_feed.c 不进模拟器 → check_registration.py 的
  SIM_EXEMPT 加一行(App/app/app_main.c 先例,已报备)。

主机单测与钩子
- test/host/ui_data:82 断言全绿。钉三类事:快照语义(首帧前读 -1、序号由本层
  盖章、三通道互不串台、NULL 防御、init 幂等复位);跨层不透明下标现场一致
  (yc 序 = adc_semantic_t、q10 序 = prot_set_id_t,同一 TU 里 include 两侧头);
  SOE 编号与名字表现场一致(八类事件都有名、采样/标定四个词互不相同、
  名长不超列宽、越界回占位)。
- .pre-commit-config.yaml 加 ui-data-unit-test 钩子(第 43 个,Lead 已批自加)。
- scripts/id_registry.json 建族 TC-HOST-UIDATA(属主登记处 = 单测目录,同
  TC-HOST-ADCCAL 先例;不建族则新号被编号门禁判"未登记前缀")。

体积(相对 HEAD 台账 Code 215744 / RO 21396 / RW 1224 / ZI 131984)
- Code +1272 B、RO +108 B(字库 4 字,见上一笔)、RW +24 B、ZI +696 B
  → ER_IROM1 238520 B(22.7%)、RW_IRAM1 76504 B(58.4%,台账 57.8%)。
- .map 逐项:ui_data.o 488 B(Code 184/RW 12/ZI 476)、ui_data_feed.o 221 B
  (1034/12/209);LVGL 池 0 增量;ui_w_soe 未接线不进镜像(.map 无其符号)。
- 台架未跑(本笔零像素、零接线):首帧时刻、200 ms 刷新、eng 升档与遥信
  位图真值全部留"待台架实测",接线完成后请 Lead 排窗。

遗留与风险(随回报进台账)
- 写路径(契约 §6 的 param_request/state)不落:无消费者就是永不执行的死码,
  随整定 4 页一起落;届时须先定"UI 整定 vs iec104Task 写 prot_set"的并发口径
  (prot_set_set_sealed 的影子区非重入)。
- 公共地址 CA 未下发:固件里 CA 逐帧从 ASDU 头解析,无集中常量可取。
- 遥信位图与 App/protocol/iec104/iec104_pts.c 板端源是两处镜像(该文件位号
  写在函数体内未导出,UI 不得 include 协议头),开入采集落地时应并成单源。
- 版本类字段是本文件内的宏(仓库无产品级版本登记表,值沿用页面既有显示
  值);软件校验位在拿到构建期 CRC 步骤前一律显示 "--",不抄参考遗留值。

gate: 全量钩子(--all-files)40 项通过;cppcheck/test-cppcheck/coding-standard/
layer-deps(基线仍 3 条)/registration/bsp-err(存量 38 条不变)/font-charset/
id-check(增量无新账)/doc-links/ui-golden/主机单测与覆盖率全绿;唯一红项是
mem-numbers 与 mem-budget——两者只因 Flash/RAM 台账未回填(禁区,归 Lead)。

gate: only mem-budget/mem-numbers pending lead ledger backfill

自查:BOM(Drivers/BSP/UI/* 与 App/task/ui_task.c 保持 EF BB BF,新建 UI 两件
同规范;App/app 与 test/host 无 BOM 与本目录一致);CR = 0 全 LF;U+FFFD = 0;
字符串字面量纯 ASCII(中文只进注释)。
2026-10-25 09:56:22 +08:00

197 lines
7.3 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 SOE 记录页共享组件实现(ui_w_soe)
* 模块功能:见 Drivers/BSP/UI/ui_w_soe.h——事件类型/元件显示名映射 + 每记录 2 行
* 渲染 + 单条读盘缓存;数据经注入 reader 取,未注入按空态渲染
* 适用范围:Drivers/BSP/UI
* 说明:与 ui_w_records.c 同构(同一份"分页读记录 → 两行/条 + 实例缓存 + 空态
* 伪行"骨架),差别只在记录形态是二进制解码后的视图结构
* 作者:王建锋
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 王建锋 创建(道E task-14)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_w_soe.h"
/* 私有宏定义区 */
/*
* 框架文本缓冲宽度(真源 Drivers/BSP/UI/ui_tokens.h 的 UI_BUF_STRLEN = 32)。
* 这里**有意不 include ui_tokens.h**:该头经 ui_port.h 牵入整条 LVGL 头链,而本组件
* 是纯数据 + 纯文本搬运、一个 LVGL 调用都没有——容量宏本地化之后,
* test/host/ui_data 可以直接把本 .c 编进主机单测核对显示名映射(同
* Drivers/BSP/UI/ui_w_records.h 把 ts/desc 容量宏本地化的同一先例与理由:
* 容量不足只会退化成占位显示,不会越界读)。框架令牌若加宽,本宏同步加宽。
*/
#define SOE_BUF_MAX 32u
#define SOE_INVALID_STR "--" /* 未知编号/越界/读失败的统一占位(ASCII) */
#define SOE_EMPTY_STR "无记录" /* 空态伪行(与文本记录页同字) */
#define SOE_NO_CLOCK_STR "时钟无效" /* 时标行值列:IV 位不显示猜测时间 */
/* 静态变量定义区 */
/* 注入的 SOE reader(模块级单例:SOE 落盘本就是全局一份,两端各只有一份实现;
* NULL = 未注入 → 空态。当前两端均未绑定,理由见头注"说明") */
static const ui_soe_reader_t *s_reader;
/*
* 事件类型显示名(下标 = UI_W_SOE_T_*;表长 = UI_W_SOE_T_COUNT)
* 词长分两档是**版式约束**不是措辞偏好:值列可用宽约 59 px(4 汉字),
* 名列约 94 px(7 汉字)——"动作/返回"是元件行的值列词,其余四类 4 字词
* 落在非元件事件的名列(口径 docs/UI设计/页面扩充详细方案.md §2.3 第 4 条)
*/
static const char *const s_type_name[UI_W_SOE_T_COUNT] = {
SOE_INVALID_STR, /* 0 未知/未映射(含读失败) */
"动作", /* 1 元件动作 */
"返回", /* 2 元件返回 */
"压板退出", /* 3 软压板退出 */
"压板投入", /* 4 软压板投入 */
"采样闭锁", /* 5 采样故障闭锁 */
"采样恢复", /* 6 采样故障恢复 */
"标定闭锁", /* 7 未标定闭锁 */
"标定复归", /* 8 标定复归 */
};
/* 元件显示名(下标 = UI 元件编号 = 保护元件枚举序;越界不入表) */
static const char *const s_elm_name[UI_W_SOE_ELM_N] = {
"过流Ⅰ段", /* 0 */
"过流Ⅱ段", /* 1 */
"过流Ⅲ段", /* 2 */
"过负荷", /* 3 */
"过压", /* 4 */
"欠压", /* 5 */
"零序过压", /* 6 */
"过频", /* 7 */
"低频", /* 8 */
};
/* 函数定义区 */
/*
* 函数功能:注入 SOE reader
*/
void ui_w_soe_bind(const ui_soe_reader_t *a_reader) {
s_reader = a_reader;
}
/*
* 函数功能:把定长源串写入框架文本缓冲
* 入口参数:a_dst - 框架给的名/值缓冲(宽 = SOE_BUF_MAX,与框架 UI_BUF_STRLEN 同值) char *
* a_src - 源串(Flash 字面量或实例缓存) const char *
* src_bytes - 源缓冲总宽(含 NUL,整块拷入以带上终止符) size_t
* 返回值:无
* 限定条件:两者非 NULL;src_bytes 超 SOE_BUF_MAX 即按上限截断拷贝
* 函数说明:不用 strcpy/sprintf(门禁 CODE-RULE-06 禁用不安全函数);本组件
* 只做"整块搬运",不做格式化——格式化属页面行表(表现层)的事
*/
static void soe_put(char *a_dst, const char *a_src, size_t src_bytes) {
if ((a_dst == NULL) || (a_src == NULL)) {
return;
}
if (src_bytes > SOE_BUF_MAX) {
src_bytes = SOE_BUF_MAX;
}
(void)memcpy(a_dst, a_src, src_bytes);
}
/*
* 函数功能:事件类型编号 → 显示名
*/
const char *ui_w_soe_type_name(uint8_t a_type) {
if (a_type >= UI_W_SOE_T_COUNT) {
return SOE_INVALID_STR;
}
return s_type_name[a_type];
}
/*
* 函数功能:元件编号 → 显示名
*/
const char *ui_w_soe_elm_name(uint8_t a_elm) {
if (a_elm >= UI_W_SOE_ELM_N) {
return SOE_INVALID_STR;
}
return s_elm_name[a_elm];
}
/*
* 函数功能:总行数(每条记录 2 行;空态 = 单条伪行"无记录")
*/
uint16_t ui_w_soe_count(ui_w_soe_t *a_inst) {
uint32_t n;
(void)a_inst;
n = (s_reader != NULL) ? (uint32_t)s_reader->count() : 0u;
if (n == 0u) {
return 1u; /* 空态伪行(页脚"共 1 页",避免"共 0 页") */
}
return (uint16_t)(n * 2u);
}
/*
* 函数功能:逐行取数(偶数行 = 时标,奇数行 = 元件 + 事件类型)
*/
void ui_w_soe_refresh(ui_w_soe_t *a_inst, uint16_t a_row, char *a_name, char *a_value) {
uint32_t n;
uint32_t rec;
uint8_t ok;
a_value[0] = '\0';
n = (s_reader != NULL) ? (uint32_t)s_reader->count() : 0u;
if (n == 0u) {
(void)memcpy(a_name, SOE_EMPTY_STR, sizeof(SOE_EMPTY_STR));
return;
}
rec = (uint32_t)a_row / 2u;
ok = 0u;
if (a_inst->cache_valid != 0u && rec == a_inst->last_rec) {
ok = 1u; /* 同一条记录的两行共用一次读盘 */
} else if (s_reader != NULL) {
if ((s_reader->get)(rec, &a_inst->rec) == 0) {
a_inst->last_rec = rec;
a_inst->cache_valid = 1u;
ok = 1u;
} else {
a_inst->cache_valid = 0u; /* 越界/读失败:本行退化为占位,不留脏缓存 */
}
}
if (ok == 0u) {
(void)memcpy(a_name, SOE_INVALID_STR, sizeof(SOE_INVALID_STR));
return;
}
if (((uint16_t)a_row % 2u) == 0u) {
/* 时标行:IV 位或未对时 → 名列 "--"、值列"时钟无效",绝不显示猜测时间 */
if ((a_inst->rec.flags & UI_W_SOE_FLG_IV) != 0u || a_inst->rec.ts[0] == '\0') {
(void)memcpy(a_name, SOE_INVALID_STR, sizeof(SOE_INVALID_STR));
(void)memcpy(a_value, SOE_NO_CLOCK_STR, sizeof(SOE_NO_CLOCK_STR));
} else {
soe_put(a_name, a_inst->rec.ts, sizeof(a_inst->rec.ts));
}
return;
}
/* 内容行:元件事件 = 元件名 + 动作/返回;非元件事件 = 类型名(名列) */
if (a_inst->rec.elm != UI_W_SOE_ELM_NONE) {
const char *elm = ui_w_soe_elm_name(a_inst->rec.elm);
soe_put(a_name, elm, strlen(elm) + 1u);
/* 只有动作/返回两类是元件级事件;其余编号(理论不该出现)仍给类型名 */
if (a_inst->rec.type == UI_W_SOE_T_ELM_ASSERT
|| a_inst->rec.type == UI_W_SOE_T_ELM_RETURN) {
soe_put(a_value, ui_w_soe_type_name(a_inst->rec.type),
strlen(ui_w_soe_type_name(a_inst->rec.type)) + 1u);
}
} else {
const char *tn = ui_w_soe_type_name(a_inst->rec.type);
soe_put(a_name, tn, strlen(tn) + 1u);
}
}