CalibrationStore.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using IvfTl.AutoFocus.Calib;
  5. namespace IvfTl.AutoFocus.Storage
  6. {
  7. /// <summary>
  8. /// 标定结果存储入口(M2-04)。
  9. /// 依据:项目文档/开发计划/2026-06-17-M2-本地自动对焦子计划.md (M2-04)、需求文档/12 §2.7、03 §4。
  10. ///
  11. /// 职责:
  12. /// 1) 真相源——把标定结果写本地 calibration.json(复用 CalibrationFile.Save;
  13. /// 读回经 CalibrationFile.Load 已带 IncludeFields=true,public 字段不会静默丢失,03 §4)。
  14. /// 2) 库内镜像——把单条 WellCalib 解包后通过注入的回调写入 house_autofocus_calibration
  15. /// (scene 0 基准 upsert / 1 日常 append)。本程序集不引用 SqlSugar/Entity,故库写以
  16. /// 委托回调形式由 control 端(AppData→DBService.SaveAutofocusCalibration)实现,避免反向依赖。
  17. ///
  18. /// scene:0=出厂基准(调试页一键标定,M2-05) 1=日常对焦(放皿/门/定时触发,M2-03 走此路径)。
  19. /// 任一步骤异常都被本类吞掉(经 Log 回调记日志),绝不向上抛,保证不崩对焦/采集线程。
  20. /// </summary>
  21. public sealed class CalibrationStore
  22. {
  23. /// <summary>
  24. /// 库镜像回调:参数依次为
  25. /// (tlSn, houseSn, wellSn, scene, focusZ, exposure, horizontalPulse, peakRatio,
  26. /// circleFound, centerOffsetPct, note, source)。
  27. /// 由 control 端绑定到 DBService.SaveAutofocusCalibration;为空则只写 JSON、不镜像库。
  28. /// </summary>
  29. public Action<string, int, int, int, int, int, int, decimal, bool, decimal, string, string> DbMirror { get; set; }
  30. /// <summary>
  31. /// M2-06 安全门降级用:读 scene=0 出厂基准的最清晰层 Z 脉冲(FocusZ)回调。
  32. /// 参数:(tlSn, houseSn, wellSn);返回该 well 的基准 FocusZ,无基准返回 null。
  33. /// 由 control 端绑定到 DBService.GetBaselineFocusZ(查 house_autofocus_calibration scene=0);
  34. /// 为空或返回 null 时,安全门关闭路径无基准可用 → 本轮跳过该 well(不污染拍摄,记日志)。
  35. /// 本程序集不引用 SqlSugar/Entity,故以委托回调形式由 control 端实现,避免反向依赖(同 DbMirror)。
  36. /// </summary>
  37. public Func<string, int, int, int?> BaselineReader { get; set; }
  38. /// <summary>日志回调(可空)。</summary>
  39. public Action<string> Log { get; set; }
  40. /// <summary>calibration.json 路径(真相源)。由部署配置注入,不写死测试外壳常量路径。</summary>
  41. public string JsonPath { get; set; }
  42. /// <summary>结果来源标记,写入库镜像 source 列。</summary>
  43. public string Source { get; set; } = "LOCAL_JSON";
  44. /// <summary>
  45. /// 保存单个 well 的标定结果:先写 JSON 真相源(按 tlSn/houseSn/wellSn 合并到既有档案),
  46. /// 再镜像库(scene 0 upsert / 1 append)。
  47. /// </summary>
  48. public void SaveCalibration(WellCalib wc, string tlSn, int houseSn, int wellSn, int scene,
  49. string port = null, int ccdIndex = 0, string ccdSn = null)
  50. {
  51. if (wc == null) return;
  52. // ① 写 JSON 真相源(容错:JSON 失败不影响库镜像,反之亦然)。
  53. try
  54. {
  55. WriteJson(wc, tlSn, houseSn, wellSn, port, ccdIndex, ccdSn);
  56. }
  57. catch (Exception ex)
  58. {
  59. Log?.Invoke($"[标定存储]写 calibration.json 失败:{ex.Message}");
  60. }
  61. // ② 镜像库(通过注入回调,由 control 端落 SqlSugar)。
  62. try
  63. {
  64. DbMirror?.Invoke(tlSn, houseSn, wellSn, scene, wc.FocusZ, wc.Exposure, wc.HorizontalPulse,
  65. (decimal)wc.PeakRatio, wc.CircleFound, (decimal)wc.CenterOffsetPct, wc.Note ?? "", Source);
  66. }
  67. catch (Exception ex)
  68. {
  69. Log?.Invoke($"[标定存储]镜像 house_autofocus_calibration 失败:{ex.Message}");
  70. }
  71. }
  72. /// <summary>
  73. /// 把 WellCalib 合并写入 calibration.json:读出既有档案(无则新建),按 house/well 覆盖该 well 条目后回写。
  74. /// </summary>
  75. private void WriteJson(WellCalib wc, string tlSn, int houseSn, int wellSn, string port, int ccdIndex, string ccdSn)
  76. {
  77. if (string.IsNullOrEmpty(JsonPath)) return;
  78. CalibrationFile file = null;
  79. if (File.Exists(JsonPath))
  80. {
  81. try { file = CalibrationFile.Load(JsonPath); }
  82. catch (Exception ex) { Log?.Invoke($"[标定存储]读旧 calibration.json 失败,将重建:{ex.Message}"); }
  83. }
  84. if (file == null) file = new CalibrationFile();
  85. if (string.IsNullOrEmpty(file.TlSn)) file.TlSn = tlSn ?? "";
  86. file.Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  87. var house = file.Houses.Find(h => h.House == houseSn);
  88. if (house == null)
  89. {
  90. house = new HouseCalib { House = houseSn, Port = port ?? "", CcdIndex = ccdIndex, CcdSn = ccdSn ?? "" };
  91. file.Houses.Add(house);
  92. }
  93. else
  94. {
  95. if (port != null) house.Port = port;
  96. if (ccdSn != null) house.CcdSn = ccdSn;
  97. if (ccdIndex != 0) house.CcdIndex = ccdIndex;
  98. }
  99. // wc.Well 可能未被调用方填好,这里以入参 wellSn 为准对齐 well 序号。
  100. wc.Well = wellSn;
  101. int idx = house.Wells.FindIndex(w => w.Well == wellSn);
  102. if (idx >= 0) house.Wells[idx] = wc;
  103. else house.Wells.Add(wc);
  104. file.Save(JsonPath);
  105. }
  106. }
  107. }