通用配置文件 P1–P3:设计文档、FS 并发 A+ 定案、FatFs 墙钟接入、cfg_file 双端引擎与 net_cfg 集成(旧档 NCFG 首启迁移)

This commit is contained in:
2026-09-14 02:15:49 +08:00
parent cf2597f998
commit 97661415ca
29 changed files with 3076 additions and 359 deletions
+1192
View File
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
/*
* 模块名称:cfg_file
* 模块功能:通用配置文件引擎(双端共享核心)公共接口——INI 段/键表解析校验、
* 外科手术式保存(保留注释/未知内容,缺失段/键写回)、出厂模板生成、
* 沉降去抖轮询与分段变更回调;平台文件操作经 cfg_file_store 后端
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成)
*/
#ifndef CFG_FILE_H
#define CFG_FILE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
/* 配置文件长度上限(字节);超过 = 文件损坏,按出厂模板重建 */
#define CFG_FILE_MAX_LEN 4096u
/* 引擎可注册段数上限 */
#define CFG_FILE_MAX_SECTIONS 8u
/* 单段键数上限 */
#define CFG_FILE_MAX_KEYS 16u
/* 沉降去抖:变更基线连续 N 次轮询不变才重载(轮询周期 2s → 4s 沉降) */
#define CFG_FILE_SETTLE_CNT 2u
/*
* 键值类型
* UINT:无符号十进制;len = 目标字段字节数(1/2/4,小端)
* IP4 :点分 IPv4 "a.b.c.d";字段 uint8_t[4]
* MAC :十六进制 MAC "xx:xx:xx:xx:xx:xx"(分隔符兼容 - 与 .);字段 uint8_t[6]
* STR :ASCII 字符串;字段 char[len](len = 缓冲长度,自动补 NUL)
*/
typedef enum {
CFG_VAL_UINT = 0,
CFG_VAL_IP4,
CFG_VAL_MAC,
CFG_VAL_STR
} cfg_val_type_t;
/* 键定义(offset/len 均相对段 RAM 副本起始) */
typedef struct {
const char *name; /* 键名(小写) */
cfg_val_type_t type; /* 值类型 */
size_t offset; /* 字段偏移 */
size_t len; /* 字段长度 */
} cfg_key_def_t;
/* 段注册描述符 */
typedef struct {
const char *name; /* 段名(≤ 15 字符) */
void *p_data; /* 段 RAM 副本(模块自有静态区,引擎原地更新) */
const void *p_defaults; /* 工厂默认值(与 p_data 同布局;load 先复位再解析) */
size_t data_size; /* RAM 副本字节数 */
const cfg_key_def_t *p_keys; /* 键表 */
uint8_t key_count; /* 键数(1 ~ CFG_FILE_MAX_KEYS) */
void (*p_cb)(void); /* 变更回调(可空;poll 重载后该段有差异时调用;
注册段 data_size 须 ≤ 64 字节方可挂回调) */
} cfg_section_reg_t;
/*
* 函数功能:注册配置段(引擎建立"段 → RAM 副本 + 键表"映射)
* 入口参数:p_reg - 段注册描述符(不可为 NULL) const cfg_section_reg_t *
* 返回值:0 - 成功;-1 - 参数非法/段名重复/段数超限 int
* 限定条件:启动早期调用一次(重复注册同段名拒绝);
* 注册后 p_data/p_defaults/p_keys 生命周期须覆盖引擎使用期
*/
int cfg_file_register(const cfg_section_reg_t *p_reg);
/*
* 函数功能:从配置文件加载全部段(文件 = 配置唯一来源)
* 入口参数:无
* 返回值:0 - 正常(含回退工厂默认);-1 - 严重错误(RAM 已置工厂默认,仍安全) int
* 限定条件:至少注册一个段
* 函数说明: 1. 各段先复位工厂默认(文件缺失/损坏的键 = 工厂默认)
* 2. 文件缺失 → 按当前 RAM 副本生成出厂模板写盘(首启建文件)
* 3. 文件存在 → 解析:有效键写入 RAM 副本;无效键留工厂默认并留痕
* 4. 文件超长(> CFG_FILE_MAX_LEN)= 损坏 → 工厂默认 + 重建模板
*/
int cfg_file_load(void);
/*
* 函数功能:保存全部段到配置文件(外科手术式)
* 入口参数:无
* 返回值:0 - 成功;-1 - 写失败(原文件保持不动) int
* 限定条件:至少注册一个段
* 函数说明:文件存在 → 逐行保留注释/未知段/未知键,受管段/键按 RAM 副本
* 值原位改写,缺失段/键补写;文件缺失/损坏 → 按 RAM 副本重建
* 完整文档(含文件头注释);写盘 = 临时文件 + 原子改名
*/
int cfg_file_save(void);
/*
* 函数功能:配置文件变更轮询(沉降去抖 + 重载 + 分段变更回调)
* 入口参数:无
* 返回值:无
* 限定条件:由单一任务周期调用(建议 2s,收敛于 netTask)
* 函数说明:(size, mtime 变更令牌) 相对基线变化且连续 CFG_FILE_SETTLE_CNT
* 次轮询稳定 → 重载全部段(各段复位工厂默认后解析);挂回调段
* 有差异 → 调用其回调(如触发网络重建);重载后刷新基线
* (回调触发的写盘不引发二次重载)
*/
void cfg_file_poll(void);
/*
* 函数功能:取段 RAM 副本指针
* 入口参数:p_name - 段名 const char *
* 返回值:段 RAM 副本指针;未注册返回 NULL void *
* 限定条件:无
*/
void *cfg_file_section_data(const char *p_name);
#ifdef __cplusplus
}
#endif
#endif /* CFG_FILE_H */
+86
View File
@@ -0,0 +1,86 @@
/*
* 模块名称:cfg_file_store
* 模块功能:cfg_file 平台存储后端接口——带路径的文件操作抽象;
* MCU 实现 = NAND 上 FatFS(cfg_file_store_mcu.c),
* 模拟器实现 = 宿主文件系统(cfg_file_store_sim.c)
* 适用平台:双端接口(实现分居两端)
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本
*/
#ifndef CFG_FILE_STORE_H
#define CFG_FILE_STORE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
/*
* 文件变更基线:size + mtime 变更令牌
* mtime 仅作等值比较(不等 = 可能变更):
* MCU 端 = FAT 打包时间(fdate<<16 | fmtime,分钟级粒度)
* 模拟器 = 宿主文件 mtime(秒级)
*/
typedef struct {
uint32_t size;
uint32_t mtime;
} cfg_file_stat_t;
/*
* 函数功能:读文件(最多 cap 字节)
* 入口参数:p_path - 文件路径 const char *
* p_buf - 接收缓冲(不可为 NULL) uint8_t *
* cap - 缓冲容量 size_t
* p_nread- 实际读字节数输出(不可为 NULL) size_t *
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:调用方保证 p_buf 容量 ≥ 所需长度(引擎读上限 CFG_FILE_MAX_LEN)
*/
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread);
/*
* 函数功能:原子写文件(先写临时文件,成功后改名覆盖目标)
* 入口参数:p_path - 目标文件路径 const char *
* p_buf - 待写数据(不可为 NULL) const uint8_t *
* len - 数据长度 size_t
* 返回值:0 - 成功;-1 - 写/改名失败(目标文件保持不动,临时文件清理) int
* 限定条件:中途失败不得损坏目标文件(掉电安全)
*/
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len);
/*
* 函数功能:文件状态(size + mtime 变更令牌)
* 入口参数:p_path - 文件路径 const char *
* p_st - 输出(不可为 NULL) cfg_file_stat_t *
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:无
*/
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st);
/*
* 函数功能:删除文件
* 入口参数:p_path - 文件路径 const char *
* 返回值:0 - 成功;-1 - 文件缺失/删除失败 int
* 限定条件:无
*/
int cfg_store_remove(const char *p_path);
/* 函数功能:取配置文件路径(如 MCU "/config.ini"、模拟器 "sim_config.ini") */
const char *cfg_store_config_path(void);
/* 函数功能:取配置文件临时文件路径(write_atomic 内部用) */
const char *cfg_store_tmp_path(void);
/* 函数功能:取旧版配置遗留文件路径(net_cfg_legacy 迁移用) */
const char *cfg_store_legacy_path(void);
#ifdef __cplusplus
}
#endif
#endif /* CFG_FILE_STORE_H */
+158
View File
@@ -0,0 +1,158 @@
/*
* 模块名称:cfg_file_store (MCU 后端)
* 模块功能:cfg_file 存储后端——NAND 上 FatFS 文件 /config.ini(卷根,8.3 合规);
* 原子写 = 临时文件 /config.TMP + f_rename;mtime 变更令牌 =
* (fdate << 16) | fmtime;旧版遗留文件 /cfg/netcfg.bin 供迁移读取
* 适用平台:STM32F407ZGTx
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include <string.h>
#include "cfg_file_store.h"
#include "ff.h"
/* 静态变量定义区 */
static const char s_path[] = "/config.ini";
static const char s_path_tmp[] = "/config.TMP";
static const char s_path_legacy[] = "/cfg/netcfg.bin";
/* 函数定义区 */
/*
* 函数功能:读文件(最多 cap 字节)
* 入口参数:p_path - 文件路径 const char *
* p_buf - 接收缓冲(不可为 NULL) uint8_t *
* cap - 缓冲容量 size_t
* p_nread - 实际读字节数输出(不可为 NULL) size_t *
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:卷已挂载(未挂载时 f_open 返回 FR_NO_SYSTEM,转 -1)
*/
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
FIL f;
FRESULT fr;
UINT br = 0u;
if (p_buf == NULL || p_nread == NULL) {
return -1;
}
fr = f_open(&f, p_path, FA_READ);
if (fr != FR_OK) {
return -1;
}
fr = f_read(&f, p_buf, (UINT)cap, &br);
(void)f_close(&f);
if (fr != FR_OK) {
return -1;
}
*p_nread = (size_t)br;
return 0;
}
/*
* 函数功能:原子写文件(先写 /config.TMP,成功后 f_rename 覆盖目标)
* 入口参数:p_path - 目标文件路径 const char *
* p_buf - 待写数据(不可为 NULL) const uint8_t *
* len - 数据长度 size_t
* 返回值:0 - 成功;-1 - 写/改名失败(目标文件保持不动,临时文件清理) int
* 限定条件:卷已挂载;中途失败(含掉电)不损坏目标文件
*/
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
FIL f;
FRESULT fr;
UINT bw = 0u;
if (p_buf == NULL) {
return -1;
}
fr = f_open(&f, s_path_tmp, FA_WRITE | FA_CREATE_ALWAYS);
if (fr != FR_OK) {
return -1;
}
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);
return -1;
}
fr = f_rename(s_path_tmp, p_path);
if (fr != FR_OK) {
(void)f_unlink(s_path_tmp);
return -1;
}
return 0;
}
/*
* 函数功能:文件状态(size + mtime 变更令牌)
* 入口参数:p_path - 文件路径 const char *
* p_st - 输出(不可为 NULL) cfg_file_stat_t *
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:无
* 函数说明:FAT 时间分钟级粒度——同分钟内同长度重写不可检出(可接受,
* 见《配置文件设计说明》§6.2 mtime 令牌说明)
*/
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
FILINFO fno;
FRESULT fr;
if (p_st == NULL) {
return -1;
}
fr = f_stat(p_path, &fno);
if (fr != FR_OK) {
return -1;
}
p_st->size = (uint32_t)fno.fsize;
p_st->mtime = (uint32_t)(((uint32_t)fno.fdate << 16) | (uint32_t)fno.ftime);
return 0;
}
/*
* 函数功能:删除文件
* 入口参数:p_path - 文件路径 const char *
* 返回值:0 - 成功;-1 - 文件缺失/删除失败 int
* 限定条件:无
*/
int cfg_store_remove(const char *p_path) {
if (f_unlink(p_path) != FR_OK) {
return -1;
}
return 0;
}
/*
* 函数功能:取配置文件路径
* 入口参数:无
* 返回值:"/config.ini"(卷根,8.3 合规:CONFIG + INI) const char *
* 限定条件:无
*/
const char *cfg_store_config_path(void) {
return s_path;
}
/*
* 函数功能:取配置文件临时文件路径
* 入口参数:无
* 返回值:"/config.TMP"(卷根,8.3 合规:CONFIG + TMP) const char *
* 限定条件:无
*/
const char *cfg_store_tmp_path(void) {
return s_path_tmp;
}
/*
* 函数功能:取旧版配置遗留文件路径
* 入口参数:无
* 返回值:"/cfg/netcfg.bin"(旧 NCFG 二进制格式) const char *
* 限定条件:无
*/
const char *cfg_store_legacy_path(void) {
return s_path_legacy;
}
+25
View File
@@ -6,6 +6,8 @@
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-09-13 王建锋 新增 sys_clock_get_time()(日历时间获取器,供 FatFs 时间钩子等使用)
* v1.2 2026-09-13 王建锋 文件移入 App/util/(改归 ④中间件层:通用平台时间服务,多服务共用)
*/
#include "sys_clock.h"
@@ -323,6 +325,29 @@ char *sys_clock_get_str(char *buf, size_t buf_size) {
return buf;
}
/*
* 函数功能:获取当前系统墙钟时间(日历时间)
* 入口参数:p_time - 输出时间结构指针(非 NULL)
* 出口参数:p_time - 填充年/月/日/时/分/秒/星期
* 返回值:0 - 成功, -1 - 失败(墙钟未初始化或参数无效)
* 限定条件:sys_clock_init() 已调用
* 函数说明:1. 取当前 Unix 秒数后经 unix_to_sd2506() 换算为日历时间
* 2. 与调试日志时间戳同一时间源(开机 RTC 基准 + tick 推进,运行期零 I2C)
*/
int sys_clock_get_time(sd2506_time_t *p_time) {
uint32_t unix_secs = sys_clock_get();
if (p_time == NULL) {
return -1;
}
if (unix_secs == UINT32_MAX) {
return -1;
}
unix_to_sd2506(unix_secs, p_time);
return 0;
}
/*
* 函数功能:设置(更新)系统墙钟时间基准,并可写回 SD2506 RTC
* 入口参数:unix_secs - 新的 Unix 时间戳 uint32_t 0~0x7FFFFFFF
+13
View File
@@ -7,6 +7,8 @@
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-09-13 王建锋 新增 sys_clock_get_time()(日历时间获取器,供 FatFs 时间钩子等使用)
* v1.2 2026-09-13 王建锋 文件移入 App/util/(改归 ④中间件层:通用平台时间服务,多服务共用)
*/
#ifndef SYS_CLOCK_H
@@ -48,6 +50,17 @@ uint32_t sys_clock_get(void);
*/
char *sys_clock_get_str(char *buf, size_t buf_size);
/*
* 函数功能:获取当前系统墙钟时间(日历时间)
* 入口参数:p_time - 输出时间结构指针(非 NULL)
* 出口参数:p_time - 填充年/月/日/时/分/秒/星期
* 返回值:0 - 成功, -1 - 失败(墙钟未初始化或参数无效)
* 限定条件:sys_clock_init() 已调用
* 函数说明:等价于 "sys_clock_get() + 内部日历换算",为需要日历字段的
* 使用方(如 FatFs 时间钩子)提供类型化接口
*/
int sys_clock_get_time(sd2506_time_t *p_time);
/*
* 函数功能:设置系统墙钟时间
* 入口参数:unix_secs - Unix epoch time (秒)