Files
CapMachine/CapMachine.Wpf/Services/PPCService.cs
2025-11-02 01:47:18 +08:00

859 lines
34 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 物性计算的服务
/// </summary>
public class PPCService : BindableBase
{
/// <summary>
/// 计算扫描 Task
/// </summary>
private static Task CalcTask { get; set; }
public ConfigService ConfigService { get; }
private IEventAggregator _EventAggregator { get; set; }
public DataRecordService DataRecordService { get; }
public SysRunService SysRunServer { get; }
public ILogService Logger { get; }
public MachineRtDataService MachineRtDataService { get; }
public IDialogService DialogService { get; }
private readonly IDrynessCalculator _drynessCalculator;
/// <summary>
/// 标签中心
/// </summary>
public TagManager TagManager { get; set; }
/// <summary>
/// 实例化
/// </summary>
public PPCService(ConfigService configService, IEventAggregator eventAggregator,
DataRecordService dataRecordService, SysRunService sysRunService, ILogService logService,
MachineRtDataService machineRtDataService, IDialogService dialogService,
IDrynessCalculator drynessCalculator)
{
ConfigService = configService;
//事件服务
_EventAggregator = eventAggregator;
DataRecordService = dataRecordService;
SysRunServer = sysRunService;
Logger = logService;
MachineRtDataService = machineRtDataService;
DialogService = dialogService;
_drynessCalculator = drynessCalculator;
TagManager = MachineRtDataService.TagManger;
//SpeedTag = TagManager.DicTags.GetValueOrDefault("转速[rpm]");
//ExPressTag = TagManager.DicTags.GetValueOrDefault("排气压力[BarA]");
//ExTempTag = TagManager.DicTags.GetValueOrDefault("排气温度[℃]");
if (TagManager.TryGetShortTagByName("吸气压力[BarA]", out ShortValueTag? InhPressShortControlTag))
{
InhPressTag = InhPressShortControlTag!;
}
if (TagManager.TryGetShortTagByName("吸气温度[℃]", out ShortValueTag? InhTempShortControlTag))
{
InhTempTag = InhTempShortControlTag!;
}
//InhTempTag = TagManager.DicTags.GetValueOrDefault("吸气温度[℃]")!;
//ComCapBusVolTag = TagManager.DicTags.GetValueOrDefault("通讯母线电压[V]");
//ComCapBusCurTag = TagManager.DicTags.GetValueOrDefault("通讯母线电流[A]");
//ComCapPwTag = TagManager.DicTags.GetValueOrDefault("通讯功率[W]");
//OS2TempTag = TagManager.DicTags.GetValueOrDefault("吸气混合器温度[℃]");
//TxvFrTempTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前温度[℃]")!;
//TxvFrPressTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前压力[BarA]")!;
if (TagManager.TryGetShortTagByName("SUBCOOL出口温度[℃]", out ShortValueTag? TxvFrTempShortTag))
{
TxvFrTempTag = TxvFrTempShortTag!;
}
if (TagManager.TryGetShortTagByName("膨胀阀前压力[BarA]", out ShortValueTag? TxvFrPressShortTag))
{
TxvFrPressTag = TxvFrPressShortTag!;
}
if (TagManager.TryGetShortTagByName("液冷媒流量[kg/h]", out ShortValueTag? LiqRefFlowShortTag))
{
LiqRefFlowTag = LiqRefFlowShortTag!;
}
//kg/h
if (TagManager.TryGetShortTagByName("冷媒流量[kg/h]", out ShortValueTag? VRVShortTag))
{
VRVTag = VRVShortTag!;
}
// 气路阀前 P/T用于质量流量加权混合焓的计算LabVIEW 流程)
if (TagManager.TryGetShortTagByName("气路阀前压力[BarA]", out ShortValueTag? gasPreP))
{
GasPreValvePressTag = gasPreP!;
}
if (TagManager.TryGetShortTagByName("气路阀前温度[℃]", out ShortValueTag? gasPreT))
{
GasPreValveTempTag = gasPreT!;
}
//Cond1TempTag = TagManager.DicTags.GetValueOrDefault("冷凝器出口水温[℃]");
//CondInTempTag = TagManager.DicTags.GetValueOrDefault("冷凝器进口温度[℃]");
//Superheat = TagManager.DicTags.GetValueOrDefault("过热度[K]");
//Subcool = TagManager.DicTags.GetValueOrDefault("过冷度[K]");
if (TagManager.TryGetShortTagByName("过热度[K]", out ShortValueTag? SuperheatShortTag))
{
Superheat = SuperheatShortTag!;
}
if (TagManager.TryGetShortTagByName("过冷度[K]", out ShortValueTag? SubcoolShortTag))
{
Subcool = SubcoolShortTag!;
}
// 干度标签(如不存在则仅跳过写入,不抛异常)。优先使用“干度[%]”,其次“干度[-]”。
if (TagManager.TryGetShortTagByName("干度[%]", out ShortValueTag? drynessPct))
{
DrynessTag = drynessPct!;
}
SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath");
SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen");
RtScanDeviceStart();
}
/// <summary>
/// 当前的配置
/// </summary>
public SuperHeatCoolConfigModel SuperHeatCoolConfig { get; set; } = new SuperHeatCoolConfigModel();
/// <summary>
/// 保存配置信息
/// </summary>
public void SaveSuperHeatCoolConfig()
{
ConfigHelper.SetValue("FluidsPath", SuperHeatCoolConfig.FluidsPath);
ConfigHelper.SetValue("Cryogen", SuperHeatCoolConfig.Cryogen);
}
/// <summary>
/// 吸气压力
/// </summary>
public ShortValueTag InhPressTag { get; set; }
/// <summary>
/// 吸气温度
/// </summary>
public ShortValueTag InhTempTag { get; set; }
/// <summary>
/// 液体阀前温度
/// </summary>
public ShortValueTag TxvFrTempTag { get; set; }
/// <summary>
/// 液体阀前压力
/// </summary>
public ShortValueTag TxvFrPressTag { get; set; }
/// <summary>
/// 过热度
/// </summary>
public ShortValueTag Superheat { get; set; }
/// <summary>
/// 过冷度
/// </summary>
public ShortValueTag Subcool { get; set; }
/// <summary>
/// 干度(无量纲 [-]
/// </summary>
public ShortValueTag DrynessTag { get; set; }
/// <summary>
/// 气路阀前压力BarA
/// </summary>
public ShortValueTag GasPreValvePressTag { get; set; }
/// <summary>
/// 气路阀前温度(℃)
/// </summary>
public ShortValueTag GasPreValveTempTag { get; set; }
/// <summary>
/// 冷媒流量kg/h
/// </summary>
public ShortValueTag VRVTag { get; set; }
/// <summary>
/// 液体流量kg/h
/// 液冷媒流量kg/h=液体流量kg/h
/// </summary>
public ShortValueTag LiqRefFlowTag { get; set; }
/// <summary>
/// 风量数据-乘以系数的后的最终结果
/// </summary>
private double AirVolumeData { get; set; } = 0.0;
/// <summary>
/// 风量数据-公式计算的原始数据
/// </summary>
private double AirVolumeDataSource { get; set; } = 0;
/// <summary>
/// 风量喷嘴启用信息数据
/// </summary>
private string RozzleEnableInfo { get; set; } = "";
/// <summary>
/// 启用计算
/// </summary>
private bool RtCalcEnable { get; set; } = true;
/// <summary>
/// 触发日志
/// </summary>
private bool DebugLog { get; set; } = false;
/// <summary>
/// PLC扫描线程
/// </summary>
private void RtScanDeviceStart()
{
CalcTask = Task.Run(async () =>
{
while (RtCalcEnable)
{
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;//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;
//气体流量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 (TryTHERM_VaporEntropy_ByTD(GasPreValveTempTag.PVModel.EngValue, D_molL, out var s_kJkgK, out var D_molLErr2))
{
// 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)
{
Logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
}
}
});
}
/// <summary>
/// 获取指定组分的摩尔质量kg/mol
/// </summary>
/// <param name="componentId">组分IDicomp</param>
/// <returns>摩尔质量kg/mol</returns>
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);
// 将wmm从g/mol转换为kg/mol乘以0.001
double molarMassKgPerMol = wmm * 0.001;
return molarMassKgPerMol;
}
catch (Exception ex)
{
throw new Exception($"获取组分{componentId}的摩尔质量时出错: {ex.Message}", ex);
}
}
// 若类中尚未定义,请添加全局互斥锁,串行化所有 REFPROP 调用
private static readonly object _refpropLock = new object();
/// <summary>
///
/// 计算获取 气相密度D_vap mol/l
///
/// TPRHOdll 封装:按 T(℃)、P(MPa) 计算“气相”摩尔密度 D [mol/L]
/// LabVIEW 对应kph=2气相kguess=0
/// </summary>
/// <param name="pressureMPa">气体阀前压力mpa</param>
/// <param name="temperatureC">气体阀前温度 C</param>
/// <param name="densityMolPerL">“气相”摩尔密度 D [mol/L] / 气相密度D_vap mol/l</param>
/// <param name="error">ERR</param>
/// <returns></returns>
public bool TryTPRHO_VaporDensity_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 = 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;
}
/// <summary>
/// 计算气路阀前气相焓h_vap(单相气相)
///
/// THERMdll 封装:按 T(℃) 与上一步得到的 D[mol/L] 计算气相熵 s [kJ/(kg·K)]
/// LabVIEW 对应THERM(T, D) -> s[J/(mol·K)],再 /M[kg/mol] 并 *0.001
/// </summary>
/// <param name="temperatureC">气体阀前温度 C</param>
/// <param name="densityMolPerL">气相密度D_vap mol/l</param>
/// <param name="entropy_kJ_per_kgK"></param>
/// <param name="error"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// 计算液路阀前液相密度D_liq(单相液相) 液相密度D_liq mol/L
///
/// TPRHOdll 封装(液相):按 T(℃)、P(MPa) 计算“液相”摩尔密度 D_liq [mol/L]
/// LabVIEW 对应kph=1液相kguess=0
/// </summary>
/// <param name="pressureMPa"></param>
/// <param name="temperatureC"></param>
/// <param name="densityMolPerL"></param>
/// <param name="error"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 计算获取 液相质量焓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
/// </summary>
/// <param name="temperatureC">液体阀前温度'℃</param>
/// <param name="densityMolPerL">液相密度D_liq mol/L</param>
/// <param name="h_liq_kJ_per_kg">液相质量焓h lig k/kg</param>
/// <param name="error">Err</param>
/// <returns></returns>
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;
}
/// <summary>
/// SATPdll 封装:由压力 P(MPa) 求饱和温度 Tsat[K]、饱和液/气摩尔密度 Dl/Dv [mol/L]
/// LabVIEW 对应P(MPa)*1000 -> kPakph=1bubble
/// </summary>
/// <param name="pressureMPa">王缩机吸气压力mpa</param>
/// <param name="tSatK">吸气压力下的饱和温度T_sat K</param>
/// <param name="Dl_molL">饱和液相密度Dl molL</param>
/// <param name="Dv_molL">饱和气相密度Dv mol/L</param>
/// <param name="error"></param>
/// <returns></returns>
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;
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);
lock (_refpropLock)
{
IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSatK, ref Dl, ref Dv, xliq, xvap, ref ierr, ref herr, ref herrLen);
}
if (ierr != 0)
{
error = $"SATP 错误: {herr.Trim()} (ierr={ierr})";
return false;
}
Dl_molL = Dl;
Dv_molL = Dv;
return true;
}
/// <summary>
/// THERMdll 封装:由 T[K] 与 D[mol/L] 计算质量比焓 h[kJ/kg]
/// LabVIEW 对应THERM(T, D) -> h[J/mol],再 /M[kg/mol] * 0.001
/// </summary>
/// <param name="temperatureK"></param>
/// <param name="densityMolPerL"></param>
/// <param name="h_kJ_per_kg"></param>
/// <param name="error"></param>
/// <returns></returns>
public bool TryTHERM_Enthalpy_kJkg_ByT_K_D(
double temperatureK,
double densityMolPerL,
out double h_kJ_per_kg,
out string error)
{
h_kJ_per_kg = double.NaN;
error = string.Empty;
double tK = temperatureK;
double D = densityMolPerL;
double[] x = new double[20]; x[0] = 1.0;
double pOut = 0, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0;
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);
}
h_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; // J/mol -> kJ/kg
return true;
}
/// <summary>
/// 便捷:由压力 P(MPa) 直接得到“饱和液”质量比焓 h_liq[kJ/kg]
/// 流程SATP(P)->Tsat/Dl再 THERM(Tsat, Dl)
/// </summary>
/// <param name="pressureMPa"></param>
/// <param name="h_liq_kJkg"></param>
/// <param name="error"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 便捷:由压力 P(MPa) 直接得到“饱和气”质量比焓 h_vap[kJ/kg]
/// 流程SATP(P)->Tsat/Dv再 THERM(Tsat, Dv)
/// </summary>
/// <param name="pressureMPa"></param>
/// <param name="h_vap_kJkg"></param>
/// <param name="error"></param>
/// <returns></returns>
public bool TryGetSaturationVaporEnthalpy_ByP_MPa(
double pressureMPa,
out double h_vap_kJkg,
out string error)
{
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);
}
/// <summary>
/// 按图片的最终流程计算干度:
/// 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_mixfalse 返回 error
/// </summary>
/// <param name="hVap_kJkg">气相质量焓 h_vap [kJ/kg]</param>
/// <param name="hLiq_kJkg">液相质量焓 h_liq [kJ/kg]</param>
/// <param name="mGas_kg_h">气体质量流量 mg [kg/h]</param>
/// <param name="mLiq_kg_h">液体质量流量 ml [kg/h]</param>
/// <param name="hSatL_kJkg">饱和液质量焓 h_l [kJ/kg]</param>
/// <param name="hSatV_kJkg">饱和气质量焓 h_v [kJ/kg]</param>
/// <param name="dryness">输出干度 x ∈ [0,1]</param>
/// <param name="hMix_kJkg">输出混合后总比焓 h_mix [kJ/kg]</param>
/// <param name="error">Err</param>
/// <returns></returns>
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))
{
error = "输入焓值存在 NaN";
return false;
}
if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h))
{
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;
}
}
}