diff --git a/CapMachine.Wpf/App.xaml.cs b/CapMachine.Wpf/App.xaml.cs index 7fa6444..ddfb858 100644 --- a/CapMachine.Wpf/App.xaml.cs +++ b/CapMachine.Wpf/App.xaml.cs @@ -1,10 +1,11 @@ -using AutoMapper; +using AutoMapper; using CapMachine.Core; using CapMachine.Core.IService; using CapMachine.Wpf.MapperProfile; using CapMachine.Wpf.Models; using CapMachine.Wpf.Services; using CapMachine.Wpf.SoftAuthorizeCore; +using CapMachine.Wpf.PPCalculation; using CapMachine.Wpf.ViewModels; using CapMachine.Wpf.Views; using FreeSql; @@ -168,6 +169,8 @@ namespace CapMachine.Wpf }); containerRegistry.RegisterSingleton(); + // 注册干度计算服务(REFPROP 实现) + containerRegistry.RegisterSingleton(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); diff --git a/CapMachine.Wpf/Models/CsvRecordModel.cs b/CapMachine.Wpf/Models/CsvRecordModel.cs index 5f1080c..27e1573 100644 --- a/CapMachine.Wpf/Models/CsvRecordModel.cs +++ b/CapMachine.Wpf/Models/CsvRecordModel.cs @@ -146,15 +146,15 @@ namespace CapMachine.Wpf.Models public double EVAPExpTemp { get; set; } /// - /// 冷媒流量[L/min] + /// 冷媒流量[kg/h] /// - [Name("冷媒流量[L/min]")] + [Name("冷媒流量[kg/h]")] public double VRV { get; set; } /// - /// 润滑油流量[L/min] + /// 润滑油流量[kg/h] /// - [Name("润滑油流量[L/min]")] + [Name("润滑油流量[kg/h]")] public double LubeFlow { get; set; } /// diff --git a/CapMachine.Wpf/Models/CsvRecordModelMap.cs b/CapMachine.Wpf/Models/CsvRecordModelMap.cs index 0552a3a..2e33ac9 100644 --- a/CapMachine.Wpf/Models/CsvRecordModelMap.cs +++ b/CapMachine.Wpf/Models/CsvRecordModelMap.cs @@ -37,8 +37,8 @@ namespace CapMachine.Wpf.Models Map(m => m.OS2Temp).Name("OS2温度[℃]"); Map(m => m.Cond2Temp).Name("COND2温度[℃]"); Map(m => m.EVAPExpTemp).Name("EVAP出口温度[℃]"); - Map(m => m.VRV).Name("冷媒流量[L/min]"); - Map(m => m.LubeFlow).Name("润滑油流量[L/min]"); + Map(m => m.VRV).Name("冷媒流量[kg/h]"); + Map(m => m.LubeFlow).Name("润滑油流量[kg/h]"); Map(m => m.ExTemp).Name("排气温度[℃]"); Map(m => m.TxvFrPress).Name("膨胀阀前压力[BarA]"); Map(m => m.TxvFrTemp).Name("膨胀阀前温度[℃]"); diff --git a/CapMachine.Wpf/Models/LightChart/ChartManager.cs b/CapMachine.Wpf/Models/LightChart/ChartManager.cs index 56c4471..6dcf215 100644 --- a/CapMachine.Wpf/Models/LightChart/ChartManager.cs +++ b/CapMachine.Wpf/Models/LightChart/ChartManager.cs @@ -239,9 +239,9 @@ namespace CapMachine.Wpf.Models.LightChart return CurHistoryData.Select(a => new ChartPoint() { Value = a.Cond2Temp, Time = a.CreateTime }).OrderBy(a => a.Time).ToList(); case "EVAP出口温度[℃]": return CurHistoryData.Select(a => new ChartPoint() { Value = a.EVAPExpTemp, Time = a.CreateTime }).OrderBy(a => a.Time).ToList(); - case "冷媒流量[L/min]": + case "冷媒流量[kg/h]": return CurHistoryData.Select(a => new ChartPoint() { Value = a.VRV, Time = a.CreateTime }).OrderBy(a => a.Time).ToList(); - case "润滑油流量[L/min]": + case "润滑油流量[kg/h]": return CurHistoryData.Select(a => new ChartPoint() { Value = a.LubeFlow, Time = a.CreateTime }).OrderBy(a => a.Time).ToList(); case "排气温度[℃]": return CurHistoryData.Select(a => new ChartPoint() { Value = a.ExTemp, Time = a.CreateTime }).OrderBy(a => a.Time).ToList(); diff --git a/CapMachine.Wpf/PPCalculation/DrynessTypes.cs b/CapMachine.Wpf/PPCalculation/DrynessTypes.cs new file mode 100644 index 0000000..16bb567 --- /dev/null +++ b/CapMachine.Wpf/PPCalculation/DrynessTypes.cs @@ -0,0 +1,31 @@ +using System; + +namespace CapMachine.Wpf.PPCalculation +{ + /// + /// 干度计算的工况判定 + /// + public enum DrynessState + { + Unknown = 0, + TwoPhase = 1, + Superheated = 2, + Subcooled = 3, + Supercritical = 4 + } + + /// + /// 干度计算返回结果模型。为实时轮询而设计,包含计算状态与关键物性量。 + /// 单位:H/Hl/Hv 为 J/mol;QMass/QMolar 范围 [0,1];错误信息在 Error。 + /// + public struct DrynessCalcResult + { + public double QMolar; // 摩尔基干度 [0,1],若无效则为 NaN + public double QMass; // 质量基干度 [0,1],若无效则为 NaN + public DrynessState State; + public double H; // 当前态焓 J/mol + public double Hl; // 饱和液相焓 J/mol(同压) + public double Hv; // 饱和气相焓 J/mol(同压) + public string Error; // 错误或提示信息 + } +} diff --git a/CapMachine.Wpf/PPCalculation/IDrynessCalculator.cs b/CapMachine.Wpf/PPCalculation/IDrynessCalculator.cs new file mode 100644 index 0000000..aa3c59f --- /dev/null +++ b/CapMachine.Wpf/PPCalculation/IDrynessCalculator.cs @@ -0,0 +1,22 @@ +using System; + +namespace CapMachine.Wpf.PPCalculation +{ + /// + /// 干度计算服务接口(REFPROP 实现) + /// + public interface IDrynessCalculator + { + /// + /// 计算干度(T-P 闪蒸),输入压力(MPa)与温度(℃)。 + /// 可传入外部的 REFPROP 全局锁以实现跨服务串行化。 + /// + DrynessCalcResult CalcDrynessByTP_MPaC(double pressureMPa, double temperatureC, object externalRefpropLock = null); + + /// + /// 计算干度(T-P 闪蒸),输入压力(BarA)与温度(℃)。 + /// 可传入外部的 REFPROP 全局锁以实现跨服务串行化。 + /// + DrynessCalcResult CalcDrynessByTP_BarA_C(double pressureBarA, double temperatureC, object externalRefpropLock = null); + } +} diff --git a/CapMachine.Wpf/PPCalculation/RefpropDrynessCalculator.cs b/CapMachine.Wpf/PPCalculation/RefpropDrynessCalculator.cs new file mode 100644 index 0000000..af95a4f --- /dev/null +++ b/CapMachine.Wpf/PPCalculation/RefpropDrynessCalculator.cs @@ -0,0 +1,213 @@ +using System; +using CapMachine.Core; + +namespace CapMachine.Wpf.PPCalculation +{ + /// + /// 基于 REFPROP 的干度计算实现。 + /// 注意:REFPROP 为全局状态的非托管库,调用需串行化;可通过 externalRefpropLock 传入外部全局锁。 + /// + public class RefpropDrynessCalculator : IDrynessCalculator + { + private readonly object _internalLock = new object(); + private bool _rpInitialized = false; + private string _rpFluidFile = string.Empty; + + private bool EnsureRefpropInitialized(out string error) + { + error = string.Empty; + + // 从配置中心读取(与 PPCService 保持一致的键名) + string hpath = ConfigHelper.GetValue("FluidsPath"); + if (string.IsNullOrWhiteSpace(hpath)) + hpath = @".\PPCalculation\REFPROP\FLUIDS"; + + string configuredCryogen = ConfigHelper.GetValue("Cryogen"); + if (string.IsNullOrWhiteSpace(configuredCryogen)) configuredCryogen = "R134a"; + + // R134a -> R134A.FLD,其它 -> R1234YF.FLD(与现有实现一致) + string hfldCore = configuredCryogen.Equals("R134a", StringComparison.OrdinalIgnoreCase) + ? "R134A.FLD" + : "R1234YF.FLD"; + + if (_rpInitialized && string.Equals(_rpFluidFile, hfldCore, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + try + { + lock (_internalLock) + { + long size = hpath.Length; + string hpathPadded = hpath + new string(' ', Math.Max(0, 255 - (int)size)); + IRefProp64.SETPATHdll(hpathPadded, ref size); + + long numComps = 1; + string hfld = hfldCore; + size = hfld.Length; + string hfldPadded = hfld + new string(' ', Math.Max(0, 10000 - (int)size)); + + string hfmix = "hmx.bnc" + new string(' ', 255); + string hrf = "DEF"; + string herr = new string(' ', 255); + long ierr = 0; + + long hfldLen = hfldPadded.Length, hfmixLen = hfmix.Length, hrfLen = hrf.Length, herrLen = herr.Length; + IRefProp64.SETUPdll(ref numComps, ref hfldPadded, ref hfmix, ref hrf, ref ierr, ref herr, ref hfldLen, ref hfmixLen, ref hrfLen, ref herrLen); + + if (ierr != 0) + { + error = $"REFPROP 初始化失败: {herr.Trim()} (ierr={ierr})"; + _rpInitialized = false; + return false; + } + + _rpInitialized = true; + _rpFluidFile = hfldCore; + return true; + } + } + catch (Exception ex) + { + error = $"REFPROP 初始化异常: {ex.Message}"; + _rpInitialized = false; + return false; + } + } + + public DrynessCalcResult CalcDrynessByTP_MPaC(double pressureMPa, double temperatureC, object externalRefpropLock = null) + { + var result = new DrynessCalcResult + { + QMolar = double.NaN, + QMass = double.NaN, + State = DrynessState.Unknown, + H = double.NaN, + Hl = double.NaN, + Hv = double.NaN, + Error = string.Empty + }; + + if (!EnsureRefpropInitialized(out string initErr)) + { + result.Error = initErr; + return result; + } + + // 纯工质 + double[] x = new double[20]; + x[0] = 1.0; + + double tK = temperatureC + 273.15; // K + double pKPa = pressureMPa * 1000.0; // MPa -> kPa + + double d = 0, Dl = 0, Dv = 0, q = 0, ee = 0, h = 0, ss = 0, cp = 0, cv = 0, w = 0; + double[] xliq = new double[20]; + double[] xvap = new double[20]; + string herr = new string(' ', 255); + long ierr = 0, ln = 255; + + double tSat = 0, DlSat = 0, DvSat = 0; + string herrSat = new string(' ', 255); + long ierrSat = 0, kph = 1, herrLen = 255; + + double hl = double.NaN, hv = double.NaN, el = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0; + + // 选择锁对象 + object locker = externalRefpropLock ?? _internalLock; + + lock (locker) + { + try + { + // T-P 闪蒸 + IRefProp64.TPFLSHdll(ref tK, ref pKPa, x, ref d, ref Dl, ref Dv, xliq, xvap, ref q, ref ee, ref h, ref ss, ref cv, ref cp, ref w, ref ierr, ref herr, ref ln); + result.H = h; + + if (ierr != 0) + { + result.Error = $"TPFLSH 错误: {herr.Trim()} (ierr={ierr})"; + } + + // 相态判定 + if (q >= 0 && q <= 1) + { + result.State = DrynessState.TwoPhase; + result.QMolar = q; + + // QMASS 转换为质量基 + double qkg = double.NaN, wliq = 0, wvap = 0; + double[] xlkg = new double[20]; + double[] xvkg = new double[20]; + long ierrQ = 0; string herrQ = new string(' ', 255); long lnQ = 255; + IRefProp64.QMASSdll(ref q, xliq, xvap, ref qkg, xlkg, xvkg, ref wliq, ref wvap, ref ierrQ, ref herrQ, ref lnQ); + if (ierrQ == 0) result.QMass = qkg; else result.Error = $"QMASS 错误: {herrQ.Trim()} (ierr={ierrQ})"; + } + else if (q == 999) + { + result.State = DrynessState.Supercritical; + result.Error = "超临界状态,干度无定义"; + } + else if (q > 1) + { + result.State = DrynessState.Superheated; + } + else + { + result.State = DrynessState.Subcooled; + } + + // 求同压饱和焓 Hl/Hv(用于诊断、或在外部回退逻辑使用) + double pForSat = pKPa; + IRefProp64.SATPdll(ref pForSat, x, ref kph, ref tSat, ref DlSat, ref DvSat, xliq, xvap, ref ierrSat, ref herrSat, ref herrLen); + if (ierrSat == 0) + { + // 饱和液焓 + double tSatLocal = tSat; double DlLocal = DlSat; double pOut = 0; + IRefProp64.THERMdll(ref tSatLocal, ref DlLocal, xliq, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt); + // 饱和气焓 + tSatLocal = tSat; double DvLocal = DvSat; pOut = 0; double ev = 0, hvLocal = 0, sv = 0, cvv = 0, cpv = 0, wv = 0, hjtv = 0; + IRefProp64.THERMdll(ref tSatLocal, ref DvLocal, xvap, ref pOut, ref ev, ref hvLocal, ref sv, ref cvv, ref cpv, ref wv, ref hjtv); + + result.Hl = hl; + result.Hv = hvLocal; + + // 保持与现行策略一致:非两相区不回填干度,仅记录诊断 + if (double.IsNaN(result.QMolar) || result.State != DrynessState.TwoPhase) + { + double denom = (hvLocal - hl); + if (Math.Abs(denom) > 1e-6) + { + double qhRaw = (h - hl) / denom; + if (string.IsNullOrWhiteSpace(result.Error)) + { + result.Error = $"非两相态({result.State}),TP回退不提供干度。诊断qhRaw={qhRaw:F4}"; + } + } + else + { + if (string.IsNullOrWhiteSpace(result.Error)) result.Error = "临界点附近或数据异常导致饱和焓差接近 0,无法通过焓差估算干度"; + } + } + } + else + { + if (string.IsNullOrWhiteSpace(result.Error)) result.Error = $"SATP 失败: {herrSat.Trim()} (ierr={ierrSat})"; + } + } + catch (Exception ex) + { + result.Error = $"干度计算异常: {ex.Message}"; + } + } + + return result; + } + + public DrynessCalcResult CalcDrynessByTP_BarA_C(double pressureBarA, double temperatureC, object externalRefpropLock = null) + { + return CalcDrynessByTP_MPaC(pressureBarA / 10.0, temperatureC, externalRefpropLock); + } + } +} diff --git a/CapMachine.Wpf/Services/DataRecordService.cs b/CapMachine.Wpf/Services/DataRecordService.cs index 2bf8b08..7fd2683 100644 --- a/CapMachine.Wpf/Services/DataRecordService.cs +++ b/CapMachine.Wpf/Services/DataRecordService.cs @@ -123,8 +123,8 @@ namespace CapMachine.Wpf.Services new Columns(){ Name="OS2温度[℃]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, new Columns(){ Name="COND2温度[℃]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, new Columns(){ Name="EVAP出口温度[℃]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, - new Columns(){ Name="冷媒流量[L/min]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, - new Columns(){ Name="润滑油流量[L/min]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, + new Columns(){ Name="冷媒流量[kg/h]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, + new Columns(){ Name="润滑油流量[kg/h]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, new Columns(){ Name="排气温度[℃]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, new Columns(){ Name="膨胀阀前压力[BarA]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, new Columns(){ Name="膨胀阀前温度[℃]",MapType=typeof(float).ToString(),IsIdentity=false,IsPrimary=false}, diff --git a/CapMachine.Wpf/Services/PPCService.cs b/CapMachine.Wpf/Services/PPCService.cs index f66735d..a4c05b4 100644 --- a/CapMachine.Wpf/Services/PPCService.cs +++ b/CapMachine.Wpf/Services/PPCService.cs @@ -2,10 +2,16 @@ using CapMachine.Core; using CapMachine.Wpf.Models.PPCalc; using CapMachine.Wpf.Models.Tag; using CapMachine.Wpf.PPCalculation; +using ImTools; +using MaterialDesignThemes.Wpf; using NLog; +using NPOI.XWPF.UserModel; using Prism.Events; using Prism.Mvvm; using Prism.Services.Dialogs; +using SixLabors.ImageSharp.ColorSpaces; +using System.Collections.Generic; +using System.Text; namespace CapMachine.Wpf.Services { @@ -26,6 +32,7 @@ namespace CapMachine.Wpf.Services public ILogService Logger { get; } public MachineRtDataService MachineRtDataService { get; } public IDialogService DialogService { get; } + private readonly IDrynessCalculator _drynessCalculator; /// /// 标签中心 @@ -37,7 +44,8 @@ namespace CapMachine.Wpf.Services /// public PPCService(ConfigService configService, IEventAggregator eventAggregator, DataRecordService dataRecordService, SysRunService sysRunService, ILogService logService, - MachineRtDataService machineRtDataService, IDialogService dialogService) + MachineRtDataService machineRtDataService, IDialogService dialogService, + IDrynessCalculator drynessCalculator) { ConfigService = configService; //事件服务 @@ -47,6 +55,7 @@ namespace CapMachine.Wpf.Services Logger = logService; MachineRtDataService = machineRtDataService; DialogService = dialogService; + _drynessCalculator = drynessCalculator; TagManager = MachineRtDataService.TagManger; //SpeedTag = TagManager.DicTags.GetValueOrDefault("转速[rpm]"); @@ -84,8 +93,8 @@ namespace CapMachine.Wpf.Services { LiqRefFlowTag = LiqRefFlowShortTag!; } - //kg/h L/min - if (TagManager.TryGetShortTagByName("冷媒流量[L/min]", out ShortValueTag? VRVShortTag)) + //kg/h + if (TagManager.TryGetShortTagByName("冷媒流量[kg/h]", out ShortValueTag? VRVShortTag)) { VRVTag = VRVShortTag!; } @@ -100,19 +109,6 @@ namespace CapMachine.Wpf.Services GasPreValveTempTag = gasPreT!; } - //// 可选:气/液支路质量流量(若现场提供则可走“质量流量加权”分支) - //// 为兼容不同命名,尝试多个候选名称 - //if (TagManager.TryGetShortTagByName("气体质量流量[kg/h]", out ShortValueTag? gasFlow1) || - // TagManager.TryGetShortTagByName("气路质量流量[kg/h]", out gasFlow1)) - //{ - // GasMassFlowTag = gasFlow1!; - //} - //if (TagManager.TryGetShortTagByName("液体质量流量[kg/h]", out ShortValueTag? liqFlow1) || - // TagManager.TryGetShortTagByName("液路质量流量[kg/h]", out liqFlow1)) - //{ - // LiquidMassFlowTag = liqFlow1!; - //} - //Cond1TempTag = TagManager.DicTags.GetValueOrDefault("冷凝器出口水温[℃]"); //CondInTempTag = TagManager.DicTags.GetValueOrDefault("冷凝器进口温度[℃]"); @@ -132,21 +128,14 @@ namespace CapMachine.Wpf.Services if (TagManager.TryGetShortTagByName("干度[%]", out ShortValueTag? drynessPct)) { DrynessTag = drynessPct!; - DrynessTagIsPercent = true; - } - else if (TagManager.TryGetShortTagByName("干度[-]", out ShortValueTag? drynessAbs)) - { - DrynessTag = drynessAbs!; - DrynessTagIsPercent = false; } SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath"); SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen"); - //RtScanDeviceStart(); + RtScanDeviceStart(); } - /// /// 当前的配置 /// @@ -172,12 +161,12 @@ namespace CapMachine.Wpf.Services public ShortValueTag InhTempTag { get; set; } /// - /// 膨胀阀前温度 + /// 液体阀前温度 /// public ShortValueTag TxvFrTempTag { get; set; } /// - /// 膨胀阀前压力 + /// 液体阀前压力 /// public ShortValueTag TxvFrPressTag { get; set; } @@ -195,7 +184,7 @@ namespace CapMachine.Wpf.Services /// 干度(无量纲 [-]) /// public ShortValueTag DrynessTag { get; set; } - private bool DrynessTagIsPercent { get; set; } = false; + /// /// 气路阀前压力(BarA) @@ -208,49 +197,17 @@ namespace CapMachine.Wpf.Services public ShortValueTag GasPreValveTempTag { get; set; } /// - /// 气相质量流量(kg/h)。 - /// VRV 安装在气路阀前段,测量值单位为 kg/h,可直接作为 mg(气体质量流量)参与干度加权计算。 - /// 若值<=0,则视为无效,将导致干度计算失败并置0。 + /// 冷媒流量kg/h /// public ShortValueTag VRVTag { get; set; } /// - /// 液体质量流量(kg/h)。 - /// LabVIEW 中该量为常值;此处提供为可配置常量,实时计算直接使用,不再读取 Tag。 - /// 若值<=0,则视为无效,将导致干度计算失败并置0。 - /// 液冷媒流量=液体流量=液体质量流量 + /// 液体流量(kg/h) + /// 液冷媒流量kg/h=液体流量kg/h /// public ShortValueTag LiqRefFlowTag { get; set; } - ///// - ///// 气相质量流量(kg/h),可选 - ///// - //public ShortValueTag GasMassFlowTag { get; set; } - - ///// - ///// 液相质量流量(kg/h),可选 - ///// - //public ShortValueTag LiquidMassFlowTag { get; set; } - - - - - ///// - ///// 过热度 - ///// - //public double Superheat { get; set; } - - ///// - ///// 过冷度 - ///// - //public double Subcool { get; set; } - - ///// - ///// 制冷剂 - ///// - //public string Cryogen { get; set; } = "R134a"; - /// /// 风量数据-乘以系数的后的最终结果 /// @@ -274,7 +231,7 @@ namespace CapMachine.Wpf.Services /// /// 触发日志 /// - private bool DebugLog { get; set; }=false; + private bool DebugLog { get; set; } = false; /// /// PLC扫描线程 @@ -288,108 +245,154 @@ namespace CapMachine.Wpf.Services await Task.Delay(1000); try { + long iErr, kph = 1; + double te = 0.0, te1 = 0.0, p = 0.0, p1 = 0.0, d = 0.0, Dl = 0.0, Dv = 0.0, q = 0.0, h = 0.0, ee = 0.0, hh = 0.0, ss = 0.0, cp = 0.0, cv = 0.0, w = 0.0; double[] x = new double[20], xliq = new double[20], xvap = new double[20]; double[] xlkg = new double[20], xvkg = new double[20]; - double tk = 0.0, wm = 0.0, prevDeltaH = 0.0; - if (!EnsureRefpropInitialized(out string initErrLoop)) + double tk = 0.0, wm = 0.0, prevDeltaH = 0.0;//prevDeltaH 未使用 + + //textBox5.Text = ""; + + string hpath = SuperHeatCoolConfig.FluidsPath; + long size = hpath.Length; + + hpath += new String(' ', 255 - (int)size); + IRefProp64.SETPATHdll(hpath, ref size); + + long numComps = 1;//冷媒个数 + //string hfld = "R1234YF.FLD"; R1234YF + string hfld = ""; + if (SuperHeatCoolConfig.Cryogen == "R134a") { + hfld = "R134A.FLD"; + } + else + { + hfld = "R134A.FLD"; + } + //string hfld = "R134A.FLD"; + //string hfld = Convert.ToString(comboBox1.Text);//从控件获取数据 + size = hfld.Length; + + hfld += new String(' ', 10000 - (int)size); + + //string hfmix = "hmx.bnc" + new String(' ', 255);//原来的 + string hfmix = "hmx.bnc" + new String(' ', 255);//248 + string hrf = "DEF"; + string herr = new String(' ', 255); + + iErr = 0; + long hfldLen = hfld.Length, hfmixLen = hfmix.Length, hrfLen = hrf.Length, herrLen = herr.Length; + + //numComps = -1; + IRefProp64.SETUPdll(ref numComps, ref hfld, ref hfmix, ref hrf, ref iErr, ref herr, ref hfldLen, ref hfmixLen, ref hrfLen, ref herrLen); + + double version = iErr / 10000.0;//错误信息 + if (iErr != 0) + { + //MessageBox.Show(herr, "RefProp SETUPdll() Error ", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + IRefProp64.WMOLdll(x, ref wm); + + //p = Convert.ToDouble(textBox2.Text) * 1000.0;//textBox2 Comp.吸气压力(Mpa) + p = (InhPressTag.PVModel.EngValue) * 1000.0;//textBox2 Comp.吸气压力(Mpa) + kph = 1; + + p1 = (TxvFrPressTag.PVModel.EngValue) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa) + //p1 = Convert.ToDouble(textBox3.Text) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa) + IRefProp64.SATPdll(ref p, x, ref kph, ref te, ref Dl, ref Dv, xliq, xvap, ref iErr, ref herr, ref herrLen); + + if (iErr == 0) + Superheat.PVModel.EngValue = InhTempTag.PVModel.EngValue - (te - 273.15); + //textBox5.Text = String.Format("{0:n4}", Convert.ToDouble(textBox1.Text) - (te - 273.15));//textBox1 Comp.吸气温度(℃) + else Superheat.PVModel.EngValue = 0; + IRefProp64.SATPdll(ref p1, x, ref kph, ref te1, ref Dl, ref Dv, xliq, xvap, ref iErr, ref herr, ref herrLen); + if (iErr == 0) + Subcool.PVModel.EngValue = TxvFrTempTag.PVModel.EngValue - (te1 - 273.15);//textBox4 Evap.膨胀阀前温度(℃) + //GuoLengDu = (te1 - 273.15) - ListKRLogCellValue.Find(a => a.Name == "膨胀阀前温度").Value;//textBox4 Evap.膨胀阀前温度(℃) + //textBox6.Text = String.Format("{0:n4}", Convert.ToDouble(textBox4.Text) - (te1 - 273.15));//textBox4 Evap.膨胀阀前温度(℃) + else Subcool.PVModel.EngValue = 0; - continue; - } - lock (_refpropLock) + //气体流量kg/h=冷媒流量kg/h-液冷媒流量kg/h + var GasFlowKgPerH = VRVTag.PVModel.EngValue - LiqRefFlowTag.PVModel.EngValue;//气体流量kg/h + + //摩尔质量(kg/mol) + var molarMassKgPerMol = GetMolarMass(); + + //定义气相质量焓 kJ/(kg·K) + var Gas_kJkgK = 0.0; + + //步骤1:计算气路阀前气相焓h vap(单相气相) + if (TryTPRHO_VaporDensity_ByTP_MPa_C(GasPreValvePressTag.PVModel.EngValue, GasPreValveTempTag.PVModel.EngValue, out var D_molL, out var D_molLErr1)) { - // 过热度 - if (TryComputeSuperheatK_ByBarA(InhPressTag.PVModel.EngValue, InhTempTag.PVModel.EngValue, out double superheatK, out string shErr)) - Superheat.PVModel.EngValue = superheatK; - else + if (TryTHERM_VaporEntropy_ByTD(GasPreValveTempTag.PVModel.EngValue, D_molL, out var s_kJkgK, out var D_molLErr2)) { - Superheat.PVModel.EngValue = 0; - if (DebugLog) Logger.Error($"过热度计算失败:{shErr}"); - } - - // 过冷度 - if (TryComputeSubcoolK_ByBarA(TxvFrPressTag.PVModel.EngValue, TxvFrTempTag.PVModel.EngValue, out double subcoolK, out string scErr)) - Subcool.PVModel.EngValue = subcoolK; - else - { - Subcool.PVModel.EngValue = 0; - if (DebugLog) Logger.Error($"过冷度计算失败:{scErr}"); - } - - // 干度计算 - var missing = new List(); - if (InhPressTag == null) missing.Add("吸气压力[BarA]"); - if (GasPreValvePressTag == null) missing.Add("气路阀前压力[BarA]"); - if (GasPreValveTempTag == null) missing.Add("气路阀前温度[℃]"); - if (TxvFrPressTag == null) missing.Add("膨胀阀前压力[BarA]"); - if (TxvFrTempTag == null) missing.Add("SUBCOOL出口温度[℃]"); - if (VRVTag == null) missing.Add("气体质量流量[kg/h]"); - if (LiqRefFlowTag == null) missing.Add("液冷媒流量[kg/h]"); - - if (missing.Count > 0) - { - if (DrynessTag != null) DrynessTag.PVModel.EngValue = 0; - if (DebugLog) Logger.Error($"质量流量加权干度:必要测点缺失,已将干度置0。缺失: {string.Join(", ", missing)}"); - } - else - { - string dryErr; - //double ps = InhPressTag.PVModel.EngValue; - //double pg = GasPreValvePressTag.PVModel.EngValue; - //double tg = GasPreValveTempTag.PVModel.EngValue; - //double plv = TxvFrPressTag.PVModel.EngValue; - //double tlv = TxvFrTempTag.PVModel.EngValue; - - double ps = InhPressTag.PVModel.EngValue;//吸气压力 - //ps = 2.374421;//Mpa ->BarA - double pg = GasPreValvePressTag.PVModel.EngValue;//气路阀前压力 - //pg = 19.77032;//Mpa ->BarA - double tg = GasPreValveTempTag.PVModel.EngValue;//气路阀前温度 - //tg = 61.76869; - double plv = TxvFrPressTag.PVModel.EngValue;//膨胀阀前压力=液路阀前压力 - //plv = 19.26757;//Mpa ->BarA - double tlv = TxvFrTempTag.PVModel.EngValue;//膨胀阀前温度 = SUBCOOL出口温度 =液路阀前温度 - //tlv = 64.96428; - - double LiquidMassFlowKgPerH = Math.Max(0.0, LiqRefFlowTag?.PVModel?.EngValue ?? 0.0); - //double LiquidMassFlowKgPerH = 214.3051; - //double GasMassFlowKgPerH = Math.Max(0.0, VRVTag?.PVModel?.EngValue ?? 0.0); - double GasMassFlowKgPerH = (double)(VRVTag?.PVModel?.EngValue - LiquidMassFlowKgPerH); - - // 校验总质量流量 - if ((GasMassFlowKgPerH + LiquidMassFlowKgPerH) <= 0) - { - if (DrynessTag != null) DrynessTag.PVModel.EngValue = 0; - if (DebugLog) Logger.Error("质量流量加权干度:气/液质量流量和<=0,已将干度置0。"); - } - else - { - double dryness = Step_ComputeDryness_LV_FlowWeighted( - GasMassFlowKgPerH, - LiquidMassFlowKgPerH, - ps, pg, tg, plv, tlv, - out dryErr - ); - - if (!double.IsNaN(dryness)) - { - // 物理意义上干度应在 [0,1],写入 Tag 时进行夹紧保护 - double drynessClamped = Math.Min(1.0, Math.Max(0.0, dryness)); - if (DrynessTag != null) - DrynessTag.PVModel.EngValue = DrynessTagIsPercent ? drynessClamped * 100.0 : drynessClamped; - } - else - { - if (DebugLog) Logger.Warn($"质量流量加权干度计算失败:{dryErr};已跳过本次干度写入。输入: mg={GasMassFlowKgPerH:F3} kg/h, ml={LiquidMassFlowKgPerH:F3} kg/h, Ps={ps:F3} BarA, Pg={pg:F3} BarA, Tg={tg:F3} ℃, Pl={plv:F3} BarA, Tl={tlv:F3} ℃"); - } - } + // s_kJkgK 即为图片中的“气相质量熵 kJ/(kg·K)” + Gas_kJkgK = s_kJkgK; } } + + //定义液相质量焓 kJ/(kg·K) + var Liquid_kJkg = 0.0; + + //步骤2:计算液路阀前气相焓hlig(单相液相) TxvFrTempTag 液体阀前温度 TxvFrPressTag 液体阀前压力 + // 1) 先求 D_liq + if (TryTPRHO_LiquidDensity_ByTP_MPa_C(TxvFrPressTag.PVModel.EngValue, TxvFrTempTag.PVModel.EngValue, out var D_liq_molL, out var D_liqErr1)) + { + // 2) 再用 THERM 求 h_liq + if (TryTHERM_LiquidEnthalpy_ByTD(TxvFrTempTag.PVModel.EngValue, D_liq_molL, out var h_liq_kJkg, out var D_liqErr2)) + { + // h_liq_kJkg 即为图片中的“液相质量焓 h_liq kJ/kg” + Liquid_kJkg = h_liq_kJkg; + } + else + { + // 处理 err2 + } + } + else + { + // 处理 err1 + } + + //定义饱和液质量焓hl kJ/kg + var Liquid_h_liq = 0.0; + //定义饱和气质量焓hl k)/kg + var Gas_h_vap = 0.0; + + if (TryGetSaturationLiquidEnthalpy_ByP_MPa(InhPressTag.PVModel.EngValue * 10, out var h_liq, out var h_liqErr1) && + TryGetSaturationVaporEnthalpy_ByP_MPa(InhPressTag.PVModel.EngValue * 10, out var h_vap, out var h_vapErr2)) + { + // h_liq / h_vap 即为图片右侧的两个“饱和液/饱和气 质量焓 kJ/kg” + Liquid_h_liq = h_liq; + Gas_h_vap = h_vap; + } + + // 气相焓 h_vap_kJkg(由 TPRHO 气相 + THERM) + // 液相焓 h_liq_kJkg(由 TPRHO 液相 + THERM) + // 饱和液 / 气焓 h_l / h_v(由 SATP +THERM) + //气/液质量流量 mg/ml + if (TryComputeDrynessByEnthalpy( + Gas_kJkgK, Liquid_kJkg,//气相质量焓 h vap [k/kg] 液相质量焓 h liq [kJ/kg] + GasFlowKgPerH, LiqRefFlowTag.PVModel.EngValue,//气体质量流量 mg [kg/h] 液体质量流量 ml [kg/h] + Liquid_h_liq, Gas_h_vap, //饱和液质量焓 h liq [kJ/kg] 饱和气质量焓 h vap [kJ/kg] + out var GasValue, out var hMix, out var err)) + { + // x 为最终干度 [0..1],hMix 为混合后比焓 + DrynessTag.PVModel.EngValue = GasValue; + } + else + { + // 处理 err + } + } catch (Exception ex) { @@ -399,866 +402,457 @@ namespace CapMachine.Wpf.Services }); } - - #region 过热度和过冷度相关计算 - /// - /// 计算过热度(K)。输入为吸气压力[BarA]与吸气温度[℃],内部以 BarA→kPa 调用 SATP 求饱和温度。 + /// 获取指定组分的摩尔质量(kg/mol) /// - private bool TryComputeSuperheatK_ByBarA(double suctionPressureBarA, double suctionTempC, out double superheatK, out string error) - { - superheatK = double.NaN; error = string.Empty; - if (!EnsureRefpropInitialized(out error)) return false; - double[] x = new double[20]; x[0] = 1.0; - double pKPa = suctionPressureBarA * 100.0; // BarA -> kPa - double tSatK = 0, Dl = 0, Dv = 0; - long kph = 1, ierr = 0, herrLen = 255; - string herr = new string(' ', 255); - lock (_refpropLock) - { - IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSatK, ref Dl, ref Dv, new double[20], new double[20], ref ierr, ref herr, ref herrLen); - } - if (ierr != 0) { error = $"SATP 失败: {herr.Trim()} (ierr={ierr})"; return false; } - double tSatC = tSatK - 273.15; - superheatK = suctionTempC - tSatC; - return true; - } - - /// - /// 计算过冷度(K)。输入为膨胀阀前压力[BarA]与对应温度[℃],内部以 BarA→kPa 调用 SATP 求饱和温度。 - /// - private bool TryComputeSubcoolK_ByBarA(double preValvePressureBarA, double preValveTempC, out double subcoolK, out string error) - { - subcoolK = double.NaN; error = string.Empty; - if (!EnsureRefpropInitialized(out error)) return false; - double[] x = new double[20]; x[0] = 1.0; - double pKPa = preValvePressureBarA * 100.0; // BarA -> kPa - double tSatK = 0, Dl = 0, Dv = 0; - long kph = 1, ierr = 0, herrLen = 255; - string herr = new string(' ', 255); - lock (_refpropLock) - { - IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSatK, ref Dl, ref Dv, new double[20], new double[20], ref ierr, ref herr, ref herrLen); - } - if (ierr != 0) { error = $"SATP 失败: {herr.Trim()} (ierr={ierr})"; return false; } - double tSatC = tSatK - 273.15; - subcoolK = tSatC - preValveTempC; - return true; - } - - /// - /// 过热度和过冷度计算函数调用 风量的调用 - /// - private void REFPROPSum() + /// 组分ID(icomp) + /// 摩尔质量(kg/mol) + public static double GetMolarMass() { + // 初始化所有变量,因为我们将使用ref传递 + double wmm = 0, Trp = 0, Tnbpt = 0, Tc = 0, Pc = 0, Dc = 0, Zc = 0, acf = 0, dip = 0, Rgas = 0; + long componentId = 1; try { + // 调用INFOdll函数获取物性参数,使用ref + IRefProp64.INFOdll(ref componentId, ref wmm, ref Trp, ref Tnbpt, ref Tc, ref Pc, ref Dc, ref Zc, ref acf, ref dip, ref Rgas); - long iErr, kph = 1; - - double te = 0.0, te1 = 0.0, p = 0.0, p1 = 0.0, d = 0.0, Dl = 0.0, Dv = 0.0, q = 0.0, h = 0.0, ee = 0.0, hh = 0.0, ss = 0.0, cp = 0.0, cv = 0.0, w = 0.0; - double[] x = new double[20], xliq = new double[20], xvap = new double[20]; - double[] xlkg = new double[20], xvkg = new double[20]; - - - double tk = 0.0, wm = 0.0, prevDeltaH = 0.0;//prevDeltaH 未使用 - - //textBox5.Text = ""; - - - // 统一初始化(必要时) - if (!EnsureRefpropInitialized(out string initErr)) - { - return; // 初始化失败直接返回,避免无意义计算 - } - - //p = Convert.ToDouble(textBox2.Text) * 1000.0;//textBox2 Comp.吸气压力(Mpa) - //p = (ListRtKPMeter.Find(a => a.MeterName == "吸入压力").RtPV) * 1000.0;//textBox2 Comp.吸气压力(Mpa) - p = (InhPressTag.PVModel.EngValue) * 1000.0;//textBox2 Comp.吸气压力(Mpa) - kph = 1; - - //p1 = (ListKRLogCellValue.Find(a => a.Name == "膨胀阀前压力").Value) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa) - p1 = (TxvFrPressTag.PVModel.EngValue) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa) - //p1 = Convert.ToDouble(textBox3.Text) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa) - - - // 在同一把锁中执行 REFPROP 计算,保证并发安全 - lock (_refpropLock) - { - string herr = new string(' ', 255); long herrLen = 255; iErr = 0; // 确保按 ref 传入前已赋值 - IRefProp64.SATPdll(ref p, x, ref kph, ref te, ref Dl, ref Dv, xliq, xvap, ref iErr, ref herr, ref herrLen); - - if (iErr == 0) - Superheat.PVModel.EngValue = InhTempTag.PVModel.EngValue - (te - 273.15); - else - Superheat.PVModel.EngValue = 0; - - herr = new string(' ', 255); herrLen = 255; iErr = 0; - IRefProp64.SATPdll(ref p1, x, ref kph, ref te1, ref Dl, ref Dv, xliq, xvap, ref iErr, ref herr, ref herrLen); - if (iErr == 0) - Subcool.PVModel.EngValue = (te1 - 273.15) - TxvFrTempTag.PVModel.EngValue; - - else - Subcool.PVModel.EngValue = 0; - } + // 将wmm从g/mol转换为kg/mol(乘以0.001) + double molarMassKgPerMol = wmm * 0.001; + return molarMassKgPerMol; } catch (Exception ex) { - //logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString())); + throw new Exception($"获取组分{componentId}的摩尔质量时出错: {ex.Message}", ex); } } - /// - /// STEP S0(LabVIEW 对应:初始化库) - /// 功能:确保 REFPROP 完成初始化(流体路径/工质/参考态)。 - /// 结果:成功返回 true,否则返回 false 并带出错误信息。 - /// 性能:幂等,实际初始化仅在必要时执行。 - /// - private bool Step_S0_EnsureInitialized(out string error) => EnsureRefpropInitialized(out error); + + // 若类中尚未定义,请添加全局互斥锁,串行化所有 REFPROP 调用 + private static readonly object _refpropLock = new object(); /// - /// STEP Sx(LabVIEW 对应:INFO/WMOL 取摩尔质量) - /// 功能:获取当前工质的摩尔质量 wmm[g/mol]。 - /// 结果:wmm_g_per_mol(g/mol)。 - /// 说明:LabVIEW 常用 INFOdll 取 wmm,本实现用 WMOLdll 等价;纯工质场景下 x[0]=1。 + /// + /// 计算获取 气相密度D_vap mol/l + /// + /// TPRHOdll 封装:按 T(℃)、P(MPa) 计算“气相”摩尔密度 D [mol/L] + /// LabVIEW 对应:kph=2(气相),kguess=0 /// - private bool Step_Sx_GetMolarMass_g_per_mol(out double wmm_g_per_mol, out string error) - { - wmm_g_per_mol = double.NaN; error = string.Empty; - if (!Step_S0_EnsureInitialized(out error)) return false; - double[] x = new double[20]; x[0] = 1.0; - double wm = 0; - lock (_refpropLock) - { - IRefProp64.WMOLdll(x, ref wm); - } - if (wm <= 0) { error = "WMOL 返回无效摩尔质量"; return false; } - wmm_g_per_mol = wm; - return true; - } - - /// - /// STEP S1(LabVIEW 对应:阀前两路“指定相别”的相焓计算) - /// 功能:按给定阀前 T(℃)/P(BarA) 和相别(kph=1液相/2气相)计算相焓 h[kJ/kg]。 - /// 实现:WMOL → wmm[g/mol];TPRHO(T,P,kph) → D;THERM(T,D) → h[J/mol];h[kJ/kg] = h / wmm。 - /// 说明:严格对齐 LabVIEW 的相路径,避免两相边界的相误判。 - /// - private bool Step_S1_GetPhaseHmassByTP_BarAC(double pressureBarA, double temperatureC, long kphPhase, - out double h_kJkg, out string error) - { - h_kJkg = double.NaN; error = string.Empty; - if (!EnsureRefpropInitialized(out error)) return false; - - // 统一输入与数组 - double[] x = new double[20]; x[0] = 1.0; - double wmm = 0; // g/mol - double tK = temperatureC + 273.15; - double pKPa = pressureBarA * 100.0; - double D = 0; long kguess = 0; long ierr = 0; string herr = new string(' ', 255); long ln = 255; - double pOut = 0, el = 0, hl = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0; - - // 相态自适应:当期望液相(kph=1)但温度高于同压饱和或接近相边界时,改用 TPFLSH 或饱和液焓,避免跳变 - const double EpsBandK = 0.4; // 饱和带宽(K) - lock (_refpropLock) - { - IRefProp64.WMOLdll(x, ref wmm); - if (!(wmm > 0)) { error = "WMOL 返回无效摩尔质量"; return false; } - - // 计算当前压力下的饱和温度 - double tSat = 0, DlSat = 0, DvSat = 0; long kphSat = 1; string herrSat = new string(' ', 255); long lnSat = 255; long ierrSat = 0; - double pForSat = pKPa; - IRefProp64.SATPdll(ref pForSat, x, ref kphSat, ref tSat, ref DlSat, ref DvSat, new double[20], new double[20], ref ierrSat, ref herrSat, ref lnSat); - double tSatC = (ierrSat == 0) ? (tSat - 273.15) : double.NaN; - - // 当调用者要求液相,但温度在饱和带内或高于饱和温度时,优先使用 TPFLSH 获取真实相态焓 - if (kphPhase == 1 && !double.IsNaN(tSatC)) - { - double dT = temperatureC - tSatC; - if (dT >= EpsBandK) - { - // 已明显高于饱和:按真实相态计算焓(TPFLSH) - string e2; - if (TryComputeHmassKJkgByTP_BarAC(pressureBarA, temperatureC, out double hMassTPF, out e2)) - { h_kJkg = hMassTPF; return true; } - // 若 TPFLSH 也失败,则退化为气相路径 TPRHO - long kphGas = 2; double tK2 = tK; double pKPa2 = pKPa; double DG = 0; long ierr2 = 0; string herr2 = new string(' ', 255); long ln2 = 255; - IRefProp64.TPRHOdll(ref tK2, ref pKPa2, x, ref kphGas, ref kguess, ref DG, ref ierr2, ref herr2, ref ln2); - if (ierr2 == 0) - { - double tLocal2 = tK2; double DLocal2 = DG; - IRefProp64.THERMdll(ref tLocal2, ref DLocal2, x, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt); - h_kJkg = hl / wmm; return true; - } - // 否则继续走默认液相路径(尽力返回) - } - else if (Math.Abs(dT) <= EpsBandK) - { - // 接近饱和:改用 TPFLSH(T,P) 真实相态焓,避免“直接切换为饱和液焓”导致阶跃 - string e2b; - if (TryComputeHmassKJkgByTP_BarAC(pressureBarA, temperatureC, out double hMassTPF2, out e2b)) - { h_kJkg = hMassTPF2; return true; } - // 若失败,继续默认液相路径 - } - } - - // 默认:按给定 kphPhase 走 TPRHO→THERM(与 LV 路径一致) - IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kphPhase, ref kguess, ref D, ref ierr, ref herr, ref ln); - if (ierr != 0) { error = $"TPRHO 失败: {herr.Trim()} (ierr={ierr})"; return false; } - double tLocal = tK; double DLocal = D; - IRefProp64.THERMdll(ref tLocal, ref DLocal, x, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt); - h_kJkg = hl / wmm; // J/mol ÷ g/mol = J/g = kJ/kg - return true; - } - } - - /// - /// STEP S2(LabVIEW 对应:质量流量加权) - /// 功能:质量流量加权计算混合焓。 - /// 结果:hMix_kJkg。 - /// 限制:m_g + m_l 必须大于 0。 - /// - private bool Step_S2_ComputeMixEnthalpyByFlows(double mg_kg_h, double ml_kg_h, double hV_kJkg, double hL_kJkg, - out double hMix_kJkg, out string error) - { - error = string.Empty; hMix_kJkg = double.NaN; - double sum = mg_kg_h + ml_kg_h; - if (!(sum > 0)) { error = "质量流量和<=0"; return false; } - hMix_kJkg = (mg_kg_h * hV_kJkg + ml_kg_h * hL_kJkg) / sum; - return true; - } - - /// - /// STEP S3(LabVIEW 对应:SATP→THERM 求同压饱和焓) - /// 功能:在给定压力 BarA 下,求饱和液/气的质量比焓。 - /// 实现:SATP(P) → Tsat,Dl,Dv,xliq,xvap;THERM(Tsat,Dl,xliq) 与 THERM(Tsat,Dv,xvap) → hl/hv[J/mol]; - /// WMOL → wmm[g/mol],换算 hl/hv 到 kJ/kg。 - /// - private bool Step_S3_GetSaturationEnthalpies_atPressure_BarA(double pressureBarA, - out double hl_kJkg, out double hv_kJkg, out string error) - { - return TryGetSaturationH_atPressure_MPa(pressureBarA / 10.0, out hl_kJkg, out hv_kJkg, out error); - } - - /// - /// STEP S4(LabVIEW 对应:按焓差计算干度) - /// 功能:x = (h_mix − h_l) / (h_v − h_l),并限幅到 [0,1]。 - /// - private bool Step_S4_ComputeDrynessFromEnthalpies(double hMix_kJkg, double hl_kJkg, double hv_kJkg, - out double x, out string error) - { - x = double.NaN; error = string.Empty; - double denom = hv_kJkg - hl_kJkg; - if (Math.Abs(denom) < 1e-9) { error = "饱和焓差过小,可能临界附近"; return false; } - double val = (hMix_kJkg - hl_kJkg) / denom; - if (double.IsNaN(val) || double.IsInfinity(val)) { error = "干度数值异常"; return false; } - // 不再夹紧到 [0,1],返回原始 x(可能 <0 或 >1),以避免跨界限时的阶跃 - x = val; - return true; - } - - /// - /// 汇总步骤(LabVIEW 主流程,质量流量加权): - /// - S1:阀前气/液状态分别按 T、P 求相焓(kJ/kg) - /// - S2:按质量流量加权混合焓 h_mix(kJ/kg) - /// - S3:在吸气压力下求同压饱和焓 h_l/h_v(kJ/kg) - /// - S4:x = (h_mix − h_l)/(h_v − h_l) - /// 返回:干度 x∈[0,1];失败返回 NaN,并在 error 输出原因。 - /// 性能:所有 REFPROP 调用在锁内,且无多余重复初始化;尽量少堆分配。 - /// - private double Step_ComputeDryness_LV_FlowWeighted( - double mg_kg_h, - double ml_kg_h, - double ps_barA, - double pg_barA, - double tg_C, - double pl_barA, - double tl_C, + /// 气体阀前压力mpa + /// 气体阀前温度 C + /// “气相”摩尔密度 D [mol/L] / 气相密度D_vap mol/l + /// ERR + /// + public bool TryTPRHO_VaporDensity_ByTP_MPa_C( + double pressureMPa, + double temperatureC, + out double densityMolPerL, out string error) { + densityMolPerL = double.NaN; error = string.Empty; - // S1:两路相焓(指定相别:气相=2,液相=1) - if (!Step_S1_GetPhaseHmassByTP_BarAC(pg_barA, tg_C, 2, out double hV_kJkg, out string e1)) { error = $"气路相焓失败: {e1}"; return double.NaN; } - if (!Step_S1_GetPhaseHmassByTP_BarAC(pl_barA, tl_C, 1, out double hL_kJkg, out string e2)) { error = $"液路相焓失败: {e2}"; return double.NaN; } + // 输入换算 + double tK = temperatureC + 273.15; // K + double pKPa = pressureMPa * 1000.0; // MPa -> kPa - // S2:质量流量加权混合焓 - if (!Step_S2_ComputeMixEnthalpyByFlows(mg_kg_h, ml_kg_h, hV_kJkg, hL_kJkg, out double hMix_kJkg, out string e3)) - { error = e3; return double.NaN; } - - // S3:吸气压力同压饱和焓 - if (!Step_S3_GetSaturationEnthalpies_atPressure_BarA(ps_barA, out double hl_kJkg, out double hv_kJkg, out string e4)) - { error = e4; return double.NaN; } - - // S4:干度 - if (!Step_S4_ComputeDrynessFromEnthalpies(hMix_kJkg, hl_kJkg, hv_kJkg, out double x, out string e5)) - { error = e5; return double.NaN; } - - return x; - } - - /// - /// 将体积流量[L/min]换算为质量流量[kg/h]。 - /// 计算公式:m_dot = V_dot[L/min] × D[mol/L] × wmm[g/mol] × (60/1000) = V_dot × D × wmm × 0.06。 - /// 其中 D 由 TPRHO(T,P,kph) 得到,kph=1 液相,kph=2 气相,请按传感器所在管段指定。 - /// 输入压力为 BarA,温度为 ℃。 - /// - private bool TryConvertVolumetricFlowLMinToMassKgPerH(double volFlowLMin, double pressureBarA, double temperatureC, long kphPhase, - out double massFlowKgPerH, out string error) - { - massFlowKgPerH = 0.0; error = string.Empty; - if (volFlowLMin <= 0) { massFlowKgPerH = 0.0; return true; } - if (!EnsureRefpropInitialized(out error)) return false; - - double[] x = new double[20]; x[0] = 1.0; - double wmm = 0; // g/mol - double tK = temperatureC + 273.15; - double pKPa = pressureBarA * 100.0; // BarA -> kPa - double D = 0; long kguess = 0; long ierr = 0; string herr = new string(' ', 255); long ln = 255; - - lock (_refpropLock) - { - IRefProp64.WMOLdll(x, ref wmm); - if (!(wmm > 0)) { error = "WMOL 返回无效摩尔质量"; return false; } - IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kphPhase, ref kguess, ref D, ref ierr, ref herr, ref ln); - } - - if (ierr != 0 || D <= 0) - { - error = $"TPRHO 失败或密度无效: {herr.Trim()} (ierr={ierr})"; - return false; - } - - // 体积流量[L/min] -> 质量流量[kg/h] - massFlowKgPerH = volFlowLMin * D * wmm * 0.06; - if (massFlowKgPerH < 0) massFlowKgPerH = 0; // 保护 - return true; - } - - /// - /// 风量数据的计算 - /// - private void AirVolumeDataSum() - { - //摄氏干球温度 Cd = 12号通道 蒸发器出口风洞温度 单位℃ - //var Cd = ListKRLogCellValue.Find(a => a.Name == "蒸发器出口风洞温度").Value; - var Cd = 40; - - //相对湿度% CRh = 11号通道 蒸发器出口湿度 单位% - //var CRh = ListKRLogCellValue.Find(a => a.Name == "蒸发器出口湿度").Value; - var CRh = 13.3; - - //PKK-零调差压 31号通道(大气压减去测量点的气压)== 静压 单位pa - //var PKK = ListKRLogCellValue.Find(a => a.Name == "静压").Value; - var PKK = 0.9; - - //BPRES - 测量的大气压 kPa:常数 kPa BPRES= 101.325Kpa - var BPRES = 101.325; - - //CDP - 测量到的喷嘴差压(Pa) 记录仪3号通道 蒸発风道差圧 单位pa - //var CDP = ListKRLogCellValue.Find(a => a.Name == "蒸発风道差圧").Value; - var CDP = 44; - - //prmAirFlowFactor_i--喷嘴修正系数:设置为默认1.01 - var prmAirFlowFactor_i = 1.01; - - AirVolumeData = AirCALCHepler.CVFCALC(Cd, CRh, PKK, BPRES, CDP, prmAirFlowFactor_i) * 60; - - AirVolumeDataSource = AirVolumeData; - - //系数的转换 - //AirCALCHepler.ListRozzle - //RozzleEnableInfo - - if (AirVolumeData >= 200 && AirVolumeData < 250) - { - if (RozzleEnableInfo.Contains("[40]") && RozzleEnableInfo.Contains("[50]") && !RozzleEnableInfo.Contains("[80]")) - { - AirVolumeData = AirVolumeData * 0.95; - } - } - - if (AirVolumeData >= 250 && AirVolumeData < 300) - { - if (RozzleEnableInfo.Contains("[40]") && RozzleEnableInfo.Contains("[50]") && !RozzleEnableInfo.Contains("[80]")) - { - AirVolumeData = AirVolumeData * 1.05; - } - } - - if (AirVolumeData >= 300 && AirVolumeData < 400) - { - if (!RozzleEnableInfo.Contains("[40]") && !RozzleEnableInfo.Contains("[50]") && RozzleEnableInfo.Contains("[80]")) - { - AirVolumeData = AirVolumeData * 1.125; - } - } - - if (AirVolumeData >= 400 && AirVolumeData < 600) - { - if (RozzleEnableInfo.Contains("[40]") && !RozzleEnableInfo.Contains("[50]") && RozzleEnableInfo.Contains("[80]")) - { - AirVolumeData = AirVolumeData * 1.114; - } - } - - //Console.WriteLine($"时间:{DateTime.Now.ToString()}摄氏干球温度 单位℃:{Cd} 相对湿度 单位%:{CRh}-零调差压 单位pa:{PKK}-大气压 单位Kpa:{BPRES}-喷嘴差压 单位Pa:{CDP}-喷嘴修正系数:{prmAirFlowFactor_i} 80通道风量结果:{Result}"); - - } - - #endregion - - - #region 干度计算相关 - - /* - * 干度计算——实现说明(阅读指引) - * - * 一、目标 - * 在实时测试场景下,根据测得的温度(℃)与压力(可为 MPa 或 BarA), - * 通过 REFPROP 库计算工质当前状态的“干度”(两相区内蒸汽相所占比例,0~1)。 - * - * 二、单位约定 - * - REFPROP 要求:T[K],P[kPa] - * - 本实现对外接口: - * 1) CalcDrynessByTP_MPaC / GetDrynessByTP 接受 MPa、℃ - * 2) CalcDrynessByTP_BarA_C / GetDrynessByTP_BarA 接受 BarA、℃,内部转换为 MPa - * - 注意与现有“过热度/过冷度”实现的单位保持一致,避免歧义 - * - * 三、核心流程 - * STEP 0:初始化 REFPROP(路径/工质/参考态),仅在必要时执行(幂等) - * STEP 1:使用 TPFLSHdll 做 T-P 闪蒸,获取:密度、焓 h、摩尔基干度 q 等 - * STEP 2:按 q 判断相态: - * - 0<=q<=1:两相区,调用 QMASSdll 将 q(摩尔基)转换为质量基干度 - * - q>1:过热蒸汽;q<0:过冷液体;q==999:超临界(干度无定义) - * STEP 3:计算同压饱和液/气焓(SATPdll -> THERMdll)得到 Hl/Hv - * STEP 4:若不在两相区,则按 h=(1-x)*Hl + x*Hv 反推干度 x,并限幅到 [0,1] - * - * 四、并发与鲁棒性 - * - _refpropLock:所有 REFPROP 调用均串行化,避免非托管库内部状态竞态 - * - 异常和错误码:捕获异常并透传可读错误文本到 DrynessCalcResult.Error - * - 临界点附近:当 Hv≈Hl 时进行防护,避免数值不稳定 - * - * 五、典型用法 - * - 仅取数值:GetDrynessByTP / GetDrynessByTP_BarA(返回 [0,1] 或 NaN) - * - 取完整诊断信息:CalcDrynessByTP_MPaC / CalcDrynessByTP_BarA_C - * - 基于当前标签:CalcDrynessAtInlet(使用现有“吸气压力/吸气温度”标签) - */ - - /// - /// 干度计算返回结果模型。为实时轮询而设计,包含计算状态与关键物性量。 - /// - public struct DrynessCalcResult - { - public double QMolar; // 摩尔基干度 [0,1],若无效则为 NaN - public double QMass; // 质量基干度 [0,1],若无效则为 NaN - public DrynessState State; - public double H; // 当前态焓 J/mol - public double Hl; // 饱和液相焓 J/mol(同压) - public double Hv; // 饱和气相焓 J/mol(同压) - public string Error; // 错误或提示信息 - } - - /// - /// 干度计算的工况判定 - /// - public enum DrynessState - { - Unknown = 0, - TwoPhase = 1, - Superheated = 2, - Subcooled = 3, - Supercritical = 4 - } - - /// - /// REFPROP 调用的并发锁。由于 REFPROP 为非托管库且内部通常为全局状态, - /// 在多线程/高频轮询调用时建议串行化访问,避免竞态与资源冲突。 - /// - private readonly object _refpropLock = new object(); - - /// - /// REFPROP 初始化标记,避免重复 SETUP 开销 - /// - private bool _rpInitialized = false; - - /// - /// 已初始化的流体文件名(例如 R134A.FLD) - /// - private string _rpFluidFile = string.Empty; - - /// - /// 确保 REFPROP 完成初始化:设置流体路径、装载工质并建立参考态。 - /// 该方法具备幂等性,多次调用只在必要时重新初始化。 - /// - private bool EnsureRefpropInitialized(out string error) - { - error = string.Empty; - - string hpath = string.IsNullOrWhiteSpace(SuperHeatCoolConfig?.FluidsPath) - ? @".\PPCalculation\REFPROP\FLUIDS" - : SuperHeatCoolConfig.FluidsPath; - - // 与 REFPROPSum 中逻辑保持一致:R134a -> R134A.FLD,其它 -> R1234YF.FLD - string hfldCore = (SuperHeatCoolConfig?.Cryogen ?? "R134a").Equals("R134a", StringComparison.OrdinalIgnoreCase) - ? "R134A.FLD" - : "R1234YF.FLD"; - - if (_rpInitialized && string.Equals(_rpFluidFile, hfldCore, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - try - { - lock (_refpropLock) - { - long size = hpath.Length; - string hpathPadded = hpath + new string(' ', Math.Max(0, 255 - (int)size)); - IRefProp64.SETPATHdll(hpathPadded, ref size); - - long numComps = 1; - string hfld = hfldCore; - size = hfld.Length; - string hfldPadded = hfld + new string(' ', Math.Max(0, 10000 - (int)size)); - - string hfmix = "hmx.bnc" + new string(' ', 255); - string hrf = "DEF"; - string herr = new string(' ', 255); - long ierr = 0; - - long hfldLen = hfldPadded.Length, hfmixLen = hfmix.Length, hrfLen = hrf.Length, herrLen = herr.Length; - IRefProp64.SETUPdll(ref numComps, ref hfldPadded, ref hfmix, ref hrf, ref ierr, ref herr, ref hfldLen, ref hfmixLen, ref hrfLen, ref herrLen); - - if (ierr != 0) - { - error = $"REFPROP 初始化失败: {herr.Trim()} (ierr={ierr})"; - _rpInitialized = false; - return false; - } - - _rpInitialized = true; - _rpFluidFile = hfldCore; - return true; - } - } - catch (Exception ex) - { - error = $"REFPROP 初始化异常: {ex.Message}"; - _rpInitialized = false; - return false; - } - } - - /// - /// 计算干度(T-P 闪蒸),输入为压力(MPa)与温度(℃)。 - /// 调用逻辑: - /// 1) 通过 TPFLSHdll 获取整体态性质与摩尔基干度 q; - /// 2) 若 q∈[0,1],使用 QMASSdll 转换为质量基干度; - /// 3) 若 q 不在 [0,1](过冷/过热/超临界等),则以同压饱和液/气焓为基准,按 h=(1-x)*hl+x*hv 反推 x(并限幅到 [0,1])。 - /// - public DrynessCalcResult CalcDrynessByTP_MPaC(double pressureMPa, double temperatureC) - { - var result = new DrynessCalcResult - { - QMolar = double.NaN, - QMass = double.NaN, - State = DrynessState.Unknown, - H = double.NaN, - Hl = double.NaN, - Hv = double.NaN, - Error = string.Empty - }; - - // STEP 0:初始化 REFPROP(路径/工质/参考态),幂等 - if (!EnsureRefpropInitialized(out string initErr)) - { - result.Error = initErr; - return result; - } - - // 纯工质摩尔分数:x[0]=1 + // 纯工质:x[0]=1 double[] x = new double[20]; x[0] = 1.0; - // 外部温度(℃) -> K;压力(MPa) -> kPa - double tK = temperatureC + 273.15; + // TPRHO 参数 + long kph = 2; // 气相 + long kguess = 0; // 让例程自行选择初值 + double D = 0.0; // 输出:mol/L + long ierr = 0; + long herrLen = 255; + string herr = new string(' ', 255); + + lock (_refpropLock) + { + IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kph, ref kguess, ref D, ref ierr, ref herr, ref herrLen); + } + + if (ierr != 0) + { + error = $"TPRHO 错误: {herr.Trim()} (ierr={ierr})"; + return false; + } + + densityMolPerL = D; // mol/L + return true; + } + + + /// + /// 计算气路阀前气相焓h_vap(单相气相) + /// + /// THERMdll 封装:按 T(℃) 与上一步得到的 D[mol/L] 计算气相熵 s [kJ/(kg·K)] + /// LabVIEW 对应:THERM(T, D) -> s[J/(mol·K)],再 /M[kg/mol] 并 *0.001 + /// + /// 气体阀前温度 C + /// 气相密度D_vap mol/l + /// + /// + /// + public bool TryTHERM_VaporEntropy_ByTD( + double temperatureC, + double densityMolPerL, + out double entropy_kJ_per_kgK, + out string error) + { + entropy_kJ_per_kgK = double.NaN; + error = string.Empty; + + // 输入换算 + double tK = temperatureC + 273.15; // K + double D = densityMolPerL; // mol/L + + // 纯工质:x[0]=1 + double[] x = new double[20]; + x[0] = 1.0; + + // THERM 输出 + double pOut = 0, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; + + // 摩尔质量(kg/mol)——使用你已封装的 GetMolarMass() + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + lock (_refpropLock) + { + IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); + // THERM 不返回 ierr,如输入越界会在上游 TPRHO 就失败 + } + + // J/(mol·K) -> kJ/(kg·K):先除以 kg/mol,再乘 0.001 + entropy_kJ_per_kgK = (sJmolK / molarMassKgPerMol) * 0.001; + return true; + } + + + /// + /// + /// 计算液路阀前液相密度D_liq(单相液相) 液相密度D_liq mol/L + /// + /// TPRHOdll 封装(液相):按 T(℃)、P(MPa) 计算“液相”摩尔密度 D_liq [mol/L] + /// LabVIEW 对应:kph=1(液相),kguess=0 + /// + /// + /// + /// + /// + /// + public bool TryTPRHO_LiquidDensity_ByTP_MPa_C( + double pressureMPa, + double temperatureC, + out double densityMolPerL, + out string error) + { + densityMolPerL = double.NaN; + error = string.Empty; + + // 输入换算 + double tK = temperatureC + 273.15; // K + double pKPa = pressureMPa * 1000.0; // MPa -> kPa + + // 纯工质:x[0]=1 + double[] x = new double[20]; + x[0] = 1.0; + + // TPRHO 参数 + long kph = 1; // 液相 + long kguess = 0; // 初值由例程选择 + double D = 0.0; // 输出:mol/L + long ierr = 0; + long herrLen = 255; + string herr = new string(' ', 255); + + lock (_refpropLock) + { + IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kph, ref kguess, ref D, ref ierr, ref herr, ref herrLen); + } + + if (ierr != 0) + { + error = $"TPRHO(液相) 错误: {herr.Trim()} (ierr={ierr})"; + return false; + } + + densityMolPerL = D; // mol/L + return true; + } + + + /// + /// 计算获取 液相质量焓h lig k/kg + /// THERMdll 封装(液相):按 T(℃) 与 D_liq[mol/L] 计算液相质量焓 h_liq [kJ/kg] + /// LabVIEW 对应:THERM(T, D) -> h[J/mol],再 /M[kg/mol] * 0.001 + /// + /// 液体阀前温度'℃ + /// 液相密度D_liq mol/L + /// 液相质量焓h lig k/kg + /// Err + /// + public bool TryTHERM_LiquidEnthalpy_ByTD( + double temperatureC, + double densityMolPerL, + out double h_liq_kJ_per_kg, + out string error) + { + h_liq_kJ_per_kg = double.NaN; + error = string.Empty; + + // 输入换算 + double tK = temperatureC + 273.15; // K + double D = densityMolPerL; // mol/L + + // 纯工质:x[0]=1 + double[] x = new double[20]; + x[0] = 1.0; + + // THERM 输出 + double pOut = 0, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; + + // 摩尔质量(kg/mol)——使用已封装的 GetMolarMass() + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + lock (_refpropLock) + { + IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); + // THERM 不返回 ierr,越界一般在上游 TPRHO 即失败 + } + + // J/mol -> kJ/kg:先除以 kg/mol,再乘 0.001 + h_liq_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; + return true; + } + + + /// + /// SATPdll 封装:由压力 P(MPa) 求饱和温度 Tsat[K]、饱和液/气摩尔密度 Dl/Dv [mol/L] + /// LabVIEW 对应:P(MPa)*1000 -> kPa;kph=1(bubble) + /// + /// 王缩机吸气压力mpa + /// 吸气压力下的饱和温度T_sat K + /// 饱和液相密度Dl molL + /// 饱和气相密度Dv mol/L + /// + /// + public bool TrySATP_SaturationByP_MPa( + double pressureMPa, + out double tSatK, + out double Dl_molL, + out double Dv_molL, + out string error) + { + tSatK = double.NaN; + Dl_molL = double.NaN; + Dv_molL = double.NaN; + error = string.Empty; + double pKPa = pressureMPa * 1000.0; // MPa -> kPa + double[] x = new double[20]; x[0] = 1.0; - double d = 0, Dl = 0, Dv = 0, q = 0, ee = 0, h = 0, ss = 0, cp = 0, cv = 0, w = 0; + long kph = 1; // 饱和液侧(bubble),纯工质下 dew/bubble 的 Tsat 一致 + double Dl = 0, Dv = 0; double[] xliq = new double[20]; double[] xvap = new double[20]; + long ierr = 0, herrLen = 255; string herr = new string(' ', 255); - long ierr = 0, ln = 255; - - double tSat = 0, DlSat = 0, DvSat = 0; - string herrSat = new string(' ', 255); - long ierrSat = 0, kph = 1, herrLen = 255; - - double hl = double.NaN, hv = double.NaN, el = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0; lock (_refpropLock) { - try - { - // STEP 1:T-P 闪蒸(核心调用)——获取当前态的性质与摩尔基干度 q - IRefProp64.TPFLSHdll(ref tK, ref pKPa, x, ref d, ref Dl, ref Dv, xliq, xvap, ref q, ref ee, ref h, ref ss, ref cv, ref cp, ref w, ref ierr, ref herr, ref ln); - result.H = h; - - if (ierr != 0) - { - result.Error = $"TPFLSH 错误: {herr.Trim()} (ierr={ierr})"; - } - - // STEP 2:依据 q 判断相态,并在两相区内获取质量基干度 - if (q >= 0 && q <= 1) - { - result.State = DrynessState.TwoPhase; - result.QMolar = q; - - double qkg = double.NaN, wliq = 0, wvap = 0; - double[] xlkg = new double[20]; - double[] xvkg = new double[20]; - long ierrQ = 0; string herrQ = new string(' ', 255); long lnQ = 255; - IRefProp64.QMASSdll(ref q, xliq, xvap, ref qkg, xlkg, xvkg, ref wliq, ref wvap, ref ierrQ, ref herrQ, ref lnQ); - if (ierrQ == 0) result.QMass = qkg; else result.Error = $"QMASS 错误: {herrQ.Trim()} (ierr={ierrQ})"; - } - else if (q == 999) - { - result.State = DrynessState.Supercritical; - result.Error = "超临界状态,干度无定义"; - } - else if (q > 1) - { - result.State = DrynessState.Superheated; - } - else - { - result.State = DrynessState.Subcooled; - } - - // STEP 3:计算同压饱和点,并求取饱和液/气焓 Hl/Hv - double pForSat = pKPa; - IRefProp64.SATPdll(ref pForSat, x, ref kph, ref tSat, ref DlSat, ref DvSat, xliq, xvap, ref ierrSat, ref herrSat, ref herrLen); - if (ierrSat == 0) - { - // 饱和液焓 - double tSatLocal = tSat; double DlLocal = DlSat; double pOut = 0; - IRefProp64.THERMdll(ref tSatLocal, ref DlLocal, xliq, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt); - // 饱和气焓 - tSatLocal = tSat; double DvLocal = DvSat; pOut = 0; double ev = 0, hvLocal = 0, sv = 0, cvv = 0, cpv = 0, wv = 0, hjtv = 0; - IRefProp64.THERMdll(ref tSatLocal, ref DvLocal, xvap, ref pOut, ref ev, ref hvLocal, ref sv, ref cvv, ref cpv, ref wv, ref hjtv); - - result.Hl = hl; - result.Hv = hvLocal; - - // STEP 4:若不在两相区,依据焓差回推干度并限幅 - if (double.IsNaN(result.QMolar) || result.State != DrynessState.TwoPhase) - { - double denom = (hvLocal - hl); - if (Math.Abs(denom) > 1e-6) - { - double qh = (h - hl) / denom; - qh = Math.Min(1.0, Math.Max(0.0, qh)); - if (double.IsNaN(result.QMolar)) result.QMolar = qh; - if (double.IsNaN(result.QMass)) result.QMass = qh; // 纯工质近似 - if (string.IsNullOrWhiteSpace(result.Error)) result.Error = "非两相态,基于焓差的干度估算(已限幅)"; - } - else - { - if (string.IsNullOrWhiteSpace(result.Error)) result.Error = "临界点附近或数据异常导致饱和焓差接近 0,无法通过焓差估算干度"; - } - } - } - else - { - if (string.IsNullOrWhiteSpace(result.Error)) result.Error = $"SATP 失败: {herrSat.Trim()} (ierr={ierrSat})"; - } - } - catch (Exception ex) - { - result.Error = $"干度计算异常: {ex.Message}"; - } + IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSatK, ref Dl, ref Dv, xliq, xvap, ref ierr, ref herr, ref herrLen); } - return result; - } - - /// - /// 便捷接口:仅返回干度数值 [0,1]。默认返回质量基干度;若质量基无效,则回退摩尔基; - /// 若均无效则返回 NaN,并可通过 获取详细错误原因。 - /// - /// 压力(MPa) - /// 温度(℃) - /// 是否优先返回质量基干度(默认 true) - public double GetDrynessByTP(double pressureMPa, double temperatureC, bool preferMassBasis = true) - { - var r = CalcDrynessByTP_MPaC(pressureMPa, temperatureC); - if (preferMassBasis) + if (ierr != 0) { - if (!double.IsNaN(r.QMass)) return r.QMass; - if (!double.IsNaN(r.QMolar)) return r.QMolar; + error = $"SATP 错误: {herr.Trim()} (ierr={ierr})"; + return false; } - else - { - if (!double.IsNaN(r.QMolar)) return r.QMolar; - if (!double.IsNaN(r.QMass)) return r.QMass; - } - return double.NaN; + + Dl_molL = Dl; + Dv_molL = Dv; + return true; } - /// - /// 便捷接口(BarA 压力输入):仅返回干度数值 [0,1]。 - /// 内部将 BarA 转为 MPa(1 bar = 0.1 MPa)。 - /// - public double GetDrynessByTP_BarA(double pressureBarA, double temperatureC, bool preferMassBasis = true) - { - return GetDrynessByTP(pressureBarA / 10.0, temperatureC, preferMassBasis); - } /// - /// 详细结果版本(BarA 压力输入):返回包含状态与焓信息的结构体。 - /// 内部将 BarA 转为 MPa(1 bar = 0.1 MPa)。 + /// THERMdll 封装:由 T[K] 与 D[mol/L] 计算质量比焓 h[kJ/kg] + /// LabVIEW 对应:THERM(T, D) -> h[J/mol],再 /M[kg/mol] * 0.001 /// - public DrynessCalcResult CalcDrynessByTP_BarA_C(double pressureBarA, double temperatureC) + /// + /// + /// + /// + /// + public bool TryTHERM_Enthalpy_kJkg_ByT_K_D( + double temperatureK, + double densityMolPerL, + out double h_kJ_per_kg, + out string error) { - return CalcDrynessByTP_MPaC(pressureBarA / 10.0, temperatureC); - } + h_kJ_per_kg = double.NaN; + error = string.Empty; - /// - /// LabVIEW 对齐版本:基于混合焓与同压饱和焓计算干度(质量基)。 - /// 步骤: - /// 1) TPFLSH(t,p) 获取总体焓 h_mix[J/mol];WMOL 得到 w_m[g/mol];换算 h_mix[kJ/kg] = h_mix[J/mol] / w_m[g/mol] - /// 2) SATP(p) 得到饱和温度 T_sat、饱和液/气密度 Dl/Dv;THERM(T_sat, Dl/Dv) 分别得到 hl/hv[J/mol] - /// 换算 hl_mass/hv_mass[kJ/kg] = hl[J/mol]/w_m[g/mol], hv[J/mol]/w_m[g/mol] - /// 3) x = (h_mix - h_l_mass) / (h_v_mass - h_l_mass),并限幅到 [0,1] - /// 注意:单位 MPa/℃ 输入;内部统一转换为 kPa/K。纯工质场景下 molar mass 为常数。 - /// - public double GetDrynessByTP_LV(double pressureMPa, double temperatureC) - { - if (!EnsureRefpropInitialized(out string initErr)) return double.NaN; + double tK = temperatureK; + double D = densityMolPerL; double[] x = new double[20]; x[0] = 1.0; - double tK = temperatureC + 273.15; - double pKPa = pressureMPa * 1000.0; - double d = 0, Dl = 0, Dv = 0, q = 0, ee = 0, h = 0, ss = 0, cp = 0, cv = 0, w = 0; - double[] xliq = new double[20]; - double[] xvap = new double[20]; - string herr = new string(' ', 255); - long ierr = 0, ln = 255; + double pOut = 0, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; - double wm = 0; // g/mol - double tSat = 0, DlSat = 0, DvSat = 0; long ierrSat = 0, kph = 1, herrLen = 255; string herrSat = new string(' ', 255); - double pOut = 0, el = 0, hl = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0; - double ev = 0, hv = 0, sv = 0, cvv = 0, cpv = 0, wv = 0, hjtv = 0; + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } lock (_refpropLock) { - // 质量摩尔质量 g/mol(纯工质恒定) - IRefProp64.WMOLdll(x, ref wm); - - // 1) TPFLSH 得 h_mix[J/mol] - IRefProp64.TPFLSHdll(ref tK, ref pKPa, x, ref d, ref Dl, ref Dv, xliq, xvap, ref q, ref ee, ref h, ref ss, ref cv, ref cp, ref w, ref ierr, ref herr, ref ln); - if (ierr != 0) return double.NaN; - double h_mix_kJkg = h / wm; // 见单位换算推导:J/mol ÷ g/mol = J/g = kJ/kg - - // 2) SATP+THERM 得到同压饱和 hl/hv[J/mol] - double pForSat = pKPa; ierrSat = 0; - IRefProp64.SATPdll(ref pForSat, x, ref kph, ref tSat, ref DlSat, ref DvSat, xliq, xvap, ref ierrSat, ref herrSat, ref herrLen); - if (ierrSat != 0) return double.NaN; - - double tLocal = tSat; double DlLocal = DlSat; pOut = 0; - IRefProp64.THERMdll(ref tLocal, ref DlLocal, xliq, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt); - tLocal = tSat; double DvLocal = DvSat; pOut = 0; - IRefProp64.THERMdll(ref tLocal, ref DvLocal, xvap, ref pOut, ref ev, ref hv, ref sv, ref cvv, ref cpv, ref wv, ref hjtv); - - double hl_kJkg = hl / wm; - double hv_kJkg = hv / wm; - - double denom = hv_kJkg - hl_kJkg; - if (Math.Abs(denom) < 1e-9) return double.NaN; - double xdry = (h_mix_kJkg - hl_kJkg) / denom; - // 限幅 [0,1] - if (double.IsNaN(xdry) || double.IsInfinity(xdry)) return double.NaN; - return Math.Max(0.0, Math.Min(1.0, xdry)); + IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); } + + h_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; // J/mol -> kJ/kg + return true; + } + + + /// + /// 便捷:由压力 P(MPa) 直接得到“饱和液”质量比焓 h_liq[kJ/kg] + /// 流程:SATP(P)->Tsat/Dl,再 THERM(Tsat, Dl) + /// + /// + /// + /// + /// + public bool TryGetSaturationLiquidEnthalpy_ByP_MPa( + double pressureMPa, + out double h_liq_kJkg, + out string error) + { + h_liq_kJkg = double.NaN; + error = string.Empty; + + if (!TrySATP_SaturationByP_MPa(pressureMPa, out double tSatK, out double Dl, out _, out error)) + return false; + + return TryTHERM_Enthalpy_kJkg_ByT_K_D(tSatK, Dl, out h_liq_kJkg, out error); } /// - /// 计算指定 T(℃)/P(MPa) 下的质量比焓 h[kJ/kg](TPFLSH + WMOL 换算)。 + /// 便捷:由压力 P(MPa) 直接得到“饱和气”质量比焓 h_vap[kJ/kg] + /// 流程:SATP(P)->Tsat/Dv,再 THERM(Tsat, Dv) /// - private bool TryComputeHmassKJkgByTP_MPaC(double pressureMPa, double temperatureC, out double h_kJkg, out string error) + /// + /// + /// + /// + public bool TryGetSaturationVaporEnthalpy_ByP_MPa( + double pressureMPa, + out double h_vap_kJkg, + out string error) { - h_kJkg = double.NaN; error = string.Empty; - if (!EnsureRefpropInitialized(out error)) return false; - double[] x = new double[20]; x[0] = 1.0; - double tK = temperatureC + 273.15; - double pKPa = pressureMPa * 1000.0; - double d = 0, Dl = 0, Dv = 0, q = 0, ee = 0, h = 0, ss = 0, cp = 0, cv = 0, w = 0; long ierr = 0, ln = 255; - string herr = new string(' ', 255); - double wm = 0; - lock (_refpropLock) + h_vap_kJkg = double.NaN; + error = string.Empty; + + if (!TrySATP_SaturationByP_MPa(pressureMPa, out double tSatK, out _, out double Dv, out error)) + return false; + + return TryTHERM_Enthalpy_kJkg_ByT_K_D(tSatK, Dv, out h_vap_kJkg, out error); + } + + /// + /// 按图片的最终流程计算干度: + /// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml) + /// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1] + /// + /// 入参单位: + /// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg + /// - mGas_kg_h, mLiq_kg_h 均为 kg/h + /// 返回:true 表示成功,输出 x∈[0,1] 与 h_mix;false 返回 error + /// + /// 气相质量焓 h_vap [kJ/kg] + /// 液相质量焓 h_liq [kJ/kg] + /// 气体质量流量 mg [kg/h] + /// 液体质量流量 ml [kg/h] + /// 饱和液质量焓 h_l [kJ/kg] + /// 饱和气质量焓 h_v [kJ/kg] + /// 输出干度 x ∈ [0,1] + /// 输出混合后总比焓 h_mix [kJ/kg] + /// Err + /// + public bool TryComputeDrynessByEnthalpy( + double hVap_kJkg, // 气相质量焓 h_vap [kJ/kg] + double hLiq_kJkg, // 液相质量焓 h_liq [kJ/kg] + double mGas_kg_h, // 气体质量流量 mg [kg/h] + double mLiq_kg_h, // 液体质量流量 ml [kg/h] + double hSatL_kJkg, // 饱和液质量焓 h_l [kJ/kg] + double hSatV_kJkg, // 饱和气质量焓 h_v [kJ/kg] + out double dryness, // 输出干度 x ∈ [0,1] + out double hMix_kJkg, // 输出混合后总比焓 h_mix [kJ/kg] + out string error) + { + dryness = double.NaN; + hMix_kJkg = double.NaN; + error = string.Empty; + + // 1) 合法性校验 + if (double.IsNaN(hVap_kJkg) || double.IsNaN(hLiq_kJkg) || double.IsNaN(hSatL_kJkg) || double.IsNaN(hSatV_kJkg)) { - IRefProp64.WMOLdll(x, ref wm); - IRefProp64.TPFLSHdll(ref tK, ref pKPa, x, ref d, ref Dl, ref Dv, new double[20], new double[20], ref q, ref ee, ref h, ref ss, ref cv, ref cp, ref w, ref ierr, ref herr, ref ln); - if (ierr != 0) { error = $"TPFLSH 失败: {herr.Trim()} (ierr={ierr})"; return false; } - if (Math.Abs(wm) < 1e-12) { error = "WMOL 返回 0(无效摩尔质量)"; return false; } - h_kJkg = h / wm; // J/mol ÷ g/mol = J/g = kJ/kg - return true; + error = "输入焓值存在 NaN"; + return false; } - } - - /// - /// 计算指定 T(℃)/P(BarA) 下的质量比焓 h[kJ/kg](便捷包装)。 - /// - private bool TryComputeHmassKJkgByTP_BarAC(double pressureBarA, double temperatureC, out double h_kJkg, out string error) - { - return TryComputeHmassKJkgByTP_MPaC(pressureBarA / 10.0, temperatureC, out h_kJkg, out error); - } - - /// - /// 计算给定压力(MPa)下的同压饱和液/气焓(质量基,kJ/kg)。 - /// - private bool TryGetSaturationH_atPressure_MPa(double pressureMPa, out double hl_kJkg, out double hv_kJkg, out string error) - { - hl_kJkg = double.NaN; hv_kJkg = double.NaN; error = string.Empty; - if (!EnsureRefpropInitialized(out error)) return false; - double[] x = new double[20]; x[0] = 1.0; - double[] xliq = new double[20]; - double[] xvap = new double[20]; - double wm = 0; long kph = 1; string herrSat = new string(' ', 255); long herrLen = 255; long ierrSat = 0; - double pKPa = pressureMPa * 1000.0; double tSat = 0, DlSat = 0, DvSat = 0; - double pOut = 0, el = 0, hl = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0; - double ev = 0, hv = 0, sv = 0, cvv = 0, cpv = 0, wv = 0, hjtv = 0; - lock (_refpropLock) + if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h)) { - IRefProp64.WMOLdll(x, ref wm); - IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSat, ref DlSat, ref DvSat, xliq, xvap, ref ierrSat, ref herrSat, ref herrLen); - if (ierrSat != 0) { error = $"SATP 失败: {herrSat.Trim()} (ierr={ierrSat})"; return false; } - double tLocal = tSat; double DlLocal = DlSat; - IRefProp64.THERMdll(ref tLocal, ref DlLocal, xliq, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt); - tLocal = tSat; double DvLocal = DvSat; - IRefProp64.THERMdll(ref tLocal, ref DvLocal, xvap, ref pOut, ref ev, ref hv, ref sv, ref cvv, ref cpv, ref wv, ref hjtv); - if (Math.Abs(wm) < 1e-12) { error = "WMOL 返回 0(无效摩尔质量)"; return false; } - hl_kJkg = hl / wm; hv_kJkg = hv / wm; - return true; + error = "输入质量流量存在 NaN"; + return false; } + // 负值处理:小于 0 视为 0(避免传感器噪声或符号错误影响) + double mg = Math.Max(0.0, mGas_kg_h); + double ml = Math.Max(0.0, mLiq_kg_h); + double mSum = mg + ml; + if (mSum <= 0) + { + error = "气液质量流量之和为 0,无法进行加权混合焓计算"; + return false; + } + + // 2) 质量流量加权混合焓(严格按图片:上、下两路相加后除以总流量) + hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum; + + // 3) 干度计算:x = (h_mix - h_l) / (h_v - h_l) + double denom = (hSatV_kJkg - hSatL_kJkg); + const double eps = 1e-9; + if (Math.Abs(denom) < eps) + { + error = "饱和气/液焓差过小,无法计算干度(可能接近临界点或输入异常)"; + return false; + } + + double x = (hMix_kJkg - hSatL_kJkg) / denom; + + // 4) 限幅到 [0,1] + if (double.IsNaN(x) || double.IsInfinity(x)) + { + error = "干度计算结果异常(NaN/Inf)"; + return false; + } + dryness = Math.Min(1.0, Math.Max(0.0, x)); + return true; } - /// - /// LabVIEW 严格流程(质量流量加权): - /// 基于气/液两路阀前 T,P 求各自相焓,并用质量流量做加权得到 h_mix,再用吸气压力的饱和焓计算干度。 - /// 本方法从 Tag 中取值: - /// - 吸气压力[BarA](用于同压饱和焓) - /// - 气路阀前压力[BarA]、气路阀前温度[℃] - /// - 膨胀阀前压力[BarA]、SUBCOOL出口温度[℃](作为液体阀前 P/T) - /// - 气体质量流量[kg/h]、液体质量流量[kg/h](至少两者之一>0) - /// 返回:干度 x∈[0,1];若缺少必要测点或计算失败,返回 NaN,并在 error 输出原因。 - /// - - - #endregion - } } diff --git a/CapMachine.Wpf/TagConfig/SysTags.json b/CapMachine.Wpf/TagConfig/SysTags.json index d22a043..7efcea6 100644 --- a/CapMachine.Wpf/TagConfig/SysTags.json +++ b/CapMachine.Wpf/TagConfig/SysTags.json @@ -420,7 +420,7 @@ }, { "Id": 200, - "Name": "冷媒流量[L/min]", + "Name": "冷媒流量[kg/h]", "NameNoUnit": "冷媒流量", "EnName": "VRV", "Group": "采集", @@ -429,7 +429,7 @@ "IsMeter": false, "DecimalPoint": 1, "Precision": 10, - "Unit": "L/min", + "Unit": "kg/h", "DataType": "Short", "RWInfo": "PLCRead", "PVModel": { @@ -442,7 +442,7 @@ }, { "Id": 210, - "Name": "润滑油流量[L/min]", + "Name": "润滑油流量[kg/h]", "NameNoUnit": "润滑油流量", "EnName": "LubeFlow", "Group": "采集", @@ -451,7 +451,7 @@ "IsMeter": false, "DecimalPoint": 1, "Precision": 10, - "Unit": "L/min", + "Unit": "kg/h", "DataType": "Short", "RWInfo": "PLCRead", "PVModel": {