Procházet zdrojové kódy

文档核对修订:全量文档↔源码核对 + P0/P1 订正 + CLAUDE.md/HTML背景补强

以源码+108数据库为基准全量核对项目文档,落地修正(纯文档,无代码改动):
- 阶段1计划(P0):ControlHost Program.cs 的 Log4netHelper 命名空间 ivf_tl_Services→IvfTl.Control.Services(避 CS0234),operate侧保留并加注;行号 67-134→67-132。
- control全景/背景指南/HTML(P1):SQLite "8表"→9表、"不建表"→house_autofocus_calibration由CodeFirst.InitTables自动建;背景指南 #if DEBUG地址 192.168.0.207→最终生效 test-gateway.aivfo.com:36000。
- CLAUDE.md:补 项目背景与上手指南.md 为开机先读第1篇+文档地图;环境权威源改指 服务器测试环境.md(108);新增 C#/.NET 编译段;补 服务器测试环境.md;operation_log 列 15→实测19列。
- 连接配置清单:订正"front 无 Kafka/oplog接入"为有(App.config kfkaIP+App.xaml.cs InitOperationLog 坐实)。
- HTML总图背景段:点明三项目=operate+control+AutoFocusTool(front未合并)+"接半成品"提示。
- 进度三件套(交接卡/进度状态/工作计划表/待验证清单)随之回写。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie před 3 dny
rodič
revize
bfe8e6baee

+ 17 - 14
CLAUDE.md

@@ -11,15 +11,16 @@
 - 第二节(开机先读)与第三节(回写协议)是**硬纪律**,每次都执行。
 
 ## 二、开机先读这些(无缝衔接引擎,按顺序)
-> **当前任务:operate / control 双进程拆分**(旧任务 M0-M8 合并改造已结项、旧文档已清空,本体系为新任务重建)。
-每次新会话 / 关机重启后,严格按此顺序读,读完即可接着干:
-1. `项目文档/进度/进度状态.yaml` —— **轻断点**:当前断点 / 下一步 / 阶段概览(只读这个就知道现在干到哪、下一步干啥)。
-2. `项目文档/进度/工作计划表.md` —— **阶段真实状态 + 顶部「全局概览·续接指南」**(阶段1/2/3 干到哪、文档三层结构、续接三件套一览)。
-3. `项目文档/开发计划/2026-06-22-阶段1-control独立进程骨架.md` —— **当前阶段实现计划**(7 个 bite-sized 任务,标注哪些受真机门控)。
-4. `项目文档/进度/交接卡.md` —— **拉到最后一段** = 最近一次工作的细节。
-5. 按需再读:架构看 `需求文档/specs/2026-06-22-operate-control-双进程拆分-design.md`;现状基线看 `需求文档/control-逻辑与配置全景.md`(control 全部逻辑+配置,接手全图);验证进度看 `进度/待验证清单.md`;全局背景看 `00-需求总览.md`(总纲,勿删)。
+> **当前任务:operate / control 双进程拆分**(旧任务 M0-M8 三项目合并:**代码完成、真机验收整体未做、有 operate 侧降级遗留〔见 `进度/待验证清单.md` M-01~M-07〕**;旧文档已清空,本体系为新任务重建)。
+每次新会话 / 关机重启后,严格按此顺序读,读完即可接着干(**本节为续接顺序的唯一权威**,`项目背景与上手指南.md §四` / `00-需求总览.md §六` 应与本节对齐):
+1. `项目文档/项目背景与上手指南.md` —— **总入口·新会话第一篇**:项目全貌(三桌面程序 + Java 微服务)/当前任务/已定架构/技术栈/已知坑,一篇读懂"在做什么"。
+2. `项目文档/进度/进度状态.yaml` —— **轻断点**:当前断点 / 下一步 / 阶段概览(读这个就知道现在干到哪、下一步干啥)。
+3. `项目文档/进度/工作计划表.md` —— **阶段真实状态 + 顶部「全局概览·续接指南」**(阶段1/2/3 干到哪、文档三层结构、续接三件套一览)。
+4. `项目文档/开发计划/2026-06-22-阶段1-control独立进程骨架.md` —— **当前阶段实现计划**(7 个 bite-sized 任务,标注哪些受真机门控)。
+5. `项目文档/进度/交接卡.md` —— **拉到最后一段** = 最近一次工作的细节。
+6. 按需再读:架构看 `需求文档/specs/2026-06-22-operate-control-双进程拆分-design.md`;现状基线看 `需求文档/control-逻辑与配置全景.md`(control 全图)+ `需求文档/操作端逻辑与配置全景.md`(operate 全图,含**§八 合并降级登记**);验证进度看 `进度/待验证清单.md`;全局背景看 `00-需求总览.md`(总纲,勿删)。
 
-⚠ 环境/账号信息以 `项目文档/开发环境/环境与账号清单.md` 与用户实际部署为准
+⚠ 环境/账号信息以 `项目文档/开发环境/服务器测试环境.md`(**当前实际部署 = 108 原生环境,权威**)+ `连接配置清单-换服务器必读.md` 与用户实际部署为准;`环境与账号清单.md` 描述的是**本机 Docker 备用环境**(compose `tl-mw`),非当前运行环境,勿据它定连接信息
 
 ## 三、回写协议 —— 硬纪律(文档不滞后引擎)
 
@@ -45,11 +46,13 @@
 
 ## 四、文档地图(`项目文档/` 下,按需深入查)
 > 当前任务 = operate/control 双进程拆分。旧任务(M0-M8)文档已清空。
+- `项目背景与上手指南.md`   ★**新会话第一篇总入口**(项目全貌/当前任务/已定架构/技术栈/已知坑);开机先读顺序见第二节。
 - `00-需求总览.md`            新任务总纲(为什么拆/目标/范围/关键决策,勿删)。
-- `需求文档/`              *做什么*:`specs/2026-06-22-operate-control-双进程拆分-design.md`(架构设计,9章含拓扑/HTTP契约/生命周期/分阶段)+ `control-逻辑与配置全景.md`(现状基线:control 全部逻辑+全配置参数,标 file:line,接手全图)。
+- `需求文档/`              *做什么*:`specs/2026-06-22-operate-control-双进程拆分-design.md`(架构设计,9章含拓扑/HTTP契约/生命周期/分阶段)+ `control-逻辑与配置全景.md`(control 现状基线)+ `操作端逻辑与配置全景.md`(operate 现状基线:启动/业务模块/交接面/HTTP/配置 + **§八 合并降级登记**,标 file:line)。
 - `开发计划/`              *怎么做*:`2026-06-22-阶段1-control独立进程骨架.md`(阶段1 七任务,可执行)。阶段2/3 待拆。
+- `系统业务流程详图.html`  ★开发上手总文档(单页可视化,浏览器打开):汇总背景/架构/两份全景/阶段1计划/降级登记,读一篇即懂「为什么改→改成什么样→现在什么样→接下来改什么→怎么上手」。
 - `进度/`              续接回写文件(进度状态.yaml=断点 / 工作计划表.md=阶段状态 / 交接卡.md=追加历史 / 待验证清单.md=真机门控)+ `进度数据.js` / `监控面板.html`(实时面板)。
-- `开发环境/`            `环境与账号清单.md`——本机环境、版本、账号/凭证、Docker 排障记录(编译与部署先查这里);`连接配置清单-换服务器必读.md`;`start-all.sh`(一键起微服务集群)。
+- `开发环境/`            `服务器测试环境.md`——**当前实际中间件部署(108 原生环境,权威·编译部署先查这里)**;`连接配置清单-换服务器必读.md`——换服务器逐项改 IP/端口/账号;`环境与账号清单.md`——本机环境/版本/账号凭证/Docker 备用环境排障记录;`start-all.sh`(一键起微服务集群)。
 
 ## 五、排障利器:全量操作日志(运行期能力,旧任务遗留、仍有效)
 - 系统做了"全量操作日志":C#/Java 所有操作记 谁/功能/输入/输出/报错/结果,经 Kafka→日志微服务 `aivfo-oplog`→`log` 库 `operation_log` 表,跨端共用 `trace_id`。
@@ -70,7 +73,7 @@
 3. operate/front 的 `App.config` `kfkaIP=192.168.0.108`、topic=`tl-oplog`,产消息端就绪。**不起 oplog 则消息只堆 Kafka 不入库**(这是"没记录"的最常见原因)。
 
 ### 5.3 怎么查库(本机无 mysql 客户端时,用 JDK11 + 驱动直查)
-驱动:`C:/TLData/tools/maven-repo/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar`;库:`jdbc:mysql://192.168.0.108:3306/log`(root/root)。写个单文件 `临时文件/Q.java` 用 `java -cp <驱动> 临时文件/Q.java` 跑。`operation_log` 关键列:`id/trace_id/op_time/project/module/operation/operator/input/output/result/error/elapsed_ms/house_sn/well_sn/tl_sn`。常用查询:
+驱动:`C:/TLData/tools/maven-repo/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar`;库:`jdbc:mysql://192.168.0.108:3306/log`(root/root)。写个单文件 `临时文件/Q.java` 用 `java -cp <驱动> 临时文件/Q.java` 跑。`operation_log` 实测 **19 列**:`id/trace_id/parent_id/op_time/project/module/operation/operator/input/output/result/error/elapsed_ms/level/house_sn/well_sn/tl_sn/host/create_time`(`parent_id`=父子调用串联、`create_time`=入库时间、`level`=日志级、`host`=来源主机)。常用查询:
 - 最近 N 条:`SELECT id,op_time,project,module,operation,result FROM operation_log ORDER BY id DESC LIMIT 20`
 - 拉一次操作跨端时间线:`... WHERE trace_id='xxx' ORDER BY op_time`
 - 只看失败:`... WHERE result='失败' ORDER BY id DESC LIMIT 20`(读对应 input+error)
@@ -78,9 +81,9 @@
 - 终端里中文显示乱码是控制台编码问题,库里是正确 UTF-8。
 
 ## 六、编译环境(若需本地编译)
-- JDK 11.0.25 + Maven 3.9.9 @ `C:\TLData\tools`;本地仓库 `C:\TLData\tools\maven-repo`
-- Nexus 私服凭证已配在 `~/.m2/settings.xml`(admin);详细环境/账号见 `项目文档/开发环境/环境与账号清单.md`。
-- ★本地多仓构建顺序:先 `mvn -DskipTests install` 装 **aivfo-framework**(底座,提供 com.aivfo SNAPSHOT),再编译依赖它的各微服务,否则报 Could not find artifact。
+- **C#/.NET(当前任务 operate/control/front 用这套)**:目标框架 `net6.0-windows`(仅 `control/IvfTl.AutoFocus.Tests` 为 net8.0;本机装 .NET 8.0.x SDK,可编 net6.0)。解决方案:`ivf_tl_operate_2.0/ivf_tl_Operate.sln`(operate,含 control 子树)/ `ivf_tl_operate_2.0/control/ivf_tl_Control.sln`(control 类库)/ `aivfo-front-manament-2.0/ivf_tl_Manage.sln`(front)。编译 `dotnet build <csproj|sln> -c Debug|Release`;**真机/连内网必须 Release**(Debug 的 `#if DEBUG` 覆写服务器地址到 test-gateway 外网,见 operate 全景 §十 / 待验证清单 M-07);operate.exe 正运行会锁 DLL 报 MSB3021,先关 operate 再编
+- **Java 微服务**:JDK 11.0.25 + Maven 3.9.9 @ `C:\TLData\tools`;本地仓库 `C:\TLData\tools\maven-repo`。Nexus 私服凭证已配在 `~/.m2/settings.xml`(admin);详细环境/账号见 `项目文档/开发环境/环境与账号清单.md`。
+- ★Java 本地多仓构建顺序:先 `mvn -DskipTests install` 装 **aivfo-framework**(底座,提供 com.aivfo SNAPSHOT),再编译依赖它的各微服务,否则报 Could not find artifact。
 
 ## 七、Git(改动可追溯/可回退)
 - 仓库:`http://git.aivfo.com:36000/huangjie/aivfo-tl-3.0`,主分支 `main`,仓库名 `aivfo-tl-3.0`。

+ 3 - 1
项目文档/00-需求总览.md

@@ -1,6 +1,6 @@
 # 00 · 需求总览(总纲,勿删)
 
-> **新任务**:operate / control 双进程拆分。旧任务(三项目合并改造 M0–M8)已结项,旧文档已清空,本体系为新任务重建。
+> **新任务**:operate / control 双进程拆分。旧任务(三项目合并改造 M0–M8)**代码完成、真机验收整体未做、有 operate 侧降级遗留(见 `进度/待验证清单.md` M-01~M-07 与 `需求文档/操作端逻辑与配置全景.md` §八)**;旧文档已清空,本体系为新任务重建。
 > 本文件是总纲,指向各专题文档。续接靠"进度/"三件套(见第五节)。
 
 ## 一、新任务是什么(一句话)
@@ -42,6 +42,8 @@ operate 与 control 原是两个独立软件,"合并"只为代码管理方便,
 - **本文件** `00-需求总览.md` —— 总纲(勿删)。
 - `需求文档/specs/2026-06-22-operate-control-双进程拆分-design.md` —— **架构设计**(做什么/怎么连,9 章含拓扑图、HTTP 契约、生命周期、分阶段)。
 - `需求文档/control-逻辑与配置全景.md` —— **现状基线**(control 全部逻辑 + 全部配置参数,标 file:line;接手代码的全图)。
+- `需求文档/操作端逻辑与配置全景.md` —— **现状基线(operate 侧)**(启动时序 / 业务模块逐功能 / operate↔control 交接面 / HTTP 接口全表 / 配置 + **§八 合并改动·降级·待验证登记**,标 file:line)。
+- `系统业务流程详图.html` —— **★开发上手总文档(单页·可视化·汇总入口)**:给刚接手的开发一篇读懂「为什么改造→改成什么样→现在什么样→接下来改什么→怎么上手」。汇总自背景/架构设计/两份全景/阶段1计划/降级登记,细化到每个功能、标 file:line;浏览器直接打开。
 - `开发计划/2026-06-22-阶段1-control独立进程骨架.md` —— **阶段1 实现计划**(7 个 bite-sized 任务,可执行)。
 - `进度/` —— **续接三件套 + 实时面板**:
   - `进度状态.yaml` = 轻断点(现在干到哪、下一步)。**开机先读这个。**

+ 3 - 2
项目文档/开发环境/连接配置清单-换服务器必读.md

@@ -22,7 +22,7 @@
 | **MySQL** | 192.168.0.108:**3306** | root | root | Java 各服务 jdbcUrl;C# 不直连 |
 | **Redis** | 192.168.0.108:**6379** | — | **123456** | ✅实测可连(+PONG)但 **全仓代码无任何服务使用 Redis**(无 `data-redis`/`RedisTemplate`/`Jedis`/`Redisson` 依赖)→ 部署了但不参与运行,换机**无需为它改任何配置** |
 | **Nacos** | 192.168.0.108:**8848**(+9848) | nacos | nacos | Java `spring.cloud.nacos.discovery.server-addr` |
-| **Kafka** | 192.168.0.108:**9092** | 无 | 无 | Java `aivfo.kafka.properties.ips`;C# operate 的 `kfkaIP`(oplog) |
+| **Kafka** | 192.168.0.108:**9092** | 无 | 无 | Java `aivfo.kafka.properties.ips`;C# operate/front 的 `kfkaIP`(oplog) |
 | **FastDFS** | tracker **22122** / storage 23000 / nginx **8888** | 无 | 无 | Java `aivfo.dfs.fastdfs.trackerServers`(只一个 tracker:22122) |
 | **MQTT**(Mosquitto) | 192.168.0.108:**1883** | aivfo | aivfo | Java `aivfo.mqtt.clients[0].url`;C# `mqttIp` |
 
@@ -84,8 +84,9 @@ App.config 键 → 代码读取处 `AppData.cs:83-86`:
 | `outInter` | 内/外网 | 0 | 0 |
 | `urlIp` + `urlPort` | gateway 地址 | http://192.168.1.92 : 10010 | ⚠ gateway 所在机(待按 front 实际部署机定) |
 | `mqttIp` + `mqttPort` | MQTT broker | 192.168.0.108 : 1883 | 192.168.0.108 |
+| `kfkaIP` + `kfkaPort` | **Kafka**(oplog 操作日志发送端) | 192.168.0.108 : 9092 | Kafka 所在机=192.168.0.108 |
 
-> front 无 Kafka/oplog 接入。硬编码默认 `AppData.cs:123-125`。
+> ⚠ 订正(2026-06-22 源码核实):front **有** Kafka/oplog 接入 —— `ivf_tl_Manage/App.config` 含 `kfkaIP/kfkaPort`,`App.xaml.cs` 的 `InitOperationLog()`(project=front、topic=`tl-oplog`)启动 Kafka 产消息端,oplog 模块仅 界面点击/HTTP(详见 CLAUDE.md §5.1)。硬编码默认 BaseUrl/MqttIp 在 `AppData.cs:123-125`。
 
 ### control(历史路径 `ivf_tl_control_2.0/ivf_tl_ControlTest/App.config`,合并后已并入 `ivf_tl_operate_2.0/control/`)
 含 `urlIp/kfkaIP/mqttIp`(值 192.168.0.91),但 **control 合并为 operate 单进程后,运行期读 operate 的 App.config,ControlTest 这份失效**(且该工程计划退役,见 01 §5.5)。**换机无需改它。**

+ 16 - 14
项目文档/开发计划/2026-06-22-阶段1-control独立进程骨架.md

@@ -37,7 +37,7 @@
 operate 侧修改:
 | 文件 | 改动 |
 |---|---|
-| `ivf_tl_Operate/MainWindow.xaml.cs:67-134` | 删除内嵌 control 那段 `Task.Run`,改为调用新的 `ControlProcessLauncher.EnsureRunning(...)` |
+| `ivf_tl_Operate/MainWindow.xaml.cs:67-132` | 删除内嵌 control 那段 `Task.Run`,改为调用新的 `ControlProcessLauncher.EnsureRunning(...)` |
 | `ivf_tl_Operate/Helpers/ControlProcessLauncher.cs`(新建) | 探 `/ping`→不在则 Process.Start 拉起 control.exe(传参)→轮询就绪 |
 | `ivf_tl_Operate/App.config:67` 后 | 加 `controlPort`、`controlExePath` 两个 key |
 
@@ -544,6 +544,7 @@ using System;
 using System.Diagnostics;
 using System.Threading;
 using ivf_tl_Control;                 // AppData, StartMain
