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:
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""开机账本(boot_stats)的主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/boot_stats/boot_stats_test.c(以 #include 纳入**生产源**
|
||||
App/util/boot_stats.c),在 PC 上离线验证上电次数的**四态判定**(不可信 / 首次启用 /
|
||||
延续 / 电池域掉过电)、记录校验与格式版本、I2C 读失败时**绝不回写**、写失败时
|
||||
"少一笔账"的记号、幂等、饱和,以及复位原因优先级链的唯一出处。
|
||||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||||
|
||||
为什么要主机侧单测(而不是只靠真机):
|
||||
这一格数字的唯一用途是告诉人"这台装置上了多少次电",而它错的时候**永远不像错**:
|
||||
1. I2C 读失败若当成"没有记录",计数会在某次通信抖动后悄悄从 1 重来——屏幕上
|
||||
是个很像样的小数字,没有任何一行日志会说它假;
|
||||
2. 读失败若还顺手回写,就把可能完好的旧记录**覆盖掉**,故障从"读不到"升级成
|
||||
"永久少账";
|
||||
3. "第一次上电"与"RTC 掉过电"合并成一个状态,就等于拿前者冒充后者(文案会写错
|
||||
整整一格的口径);
|
||||
4. 只信 magic 不信校验:电池坏区常是整片全 0 / 全 FF(magic 那关自然过不去),
|
||||
但更常见的"个别字节翻转"里 magic 可能仍然好,次数会错 256 倍。
|
||||
这些都要能把 SRAM 内容、BLF、I2C 错误码逐个构造出来才测得到;真机上跑一次
|
||||
只覆盖"一切正常"那一条。桩边界见下面的注释;上板另有一组判据(断电 ≥5 min 再
|
||||
上电 +1、换电池后落 RESTARTED),见 task-12 卡片,主机测不出来。
|
||||
|
||||
桩的边界:
|
||||
- stub/main.h:只给 RCC_CSR 六个复位标志位掩码(位号照抄 CMSIS,见该文件注释);
|
||||
真实 main.h 拉整棵 HAL 头树,PC 上编不动。
|
||||
- sd2506.h / bsp_err.h / boot_stats.h / app_main.h 一律用**真实头**(它们不依赖
|
||||
HAL 类型);被测源用到的三个 SD2506 函数
|
||||
sd2506_read_sram / sd2506_write_sram / sd2506_read_ctr1 由本测试单元实现为
|
||||
可注入失败的假件(含一片 70 B 假 SRAM,地址越界按真驱动语义返 BSP_ERR_PARAM)。
|
||||
- g_rcc_csr_latched 的真实定义在 App/app/app_main.c(本套件不编译它),
|
||||
由本测试单元给出。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/boot_stats/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||||
|
||||
设计依据:App/util/boot_stats.h、docs/任务规划/UI补齐.md §2(运行报告四个数的口径)
|
||||
创建日期:2026-10-27(道G task-12)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
# 顺序敏感:stub 必须排在最前,才能用桩 main.h 覆盖真实 main.h。
|
||||
BUILD = TestBuild(
|
||||
name="Boot Stats Tests",
|
||||
src=os.path.join(HERE, "boot_stats_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "App", "util"),
|
||||
os.path.join(ROOT, "App", "app"),
|
||||
os.path.join(ROOT, "Drivers", "BSP"),
|
||||
os.path.join(ROOT, "Drivers", "BSP", "SD2506"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== Boot Stats Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
Reference in New Issue
Block a user