Files
wandering1 94b0d79026 feat(ui)+chore(docs): UI 补齐清单清空;数据可信销项与「卡」号清退
本笔含两组收尾工作(同一工作区内交错在 6 个共享文档/门禁配置里,
拆开会产生"文档声称的页数与树里黄金数不一致"的中间提交,故合为一笔):

一、UI 补齐(清单清空)
- 新建 6 个内容页模块:ui_page_accident / oper / alarm / start / yk
  复用 ui_w_records 共享组件(分页、空态"无记录"、按实例读盘缓存),
  记录层与五个写入点已由道F task-10 / 道G task-12 落地,本批只补页面;
  ui_page_runreport 为 13 行 2 页只读统计,四个数逐个挂范围标签
  (自电池周期 / 本次开机),boot_ready=0 显 "--" 不显 0。
- 数据面:ui_data_info_t 尾部追加 9 字段,ui_data_feed 用
  boot_stats_get() + iec104_serv_get_stats() 纯搬运;模拟器填确定值假数据
  (差异登记 docs/测试说明/模拟器差距清单.md §2.3 两行)。
- 接线:菜单树 6 叶绑 #page=,绑定叶 22→28、UI_PAGE_MAP_COUNT=28;
  字库 185→218 字 / 280→313 字形;双端注册 uvprojx +30、CMakeLists +6。
- 回归:内容页黄金 53→60(含 runreport_p2 真拍第 2 页),文本断言
  425 正向 / 36 反向;存量 53 张逐字节零漂移;记录 5 页各用独立空目录
  (模拟器把操作留痕写进 cwd 的 sim_oper.log,共用 tmp 会让空态拍不到)。

二、数据可信销项 + 「数据可信」任务卡号整族清退
- 删除已闭环的"已知电压注入验证"行与闭环结论块(证据在
  docs/硬件平台/ADC通道映射说明.md §8.1 与
  docs/测试说明/ADC前端注入验证流程.md §5~§7),§5 标识列改语义短语。
- 卡 0/0b/1~12 共 14 个号全仓 288 站点 / 56 文件清退成语义短语,
  代码注释、测试、门禁配置与文档一并改;换算表入 docs/编号规范.md §9.3。
  这一族是中文前缀,不进 check_ids 语料,刻意不入 id_aliases.json。

验证(实测)
- @build 0 错 0 警:Code=236456 RO=28124 RW=1876 ZI=137084;
  RAM +464 B(RW +304 / ZI +160),RW_IRAM1 62.2% / RW_CCM 87.9%,
  体积同码凭证与 RAM 台账已回填。
- check_menu_rows 8 状态 rc=0(池 5368/5448 常量未动);
  check_content_pages 60 页 rc=0;check_font_charset / registration /
  coding-standard / bom-drift / mojibake / doc-links --all / layer-deps /
  nested-comment 全 rc=0;check_ids --strict 无错误;主机单测
  ui_data 295 / boot_stats 21 / sys_log_mcu 141 / soe 32 / soe_store 27。

未做(不是遗漏)
- 六页真机判据(真记录翻页、掉电保持、清除后归零、boot_count 自增、
  拔 RTC 电池落 RESTARTED、IWDG 复位落 rst_cause、主站连一次 conn_up+1)
  已登记 docs/任务规划/登记簿.md §7;模拟器结构上照不到。
- docs/产品需求/产品需求设计文档.md 属另一条在飞改动,本笔不含;
  其中我已同步 6 处现状数字(28 叶 / 60 页 / 218 字),随该笔入库。
2026-10-28 07:23:16 +08:00