+using IvfTl.Control.Services;         // Log4netHelper —— ⚠ control 子树命名空间是 IvfTl.Control.Services(程序集名 ivf_tl_Control_Services),切勿写成 operate 端的 ivf_tl_Services,否则 CS0234 编译失败
 using IvfTl.Control.Entity.GlobalEnums; // LogEnum
 
 namespace IvfTl.ControlHost
@@ -562,33 +563,33 @@ namespace IvfTl.ControlHost
             _singleton = new Mutex(true, @"Global\ivf_tl_control_singleton", out isNew);
             if (!isNew)
             {
-                ivf_tl_Services.Log4netHelper.WriteLog("ControlHost: 已有实例在运行,本进程退出");
+                Log4netHelper.WriteLog("ControlHost: 已有实例在运行,本进程退出");
                 return 0;
             }
 
             try
             {
                 var hostArgs = HostArgs.Parse(args);
-                ivf_tl_Services.Log4netHelper.WriteLog($"ControlHost 启动 port={hostArgs.Port} account={hostArgs.Account}");
+                Log4netHelper.WriteLog($"ControlHost 启动 port={hostArgs.Port} account={hostArgs.Account}");
 
                 // 2) 先起 HTTP(让 operate 能尽早探到"在启动中"),started=false。
                 _http = new ControlHttpServer(
                     hostArgs.Port,
                     BuildStatus,
-                    msg => ivf_tl_Services.Log4netHelper.WriteLog(msg));
+                    msg => Log4netHelper.WriteLog(msg));
                 _http.Start();
 
                 // 3) 账号守卫(对齐 operate 空账号跳过逻辑)。
                 if (!hostArgs.IsValid)
                 {
-                    ivf_tl_Services.Log4netHelper.WriteLog("ControlHost: 账号/密码为空,不启动采集(仅 HTTP 存活)");
+                    Log4netHelper.WriteLog("ControlHost: 账号/密码为空,不启动采集(仅 HTTP 存活)");
                 }
                 else
                 {
                     // 4) 复刻 operate MainWindow 启动序(顺序不可变)。
                     if (!AppData.Instance.Login(hostArgs.Account, hostArgs.Password))
                     {
-                        ivf_tl_Services.Log4netHelper.WriteLog("ControlHost: control 登录失败");
+                        Log4netHelper.WriteLog("ControlHost: control 登录失败");
                     }
                     else
                     {
@@ -600,23 +601,23 @@ namespace IvfTl.ControlHost
                         try
                         {
                             IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.Log =
-                                msg => ivf_tl_Services.Log4netHelper.WriteLog(msg);
+                                msg => Log4netHelper.WriteLog(msg);
                             var devices = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ScanDevices();
-                            ivf_tl_Services.Log4netHelper.WriteLog($"ControlHost: HAL 发现 {devices.Count} 个舱");
+                            Log4netHelper.WriteLog($"ControlHost: HAL 发现 {devices.Count} 个舱");
                         }
                         catch (Exception hex)
                         {
-                            ivf_tl_Services.Log4netHelper.WriteLog("ControlHost: HAL 发现异常(降级):" + hex.Message);
+                            Log4netHelper.WriteLog("ControlHost: HAL 发现异常(降级):" + hex.Message);
                         }
 
                         var startMain = new StartMain();
                         string err = startMain.StartRun(); // 阻塞:InitTL→InitHouse→StartAsync
                         if (!string.IsNullOrEmpty(err))
-                            ivf_tl_Services.Log4netHelper.WriteLog("ControlHost: control 启动失败:" + err);
+                            Log4netHelper.WriteLog("ControlHost: control 启动失败:" + err);
                         else
                         {
                             _started = true;
-                            ivf_tl_Services.Log4netHelper.WriteLog("ControlHost: control 启动成功,常驻运行");
+                            Log4netHelper.WriteLog("ControlHost: control 启动成功,常驻运行");
                         }
                     }
                 }
@@ -629,7 +630,7 @@ namespace IvfTl.ControlHost
             }
             catch (Exception ex)
             {
-                ivf_tl_Services.Log4netHelper.WriteLog("ControlHost 致命异常", ex);
+                Log4netHelper.WriteLog("ControlHost 致命异常", ex);
                 return 1;
             }
             finally
@@ -687,7 +688,7 @@ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
 
 **Files:**
 - Create: `ivf_tl_operate_2.0/ivf_tl_Operate/Helpers/ControlProcessLauncher.cs`
-- Modify: `ivf_tl_operate_2.0/ivf_tl_Operate/MainWindow.xaml.cs:67-134`
+- Modify: `ivf_tl_operate_2.0/ivf_tl_Operate/MainWindow.xaml.cs:67-132`
 - Modify: `ivf_tl_operate_2.0/ivf_tl_Operate/App.config`(加 controlPort/controlExePath)
 
 - [ ] **Step 1: App.config 加两个 key**
@@ -782,7 +783,7 @@ namespace ivf_tl_Operate.Helpers
 
 - [ ] **Step 3: 替换 MainWindow.xaml.cs 内嵌段**
 
-把 `MainWindow.xaml.cs` 第 67-134 行的整个 `System.Threading.Tasks.Task.Run(() => { ... });` 块(从 `// M1-01 步骤3` 注释起到该 Task.Run 闭合)替换为:
+把 `MainWindow.xaml.cs` 第 67-132 行的整个 `System.Threading.Tasks.Task.Run(() => { ... });` 块(从 `// M1-01 步骤3` 注释起到该 Task.Run 闭合)替换为:
 ```csharp
             // 双进程改造:control 已剥离为独立进程 ivf_tl_ControlHost.exe。
             // operate 登录后只负责"确保 control 在跑"(探活→不在则拉起→轮询就绪),
@@ -811,6 +812,7 @@ namespace ivf_tl_Operate.Helpers
             });
 ```
 > 删掉的旧逻辑(进程内 Login/ScanDevices/StartRun)已迁入 ControlHost.Program。`using ivf_tl_Control;` 等若变成未使用,可留(不影响编译)或清理。
+> ⚠ 注意:本段在 operate 项目里,`Log4netHelper` 的命名空间就是 `ivf_tl_Services`(operate 端),**保持 `ivf_tl_Services.Log4netHelper` 正确,勿改成 control 端的 `IvfTl.Control.Services`**。与 Step 1 的 ControlHost.Program 是两个不同程序集的同名类——别混。
 
 - [ ] **Step 4: 编译 operate**
 

+ 779 - 0
项目文档/系统业务流程详图.html

