DebugCalibrationAdapter.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. using System;
  2. using ivf_tl_Entity.CameraEntitys;
  3. using ivf_tl_Entity.ComEntitys;
  4. using IvfTl.Hardware;
  5. using CommunityToolkit.Mvvm.ComponentModel;
  6. namespace ivf_tl_Operate.ViewModel
  7. {
  8. /// <summary>
  9. /// M2-05 调试页标定适配器:把 operate 调试栈已打开的 ComBin/Camera 适配成
  10. /// HAL 接口 ISerialChannel/ICamera,供 IvfTl.AutoFocus.CalibrationEngine 复用(不重复实现算法)。
  11. ///
  12. /// 【为何用适配器而非 Acquire(OperateDebug) 取 lease.Serial/lease.Camera】
  13. /// 调试页(M1-03)刻意仍用 operate 侧具体类型 ComBin/Camera(方法更全:WriteEEPROM/正反向/RawToRgb),
  14. /// 并已通过 comBin.OpenPort()/camera.Init() 独占持有了本舱 COM 口与相机句柄。
  15. /// 若再向 HAL 申请前台借用并用 lease.Serial(HAL SerialChannelImpl)/lease.Camera(HAL CameraImpl),
  16. /// 会在同一已被调试栈占用的 COM 口/相机上二次 Open/Init → 句柄冲突。
  17. /// 故标定引擎直接驱动调试栈【已打开】的 ComBin/Camera:本适配器只暴露 CalibrationEngine 实际调用的少量方法,
  18. /// 其余接口成员抛 NotSupportedException(引擎从不调用)。互斥仍由调试页持有的 HAL 前台借用(OperateDebug)
  19. /// 保证(打开调试页时 ComHouseInit 已 Acquire(OperateDebug),HAL 暂停 control 对本舱采集)。
  20. ///
  21. /// CalibrationEngine 实际用到的依赖面(codegraph 核对 CalibrationEngine.cs):
  22. /// ISerialChannel: HorizontalMoveToWait / VerticalMoveToWait(引擎内);
  23. /// ReadWellHorizontalPosWait / ReadWellFocusZeroWait(VM 取扫描中心,引擎外)。
  24. /// ICamera: Width / Height / SetExposure / GrabRgb / GetFrameBuffer。
  25. /// </summary>
  26. internal sealed class DebugSerialAdapter : ISerialChannel
  27. {
  28. private readonly ComBin _comBin;
  29. private readonly int _motorDelay;
  30. public DebugSerialAdapter(ComBin comBin, int motorDelay)
  31. {
  32. _comBin = comBin ?? throw new ArgumentNullException(nameof(comBin));
  33. _motorDelay = motorDelay;
  34. }
  35. public Action<string> Log { get; set; }
  36. // ── 引擎实际调用的方法 ──
  37. public bool HorizontalMoveToWait(int pulse, int delayMs = -1)
  38. {
  39. // delayMs<0 时引擎用默认 ScanDelayMs;这里统一用调试页配置的 motorDelay 作为电机到位等待。
  40. return _comBin.HorizontalMotorAbsoluteWait(new CustomProtocol(), _motorDelay, pulse);
  41. }
  42. public bool VerticalMoveToWait(int pulse, int delayMs = -1)
  43. {
  44. // 调试栈垂直绝对运动签名含 hor/pictureId/well/focal(仅日志/记录用),标定阶段填占位 1。
  45. return _comBin.VerticalMotorAbsoluteWait(new CustomProtocol(), _motorDelay, pulse, 1, 1, 1, 1);
  46. }
  47. public int ReadWellHorizontalPosWait(int well)
  48. => _comBin.ReadEEPROMhoriMtWellHorHorWait(new CustomProtocol(), well);
  49. public int ReadWellFocusZeroWait(int well)
  50. // 调试栈无"按 well 读 Z 焦准零点"命令;读垂直电机清晰起点脉冲作扫描参考(§2.5:EEPROM 仅参考,不进解析链/不回写)。
  51. => _comBin.ReadEEPROMvertMtStartPulseWait(new CustomProtocol());
  52. // ── 以下接口成员引擎从不调用,调试页标定流程不会触达;保持 ISerialChannel 完整实现 ──
  53. public string PortName => _comBin?.ToString() ?? "";
  54. public bool IsOpen => true;
  55. public int MoveReadTimeoutMs { get; set; } = 12000;
  56. public int QueryReadTimeoutMs { get; set; } = 3000;
  57. public int MotorSettleMs { get; set; } = 1500;
  58. public bool Open() => true; // 调试栈已 OpenPort,HAL 适配层不重复开
  59. public void Close() { } // 句柄生命周期归调试页 ComHouseUnit,适配器不关
  60. public void Dispose() { } // 同上:适配器不持有句柄所有权
  61. private static NotSupportedException NotUsed(string m)
  62. => new NotSupportedException($"DebugSerialAdapter.{m} 不在调试页标定路径内(CalibrationEngine 未调用)。");
  63. public byte[] SendWait(byte[] frame, int extraWaitMs = 0) => throw NotUsed(nameof(SendWait));
  64. public int ShakeHandsWait() => throw NotUsed(nameof(ShakeHandsWait));
  65. public int ReadCcdSnWait() => throw NotUsed(nameof(ReadCcdSnWait));
  66. public int ReadLightBrightnessWait() => throw NotUsed(nameof(ReadLightBrightnessWait));
  67. public int ReadScanStepWait() => throw NotUsed(nameof(ReadScanStepWait));
  68. public bool WriteWellHorizontalPosWait(int well, int pulseValue) => throw NotUsed(nameof(WriteWellHorizontalPosWait));
  69. public bool VerticalForwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(VerticalForwardWait));
  70. public bool VerticalBackwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(VerticalBackwardWait));
  71. public bool VerticalResetWait(int delayMs = -1) => throw NotUsed(nameof(VerticalResetWait));
  72. public int ReadVerticalPositionWait() => throw NotUsed(nameof(ReadVerticalPositionWait));
  73. public bool HorizontalForwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(HorizontalForwardWait));
  74. public bool HorizontalBackwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(HorizontalBackwardWait));
  75. public bool HorizontalResetWait(int delayMs = -1) => throw NotUsed(nameof(HorizontalResetWait));
  76. public int ReadHorizontalPositionWait() => throw NotUsed(nameof(ReadHorizontalPositionWait));
  77. public decimal TemperatureWait() => throw NotUsed(nameof(TemperatureWait));
  78. public decimal ShangTemperatureWait() => throw NotUsed(nameof(ShangTemperatureWait));
  79. public decimal BoLiTemperatureWait() => throw NotUsed(nameof(BoLiTemperatureWait));
  80. public decimal PressureWait() => throw NotUsed(nameof(PressureWait));
  81. public (decimal pressure, decimal t1, decimal t2) BufferBottleStateWait() => throw NotUsed(nameof(BufferBottleStateWait));
  82. public DoorState DoorStatusWait() => throw NotUsed(nameof(DoorStatusWait));
  83. public bool OpenLedWait() => throw NotUsed(nameof(OpenLedWait));
  84. public bool CloseLedWait() => throw NotUsed(nameof(CloseLedWait));
  85. public bool OpenIntakeValveWait() => throw NotUsed(nameof(OpenIntakeValveWait));
  86. public bool CloseIntakeValveWait() => throw NotUsed(nameof(CloseIntakeValveWait));
  87. public bool OpenExhaustValveWait() => throw NotUsed(nameof(OpenExhaustValveWait));
  88. public bool CloseExhaustValveWait() => throw NotUsed(nameof(CloseExhaustValveWait));
  89. public bool HouseAerationWait() => throw NotUsed(nameof(HouseAerationWait));
  90. public bool HouseVentWait() => throw NotUsed(nameof(HouseVentWait));
  91. public bool BufferBottleAerationWait() => throw NotUsed(nameof(BufferBottleAerationWait));
  92. public bool AutoAirSwapWait(bool on) => throw NotUsed(nameof(AutoAirSwapWait));
  93. public object RawComBin => _comBin;
  94. }
  95. /// <summary>
  96. /// M2-05 调试页标定相机适配器:把 operate 调试栈已 Init 的 Camera 适配成 ICamera。
  97. /// 引擎仅用 Width/Height/SetExposure/GrabRgb/GetFrameBuffer;其余成员从不被调用。
  98. /// </summary>
  99. internal sealed class DebugCameraAdapter : ICamera
  100. {
  101. private readonly Camera _camera;
  102. public DebugCameraAdapter(Camera camera) => _camera = camera ?? throw new ArgumentNullException(nameof(camera));
  103. public int Index => _camera.Index;
  104. public int Width => _camera.Width;
  105. public int Height => _camera.Height;
  106. public int Exposure => _camera.Exposure;
  107. public string SerialNumber => _camera.NumBer;
  108. public bool IsInit => _camera.IsInit;
  109. public bool IsStart => _camera.IsStart;
  110. // ── 引擎实际调用的方法 ──
  111. public int SetExposure(int exposure100us) => _camera.SetPartOfCapInfo(exposure100us);
  112. public int GrabRgb() => _camera.GetRgbData(); // 调试栈抓帧到内部缓冲,返回 0 成功
  113. public byte[] GetFrameBuffer()
  114. {
  115. try { return _camera.SourceBuffer; } // 24bpp BGR W*H*3,与 af GetSourceBuffer 同语义
  116. catch { return null; } // _pDest 已释放等异常 → null,引擎按抓帧失败重试
  117. }
  118. // ── 以下接口成员引擎从不调用(标定路径不触达);调试页生命周期管句柄,适配器不接管 ──
  119. private static NotSupportedException NotUsed(string m)
  120. => new NotSupportedException($"DebugCameraAdapter.{m} 不在调试页标定路径内(CalibrationEngine 未调用)。");
  121. public int Init(byte redGain = 25, byte greenGain = 14, byte blueGain = 25) => throw NotUsed(nameof(Init));
  122. public int SetOpMode(byte mode = 0, bool strobe = false) => throw NotUsed(nameof(SetOpMode));
  123. public int ReadSerial() => throw NotUsed(nameof(ReadSerial));
  124. public int UnInit() => throw NotUsed(nameof(UnInit));
  125. public int SetGain(byte red, byte green, byte blue) => throw NotUsed(nameof(SetGain));
  126. public byte[] GrabStable(int preDelayMs = 0, bool discardStale = true, int retry = 2)
  127. {
  128. // 引擎走自带 Grab()(双 Grab 丢帧)而非此便捷重载;仍按"丢一帧+重试"实现以备调用。
  129. if (preDelayMs > 0) System.Threading.Thread.Sleep(preDelayMs);
  130. if (discardStale) { GrabRgb(); }
  131. for (int i = 0; i <= retry; i++)
  132. {
  133. if (GrabRgb() == 0)
  134. {
  135. var b = GetFrameBuffer();
  136. if (b != null && b.Length > 0) return b;
  137. }
  138. System.Threading.Thread.Sleep(50);
  139. }
  140. return null;
  141. }
  142. public int StartPreview(IntPtr hostControl, int left, int top, int width, int height) => throw NotUsed(nameof(StartPreview));
  143. public int StopPreview() => throw NotUsed(nameof(StopPreview));
  144. public void Dispose() { } // 调试页 ComHouseUnit 负责 UnInit,适配器不接管
  145. public object RawCamera => _camera;
  146. }
  147. /// <summary>M2-05 单 well 标定 UI 状态:待标定/标定中/合格(绿)/伪峰(红)/失败(红)。</summary>
  148. public enum WellCalibState { Pending, Running, Qualified, FakePeak, Failed }
  149. /// <summary>
  150. /// M2-05 一键标定单 well UI 结果项(绑定调试页 16 格实时显示)。
  151. /// 合格(Qualified)绿、伪峰/失败(FakePeak/Failed)红;含 FocusZ/峰比/偏移/曝光/Note。
  152. /// 实现 ObservableObject 以便每 well 标定完即时刷新对应格子。
  153. /// 颜色判定建议在 View 用 DataTrigger 绑定 State(绿=Qualified,红=FakePeak|Failed,中性=Pending|Running)。
  154. /// </summary>
  155. public partial class WellCalibUiItem : ObservableObject
  156. {
  157. [ObservableProperty] private int well;
  158. [ObservableProperty] private WellCalibState state = WellCalibState.Pending;
  159. [ObservableProperty] private int focusZ;
  160. [ObservableProperty] private double peakRatio;
  161. [ObservableProperty] private double centerOffsetPct;
  162. [ObservableProperty] private bool circleFound;
  163. [ObservableProperty] private int exposure;
  164. [ObservableProperty] private string note = "";
  165. /// <summary>是否合格(供 View 直接绑定做绿/红,等价 State==Qualified)。</summary>
  166. public bool IsQualified => State == WellCalibState.Qualified;
  167. }
  168. /// <summary>
  169. /// M2-07 对焦后手调拍摄层 · 单层预览项(绑定预览列表,让工程师看到调整后各层绝对 Z)。
  170. /// 由 PhotoLayerCalculator.ComputeLayerPositions(标定FocusZ, 手调cfg, pulseMax) 生成;
  171. /// IsFocusLayer 标记清晰层(第 down 层)以便 View 高亮。
  172. /// </summary>
  173. public partial class LayerPreviewItem : ObservableObject
  174. {
  175. /// <summary>层序号(0 .. count-1,0=对焦起点)。</summary>
  176. [ObservableProperty] private int layerIndex;
  177. /// <summary>该层绝对 Z 脉冲(公式算出,含 pulseMax 钳位)。</summary>
  178. [ObservableProperty] private int zPulse;
  179. /// <summary>是否为清晰层(即第 down 层,对焦锚点 FocusZ 所在层)。</summary>
  180. [ObservableProperty] private bool isFocusLayer;
  181. }
  182. }