feat(UI/采集/104): 通讯设置页 + 开机账本 + adc_cal 可诊断化——三车道同树一次构建入库

三条道在同一棵树上交付,故合并成**一次权威构建**(@build 0 错 0 警,Code=235236 RO=26308 RW=1572
ZI=136924)后同笔入库;台账已按该构建回填(`ram_baseline.py --update` + `--write`、`chec
k_mem_numbers.py
 --write`),凭证与实际 .map 同源。归属如下:

· **通讯设置页**(道G task-9):新增 `Drivers/BSP/UI/ui_page_comm.{c,h}`(通道六注入,与死
区页同骨架同纪
  律),`#page=comm` 绑定使 bound **21 → 22**、内容页黄金 **47 → 53**(旧 47 页字节零变化,
新增六态含
  WAIT/OK 两张);`iec104_comm` 脏位 + store_save(**无事件位**)挂 defaultTask 同点位
轮询;`port_104` 由
  宏改走 getter、`IEC104_SERV_PORT` 删除;字体 +听/监(185 字全覆)。**实机链已闭环**:FTP 外改
[comm]
  的 ca/port → 复位 → 串口出现 `section 'comm' (2 keys)` 与 `i104: listening o
n 9004` → 还原 2404,全程无
  FAIL ⇒「重启即生效 + 端口真值」不是推断是实测。上限核对 6/8 段、2/16 键,超限现象由 TC-CONFIG-056/057
  钉死(注册 -1 + `comm init: FAIL` 锚 + getter 回 ROM),**不抬宏**。
· **开机账本**(道F task-12):新增 `App/util/boot_stats.{c,h}`——上电次数落 SD2506 用户
 SRAM(magic +
  小端计数 + 版本 + 异或校验),一次上电 = 1 读 + ≤1 写,**零 NAND、零卷锁、零配置段**;四态互斥
  UNKNOWN / FRESH / CONTINUED / RESTARTED(BLF=1 才算电池域掉过电),**读失败绝不回写**;
文案口径「自 RTC
  电池周期起」,**禁写终身次数**。复位原因优先级链从 `app_main.c` 的 if-else 收进一份真源(串口与屏幕同
  源);104 四个只增不清计数 + `iec104_serv_get_stats()` 留在 serv、未塞进 link(会被 open
/close 洗掉)。
  运行时长按用户裁定 = **只报本次开机以来**,零新增代码。新套件 `test/host/boot_stats` 21/21,五处注入
  **全部有牙**(含「漂一字节被校验和挡下而不是报 256 倍错账」)。
· **adc_cal 可诊断化**(道E task-43):三个 volatile 计数 + `adc_cal_get_stats()`
/ `adc_cal_note_persisted()`
  (水位**盖写非自增**,因为一次 save 覆盖整档,数 save 会留假未落盘);「差额 × 脏位」三态语义唯一落在
  `adc_cal.h`,其中「差额 > 0 且脏位恒 0」= 绕过语义 setter 的静默丢失;TC-ADCCAL-019~023。

三条**必须留档的自纠**(都是执行道自己报的,我不替他们润色):
① stdint 那条断链是**宿主套件抓到、通讯道先修**,采集道承认自己是重复添加者并删掉自己那份;
② 「exit 2 不是证据,断言变红才是」——采集道第一版反证注入了未声明标识符导致编译错,差点当成功;
③ 套件汇总行原写 `s_total/s_total`,失败时会打印「21/21 PASSED (failed=1)」这种自相矛盾的行,已改。

另修:`check_registration.py` 的 `SIM_EXEMPT` 加一行豁免 `App/util/boot_stats.c
`(依赖 I2C 与 RCC 位掩码,
MCU 专属;核过 `sys_clock.c`/`sha256.c` 同形态先例),注册反向校验 exit 1 → 0。发号 TC-IEC1
04-034、
TC-UIDATA-029 已实抬游标(`TC-IEC104-033→034`、`TC-UIDATA-026→029`)。

