4a4ad6bc5f
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名, 不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体 run_boot_test.py 的真实调用。 - AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改 「编译(/keil-build)」「串口回归(/serial-boot-test)」 - keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree 方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围) - 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入 等 41 行历史证据保留旧名 - gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑 - 顺手补并行方案同行漏网计数 51→52 钩子 验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52 钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
109 lines
14 KiB
Markdown
109 lines
14 KiB
Markdown
# 模拟器差距清单(PC 模拟器 vs 真机)
|
||
|
||
> **用途**:把散落在各子系统文档里的"模拟器无法建模 X"汇成**单一事实源**,回答一个关键问题——
|
||
> **模拟器回归通过 ≠ 真机通过,那么差在哪?差多少?哪些必须真机补测?**
|
||
> **性质**:**登记册**(living document)。新增模拟器后端差异时必须在此登记一行,不得只写在别处。
|
||
> **上游依据**:`docs/网络/Net层阻塞模型架构设计.md` §10(net 层 pending 化现状)。
|
||
> **相关**:`UI设计/LVGL模拟器部署与使用说明.md`(部署与使用)、`simulator/README.md`(目录与架构)、
|
||
> `docs/测试说明/流程与规范/测试开发标准流程.md`(模拟器回归门禁在测试分层中的位置)。
|
||
|
||
---
|
||
|
||
## 0. 一句话结论
|
||
|
||
> 模拟器覆盖的是 **「纯逻辑 + 平台抽象之上」的那一层**:UI 场景/渲染/导航、配置引擎、日志记录格式、
|
||
> `net_*` BSD API 之上的协议状态机。它**不覆盖**「硬件时序 / 中断 / 真实复位 / CH395F 与 NAND 芯片行为」。
|
||
> 因此 **模拟器回归与板端测试套件 / 主机侧回归(`/serial-boot-test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。
|
||
|
||
---
|
||
|
||
## 1. 模拟器覆盖范围(基线)
|
||
|
||
| 覆盖 | 说明 |
|
||
|---|---|
|
||
| UI 场景层 | `Drivers/BSP/UI/ui_menu.c` 等**同一份源码**,MCU 与 PC 共用(`simulator/README.md` §2.3) |
|
||
| 渲染链路 | LVGL I1 渲染 → SDL 窗口(MCU 端为帧缓冲 → RemoDispBus) |
|
||
| 配置文件引擎 | `App/cfg/cfg_file.c` 共享核心 + `cfg_file_store_sim.c`(宿主文件 `sim_config.ini`) |
|
||
| `net_*` BSD API 之上的协议 | `ui_net_sink.c`(RemoDispBus 服务端,TCP 7003)与 `net_socket_sim.c`(Winsock 后端) |
|
||
| 日志记录格式 | `sys_log` 定长记录编解码 + 记录删除 + **七类记录**(run / selftest / accid / alarm / start / oper / yk 各一份宿主文件,道F task-10;口径 = `App/log/README.md`「记录类型 = 日志 ID」)。`sim_syslog.c` 写/删宿主文件;删除与投递的**时序**与真机不同,见 §2.2 |
|
||
|
||
**不覆盖**:CH395F 真实网络栈、GD5F NAND、SD2506 RTC、TPAFE5160 ADC、RS-485、IWDG、中断/时序相关行为。
|
||
|
||
---
|
||
|
||
## 2. 差距清单
|
||
|
||
> **列含义**:`模拟器行为` / `真机行为` 描述差异本身;`影响` 说明它会让什么 bug 逃过模拟器回归;
|
||
> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`/serial-boot-test` / 板端套件 / 目检)。
|
||
|
||
### 2.1 网络(`net_socket_sim.c` vs `net_socket.c`)
|
||
|
||
| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||
|---|---|---|---|---|
|
||
| **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | accept 轮询计时失真、lftpd 控制通道 errno 分类、timeSync errno 分级等在模拟器上无法复现 | `/serial-boot-test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 |
|
||
| **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `/serial-boot-test` 网络重配置用例(UI 改 IP 免重启生效) |
|
||
| **消息泵缺失**:~~`net_recv_to` 缺失~~(**已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 |
|
||
| **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `/serial-boot-test` |
|
||
| **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 |
|
||
| **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(无 pending 机制)仍不建模 | —— |
|
||
|
||
### 2.2 日志与配置
|
||
|
||
| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||
|---|---|---|---|---|
|
||
| **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `/serial-boot-test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 |
|
||
| **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 |
|
||
| **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `/serial-boot-test`(配置保存 → UI 生效) |
|
||
| **「清除记录」两端时序不同** | `syslog_request_clear()` 在调用内**同步**删除宿主文件(`sim_syslog.c` 单线程,没有卷锁、没有队列),返回后状态立刻是 `OK`/`FAIL`,`SYSLOG_CLEAR_WAIT` 永不出现 | 非写者上下文只置标志,真正的 `f_unlink` 排在 defaultTask 的 `syslog_drain()` 尾部(卷锁 try:拿不到锁就顺延,见 `App/log/sys_log_mcu.c`) | 「按了按钮 → 下一拍才见结果」这条异步链、卷占用顺延、以及删除与写者重建的交错,在模拟器上**结构上不存在**;UI 页面若把 WAIT 当失败,模拟器上看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-110..122:请求侧零盘触点 / 落地只在 drain / 卷占用顺延 / 幂等 / 不误删;TC-SYSLOG-133/133b:清除成功投一条操作记录、清 oper 自己时不投)+ 台架装置维护页目检 |
|
||
| **两条投递口在宿主端合一** | `syslog_post_quiet()` = `syslog_post()` 的别名(直写宿主文件,没有队列/待发数组),`syslog_post_dropped()` **恒返 0** | 两个口分开且判据不同:quiet 的调用链上不许内联日志行缓冲(protectTask / iec104Task 的静态最坏链硬线),丢弃只累加计数、由 defaultTask 在 `syslog_drain()` 里按差值补留痕 | 五类记录(事故 / 告警 / 启动 / 操作 / 遥控)在模拟器上**永不丢弃**,"队列满丢记录"、"丢了几条"、"投递侧不许打日志"这三件事在模拟器上结构上不可见;UI 若把"投了"当成"落盘了",模拟器看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-132 / 134 / 135:队列侧与启动前数组侧各自的计数与零留痕)+ `scripts/stack_report.py` 静态最坏链(真机侧唯一手段) |
|
||
| **宿主 cwd 的记录文件互相污染** | 七类记录在模拟器落**当前工作目录**的 `sim_<id>.log`(如 `sim_oper.log`);`sim_post_oper()` 在时钟保存 / 网口保存 / 定值保存等**动作页**用例里真投记录 | 每条记录进 `/log/` 下各自 8.3 短名文件,跨页不互相影响(同一进程内不同页读的是同一份 NAND 文件,本来就该看见彼此写入的记录) | 回归若在**同一个 tmp 目录**跑全部页,前面动作页写的 3 条操作留痕会让「操作记录」页拍不到空态 → 空态判据被环境污染掩盖(真机上没有这个问题:写者只有 defaultTask 与事件边沿) | `simulator/tests/check_content_pages.py` 的 `FRESH_CWD` 表:记录 5 页各在独立空目录里跑(其余 55 页环境不变);手工跑 `lvgl_sim.exe` 前先确认当前目录没有 `sim_*.log`/`sim_config.ini` 残留 |
|
||
| **堆后端不同** | `cfg_store_alloc` = 宿主 `malloc`(堆余量以 MB 计) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 48 KB,实测最低水位 ~4.8 KB) | **分配失败路径在模拟器上永不触发**——"KB 级分配必然返回 NULL"这类缺陷模拟器照不出来(`AGENTS.md` 已列为铁律) | 内存预算门禁 + 真机 `[HEAP]` 水位告警(S11) |
|
||
|
||
### 2.3 硬件专属(模拟器完全无对应)
|
||
|
||
| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||
|---|---|---|---|---|
|
||
| **IWDG 硬件行为** | **不编译看门狗模块**(`simulator/` 无 `task_watchdog.c`,也无 IWDG 桩) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑也不在模拟器内跑——改由主机侧单测覆盖 | 真机 `/serial-boot-test`(复位原因日志 + 故障注入 V2/V4)+ 看门狗主机侧纯逻辑单测(39 断言)<br>`看门狗设计说明.md` §4.7 / §7 |
|
||
| **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `/serial-boot-test` |
|
||
| **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `/serial-boot-test` |
|
||
| **SD2506 RTC** | 无 | I2C RTC:时间读写、闹钟、温度、电池电压 | 时间源相关(`sys_clock_init` 降级路径)在模拟器上不可触发 | 板端 RTC 套件(18 断言点)+ `sys_clock` 主机侧纯逻辑单测(已覆盖算术面) |
|
||
| **开机账本 boot_stats(上电次数 / 复位原因快照)** | **不编译** `App/util/boot_stats.c`(依赖 SD2506 用户 SRAM 与 `RCC_CSR`);UI「运行报告」页的四个数在模拟器上是 `simulator/main.c` 的**固定假数据**(`boot_count=7`、`seq_state=延续`、`rst_cause=上电复位`、104 四枚 3 / 128 / 0 / 4096、`uptime_s=0`) |`boot_stats_init()` 读 SRAM → 自增 → 写回 → 快照驻留;四个 104 观测计数由 iec104Task 真实累加(跨重连不清零、断电归零) | 「自增有没有真落 SRAM」「电池域掉电后是否落 RESTARTED」「写失败是否只少计不重复」「104 计数是否跨重连累计」四类事在模拟器上**结构上不可见**;黄金参考只拍得到显示口径(范围标签在不在屏上、值有没有串行、`boot_ready=0` 是否显 `--` 而不是 0) | 主机单测 `test/host/boot_stats/`(SRAM 状态机与四态判定)+ 真机 `/serial-boot-test`:上电一次看 `boot_count` +1、拔电池后落 RESTARTED、IWDG 复位后 `rst_cause=IWDG`、主站连一次看 `conn_up` +1 |
|
||
| **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) |
|
||
| **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) |
|
||
| **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6 已落地) |
|
||
|
||
### 2.4 哪些差异是"有意的对齐"(非缺陷)
|
||
|
||
登记以免后人误判为差距:
|
||
|
||
| 项 | 说明 |
|
||
|---|---|
|
||
| `lv_conf.h` 独立 | 模拟器 `simulator/lv_conf.h` 与 MCU `Lib/lv_conf.h` 相互独立(SDL/主题差异),**有意不统一** |
|
||
| `DBG_*` → `printf` | 模拟器 stub 覆盖 MCU 版头(`simulator/README.md` 文件职责表),**有意** |
|
||
| `NET_NO_OS` 剥离 FreeRTOS | `net_socket_sim.c` 构建时定义,剥离 RTOS 依赖,**有意** |
|
||
| 无 SDL_PollEvent | LVGL SDL 驱动内部统一轮询,主循环轮询会吞键盘事件(`simulator/README.md` 已知坑 7) |
|
||
|
||
---
|
||
|
||
## 3. 什么时候必须回真机(决策规则)
|
||
|
||
模拟器回归通过后,**满足以下任一条件即必须走真机**(`/serial-boot-test` / 板端套件):
|
||
|
||
1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码
|
||
2. **改动触及 §2.3 硬件专属差距的任一子系统**的初始化或运行时行为
|
||
3. **改动触及 §2.2 的 mtime 粒度 / 无应用回调 / 堆后端三条**:变更检测令牌、段回调副作用、内存分配失败路径
|
||
4. **涉及网络重建 / 多连接并发**(代次号恒为 0、单连接 backlog)
|
||
5. **涉及启动段(`osKernelStart` 之前)**(启动段硬件初始化)
|
||
6. **涉及内存预算**:新增任务/缓冲/栈(堆后端不同;模拟器堆不可作依据)
|
||
|
||
反之,**纯 UI 场景/渲染/导航、配置解析算术、日志记录编解码格式**的改动,模拟器 + 主机侧纯逻辑单测可给出足够信心。
|
||
|
||
---
|
||
|
||
## 4. 登记约定
|
||
|
||
- **何时登记**:新增/修改 `simulator/sim/` 下任何后端、或发现"某逻辑模拟器走了不同分支"时,**必须**在 §2 加一行。
|
||
- **格式**:沿用本表列(差距 / 模拟器行为 / 真机行为 / 影响 / 回归补测)。
|
||
- **联动**:若该差距影响模拟器回归门禁的可信度,须同步在 `UI设计/UI_自动化测试流程.md` 引用本清单。
|
||
- **收敛**:当某差距被后续工作**消除**时,不删行——改标 ~~删除线~~ + 注明消除日期与提交,保留"曾经差在哪"的历史。
|