阶段 D 收口:P3 卫生六项 + S-2 boot_log 存档流程 + 登记簿销项

阶段 D(验证与卫生)离线可完成项收口;台架依赖项 D2/D5/D6 保留登记。

- D4 P3 卫生六项:
  - P3-1 cfg_file.c s_encode:STR 型 len==0 回绕(p_key->len - 1u)直接拒绝
  - P3-2 cfg_file_store_mcu.c:原子写临时名由 p_path 同目录推导(s_build_tmp_path)
  - P3-4 rs485.c:模块描述订正为实际实现(HAL_Delay(1),非 DWT µs)
  - P3-6 ch395f_get_socket_status:补 p_status NULL 检查
  - P3-8 build.md:退出码契约订正为 0/1/2 三档
  - P3-11 adc_task/rs485_task:注明"预留桩(未启用)"
- D3 S-2:新增 docs/测试说明/boot_log_data/(快照存档目录 + 流程),
  并在 测试开发标准流程.md §7.3 登记沉淀约定
- 登记簿销项:删 P3-1/2/4/6/8/11、S-2;阶段 D 表移除 D3/D4;
  §1 计数更新(P3 6→0、登记簿 10→9);§3 章节重排
  (登记簿→§3.2、专项→§3.3);清理 App/cfg/README.md 等对已销编号的引用
- 同步刷新内存快照:Flash 266,632 B / ROM 合计 267,372 B

