| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using IvfTl.AutoFocus.Calib;
- namespace IvfTl.AutoFocus.Storage
- {
- /// <summary>
- /// 标定结果存储入口(M2-04)。
- /// 依据:项目文档/开发计划/2026-06-17-M2-本地自动对焦子计划.md (M2-04)、需求文档/12 §2.7、03 §4。
- ///
- /// 职责:
- /// 1) 真相源——把标定结果写本地 calibration.json(复用 CalibrationFile.Save;
- /// 读回经 CalibrationFile.Load 已带 IncludeFields=true,public 字段不会静默丢失,03 §4)。
- /// 2) 库内镜像——把单条 WellCalib 解包后通过注入的回调写入 house_autofocus_calibration
- /// (scene 0 基准 upsert / 1 日常 append)。本程序集不引用 SqlSugar/Entity,故库写以
- /// 委托回调形式由 control 端(AppData→DBService.SaveAutofocusCalibration)实现,避免反向依赖。
- ///
- /// scene:0=出厂基准(调试页一键标定,M2-05) 1=日常对焦(放皿/门/定时触发,M2-03 走此路径)。
- /// 任一步骤异常都被本类吞掉(经 Log 回调记日志),绝不向上抛,保证不崩对焦/采集线程。
- /// </summary>
- public sealed class CalibrationStore
- {
- /// <summary>
- /// 库镜像回调:参数依次为
- /// (tlSn, houseSn, wellSn, scene, focusZ, exposure, horizontalPulse, peakRatio,
- /// circleFound, centerOffsetPct, note, source)。
- /// 由 control 端绑定到 DBService.SaveAutofocusCalibration;为空则只写 JSON、不镜像库。
- /// </summary>
- public Action<string, int, int, int, int, int, int, decimal, bool, decimal, string, string> DbMirror { get; set; }
- /// <summary>
- /// M2-06 安全门降级用:读 scene=0 出厂基准的最清晰层 Z 脉冲(FocusZ)回调。
- /// 参数:(tlSn, houseSn, wellSn);返回该 well 的基准 FocusZ,无基准返回 null。
- /// 由 control 端绑定到 DBService.GetBaselineFocusZ(查 house_autofocus_calibration scene=0);
- /// 为空或返回 null 时,安全门关闭路径无基准可用 → 本轮跳过该 well(不污染拍摄,记日志)。
- /// 本程序集不引用 SqlSugar/Entity,故以委托回调形式由 control 端实现,避免反向依赖(同 DbMirror)。
- /// </summary>
- public Func<string, int, int, int?> BaselineReader { get; set; }
- /// <summary>日志回调(可空)。</summary>
- public Action<string> Log { get; set; }
- /// <summary>calibration.json 路径(真相源)。由部署配置注入,不写死测试外壳常量路径。</summary>
- public string JsonPath { get; set; }
- /// <summary>结果来源标记,写入库镜像 source 列。</summary>
- public string Source { get; set; } = "LOCAL_JSON";
- /// <summary>
- /// 保存单个 well 的标定结果:先写 JSON 真相源(按 tlSn/houseSn/wellSn 合并到既有档案),
- /// 再镜像库(scene 0 upsert / 1 append)。
- /// </summary>
- public void SaveCalibration(WellCalib wc, string tlSn, int houseSn, int wellSn, int scene,
- string port = null, int ccdIndex = 0, string ccdSn = null)
- {
- if (wc == null) return;
- // ① 写 JSON 真相源(容错:JSON 失败不影响库镜像,反之亦然)。
- try
- {
- WriteJson(wc, tlSn, houseSn, wellSn, port, ccdIndex, ccdSn);
- }
- catch (Exception ex)
- {
- Log?.Invoke($"[标定存储]写 calibration.json 失败:{ex.Message}");
- }
- // ② 镜像库(通过注入回调,由 control 端落 SqlSugar)。
- try
- {
- DbMirror?.Invoke(tlSn, houseSn, wellSn, scene, wc.FocusZ, wc.Exposure, wc.HorizontalPulse,
- (decimal)wc.PeakRatio, wc.CircleFound, (decimal)wc.CenterOffsetPct, wc.Note ?? "", Source);
- }
- catch (Exception ex)
- {
- Log?.Invoke($"[标定存储]镜像 house_autofocus_calibration 失败:{ex.Message}");
- }
- }
- /// <summary>
- /// 把 WellCalib 合并写入 calibration.json:读出既有档案(无则新建),按 house/well 覆盖该 well 条目后回写。
- /// </summary>
- private void WriteJson(WellCalib wc, string tlSn, int houseSn, int wellSn, string port, int ccdIndex, string ccdSn)
- {
- if (string.IsNullOrEmpty(JsonPath)) return;
- CalibrationFile file = null;
- if (File.Exists(JsonPath))
- {
- try { file = CalibrationFile.Load(JsonPath); }
- catch (Exception ex) { Log?.Invoke($"[标定存储]读旧 calibration.json 失败,将重建:{ex.Message}"); }
- }
- if (file == null) file = new CalibrationFile();
- if (string.IsNullOrEmpty(file.TlSn)) file.TlSn = tlSn ?? "";
- file.Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
- var house = file.Houses.Find(h => h.House == houseSn);
- if (house == null)
- {
- house = new HouseCalib { House = houseSn, Port = port ?? "", CcdIndex = ccdIndex, CcdSn = ccdSn ?? "" };
- file.Houses.Add(house);
- }
- else
- {
- if (port != null) house.Port = port;
- if (ccdSn != null) house.CcdSn = ccdSn;
- if (ccdIndex != 0) house.CcdIndex = ccdIndex;
- }
- // wc.Well 可能未被调用方填好,这里以入参 wellSn 为准对齐 well 序号。
- wc.Well = wellSn;
- int idx = house.Wells.FindIndex(w => w.Well == wellSn);
- if (idx >= 0) house.Wells[idx] = wc;
- else house.Wells.Add(wc);
- file.Save(JsonPath);
- }
- }
- }
|