通用配置文件 P1–P3:设计文档、FS 并发 A+ 定案、FatFs 墙钟接入、cfg_file 双端引擎与 net_cfg 集成(旧档 NCFG 首启迁移)
This commit is contained in:
+1192
File diff suppressed because it is too large
Load Diff
+126
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 (秒)
|
||||
Reference in New Issue
Block a user