验证:@build 0 错 0 警;cfg_file 主机单测 149/149;
pre-commit --all-files 17/17 通过。
This commit is contained in:
2026-10-05 10:15:53 +08:00
parent de1b50efe9
commit ad0fd53389
12 changed files with 129 additions and 48 deletions
+1 -2
View File
@@ -9,7 +9,7 @@
|---|---|
| `cfg_file.c/h` | **配置引擎核心**:解析 / 查询 / 保存 / 2s 周期 `cfg_file_poll` |
| `cfg_file_store.h` | **存储后端抽象接口**(MCU=FreeRTOS 堆 / 模拟器=宿主堆,共享核心零分配符号) |
| `cfg_file_store_mcu.c` | **MCU 端后端实现**:`/config.ini`(`/config.TMP` + `f_rename` 原子写) |
| `cfg_file_store_mcu.c` | **MCU 端后端实现**:`/config.ini`(临时名由目标路径同目录推导 + `f_rename` 原子写) |
## 设计要点
@@ -25,4 +25,3 @@
- `docs/子系统设计/配置文件设计说明.md` — 完整设计(分层/解析/持久化/迁移)
- `docs/子系统设计/网络配置说明.md` — 网络段
- `docs/待处理内容.md` P3-2 — 临时文件名硬编码问题
+8 -1
View File
@@ -266,7 +266,14 @@ static int s_encode(const cfg_key_def_t *p_key, const uint8_t *p_src, char *p_ds
(unsigned)p_src[3], (unsigned)p_src[4], (unsigned)p_src[5]);
break;
case CFG_VAL_STR: {
size_t slen = strlen((const char *)p_src);
size_t slen;
/* P3-1:len==0 时 `p_key->len - 1u` 回绕为 SIZE_MAX → memcpy 巨长越界。
* STR 字段至少 1 字节(含结尾 NUL),len==0 非法,直接拒绝 */
if (p_key->len == 0u) {
return -1;
}
slen = strlen((const char *)p_src);
if (slen >= p_key->len) {
slen = p_key->len - 1u;
}
+46 -6
View File
@@ -1,7 +1,7 @@
/*
* 模块名称:cfg_file_store (MCU 后端)
* 模块功能:cfg_file 存储后端——NAND 上 FatFS 文件 /config.ini(卷根,8.3 合规);
* 原子写 = 临时文件 /config.TMP + f_rename;mtime 变更令牌 =
* 原子写 = 临时文件 config.TMP(与目标同目录)+ f_rename;mtime 变更令牌 =
* (fdate << 16) | fmtime;旧版遗留文件 /cfg/netcfg.bin 供迁移读取
* 适用平台:STM32F407ZGTx
* 作者:王建锋
@@ -18,6 +18,9 @@
* dhara journal 竞争实证);busy 返回 -1 由调用方
* 按既有失败语义处理(poll 保留 RAM/保存待重试),
* 本任务 = 看门狗喂狗点,绝不阻塞等锁
* v1.4 2026-10-05 王建锋 原子写临时文件由 p_path 推导(P3-2):与目标同目录的
* 固定 8.3 名 config.TMP,不再硬编码卷根;目标恒为
* "/config.ini" 时推导结果与旧值逐字节一致
*/
/* 头文件包含区 */
@@ -74,8 +77,40 @@ int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nre
return 0;
}
/* 临时文件名(8.3 合规)与路径缓冲上限 */
#define CFG_STORE_TMP_NAME "config.TMP"
#define CFG_STORE_TMP_PATH_MAX 24u
/*
* 函数功能:原子写文件(先写 /config.TMP,成功后 unlink 既有目标 + f_rename 替换)
* 函数功能:由目标路径推导同目录的临时文件路径(P3-2)
* 入口参数:p_path - 目标文件路径 const char *
* p_out - 输出缓冲 char *
* cap - 缓冲容量 size_t
* 返回值:0 - 成功;-1 - 参数非法/缓冲不足 int
* 函数说明:取 p_path 的目录前缀(含末尾 '/';无 '/' 则用当前目录),拼固定 8.3 名
* config.TMP。目标 "/config.ini" → "/config.TMP"(与旧硬编码值一致)
*/
static int s_build_tmp_path(const char *p_path, char *p_out, size_t cap) {
const char *p_slash;
size_t dirlen;
if (p_path == NULL || p_out == NULL) {
return -1;
}
p_slash = strrchr(p_path, '/');
dirlen = (p_slash != NULL) ? ((size_t)(p_slash - p_path) + 1u) : 0u;
if (dirlen + sizeof(CFG_STORE_TMP_NAME) > cap) {
return -1;
}
if (dirlen != 0u) {
(void)memcpy(p_out, p_path, dirlen);
}
(void)memcpy(p_out + dirlen, CFG_STORE_TMP_NAME, sizeof(CFG_STORE_TMP_NAME));
return 0;
}
/*
* 函数功能:原子写文件(先写与目标同目录的 config.TMP,成功后 unlink 既有目标 + f_rename 替换)
* 入口参数:p_path - 目标文件路径 const char *
* p_buf - 待写数据(不可为 NULL) const uint8_t *
* len - 数据长度 size_t
@@ -86,14 +121,19 @@ int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len)
FIL f;
FRESULT fr;
UINT bw = 0u;
char tmp_path[CFG_STORE_TMP_PATH_MAX];
if (p_buf == NULL) {
return -1;
}
/* P3-2:临时文件由 p_path 推导(同目录 + 固定 8.3 名),不再硬编码卷根 */
if (s_build_tmp_path(p_path, tmp_path, sizeof(tmp_path)) != 0) {
return -1;
}
if (vol_lock_wait(0u) == 0u) {
return -1; /* 卷被占用:本轮跳过(保存请求由 2s 轮询重试) */
}
fr = f_open(&f, s_path_tmp, FA_WRITE | FA_CREATE_ALWAYS);
fr = f_open(&f, tmp_path, FA_WRITE | FA_CREATE_ALWAYS);
if (fr != FR_OK) {
vol_lock_release();
return -1;
@@ -101,7 +141,7 @@ int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len)
fr = f_write(&f, (const void *)p_buf, (UINT)len, &bw);
(void)f_close(&f);
if (fr != FR_OK || bw != (UINT)len) {
(void)f_unlink(s_path_tmp);
(void)f_unlink(tmp_path);
vol_lock_release();
return -1;
}
@@ -113,9 +153,9 @@ int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len)
* 开机 load 按"首启"重建工厂模板——与函数失败语义一致,丢失边界 =
* 本次进行中的保存内容(目标原值本就是待覆盖的旧代数据) */
(void)f_unlink(p_path); /* 目标不存在返回 FR_NO_FILE,无害 */
fr = f_rename(s_path_tmp, p_path);
fr = f_rename(tmp_path, p_path);
if (fr != FR_OK) {
(void)f_unlink(s_path_tmp);
(void)f_unlink(tmp_path);
vol_lock_release();
return -1;
}
+4
View File
@@ -1,11 +1,15 @@
/*
* 模块名称:ADC Sampling Task
* 模块功能:TPAFE5160 8 通道同步采样,数据缓存与通知
* 状态说明:**预留桩(未启用)**——采集链路尚未实现,产品定义未定
* (见 docs/待处理内容.md 阶段 B0/B1)。当前 init/func 为空实现,
* 不触发 CONVST、不消费数据。
* 适用平台:STM32F407ZGTx + TPAFE5160
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-10-05 王建锋 P3-11:注明"预留桩(未启用)",避免误认为已实现
*/
#include "adc_task.h"
+4
View File
@@ -1,11 +1,15 @@
/*
* 模块名称:RS485 Communication Task
* 模块功能:RS-485 轮询收发、协议帧解析与响应
* 状态说明:**预留桩(未启用)**——采集/协议链路尚未实现,产品定义未定
* (见 docs/待处理内容.md 阶段 B0/B2)。当前 init/func 为空实现,
* 不收发、不解析。
* 适用平台:STM32F407ZGTx
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-10-05 王建锋 P3-11:注明"预留桩(未启用)",避免误认为已实现
*/
#include "rs485_task.h"