Explorar o código

docs(d2-02-t3): 自动对焦重构设计 spec——调试页全自动标定+control升级版自动对焦+per-well范围参数(沿用服务器DB为权威+本地缓存,最小新增列)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie hai 2 días
pai
achega
8036bcbd06

+ 222 - 0
项目文档/需求文档/specs/2026-06-24-D2-02-第三阶段-自动对焦重构-design.md

@@ -0,0 +1,222 @@
+# D2-02 第三阶段 · 自动对焦重构(调试页全自动标定 + control 升级版自动对焦)设计
+
+> 状态:设计已与用户逐节确认,待用户审阅本文档。
+> 上游:本设计取代 `2026-06-24-D2-02-第三阶段-operate调试页接入-design.md` 中"调试页逐命令接入"的窄方案——经业务澄清,第三阶段实为一次跨 operate/control/Java/DB 四层的自动对焦重构。原"VM 逐方法走 command"半成品已 stash 弃用。
+> 防丢纪要(不入 git):`临时文件/自动对焦重构-讨论纪要与需求锚点.md`(含全部源码依据 file:line)。
+
+---
+
+## 一、为什么做(背景与目标)
+
+### 1.1 业务背景
+- 培养箱每舱 16 个 well,采集时要给每个 well 自动拍延时照片。**拍清楚**需要两个前提:相机对准孔中心(水平位)、镜头调到正确对焦高度(垂直 FocusZ)。因机械装配公差,每台机器、每个孔都不同,必须实测标定。
+- **旧方式(要废)**:工程师手动一个个调舱室(手动移电机、看画面、记清晰位),采集时围绕记下的清晰层上下拍 N 层,拍完送 Java 端图像清晰度评分排序选最清晰层当 0 层(赤道面)。人工工作量大、难度高。
+- **新方式(本次做)**:引入全自动对焦取代人工。进调试页 16 孔全自动重标一遍;哪个孔不准就在自动结果基础上微调。采集时由四步算法直接找最清晰焦面,不再需要 Java 端事后评分选层。
+
+### 1.2 目标(两块一起做)
+- **A 调试页重构**:进页面 = 16 孔全自动对焦(仿 `临时文件/AutoFocusTool`),4×4 风格总览(竖屏 3 列×6 行);哪孔不准就地微调;保留手动电机/光源作校准兜底。
+- **B control 升级版自动对焦**:把四步智能算法(CalibrationEngine)正式启用到采集对焦,取代"静态清晰层 Z + 上下拍"老方式。
+- **C 每 well 运动范围**:每个 well 调出"水平参考位±X、垂直区间"存配置;采集时在范围内对焦——既提速又防撞机(电机不被撞坏)。
+
+### 1.3 关键洞察(决定方案为何可行且不大)
+1. **引擎已在 control**:`CalibrationEngine` 本就在 `control/IvfTl.AutoFocus/`,且采集主循环 `StartAutoFocus` path B 已接入它,只被安全门 `local_autofocus_enabled`(默认0)锁着。"升级"≈"开启+验证+让它读 per-well 范围",不是从头搬。
+2. **触发链不动**:对焦触发(放皿/每日定时/关舱门/MQTT手动)现成,升级只换"触发后怎么算焦面",主循环与状态机零改。
+3. **范围是 74000 伪峰的治本药**:per-well 收窄垂直搜索窗后,远处伪峰被范围直接排除。
+4. **存储沿用现成链**:well 配置历史就是"服务器 MySQL 为权威 + 本地 SQLite 缓存",范围参数照搬。
+5. **旧 Java 评分链已废**(M3-01 删干净),本次只是不恢复,无新删除。
+
+---
+
+## 二、范围(做什么 / 不做什么)
+
+### 2.1 做
+- operate:重构调试页(全自动标定总览 + 逐孔微调 + 保留手动电机/光源),新增 `CalibrationClient`。
+- control:新增标定协作端点 + `CalibrationCoordinator`;改造 `CalibrationEngine` 从配置注入 per-well 范围;改造采集 `StartAutoFocus` path B 读范围对焦;启用并验证安全门。
+- Java:`house_well_setting`/`tl_setting` 加范围字段读写;`well/update` 补 MQTT 通知。
+- DB:最小新增列(见 §五)。
+
+### 2.2 不做(YAGNI / 边界)
+- 不动对焦触发时机(放皿/定时/门/手动)与主循环状态机。
+- 不删 path A 静态降级路径(保留作 Java 不可达/未标定兜底)。
+- 不动 `getTlDataTransmissionSetting`(喂抠图的 SELECT)、不动 `calCCDPosition/calAutofocusStart`(在用的拍照层换算)。
+- 对焦结果不回写 EEPROM(清晰位历来只读参考);EEPROM 手写只保留老代码 4 个(进/排气阀时间、扫描间隔、well水平位)。
+- 缓冲瓶舱11 无相机无标定,仅保留手动功能。
+
+---
+
+## 三、关键决策登记(逐条用户确认)
+
+| # | 决策 |
+|---|---|
+| D1 | 三个真机门(74000伪峰 / 真胚胎峰比阈值 / EEPROM回写)这次一起验+修,真正启用升级版自动对焦 |
+| D2 | 调试页进去就 16 孔全自动重标(不选孔);哪孔不准就地微调;UI 竖屏 3列×6行(细节后定) |
+| D3 | 标定引擎统一在 control 执行,operate 发高层指令 + 收画面/进度(架构方案 A) |
+| D4 | 旧"拍N层→Java评分选赤道面"已废,新算法对焦时直接定最清晰层,不恢复 |
+| D5 | 范围粒度:水平半幅X、垂直区间 = per-well;曝光范围、扫描步进/层数 = 设备级 |
+| D6 | 范围参数存储沿用"服务器DB为权威 + 本地SQLite缓存"现有链,需新增 DB 字段 |
+| D7 | 手动功能(水平/垂直电机、光源)保留 |
+| D8 | 对焦触发时机沿用原有(放皿/每日定时/关舱门/MQTT手动),不动触发,只换算焦面 |
+| D9 | EEPROM 回写保留老代码 4 个手写;对焦清晰位不写 EEPROM |
+| D10 | 区间用"中心±半幅"存(复用现有参考位列),不用绝对上下限 |
+
+---
+
+## 四、架构与模块边界
+
+### 4.1 总览
+```
+┌─ operate 调试页(重构) ─────┐         ┌─ control ────────────────────────────┐
+│ 新版 ViewModel/View        │  HTTP   │ ControlHttpServer                      │
+│ · 16孔总览(3×6)            │ ──指令─▶│  + /debug/calibrate/* 端点(新)         │
+│ · 逐孔微调                 │ ◀─进度─ │  CalibrationCoordinator(新)            │
+│ · 手动电机/光源(走command) │ ◀─画面─ │   └ CalibrationEngine(已有,改造:注入范围)│
+│ · CalibrationClient(新)    │  MJPEG  │  DebugSessionManager(已有,借硬件)      │
+└──────────┬─────────────────┘         │  HouseBin.StartAutoFocus path B(改造)  │
+           │保存范围参数 HTTP            │   └ 同一个 CalibrationEngine(采集入口)  │
+           ▼                            └───────────┬───────────────────────────┘
+      Java aivof-tl-control                         │读范围参数
+      · well/update +范围字段 +补MQTT通知            ▼
+           │                            ┌───────────────────────────┐
+           ▼                            │ 服务器 MySQL aivfo_tl_setting │
+      服务器 MySQL ◀──同步(MQTT ping)── │ house_well_setting(+2范围列)  │
+           ▲                            │ tl_setting(+设备级范围列)     │
+           └──control启动/重拉─本地SQLite缓存────────────────────────┘
+```
+
+### 4.2 模块边界(各一件事)
+- **CalibrationEngine(control,已有,改造)**:纯四步算法。改造 = 范围参数从硬编码默认值改为构造后由调用方注入(per-well)。两个调用方(调试页 Coordinator + 采集 path B)**共用同一个"读范围→注入引擎"的公用方法**,杜绝改一处忘另一处(影响面隐性漏点 HouseBin.cs:1574)。
+- **CalibrationCoordinator(control,新)**:管一次标定任务。借硬件(复用 DebugSessionManager lease)→ 逐孔 `CalibrateWell`(注入该孔范围)→ 收集结果 → 暴露 progress;支持中止/重标单孔/微调单孔。
+- **CalibrationClient(operate,新)**:封装标定端点 + 轮询进度;画面用已有 MjpegStreamClient。
+- **采集 path B(HouseBin.StartAutoFocus,改造)**:触发链/主循环/GetClearest 外壳/返回契约全不动,只把"算 FocusZ"段改成"读 DB 范围 → 注入引擎 → 算",仍填 `_autoFocusPhoto`。
+
+---
+
+## 五、数据层设计(基于现有 aivfo_tl_setting 结构,最小新增)
+
+### 5.1 复用现有列(不动)
+| 用途 | 现有列 | 表 |
+|---|---|---|
+| 水平参考位(区间中心) | `horizontal_motor_position` | house_well_setting |
+| 垂直参考点(区间中心) | `eeprom_clear_position` | house_well_setting |
+| 垂直全局硬上限(撞机保护) | `vertical_motor_pulse_max` | tl_setting |
+| 拍 N 层:层间距/层数/下移 | `focus_layer_spacing_pulse`/`focus_layer_count`(well级覆盖)、`focus_layer_down`、`move_down_layer` | tl_setting + house_well_setting |
+| 峰比阈值 / 安全门 | `focus_peak_ratio_threshold` / `local_autofocus_enabled` | tl_setting |
+| 标定结果镜像 | `house_autofocus_calibration`(focus_z/exposure/horizontal_pulse/peak_ratio/circle_found/center_offset_pct/scene) | 整表已有 |
+
+### 5.2 新增列(最小集,决策 D5/D10)
+**`house_well_setting`(per-well,可空=继承设备级默认):**
+- `horizontal_focus_range` int DEFAULT NULL —— 水平搜索半幅 X(围绕 horizontal_motor_position;空=继承 tl_setting.focus_h_range_default)
+- `vertical_focus_range` int DEFAULT NULL —— 垂直搜索半幅(围绕 eeprom_clear_position;空=继承 tl_setting.focus_v_range_default)
+
+**`tl_setting`(设备级默认/统一):**
+- `focus_exposure_min` int —— 对焦曝光二分下限(默认沿用引擎现值 10)
+- `focus_exposure_max` int —— 对焦曝光二分上限(默认 800)
+- `focus_h_range_default` int —— 水平半幅设备级默认(well 级留空时用)
+- `focus_v_range_default` int —— 垂直半幅设备级默认
+- 对焦扫描步距(粗/精):实现期评估——若现有 `focus_layer_spacing_pulse` 可复用则不加;确需独立的对焦扫描步距再加 `focus_coarse_step`/`focus_fine_step`(spec 实现阶段定,倾向不加、复用)
+
+> 区间表达:水平 = `horizontal_motor_position ± horizontal_focus_range`;垂直 = `eeprom_clear_position ± vertical_focus_range`,并与 `vertical_motor_pulse_max` 取交集做硬钳位。
+
+### 5.3 加列同步清单(漏一处静默丢数据 —— 实现期 checklist)
+- **Java**:`HouseWellSetting` DAO + `house_well_setting` Mapper 的 `saveOrUpdateData` INSERT 列清单(硬编码,必须加)+ `updateHouseWellSettings` 支持新字段;`TlSetting` DAO + Mapper(确认 saveOrUpdateData 是否硬编码列)。
+- **C#**:`HouseWellSettingDB` / `TLSettingDB` 加字段;`ConvertHelper`——control 端 3 段(ConvertToTLSetting×2 + ConvertToTLSettingDB;及 HouseWellSetting 对应转换)、operate 端 1 段。
+- **禁区**:不把新列加进 `getTlDataTransmissionSetting` 的 SELECT(喂抠图/数据传输)。
+
+---
+
+## 六、标定协作协议(方案 A,control 新增端点)
+
+挂现有 `ControlHttpServer`,复用 DebugSession 借用/心跳/release 机制。
+
+| 端点 | 方法 | 作用 |
+|---|---|---|
+| `/debug/calibrate/start` | POST | 开始标定(sid + well列表,默认16孔)。control 起后台任务逐孔跑引擎 |
+| `/debug/calibrate/progress` | GET | 拉进度(逐孔:待标/标定中/合格绿/伪峰橙 + FocusZ/曝光/峰比/居中偏移 + 当前步骤)|
+| `/debug/calibrate/recalibrate` | POST | 重标单孔(sid + wellSn)|
+| `/debug/calibrate/adjust` | POST | 单孔微调(sid + wellSn + 焦面±delta / 改半幅 / 改曝光)|
+| `/debug/calibrate/stop` | POST | 中止标定 |
+| `/debug/preview/stream` | (已有)| 标定实时画面:引擎 OnFrame 帧推此 MJPEG 流 |
+
+- **进度回传**:operate 轮询 `progress`(标定秒级逐孔,无需 SSE)。引擎 OnStep(第几孔第几步)并入 progress。
+- **产出语义**:标定结果(FocusZ/曝光/峰比)仅用于调试页显示 + 验证范围调得对不对;工程师满意后**作为配置落库的是范围参数**(走现有 well/update),不是 FocusZ(决策 D:调试页只存范围)。
+  - 澄清(与 §5.1 不矛盾):`house_autofocus_calibration` 表存 focus_z,是 control 采集对焦时写的 scene=1 运行期镜像/scene=0 基准,属"标定结果记录",非"调试页存的配置"。调试页本次不再把 FocusZ 当配置主动写入采集链;范围参数(半幅)才是调试页的配置产物。
+- **借用生命周期**:标定全程持 DebugSession lease(control 采集该舱暂停);标定结束/中止 release,采集恢复。复用现成机制,勿破坏。
+
+---
+
+## 七、control 采集对焦改造(升级版自动对焦)
+
+### 7.1 改动边界(极小)
+触发链、主循环、`GetClearest` 外壳、`StartAutoFocus` 返回契约(填 `_autoFocusPhoto` ≥1条→true)全不动。只改 path B(HouseBin.cs:1552-1623)的"算 FocusZ"段:
+```
+现状: new CalibrationEngine(lease.Serial, lease.Camera){ Log=... }   // 范围吃硬编码默认
+改造: var range = ReadWellFocusRange(wellSn)  // 公用方法:well级覆盖→设备级默认→就近优先
+      new CalibrationEngine(lease.Serial, lease.Camera){
+          Log=..., HFineRange=range.H, ZCoarseCenter=参考点, ZCoarseHalf=range.V,
+          ExpLo=range.ExpLo, ExpHi=range.ExpHi, ... }                // 注入 per-well 范围
+      → CalibrateWell(well, 参考位, 参考点) → 填 _autoFocusPhoto(verticalMotorPosition=FocusZ)
+```
+
+### 7.2 三个真机门落点(决策 D1:一起验+修)
+- **74000 伪峰**:per-well 垂直区间收窄粗对焦窗口,远处伪峰被范围排除(治本);真机开 DebugSave 存图验证。
+- **真胚胎峰比阈值**:用 `tl_setting.focus_peak_ratio_threshold`(已有可调列),真机活体胚胎上确认阈值。
+- **EEPROM 回写**:决策 D9——对焦不写;只验调试页 4 个手动写命令真机生效。
+
+### 7.3 安全门
+`local_autofocus_enabled`:验证通过后置 1 启用;path A 静态降级保留作兜底。
+
+### 7.4 契约约束(保下游无感)
+- `_autoFocusPhoto` 每孔一条 `{wellSn, verticalMotorPosition=FocusZ}`,下游 AllEmbryoAutofocus/SingleEmbryoAutofocus/PhotoLayerCalculator/拍照无感。
+- FocusZ 必须与 `vertical_motor_pulse_max` 同脉冲基准(HouseBin.cs:1995 有校验,量纲错会误删孔)。
+- path B 逐孔循环保留 `IsStopClearest || FirstClearest` 中断检查(开门急停/重触发抢占)。
+
+---
+
+## 八、operate 调试页重构(UI + 交互)
+
+- 整体重构 `HouseDebugPageView` + `HouseDebugPageViewModel`(唯一导航入口 SettingPageView.xaml.cs:283,不波及别页面;BufferDebugViewModel 独立不共享基类)。
+- **主区 16 孔总览**:竖屏 3 列×6 行,每格 = 实时画面 + 状态(待标/标定中黄框/合格绿/伪峰橙)+ 关键值(FocusZ/峰比)。
+- **进页面自动开标**:选舱 → acquire 借硬件 → `/debug/calibrate/start` 全 16 孔,画面/进度填总览。
+- **逐孔微调**:点某孔 → 重标本孔 / 焦面±调 / 改本孔水平半幅·垂直区间 / 手动电机·光源。UI 形态(孔内按钮 vs 侧栏)实现期定,接口已就绪。
+- **手动功能保留**(D7):水平/垂直电机正反转·复位·到位、光源、曝光 → 走现有 `/debug/command`(control 已支持)。
+- **保存范围参数**:每孔水平/垂直半幅 → `house_well_setting`(现有 well/update 链)。
+- **EEPROM 手写 4 个保留**(D9):进/排气阀时间、扫描间隔、well水平位 → command。
+- **复用资产**:MjpegStreamClient(预览)、DebugSessionClient(借用/心跳/release,有单测,勿破坏生命周期)。旧 `OneClickCalibrate` 整体替换为 CalibrationClient 协作。
+
+---
+
+## 九、Java 侧改动(最小化)
+
+1. `house_well_setting` 加 2 范围列读写:DAO 字段 + Mapper saveOrUpdateData INSERT 列同步 + `updateHouseWellSettings` 支持新字段。
+2. `tl_setting` 加设备级范围列:DAO + Mapper 同步。
+3. **补 MQTT 通知**(关键缺口):`well/update` 保存范围后补 `sendUpdateSettingEvent(tlSn)`,control 即时重拉生效(否则改完延迟到下次自发重拉)。
+4. 不碰 `getTlDataTransmissionSetting`;`calCCDPosition/calAutofocusStart` 在用不动;旧评分链已废不恢复。
+
+---
+
+## 十、影响面控制 + 测试策略
+
+### 10.1 影响面三道防线
+1. **加列同步清单**(§5.3,漏一处静默丢数据)写进实现 checklist。
+2. **契约不变保下游无感**:`_autoFocusPhoto` 契约 + FocusZ 同脉冲基准。
+3. **禁区标注**:`getTlDataTransmissionSetting` SELECT 不动;DebugSessionClient 借用生命周期不破坏;MjpegStreamClient 防残帧语义保留。
+
+### 10.2 测试策略(分层)
+- **纯逻辑单测(子代理驱动 TDD)**:范围解析(well级覆盖→设备级默认→就近优先,仿 PhotoLayerCalculator.Resolve)、CalibrationCoordinator 任务状态机、CalibrationClient 协议拼装/进度解析、范围→引擎注入映射。
+- **编译验证**:control + operate Release 双编译 0 错;Java 编译。
+- **真机验证(决策 D1,Claude 自主跑,UAC 静默提权;电机守安全区间 水平[0,220000]/垂直[0,125000])**:
+  1. 调试页 16 孔标定出图 + 逐孔微调 + 范围调整。
+  2. 范围存库 → control 重拉(验 MQTT 通知生效)→ 采集用新范围对焦。
+  3. 三个真机门:74000伪峰(开 DebugSave 验范围排除)/ 真胚胎峰比阈值 / EEPROM 4 个手写真机生效。
+  4. 电机安全区间不撞机(范围钳位生效)。
+  5. 升级版自动对焦端到端:放皿/定时触发 → 范围内对焦 → 上下拍 N 层;焦面复用到下次触发。
+  6. use-after-free 压测(标定中反复 release/超时回收同舱,沿用第二阶段 I-1 关注点)。
+
+---
+
+## 十一、自检(写完对照需求)
+- §1.2 三目标 A/B/C → §四/七/八(调试页)、§七(采集升级)、§五(范围存储)全覆盖。✓
+- 决策 D1-D10 → §三登记,分别落 §七(D1/D8)、§八(D2/D7/D9)、§四(D3)、§九(D4)、§五(D5/D6/D10)。✓
+- 用户两点硬嘱咐:存储参考以前方式 → §五基于实读表结构 + §1.3洞察4;影响面 → §十三防线 + §5.3同步清单。✓
+- 无 TBD(§5.2 对焦扫描步距标"实现期评估倾向复用",是明确的实现指令非空缺)。✓
+- 不做边界 §2.2 明确(触发不动/path A不删/抠图SELECT不碰/对焦不写EEPROM/缓冲瓶无标定)。✓