门禁:id-check --strict 与 plain 均 rc=0、doc-links --all rc=0、注册校验 0、cppche
ck / coding-standard /
bom-drift 0;iec104 179/179、ui_data 295/295、boot_stats 21/21、adc_cal 11
7/117、run_par 67/67;菜单 8 状态
池 5368 不变、内容页 53/53。**已知遗留**:内容页数 47 → 53 的全仓文本同步尚未做(另卡跟踪),本笔只保
证代码与黄金一致;面板注键真机那一腿缺 CLI,交台架道补。
This commit is contained in:
2026-10-27 11:29:23 +08:00
parent 0ae9c10f24
commit 728d60675f
47 changed files with 2364 additions and 123 deletions
+102
View File
@@ -76,6 +76,8 @@
* TC-UIDATA-026 注入后转发言行:epoch 0 哨兵不下发、reset_req 自行投操作记录=0 次
* TC-UIDATA-027 遥测整定通道未注入防御:八个口一律安全返回、不碰出参;
* 残表整表拒绝且不覆盖好表(与 025 同一条纪律的第五张表复刻)
* TC-UIDATA-029 通讯参数通道六(道G task-9):未注入防御 + 注入后转发
* (行现状逐字段 / 带外拒不置脏 / 脏位零缓存跟随 / 撤销脱离)
* TC-UIDATA-028 遥测整定注入后转发言行:行现状逐字段原样上抛、写口的 idx 与
* 工程量原样下传、返回码直通、pending 无状态直读(本层零缓存——
* 页面 OK/WAIT 的唯一依据必须是执行侧此刻的脏位)
@@ -1480,6 +1482,103 @@ static void tc_yt_forwarding(void)
CHECK(s_yt_pending_calls == 2, "TC-UIDATA-028q unbind stops reaching executor entirely");
}
/* ── 道G task-9 通道六:通讯参数注入(027/028 的第五张表复刻到第六张表)──
* 编号 029 = 向 Lead 申请发号(cursor_owner_only),cursor 现 026。
* 假件语义 = simulator/main.c 同形(带内写副本、带外拒、写成功置脏)。 */
static uint32_t s_cf_val[2] = { 1u, 2404u };
static uint32_t s_cf_lo[2] = { 1u, 1024u };
static uint32_t s_cf_hi[2] = { 65535u, 65535u };
static uint8_t s_cf_dirty;
static uint8_t s_cf_last_idx = 0xFFu;
static uint32_t s_cf_last_val;
static int s_cf_calls;
static int cf_stub_row(uint8_t a_idx, ui_data_comm_row_t *a_out)
{
s_cf_calls++;
if (a_idx >= UI_DATA_COMM_ROW_N) {
return -1;
}
a_out->val = s_cf_val[a_idx];
a_out->lo = s_cf_lo[a_idx];
a_out->hi = s_cf_hi[a_idx];
return 0;
}
static int cf_stub_set(uint8_t a_idx, uint32_t a_val)
{
s_cf_calls++;
s_cf_last_idx = a_idx;
s_cf_last_val = a_val;
if ((a_idx >= UI_DATA_COMM_ROW_N) || (a_val < s_cf_lo[a_idx]) ||
(a_val > s_cf_hi[a_idx])) {
return -1;
}
s_cf_val[a_idx] = a_val;
s_cf_dirty = 1u;
return 0;
}
static uint8_t cf_stub_pending(void)
{
s_cf_calls++;
return s_cf_dirty;
}
static const ui_data_comm_ops_t s_cf_stub = {
cf_stub_row, cf_stub_set, cf_stub_pending
};
/* 残表:只缺 pending——三口里最容易被"顺手少注一件"漏掉的那个(027l 同法) */
static const ui_data_comm_ops_t s_cf_broken = {
cf_stub_row, cf_stub_set, NULL
};
/* TC-UIDATA-029a-e 未注入防御:三个口一律安全返回、不碰出参、不假报脏 */
static void tc_comm_ch6_not_bound(void)
{
ui_data_comm_row_t r;
const uint32_t sentinel = 0xA5A5A5A5uL;
(void)ui_data_comm_bind(NULL);
r.val = sentinel;
CHECK(ui_data_comm_row(0u, &r) == -1, "TC-UIDATA-029a row not bound is -1");
CHECK(r.val == sentinel, "TC-UIDATA-029b row not bound wrote nothing");
CHECK(ui_data_comm_set(0u, 2u) == -1, "TC-UIDATA-029c set not bound is -1");
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029d pending not bound is 0 (never fabricate WAIT)");
CHECK(ui_data_comm_bind(&s_cf_broken) == -1, "TC-UIDATA-029e incomplete table refused");
}
/* TC-UIDATA-029f-o 注入后转发言行:现状逐字段原样上抛、写口 idx/值精确送达、
* 脏位零缓存跟随(页面 WAIT/OK 的正确性全系于这两条) */
static void tc_comm_ch6_forwarding(void)
{
ui_data_comm_row_t r;
CHECK(ui_data_comm_bind(&s_cf_stub) == 0, "TC-UIDATA-029f complete table accepted");
CHECK(ui_data_comm_row(UI_DATA_COMM_ROW_PORT, &r) == 0, "TC-UIDATA-029g row forwarded");
CHECK((r.val == 2404u) && (r.lo == 1024u) && (r.hi == 65535u),
"TC-UIDATA-029h row fields copied verbatim (band = executor's, not a copy)");
CHECK(ui_data_comm_row(UI_DATA_COMM_ROW_N, &r) == -1,
"TC-UIDATA-029i out-of-range idx rejected at the port too");
CHECK(ui_data_comm_row(0u, NULL) == -1, "TC-UIDATA-029j NULL out refused even bound");
CHECK(ui_data_comm_set(UI_DATA_COMM_ROW_CA, 0u) == -1,
"TC-UIDATA-029k out-of-band write rejected (band owned by executor)");
CHECK(s_cf_dirty == 0u, "TC-UIDATA-029l rejection left the section clean");
CHECK(ui_data_comm_set(UI_DATA_COMM_ROW_CA, 7u) == 0, "TC-UIDATA-029m in-band write accepted");
CHECK((s_cf_last_idx == UI_DATA_COMM_ROW_CA) && (s_cf_last_val == 7u),
"TC-UIDATA-029n executor got exact idx and value");
CHECK(ui_data_comm_pending() == 1u, "TC-UIDATA-029o dirty forwarded (WAIT leg)");
s_cf_dirty = 0u;
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029p clean follows immediately (OK leg, zero cache)");
(void)ui_data_comm_bind(NULL);
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029q unbind restores safe defaults");
s_cf_dirty = 1u; /* 桩此刻仍脏:撤销后连问都不该问到桩 */
CHECK(ui_data_comm_pending() == 0u, "TC-UIDATA-029r unbind stops reaching executor entirely");
s_cf_dirty = 0u;
s_cf_val[0] = 1u; /* 复位假件,别给后面的用例留串台状态 */
}
/* ===================== main ===================== */
int main(void)
@@ -1539,6 +1638,9 @@ int main(void)
printf("-- yt channel: forwarding --\n");
tc_yt_forwarding();
tc_comm_ch6_not_bound(); /* 道G task-9 通道六(TC-UIDATA-029) */
tc_comm_ch6_forwarding();
printf("\n=== UI Data Snapshot Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;