using System; using ivf_tl_Entity.CameraEntitys; using ivf_tl_Entity.ComEntitys; using IvfTl.Hardware; using CommunityToolkit.Mvvm.ComponentModel; namespace ivf_tl_Operate.ViewModel { /// /// M2-05 调试页标定适配器:把 operate 调试栈已打开的 ComBin/Camera 适配成 /// HAL 接口 ISerialChannel/ICamera,供 IvfTl.AutoFocus.CalibrationEngine 复用(不重复实现算法)。 /// /// 【为何用适配器而非 Acquire(OperateDebug) 取 lease.Serial/lease.Camera】 /// 调试页(M1-03)刻意仍用 operate 侧具体类型 ComBin/Camera(方法更全:WriteEEPROM/正反向/RawToRgb), /// 并已通过 comBin.OpenPort()/camera.Init() 独占持有了本舱 COM 口与相机句柄。 /// 若再向 HAL 申请前台借用并用 lease.Serial(HAL SerialChannelImpl)/lease.Camera(HAL CameraImpl), /// 会在同一已被调试栈占用的 COM 口/相机上二次 Open/Init → 句柄冲突。 /// 故标定引擎直接驱动调试栈【已打开】的 ComBin/Camera:本适配器只暴露 CalibrationEngine 实际调用的少量方法, /// 其余接口成员抛 NotSupportedException(引擎从不调用)。互斥仍由调试页持有的 HAL 前台借用(OperateDebug) /// 保证(打开调试页时 ComHouseInit 已 Acquire(OperateDebug),HAL 暂停 control 对本舱采集)。 /// /// CalibrationEngine 实际用到的依赖面(codegraph 核对 CalibrationEngine.cs): /// ISerialChannel: HorizontalMoveToWait / VerticalMoveToWait(引擎内); /// ReadWellHorizontalPosWait / ReadWellFocusZeroWait(VM 取扫描中心,引擎外)。 /// ICamera: Width / Height / SetExposure / GrabRgb / GetFrameBuffer。 /// internal sealed class DebugSerialAdapter : ISerialChannel { private readonly ComBin _comBin; private readonly int _motorDelay; public DebugSerialAdapter(ComBin comBin, int motorDelay) { _comBin = comBin ?? throw new ArgumentNullException(nameof(comBin)); _motorDelay = motorDelay; } public Action Log { get; set; } // ── 引擎实际调用的方法 ── public bool HorizontalMoveToWait(int pulse, int delayMs = -1) { // delayMs<0 时引擎用默认 ScanDelayMs;这里统一用调试页配置的 motorDelay 作为电机到位等待。 return _comBin.HorizontalMotorAbsoluteWait(new CustomProtocol(), _motorDelay, pulse); } public bool VerticalMoveToWait(int pulse, int delayMs = -1) { // 调试栈垂直绝对运动签名含 hor/pictureId/well/focal(仅日志/记录用),标定阶段填占位 1。 return _comBin.VerticalMotorAbsoluteWait(new CustomProtocol(), _motorDelay, pulse, 1, 1, 1, 1); } public int ReadWellHorizontalPosWait(int well) => _comBin.ReadEEPROMhoriMtWellHorHorWait(new CustomProtocol(), well); public int ReadWellFocusZeroWait(int well) // 调试栈无"按 well 读 Z 焦准零点"命令;读垂直电机清晰起点脉冲作扫描参考(§2.5:EEPROM 仅参考,不进解析链/不回写)。 => _comBin.ReadEEPROMvertMtStartPulseWait(new CustomProtocol()); // ── 以下接口成员引擎从不调用,调试页标定流程不会触达;保持 ISerialChannel 完整实现 ── public string PortName => _comBin?.ToString() ?? ""; public bool IsOpen => true; public int MoveReadTimeoutMs { get; set; } = 12000; public int QueryReadTimeoutMs { get; set; } = 3000; public int MotorSettleMs { get; set; } = 1500; public bool Open() => true; // 调试栈已 OpenPort,HAL 适配层不重复开 public void Close() { } // 句柄生命周期归调试页 ComHouseUnit,适配器不关 public void Dispose() { } // 同上:适配器不持有句柄所有权 private static NotSupportedException NotUsed(string m) => new NotSupportedException($"DebugSerialAdapter.{m} 不在调试页标定路径内(CalibrationEngine 未调用)。"); public byte[] SendWait(byte[] frame, int extraWaitMs = 0) => throw NotUsed(nameof(SendWait)); public int ShakeHandsWait() => throw NotUsed(nameof(ShakeHandsWait)); public int ReadCcdSnWait() => throw NotUsed(nameof(ReadCcdSnWait)); public int ReadLightBrightnessWait() => throw NotUsed(nameof(ReadLightBrightnessWait)); public int ReadScanStepWait() => throw NotUsed(nameof(ReadScanStepWait)); public bool WriteWellHorizontalPosWait(int well, int pulseValue) => throw NotUsed(nameof(WriteWellHorizontalPosWait)); public bool VerticalForwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(VerticalForwardWait)); public bool VerticalBackwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(VerticalBackwardWait)); public bool VerticalResetWait(int delayMs = -1) => throw NotUsed(nameof(VerticalResetWait)); public int ReadVerticalPositionWait() => throw NotUsed(nameof(ReadVerticalPositionWait)); public bool HorizontalForwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(HorizontalForwardWait)); public bool HorizontalBackwardWait(int pulse, int delayMs = -1) => throw NotUsed(nameof(HorizontalBackwardWait)); public bool HorizontalResetWait(int delayMs = -1) => throw NotUsed(nameof(HorizontalResetWait)); public int ReadHorizontalPositionWait() => throw NotUsed(nameof(ReadHorizontalPositionWait)); public decimal TemperatureWait() => throw NotUsed(nameof(TemperatureWait)); public decimal ShangTemperatureWait() => throw NotUsed(nameof(ShangTemperatureWait)); public decimal BoLiTemperatureWait() => throw NotUsed(nameof(BoLiTemperatureWait)); public decimal PressureWait() => throw NotUsed(nameof(PressureWait)); public (decimal pressure, decimal t1, decimal t2) BufferBottleStateWait() => throw NotUsed(nameof(BufferBottleStateWait)); public DoorState DoorStatusWait() => throw NotUsed(nameof(DoorStatusWait)); public bool OpenLedWait() => throw NotUsed(nameof(OpenLedWait)); public bool CloseLedWait() => throw NotUsed(nameof(CloseLedWait)); public bool OpenIntakeValveWait() => throw NotUsed(nameof(OpenIntakeValveWait)); public bool CloseIntakeValveWait() => throw NotUsed(nameof(CloseIntakeValveWait)); public bool OpenExhaustValveWait() => throw NotUsed(nameof(OpenExhaustValveWait)); public bool CloseExhaustValveWait() => throw NotUsed(nameof(CloseExhaustValveWait)); public bool HouseAerationWait() => throw NotUsed(nameof(HouseAerationWait)); public bool HouseVentWait() => throw NotUsed(nameof(HouseVentWait)); public bool BufferBottleAerationWait() => throw NotUsed(nameof(BufferBottleAerationWait)); public bool AutoAirSwapWait(bool on) => throw NotUsed(nameof(AutoAirSwapWait)); public object RawComBin => _comBin; } /// /// M2-05 调试页标定相机适配器:把 operate 调试栈已 Init 的 Camera 适配成 ICamera。 /// 引擎仅用 Width/Height/SetExposure/GrabRgb/GetFrameBuffer;其余成员从不被调用。 /// internal sealed class DebugCameraAdapter : ICamera { private readonly Camera _camera; public DebugCameraAdapter(Camera camera) => _camera = camera ?? throw new ArgumentNullException(nameof(camera)); public int Index => _camera.Index; public int Width => _camera.Width; public int Height => _camera.Height; public int Exposure => _camera.Exposure; public string SerialNumber => _camera.NumBer; public bool IsInit => _camera.IsInit; public bool IsStart => _camera.IsStart; // ── 引擎实际调用的方法 ── public int SetExposure(int exposure100us) => _camera.SetPartOfCapInfo(exposure100us); public int GrabRgb() => _camera.GetRgbData(); // 调试栈抓帧到内部缓冲,返回 0 成功 public byte[] GetFrameBuffer() { try { return _camera.SourceBuffer; } // 24bpp BGR W*H*3,与 af GetSourceBuffer 同语义 catch { return null; } // _pDest 已释放等异常 → null,引擎按抓帧失败重试 } // ── 以下接口成员引擎从不调用(标定路径不触达);调试页生命周期管句柄,适配器不接管 ── private static NotSupportedException NotUsed(string m) => new NotSupportedException($"DebugCameraAdapter.{m} 不在调试页标定路径内(CalibrationEngine 未调用)。"); public int Init(byte redGain = 25, byte greenGain = 14, byte blueGain = 25) => throw NotUsed(nameof(Init)); public int SetOpMode(byte mode = 0, bool strobe = false) => throw NotUsed(nameof(SetOpMode)); public int ReadSerial() => throw NotUsed(nameof(ReadSerial)); public int UnInit() => throw NotUsed(nameof(UnInit)); public int SetGain(byte red, byte green, byte blue) => throw NotUsed(nameof(SetGain)); public byte[] GrabStable(int preDelayMs = 0, bool discardStale = true, int retry = 2) { // 引擎走自带 Grab()(双 Grab 丢帧)而非此便捷重载;仍按"丢一帧+重试"实现以备调用。 if (preDelayMs > 0) System.Threading.Thread.Sleep(preDelayMs); if (discardStale) { GrabRgb(); } for (int i = 0; i <= retry; i++) { if (GrabRgb() == 0) { var b = GetFrameBuffer(); if (b != null && b.Length > 0) return b; } System.Threading.Thread.Sleep(50); } return null; } public int StartPreview(IntPtr hostControl, int left, int top, int width, int height) => throw NotUsed(nameof(StartPreview)); public int StopPreview() => throw NotUsed(nameof(StopPreview)); public void Dispose() { } // 调试页 ComHouseUnit 负责 UnInit,适配器不接管 public object RawCamera => _camera; } /// M2-05 单 well 标定 UI 状态:待标定/标定中/合格(绿)/伪峰(红)/失败(红)。 public enum WellCalibState { Pending, Running, Qualified, FakePeak, Failed } /// /// M2-05 一键标定单 well UI 结果项(绑定调试页 16 格实时显示)。 /// 合格(Qualified)绿、伪峰/失败(FakePeak/Failed)红;含 FocusZ/峰比/偏移/曝光/Note。 /// 实现 ObservableObject 以便每 well 标定完即时刷新对应格子。 /// 颜色判定建议在 View 用 DataTrigger 绑定 State(绿=Qualified,红=FakePeak|Failed,中性=Pending|Running)。 /// public partial class WellCalibUiItem : ObservableObject { [ObservableProperty] private int well; [ObservableProperty] private WellCalibState state = WellCalibState.Pending; [ObservableProperty] private int focusZ; [ObservableProperty] private double peakRatio; [ObservableProperty] private double centerOffsetPct; [ObservableProperty] private bool circleFound; [ObservableProperty] private int exposure; [ObservableProperty] private string note = ""; /// 是否合格(供 View 直接绑定做绿/红,等价 State==Qualified)。 public bool IsQualified => State == WellCalibState.Qualified; } /// /// M2-07 对焦后手调拍摄层 · 单层预览项(绑定预览列表,让工程师看到调整后各层绝对 Z)。 /// 由 PhotoLayerCalculator.ComputeLayerPositions(标定FocusZ, 手调cfg, pulseMax) 生成; /// IsFocusLayer 标记清晰层(第 down 层)以便 View 高亮。 /// public partial class LayerPreviewItem : ObservableObject { /// 层序号(0 .. count-1,0=对焦起点)。 [ObservableProperty] private int layerIndex; /// 该层绝对 Z 脉冲(公式算出,含 pulseMax 钳位)。 [ObservableProperty] private int zPulse; /// 是否为清晰层(即第 down 层,对焦锚点 FocusZ 所在层)。 [ObservableProperty] private bool isFocusLayer; } }