@@ -0,0 +1,779 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>时差培养箱 · 系统改造需求 + 业务流程总文档(开发上手必读)</title>
+<style>
+:root{
+  --c-control:#2563eb; --c-control-bg:#eff6ff;
+  --c-operate:#16a34a; --c-operate-bg:#f0fdf4;
+  --c-server:#7c3aed; --c-server-bg:#f5f3ff;
+  --c-hw:#0891b2;      --c-hw-bg:#ecfeff;
+  --c-db:#64748b;      --c-db-bg:#f8fafc;
+  --c-down:#dc2626;    --c-down-bg:#fef2f2;
+  --c-new:#ea580c;     --c-new-bg:#fff7ed;
+  --ink:#1e293b; --muted:#64748b; --line:#e2e8f0; --bg:#eef2f6;
+}
+*{box-sizing:border-box;margin:0;padding:0}
+body{font-family:"Microsoft YaHei","Segoe UI",system-ui,sans-serif;color:var(--ink);background:var(--bg);line-height:1.65;font-size:14px}
+a{color:inherit;text-decoration:none}
+.layout{display:flex;align-items:flex-start}
+nav.toc{position:sticky;top:0;width:268px;height:100vh;overflow-y:auto;background:#0f172a;color:#cbd5e1;padding:16px 13px;flex-shrink:0}
+nav.toc h2{font-size:14px;color:#fff;margin-bottom:8px;padding-bottom:7px;border-bottom:1px solid #334155}
+nav.toc .part{font-size:11px;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin:13px 0 3px;padding-left:6px;font-weight:700}
+nav.toc a{display:block;padding:4px 8px;border-radius:6px;font-size:12.5px;color:#94a3b8;margin:1px 0}
+nav.toc a:hover{background:#1e293b;color:#fff}
+nav.toc a.sub{padding-left:20px;font-size:11.5px;color:#7c8aa0}
+nav.toc .tag{font-size:9.5px;padding:1px 5px;border-radius:4px;margin-left:3px;color:#fff}
+main{flex:1;max-width:1080px;margin:0 auto;padding:24px 32px 90px}
+header.hero{background:linear-gradient(120deg,#1e3a8a,#0f766e);color:#fff;border-radius:14px;padding:28px 32px;margin-bottom:14px;box-shadow:0 8px 24px rgba(15,23,42,.18)}
+header.hero h1{font-size:24px;margin-bottom:7px}
+header.hero p{opacity:.93;font-size:13px;max-width:820px}
+.legend{display:flex;flex-wrap:wrap;gap:8px;margin-top:15px}
+.legend span{font-size:12px;padding:3px 10px;border-radius:20px;background:rgba(255,255,255,.16);color:#fff}
+.legend .dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px;vertical-align:middle}
+.readme{background:#fff;border:1px solid var(--line);border-left:5px solid #0f766e;border-radius:10px;padding:14px 18px;margin-bottom:20px;font-size:13px}
+.readme b{color:#0f766e}
+.part-banner{font-size:13px;font-weight:700;color:#fff;background:#334155;border-radius:8px;padding:7px 16px;margin:26px 0 12px;letter-spacing:.5px}
+.part-banner.a{background:linear-gradient(90deg,#7c2d12,#9a3412)}
+.part-banner.b{background:linear-gradient(90deg,#1e3a8a,#1d4ed8)}
+.part-banner.c{background:linear-gradient(90deg,#065f46,#047857)}
+.part-banner.d{background:linear-gradient(90deg,#7f1d1d,#b91c1c)}
+.part-banner.e{background:linear-gradient(90deg,#4338ca,#6d28d9)}
+section{background:#fff;border-radius:12px;padding:20px 26px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.06);border:1px solid var(--line)}
+section>h2{font-size:18.5px;margin-bottom:3px;display:flex;align-items:center;gap:9px;cursor:pointer}
+section>h2 .num{background:#0f172a;color:#fff;border-radius:8px;min-width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;padding:0 4px}
+section>h2 .chev{margin-left:auto;color:var(--muted);font-size:14px;transition:.2s}
+section.collapsed>h2 .chev{transform:rotate(-90deg)}
+section.collapsed>.body{display:none}
+.lead{color:var(--muted);font-size:13px;margin:6px 0 14px;padding-left:39px}
+h3{font-size:15.5px;margin:20px 0 9px;padding-left:11px;border-left:4px solid var(--c-control);color:#0f172a}
+h4{font-size:13.5px;margin:14px 0 6px;color:#334155}
+p{margin:7px 0}
+p.note{font-size:12.5px;color:var(--muted)}
+code{background:#f1f5f9;padding:1px 6px;border-radius:5px;font-family:Consolas,monospace;font-size:12px;color:#0f766e}
+.fl{color:#94a3b8;font-family:Consolas,monospace;font-size:11px}
+.flow{margin:13px 0}
+.step{position:relative;padding:10px 14px 10px 46px;margin:0 0 0 16px;border-left:2px dashed #cbd5e1}
+.step:last-child{border-left:2px solid transparent}
+.step .idx{position:absolute;left:-15px;top:9px;width:30px;height:30px;border-radius:50%;background:var(--c-control);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;box-shadow:0 0 0 4px #fff}
+.step .box{background:var(--c-control-bg);border:1px solid #bfdbfe;border-radius:9px;padding:9px 13px}
+.step .box b{color:#1e3a8a}
+.step.op .idx{background:var(--c-operate)} .step.op .box{background:var(--c-operate-bg);border-color:#bbf7d0} .step.op .box b{color:#14532d}
+.step.sv .idx{background:var(--c-server)} .step.sv .box{background:var(--c-server-bg);border-color:#ddd6fe} .step.sv .box b{color:#5b21b6}
+.step.hw .idx{background:var(--c-hw)} .step.hw .box{background:var(--c-hw-bg);border-color:#a5f3fc} .step.hw .box b{color:#155e75}
+.step.dn .idx{background:var(--c-down)} .step.dn .box{background:var(--c-down-bg);border-color:#fecaca} .step.dn .box b{color:#991b1b}
+.step small{display:block;color:var(--muted);font-size:11.5px;margin-top:3px}
+.chain{display:flex;flex-wrap:wrap;align-items:stretch;gap:0;margin:12px 0}
+.chain .node{background:var(--c-control-bg);border:1px solid #bfdbfe;border-radius:8px;padding:8px 11px;font-size:12px;min-width:78px;display:flex;flex-direction:column;justify-content:center}
+.chain .node b{font-size:12.5px}
+.chain .node.op{background:var(--c-operate-bg);border-color:#bbf7d0}
+.chain .node.sv{background:var(--c-server-bg);border-color:#ddd6fe}
+.chain .node.hw{background:var(--c-hw-bg);border-color:#a5f3fc}
+.chain .node.db{background:var(--c-db-bg);border-color:#cbd5e1}
+.chain .arrow{display:flex;align-items:center;color:#94a3b8;font-size:18px;padding:0 6px}
+table{width:100%;border-collapse:collapse;margin:10px 0;font-size:12.5px}
+th,td{border:1px solid var(--line);padding:6px 9px;text-align:left;vertical-align:top}
+th{background:#f8fafc;color:#334155;font-weight:600}
+tr:nth-child(even) td{background:#fcfdfe}
+td.c{white-space:nowrap}
+.b{display:inline-block;font-size:10.5px;padding:1px 7px;border-radius:5px;color:#fff;font-weight:600;white-space:nowrap}
+.b.control{background:var(--c-control)} .b.operate{background:var(--c-operate)}
+.b.server{background:var(--c-server)} .b.hw{background:var(--c-hw)}
+.b.db{background:var(--c-db)} .b.down{background:var(--c-down)} .b.new{background:var(--c-new)}
+.b.o{background:#fff;border:1px solid currentColor}
+.callout{border-radius:9px;padding:12px 15px;margin:13px 0;font-size:12.8px}
+.callout.warn{background:var(--c-down-bg);border:1px solid #fecaca;color:#7f1d1d}
+.callout.info{background:#eff6ff;border:1px solid #bfdbfe;color:#1e3a8a}
+.callout.new{background:var(--c-new-bg);border:1px solid #fed7aa;color:#9a3412}
+.callout.ok{background:#f0fdf4;border:1px solid #bbf7d0;color:#14532d}
+.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
+.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}
+@media(max-width:980px){.grid2,.grid3{grid-template-columns:1fr}nav.toc{display:none}}
+.vs{display:grid;grid-template-columns:1fr auto 1fr;gap:0;align-items:stretch;margin:14px 0}
+.vs .col{border-radius:10px;padding:13px 16px}
+.vs .before{background:var(--c-down-bg);border:1px solid #fecaca}
+.vs .after{background:var(--c-operate-bg);border:1px solid #bbf7d0}
+.vs .mid{display:flex;align-items:center;color:#94a3b8;font-size:24px;padding:0 12px}
+.vs h4{margin-top:0}
+.vs ul{margin:6px 0 0 16px;font-size:12.3px}
+.vs li{margin:4px 0}
+.pill-row{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}
+.pill{font-size:11.5px;background:#f1f5f9;border:1px solid var(--line);border-radius:16px;padding:3px 10px}
+.kicker{font-size:11px;font-weight:700;letter-spacing:.5px;color:var(--muted);text-transform:uppercase}
+ol.clean{margin:6px 0 6px 20px}ol.clean li{margin:5px 0}
+ul.clean{margin:6px 0 6px 18px}ul.clean li{margin:4px 0}
+hr.sep{border:none;border-top:1px dashed var(--line);margin:14px 0}
+.tasknum{display:inline-flex;width:22px;height:22px;border-radius:50%;background:#1d4ed8;color:#fff;font-size:11px;align-items:center;justify-content:center;margin-right:6px;font-weight:700}
+</style>
+</head>
+<body>
+<div class="layout">
+<nav class="toc">
+  <h2>📖 总文档目录</h2>
+  <div class="part">A · 为什么改造</div>
+  <a href="#what">0 · 项目是什么</a>
+  <a href="#bg">1 · 改造背景与三大问题</a>
+  <a href="#goal">2 · 目标·范围·关键决策</a>
+  <div class="part">B · 改成什么样</div>
+  <a href="#arch">3 · 目标架构(双进程)</a>
+  <a href="#http-contract" class="sub">3.1 本地HTTP接口契约</a>
+  <a href="#lifecycle" class="sub">3.2 生命周期管理</a>
+  <div class="part">C · 现在是什么样(现状流程)</div>
+  <a href="#overview">4 · 当前系统拓扑</a>
+  <a href="#boot">5 · 启动与登录</a>
+  <a href="#loop">6 · 采集主循环 <span class="tag" style="background:#2563eb">control</span></a>
+  <a href="#photo">7 · 拍照 / 对焦四步</a>
+  <a href="#air">8 · 换气 / 缓冲瓶排队</a>
+  <a href="#server">9 · 服务器双向交互</a>
+  <a href="#operate">10 · operate 模块逐功能 <span class="tag" style="background:#16a34a">operate</span></a>
+  <a href="#hal">11 · 硬件层 / 借串口</a>
+  <a href="#data">12 · 数据库与数据流</a>
+  <div class="part">D · 接下来改什么</div>
+  <a href="#degrade">13 · ★合并降级登记 <span class="tag" style="background:#dc2626">必读</span></a>
+  <a href="#roadmap">14 · 三阶段路线图</a>
+  <a href="#stage1" class="sub">14.1 阶段1·七个任务</a>
+  <a href="#split">15 · 双进程8个改造点</a>
+  <div class="part">E · 怎么上手</div>
+  <a href="#env">16 · 技术栈 / 环境 / 构建</a>
+  <a href="#pitfall">17 · 已知坑</a>
+  <a href="#howto">18 · 开工方式</a>
+</nav>
+<main>
+<header class="hero">
+  <h1>🧬 时差培养箱 · 系统改造需求 + 业务流程总文档</h1>
+  <p>给<b>刚接手、不熟悉代码与需求</b>的开发:读这一篇即可掌握「为什么改造 → 改成什么样 → 现在是什么样 → 接下来改什么 → 怎么上手」。流程细化到每个功能,关键处标 <code>file:line</code>(2026-06-22 源码复核)。</p>
+  <div class="legend">
+    <span><i class="dot" style="background:#2563eb"></i>control 机器驱动</span>
+    <span><i class="dot" style="background:#16a34a"></i>operate 操作界面</span>
+    <span><i class="dot" style="background:#7c3aed"></i>服务器/中央</span>
+    <span><i class="dot" style="background:#0891b2"></i>硬件/下位机</span>
+    <span><i class="dot" style="background:#64748b"></i>数据库</span>
+    <span><i class="dot" style="background:#dc2626"></i>合并降级</span>
+    <span><i class="dot" style="background:#ea580c"></i>合并新增</span>
+  </div>
+</header>
+
+<div class="readme">
+  <b>怎么读这份文档:</b> 全文分 5 部分 —— <b>A 为什么改造</b>(背景,先建立认知)→ <b>B 改成什么样</b>(目标架构,你要交付的)→ <b>C 现在是什么样</b>(现状全量业务流程,你要在它上面动手)→ <b>D 接下来改什么</b>(降级坑 + 三阶段路线图 + 你从哪开始)→ <b>E 怎么上手</b>(环境/构建/已知坑)。每节标题可点击折叠。配色见上方图例。<br>
+  <b>权威基线文档(本页是它们的汇总入口):</b> <code>需求文档/control-逻辑与配置全景.md</code>、<code>需求文档/操作端逻辑与配置全景.md</code>、<code>需求文档/specs/...双进程拆分-design.md</code>、<code>开发计划/2026-06-22-阶段1-control独立进程骨架.md</code>。改代码前查对应全景。
+</div>
+
+<!-- ============================ PART A ============================ -->
+<div class="part-banner a">A · 为什么改造(背景认知)</div>
+
+<section id="what">
+  <h2><span class="num">0</span> 这是什么项目 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead"><b>时差培养箱(IVF 胚胎培养)软件系统</b>:培养箱 7×24 自动培养胚胎,定时拍照记录胚胎发育延时影像,数据上传服务器供医生查看打分。</p>
+  <table>
+    <tr><th>程序</th><th>目录</th><th>角色</th><th>跑在哪</th></tr>
+    <tr><td><span class="b operate">operate</span></td><td><code>ivf_tl_operate_2.0/</code></td><td>培养箱<b>本机操作界面</b>(舱室/调试/对焦/配置/监控)</td><td>培养箱机器</td></tr>
+    <tr><td><span class="b control">control</span></td><td><code>ivf_tl_operate_2.0/control/</code><br>(现为类库,被 operate 进程内托管)</td><td><b>机器驱动大脑</b>:串口控下位机、拍照、换气补气、自动对焦、图片上传</td><td>培养箱机器</td></tr>
+    <tr><td><span class="b" style="background:#0d9488">front</span></td><td><code>aivfo-front-manament-2.0/</code></td><td>管理端/医生工作站:患者管理、胚胎打分、出报告</td><td>医生办公电脑</td></tr>
+    <tr><td><span class="b server">Java 微服务</span></td><td>gateway / tl-control / business-manage / data-transmission / oplog 等</td><td>网关/业务/数据传输/操作日志</td><td>服务器(开发时 108+本机)</td></tr>
+  </table>
+  <div class="callout info"><b>本次改造只涉及 operate 与 control(都在培养箱本机)。front 与 Java 后端完全不动。</b> 技术栈:C# / .NET 6 / WPF / MVVM。</div>
+  </div>
+</section>
+
+<section id="bg">
+  <h2><span class="num">1</span> 改造背景与三大问题 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">理解改造,先理解"合并的真相"。</p>
+  <h4>1.1 合并的真相</h4>
+  <p>operate(本机操作界面)与 control(机器驱动)<b>原本是两个独立进程、独立软件</b>,各自连服务器,本地互不通信。之前做过一次"三项目合并",<b>只为代码仓库/管理方便</b>,把 control 逻辑塞进了 operate 进程内运行(operate <code>MainWindow_Loaded → Task.Run → StartMain.StartRun()</code>)。</p>
+  <div class="callout info">📌 <b>"三项目"= operate + control + AutoFocusTool</b>(对焦工具,重构进 <code>control/IvfTl.AutoFocus</code> / <code>IvfTl.Hardware</code>,核心算法逐字保留,见 §7.1)三者合并进 operate;<b>front(医生端)从未被合并</b>,是独立程序,本次也完全不动。原始三项目代码仍在 <code>临时文件/</code> 下(ivf_tl_operate / ivf_tl_control / AutoFocusTool)。</div>
+  <div class="callout warn">⚠ 这次合并把<b>两个生命周期相反</b>的东西塞进了一个进程:control 要 7×24 常驻不能停,operate 只是 UI 想关就关。由此引出下面三个必须解决的问题。</div>
+
+  <h4>1.2 合并引入的三个问题(本次改造要解决)</h4>
+  <div class="grid3">
+    <div class="callout warn" style="margin:0"><b>① 生命周期冲突</b><br>operate 关 UI 后进程退不掉(control 的 LongRunning 前台线程撑着)→ <b>残留无窗口进程</b>(如曾出现 PID 20268);重开 operate 又起一套 control → <b>多实例抢串口</b>。</div>
+    <div class="callout warn" style="margin:0"><b>② 监控功能没写全</b><br>operate"服务监控"页本应跨进程看 control 运行状态,合并时只做了"同进程直读内存"(<code>GetMonitorSnapshot</code>),内容也不全。</div>
+    <div class="callout warn" style="margin:0"><b>③ 调试串口占用</b><br>control 常驻占着串口,operate 进调试页/对焦页要用串口时与 control 冲突(现有 HouseGate 闸门只是<b>同进程内</b>协调)。</div>
+  </div>
+
+  <h4>1.3 改造前 vs 改造后</h4>
+  <div class="vs">
+    <div class="col before">
+      <h4>🔴 现状(合并态·单进程)</h4>
+      <ul>
+        <li>operate.exe 一个进程,内部 Task.Run 跑 control</li>
+        <li>operate 关闭 → control 跟着死 / 或残留退不掉</li>
+        <li>重开 operate → 又起一套 control 抢串口</li>
+        <li>监控页同进程直读,内容不全</li>
+        <li>调试借串口靠同进程内 HouseGate 闸门</li>
+        <li>退出语义缺失(App_Exit 空、去掉了 Environment.Exit)</li>
+      </ul>
+    </div>
+    <div class="mid">➡</div>
+    <div class="col after">
+      <h4>🟢 目标(双进程)</h4>
+      <ul>
+        <li>control.exe 独立常驻 + operate.exe 可随时开关</li>
+        <li><b>operate 关闭 → control 继续驱动机器</b>(采集/换气/拍照/上传不断)</li>
+        <li>control 用 Mutex 单实例,永不多开抢串口</li>
+        <li>监控页跨进程读 control 的 /status,内容补全</li>
+        <li>调试经本地 HTTP 跨进程借/还串口,control 让路调完恢复</li>
+        <li><b>用户/装机仍是一个软件</b>:只装只启 operate,control 由它自动拉起</li>
+      </ul>
+    </div>
+  </div>
+  <div class="callout warn">⚠ <b>本次是接一个"半成品"</b>:上一个"三项目合并"任务<b>代码完成,但真机验收整体未做、且有 operate 侧功能降级遗留</b>(详见 §13 合并降级登记 / <code>待验证清单.md</code> M-01~M-07)。双进程拆分在其基础上推进。</div>
+  </div>
+</section>
+
+<section id="goal">
+  <h2><span class="num">2</span> 目标 · 范围 · 关键决策 <span class="chev">▼</span></h2>
+  <div class="body">
+  <div class="grid2">
+    <div>
+      <h4>🎯 目标</h4>
+      <ul class="clean">
+        <li>运行时回归两个独立进程:control.exe 常驻驱动机器,operate.exe 可随时开关。</li>
+        <li>用户/装机仍是一个软件:只装、只启动 operate;control 由 operate 自动管理。</li>
+        <li>operate 关闭 → control 继续驱动机器、采集、上传。</li>
+        <li>补全监控页(跨进程读 control 真实状态)。</li>
+        <li>解决调试串口占用(跨进程借用/归还)。</li>
+      </ul>
+    </div>
+    <div>
+      <h4>🚧 范围红线</h4>
+      <div class="callout warn" style="margin-top:6px"><b>只动 operate / control,front 完全不动。</b> control 的采集/换气/对焦/上传<b>业务逻辑零改动</b>,只动"进程边界 + 进程间本地通信"(降低回归风险)。</div>
+    </div>
+  </div>
+  <h4>🔑 已确认的关键决策(已与用户逐项确认,勿推翻重议)</h4>
+  <table>
+    <tr><th>#</th><th>决策</th><th>选定方案</th></tr>
+    <tr><td>1</td><td>进程模型</td><td>operate / control <b>两个独立进程</b>;代码仍一个解决方案管理,装机一个软件;front 不动</td></tr>
+    <tr><td>2</td><td>进程间通信</td><td><b>control 开本地 HTTP 小服务</b>(<code>127.0.0.1:38080</code>,.NET 自带 HttpListener),operate 调</td></tr>
+    <tr><td>3</td><td>谁拉起 control</td><td><b>operate 按需拉起</b>(登录后探活,不在则 Process.Start)+ operate 开机自启 + control <b>Mutex 单实例</b></td></tr>
+    <tr><td>4</td><td>调试让串口</td><td>operate 调 <code>/serial/pause</code>+<code>/resume</code>,复用现有 HouseGate 闸门(改跨进程);control 不死、调完恢复</td></tr>
+    <tr><td>5</td><td>整体停止 control</td><td>监控页<b>受护栏按钮</b>(二次确认 + 工程师口令 <code>tl13579</code>)→ <code>/shutdown</code> 安全停机</td></tr>
+    <tr><td>6</td><td>监控页补全</td><td>补:各舱实时活动、后台线程健康/心跳、串口借用/占用状态</td></tr>
+    <tr><td>7</td><td>实现节奏</td><td>分三阶段:①独立进程骨架 ②监控/借串口/停止 ③清理老壳+装机</td></tr>
+  </table>
+  </div>
+</section>
+
+<!-- ============================ PART B ============================ -->
+<div class="part-banner b">B · 改成什么样(目标架构 = 你要交付的)</div>
+
+<section id="arch">
+  <h2><span class="num">3</span> 目标架构(双进程) <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">两进程业务上仍各自连服务器(维持现状);<b>本地新增的唯一通道是 control 的 HTTP 小服务</b>,只承载"读状态/借串口/停止"三类本地协调。</p>
+  <div class="chain" style="flex-direction:column;align-items:stretch;gap:10px">
+    <div class="node op" style="border-width:2px"><b>operate.exe(UI,可随时开关,管理员)</b><small>本机操作界面 · 服务监控页轮询 control 的 /status · 启动时确保 control 在跑(不在则拉起)</small></div>
+    <div style="text-align:center;color:#94a3b8;font-size:13px">▼ 本地 HTTP(127.0.0.1:38080)&nbsp; GET /status /ping &nbsp; POST /serial/pause|resume /shutdown ▼</div>
+    <div class="node" style="border-width:2px"><b>control.exe(无界面常驻,管理员,Mutex 单实例)</b><small>StartMain.StartRun():串口/相机/采集/对焦/换气 · 10×HouseBin 采集循环 + BufferBottleBin + 上报线程 · 内嵌轻量 HttpListener</small></div>
+    <div style="text-align:center;color:#94a3b8;font-size:13px">▼ control 连服务器(MQTT指令/Kafka图片/HTTP) &nbsp;&nbsp; operate 连服务器(gateway HTTP/MQTT) ▼</div>
+    <div class="node sv" style="border-width:2px"><b>中央服务器</b><small>网关 / 业务 / 数据传输 / 配置下发 / 操作日志</small></div>
+  </div>
+
+  <h4>control 独立进程的形态</h4>
+  <ul class="clean">
+    <li><b>无界面后台常驻</b>(control 本就无功能界面),不弹登录窗(原 Window1 退役)。</li>
+    <li>账号由 operate 拉起时<b>命令行参数传入</b>(替代原读配置+透传)。</li>
+    <li>启动即 <code>StartMain.StartRun()</code> 跑采集 + 起 HttpListener。</li>
+    <li>基于<b>干净的 <code>ivf_tl_Control</code> 类库新建</b>启动器(<code>ivf_tl_ControlHost</code>),<b>不改造脏壳 <code>ivf_tl_ControlTest</code></b>(其唯一正式资产 Window1 已被 operate 复刻,其余是测试/死代码,阶段3 退役删)。</li>
+  </ul>
+
+  <h3 id="http-contract">3.1 本地 HTTP 接口契约(control 提供)</h3>
+  <p class="note">control 内用 .NET 自带 <code>HttpListener</code>,只监听 <code>127.0.0.1:&lt;端口&gt;</code>(默认 38080,可配)。全部仅本机、拒绝外部请求(防外部调停机/借串口)。</p>
+  <table>
+    <tr><th>方法</th><th>路径</th><th>入参</th><th>返回</th><th>用途</th><th>阶段</th></tr>
+    <tr><td>GET</td><td><code>/ping</code></td><td>—</td><td><code>{ok,pid,tlSn}</code></td><td>operate 启动探活,判断要不要拉起</td><td><span class="b control o" style="color:#2563eb">阶段1</span></td></tr>
+    <tr><td>GET</td><td><code>/status</code></td><td>—</td><td>JSON 快照(§6 三块)</td><td>监控页轮询(每 2s)</td><td><span class="b control o" style="color:#2563eb">1→2补</span></td></tr>
+    <tr><td>POST</td><td><code>/serial/pause</code></td><td><code>{houseSn}</code></td><td><code>{ok}</code></td><td>调试借串口:control 让出该舱串口</td><td><span class="b new">阶段2</span></td></tr>
+    <tr><td>POST</td><td><code>/serial/resume</code></td><td><code>{houseSn}</code></td><td><code>{ok}</code></td><td>调试完归还:恢复该舱采集</td><td><span class="b new">阶段2</span></td></tr>
+    <tr><td>POST</td><td><code>/shutdown</code></td><td><code>{token}</code></td><td><code>{ok}</code></td><td>受护栏整体停机(token=工程师口令校验)</td><td><span class="b new">阶段2</span></td></tr>
+  </table>
+
+  <h3 id="lifecycle">3.2 生命周期管理</h3>
+  <div class="grid2">
+    <div>
+      <h4>拉起(operate → control)</h4>
+      <ol class="clean">
+        <li>operate 登录成功 → 探 <code>GET /ping</code>。</li>
+        <li>通 → control 已在跑,直接"已连接",<b>不拉起</b>。</li>
+        <li>不通 → <code>Process.Start</code> 拉起 control.exe,账号/密码/缓存盘<b>命令行传入</b>;管理员静默不弹 UAC。</li>
+        <li>轮询 <code>/ping</code> 直到就绪(带超时重试)。</li>
+      </ol>
+      <h4>单实例(control)</h4>
+      <p class="note">启动用命名 Mutex <code>Global\ivf_tl_control_singleton</code> 判重:已存在则立即退出。根治多实例抢串口。</p>
+    </div>
+    <div>
+      <h4>关闭(operate)</h4>
+      <p class="note">operate 关 UI <b>只退自己,绝不动 control</b>。control 继续驱动机器。(合并期的"退出语义缺失"在双进程下反而是对的。)</p>
+      <h4>停止(control,仅受护栏)</h4>
+      <p class="note">仅监控页受护栏按钮(二次确认+工程师口令)→ <code>/shutdown</code> → control 执行<b>安全停机</b>:停采集→停上报线程→<code>HardwareAccessLayer.ShutdownAll()</code>关句柄→释放 Mutex→退出。<b>control 需新增统一 <code>Shutdown()</code> 入口</b>(现状无统一停机)。</p>
+      <h4>开机自启</h4>
+      <p class="note">装机把 operate 设开机自启 → 开机自动起 operate → 拉起 control → 机器自动被驱动。</p>
+    </div>
+  </div>
+  </div>
+</section>
+
+<!-- ============================ PART C ============================ -->
+<div class="part-banner c">C · 现在是什么样(现状全量业务流程 · 你要在它上面动手)</div>
+
+<section id="overview">
+  <h2><span class="num">4</span> 当前系统拓扑(合并态) <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">当前"一个软件"= operate UI 进程,内部用 <code>Task.Run</code> 把 control 当类库托起来常驻跑;control 经串口驱动下位机。</p>
+  <div class="chain">
+    <div class="node sv"><b>中央服务器</b><small>MySQL/MQTT/Kafka/网关</small></div>
+    <div class="arrow">⇄</div>
+    <div class="node op"><b>operate.exe 进程</b><small>登录·UI·调试·监控</small></div>
+    <div class="arrow">⊃</div>
+    <div class="node"><b>control 线程组</b><small>Task.Run StartRun()</small></div>
+    <div class="arrow">→</div>
+    <div class="node hw"><b>下位机 ×11</b><small>10 培养舱+1 缓冲瓶</small></div>
+  </div>
+  <div class="grid2">
+    <div>
+      <h4>三条服务器通道</h4>
+      <table>
+        <tr><th>通道</th><th>用途</th></tr>
+        <tr><td><span class="b server">MQTT</span></td><td>下行指令(StartDish等);上报每舱实时状态 ~1s</td></tr>
+        <tr><td><span class="b server">HTTP</span></td><td>配置下发/历史/报警/心跳/回执;operate 全部业务查询</td></tr>
+        <tr><td><span class="b server">Kafka</span></td><td>拍照图片上传(扫盘→上传→成功删盘)</td></tr>
+      </table>
+    </div>
+    <div>
+      <h4>合并后常驻线程</h4>
+      <table>
+        <tr><th>线程</th><th>数量/节奏</th></tr>
+        <tr><td>采集主循环</td><td>×10 / while</td></tr>
+        <tr><td>运行监测·历史上报</td><td>×10·×10</td></tr>
+        <tr><td>缓冲瓶自监测+换气调度</td><td>×1+1</td></tr>
+        <tr><td>状态上报/图片上传/心跳</td><td>1s/5s/10min</td></tr>
+        <tr><td>operate 详情页报警轮询</td><td>×1 / 10s</td></tr>
+      </table>
+    </div>
+  </div>
+  <div class="callout warn">⚠ 这些大多是<b>前台 LongRunning 线程</b> —— operate 关 UI 进程退不掉的根因(合并只接了"启动"没接"停止")。正是 §15 拆分要解决的。</div>
+  </div>
+</section>
+
+<section id="boot">
+  <h2><span class="num">5</span> 启动与登录流程 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">单实例(Mutex <code>ivf_tl_Operate</code>)→ 登录窗 → 云端校验 → 后台线程托管 control。</p>
+  <div class="flow">
+    <div class="step op"><div class="idx">1</div><div class="box"><b>App 启动</b> <span class="fl">App.xaml.cs:33-68</span><br>单实例互斥;注册三类全局异常;<span class="b new">新增</span>凭据迁移(明文口令一次性 DPAPI 加密)、操作日志初始化、全局点击层、语言加载<small>App_Exit 体为空 → 退出语义缺失(§15)</small></div></div>
+    <div class="step op"><div class="idx">2</div><div class="box"><b>弹登录窗</b> <span class="fl">MainWindow.xaml.cs:47-61</span> → 校验账号/密码(DPAPI解密)/设备号 → <span class="b server">HTTP</span> 云端校验 → 置 <code>TlSn=NEO-1-{tlNum}</code> → 建 MqttHelper 订阅 → 拉通用设置+受精类型字典 <span class="fl">AppData.cs:114-147</span></div></div>
+    <div class="step"><div class="idx">3</div><div class="box"><b>后台线程托管 control</b> <span class="fl">MainWindow.xaml.cs:67-132</span> <span class="b new">合并核心</span><br><code>Task.Run</code>:① control.AppData.Login(同账号透传)→ ② 设缓存盘 → ③ HAL.ScanDevices() 设备发现 → ④ StartMain.StartRun() 阻塞常驻<small>全程异常只记日志不退进程(已去掉 control 原 Environment.Exit)</small></div></div>
+    <div class="step"><div class="idx">4</div><div class="box"><b>StartMain.StartRun 四步</b> <span class="fl">control/ivf_tl_Control/StartMain.cs:34</span><br><code>AppData.Instance</code>(读配置/建服务/开SQLite)→ <code>InitTL</code>(枚举相机+扫COM握手+拉配置)→ <code>InitHouse</code>(建1缓冲瓶+10舱逐舱起采集)→ <code>StartAsync</code>(Kafka/MQTT/上报线程)</div></div>
+    <div class="step hw"><div class="idx">5</div><div class="box"><b>InitTL 硬件发现</b> <span class="fl">StartMain.cs:66</span><br>并行枚举相机 index0-9 读 CCDSN → 扫 COM(跳过COM1/2)握手得每舱 houseSn、读 EEPROM、配对相机 → 从11号缓冲瓶读 TLNum 组 tlSn<small>期望11模块,不符看 ContinueOnModuleCountMismatch(默认继续)</small></div></div>
+  </div>
+  <h4>配置下发链(断网也能开机)</h4>
+  <div class="chain">
+    <div class="node sv"><b>中央 MySQL</b><small>aivfo_tl_setting</small></div><div class="arrow">→</div>
+    <div class="node"><b>HTTP拉取</b><small>重试3次</small></div><div class="arrow">→</div>
+    <div class="node"><b>运行态对象</b><small>TLSetting/House/Well</small></div><div class="arrow">→</div>
+    <div class="node db"><b>本地SQLite</b><small>缓存兜底</small></div>
+  </div>
+  <p class="note">开机链重试 3 次全失败 → 回退本地 SQLite <code>DbGetTLInfo</code>(断网开机);运行期收 MQTT <code>Update</code> 热刷新。映射在 <code>ivf_tl_UtilHelper/ConvertHelper.cs</code>。</p>
+  </div>
+</section>
+
+<section id="loop">
+  <h2><span class="num">6</span> 采集主循环 —— control 核心 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">每舱一个 <code>while(true)</code> 线程(<code>HouseBin.MainThread</code> · HouseBin.cs:614),靠串口往返自然节流。</p>
+  <div class="flow">
+    <div class="step dn"><div class="idx">1</div><div class="box"><b>让路守卫</b> <span class="fl">:632</span><br><code>IsDebug</code>(MQTT调试)或 <code>CapturePausedByGate</code>(前台借串口)任一为真 → 不发任何串口命令、Sleep(3s)<small>这是 operate 调试页借走串口时 control 让路的机制(协作式)</small></div></div>
+    <div class="step"><div class="idx">2</div><div class="box"><b>温压读取+补气/排气</b> <span class="fl">ParamFun :663/1255</span><br>读门/三路温度/压力;<code>IsPai</code>且压力高→排气 PaiQi;压力<code>&lt;pressureAlarmMin</code>→补气 AerationNew<small>温度=只读(下位机自控温),气压=control 主动控制</small></div></div>
+    <div class="step"><div class="idx">3</div><div class="box"><b>舱门守卫</b> <span class="fl">:667</span> 门没关→只读温压本轮结束<small>开门会立即置 IsStop* 打断进行中的对焦/拍照/换气</small></div></div>
+    <div class="step"><div class="idx">4</div><div class="box"><b>按模式分流</b> <code>Dish.id&gt;0</code> 培养皿 / <code>Balance.id&gt;0</code> 平衡</div></div>
+  </div>
+  <div class="grid2">
+    <div>
+      <h4>🟦 培养皿模式 <span class="fl">:671-750</span></h4>
+      <table>
+        <tr><th>触发</th><th>动作</th></tr>
+        <tr><td>换气间隔到 :696</td><td>AirSwapFun 换气</td></tr>
+        <tr><td>拍照间隔到 :704</td><td>StartCCD+ccdThreadFun(§7)</td></tr>
+        <tr><td>FirstClearest 真 :712</td><td>等气稳→对焦→成功后换气拍照</td></tr>
+        <tr><td>拍照异常冷却 :722</td><td>CCDState=1 未到等待→continue</td></tr>
+      </table>
+    </div>
+    <div>
+      <h4>🟦 平衡模式 <span class="fl">:753-783</span></h4>
+      <p class="note"><b>只有换气,无拍照/对焦。</b> 排气走 <code>VentWaitTimeB</code>(培养态 <code>VentWaitTimeD</code>)。</p>
+      <div class="callout info">对焦默认降级:<code>localAutofocusEnabled</code> 默认 0 时<b>不做实时对焦</b>,按 scene=0 出厂基准 / eepromClearPosition 回退拍照(:1462);置1才走实时对焦。</div>
+    </div>
+  </div>
+  <h4>关键状态标志</h4>
+  <table>
+    <tr><th>标志</th><th class="c">行</th><th>含义</th></tr>
+    <tr><td><code>IsPai</code></td><td class="c">:153</td><td>排队换气模式,App.config QueuAir 注入</td></tr>
+    <tr><td><code>FirstClearest</code></td><td class="c">:277</td><td>本轮是否先对焦;关门/StartDish/HouseAutoFocus 置 true</td></tr>
+    <tr><td><code>IsStopClearest/CCD/AirSwap</code></td><td class="c">:287</td><td><b>开门</b>全置 true,立即打断对焦/拍照/换气</td></tr>
+    <tr><td><code>CCDState</code></td><td class="c">:369</td><td>连续抓图失败 CCDFailedNumber 次→报警+冷却</td></tr>
+    <tr><td><code>CapturePausedByGate</code>/<code>IsDebug</code></td><td class="c">:90</td><td>HAL借用让路 / MQTT调试让路</td></tr>
+  </table>
+  </div>
+</section>
+
+<section id="photo">
+  <h2><span class="num">7</span> 拍照 / 对焦动作内部 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">拍照 <code>ccdThreadFun</code>(HouseBin.cs:2134):逐 well × 逐层 移电机抓图上传。</p>
+  <div class="flow">
+    <div class="step hw"><div class="idx">1</div><div class="box"><b>水平电机复位 → 开 LED</b></div></div>
+    <div class="step hw"><div class="idx">2</div><div class="box"><b>逐 well</b> 移水平电机到该 well 的 <code>horizontalMotorPosition</code></div></div>
+    <div class="step hw"><div class="idx">3</div><div class="box"><b>逐层</b> 移垂直电机到该层 Z(层间距 <code>focusLayerSpacingPulse</code> × 层数 <code>focusLayerCount</code>)</div></div>
+    <div class="step hw"><div class="idx">4</div><div class="box"><b>Photograph 抓图+处理+上传</b> 失败重试 <code>CCDFailedNumber</code> 次;门开/欠压有打断与插补</div></div>
+    <div class="step hw"><div class="idx">5</div><div class="box"><b>关 LED → 电机归位</b></div></div>
+  </div>
+  <div class="callout warn">⚠ <b>层链就近优先</b>:well 级(非空)&gt; 设备级 &gt; 报错。层间距缺失抛 <code>FocusConfigMissingException</code> 跳过该 well,<b>不兜底</b>。</div>
+  <h3>7.1 自动对焦四步标定(合并自 AutoFocusTool,算法逐字保留)</h3>
+  <p class="note">重构进 <code>control/IvfTl.AutoFocus/Calib/CalibrationEngine.cs:189</code>,硬件依赖改走 HAL。</p>
+  <div class="chain">
+    <div class="node hw"><b>①粗对焦</b><small>中心90000±30000<br>步2000·中央40%ROI</small></div><div class="arrow">→</div>
+    <div class="node hw"><b>②水平居中</b><small>EEPROM±2000微调</small></div><div class="arrow">→</div>
+    <div class="node hw"><b>③曝光二分</b><small>MeanLo95/Hi150</small></div><div class="arrow">→</div>
+    <div class="node hw"><b>④精对焦</b><small>0.95r ROI·抛物线插值<br>PeakRatio&gt;1.2 判合格</small></div>
+  </div>
+  <p class="note">清晰度评价 Tenengrad/Laplacian÷mean(真机修复);标定真相源 <code>calibration.json</code> → 镜像写库 <code>house_autofocus_calibration</code>(scene0出厂基准永留/scene1日常),经 <code>CalibrationStore</code> 委托回调。</p>
+  </div>
+</section>
+
+<section id="air">
+  <h2><span class="num">8</span> 换气 / 补气 / 排气 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">control 主动控制气体:周期换气置换预混气、低压补气、高压排气。</p>
+  <table>
+    <tr><th>动作</th><th class="c">行</th><th>逻辑</th></tr>
+    <tr><td><b>换气</b> AirSwapOldFun <span class="b control o" style="color:#2563eb">唯一启用</span></td><td class="c">:991</td><td>循环 <code>airSwapTime</code> 次:开排气阀→放气 <code>ventilationDelay</code> 秒→关阀→欠压补气。反复排空+回充稀释置换<br><span class="fl">newAirSwap 新版被注释,实质未生效</span></td></tr>
+    <tr><td><b>补气</b> AerationNew</td><td class="c">:1307</td><td>循环 <code>houseAerationNum</code> 次,每次 Sleep(<code>aerationDelay</code>) 读压力,达 <code>pressureAlarmMin</code> 停</td></tr>
+    <tr><td><b>排气</b> PaiQi</td><td class="c">:1341</td><td>循环 <code>VentNum</code> 次,降到 <code>VentPre</code> 停</td></tr>
+  </table>
+  <h3>8.1 缓冲瓶排队换气(IsPai=true,8步握手)</h3>
+  <p class="note">11号缓冲瓶不是培养舱(无相机/不拍照),是给多舱排队换气供预混气的共享气源。<code>AirSwapQueueFun</code>(:1120)与缓冲瓶 <code>HuanQiThread</code>(:398)经 5 个 bool 握手位协调。</p>
+  <div class="flow">
+    <div class="step"><div class="idx">1</div><div class="box">舱<b>报名入队</b></div></div>
+    <div class="step"><div class="idx">2</div><div class="box">缓冲瓶通知"轮到你"(QueueAir)</div></div>
+    <div class="step"><div class="idx">3</div><div class="box">舱开自己进/排气阀(ReadyAir)</div></div>
+    <div class="step"><div class="idx">4</div><div class="box"><b>缓冲瓶开自己进气阀</b>(OpenBuffer)</div></div>
+    <div class="step"><div class="idx">5</div><div class="box">舱按 TongQi 时间冲刷</div></div>
+    <div class="step"><div class="idx">6</div><div class="box">缓冲瓶收尾、队空关阀(EndAir)</div></div>
+    <div class="step"><div class="idx">7</div><div class="box">舱关阀补气</div></div>
+    <div class="step dn"><div class="idx">8</div><div class="box">StopPai 防中途退出的舱卡住队列</div></div>
+  </div>
+  <p class="note">缓冲瓶自身:1s/轮读气压,<code>&lt;bufferBottlerPressureMin+10</code>→自补气(循环 <code>bufferBottlerAerationNum</code> 次)。只控进气阀+自补气,无排气阀。</p>
+  </div>
+</section>
+
+<section id="server">
+  <h2><span class="num">9</span> 服务器双向交互 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">指令走 MQTT、回执走 HTTP、图片走 Kafka。</p>
+  <div class="grid2">
+    <div>
+      <h4>下行指令(服务器→control,MQTT)<span class="fl">AppData.MqttMessage:972</span></h4>
+      <table>
+        <tr><th>指令</th><th>作用</th></tr>
+        <tr><td>StartBalance/EndBalance(1/2)</td><td>起/停平衡</td></tr>
+        <tr><td>StartDish/EndDish(3/4)</td><td>起/停培养皿</td></tr>
+        <tr><td>EmbryoState(5)</td><td>改胚胎状态</td></tr>
+        <tr><td>Update(6)</td><td>重拉配置刷新</td></tr>
+        <tr><td>DebugStart(7)</td><td>进调试(置IsDebug,唯一带uuid回执)</td></tr>
+        <tr><td>HouseAutoFocus/WellAutoFocus(8/9)</td><td>动电机+拍照对焦整舱/单孔</td></tr>
+      </table>
+    </div>
+    <div>
+      <h4>上行(control→服务器)</h4>
+      <table>
+        <tr><th>类型</th><th>通道/内容</th></tr>
+        <tr><td>状态~1s</td><td><span class="b server">MQTT</span> 压力/温度/门/阀/运行态 → TL/House/collecting-data QoS2</td></tr>
+        <tr><td>图片5s</td><td><span class="b server">Kafka</span> 扫盘上传,成功才删</td></tr>
+        <tr><td>报警/历史/心跳</td><td><span class="b server">HTTP</span> 门/串口/拍照态;曲线;每日维护</td></tr>
+        <tr><td>回执</td><td><span class="b server">HTTP</span> MqttResultController /result</td></tr>
+      </table>
+    </div>
+  </div>
+  <h4>trace_id 全链路(排障利器)</h4>
+  <div class="chain">
+    <div class="node op"><b>operate/control</b><small>OperationLogContext.TraceId</small></div><div class="arrow">→</div>
+    <div class="node"><b>HTTP请求头 traceId</b><small>HttpHelper:1460</small></div><div class="arrow">→</div>
+    <div class="node sv"><b>Java网关/微服务</b><small>同trace_id透传</small></div><div class="arrow">→</div>
+    <div class="node db"><b>log.operation_log</b><small>跨端时间线</small></div>
+  </div>
+  <p class="note">两层日志:<b>动作层</b>(谁/功能/输入/输出/结果/报错/耗时,定位失败靠它)+ <b>点击层</b>(哪页点了哪按钮)。排障第一步:拿 trace_id 拉跨端时间线找 <code>result=失败</code> 那条。</p>
+  </div>
+</section>
+
+<section id="operate">
+  <h2><span class="num">10</span> operate 业务模块逐功能 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">每功能:操作→背后动作(串口/HTTP/DB/借用)→file:line。<span class="b operate">绿=正常</span> <span class="b down">红=降级</span> <span class="b new">橙=新增</span></p>
+  <h3>10.1 首页/舱室总览 · MainPageViewModel(1023行)</h3>
+  <table>
+    <tr><th>操作</th><th>背后动作</th><th class="c">行</th></tr>
+    <tr><td>看舱室列表/状态</td><td><span class="b server">HTTP</span> GetHouseCultureListApi</td><td class="c">:142</td></tr>
+    <tr><td>报警轮询/计数/语音</td><td><span class="b server">HTTP</span> SearchAlarmHistory(Num)Api+TlInfoTimeApi</td><td class="c">:745</td></tr>
+    <tr><td>结束培养(EndDish)</td><td><b>仅本地清空 ExDish</b>,无 HTTP 下发</td><td class="c">:347</td></tr>
+    <tr><td>(隐性)反写 control 心跳</td><td>直写 <code>ControlAppData.LastHttpOkAt</code></td><td class="c">:768</td></tr>
+  </table>
+  <h3>10.2 舱室设置 / 对焦调试 · HouseDebugPageViewModel(1592行)<span class="b down">含3处降级</span></h3>
+  <p class="note">进调试 <code>ComHouseInit</code>(:244)向 HAL <code>Acquire(OperateDebug)</code> 借采集端同一句柄;退出 <code>ComHouseUnit</code>(:355)只 Dispose 归还,绝不关口。</p>
+  <table>
+    <tr><th>操作</th><th>背后动作</th><th class="c">行</th><th>状态</th></tr>
+    <tr><td>读温/压/门、开关 LED/气阀、电机点动</td><td>Serial.*Wait</td><td class="c">:383~610</td><td><span class="b operate">正常</span></td></tr>
+    <tr><td>写进气阀时间/垂直脉冲/存水平位</td><td>Serial.Write*Wait(真下发)</td><td class="c">:520/565/579</td><td><span class="b down">成功语义待验 M-05</span></td></tr>
+    <tr><td><b>写舱室排气阀时间</b></td><td>→ <b>return false 不下发</b>,仅本地暂存</td><td class="c">:534</td><td><span class="b down">降级 M-01</span></td></tr>
+    <tr><td><b>读舱室排气阀时间</b></td><td>→ <b>return -1</b>,静默保留旧值</td><td class="c">:553</td><td><span class="b down">降级 M-02</span></td></tr>
+    <tr><td><b>调试页存图</b></td><td>→ <b>丢弃宽高转 SaveBmpPic</b>,未验证</td><td class="c">:748</td><td><span class="b down">降级 M-04</span></td></tr>
+  </table>
+  <h3>10.3 缓冲瓶调试 · BufferDebugViewModel(311行)<span class="b down">含1处降级</span></h3>
+  <table>
+    <tr><th>操作</th><th>背后动作</th><th class="c">行</th><th>状态</th></tr>
+    <tr><td>读状态/补气/写进气阀时间/读灯光</td><td>Serial.*Wait(真下发/读)</td><td class="c">:229~285</td><td><span class="b operate">正常</span></td></tr>
+    <tr><td><b>写灯光亮度 EEPROM</b></td><td>→ <b>return false 不下发</b>,仅本地+服务器</td><td class="c">:268</td><td><span class="b down">降级 M-03</span></td></tr>
+  </table>
+  <div class="grid2">
+    <div>
+      <h3>10.4 其它(纯 HTTP/展示)</h3>
+      <table>
+        <tr><th>模块</th><th>关键接口</th></tr>
+        <tr><td>对焦设置</td><td>FocusSettingApi</td></tr>
+        <tr><td>皿管理/加皿</td><td>Add/Update CultureRecord</td></tr>
+        <tr><td>胚胎详情(10s报警轮询)</td><td>GetRecordDetail</td></tr>
+        <tr><td>胚胎照片/环境曲线</td><td>GetPictureView/GetHouseEnvironmentList</td></tr>
+      </table>
+    </div>
+    <div>
+      <h3>10.5 合并新增三页 <span class="b new">新增</span></h3>
+      <table>
+        <tr><th>页</th><th>职责</th></tr>
+        <tr><td>服务监控</td><td>每2s跨读 GetMonitorSnapshot(:77),只读;阈值占位待验</td></tr>
+        <tr><td>统一配置</td><td>写本地App.config(凭据加密);SaveAll 返回值恒true占位</td></tr>
+        <tr><td>内置软键盘</td><td>替代 osk.exe</td></tr>
+      </table>
+    </div>
+  </div>
+  <h3>10.6 operate HTTP 接口全表(经 HttpHelper,超时12s/无重试)</h3>
+  <div class="pill-row">
+    <span class="pill">登录 auth/login</span><span class="pill">天平 start/stopBalance</span><span class="pill">在培列表 getHouseCultureList</span>
+    <span class="pill">皿 add/end/update CultureRecord</span><span class="pill">记录详情 getRecordDetail</span><span class="pill">切层 switchVideoPictureLayers</span>
+    <span class="pill">快捷/时间按钮 getButtons</span><span class="pill">系统设置 setting/system(+update)</span><span class="pill">舱室设置 setting/house(+update)</span>
+    <span class="pill">调试回写 house/debugging</span><span class="pill">well更新 house/well/update</span><span class="pill">对焦 house/focus/setting</span>
+    <span class="pill">立即拍照 house/immediately</span><span class="pill">通用设置 setting/common(+update)</span><span class="pill">光照 lights/update</span>
+    <span class="pill">仪器时间 tlInfo/time</span><span class="pill">字典 queryDictionaryByType</span><span class="pill">环境列表 getHouseEnvironmentList</span>
+    <span class="pill">标记去向 markEmbryoDestination</span><span class="pill">皿记录 getEmbryoCultureRecord(Num)</span><span class="pill">告警 getAlarmNum/getAlarm</span>
+    <span class="pill">图片 getPictures/getSourcePictures</span><span class="pill">静音 muteAlarm</span><span class="pill">裁剪告警 getHouseCropAlarm</span>
+  </div>
+  <p class="note">统一汇聚 <code>HttpClientSendAsync</code>(:1449):token缓存、trace_id透传、Stopwatch计时、四类分支统一操作日志埋点。接口与基准<b>无增删</b>,仅加埋点。</p>
+  </div>
+</section>
+
+<section id="hal">
+  <h2><span class="num">11</span> 硬件层 / 调试借串口(HAL) <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">HAL(HardwareAccessLayer 单例)全进程唯一持有每个 COM 口/每台相机(四字典),杜绝同口重复 Open。</p>
+  <div class="flow">
+    <div class="step op"><div class="idx">1</div><div class="box"><b>前台 Acquire(OperateDebug)</b> <span class="fl">HouseGateImpl.cs:36</span> 先 MarkPause() 让后台让路,再 SemaphoreSlim 抢舱级独占锁</div></div>
+    <div class="step"><div class="idx">2</div><div class="box"><b>后台采集让路</b> HouseBin 订阅 OnPauseCapture 置 volatile <code>CapturePausedByGate</code>,主循环顶部读到停发命令(非强杀)</div></div>
+    <div class="step op"><div class="idx">3</div><div class="box"><b>前台用 lease.Serial/Camera 调试</b> 复用采集端同一句柄,不 new 第二个口</div></div>
+    <div class="step"><div class="idx">4</div><div class="box"><b>Dispose 归还 → OnResumeCapture</b> 绝不 ClosePort/UnInit,归还后采集恢复</div></div>
+  </div>
+  <div class="callout info">ComBin(串口命令):队列+单发送线程+两级握手。发送级等回包 <b>30s 超时后重发3次</b>;调用级等整条完成(无限等)。一口同时只一条在途。</div>
+  <div class="callout warn">⚠ <b>两套并行栈未去重</b>:旧栈(ComEntitys/CameraHelper)InitTL 用;HAL包装栈(IvfTl.Hardware/SerialHelper)借用用 —— 排障要分清当前走哪条。</div>
+  </div>
+</section>
+
+<section id="data">
+  <h2><span class="num">12</span> 数据库与数据流 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">本地 SQLite <code>aivfoTL.db</code>(9 表:8 表须预置,<code>house_autofocus_calibration</code> 由 DBService 启动时 CodeFirst.InitTables 自动建)+ 中央 MySQL(7库)。</p>
+  <div class="grid2">
+    <div>
+      <h4>本地 SQLite 表</h4>
+      <table>
+        <tr><th>表</th><th>用途</th></tr>
+        <tr><td>tl_setting/house/house_well_setting</td><td>配置本地缓存(断网兜底)</td></tr>
+        <tr><td>dish/embryo/balance</td><td>培养皿/胚胎/平衡(MQTT落库)</td></tr>
+        <tr><td>house_well_photo</td><td>电机位置缓存</td></tr>
+        <tr><td>picture</td><td>待传图片元数据(传成功即删)</td></tr>
+        <tr><td>house_autofocus_calibration</td><td>对焦标定镜像(scene0永留)</td></tr>
+      </table>
+    </div>
+    <div>
+      <h4>数据归属</h4>
+      <table>
+        <tr><th>类别</th><th>例</th></tr>
+        <tr><td>中央下发·本地缓存</td><td>配置、培养数据、电机位置</td></tr>
+        <tr><td>本地产生·双写镜像</td><td>对焦标定(json↔库)</td></tr>
+        <tr><td>本地产生·上报不落库</td><td>实时状态/历史/图片/告警/日志</td></tr>
+      </table>
+    </div>
+  </div>
+  <p class="note">中央7库:aivfo-auth/aivfo_services/aivfo-tl(传输·视频)/<b>aivfo_tl_setting(配置下发源)</b>/aivfo_tl(业务)/log(operation_log)/quartz。⚠ <code>aivfo_tl</code>(下划线=业务)≠ <code>aivfo-tl</code>(中划线=传输)。</p>
+  </div>
+</section>
+
+<!-- ============================ PART D ============================ -->
+<div class="part-banner d">D · 接下来改什么(你从这里开始)</div>
+
+<section id="degrade">
+  <h2><span class="num">13</span> ★ 合并降级登记(动手前必读) <span class="chev">▼</span></h2>
+  <div class="body">
+  <div class="callout warn"><b>合并代码完成 ≠ 业务闭环。</b> 以下相对合并前基准是真实功能缺失,根因统一:control 端 Commander 缺 builder,HAL <code>SerialChannelImpl.cs</code> 返回桩值,VM 据返回值提示。对应 <code>待验证清单.md</code> M-01~M-07,均须<b>真机门控(用户在场)</b>。</div>
+  <table>
+    <tr><th>编号</th><th>功能</th><th>基准行为</th><th>现状(file:line)</th><th>UI提示</th><th>补法</th></tr>
+    <tr><td><b>M-01</b></td><td>排气阀时间<b>写</b></td><td>真下发</td><td>SerialChannelImpl.cs:130 <code>return false</code> 仅本地暂存</td><td>✅有</td><td>补 control <code>CreateWriteEEPROOpenVentTimeCommand</code>+真机核对字节</td></tr>
+    <tr><td><b>M-02</b></td><td>排气阀时间<b>读</b></td><td>真读</td><td>:137 <code>return -1</code> 静默保留旧值</td><td>❌无</td><td>补 control 读命令</td></tr>
+    <tr><td><b>M-03</b></td><td>缓冲瓶灯光<b>写EEPROM</b></td><td>真写</td><td>:143 <code>return false</code> 仅本地+服务器</td><td>✅有</td><td>补 control <code>CreateWriteEEPROMLightNum</code></td></tr>
+    <tr><td><b>M-04</b></td><td>调试页<b>存图</b></td><td>MVCAPI.SavePic(带宽高)</td><td>CameraImpl.cs:148 丢宽高转 SaveBmpPic 未验证</td><td>❌无</td><td>真机核对落盘一致性</td></tr>
+    <tr><td><b>M-05</b></td><td>写EEPROM"成功=true"</td><td>—</td><td>阻塞收回复即true,未校验真实成功</td><td>—</td><td>真机核对</td></tr>
+    <tr><td><b>M-06</b></td><td>ReadWellFocusZero按well</td><td>按well</td><td>control Z零点整舱单值,well入参被忽略</td><td>—</td><td>真机核对</td></tr>
+    <tr><td><b>M-07</b></td><td>Release连内网网关</td><td>—</td><td>AppData.cs:91-111 #if DEBUG 覆写到 test-gateway 外网</td><td>—</td><td>真机/排障用 Release+现场核对 urlIp</td></tr>
+  </table>
+  <div class="callout ok"><b>正向修复(已在合并里做掉,记录在案):</b> ① 8GB 日志洪流根因修复(ChangeLanguage 精确移除字典 App.xaml.cs:265);② 日志滚动 Date→Composite;③ BaseUrl 取值健壮化(缺键不崩);④ passWord DPAPI 加密治理。</div>
+  </div>
+</section>
+
+<section id="roadmap">
+  <h2><span class="num">14</span> 三阶段路线图 <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">每阶段可独立编译/验证;真机步骤须用户在场,绝不无人值守驱动电机。<b>当前进度:阶段1 待开工</b>。</p>
+  <div class="flow">
+    <div class="step"><div class="idx">1</div><div class="box"><b>阶段1 · control 独立进程骨架(最关键)</b><br>新建无界面 control 启动器 + Mutex单实例 + 命令行接账号 + 内嵌 HttpListener(/ping+/status)+ operate 改为探活拉起<br><small><b>出口</b>:operate 能拉起独立 control.exe、control 驱动机器、<b>operate 关了 control 续命</b>、重开复用、单实例;真机采集闭环</small></div></div>
+    <div class="step"><div class="idx">2</div><div class="box"><b>阶段2 · 监控补全 + 调试借串口 + 受护栏停止</b><br>/status 补三块(各舱活动/线程心跳/串口借用);/serial/pause|resume 接 HouseGate 跨进程借;/shutdown 受护栏停止 + control 统一 Shutdown()<br><small><b>出口</b>:监控页完整;调试页跨进程借串口(control让路)调完恢复;停止按钮安全停 control</small></div></div>
+    <div class="step"><div class="idx">3</div><div class="box"><b>阶段3 · 清理老壳 + 装机收尾</b><br>退役删 ivf_tl_ControlTest;operate 开机自启;ComBin 两套栈去重;部署文档(两exe+端口+DependFile+开机自启)<br><small><b>出口</b>:全新部署一次到位、文档与代码对齐。<b>顺带补 §13 的 M-01~M-07 真机门控项</b></small></div></div>
+  </div>
+
+  <h3 id="stage1">14.1 阶段1 · 七个任务(照着做,详见开发计划文档)</h3>
+  <p class="note">新建项目 <code>ivf_tl_operate_2.0/control/ivf_tl_ControlHost/</code>(替代脏壳)。Task1-6 纯编码可独立完成,Task7 真机验证须用户在场。技术栈:.NET6 / HttpListener / Mutex / xUnit。</p>
+  <table>
+    <tr><th>任务</th><th>内容</th><th>产出/改动</th><th>验证</th></tr>
+    <tr><td><span class="tasknum">1</span>项目骨架</td><td>新建 ivf_tl_ControlHost(WinExe,requireAdmin,引用 ivf_tl_Control)</td><td>csproj/manifest/Program(空壳)</td><td>编译</td></tr>
+    <tr><td><span class="tasknum">2</span>参数解析</td><td>HostArgs:account/password/cacheDisk/port,IsValid 守卫</td><td>HostArgs.cs + 单测</td><td>xUnit 4条</td></tr>
+    <tr><td><span class="tasknum">3</span>StatusDto</td><td>/ping /status 返回体(ok/pid/tlSn/started)</td><td>StatusDto.cs + 单测</td><td>xUnit 2条</td></tr>
+    <tr><td><span class="tasknum">4</span>HttpListener</td><td>内嵌本地小服务,127.0.0.1 only,路由 /ping /status</td><td>ControlHttpServer.cs</td><td>编译</td></tr>
+    <tr><td><span class="tasknum">5</span>完整启动序</td><td>Program:Mutex→参数→Login→盘→ScanDevices→StartRun→HTTP驻留<br><span class="fl">复刻 operate MainWindow:78-130,顺序不可变</span></td><td>Program.cs</td><td>编译</td></tr>
+    <tr><td><span class="tasknum">6</span>operate 改拉起</td><td>删进程内 StartRun,新增 ControlProcessLauncher(探活→Process.Start→轮询);App.config 加 controlPort/controlExePath</td><td>Launcher + MainWindow:67-134 + App.config</td><td>编译</td></tr>
+    <tr><td><span class="tasknum">7</span><span class="b down">真机</span>端到端</td><td>部署→起微服务→启 operate 登录→观察 control 被拉起/续命/复用/单实例/采集闭环</td><td>文档回写</td><td>运行+真机</td></tr>
+  </table>
+  <div class="callout info"><b>开工方式:</b> 子代理驱动开发(每 Task 派子代理实现+两阶段审查)。<b>先建 feature 分支,勿在 main 直接改代码。</b> 每步按回写协议更新进度文档。</div>
+  </div>
+</section>
+
+<section id="split">
+  <h2><span class="num">15</span> 双进程 8 个改造点(现状直连 control 的地方) <span class="chev">▼</span></h2>
+  <div class="body">
+  <p class="lead">合并后 control 跑在 operate 同进程,这 8 个直连点要全改成跨进程(启动 control.exe + HTTP/IPC)。这是拆分的"拆除清单"。</p>
+  <table>
+    <tr><th>#</th><th>直连点(file:line)</th><th>现状</th><th>拆后改成</th><th>阶段</th></tr>
+    <tr><td>1</td><td>MainWindow.xaml.cs:89</td><td>ControlAppData.Login() 透传</td><td>启动独立进程+透传凭证</td><td>1</td></tr>
+    <tr><td>2</td><td>MainWindow.xaml.cs:100</td><td>直写 LogService.Pan</td><td>启动参数</td><td>1</td></tr>
+    <tr><td>3</td><td>MainWindow.xaml.cs:103,121</td><td>new StartMain()+StartRun()</td><td>拉起 control.exe 等就绪</td><td>1</td></tr>
+    <tr><td>4</td><td>MainWindow.xaml.cs:110,112</td><td>HAL.ScanDevices()</td><td>移入 control 进程</td><td>1</td></tr>
+    <tr><td>5</td><td>HouseDebugPageViewModel.cs:256,257</td><td>HAL.Acquire 借串口</td><td>跨进程借用协议 /serial/pause|resume</td><td>2</td></tr>
+    <tr><td>6</td><td>BufferDebugViewModel.cs:163,164</td><td>HAL.Acquire 借串口</td><td>同上</td><td>2</td></tr>
+    <tr><td>7</td><td>ServiceMonitorViewModel.cs:77</td><td>GetMonitorSnapshot() 直读</td><td>HTTP /status 拉快照</td><td>2</td></tr>
+    <tr><td>8</td><td>MainPageViewModel.cs:768</td><td>直写 LastHttpOkAt</td><td>跨进程上报心跳</td><td>2</td></tr>
+  </table>
+  </div>
+</section>
+
+<!-- ============================ PART E ============================ -->
+<div class="part-banner e">E · 怎么上手(环境 / 已知坑 / 开工)</div>
+
+<section id="env">
+  <h2><span class="num">16</span> 技术栈 / 环境 / 构建 <span class="chev">▼</span></h2>
+  <div class="body">
+  <div class="grid2">
+    <div>
+      <h4>技术栈</h4>
+      <ul class="clean">
+        <li>桌面端:C# / .NET 6(net6.0-windows)/ WPF / MVVM(CommunityToolkit.Mvvm)</li>
+        <li>JSON:Newtonsoft.Json;单测:xUnit(见 control/IvfTl.AutoFocus.Tests)</li>
+        <li>解决方案:<code>ivf_tl_operate_2.0/ivf_tl_Operate.sln</code> 与 <code>control/ivf_tl_Control.sln</code></li>
+        <li>权限:operate/control 都要求 <code>requireAdministrator</code>(串口/相机/盘符)</li>
+      </ul>
+      <h4>代码检索(重要)</h4>
+      <p class="note">仓库已建 <b>codegraph 索引</b>。理解/定位代码优先用 <code>codegraph_explore</code>/<code>codegraph_node</code> 或 shell <code>codegraph explore "..."</code>,别上来就 grep。增删文件后跑 <code>codegraph sync</code>。</p>
+    </div>
+    <div>
+      <h4>构建 / 运行依赖</h4>
+      <ul class="clean">
+        <li><code>dotnet build</code>;Java 微服务用 JDK11+Maven3.9.9 @ <code>C:\TLData\tools</code></li>
+        <li>control 启动要连本机 gateway(127.0.0.1:10010)登录、连 108 的 MySQL/Kafka/MQTT/Nacos</li>
+        <li>起微服务集群:<code>bash 项目文档/开发环境/start-all.sh</code></li>
+        <li>control 运行依赖 <code>DependFile</code>(SQLite库/相机原生DLL)+ App.config,部署须随 exe 到位</li>
+      </ul>
+      <h4>排障(操作日志)</h4>
+      <p class="note">拿 trace_id 查 <code>log.operation_log</code>(192.168.0.108:3306,root/root)拉跨端时间线,找 <code>result=失败</code> 读 input+error。需先起 <code>aivfo-oplog</code> 消费端,否则消息只堆 Kafka 不入库。</p>
+    </div>
+  </div>
+  </div>
+</section>
+
+<section id="pitfall">
+  <h2><span class="num">17</span> 已知坑(务必知道) <span class="chev">▼</span></h2>
+  <div class="body">
+  <ol class="clean">
+    <li><b>operate 两个 build 行为不同</b>:Debug 版 <code>AppData.cs:91-111</code> 有 <code>#if DEBUG</code> 把服务器地址覆盖成测试外网(test-gateway)——<b>真机/本机验证必须用 Release</b>(走 App.config 的 127.0.0.1/108)。(=M-07)</li>
+    <li><b>管理员进程</b>:operate/control 都 requireAdministrator,非交互 shell RunAs 提权可能弹不出(起不来/杀不掉)。</li>
+    <li><b>control 启动依赖运行时文件</b>:DependFile(SQLite/相机DLL)+ App.config,拆独立进程部署时要随 control.exe 到位,否则 StartRun 失败。清单见 control 全景 §八。</li>
+    <li><b>两套并行串口/相机栈</b>(旧 ComEntitys + HAL 包装栈),迁移期现状,排障要分清当前走哪条(全景 §六)。</li>
+    <li><b>本地 SQLite 多数表须预置</b>:<code>aivfoTL.db</code> 须预置文件;但 <code>house_autofocus_calibration</code> 例外,由 <code>DBService</code> 启动时 <code>CodeFirst.InitTables</code> 自动建(<code>CREATE TABLE IF NOT EXISTS</code>),勿当缺表 bug 排查。</li>
+    <li><b>老壳 ivf_tl_ControlTest</b>:混测试代码、命名乱、已被 operate 旁置不引用。<b>别改造它</b>;新启动器基于干净的 ivf_tl_Control 类库新建,老壳阶段3 退役删。</li>
+  </ol>
+  </div>
+</section>
+
+<section id="howto">
+  <h2><span class="num">18</span> 开工方式 & 续接 <span class="chev">▼</span></h2>
+  <div class="body">
+  <div class="grid2">
+    <div>
+      <h4>开工方式(用户已定)</h4>
+      <ul class="clean">
+        <li>子代理驱动开发:每 Task 派全新子代理实现 + 两阶段审查(spec 合规→代码质量)。</li>
+        <li><b>先建 feature 分支</b>(勿在 main 直接改代码)。</li>
+        <li>阶段1 Task1-6 纯编码;Task7 真机验证须用户在场。</li>
+        <li>每完成一步按回写协议更新:进度状态.yaml(断点)+ 交接卡.md(追加)+ 工作计划表 + 进度数据.js。<b>提交边界 = 文档已同步</b>。</li>
+      </ul>
+    </div>
+    <div>
+      <h4>权威文档导航(深入查)</h4>
+      <ul class="clean">
+        <li><code>需求文档/specs/...双进程拆分-design.md</code> — 架构设计全文(动手前必读)</li>
+        <li><code>开发计划/...阶段1-control独立进程骨架.md</code> — 阶段1 七任务(带完整代码/命令)</li>
+        <li><code>需求文档/control-逻辑与配置全景.md</code> — control 现状全图(改 control 前查)</li>
+        <li><code>需求文档/操作端逻辑与配置全景.md</code> — operate 现状全图 + §八降级登记</li>
+        <li><code>进度/</code> — 进度状态.yaml(断点)/工作计划表/交接卡/待验证清单(M-01~M-07)</li>
+        <li><code>CLAUDE.md</code> — 项目铁律(中文/codegraph/回写协议)</li>
+      </ul>
+    </div>
+  </div>
+  <p class="note" style="text-align:center;margin-top:18px;color:#94a3b8">— 本总文档基于 2026-06-22 源码复核生成,汇总自上述各权威文档;若代码与本页不符,以源码 + 对应全景文档为准 —</p>
+  </div>
+</section>
+
+</main>
+</div>
+<script>
+document.querySelectorAll('section>h2').forEach(h=>h.addEventListener('click',()=>h.parentElement.classList.toggle('collapsed')));
+const links=[...document.querySelectorAll('nav.toc a')];
+const secs=[...document.querySelectorAll('section')];
+const obs=new IntersectionObserver(es=>{es.forEach(e=>{if(e.isIntersecting){
+  links.forEach(l=>l.style.background='');
+  const a=links.find(l=>l.getAttribute('href')==='#'+e.target.id);
+  if(a)a.style.background='#1e293b';
+}});},{rootMargin:'-8% 0px -82% 0px'});
+secs.forEach(s=>obs.observe(s));
+links.forEach(a=>a.addEventListener('click',ev=>{
+  const t=document.querySelector(a.getAttribute('href'));
+  if(t){ev.preventDefault();t.classList.remove('collapsed');t.scrollIntoView({behavior:'smooth'});}
+}));
+</script>
+</body>
+</html>

+ 56 - 0
项目文档/进度/交接卡.md

@@ -19,3 +19,59 @@
 - **顺带(非本任务,环境修复)**:本会话还修了 data-transmission 启动失败(`application-local.properties` 里 jna.lib-path/video 路径写死成 D:\WorkSpace 开发机路径→改本机 + 重编译,DLL 加载成功、10030 通);起了本机微服务集群(gateway 等 6 服务,oplog 原在跑)。
 - **踩坑/待办**:① 旧 operate 残留进程 PID 20268(高权限,非交互 shell 杀不掉)需用户在桌面任务管理器/重启清掉;② operate Debug 版有 `#if DEBUG` 把地址覆盖到 207 外网,真机验证须用 Release。
 - **下一步**:① 建 feature 分支(勿在 main 改代码);② 子代理驱动执行阶段1 Task1-6(编码);③ Task7 真机验证待用户在场。
+
+---
+
+## 2026-06-22 · 合并工作复核评审 + 新增 operate 全景文档 + 降级遗留登记
+
+- **背景**:用户指出"三项目合并做到一半、没测、流程跑不通",要求只对源码(合并前 `临时文件/` 基准 ↔ 现有合并版)、不依赖 git,完整复核合并改动与文档完整性。
+- **复核结论(逐条源码坐实)**:
+  1. **合并保真**:control 整套搬运无源码丢失;AutoFocusTool 重构进 IvfTl.AutoFocus/IvfTl.Hardware,对焦核心算法逐字保留;operate↔control 经 ProjectReference 真接线(同名程序集已改名解冲突),静态看 operate 可编译(未实跑 build)。
+  2. **operate 侧 4 处真实功能降级**(根因:control 端 Commander 缺 builder,HAL `SerialChannelImpl` 返回桩值):排气阀时间写(:130 return false)/读(:137 return -1)、缓冲瓶灯光写EEPROM(:143 return false)、调试页存图(CameraImpl:148 丢宽高转 SaveBmpPic 未验证)。另有写EEPROM"成功=true"不可靠等待验证项。
+  3. **control 全景文档准确性高(~90%)**:抽核 file:line 几乎全中,§九 四条论断属实,降级如实披露;勘误若干(ConvertHelper 路径应为 ivf_tl_UtilHelper、ComBin 30s 超时仅一级、SQLite 应为9表、leftOffset/bottomOffset/focusPeakRatioThreshold 在 control 不消费)——已记录,待随手修订。
+- **改动(本次新增/回写)**:
+  - 新增 `需求文档/操作端逻辑与配置全景.md`(operate 全景:启动时序/业务模块逐功能/8个 operate↔control 直连点/HTTP 接口全表/凭据加密/配置全键/**§八 合并降级登记**/后台线程/上线前清理项/拆分结论)。
+  - `待验证清单.md` 新增"合并遗留·operate 侧"区(M-01~M-07)。
+  - 订正 `CLAUDE.md`/`00-需求总览.md`/`工作计划表.md`/`进度状态.yaml` 里"M0-M8 已全部结项"为"代码完成、真机验收整体未做、有降级遗留";三处文档地图挂上 operate 全景。
+- **核实**:4 处降级 + 直连点 + HTTP 接口均在现有源码逐一 file:line 坐实;两份全景互为两半(control + operate)。
+- **踩坑**:源码注释里引用的 V-010 / "13 §⑤" 指向已删旧文档(可追溯性断裂),本次用 M-01~M-07 在现存文档重新承接。Release 与 Debug 地址差异(`AppData.cs:91-111` `#if DEBUG` 覆写到 test-gateway 外网)——真机/排障必须用 Release。
+- **下一步**:① 用户过目两份全景 + 待验证清单;② (用户已要)产出超详细 HTML 业务流程图;③ 真机门控项(M-01~M-07)待用户在场逐条验/补 control builder。
+
+---
+
+## 2026-06-22 · 全量文档↔源码核对评审 + P0/P1 修正落地
+
+- **背景**:用户要求结合三个原始项目(`临时文件/`)、合并版 operate、front、108 数据库,全面核对 `项目文档/` 全部文档的完整性/全面性/滞后性/合理性,**只认源码+数据库,不看 git 历史**。
+- **核对方法**:派 5 路子代理 + 主线复核,逐条核对 ~120 处 file:line 声称。结论:
+  1. **operate 全景 ≈97% 准**(8 直连点 + 4 处降级 100% 逐行属实);**control 全景 ≈92% 准**(行号锚点几乎全中,问题在少数语义结论)。
+  2. **合并保真度坐实**:control 整套 0 文件丢失(`comm -23` 空)、对焦核心算法逐字保留(仅命名空间改 + 硬件面换 HAL)、同名程序集已改名(ivf_tl_Control_Entity/Services)。
+  3. **108 数据库实测全过**:7 库齐(JDBC 直连 SHOW DATABASES 验证)、`log.operation_log` 19 列含 CLAUDE.md 列出的全部关键列、`aivfo_tl_setting` 配置表(tl_setting/house/house_well_setting + 对焦扩列)齐。
+  4. **进度文档不滞后**:合并 operate 下无 ControlHost 目录 → 阶段1"未开工"属实。
+- **改动(本次回写,纯文档对齐,无代码改动)**:
+  - **P0(编译硬伤)** `开发计划/2026-06-22-阶段1-control独立进程骨架.md`:ControlHost `Program.cs` 的 `Log4netHelper` 命名空间错——control 子树是 `IvfTl.Control.Services`(程序集 `ivf_tl_Control_Services`),计划照搬了 operate 端的 `ivf_tl_Services` 会 CS0234。已在 using 区加 `using IvfTl.Control.Services;` + 块内 11 处改裸 `Log4netHelper`;operate 侧 Step3 块保持 `ivf_tl_Services`(本就对)并加"勿混"提示;行号 `67-134`→`67-132`(3 处)。
+  - **P1(事实错)**:① control 全景 §7.1 `8表`→`9表` + `需预置不建表`→`8表预置、house_autofocus_calibration 由 DBService CodeFirst.InitTables 自动建`;② 背景指南 §六.1 `192.168.0.207 外网`→`#if DEBUG 顺序覆盖后最终生效 test-gateway.aivfo.com:36000`、§六.5 同步订正建表表述;③ HTML 总图 §12+§17 两处 SQLite"不建表"同步订正。
+- **核实**:grep 校验残留 `67-134`=0、`8 表,DependFile`=0、`不自动建表/需预置不建表`=0;ControlHost 块无错误命名空间、operate 块保留正确命名空间。
+- **未改(P2,影响小,用户暂未要)**:control 全景 §8.2 三个"伪配置"(leftOffset/bottomOffset 死代码、focusPeakRatioThreshold 硬编码 1.2)、§四散文 `bufferBottlerPressureMin+10`(源码无 +10);完整性小缺口:`OperateHwTest`(218 行控制台测试 Exe,不在主 sln)、`IvfTl.AutoFocus.Tests`(游离工程)均无文档登记;front `oplog-config.json` 实在 `ivf_tl_Manage/` 非"front 根";start-all.sh 头注释路径写"临时文件/"实在"开发环境/"。
+- **下一步**:① 阶段1 开工时按修正后的 Program.cs 命名空间写,可避开 CS0234;② 如需,补登 OperateHwTest/IvfTl.AutoFocus.Tests + 修 P2 散碎项。
+
+---
+
+## 2026-06-22 · CLAUDE.md 核对修订 + 连接配置清单 front oplog 订正
+
+- **背景**:用户要求核查仓库根 `CLAUDE.md` 的问题并修正。逐条用源码/108 数据库核对(非 git)。
+- **核实(源码坐实,CLAUDE.md §五 操作日志节高度准确)**:front `ivf_tl_Manage/App.config` **确有** `kfkaIP=192.168.0.108/kfkaPort=9092`;`App.xaml.cs InitOperationLog()`(project=front、topic=tl-oplog)**确实**起 Kafka 产消息端;operate `oplog-config.json` 模块(界面点击/HTTP/串口/相机/舱室设置/对焦调试/对焦设置/缓冲瓶调试/皿管理/胚胎操作)与 CLAUDE.md §5.1 **一字不差**;front config 仅 界面点击/HTTP。→ §5.2.3"operate/front…kfkaIP"正确。
+- **改动 CLAUDE.md(6 项)**:① §二+§四 补 `项目背景与上手指南.md` 为开机先读第1篇/文档地图首条;② §二末环境权威源由 `环境与账号清单.md`(本机 Docker 备用)改指 `服务器测试环境.md`(108 当前部署)+`连接配置清单`;③ §六 新增 C#/.NET 编译段(net6.0-windows、三 sln、dotnet build、真机必 Release、MSB3021);④ §四 开发环境补 `服务器测试环境.md`;⑤ §5.3 operation_log 列 15→实测 19 列(补 parent_id/level/host/create_time);⑥ §二 标为"续接顺序唯一权威,背景指南/00总纲应对齐"。
+- **改动 连接配置清单-换服务器必读.md**:§三 front 表补 `kfkaIP/kfkaPort` 行;订正"front 无 Kafka/oplog 接入"为"front **有** oplog 接入"(附 App.config/App.xaml.cs 证据);§一 Kafka 行"业务里怎么连"由"operate"改"operate/front"。
+- **踩坑**:`连接配置清单 §三` 旧结论"front 无 Kafka/oplog"与源码相反——典型的文档滞后于代码;已订正。
+- **下一步**:无强制后续。背景指南 §四 / 00总纲 §六 的阅读顺序若要与 CLAUDE.md §二 完全统一,可另行对齐(本次仅在 CLAUDE.md 标注权威)。
+
+---
+
+## 2026-06-22 · HTML 总图背景段补强(三项目澄清 + 半成品提示)
+
+- **背景**:用户反馈 `系统业务流程详图.html` 背景段未点明"三项目"具体是哪三个、也没在背景段提示"上一个任务是半成品"。核实:§1.1 只讲 operate+control"两个独立进程",AutoFocusTool 要翻到 §7.1 才出现;§0 程序表列 operate/control/front,易被误读成"三项目=operate+control+front"(实则 front 从未合并)。
+- **改动 `系统业务流程详图.html`**:
+  - §1.1 后加 info callout:**"三项目"= operate + control + AutoFocusTool**(对焦工具,重构进 IvfTl.AutoFocus/IvfTl.Hardware,算法逐字保留,见 §7.1);**front 从未被合并、本次不动**;原始三项目代码在 `临时文件/` 下。
+  - §1(改造前 vs 改造后 之后)加 warn callout:**本次是接一个"半成品"**——上个三项目合并任务代码完成、但真机验收整体未做、有 operate 侧降级遗留(指向 §13 / 待验证清单 M-01~M-07)。
+- **核实**:三项目合并范围(operate+control+AutoFocusTool→operate、front 独立)此前已由原始↔合并比对坐实(交接卡 2026-06-22 复核段);AutoFocusTool 算法逐字保留、重构进 IvfTl.AutoFocus/IvfTl.Hardware 属实。
+- **下一步**:无强制后续。

+ 1 - 1
项目文档/进度/工作计划表.md

@@ -2,7 +2,7 @@
 
 > 续接载体之一(人类可读主表)。配套:进度状态.yaml / 交接卡.md / 待验证清单.md。
 > **状态图例**:☑完成(含真机验证)|🟢代码完成待真机|◐进行中|☐未开始。
-> 新任务(2026-06-22 启动)。旧任务(M0-M8 合并改造)已结项、文档已清空,本表仅服务新任务。
+> 新任务(2026-06-22 启动)。旧任务(M0-M8 合并改造)**代码完成、真机验收整体未做、有 operate 侧降级遗留(见待验证清单 M-01~M-07)**、旧文档已清空,本表仅服务新任务。
 
 ---
 

+ 17 - 0
项目文档/进度/待验证清单.md

@@ -32,3 +32,20 @@
 | D3-01 | 退役删 ivf_tl_ControlTest 后 operate/control 仍编译运行正常 | 编译+运行 | ☐ |
 | D3-02 | operate 开机自启,开机自动起 operate→拉起 control | 运行 | ☐ |
 | D3-03 | 全新部署一次到位(两 exe + 端口 + DependFile + 开机自启) | 部署 | ☐ |
+
+## 合并遗留 · operate 侧降级 / 待验证(与双进程拆分并行存在)
+
+> 来源:2026-06-22 复核评审逐条源码坐实;详见 `需求文档/操作端逻辑与配置全景.md` §八。
+> 这些是"三项目合并"阶段**做了一半、未真机验收**的功能缺口——合并代码完成 ≠ 业务闭环。
+> 状态:☐ 未验 / ☑ 已验通过 / ✗ 验证失败 / ▼ 已知降级未接通。
+> 根因统一:`ivf_tl_operate_2.0/control/IvfTl.Hardware/Impl/SerialChannelImpl.cs`(control 端 Commander 缺 builder)。
+
+| 编号 | 验证项 | 现状(file:line) | 门控 | 状态 |
+|------|--------|------|------|------|
+| M-01 | 舱室排气阀时间**写** EEPROM 真下发下位机 | `SerialChannelImpl.cs:130` return false 不下发,仅本地暂存(HouseDebug:534);需补 control `CreateWriteEEPROOpenVentTimeCommand` + 真机核对字节(源码注释挂 V-010) | **真机** | ▼ |
+| M-02 | 舱室排气阀时间**读** 真读下位机 | `SerialChannelImpl.cs:137` return -1,静默保留旧值(**无 UI 提示**,最易误判正常) | **真机** | ▼ |
+| M-03 | 缓冲瓶灯光亮度**写** EEPROM 真下发 | `SerialChannelImpl.cs:143` return false,仅本地+服务器(Buffer:268);需补 control `CreateWriteEEPROMLightNum` | **真机** | ▼ |
+| M-04 | 调试页存图与基准一致 | `CameraImpl.cs:148` 丢弃宽高转 `SaveBmpPic`,自称等价**未验证**;核对落盘格式/旋转/位深(**无 UI 提示**) | **真机** | ☐ |
+| M-05 | 写 EEPROM 类"成功=true"可靠性 | `WriteWellHorizontalPos`/`WriteScanStep`/`WriteOpenIntakeTime` 阻塞收回复即 true,未校验真实成功(SerialChannelImpl.cs:106-118) | **真机** | ☐ |
+| M-06 | `ReadWellFocusZero` 按 well 区分 | control Z 对焦零点为整舱单值,`well` 入参被忽略,与 autofocus 按 well 读零点有差异(SerialChannelImpl.cs:94) | **真机** | ☐ |
+| M-07 | Release 连内网网关(非测试外网) | `AppData.cs:91-111` `#if DEBUG` 覆写 BaseUrl 到 test-gateway 外网;真机/排障必须用 Release + 现场核对 `urlIp` | 部署 | ☐ |

+ 6 - 4
项目文档/进度/进度状态.yaml

@@ -1,7 +1,7 @@
 # 续接断点状态(机器可解析)。换会话/换电脑后首先读它定位。
 # 状态取值: 未开始 / 进行中 / 完成 / 代码完成待验证
 # 纪律:本字段只存【当前断点】,历史细节进 交接卡.md(见 CLAUDE.md 第三节)。
-更新时间: 2026-06-22 新任务启动:operate/control 双进程拆分。旧任务(M0-M8合并改造)已结项、旧文档已清空、文档体系已按新任务重建。阶段1计划已就绪,待开工。
+更新时间: 2026-06-22 复核评审完成:合并代码逐条对基准核实(control 保真/operate 4处降级坐实),新增 operate 全景文档,降级遗留登记入待验证清单 M-01~M-07,订正各处"已结项"表述。双进程拆分阶段1 仍待开工。
 当前任务: >
   【新任务·operate/control 双进程拆分·阶段1 待开工】
   · 已完成:需求梳理(control 全景+全配置参数)、架构设计(双进程+本地HTTP+按需拉起+受护栏停止)、
@@ -13,9 +13,11 @@
     ③真机门控(Task7:能起/能连/operate关了续命/单实例)须用户在场。
   续接读:进度状态.yaml(本文件) + 开发计划/阶段1计划 + 需求文档/specs/设计 + 交接卡末尾。
 说明: >
-  旧任务(三项目合并改造 M0-M8)已全部结项,项目文档下旧文档(00总纲/需求文档01-14/开发计划归档/历史报告)
-  由用户主动清空,为新任务腾空。本文档体系(2026-06-22 重建)仅服务新任务"operate/control 双进程拆分"。
-  旧任务的代码成果仍在仓库,本次新任务在其基础上做进程拆分。
+  旧任务(三项目合并改造 M0-M8):**合并代码完成,但真机验收整体未做、且有 operate 侧功能降级遗留**
+  (排气阀时间读/写、缓冲瓶灯光写EEPROM、调试页存图等,见 待验证清单.md M-01~M-07 与
+  需求文档/操作端逻辑与配置全景.md §八)。项目文档下旧文档(00总纲/需求文档01-14/开发计划归档/历史报告)
+  在 2026-06-22 重建时清空归档,本文档体系仅服务新任务"operate/control 双进程拆分"。
+  旧任务代码成果仍在仓库,本次新任务在其基础上做进程拆分。
 阶段概览:
   - id: 阶段1
     名称: control 独立进程骨架(能起/能连/operate关了续命)

+ 1 - 1
项目文档/需求文档/control-逻辑与配置全景.md

@@ -155,7 +155,7 @@
 
 ## 七、数据库与数据流
 
-### 7.1 本地 SQLite `aivfoTL.db`(8 表,`DependFile\DB\`,SqlSugar,**需预置不建表**)
+### 7.1 本地 SQLite `aivfoTL.db`(9 表,`DependFile\DB\`,SqlSugar;**8 表须预置,`house_autofocus_calibration` 例外——由 `DBService` 启动时 `CodeFirst.InitTables` 自动建(`CREATE TABLE IF NOT EXISTS`,失败仅记日志不阻断)**)
 | 表 | 用途 |
 |---|---|
 | `tl_setting`/`house`/`house_well_setting` | 中央配置的本地缓存(断网兜底) |

+ 238 - 0
项目文档/需求文档/操作端逻辑与配置全景.md

@@ -0,0 +1,238 @@
+# operate 操作端 · 逻辑与配置全景(接手指南)
+
+> 2026-06-22 全量源码梳理(3 路并行核实),**一切以源码为准,关键处标 `file:line`**。
+> 用途:接手 operate(操作端 WPF)这套合并后代码的全图;与 `control-逻辑与配置全景.md` 互为两半(control=机器驱动大脑,operate=人机交互+承载 control)。
+> 行号若无另注,均指 `ivf_tl_operate_2.0/ivf_tl_Operate/`(现有合并版)下对应文件;差异对照基准版 `临时文件/ivf_tl_operate_2.0/ivf_tl_Operate/`。
+> ⚠ **第八节是本文核心**:合并后相对基准的改动/降级/待验证项登记(operate 侧)——control 全景管不到这半边,坑都在这里。
+
+---
+
+## 〇、一句话定位
+
+**operate = 培养箱的"人机交互端 + control 宿主"**:登录→显示舱室/胚胎/曲线/报警→工程师调试(借 control 串口动硬件)→改配置/加皿/管胚胎,全走 HTTP 与服务器交互;**合并后还在自己进程里把 control 当类库托起来跑**(`Task.Run StartRun`),所以现在"一个软件"= operate UI 进程内含 control 驱动线程。
+
+关键现状(合并引入):
+- **operate 进程内托管 control**:登录成功后后台线程 `ivf_tl_Control.AppData.Login → HAL.ScanDevices → StartMain.StartRun()`(`MainWindow.xaml.cs:67-132`)。这是"operate 关了 control 也跟着死"的根源,也是双进程拆分要拆的点。
+- **调试页改 HAL 借用**:所有"进调试动硬件"的页面,从基准的"自己 `new ComBin`/`new Camera` 直开物理口",改为向 HAL 借用采集端**同一物理句柄**(`gate.Acquire(OperateDebug)`),用完只归还不关口。
+- **新增三页**:服务监控(只读跨读 control 快照)、统一配置(写本地 App.config)、软键盘(替代 osk.exe)。
+- **降级集中地**:4 处硬件下发被降级,根因统一在 `control/IvfTl.Hardware/Impl/SerialChannelImpl.cs`(control 端缺 Commander builder)。
+
+---
+
+## 一、进程 / 启动时序
+
+单实例(Mutex `ivf_tl_Operate`)。启动顺序:
+
+| 序 | 触发点 | file:line | 做了什么 |
+|---|---|---|---|
+| ① | `App()` 构造 | App.xaml.cs:25-31 | 记日志;注册 `App_Startup`/`App_Exit`(**`App_Exit` 体为空——退出语义缺失**) |
+| ② | `OnStartup` | App.xaml.cs:33-46 | **单实例互斥**,已运行则提示并 `Shutdown`;开 Stylus 触控 |
+| ③ | `App_Startup` | App.xaml.cs:52-68 | 注册 UI/Task/非UI 三类全局异常;**`MigratePlaintextCredentials()`**(明文口令一次性加密回写,须先于读凭据);`InitOperationLog()`;`ClickTrailLogger.Install()`(全局点击层);`ChangeLanguage()` |
+| ④ | `InitOperationLog` | App.xaml.cs:74-96 | 起操作日志:project=operate、Kafka=`kfkaIP:kfkaPort`(缺则 127.0.0.1:9092)、topic=`tl-oplog`、配置 exe 同目录 `oplog-config.json`;全 try 兜底 |
+| ⑤ | `MainWindow()` 构造 | MainWindow.xaml.cs:27-45 | 把 MainGrid 包进 Viewbox(Uniform)做竖屏自适应(**合并去掉了 `#if DEBUG`,Release 也生效**) |
+| ⑥ | `MainWindow_Loaded` | MainWindow.xaml.cs:47-61 | **弹 `LoginWindow.ShowDialog()`**,失败则关窗退出;成功后建 `MainPageViewModel(TlSn)`+首页 |
+| ⑦ | 登录提交 | LoginWindow.xaml.cs:42-95 | 校验账号/密码/设备号;`AppData.AppDataInit()` 成功后 `setAppConfig()` 写回 |
+| ⑧ | `AppDataInit` | AppData.cs:114-147 | 建 HttpHelper→**云端校验账号**→置 TlNum/TlSn(`NEO-1-{TlNum}`)→建 MqttHelper(连 broker 订阅)→拉 TLSettingCommon+受精类型字典→设操作日志上下文 |
+| ⑨ | 登录后后台线程 | MainWindow.xaml.cs:67-132 | **`Task.Run` 托管 control**:`ivf_tl_Control.AppData.Login`(同账号透传)→ `LogService.Pan=cacheDisk` → `HAL.ScanDevices()`(设备发现一次)→ `StartMain.StartRun()`(阻塞常驻)。**全程异常只记日志、不退进程**(已去掉 control 原 `Environment.Exit`) |
+
+> control 登录窗(Window1)已从启动路径剔除,改由 operate 单登录账号透传。`ScanDevices` 仅在 ⑨ 调一次,保证前后台拿同一组句柄。
+
+---
+
+## 二、窗口 / 页面骨架
+
+**Windows/(独立窗口)**:`LoginWindow`(登录,内置软键盘)、`MessageWindow`/`MessagePrompt`(提示/确认)、`GetDateWindow`(选日期)、`PhotoWindowNew`(看图)。
+
+**View/(MainWindow 容器内切换的 UserControl)**:`MainPageView`(首页舱室总览)、`DetailPageView`(胚胎详情,含 10s 报警轮询)、`PhotoPageView`/`ChartPageView`/`AlarmPageView`/`DishRecordView`、`HouseSettingPageView`/`HouseDebugPageView`/`AutoFocusSettingView`/`CCDSettingView`/`BufferDebugView`、`SettingPageView`/`AboutView`/`AddDishWindowView`、**`ServiceMonitorView`(新增)**、**`UnifiedConfigView`(新增)**。
+
+**CustomUserControls/**:`CustomHouseInfo`/`CustomDishInfo`/`PhotoBox`/`AlarmControl` 等;**`SoftKeyboard`+`SoftKeyboardHost`(新增,替代 osk.exe)**。
+
+**导航**:单容器 `MainWindow._container.Content`,`LoadPage(UserControl)`(MainWindow.xaml.cs:135)切页,`AppData.BackMainPage()`(AppData.cs:177)回首页。无导航框架。
+
+---
+
+## 三、业务模块(职责 + 操作 + 背后动作)
+
+> 凡"动硬件"均走 `Serial.*`/`Cam.*`(= HAL 借用句柄 → control 串口/相机);凡"查/改数据"均走 `AppData.Instance.HttpHelper.*`(HTTP)。
+
+### 3.1 首页/舱室总览 — `MainPageViewModel`(1023 行)
+- **职责**:10 舱卡片状态、培养记录、报警计数/语音、MQTT 实时消息。
+- **操作**:看舱室 `Init()`:142→`GetHouseCultureListApi`;报警轮询 `StartThread()`:745→`SearchAlarmHistoryNumApi`/`TlInfoTimeApi`/`SearchAlarmHistoryApi`;报警静音 `SetAlarmMute`:957;MQTT 实时 `RecMqttMessage()`:516。
+- **结束培养** `EndDish(houseSn)`:347 —— **仅本地清空 ExDish/发育时间,无 HTTP 下发**(注意:与"皿管理结束培养走 HTTP"是两条路)。
+- **隐性写 control**::768 `ivf_tl_Control.AppData.Instance.LastHttpOkAt = DateTime.Now`(HTTP 成功时反写 control 单例,供监控透出)。
+- **差异**:与基准方法一一对应、**无功能降级**,改动仅加操作日志埋点(行号下移)。
+
+### 3.2 舱室设置 / 对焦调试 — `HouseDebugPageViewModel`(1592 行,基准 954)
+- **进/出调试**:`ComHouseInit()`:244 —— `HAL.GetHouseGate(houseSn).Acquire(OperateDebug)`:256-257 借用;返回 null=该舱正被采集占用→提示重试(:258-262),异常→提示并退出(:264-268)。`Serial`/`Cam` = `_halLease.Serial/.Camera`(:44/46)。`ComHouseUnit()`:355 —— **只 `Dispose()` 归还,绝不 ClosePort/UnInit**(:359-360,否则关掉采集端口)。
+- **读环境**:温 `RedTem`:383、压 `RedPre`:392、门 `RedDoor`:401。
+- **LED/气阀**:开关 LED(422/436)、开关进气阀(450/462)、开关排气阀(474/487)、舱室排气 `HouseVent`:510。
+- **电机点动**:水平/垂直 复位/前进/后退/绝对移动(:589 起)。
+- **写 EEPROM**:进气阀时间 `WriteOpenIntakeTime`:520(真下发)、垂直间隔脉冲 `WriteOVerSpace`:565、保存水平位 `SaveWellHor`:579。
+- **存图**:`SavePic`:748、水平抓图 `ShuiPingZhuaPai`:775、清晰图层抓图 `AutoFocusPic`:834。
+- **一键标定(新增)**:把借用的 `Serial`/`Cam` 喂 `new CalibrationEngine(...)`(见 `DebugCalibrationAdapter.cs`)。
+- ❗**降级 3 处见 §八**:排气阀时间写/读、存图实现。
+
+### 3.3 对焦设置 — `AutoFocusSettingViewModel`(33 行,两版相同)
+- 展示/编辑各舱 autoFocus 开关、well 级对焦设置。构造 HTTP `FocusSettingApi(TlSn)`:19。无串口/降级。
+- 对焦"层数/层间距/层下移"几何预览在 `DebugCalibrationAdapter.LayerPreviewItem`(`PhotoLayerCalculator.ComputeLayerPositions`)。
+
+### 3.4 缓冲瓶调试 — `BufferDebugViewModel`(311 行,基准 273)
+- **进/出**:`ComHouseInit()`:154 借用缓冲瓶舱(houseSn=11),`ComHouseUnit()`:208 归还。
+- **操作**:读状态 `BufferState()`:229;补气 `Are()`:238;进气阀时间 `writeE`:251→`WriteOpenIntakeTimeWait(...,isBuffer:true)`:259(真下发)+HTTP `LightsUpdateApi`:261;读灯光 `RedL()`:285(真读)。
+- ❗**降级 1 处见 §八**:灯光亮度写 EEPROM。
+
+### 3.5 皿管理 / 胚胎操作
+- **加皿/编辑/平衡**(逻辑在 `View/AddDishWindowView.xaml.cs`):`AddCultureRecordApi`:614、`UpdateDishInfoApi`:426、`StartBalanceApi`:669/`StopBalanceApi`:655。纯 HTTP,无降级。
+- **胚胎详情** `DetailPageViewModel`(101 行,两版相同):`Init()`:42→`GetQuickButtonsApi`:47、`GetRecordDetailApi`:53;详情页起 10s 报警轮询(`DetailPageView.xaml.cs:91-120`→`GetHouseCropAlarmApi`)。
+- **胚胎照片** `PhotoPageViewModel`(146 行,两版相同):`GetPictureViewApi`:88/`GetSourcePictureViewApi`:80,纯 HTTP。
+
+### 3.6 其它 ViewModel(逐一扫过)
+- `DishRecordViewModel`(34)/`AlarmPageViewModel`(42):纯分页/日期状态容器,无业务调用。
+- `ChartPageViewModel`(252,两版相同):环境曲线 `RefHistroy()`:206→`GetHouseEnvironmentListApi`。
+- `HouseSettingPageViewModel`(26):`SettingSystemApi`:17+`GetSettingHouseApi`:18,纯 HTTP。
+- **`ServiceMonitorViewModel`(192,新增,只读)**:每 2s 跨读 `ivf_tl_Control.AppData.Instance.GetMonitorSnapshot()`:77,无下发控件;⚠ `StaleSeconds=30`(:28)、堆积阈值(:112-116)标 `[D10]` 占位、`[M7]` 运行时未验证。
+- **`UnifiedConfigViewModel`(85,新增)**:只写本地层 App.config(IP/端口/盘符/tlNum/语言/凭据),`SaveAll()`:61 凭据 `SaveEncrypted` 加密;⚠ :60 **返回值恒 true 占位**(异常被 Helper 吞,不反映真实成败);数据库层项本页只读。
+- **`DebugCalibrationAdapter.cs`(55,新增)**:无运行逻辑,仅数据类型;注释说明旧 `DebugSerial/CameraAdapter` 已退役删除。
+
+---
+
+## 四、operate ↔ control 交接面(= 双进程拆分改造清单)
+
+合并后 control 跑在 operate **同进程**,operate 三种方式直连 control。拆双进程时**全部**要改成 IPC/HTTP:
+
+| # | file:line | 直连方式 | 拆进程后改成 |
+|---|---|---|---|
+| 1 | MainWindow.xaml.cs:89 | `ControlAppData.Login()` 同账号透传 | 启动独立进程 + 透传凭证 |
+| 2 | MainWindow.xaml.cs:100 | 直写 `ControlAppData.LogService.Pan` | 启动参数/进程内配置 |
+| 3 | MainWindow.xaml.cs:103,121 | `new StartMain()`+`StartRun()` 进程内跑 control | 拉起 control.exe 并等就绪 |
+| 4 | MainWindow.xaml.cs:110,112 | `HAL.Instance.Log/.ScanDevices()` | 移入 control 进程自做 |
+| 5 | HouseDebugPageViewModel.cs:256,257 | `HAL.GetHouseGate().Acquire` 借串口 | 跨进程借用协议(让出/恢复采集) |
+| 6 | BufferDebugViewModel.cs:163,164 | `HAL.GetHouseGate().Acquire` 借串口 | 同上 |
+| 7 | ServiceMonitorViewModel.cs:77 | `ControlAppData.GetMonitorSnapshot()` 直读 | HTTP/IPC 拉快照 |
+| 8 | MainPageViewModel.cs:768 | 直写 `ControlAppData.LastHttpOkAt` 心跳 | 跨进程上报通讯心跳 |
+
+> 全目录已确认:其余 ViewModel 无 HAL/ControlAppData 直连,只走 HTTP。
+> 借用语义:前台 `Acquire(OperateDebug)` 触发 control 采集让路(协作式),复用同一物理句柄,`Dispose` 即归还恢复采集——详见 `control-逻辑与配置全景.md` §6.2。
+
+---
+
+## 五、operate ↔ 服务器 HTTP
+
+### 5.1 HttpHelper 机制(`ivf_tl_Services/HttpHelper.cs`,1580 行,单例)
+- **BaseUrl**:`AppData.cs:80` `{urlIp}:{urlPort}/`(经 `AppConfigHelper` 容错读,缺键回默认);`Init` 建 `HttpClient`(强制 Tls12,`ExpectContinue=false`)后立即 `Login()`。
+- **超时(两层,无重试)**:`ServiceTimeout=12s`(:42,HttpClient 整体)、`AsyncTimeout=10s`(:39,每次 SendAsync 的 CTS)。**任何失败/超时直接返回 null**,各 Api 吞异常返默认。
+- **认证**:`GetToken()`:1348 缓存 token,每请求加头 `token`。
+- **统一收发**:三个 `callWebService` 重载(无 body / JSON / Dictionary→Form)+ `callWebServiceNoToken` → 全汇聚 `HttpClientSendAsync`:1449。
+- **trace_id + 计时 + 操作日志(M8-P3b,合并新增)**:`HttpClientSendAsync`:1449-1497 取 `OperationLogContext.TraceId ?? NewTraceId()` 写请求头 `traceId`(:1460,与 Java 网关对齐,实现 C#→Java 链路串联)+ Stopwatch 计时 + 四类分支统一 `SafeOpLog`(module=HTTP,operation=接口路径,output>2000 截断,全 try 兜底)。
+
+### 5.2 接口清单(全部经 HttpHelper,POST 居多)
+登录 `/api/gateway/auth/login`(:1322/1354);天平 起/停 `…/balance/startBalance`(:191)`/stopBalance`(:215);在培列表 `…/getHouseCultureList`(:246);皿 增 `addCultureRecord`(:279)/结束 `endCultureRecord`(:308)/更新 `updateCultureRecord`(:332);图片视频 `getPicturesByRecordId`(:364);记录详情 `getRecordDetail`(:400);切层 `switchVideoPictureLayers`(:440)/`switchVideoLayerByPicture`(:985);快捷/时间按钮 `…/getButtons`(:468/497);仪器设置 `…/setting/data`(:529);系统设置 `…/setting/system`(:563)/更新(:595);版本 `getVersionBySn`(:619);舱室设置 `…/setting/house`(:651)/更新(:681)/调试回写 `…/house/debugging`(:780)/well 更新 `…/house/well/update`(:808)/对焦 `…/house/focus/setting`(:835)/立即拍照 `…/house/immediately`(:866);通用设置 `…/setting/common`(:719)/更新(:751);光照更新 `…/lights/update`(:894);仪器时间 `…/tlInfo/time`(:927);字典 `queryDictionaryByType`(:960);视频时间轴 `getVideoTimeLine`(:1008);环境列表 `…/getHouseEnvironmentList`(:1033);标记去向 `markEmbryoDestination`(:1058);皿记录 查/数 `getEmbryoCultureRecord(Num)`(:1081/1107);告警 数/历史 `getAlarmNum`(:1135)/`getAlarm`(:1161);图片视图 `getPictures`(:1189)/原始图 `getSourcePictures`(:1215);按 id 取皿 `getCultureRecordById`(:1241);静音 `muteAlarm`(:1267);裁剪告警 `getHouseCropAlarm`(:1290);文件下载(GET) `DownLoadFileAsync`(:97/138)。
+> 接口清单与基准**无增删**,仅行号因埋点偏移。
+
+---
+
+## 六、凭据与加密
+
+- **读**:`userName`/`tlNum` 明文读;`passWord` 经 `CryptoHelper.Decrypt` 回填(LoginWindow.xaml.cs:36-39)。
+- **写**:`AppData.SetApp`/`setAppConfig`(AppData.cs:187-228,XmlDocument 直改 `ivf_tl_Operate.dll.config`)写 userName/tlNum 明文、passWord 加密。
+- **加密**:`Helpers/CryptoHelper.cs` —— **DPAPI `ProtectedData`,`LocalMachine` 作用域**,附加熵 `"ivf_tl_operate_M5_02"`,密文前缀 `enc:`+Base64;Encrypt/Decrypt/IsEncrypted 幂等、失败回退原值不抛。
+- **迁移**:`AppConfigHelper.MigratePlaintextCredentials()`(:88-126)启动时一次性把明文 passWord/engineerPwd 加密回写(幂等)。
+- **工程师口令**:`engineerPwd` 默认 `tl13579`(AppConfigHelper.cs:20-23),`GetEngineerPwd()`:129 解密取用。
+- **配置封装**:`Helpers/AppConfigHelper.cs` 容错读写(缺键返默认,不裸 NPE)。
+
+---
+
+## 七、配置参数全表(operate 侧 App.config)
+
+文件 `ivf_tl_Operate/App.config`。「基准」= 是否在基准版 `临时文件/.../App.config`(基准仅 11 键、无加密、无 oplog、无 control 业务键)。
+
+| key | 现值 | 类别 | 基准 / 合并新增 |
+|---|---|---|---|
+| autoFocus | 0 | 业务(下位机换气开关) | 基准有 |
+| outInter | 0 | 连接(0内网/1外网) | 基准有 |
+| userName | admin | 凭据 | 基准有 |
+| passWord | 启动迁移为 `enc:` | 凭据 | 基准有(原明文,**合并加密**) |
+| **engineerPwd** | `enc:`(默认 tl13579) | 凭据 | **合并新增** |
+| tlNum | 20230411 | 业务(设备号) | 基准有 |
+| urlIp | http://127.0.0.1 | 连接 | 基准有(原 192.168.1.92,**合并改值,需现场核对**) |
+| urlPort | 10010 | 连接 | 基准有 |
+| mqttIp | 192.168.0.108 | 连接 | 基准有(原 192.168.1.92,**合并改值**) |
+| mqttPort | 1883 | 连接 | 基准有 |
+| **kfkaIP / kfkaPort** | 192.168.0.108 / 9092 | 连接(oplog) | **合并新增** |
+| houseEnabled | 空 | 业务(禁用舱) | 基准有 |
+| Language | Chinese.xaml | 业务 | 基准有 |
+| **CCDError/csTime/gbTime/VentNum/VentPre/VentWaitTimeB/VentWaitTimeD/AutoWaitTime/CCDAutoWaitTime/CCDFailedWaitTime/CCDFailedNumber/QueuAir** | 见值 | 业务(control 换气/CCD) | **合并新增(并入的 control 键)** |
+| **StopPro** | 15 | 业务(关机倒计时) | **合并新增(control)** |
+| **cacheDisk** | C | 业务(缓存盘,MainWindow:96 透传 control) | **合并新增(control)** |
+
+> 含义见 `control-逻辑与配置全景.md` §8.1(这些 control 业务键由 control 端消费)。operate 端实际只读 outInter/urlIp/urlPort/mqtt*/kfka*/userName/passWord/engineerPwd/tlNum/Language/houseEnabled/autoFocus/cacheDisk。
+
+---
+
+## 八、★ 合并改动 / 降级 / 待验证登记(operate 侧核心)
+
+> control 全景管不到 operate 调试页,这一节是另一半文档的存在理由。**降级根因统一**:`control/IvfTl.Hardware/Impl/SerialChannelImpl.cs`——control 端 Commander 缺对应 builder,HAL 实现"不臆造字节"返回桩值,VM 层据返回值给提示。
+
+### 8.1 功能降级登记表(相对基准,真实功能缺失)
+
+| 模块 | 操作 | 基准行为 | 现状(file:line) | UI 提示 | 风险 |
+|---|---|---|---|---|---|
+| 对焦调试 | **写舱室排气阀时间** | `WriteEEPROOpenVentTimeWait` 真下发 | `Serial.WriteOpenVentTimeWait`→**`return false` 不下发**(SerialChannelImpl.cs:130-135);VM 仅本地暂存(HouseDebug:534-548) | ✅ 有("仅本地暂存") | **中**:工程师以为已写下位机,实际未生效。补 control `CreateWriteEEPROOpenVentTimeCommand` + 真机核对字节 |
+| 对焦调试 | **读舱室排气阀时间** | `ReadEEPROMVentWait` 真读 | `Serial.ReadOpenVentTimeWait`→**`return -1`**(SerialChannelImpl.cs:137-141);读到 -1 保持原值(HouseDebug:553-559) | ❌ **无提示(静默保留旧值)** | **中**:界面显示可能是陈旧值而非下位机实时值,最易被误判正常 |
+| 缓冲瓶调试 | **写灯光亮度 EEPROM** | `WriteEEPROMLightNumWait` 真写 | `Serial.WriteLightBrightnessWait`→**`return false` 不下发**(SerialChannelImpl.cs:143-147);VM 仅本地+HTTP(Buffer:268-283) | ✅ 有("仅本地暂存") | **中**:灯光只存服务器,未写下位机 EEPROM。补 control `CreateWriteEEPROMLightNum` |
+| 对焦调试 | **调试页存图** | `camera.SavePic(name,w,h)`→厂商 `MVCAPI.SavePic(buf,w,h,name)` | `Cam.SavePic`→`CameraImpl.SavePic` **丢弃宽高**→`SaveBmpPic(name)`(CameraImpl.cs:148-153) | ❌ **无提示(实现透明替换)** | **中**:存图链路换实现,语义"等价"但**未真机验证**,落盘格式/旋转/位深需核对 |
+
+### 8.2 待验证 / 桩(非降级,但成功语义不可靠或未实现)
+
+- **写 EEPROM 类"成功=true"普遍不可靠**:`WriteWellHorizontalPosWait`/`WriteScanStepWait`/`WriteOpenIntakeTimeWait` 因旧栈写命令无显式成功出口,**"阻塞收到回复即 return true"**,未校验真实成功(SerialChannelImpl.cs:106-118,接口注释 ISerialChannel.cs:54-65)。**有下发**,但成功与否未知。统一挂 `待真机 V-010`。
+- **`SerialChannelImpl.SendWait` 返回 null**(:61-66):裸帧通道未实现(TODO M2),当前调试页未用,仅影响未来扩展。
+- **`ReadWellFocusZeroWait` 语义差异**(:94-99):control 端 Z 零点为整舱单值,`well` 入参被忽略,与 autofocus 按 well 读零点有差异(M2 待验证)。
+- **`ServiceMonitorViewModel` 阈值占位**:`StaleSeconds=30`、上传堆积阈值标 `[D10]` 占位、`[M7]` 运行时未验证。
+- **`UnifiedConfigViewModel.SaveAll` 恒返回 true**(:60-82):异常被 Helper 吞,返回值不代表真实保存结果。
+
+### 8.3 合并新增能力(非降级,记录在案)
+- `Helpers/` 整目录新增:`AppConfigHelper`(配置容错)、`CryptoHelper`(DPAPI 加密)、`ClickTrailLogger`(全局点击层日志)。
+- `App.xaml.cs` 新增:凭据迁移、操作日志初始化、点击层安装、**UI 异常按签名 10s 节流**(:184-220,防 8GB 日志复发)。
+- 新增页:`ServiceMonitorView`(只读监控)、`UnifiedConfigView`(本地配置)、内置软键盘。
+- HttpHelper 新增 trace_id 承接 + 计时 + 操作日志埋点(见 §5.1)。
+
+### 8.4 bug 修复(合并相对基准修正,正向)
+- **语言字典移除**:基准 `RemoveAt(Count-1)` 盲删(误删 AdaptiveStyles 致 8GB 日志);合并改按引用/按 `/Language/` 精确移除(App.xaml.cs:265-281)。
+- **BaseUrl 取值健壮化**:基准裸 `ConfigurationManager.AppSettings[...]`(缺键 NPE/FormatException);合并改 `AppConfigHelper.GetString/GetInt`(缺键回默认不崩)。
+- **passWord 加密治理**:基准明文读写 → 合并 DPAPI 加密。
+
+---
+
+## 九、operate 自身后台线程(非 control)
+
+| 线程/任务 | file:line | 形态 | 职责 |
+|---|---|---|---|
+| control 后台托管 | MainWindow.xaml.cs:67-132 | Task.Run 一次 | 登录后起 control(内部阻塞常驻) |
+| 胚胎详情报警轮询 | DetailPageView.xaml.cs:91-120 | Task.Run+while+Sleep(10s) | **operate 唯一常驻轮询**:每 10s `GetHouseCropAlarmApi` 刷报警;vm==null 退出 |
+| MQTT 订阅回调 | AppData.cs:138-143 | 事件回调 | broker 推送舱室实时数据(登录建立) |
+| 日志洪流节流 | App.xaml.cs:184-205 | 静态字典 | 同签名异常 10s 内只放行一条(合并新增) |
+| 按钮短任务 | 多页 | Task.Run 短任务 | 调试/查询点击不卡 UI,用完即结束 |
+
+> operate 内无 DispatcherTimer/Timers.Timer/显式 new Thread;常驻后台只有"详情页 10s 轮询"+"control 托管线程"。
+
+---
+
+## 十、可疑残留 / 上线前清理项(均经核实,多为基准同源残留、合并未清)
+
+1. ⚠ **`AppData.cs:91-111` `#if DEBUG` 外网覆盖**:Debug 编译下连续 4 次覆盖 BaseUrl,最终写死 `http://test-gateway.aivfo.com:36000/`(测试外网),无视 App.config。**真机验证/抓真实问题必须用 Release**,否则连到测试环境。(与基准一致,非合并引入)
+2. ⚠ **`AppData.cs:84-90` `outInter==1` 外网分支**:Release 下也会把 BaseUrl 切 `test-gateway.aivfo.com:36000`、MqttIp `211.149.139.131`。当前 `outInter=0` 走内网,但这是个地址打外网的开关。
+3. ⚠ **`HttpHelper.GetMp4()`:75-94 死代码**:硬编码外网 FastDFS 地址 + 桌面绝对路径,无调用方(测试残留),建议删。
+4. ⚠ **多处 `#if DEBUG` 本地绝对路径**(AppData.cs:243-247、App.xaml.cs:243-246):Logo/语言文件指向 `C:\PersonalSpace\work\...`,仅开发机有效。
+5. ⚠ **`urlIp=127.0.0.1`/`mqttIp=192.168.0.108`**(App.config,合并改值):需现场确认实际网关 IP(注释已标 `[M7] 现场核对`)。
+6. ⚠ **control 托管线程静默失败**:登录/StartRun/HAL 失败全部只记日志不退进程(MainWindow.xaml.cs:84/91/118/123/130)——control 后台没起来时 operate 前台仍可用,易被忽略(无显式告警)。
+
+---
+
+## 十一、给"双进程拆分"的关键结论(operate 侧)
+
+1. **operate 进程内托管 control 是拆分的拆除对象**:§四 8 个直连点要全改成跨进程(启动 control.exe + 等就绪 + IPC 借串口 + HTTP 读状态 + 心跳上报)。
+2. **退出语义缺失同样在 operate 侧**:`App_Exit` 空体、去掉了 control 的 `Environment.Exit`——拆分后 operate 关闭只关 UI,control 进程续命;需补对称停机入口(参 control 全景 §九.5)。
+3. **降级要补的是 control 端 builder**:§8.1 三处 EEPROM 桩,补 control Commander 三个 builder(`CreateWriteEEPROOpenVentTimeCommand` / 读排气阀 / `CreateWriteEEPROMLightNum`)+ 真机核对字节即可接通;存图需真机核对 SaveBmpPic 与 MVCAPI.SavePic 落盘一致性。
+4. **最易被误判"正常"的两处**:排气阀时间**读**(静默保留旧值,无提示)、调试页**存图**(透明换实现,无提示)——上线前必须真机确认。
+5. **写 EEPROM "成功=true" 全部待真机**(§8.2):成功语义不可靠,补字节核对前不能据 UI 成功提示判定真已写入。
+6. 纯 HTTP/展示模块(首页/详情/照片/曲线/皿管理/对焦设置)合并**无功能差异**,可信。

+ 3 - 3
项目文档/项目背景与上手指南.md

@@ -18,7 +18,7 @@
 | **front** | `aivfo-front-manament-2.0/` | **管理端/医生工作站**:患者管理、胚胎打分、出报告 | 医生办公电脑 |
 | Java 微服务 | `aivfo-gateway/`、`aivof-tl-control/`、`aivfo-business-manage/`、`aivfo-data-transmission/`、`aivfo-oplog/` 等 | 网关/业务/数据传输/操作日志 | 服务器(本机开发时跑在 108 + 本机) |
 
-> 历史:operate / control / front 原本是三个独立项目,做过一次"三项目合并"(M0–M8,**已结项**)。合并把 control 的代码塞进了 operate 进程内托管运行。**那个旧任务已全部完成,旧文档已清空**
+> 历史:operate / control / front 原本是三个独立项目,做过一次"三项目合并"(M0–M8)。合并把 control 的代码塞进了 operate 进程内托管运行。**合并代码完成,但真机验收整体未做、且有 operate 侧功能降级遗留**(见 `进度/待验证清单.md` M-01~M-07 与 `需求文档/操作端逻辑与配置全景.md` §八);旧文档在重建时清空归档
 
 ---
 
@@ -76,11 +76,11 @@
 
 ## 六、已知坑(务必知道,踩过的)
 
-1. **operate 有两个 build 行为不同**:Debug 版 `AppData.cs` 里有 `#if DEBUG` 把服务器地址覆盖成开发机 `192.168.0.207` 外网——**真机/本机验证必须用 Release 版**(走 App.config 的 127.0.0.1/108)。
+1. **operate 有两个 build 行为不同**:Debug 版 `AppData.cs:91-111` 有 `#if DEBUG` 块**顺序覆盖**服务器地址,**最终生效写死成测试网关 `test-gateway.aivfo.com:36000` / `211.149.139.131`**(中途出现的 192.168.0.207 等会被后续行覆盖,既非最终值也不是公网)——**真机/本机验证必须用 Release 版**(走 App.config 的 127.0.0.1/108)。
 2. **operate / control 是管理员进程**:非交互 shell 里 RunAs 提权可能弹不出(杀不掉/起不来高权限进程)。开关软件相关操作见 CLAUDE.md 第九节。
 3. **control 启动依赖一堆运行时文件**:`DependFile`(SQLite 库/相机原生 DLL)、App.config(连接+换气参数)。拆独立进程部署时这些要随 control.exe 到位,否则 StartRun 失败。清单见 control 全景文档 §八。
 4. **两套并行的串口/相机栈**(旧 `ivf_tl_Entity/ComEntitys` + HAL 包装栈 `IvfTl.Hardware`),是迁移期现状,排障要分清当前走哪条。详见全景文档 §六。
-5. **本地 SQLite 不自动建表**:`aivfoTL.db` 须预置文件
+5. **本地 SQLite 多数表须预置**:`aivfoTL.db` 须预置文件;但 `house_autofocus_calibration` 一表例外,由 `DBService` 启动时 `CodeFirst.InitTables` 自动建(`CREATE TABLE IF NOT EXISTS`),勿当缺表 bug 排查
 6. **老壳 `ivf_tl_ControlTest`**:是合并前 control 的独立 exe,但混了测试代码、命名乱(三命名空间)、已被 operate 旁置不引用。**别改造它**;新 control 启动器基于干净的 `ivf_tl_Control` 类库新建(阶段1),老壳阶段3 退役删。
 
 ---