394 lines
16 KiB
C
Raw Permalink 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.
/*
* 模块名称:运行参数段(run_par)主机侧单元测试
* 模块功能:离线钉死「数据可信」运行参数段的三层契约——① [run_par] cfg 段:
* 注册/工厂默认/落盘往返/文件改值生效(真实 cfg 引擎 + 内存虚拟盘,
* 同 adc_cal 套件先例);② 104 地址映射:命中集/空隙/两段式/分档
* 量程守卫;③ k/b 覆盖直通 adc_cal_set(真实标定层):合法带拒绝、
* 单侧 RMW 保另一侧、回显 = 生效值视图。
* 被测对象:真实源文件(#include 纳入):run_par.c + cfg_file.c + adc_cal.c;
* dbg 计数桩(stub/dbg_log.h)+ cfg_store_* 内存虚拟盘 + adc_input_desc
* V0.1 权威映射桩。**路由顺序证据(run_par 表先 / prot_set 后)在
* test/host/iec104 TC-IEC104-029 钉(那边才有全协议栈)**。
*
* 覆盖点(TC-RUNPAR-xxx;属主登记处 = 本目录,族 TC-HOST-RUNPAR):
* TC-RUNPAR-001 init 工厂默认 = 工程量重定后的额定档 + idx 守卫
* TC-RUNPAR-002 死区写往返 + 分档上限(V/A/Hz)+ NaN/负值拒 + 定点量化
* TC-RUNPAR-003 落盘链:set→脏→store_save 成功清脏 + 文件含 9 键 +
* load 回读往返 + 外部改文件值生效 + 无脏 save 空操作
* TC-RUNPAR-004 104 读映射:0x8030~0x8038 命中 / 空隙与邻段 -1 / k·b 回显
* TC-RUNPAR-005 104 两段式:选择段不提交不置脏,执行段提交置脏
* TC-RUNPAR-006 k/b 直通:合法带接受与拒绝 / 非正 k 拒 / b 单侧 RMW 保 k /
* 非法带值经 load 注入后回显 = 生效值(哨兵视图)
* TC-RUNPAR-007 init 幂等:重复调用复位默认 + 清脏;重注册被引擎拒(留痕)
* TC-RUNPAR-008 引擎副本 = 生效值(无回调):load 原地改副本即时可见
*
* 通过:全部 TC PASS(main 返回 0)。
* 创建日期:2026-10-25(道D 运行参数段)
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 先桩头后生产源码(顺序敏感,同 adc_cal 套件) */
#include "dbg_log.h"
/* ===================== 内存虚拟文件系统(cfg_store_* 强符号) ===================== */
#include "cfg_file_store.h"
#define VFS_CAP 8192u
static uint8_t s_vfs[VFS_CAP];
static size_t s_vfs_len = 0u;
static uint32_t s_vfs_mtime = 100u;
static int s_vfs_exists = 0;
static int s_vfs_writes = 0;
void *cfg_store_alloc(size_t size) { return malloc(size); }
void cfg_store_free(void *p_buf) { free(p_buf); }
uint32_t cfg_store_heap_free(void) { return 0u; }
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
size_t n;
(void)p_path;
if (!s_vfs_exists) {
return -1;
}
n = s_vfs_len;
if (n > cap) {
n = cap;
}
(void)memcpy(p_buf, s_vfs, n);
*p_nread = n;
return 0;
}
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
(void)p_path;
if (len > VFS_CAP) {
return -1;
}
(void)memcpy(s_vfs, p_buf, len);
s_vfs_len = len;
s_vfs_exists = 1;
s_vfs_mtime++;
s_vfs_writes++;
return 0;
}
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
(void)p_path;
if (!s_vfs_exists) {
return -1;
}
p_st->size = (uint32_t)s_vfs_len;
p_st->mtime = s_vfs_mtime;
return 0;
}
int cfg_store_remove(const char *p_path) {
(void)p_path;
s_vfs_exists = 0;
s_vfs_len = 0u;
return 0;
}
const char *cfg_store_config_path(void) { return "/config.ini"; }
const char *cfg_store_tmp_path(void) { return "/config.tmp"; }
/* ---- sys_log 桩 ---- */
#include "sys_log.h"
static int s_syslog_count = 0;
void syslog_post(syslog_id_t id, const char *desc) {
(void)id;
(void)desc;
s_syslog_count++;
}
/* ---- adc_input_desc 桩:V0.1 权威映射(docs/硬件平台/ADC通道映射说明.md) ---- */
#include "adc_input.h"
static const adc_channel_desc_t s_desc_stub[ADC_SEM_COUNT] = {
{ ADC_SRC_TPAFE, 6u }, /* UA = buf[6] */
{ ADC_SRC_STM32, 14u }, /* UB = CH14(PC4) */
{ ADC_SRC_STM32, 3u }, /* UC = CH3 */
{ ADC_SRC_TPAFE, 5u }, /* UN = buf[5] */
{ ADC_SRC_TPAFE, 2u }, /* IA = buf[2] */
{ ADC_SRC_STM32, 6u }, /* IB = CH6 */
{ ADC_SRC_STM32, 1u }, /* IC = CH1 */
{ ADC_SRC_STM32, 2u }, /* IN = CH2 */
{ ADC_SRC_STM32, 5u }, /* TEMP = CH5 */
};
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) {
if ((int)ch < 0 || (unsigned)ch >= (unsigned)ADC_SEM_COUNT) {
return NULL;
}
return &s_desc_stub[(unsigned)ch];
}
/* dbg 计数定义(stub/dbg_log.h extern 声明) */
int g_rptest_err_count = 0;
int g_rptest_info_count = 0;
int g_rptest_debug_count = 0;
/* ===================== 测试框架(极简,同 adc_cal 套件) ===================== */
static int s_total = 0, s_passed = 0, s_failed = 0;
#define CHECK(cond, name) do { \
s_total++; \
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while (0)
#define ASSERT_NEAR(a, b, eps, name) CHECK(fabsf((float)(a) - (float)(b)) <= (eps), name)
/* ===================== 被测代码 ===================== */
#include "cfg_file.c"
#include "adc_cal.c"
#include "run_par.c"
static void vfs_reset(void) {
s_vfs_len = 0u;
s_vfs_mtime = 100u;
s_vfs_exists = 0;
}
/* 全复位:引擎段复位 + run_par 复位 + 标定复位(重注册被拒 = 预期,清计数) */
static void reset_all_rp(void) {
run_par_init();
adc_cal_init();
g_rptest_err_count = 0;
}
/* ===================== 测试用例 ===================== */
static void tc_defaults(void)
{
uint8_t i;
vfs_reset();
reset_all_rp();
for (i = 0u; i < 4u; i++) {
CHECK(fabsf(run_par_deadband(i) - 0.5f) <= 1e-6f, "TC-RUNPAR-001 voltage ch factory 0.5 V");
}
for (i = 4u; i < 8u; i++) {
CHECK(fabsf(run_par_deadband(i) - 0.005f) <= 1e-9f, "TC-RUNPAR-001 current ch factory 0.005 A");
}
CHECK(fabsf(run_par_deadband(8u) - 0.5f) <= 1e-6f, "TC-RUNPAR-001 freq factory 0.5 Hz");
CHECK(run_par_deadband(9u) == 0.0f, "TC-RUNPAR-001 idx guard returns zero");
CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-001 init leaves clean");
}
static void tc_set_range(void)
{
volatile float fnan;
vfs_reset();
reset_all_rp();
CHECK((run_par_deadband_set(0u, 0.7f) == 0)
&& (fabsf(run_par_deadband(0u) - 0.7f) <= 1e-6f),
"TC-RUNPAR-002 write round trip voltage domain");
CHECK((run_par_deadband_set(4u, 0.02f) == 0)
&& (fabsf(run_par_deadband(4u) - 0.02f) <= 1e-6f),
"TC-RUNPAR-002 write round trip current domain");
CHECK(run_par_deadband_set(0u, 50.0f) == 0, "TC-RUNPAR-002 voltage cap endpoint inclusive");
CHECK((run_par_deadband_set(0u, 50.001f) == -1)
&& (fabsf(run_par_deadband(0u) - 50.0f) <= 1e-6f),
"TC-RUNPAR-002 over voltage cap rejected");
CHECK(run_par_deadband_set(4u, 1.001f) == -1, "TC-RUNPAR-002 current cap 1 A enforced");
CHECK(run_par_deadband_set(8u, 5.001f) == -1, "TC-RUNPAR-002 freq cap 5 Hz enforced");
CHECK(run_par_deadband_set(0u, -0.001f) == -1, "TC-RUNPAR-002 negative rejected");
fnan = 0.0f;
fnan = fnan / fnan;
CHECK(run_par_deadband_set(0u, fnan) == -1, "TC-RUNPAR-002 NaN rejected");
CHECK(run_par_deadband_set(9u, 1.0f) == -1, "TC-RUNPAR-002 idx guard rejected");
CHECK((run_par_deadband_set(2u, 0.1234f) == 0)
&& (fabsf(run_par_deadband(2u) - 0.123f) <= 1e-6f),
"TC-RUNPAR-002 milli quantization rounds to nearest");
}
static void tc_persist(void)
{
char probe[24];
vfs_reset();
reset_all_rp();
CHECK(run_par_store_save() == 0, "TC-RUNPAR-003 clean save is no-op ok");
CHECK(run_par_deadband_set(2u, 0.9f) == 0, "TC-RUNPAR-003 seed write ok");
CHECK(run_par_dirty_peek() == 1u, "TC-RUNPAR-003 write marks dirty");
CHECK((run_par_store_save() == 0) && (run_par_dirty_peek() == 0u),
"TC-RUNPAR-003 save clears dirty on success");
(void)memset(probe, 0, sizeof(probe));
(void)memcpy(probe, "db_uc=", 6u);
CHECK((s_vfs_exists == 1) && (strstr((const char *)s_vfs, "db_uc=900") != NULL),
"TC-RUNPAR-003 file carries milli value");
CHECK(strstr((const char *)s_vfs, "db_freq=500") != NULL,
"TC-RUNPAR-003 all nine keys present (freq spot check)");
/* 往返:改内存 → 落盘 → 引擎 load 回读 */
CHECK(run_par_deadband_set(4u, 0.02f) == 0, "TC-RUNPAR-003 second seed");
(void)run_par_store_save();
run_par_deadband_set(4u, 0.5f); /* 覆盖内存态 */
CHECK(cfg_file_load() == 0, "TC-RUNPAR-003 engine reload ok");
CHECK(fabsf(run_par_deadband(4u) - 0.02f) <= 1e-9f,
"TC-RUNPAR-003 reload restores last saved value");
/* set(4,0.5) 是一次未落盘的本地整定 → 脏必须活过 reload(reload 不是
* 清脏事件;只有 save 成功才清) */
CHECK(run_par_dirty_peek() == 1u, "TC-RUNPAR-003 unsaved local edit survives reload");
/* 外部改档(FTP 手改 config.ini 的现场形态):load 后副本即生效值 */
{
static const char s_edited[] =
"[run_par]\ndb_ua=250\ndb_ub=500\ndb_uc=500\ndb_un=500\n"
"db_ia=10\ndb_ib=5\ndb_ic=5\ndb_in=5\ndb_freq=800\n";
(void)memcpy(s_vfs, s_edited, sizeof(s_edited) - 1u);
s_vfs_len = sizeof(s_edited) - 1u;
s_vfs_exists = 1;
s_vfs_mtime++;
}
CHECK(cfg_file_load() == 0, "TC-RUNPAR-003 edited file accepted");
CHECK((fabsf(run_par_deadband(0u) - 0.25f) <= 1e-6f)
&& (fabsf(run_par_deadband(4u) - 0.01f) <= 1e-9f)
&& (fabsf(run_par_deadband(8u) - 0.8f) <= 1e-6f),
"TC-RUNPAR-003 edited values live in copy immediately");
/* 越界文件值:引擎照收(int32 域),104/COV 读到超程值 —— 守卫在**写路径**,
* 文件通路的防线是"人工编辑 + 沉降去抖 + 可再改回";与 adc_cal 合法带不同,
* 死区超程不毁量纲(只是上送变钝),此处钉住"引擎不吞值"即可 */
CHECK(fabsf(run_par_deadband(0u) - 0.25f) <= 1e-6f, "TC-RUNPAR-003 value stable re-read");
}
static void tc_104_read(void)
{
float v;
vfs_reset();
reset_all_rp();
(void)run_par_deadband_set(0u, 0.6f);
CHECK((run_par_104_read(0x8030u, &v) == 0) && (fabsf(v - 0.6f) <= 1e-6f),
"TC-RUNPAR-004 db first slot hit");
CHECK((run_par_104_read(0x8038u, &v) == 0) && (fabsf(v - 0.5f) <= 1e-6f),
"TC-RUNPAR-004 db last slot (freq) hit");
CHECK(run_par_104_read(0x8039u, &v) == -1, "TC-RUNPAR-004 gap address not owned");
CHECK(run_par_104_read(0x8027u, &v) == -1, "TC-RUNPAR-004 prot_set alarm addr untouched");
CHECK(run_par_104_read(0x802Fu, &v) == -1, "TC-RUNPAR-004 below base not claimed");
CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 152.587890625f) <= 1e-4f),
"TC-RUNPAR-004 k echo = adc_cal effective UA");
CHECK((run_par_104_read(0x8047u, &v) == 0) && (fabsf(v - 805.6640625f) <= 1e-4f),
"TC-RUNPAR-004 k echo last channel (IN, STM32 ROM)");
CHECK((run_par_104_read(0x8048u, &v) == 0) && (v == 0.0f),
"TC-RUNPAR-004 b echo factory zero");
CHECK(run_par_104_read(0x8050u, &v) == -1, "TC-RUNPAR-004 above b block not owned");
}
static void tc_104_two_step(void)
{
float v;
vfs_reset();
reset_all_rp();
CHECK((run_par_104_write(0x8031u, 0.8f, 1u) == 0)
&& (fabsf(run_par_deadband(1u) - 0.5f) <= 1e-6f)
&& (run_par_dirty_peek() == 0u),
"TC-RUNPAR-005 select validates without commit or dirty");
CHECK((run_par_104_write(0x8031u, 0.8f, 0u) == 0)
&& (fabsf(run_par_deadband(1u) - 0.8f) <= 1e-6f)
&& (run_par_dirty_peek() == 1u),
"TC-RUNPAR-005 execute commits and marks dirty");
CHECK(run_par_104_write(0x8031u, 60.0f, 1u) == -1, "TC-RUNPAR-005 range reject in select");
CHECK(run_par_104_write(0x8039u, 1.0f, 0u) == -2, "TC-RUNPAR-005 foreign addr passes through");
CHECK(run_par_104_write(0x8027u, 1.0f, 0u) == -2, "TC-RUNPAR-005 alarm addr passes through");
CHECK((run_par_104_read(0x8031u, &v) == 0) && (fabsf(v - 0.8f) <= 1e-6f),
"TC-RUNPAR-005 committed value readable at same addr");
}
static void tc_104_cal_proxy(void)
{
float v;
vfs_reset();
reset_all_rp();
CHECK(run_par_104_write(0x8040u, 300.0f, 0u) == 0, "TC-RUNPAR-006 k in-band accepted");
CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 300.0f) <= 0.01f),
"TC-RUNPAR-006 k echo follows adc_cal_set");
CHECK((run_par_104_write(0x8040u, 400.0f, 0u) == -1)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f),
"TC-RUNPAR-006 k over band rejected by adc_cal, RAM intact");
CHECK((run_par_104_write(0x8040u, 70.0f, 0u) == -1),
"TC-RUNPAR-006 k under band rejected (0.5x ROM)");
CHECK(run_par_104_write(0x8040u, -1.0f, 0u) == -1, "TC-RUNPAR-006 non-positive k rejected");
CHECK((run_par_104_write(0x8048u, -5.0f, 0u) == 0)
&& (fabsf(adc_cal_b_codes(ADC_SEM_UA) + 5.0f) <= 1e-3f)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f),
"TC-RUNPAR-006 b single-side RMW keeps k");
CHECK(run_par_104_write(0x8048u, -9000.0f, 0u) == -1, "TC-RUNPAR-006 b over +-8192 rejected");
CHECK((run_par_104_write(0x8040u, 200.0f, 1u) == 0)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f),
"TC-RUNPAR-006 k select does not touch adc_cal");
/* 生效值视图回显:文件塞越带 k → 哨兵回退 ROM → 104 回显 ROM */
{
static const char s_badk[] =
"[adc_cal]\nk_ua=99011483\nvalid=1\n";
(void)memcpy(s_vfs, s_badk, sizeof(s_badk) - 1u);
s_vfs_len = sizeof(s_badk) - 1u;
s_vfs_exists = 1;
s_vfs_mtime++;
}
CHECK(cfg_file_load() == 0, "TC-RUNPAR-006 poisoned file loaded by engine");
CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 152.587890625f) <= 1e-4f),
"TC-RUNPAR-006 echo = effective (ROM fallback), not raw file value");
reset_all_rp();
CHECK(cfg_file_save() == 0, "TC-RUNPAR-006 reset file rewritten");
}
static void tc_init_idempotent(void)
{
int err_before;
vfs_reset();
reset_all_rp();
(void)run_par_deadband_set(1u, 0.9f);
(void)run_par_104_write(0x8040u, 200.0f, 0u);
err_before = g_rptest_err_count;
run_par_init();
CHECK(fabsf(run_par_deadband(1u) - 0.5f) <= 1e-6f, "TC-RUNPAR-007 re-init restores defaults");
CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-007 re-init clears dirty");
CHECK(g_rptest_err_count > err_before, "TC-RUNPAR-007 dup register leaves trace");
CHECK(fabsf(run_par_deadband(0u) - 0.5f) <= 1e-6f, "TC-RUNPAR-007 values intact after dup reg");
}
static void tc_copy_is_effective(void)
{
static const char s_file[] =
"[run_par]\ndb_ua=650\ndb_ub=500\ndb_uc=500\ndb_un=500\n"
"db_ia=5\ndb_ib=5\ndb_ic=5\ndb_in=5\ndb_freq=500\n";
vfs_reset();
reset_all_rp();
(void)memcpy(s_vfs, s_file, sizeof(s_file) - 1u);
s_vfs_len = sizeof(s_file) - 1u;
s_vfs_exists = 1;
s_vfs_mtime++;
CHECK(cfg_file_load() == 0, "TC-RUNPAR-008 load ok");
CHECK(fabsf(run_par_deadband(0u) - 0.65f) <= 1e-6f,
"TC-RUNPAR-008 no cache layer: copy read is effective value");
CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-008 load itself is not a local edit");
}
int main(void)
{
printf("=== RunPar Layer Host Tests ===\n");
tc_defaults();
tc_set_range();
tc_persist();
tc_104_read();
tc_104_two_step();
tc_104_cal_proxy();
tc_init_idempotent();
tc_copy_is_effective();
printf("=== RunPar Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}