fix(cfg): 配置热重载吞段缺陷修复 + 两点标定工具链与台架实测落档
修复运行期改配置被打回出厂值的缺陷:cfg_file_poll() 按段循环复用同一 份读缓冲,而 s_parse() 通过 s_split_line()/s_key_value() 就地改写缓冲 (把换行与等号换成 NUL)。第一个段解析完,缓冲里已没有键值行,后续 7 个段静默保持出厂值;随后 run_par_store_save() 又把这份出厂值写回 config.ini,用户的标定值被永久覆盖。开机路径 cfg_file_load() 只解析一 轮,所以该缺陷长期不可见,只在运行中改配置时发作。 - s_parse() 改为非破坏性:入参 const char *,逐行 memcpy 到栈缓冲 (CFG_LINE_TEST_MAX + 1)后再交给会破坏缓冲的解析子函数;超长行跳 过(保存侧已拒绝落盘超长受管键行)。 - 新增 TC-CONFIG-063:同时改两段内容,断言第二个注册段也重载成功、 含负值 INT 键存活,把「热重载吞段」钉成机器面。 新增两点标定台架工具链(test/host/): - cal_two_point.py:整数算术镜像 adc_cal_two_point(),支持单路模式与 从执行卡 CALDATA 表批量求解(--from-doc),带合法带与零点自检拦截。 - cal_write.py:复用 iec104_master 作库,经 104 定值段写 r 后写 b 并 读回校验,是所有写入的唯一入口。 adc_task 的 meas: 心跳行精度提到 4 位小数,标定读数需要。 文档:新增《采样数据校正全流程》(端到端导航)与《ADC 两点标定执行 流程》(台架执行卡,含 CALDATA 可填表);ADC通道映射说明新增 §7.3 台 架实测结果(UA 554.687914、IA 29.634259,出厂默认值保持不变);配置 文件设计说明新增解析器契约 v1.19;采集与算法层设计说明 §13 系列与 README 索引同步。 台架实测:UA r=554.687914(出厂 1000.0 偏大 1.80 倍,根因是接线分压 使 PT 一次侧只拿到一半电压,实测与参数算定 606.06 只差 8.5%)、IA r=29.634259(与出厂 29.4118 只差 0.76%,反证链路未算错)。UN 按用户 裁定本轮不标定,保护闭锁(vl=0x11)是预期行为,未伪造 VALID。 同时入库 .dsh/roles 角色定义(原被 .gitignore 挡住)。
This commit is contained in:
+62
-13
@@ -40,6 +40,13 @@
|
||||
* 台架根因为卷层 dhara 旧视图回滚(复现多形态:空读、
|
||||
* 短读、同尺寸全 0 块),引擎侧无法分辨"一致但陈旧"的
|
||||
* 整卷回滚——该形态重启必收敛,见设计说明
|
||||
* v1.7 2026-11-12 DTU 项目组 s_parse 改为**非破坏性**(只读入参 + 逐行栈
|
||||
* 缓冲)——热重载对同一份读缓冲按段各解析一轮,旧实现
|
||||
* 在原地把 '=' / '\n' 写成 NUL,第一个段解析完后面
|
||||
* 所有段再无键值行 → 静默全体回出厂默认,且下次落盘
|
||||
* 把出厂值写回文件、永久覆盖用户整定(台架实证:
|
||||
* 104 写入 r/b 当场读得回,约 8 s 后打回出厂值,而
|
||||
* 开机 load 始终正确,因 load 只解析一轮)
|
||||
* 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save;
|
||||
* 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证
|
||||
* (见《配置文件设计说明》D15 A+ 方案)
|
||||
@@ -705,30 +712,72 @@ static int s_write_template(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:解析 INI 文本,将有效键写入段 RAM 副本
|
||||
* 入口参数:p_buf - 文件文本(可修改,NUL 结尾) char *
|
||||
* 函数功能:解析 INI 文本,将有效键写入段 RAM 副本(**只读入参,不修改文件文本**)
|
||||
* 入口参数:p_buf - 文件文本(NUL 结尾,只读) const char *
|
||||
* only_sec - 仅解析该段下标;-1 = 全部段 int
|
||||
* 返回值:无
|
||||
* 限定条件:调用前对应段须已复位工厂默认;无效键 = 留工厂默认并留痕
|
||||
* 函数说明:**必须保持非破坏性**——热重载(cfg_file_poll)对同一份读缓冲
|
||||
* 按段各解析一轮,历史实现在原地把 '=' 与 '\n' 改写成 NUL(行切分
|
||||
* 的常规做法),于是第一个段解析完,后面所有段拿到的文本里再无键值
|
||||
* 行 → 静默全体回出厂默认;且 RAM 被改写后紧接的落盘会把出厂值
|
||||
* 写回文件,把用户整定永久覆盖(台架 2026-11-12 实证:104 写入
|
||||
* r/b 当场读得回,约 8 s 后被打回出厂值,开机 load 却始终正确)。
|
||||
* 故此处逐行拷到本函数栈缓冲再交给 s_split_line/s_key_value 破坏。
|
||||
* 行过长(> CFG_LINE_TEST_MAX)只可能是手工编辑的未知内容或超长
|
||||
* 受管键行——后者在保存侧已被 v1.3 规则拒绝落盘,读侧按"非受管行"
|
||||
* 跳过(与保存侧同一前提)。
|
||||
*/
|
||||
static void s_parse(char *p_buf, int only_sec) {
|
||||
char *p = p_buf;
|
||||
static void s_parse(const char *p_buf, int only_sec) {
|
||||
const char *p = p_buf;
|
||||
char p_name[CFG_NAME_MAX];
|
||||
char line[CFG_LINE_TEST_MAX + 1u];
|
||||
int sec_idx = -1;
|
||||
|
||||
while (*p != '\0') {
|
||||
char *p_line = s_split_line(&p);
|
||||
const char *p_begin = p;
|
||||
const char *p_nl = strchr(p, '\n');
|
||||
size_t l;
|
||||
char *p_line = line;
|
||||
char *p_ws;
|
||||
|
||||
if (p_line[0] == '\0' || p_line[0] == ';' || p_line[0] == '#') {
|
||||
if (p_nl != NULL) {
|
||||
l = (size_t)(p_nl - p);
|
||||
p = p_nl + 1;
|
||||
}
|
||||
else {
|
||||
l = strlen(p);
|
||||
p = p + l; /* 指向结尾 NUL:本轮结束后 while 退出 */
|
||||
}
|
||||
|
||||
if (l > CFG_LINE_TEST_MAX) {
|
||||
continue; /* 超长行:受管键/段头都不可能这么长 */
|
||||
}
|
||||
(void)memcpy(line, p_begin, l);
|
||||
line[l] = '\0';
|
||||
|
||||
if (l > 0u && line[l - 1u] == '\r') {
|
||||
line[l - 1u] = '\0';
|
||||
}
|
||||
p_ws = line;
|
||||
while (*p_ws == ' ' || *p_ws == '\t') {
|
||||
p_ws++;
|
||||
}
|
||||
if (p_ws != line) {
|
||||
(void)memmove(line, p_ws, strlen(p_ws) + 1u);
|
||||
}
|
||||
|
||||
if (line[0] == '\0' || line[0] == ';' || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_match_section(p_line, p_name, sizeof(p_name)) != 0) {
|
||||
int i;
|
||||
if (s_match_section(line, p_name, sizeof(p_name)) != 0) {
|
||||
size_t i;
|
||||
|
||||
sec_idx = -1;
|
||||
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
|
||||
for (i = 0; i < (size_t)CFG_FILE_MAX_SECTIONS; i++) {
|
||||
if (s_sections[i].used != 0 && s_str_ci_eq(p_name, s_sections[i].name) != 0) {
|
||||
sec_idx = i;
|
||||
sec_idx = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1227,7 +1276,7 @@ int cfg_file_load(void) {
|
||||
syslog_post(SYSLOG_SELFTEST, "cfg load read fail");
|
||||
return -1;
|
||||
}
|
||||
s_parse((char *)p_buf, -1);
|
||||
s_parse((const char *)p_buf, -1);
|
||||
cfg_store_free(p_buf);
|
||||
DBG_INFO("load: OK (%luB, heap free %uB)",
|
||||
(unsigned long)st.size, (unsigned)cfg_store_heap_free());
|
||||
@@ -1487,7 +1536,7 @@ void cfg_file_poll(void) {
|
||||
if (p_sec->p_cb != NULL) {
|
||||
(void)memcpy(s_snap, p_sec->p_data, p_sec->data_size);
|
||||
(void)memcpy(p_sec->p_data, p_sec->p_defaults, p_sec->data_size);
|
||||
s_parse((char *)p_buf, i);
|
||||
s_parse((const char *)p_buf, i);
|
||||
if (memcmp(s_snap, p_sec->p_data, p_sec->data_size) != 0) {
|
||||
DBG_INFO("poll: section '%s' changed, reload", p_sec->name);
|
||||
p_sec->p_cb();
|
||||
@@ -1495,7 +1544,7 @@ void cfg_file_poll(void) {
|
||||
}
|
||||
else {
|
||||
(void)memcpy(p_sec->p_data, p_sec->p_defaults, p_sec->data_size);
|
||||
s_parse((char *)p_buf, i);
|
||||
s_parse((const char *)p_buf, i);
|
||||
}
|
||||
}
|
||||
cfg_store_free(p_buf);
|
||||
|
||||
+4
-4
@@ -763,11 +763,11 @@ void adc_task_func(void *arg) {
|
||||
meas_snap_t ms;
|
||||
|
||||
(void)meas_get(&ms); /* 未就绪(-2) = 清零帧,以下打印全 0 */
|
||||
DBG_INFO("meas: seq=%lu v=%u ua_mv=%ld un_mv=%ld ia_mv=%ld f=%ld fv=%u",
|
||||
DBG_INFO("meas: seq=%lu v=%u ua_mv=%.4f un_mv=%.4f ia_mv=%.4f f=%ld fv=%u",
|
||||
(unsigned long)ms.seq, (unsigned)ms.valid,
|
||||
(long)(ms.yc[ADC_SEM_UA] * 1000.0f),
|
||||
(long)(ms.yc[ADC_SEM_UN] * 1000.0f),
|
||||
(long)(ms.yc[ADC_SEM_IA] * 1000.0f),
|
||||
(double)(ms.yc[ADC_SEM_UA] * 1000.0f),
|
||||
(double)(ms.yc[ADC_SEM_UN] * 1000.0f),
|
||||
(double)(ms.yc[ADC_SEM_IA] * 1000.0f),
|
||||
(long)(ms.freq_hz * 100.0f),
|
||||
(unsigned)ms.freq_valid);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user