通用配置文件 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
+5 -2
View File
@@ -52,6 +52,7 @@ include_directories(
${UI_DIR} # ui_menu.h, ui_font_cn_12.h
${NET_DIR} # net_socket.h / net_types.h(net_* API 双端共享核心)
${NETCFG_DIR} # net_cfg.h(网络配置,双端共享核心)
${PROJECT_ROOT}/App # cfg_file.h / cfg_file_store.h(通用配置文件引擎,双端共享核心)
${PROJECT_ROOT}/Drivers/BSP # dbg_log.h(使用 simulator 的 dbg_cfg.h 配置)
${SDL2_DIR}/include # SDL2 头文件
)
@@ -86,10 +87,12 @@ set(UI_SOURCES
${UI_DIR}/ui_net_sink.c
)
# 网络配置(双端共享核心 + 模拟器存储后端;MCU 端编 net_cfg_store_mcu.c,见 Keil 工程)
# 网络配置(双端共享核心 + cfg_file 引擎 + 平台存储后端;MCU 端编 cfg_file_store_mcu.c,见 Keil 工程)
set(NETCFG_SOURCES
${NETCFG_DIR}/net_cfg.c
sim/net_cfg_store_sim.c
${NETCFG_DIR}/net_cfg_legacy.c
${PROJECT_ROOT}/App/cfg_file.c
sim/cfg_file_store_sim.c
)
# 网络后端(net_* 核心 API 的 Winsock 实现 + UI 推流平台后端;MCU 端另编 net_socket.c 与 _mcu 后端)
+6 -6
View File
@@ -17,7 +17,7 @@ simulator/
│ ├── sim_port.c / .h # 显示/输入端口(SDL 窗口 + 键盘 + mono 主题),替代 MCU 端 ui_port.c
│ ├── net_socket_sim.c # 网络后端:Winsock 实现 net_* BSD API,替代 MCU 端 net_socket.c(CH395F)
│ ├── ui_net_sink_backend_sim.c # RemoDispBus 平台 backend:取帧/注键(配合共享 ui_net_sink.c)
│ └── net_cfg_store_sim.c # 网口配置存储:本地 sim_netcfg.bin
│ └── cfg_file_store_sim.c # 配置文件存储后端:本地 sim_config.ini(旧档 sim_netcfg.bin 首启自动迁移)
├── lv_conf.h # 模拟器专用 LVGL 配置(与 MCU 端 Lib/lv_conf.h 相互独立)
├── i1_to_png.py # 截图转 PNG:把 --screenshot 的 I1(1bpp) 原始位图转成 PNG(见 §3.5)
├── tests/ # 无头回归脚本 + 本地黄金参考(见 §3.6,流程文档 docs/UI 设计/UI_自动化测试流程.md)
@@ -46,10 +46,10 @@ simulator/
| `sim/sim_port.c` | 创建 SDL 窗口显示设备(`lv_sdl_window_create`)、初始化 **mono 主题**(`lv_theme_mono_init`,1bpp 必需)、创建键盘输入设备并挂默认输入组。**不创建鼠标**(纯键盘导航,与 MCU 端 keypad 一致)。`sim_port_get_screen_mono()` 经 `SDL_RenderReadPixels` 读回屏幕像素转 1bpp,供推流取帧 |
| `sim/net_socket_sim.c` | 网络后端:Winsock 实现 `net_*` BSD Socket API(`socket`/`bind`/`listen`/`accept`/`recv`/`send`/`close`),替代 MCU 端 `Drivers/BSP/NET/net_socket.c`(CH395F) |
| `sim/ui_net_sink_backend_sim.c` | RemoDispBus 平台 backend:`ui_net_sink_get_lcdmem`→`sim_port_get_screen_mono`、`ui_net_sink_inject_key`→`lv_group_send_data`;配合**共享** `Drivers/BSP/UI/ui_net_sink.c`(协议服务端,双端同一份,TCP 7003) |
| `sim/net_cfg_store_sim.c` | 网口配置存储后端:本地文件 `sim_netcfg.bin`(`fopen`),替代 MCU 端 `Drivers/BSP/NETCFG/net_cfg_store_mcu.c`(Flash) |
| `sim/cfg_file_store_sim.c` | 通用配置文件存储后端:本地文件 `sim_config.ini`(`fopen`/`rename` 原子写),替代 MCU 端 `App/cfg_file_store_mcu.c`(FatFS);旧档 `sim_netcfg.bin` 首启自动迁移进 `sim_config.ini` |
| `lv_conf.h` | 模拟器专用 LVGL 配置。与 MCU 端 `Lib/lv_conf.h` 的关键差异见 §3.3 |
| `dbg_log.h` | MCU 版 `dbg_log.h` 依赖 `sys_clock.h`(RTC 时间戳),PC 无法编译。此 stub 把 `DBG_INFO/ERROR/DEBUG` 映射到 `printf`;因 `simulator/` 在 include 路径最前,**优先生效**覆盖 MCU 版 |
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
| `CMakeLists.txt` | 收集 LVGL 源码(`../Lib/lvgl`)+ 共享 UI 源码(`../Drivers/BSP/UI`)+ 通用配置文件引擎(`../App/cfg_file.c`、`../Drivers/BSP/NETCFG` 的 net_cfg/net_cfg_legacy)+ `sim/` 平台层,链接预编译 SDL2,产出 `lvgl_sim.exe` |
| `i1_to_png.py` | 把 `--screenshot` 产出的 I1(1bpp) 原始位图转成 PNG,便于离线比对设计稿。详见 §3.5 |
| `tests/` | 无头回归脚本(菜单 8 状态像素回归、内容页文本断言 + 黄金参考回归);`ref/` 为**本地生成物、不入库**(首次 `--update` 生成)。详见 §3.6 |
@@ -112,7 +112,7 @@ LVGL 场景(同一份 ui_menu.c)
| 输入 | 远端 keypad(`ui_port_queue_key`,网络回传键码) | SDL 键盘(`lv_sdl_keyboard_create`) |
| 主题 | 无主题(`LV_USE_THEME_* = 0`),控件用内置基础样式 | **mono 主题必须开启**(否则 1bpp 黑底黑字) |
| 日志 | `Drivers/BSP/dbg_log.h`(USART1 + RTC 时间戳) | `simulator/dbg_log.h`(printf stub) |
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无,纯 UI |
| 业务任务 | netTask / ftpTask / timeSyncTask 等 | 无(仅编译纯软件部分的配置文件引擎 net_cfg/cfg_file,可离线验证配置加载/保存/旧档迁移) |
| 时钟 | `lv_tick_inc` 由 ui_task 手动喂(10ms 周期) | SDL 驱动内部 `lv_tick_set_cb(SDL_GetTicks)`,**不可再手动喂** |
### 2.4 关键设计决策
@@ -121,7 +121,7 @@ LVGL 场景(同一份 ui_menu.c)
2. **CMake + MSVC**:`Visual Studio 16 2019` 生成器,`-A x64`;MSVC 下加 `/utf-8` 消除中文注释的 C4819 警告
3. **共享 UI 源码**:`ui_menu.c` / `ui_font_cn_12.c` 直接编译 `Drivers/BSP/UI/` 原件,改一处两端同时生效
4. **纯键盘导航**:不创建鼠标/触摸设备,与 MCU 端 keypad 操作语义一致(↑↓←→ / Enter / Esc)
5. **仅 UI 预览**:不含网络/存储/ADC 等硬件业务,无法验证与外设相关的逻辑
5. **UI 为主**:不含 CH395F 网络 / NAND 存储 / ADC 等硬件业务;通用配置文件引擎(`cfg_file`)与 `net_cfg` 核心为纯软件,配置加载/保存/旧档迁移可在模拟器离线验证(`sim_config.ini` / `sim_netcfg.bin`)
---
@@ -259,7 +259,7 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI 设计/UI_自动化
## 五、边界说明
- 模拟器**只跑 UI**:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务
- 模拟器**跑 UI + 配置文件引擎**:不含 CH395F 网络、NAND 存储、ADC、RS-485 等硬件业务;`cfg_file` 引擎与 `net_cfg`(纯软件部分)在模拟器编译运行,可离线验证配置加载/保存/旧档迁移
- 模拟器**含** `ui_net_sink`(Winsock 版 RemoDispBus 推流服务端,TCP 7003)——远端看图链路
可在模拟器上验证(`simulator/tests/check_remo_sink.py`);CH395F 真实网络栈仍只在 MCU 上验证
- `build/` 为生成物,可随时删除重建(已在 `.gitignore` 中忽略)
+148
View File
@@ -0,0 +1,148 @@
/*
* 模块名称:cfg_file_store (模拟器后端)
* 模块功能:cfg_file 存储后端——模拟器工作目录宿主文件 sim_config.ini;
* 原子写 = 临时文件 sim_config.ini.tmp + rename;mtime 变更令牌 =
* 宿主文件 mtime(秒级);旧版遗留文件 sim_netcfg.bin 供迁移读取
* 适用平台:PC 模拟器
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "cfg_file_store.h"
/* 静态变量定义区 */
static const char s_path[] = "sim_config.ini";
static const char s_path_tmp[] = "sim_config.ini.tmp";
static const char s_path_legacy[] = "sim_netcfg.bin";
/* 函数定义区 */
/*
* 函数功能:读文件(最多 cap 字节)
* 入口参数:p_path - 文件路径 const char *
* p_buf - 接收缓冲(不可为 NULL) uint8_t *
* cap - 缓冲容量 size_t
* p_nread - 实际读字节数输出(不可为 NULL) size_t *
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:无
*/
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
FILE *fp;
size_t n;
if (p_buf == NULL || p_nread == NULL) {
return -1;
}
fp = fopen(p_path, "rb");
if (fp == NULL) {
return -1;
}
n = fread(p_buf, 1u, cap, fp);
(void)fclose(fp);
*p_nread = n;
return 0;
}
/*
* 函数功能:原子写文件(先写 sim_config.ini.tmp,成功后 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) {
FILE *fp;
size_t n;
if (p_buf == NULL) {
return -1;
}
fp = fopen(s_path_tmp, "wb");
if (fp == NULL) {
return -1;
}
n = fwrite(p_buf, 1u, len, fp);
(void)fclose(fp);
if (n != len) {
(void)remove(s_path_tmp);
return -1;
}
if (rename(s_path_tmp, p_path) != 0) {
(void)remove(s_path_tmp);
return -1;
}
return 0;
}
/*
* 函数功能:文件状态(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) {
struct stat st;
if (p_st == NULL) {
return -1;
}
if (stat(p_path, &st) != 0) {
return -1;
}
p_st->size = (uint32_t)st.st_size;
p_st->mtime = (uint32_t)st.st_mtime;
return 0;
}
/*
* 函数功能:删除文件
* 入口参数:p_path - 文件路径 const char *
* 返回值:0 - 成功;-1 - 文件缺失/删除失败 int
* 限定条件:无
*/
int cfg_store_remove(const char *p_path) {
if (remove(p_path) != 0) {
return -1;
}
return 0;
}
/*
* 函数功能:取配置文件路径
* 入口参数:无
* 返回值:"sim_config.ini"(模拟器工作目录) const char *
* 限定条件:无
*/
const char *cfg_store_config_path(void) {
return s_path;
}
/*
* 函数功能:取配置文件临时文件路径
* 入口参数:无
* 返回值:"sim_config.ini.tmp" const char *
* 限定条件:无
*/
const char *cfg_store_tmp_path(void) {
return s_path_tmp;
}
/*
* 函数功能:取旧版配置遗留文件路径
* 入口参数:无
* 返回值:"sim_netcfg.bin"(与 MCU 端旧格式一致,两端可互读) const char *
* 限定条件:无
*/
const char *cfg_store_legacy_path(void) {
return s_path_legacy;
}
-67
View File
@@ -1,67 +0,0 @@
/*
* 模块名称:Network Configuration Store (模拟器后端)
* 模块功能:net_cfg 存储后端——模拟器工作目录本地文件 sim_netcfg.bin
* (磁盘格式与 MCU 端一致,两端可互读)
* 适用平台:PC 模拟器
* 作者:王建锋
* 创建日期:2026-09-10
* 修改记录:
* v1.0 2026-09-10 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include <stdio.h>
#include "net_cfg_store.h"
/* 静态变量定义区 */
static const char s_path[] = "sim_netcfg.bin";
/* 函数定义区 */
/*
* 函数功能:读取配置文件
* 入口参数:p_buf - 接收缓冲(不可为 NULL) uint8_t *
* len - 文件长度 size_t
* 返回值:0 - 成功;-1 - 文件缺失/读失败 int
* 限定条件:无
*/
int net_cfg_store_read(uint8_t *p_buf, size_t len) {
FILE *fp;
size_t n;
if (p_buf == NULL) {
return -1;
}
fp = fopen(s_path, "rb");
if (fp == NULL) {
return -1;
}
n = fread(p_buf, 1u, len, fp);
fclose(fp);
return (n == len) ? 0 : -1;
}
/*
* 函数功能:写配置文件(不存在则创建,存在则覆盖)
* 入口参数:p_buf - 待写数据(不可为 NULL) const uint8_t *
* len - 数据长度 size_t
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:无
*/
int net_cfg_store_write(const uint8_t *p_buf, size_t len) {
FILE *fp;
size_t n;
if (p_buf == NULL) {
return -1;
}
fp = fopen(s_path, "wb");
if (fp == NULL) {
return -1;
}
n = fwrite(p_buf, 1u, len, fp);
fclose(fp);
return (n == len) ? 0 : -1;
}