|
|
@@ -55,6 +55,15 @@ namespace ivf_tl_Com
|
|
|
/// </summary>
|
|
|
public event Func<int, Dictionary<int, DateTime?>, List<HouseWellPhoto>> GetAutoFocusDBEvent;
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// D2-02 闭环:本地四步对焦算出 FocusZ 后上报服务器(calAutofocusPosition)。
|
|
|
+ /// 服务器据此排"清晰层 FocusZ + 上下对称拍照层"写 t_house_photograph_setting;
|
|
|
+ /// control 随后用同一 autofocusTime 经 /ccd/position(GetCCDServiceEvent) 取回这些层拍照。
|
|
|
+ /// 这是采集对焦闭环里"本地FocusZ→服务器排层"的关键一环(老系统是拍对焦图→服务器评分选层,新系统无评分、直接上报FocusZ)。
|
|
|
+ /// 参数:houseSn, wellSn, focusZ(=clearPosition), autofocusTime("yyyy-MM-dd HH:mm:ss"), 成功层数(无评分,传足够大值越阈值)。返回是否上报成功。
|
|
|
+ /// </summary>
|
|
|
+ public event Func<int, int, int, string, int, bool> UploadAutoFocusEvent;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// M2-04 标定结果存储入口(写本地 calibration.json 真相源 + 镜像 house_autofocus_calibration)。
|
|
|
/// 由 control 端(AppData)在 InitHouseBinEvent 时注入并配置 JsonPath/DbMirror;
|
|
|
@@ -554,8 +563,8 @@ namespace ivf_tl_Com
|
|
|
TLLogEvent?.Invoke($"[{House.houseSn}][{this.PortName}]相机连续模式设置结果{cameraSetMode},结束初始化[注:0表示成功]", LogEnum.RunError);
|
|
|
return;
|
|
|
}
|
|
|
- Camera.GetRgbData();
|
|
|
- Camera.SavePreMem();
|
|
|
+ CamGate(() => Camera.GetRgbData());
|
|
|
+ CamGate(() => Camera.SavePreMem());
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
@@ -1345,6 +1354,14 @@ namespace ivf_tl_Com
|
|
|
Thread.Sleep(TLSetting.aerationDelay * 1000);
|
|
|
HouseState(commandSource, custom, 0);
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}]补气完成[{oldPressure}->{Pressure}]", LogEnum.HouseInfo);
|
|
|
+ // D2-02 真机:无气源时压力补不上(0->0)。不再跑满 houseAerationNum 次空补气拖死采集——
|
|
|
+ // 补一次后压力没上升即判定无气源,停止本次补气;有气源(压力上升)则继续直到达标。
|
|
|
+ // 修复"没气严重拖慢/卡住拍照"(对焦后冗余补气、换气、拍照逐孔补气在无气时各跑满35s)。
|
|
|
+ if (Pressure <= oldPressure)
|
|
|
+ {
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][补气后压力未上升({oldPressure}->{Pressure}),疑无气源,停止本次补气(不拖慢采集)]", LogEnum.HouseInfo);
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
this.ValveState = State.正常;
|
|
|
if (Pressure < TLSetting.pressureAlarmMin) this.ValveState = State.待补气;
|
|
|
@@ -1577,9 +1594,19 @@ namespace ivf_tl_Com
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ // D2-02 闭环:本地对焦需补光 —— 临开 LED(对齐老 AllEmbryoAutofocus 的开灯→对焦→关灯);
|
|
|
+ // 实测黑灯下对焦峰比仅 1.01(检不到圆),开灯后 1.76(找到清晰面)。本轮所有 well 共用一个对焦时间戳=批次键。
|
|
|
+ DateTime focusTime = DateTime.Now;
|
|
|
+ try { lease.Serial.OpenLedWait(); } catch (Exception exLed) { HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][本地对焦开LED失败:{exLed.Message}]", LogEnum.HouseInfo); }
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][本地对焦诊断:wellList数={wellList.Count}, IsStopClearest={IsStopClearest}, FirstClearest={FirstClearest}, gate启用={localAfEnabled}]", LogEnum.HouseInfo);
|
|
|
+ try
|
|
|
+ {
|
|
|
foreach (var well in wellList.Keys)
|
|
|
{
|
|
|
- if (IsStopClearest || FirstClearest) break;
|
|
|
+ // 只按显式停止信号 IsStopClearest 中断。
|
|
|
+ // ★不能看 FirstClearest:StartAutoFocus 由 MainThread 在 FirstClearest=true 时调用、返回后才置 false,
|
|
|
+ // 若这里 break on FirstClearest 会第一轮就退出、一个 well 都不对焦(M2-03 遗留bug,gate=1 首次真机暴露)。
|
|
|
+ if (IsStopClearest) break;
|
|
|
try
|
|
|
{
|
|
|
// 自动对焦重构 Task2.2:读 per-well 运动范围(半幅)+中心(DB),逐 well 注入引擎。
|
|
|
@@ -1629,6 +1656,34 @@ namespace ivf_tl_Com
|
|
|
{
|
|
|
ExceptionLogEvent?.Invoke(exStore, $"[{House.houseSn}][{this.PortName}][{well}号well标定结果存储失败]", null, LogEnum.RunException);
|
|
|
}
|
|
|
+
|
|
|
+ // === D2-02 闭环关键缺口:把本地算出的 FocusZ 上报服务器(calAutofocusPosition),服务器据此排各拍照层 ===
|
|
|
+ // 合格(检到圆 + 峰比>阈值)才上报;不合格不上报 = 保留上次拍照位置(不污染)。
|
|
|
+ // 上报成功才把 autofocusTime 写入 LastAutoFocusTimeDic —— StartCCD 的 IsCCD() 重建 wellList 时
|
|
|
+ // 会带上同一时间戳,使 /ccd/position 取回的正是刚上报排出的那组层(时间戳天然对齐)。
|
|
|
+ double peakThr = (double)(FocusPeakRatioThreshold ?? 1.2m);
|
|
|
+ // 甲方案(用户定):上报"合格"只看对焦峰比>阈值(找到清晰焦面即可驱动拍照层);
|
|
|
+ // 圆检测(水平居中质量)只记日志、不拦上报 —— 避免某些皿型/相机倍率检不出完整 well 圆而卡死整条对焦闭环。
|
|
|
+ bool qualified = wc.PeakRatio > peakThr;
|
|
|
+ if (qualified)
|
|
|
+ {
|
|
|
+ string afTime = focusTime.ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
+ // 新系统无云端评分,本地对焦合格即希望服务器接受该 FocusZ,成功层数传足够大值以越过服务器阈值。
|
|
|
+ bool uploaded = UploadAutoFocusEvent?.Invoke(House.houseSn, well, wc.FocusZ, afTime, 999) ?? false;
|
|
|
+ if (uploaded)
|
|
|
+ {
|
|
|
+ LastAutoFocusTimeDic[well] = focusTime;
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][{well}号well本地对焦合格(FocusZ={wc.FocusZ},峰比={wc.PeakRatio:F2})已上报,拍照层将绕此焦面]", LogEnum.HouseInfo);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][{well}号well本地对焦合格但上报失败,本轮保留上次拍照位置]", LogEnum.HouseInfo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][{well}号well本地对焦不合格(检到圆={wc.CircleFound},峰比={wc.PeakRatio:F2}≤阈值{peakThr:F2}),不上报,保留上次拍照位置]", LogEnum.HouseInfo);
|
|
|
+ }
|
|
|
}
|
|
|
catch (Exception exWell)
|
|
|
{
|
|
|
@@ -1636,6 +1691,8 @@ namespace ivf_tl_Com
|
|
|
ExceptionLogEvent?.Invoke(exWell, $"[{House.houseSn}][{this.PortName}][{well}号well本地自动对焦失败]", null, LogEnum.RunException);
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+ finally { try { lease.Serial.CloseLedWait(); } catch { } } // D2-02:对焦结束关灯(胚胎不长期受光)
|
|
|
} // lease.Dispose() → 归还借用,HAL 恢复采集
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
@@ -1832,7 +1889,7 @@ namespace ivf_tl_Com
|
|
|
HouseState(commandSource, custom, 0);
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- var startNoView = Camera.Usb2StartCapture();
|
|
|
+ var startNoView = CamGate(() => Camera.Usb2StartCapture());
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][无图预览开启结果:{startNoView}]", LogEnum.HouseInfo);
|
|
|
}
|
|
|
//打开CCD补光LED
|
|
|
@@ -1876,12 +1933,15 @@ namespace ivf_tl_Com
|
|
|
// 整段移除——拍出来无人评、无人看,纯空转。FocusZ 产生/消费(StartAutoFocus/CalibrationEngine/_autoFocusPhoto)不受影响。
|
|
|
// 保留:well 遍历骨架、电机就位、LastAutoFocusTimeDic 置位(改无条件,喂服务器查胚胎照位置的时间戳)、isSuccess 返回真(门控 MainThread GetClearest→StartCCD)。
|
|
|
// ────────────────────────────────────────────────────────────────
|
|
|
- LastAutoFocusTimeDic[itemEmbryo.wellSn] = SavePictrueTime;
|
|
|
+ // D2-02 闭环:时间戳改由 StartAutoFocus 在上报 FocusZ 成功时统一写入 LastAutoFocusTimeDic
|
|
|
+ // (与 calAutofocusPosition 上报用同一 autofocusTime,保证 StartCCD 取层时间戳对齐)。
|
|
|
+ // 此处不再覆盖,否则会用一个晚于上报的时间覆盖掉、导致 /ccd/position 取不到刚排的层。
|
|
|
+ // LastAutoFocusTimeDic[itemEmbryo.wellSn] = SavePictrueTime;
|
|
|
}
|
|
|
ComBin.CloseLEDWait(custom);
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- var stopNoView = Camera.Usb2StopCapture();
|
|
|
+ var stopNoView = CamGate(() => Camera.Usb2StopCapture());
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][无图预览关闭结果:{stopNoView}]", LogEnum.HouseInfo);
|
|
|
}
|
|
|
ComBin.HorizontalMotorResetWait(custom, TLSetting.motorDelay);
|
|
|
@@ -1933,7 +1993,7 @@ namespace ivf_tl_Com
|
|
|
HouseState(commandSource, custom, 0);
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- var startNoView = Camera.Usb2StartCapture();
|
|
|
+ var startNoView = CamGate(() => Camera.Usb2StartCapture());
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][无图预览开启结果:{startNoView}]", LogEnum.HouseInfo);
|
|
|
}
|
|
|
ComBin.OpenLEDWait(custom);
|
|
|
@@ -1993,7 +2053,8 @@ namespace ivf_tl_Com
|
|
|
// 保留:DeleteAutoFocusWell(对焦队列推进,删了 do-while 死循环)、LastAutoFocusTimeDic 置位(无条件)、isSuccess 返回真。
|
|
|
// ────────────────────────────────────────────────────────────────
|
|
|
DeleteAutoFocusWell(embryoItem.wellSn);
|
|
|
- LastAutoFocusTimeDic[embryoItem.wellSn] = SavePictrueTime;
|
|
|
+ // D2-02 闭环:时间戳改由 StartAutoFocus 上报时统一写入(同 autofocusTime),此处不再覆盖。
|
|
|
+ // LastAutoFocusTimeDic[embryoItem.wellSn] = SavePictrueTime;
|
|
|
}
|
|
|
} while (AutoFocusWellAny());
|
|
|
|
|
|
@@ -2002,7 +2063,7 @@ namespace ivf_tl_Com
|
|
|
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- var stopNoView = Camera.Usb2StopCapture();
|
|
|
+ var stopNoView = CamGate(() => Camera.Usb2StopCapture());
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][无图预览关闭结果:{stopNoView}]", LogEnum.HouseInfo);
|
|
|
}
|
|
|
|
|
|
@@ -2117,7 +2178,7 @@ namespace ivf_tl_Com
|
|
|
int allPicNum = House.photographPictureNumber;
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- var startNoView = Camera.Usb2StartCapture();
|
|
|
+ var startNoView = CamGate(() => Camera.Usb2StartCapture());
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][无图预览开启结果:{startNoView}]", LogEnum.HouseInfo);
|
|
|
}
|
|
|
ComBin.OpenLEDWait(custom);
|
|
|
@@ -2179,7 +2240,7 @@ namespace ivf_tl_Com
|
|
|
ComBin.CloseLEDWait(custom);
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- var stopNoView = Camera.Usb2StopCapture();
|
|
|
+ var stopNoView = CamGate(() => Camera.Usb2StopCapture());
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][无图预览关闭结果:{stopNoView}]", LogEnum.HouseInfo);
|
|
|
}
|
|
|
ComBin.HorizontalMotorResetWait(custom, TLSetting.motorDelay);
|
|
|
@@ -2230,11 +2291,11 @@ namespace ivf_tl_Com
|
|
|
{
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- GetRgbData = Camera.GetRawData();//获取字节流
|
|
|
+ GetRgbData = CamGate(() => Camera.GetRawData());//获取字节流(走全局相机锁)
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[第{i}次无图预览抓拍结果:{GetRgbData}][注:0表示成功]", LogEnum.HouseRunRecord);
|
|
|
if (GetRgbData == 0)
|
|
|
{
|
|
|
- GetRgbData = Camera.RawToRgb();
|
|
|
+ GetRgbData = CamGate(() => { int r = Camera.RawToRgb(); if (r == 0) _capturedFrame = Camera.SourceBuffer; return r; });//RawToRgb+取缓冲原子(同一锁内)
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[rawToRgb结果:{GetRgbData}][注:0表示成功]", LogEnum.HouseRunRecord);
|
|
|
}
|
|
|
}
|
|
|
@@ -2246,7 +2307,7 @@ namespace ivf_tl_Com
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- GetRgbData = Camera.GetRgbData();//获取字节流
|
|
|
+ GetRgbData = CamGate(() => { int r = Camera.GetRgbData(); if (r == 0) _capturedFrame = Camera.SourceBuffer; return r; });//抓图+取缓冲原子(同一相机锁内,防别舱改pDest致AV崩溃)
|
|
|
}
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[第{i}次抓拍结果:{GetRgbData}][注:0表示成功]", LogEnum.HouseRunRecord);
|
|
|
}
|
|
|
@@ -2309,6 +2370,26 @@ namespace ivf_tl_Com
|
|
|
/// <param name="content"></param>
|
|
|
/// <param name="wellSn"></param>
|
|
|
/// <returns></returns>
|
|
|
+ // ── D2-02 真机并发修复:采集(拍照)路径的相机原生调用补走【全局相机锁】 ──
|
|
|
+ // mvcapi.dll 非线程安全、全进程多相机共用一把锁(ICameraGate)。合并时对焦/调试已迁到守锁的 CameraImpl,
|
|
|
+ // 采集仍用裸 Camera(RawCamera)直连、绕过了锁 → 多舱并发抓图/取缓冲与别舱的有锁原生调用撞车 → mvcapi 死锁
|
|
|
+ // (实测:多舱并发时拍照取 Camera.SourceBuffer 卡死)。下面两个 helper 把采集相机调用也串到同一把锁上。
|
|
|
+ private T CamGate<T>(Func<T> nativeCall) => HardwareAccessLayer.Instance.CameraGate.Invoke(nativeCall);
|
|
|
+ private void CamGate(Action nativeCall) => HardwareAccessLayer.Instance.CameraGate.Invoke(nativeCall);
|
|
|
+
|
|
|
+ // D2-02 真机崩溃修复:抓图那一刻在同一把相机锁内把帧拷成托管缓冲(原子)。
|
|
|
+ // 拍照存图用这个快照,而非再次实时读 Camera.SourceBuffer(那会与别舱抓图争用共享 native pDest → AV 崩溃)。
|
|
|
+ private byte[] _capturedFrame = null;
|
|
|
+
|
|
|
+ // D2-02 真机崩溃修复(根因·全局):多舱并发拍照时,整条"拍照关键段"(抓图→存DB→存图Save2→上传)
|
|
|
+ // 串起 mvcapi.dll / Project2.dll / SQLite / Kafka / protobuf 多个无法核实线程安全的原生/三方组件,
|
|
|
+ // 3 舱并发拍照即在 coreclr 报堆破坏 AV(故障模块 coreclr.dll,固定偏移,c0000005)。逐项已排除
|
|
|
+ // 共享缓冲/static/尺寸不符/相机共享/上锁缺口 → 结论:拍照流水线整体非并发安全。
|
|
|
+ // 与既有"全局相机锁(ICameraGate)"同理:进程级一把静态锁,把整段拍照关键段跨舱串行化。
|
|
|
+ // 对焦/电机移动不受此锁约束,各舱仍并发推进;只有图像抓取-落盘-上传这段互斥(每层秒级,节拍内可容纳)。
|
|
|
+ private static readonly object _captureCriticalGate = new object();
|
|
|
+
|
|
|
+
|
|
|
public int GetRgbDataFunNew(string content, int wellSn)
|
|
|
{
|
|
|
CustomProtocol custom = new CustomProtocol();
|
|
|
@@ -2336,11 +2417,11 @@ namespace ivf_tl_Com
|
|
|
{
|
|
|
if (isWuTu)
|
|
|
{
|
|
|
- GetRgbData = Camera.GetRawData();//获取字节流
|
|
|
+ GetRgbData = CamGate(() => Camera.GetRawData());//获取字节流(走全局相机锁)
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[第{i}次无图预览抓拍结果:{GetRgbData}][注:0表示成功]", LogEnum.HouseRunRecord);
|
|
|
if (GetRgbData == 0)
|
|
|
{
|
|
|
- GetRgbData = Camera.RawToRgb();
|
|
|
+ GetRgbData = CamGate(() => { int r = Camera.RawToRgb(); if (r == 0) _capturedFrame = Camera.SourceBuffer; return r; });//RawToRgb+取缓冲原子(同一锁内)
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[rawToRgb结果:{GetRgbData}][注:0表示成功]", LogEnum.HouseRunRecord);
|
|
|
if (GetRgbData != 0)
|
|
|
{
|
|
|
@@ -2348,7 +2429,7 @@ namespace ivf_tl_Com
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- Camera.UpdataSourceBuffer();
|
|
|
+ CamGate(() => Camera.UpdataSourceBuffer());
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
@@ -2364,7 +2445,7 @@ namespace ivf_tl_Com
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- GetRgbData = Camera.GetRgbData();//获取字节流
|
|
|
+ GetRgbData = CamGate(() => { int r = Camera.GetRgbData(); if (r == 0) _capturedFrame = Camera.SourceBuffer; return r; });//抓图+取缓冲原子(同一相机锁内,防别舱改pDest致AV崩溃)
|
|
|
}
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[第{i}次抓拍结果:{GetRgbData}][注:0表示成功]", LogEnum.HouseRunRecord);
|
|
|
if (GetRgbData != 0)
|
|
|
@@ -2373,11 +2454,11 @@ namespace ivf_tl_Com
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- Camera.UpdataSourceBuffer();
|
|
|
+ CamGate(() => Camera.UpdataSourceBuffer());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- if (GetRgbData == 0 && Camera.CheckRgbData())
|
|
|
+ if (GetRgbData == 0 && CamGate(() => Camera.CheckRgbData()))
|
|
|
{
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[第{i}次抓拍校验成功]", LogEnum.HouseRunRecord);
|
|
|
rsData = 0;
|
|
|
@@ -2462,6 +2543,8 @@ namespace ivf_tl_Com
|
|
|
bool isSuccess = true;
|
|
|
try
|
|
|
{
|
|
|
+ lock (_captureCriticalGate) // D2-02 崩溃修复:拍照关键段(抓图→存DB→存图Save2→上传)跨舱串行,防 coreclr 堆破坏 AV
|
|
|
+ {
|
|
|
content = $"[{House.houseSn}][{PortName}][{customProtocol.commandNumber}][{customProtocol.commandType.ToString()}][图片ID:{customProtocol.pictureId}][图片位置:{customProtocol.well}-{currentPicNum}][水平电机:{customProtocol.HorizontalMotorPulse}][垂直电机:{customProtocol.VerticalMotorPulse}][下位机水平:{currentHorizontalMotor}][下位机垂直:{currentVerticalMotor}]";
|
|
|
if (customProtocol.HorizontalMotorPulse != currentHorizontalMotor || customProtocol.VerticalMotorPulse != currentVerticalMotor)
|
|
|
{
|
|
|
@@ -2490,8 +2573,17 @@ namespace ivf_tl_Com
|
|
|
string fileName = $"{House.houseSn}_{itemEmbryo.wellSn}_{Dish.id}_{itemEmbryo.id}_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}_{customProtocol.HorizontalMotorPulse}_{customProtocol.VerticalMotorPulse}.jpg";
|
|
|
string fullName = $"{path}{fileName}";
|
|
|
var imageDTO = GetImageDTO(itemEmbryo, fileName, path, savePictureTime.ToString("yyyy-MM-dd HH:mm:ss"), 0, allPicNum, currentPicNum, customProtocol.VerticalMotorPulse, customProtocol.HorizontalMotorPulse, positionType == 1 ? 1 : 0, isEnd);
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][诊断:存DB记录前 {fileName}]", LogEnum.HouseRunRecord);
|
|
|
SavePicDbEvent?.Invoke(imageDTO);
|
|
|
- int saveResult = SaveImage(Camera.SourceBuffer, House.ccdWidth, House.ccdHeight, fullName);
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][诊断:存DB记录完,本地存图前]", LogEnum.HouseRunRecord);
|
|
|
+ byte[] srcBuf = _capturedFrame; // D2-02 崩溃修复:用抓图时同一锁内原子拷好的快照,不再实时读 Camera.SourceBuffer(防并发AV崩溃)
|
|
|
+ if (srcBuf == null)
|
|
|
+ {
|
|
|
+ TLLogEvent?.Invoke($"{content}抓图缓冲为空,跳过存图", LogEnum.RunError);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int saveResult = SaveImage(srcBuf, House.ccdWidth, House.ccdHeight, fullName);
|
|
|
+ HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{PortName}][诊断:本地存图完 结果={saveResult}]", LogEnum.HouseRunRecord);
|
|
|
endElapsed = CCDOperateStopwatch.Elapsed;
|
|
|
HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[图片保存耗时:{StringHelper.TimeToString(endElapsed - startElapsed)}][图片保存结果:{saveResult}][注:0表示失败,-1表示异常]", LogEnum.HouseRunRecord);
|
|
|
if (saveResult != 1)
|
|
|
@@ -2507,6 +2599,7 @@ namespace ivf_tl_Com
|
|
|
}
|
|
|
imageDTO.ImageData = ByteString.CopyFrom(imageBytes);
|
|
|
UploadImageEvent?.Invoke(imageDTO);
|
|
|
+ } // end lock(_captureCriticalGate)
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -2540,7 +2633,8 @@ namespace ivf_tl_Com
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
- IntPtr ptr = AivfoHelper.ImageProcessing(imgData, width, height, path, sourcePath, isSave, wellname, devTime, leftOffset, bttOffset);
|
|
|
+ // D2-02 崩溃修复:ImageProcessing 同属 Project2.dll,与抓图/Save2 走同一把全局相机锁串行。
|
|
|
+ IntPtr ptr = CamGate(() => AivfoHelper.ImageProcessing(imgData, width, height, path, sourcePath, isSave, wellname, devTime, leftOffset, bttOffset));
|
|
|
|
|
|
if (ptr == IntPtr.Zero)
|
|
|
{
|
|
|
@@ -2570,7 +2664,9 @@ namespace ivf_tl_Com
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
- int a = AivfoHelper.Save2(bitmapData, width, height, fileName);
|
|
|
+ // D2-02 崩溃修复:Save2 属 Project2.dll(原生图像库),与 mvcapi 抓图共享全局原生状态,
|
|
|
+ // 必须与抓图走同一把全局相机锁串行,否则别舱抓图并发时撞原生状态致 AV 崩溃。
|
|
|
+ int a = CamGate(() => AivfoHelper.Save2(bitmapData, width, height, fileName));
|
|
|
return a;
|
|
|
}
|
|
|
catch (Exception ex)
|