模拟器迁移 P5:2s 配置轮询 + --cfgtest 无头自检;修复 MSVC rename 覆盖与外科保存截断缺陷
This commit is contained in:
@@ -228,6 +228,18 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI 设计/UI_自动化
|
||||
- **池水位常量漂移**:菜单/页面结构变更导致门禁失败时,确认新值合理后再更新脚本内常量并登记流程文档变更记录;无法解释的漂移 = 内存回归 bug。
|
||||
- 退出码均为 0=PASS / 1=断言失败 / 2=环境错误(exe 缺失等)。
|
||||
|
||||
### 3.7 配置文件自检(--cfgtest)
|
||||
|
||||
配置引擎"加载 → 轮询 → 重载 → 保存"的进程内闭环验证(无需窗口 / LVGL,退出码 0=PASS / 非 0=FAIL),在当前工作目录产生 `sim_config.ini`(`sim_netcfg.bin` 参与迁移时):
|
||||
|
||||
```powershell
|
||||
.\build\Debug\lvgl_sim.exe --cfgtest
|
||||
```
|
||||
|
||||
验证链:`net_cfg_load`(含旧档自动迁移)→ 第 1 次 poll 建基线 → 伪造"外部编辑"(原子写,等价 FTP 上传/文本编辑器)→ 2 次 poll 沉降(对应真机 4s 沉降)→ 断言 RAM 副本已更新为新值 → 对已存在文件做外科式保存、回读校验注释/值无损。
|
||||
|
||||
空目录运行 = 验证出厂模板生成;预置有效 `sim_netcfg.bin` = 验证旧档迁移(迁移后删除);无效旧档(CRC 不符)= 保留旧文件 + 生成出厂模板。窗口模式主循环以与 MCU netTask 相同的 2s 周期调用 `cfg_file_poll()`:外部文件变更沉降后自动重载 `[network]` 段(模拟器无网络栈,变更回调为空操作,仅日志可见)。
|
||||
|
||||
---
|
||||
|
||||
## 四、已知坑(构建/运行)
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
* lvgl_sim.exe --keys <seq> shot.bin
|
||||
* seq 字符:u=↑ d=↓ l=← r=→ e=Enter s=Esc(其余字符忽略)
|
||||
* 每键经 lv_group_send_data 注入(与 keypad 输入设备同路径),驱动 5 帧后取快照
|
||||
*
|
||||
* 配置轮询自检模式(无窗口、进程内闭环、退出码 0=PASS / 非0=FAIL):
|
||||
* lvgl_sim.exe --cfgtest
|
||||
* 验证链:net_cfg_load(含旧档迁移)→ poll 建基线 → 伪造外部编辑
|
||||
* (原子写新文档,等价 FTP 上传/文本编辑器)→ poll 两次(4s 沉降)
|
||||
* → 断言 RAM 副本已按新文件重载
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -38,6 +44,8 @@
|
||||
#include "ui_menu.h"
|
||||
#include "ui_net_sink.h"
|
||||
#include "net_cfg.h"
|
||||
#include "cfg_file.h"
|
||||
#include "cfg_file_store.h"
|
||||
|
||||
/* 屏幕尺寸(与 MCU 端一致) */
|
||||
#define SIM_SCREEN_W 160
|
||||
@@ -168,6 +176,89 @@ static int run_headless(const char * keys, const char * path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 伪造"外部编辑"文档(绕过引擎原子写 = 等价 FTP 上传/文本编辑器);
|
||||
* 刻意短于出厂模板,保证 (size, mtime) 变更令牌必变 */
|
||||
static const char s_cfgtest_doc[] =
|
||||
"; cfgtest forged external edit\r\n"
|
||||
"\r\n"
|
||||
"[network]\r\n"
|
||||
"ip=10.0.0.1\r\n"
|
||||
"mask=255.255.255.0\r\n"
|
||||
"gw=10.0.0.254\r\n"
|
||||
"mac=00:00:00:00:00:00\r\n";
|
||||
|
||||
/* 伪造编辑后 RAM 副本应呈现的 IP */
|
||||
static const uint8_t s_cfgtest_ip[4] = { 10u, 0u, 0u, 1u };
|
||||
|
||||
/*
|
||||
* @brief 配置轮询自检(无窗口、无需 LVGL/SDL,进程内闭环)
|
||||
* @note 验证链:net_cfg_load(含旧档迁移)→ poll 建基线 → 伪造外部编辑
|
||||
* → poll 两次(沉降去抖)→ 重载并更新 RAM 副本
|
||||
* @return 0 PASS,非 0 FAIL
|
||||
*/
|
||||
static int run_cfgtest(void)
|
||||
{
|
||||
int i;
|
||||
const net_cfg_t *p_cfg;
|
||||
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
(void)net_cfg_load(); /* 加载 / 旧档迁移(含 [network] 段注册) */
|
||||
|
||||
cfg_file_poll(); /* 第 1 次 poll:建立变更基线 */
|
||||
|
||||
/* 伪造外部编辑(原子写,与 P4 后 FTP STOR 同路径) */
|
||||
if (cfg_store_write_atomic(cfg_store_config_path(),
|
||||
(const uint8_t *)s_cfgtest_doc,
|
||||
strlen(s_cfgtest_doc)) != 0) {
|
||||
fprintf(stderr, "[cfgtest] forge write failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
cfg_file_poll(); /* 第 2 次 poll:检出变更,沉降计数 1 */
|
||||
cfg_file_poll(); /* 第 3 次 poll:沉降完成 → 重载 + 变更回调 */
|
||||
|
||||
p_cfg = net_cfg_get();
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (p_cfg->ip[i] != s_cfgtest_ip[i]) {
|
||||
printf("[cfgtest] FAIL ip after reload = %u.%u.%u.%u (expect 10.0.0.1)\n",
|
||||
(unsigned)p_cfg->ip[0], (unsigned)p_cfg->ip[1],
|
||||
(unsigned)p_cfg->ip[2], (unsigned)p_cfg->ip[3]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("[cfgtest] OK ip after reload = 10.0.0.1\n");
|
||||
|
||||
/* 保存回环:外科手术式保存(目标文件已存在)→ 回读校验注释保留与值一致 */
|
||||
if (net_cfg_save() != 0) {
|
||||
printf("[cfgtest] FAIL net_cfg_save (existing file)\n");
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
uint8_t buf[CFG_FILE_MAX_LEN + 1u];
|
||||
char *p_doc;
|
||||
size_t n = 0u;
|
||||
|
||||
if (cfg_store_read(cfg_store_config_path(), buf, sizeof(buf), &n) != 0) {
|
||||
printf("[cfgtest] FAIL readback open\n");
|
||||
return 1;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
p_doc = (char *)buf;
|
||||
if (strstr(p_doc, "; cfgtest forged external edit") == NULL) {
|
||||
printf("[cfgtest] FAIL comment lost after surgical save\n");
|
||||
return 1;
|
||||
}
|
||||
if (strstr(p_doc, "ip=10.0.0.1") == NULL) {
|
||||
printf("[cfgtest] FAIL value lost after surgical save\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("[cfgtest] OK surgical save (comment kept, value intact)\n");
|
||||
printf("[cfgtest] PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 程序入口
|
||||
*/
|
||||
@@ -181,6 +272,9 @@ int main(int argc, char **argv)
|
||||
if (strcmp(argv[i], "--keys") == 0 && (i + 2) < argc) {
|
||||
return run_headless(argv[i + 1], argv[i + 2]);
|
||||
}
|
||||
if (strcmp(argv[i], "--cfgtest") == 0) {
|
||||
return run_cfgtest();
|
||||
}
|
||||
}
|
||||
|
||||
/* 初始化 Winsock(供 ui_net_sink 使用) */
|
||||
@@ -233,6 +327,9 @@ int main(int argc, char **argv)
|
||||
printf("Keyboard: ↑↓←→ navigate, Enter confirm, Escape back\n");
|
||||
printf("Press Ctrl+C or close window to exit\n\n");
|
||||
|
||||
/* 配置文件轮询起点(2s 周期,与 MCU netTask 一致) */
|
||||
uint32_t cfg_last_poll = (uint32_t)SDL_GetTicks();
|
||||
|
||||
/* 主循环:驱动 LVGL 定时器与刷新
|
||||
* 注意:SDL 事件(键盘/窗口/退出)由 LVGL SDL 驱动内部 5ms 定时器
|
||||
* (lv_sdl_window.c 的 sdl_event_handler)统一轮询分发,
|
||||
@@ -242,6 +339,13 @@ int main(int argc, char **argv)
|
||||
/* 轮询 RemoDispBus 协议(非阻塞收发) */
|
||||
ui_net_sink_poll();
|
||||
|
||||
/* 配置文件轮询(2s 周期:外部文件变更 4s 沉降后自动重载) */
|
||||
uint32_t now_ms = (uint32_t)SDL_GetTicks();
|
||||
if ((uint32_t)(now_ms - cfg_last_poll) >= 2000u) {
|
||||
cfg_last_poll = now_ms;
|
||||
cfg_file_poll();
|
||||
}
|
||||
|
||||
/* 驱动 LVGL 定时器与刷新 */
|
||||
uint32_t sleep_ms = lv_timer_handler();
|
||||
if (sleep_ms == LV_NO_TIMER_READY) {
|
||||
|
||||
@@ -56,8 +56,10 @@ int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nre
|
||||
* 入口参数:p_path - 目标文件路径 const char *
|
||||
* p_buf - 待写数据(不可为 NULL) const uint8_t *
|
||||
* len - 数据长度 size_t
|
||||
* 返回值:0 - 成功;-1 - 写/改名失败(目标文件保持不动,临时文件清理) int
|
||||
* 限定条件:无
|
||||
* 返回值:0 - 成功;-1 - 写/改名失败(临时文件清理) int
|
||||
* 限定条件:MSVC rename() 不覆盖已存在目标(C1117),须先 remove 再 rename
|
||||
* (tmp 已完整写盘关闭后操作,中断窗口仅毫秒级;目标丢失时引擎
|
||||
* 下次加载自动重建出厂模板,与 MCU 端 f_rename 直接替换等效)
|
||||
*/
|
||||
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
|
||||
FILE *fp;
|
||||
@@ -76,6 +78,7 @@ int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len)
|
||||
(void)remove(s_path_tmp);
|
||||
return -1;
|
||||
}
|
||||
(void)remove(p_path); /* 目标可能不存在(首写):remove 失败忽略 */
|
||||
if (rename(s_path_tmp, p_path) != 0) {
|
||||
(void)remove(s_path_tmp);
|
||||
return -1;
|
||||
|
||||
Reference in New Issue
Block a user