1238 lines
58 KiB
C#
1238 lines
58 KiB
C#
using CapMachine.Core;
|
||
using CapMachine.Wpf.Models.PPCalc;
|
||
using CapMachine.Wpf.Models.Tag;
|
||
using CapMachine.Wpf.PPCalculation;
|
||
using NLog;
|
||
using Prism.Events;
|
||
using Prism.Mvvm;
|
||
using Prism.Services.Dialogs;
|
||
|
||
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; }
|
||
|
||
/// <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)
|
||
{
|
||
ConfigService = configService;
|
||
//事件服务
|
||
_EventAggregator = eventAggregator;
|
||
DataRecordService = dataRecordService;
|
||
SysRunServer = sysRunService;
|
||
Logger = logService;
|
||
MachineRtDataService = machineRtDataService;
|
||
DialogService = dialogService;
|
||
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!;
|
||
}
|
||
if (TagManager.TryGetShortTagByName("冷媒流量[L/min]", 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!;
|
||
}
|
||
|
||
//// 可选:气/液支路质量流量(若现场提供则可走“质量流量加权”分支)
|
||
//// 为兼容不同命名,尝试多个候选名称
|
||
//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("冷凝器进口温度[℃]");
|
||
|
||
//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!;
|
||
DrynessTagIsPercent = true;
|
||
}
|
||
|
||
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; }
|
||
private bool DrynessTagIsPercent { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// 气路阀前压力(BarA)
|
||
/// </summary>
|
||
public ShortValueTag GasPreValvePressTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 气路阀前温度(℃)
|
||
/// </summary>
|
||
public ShortValueTag GasPreValveTempTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 气相质量流量(kg/h)。
|
||
/// VRV 安装在气路阀前段,测量值单位为 kg/h,可直接作为 mg(气体质量流量)参与干度加权计算。
|
||
/// 若值<=0,则视为无效,将导致干度计算失败并置0。
|
||
/// </summary>
|
||
public ShortValueTag VRVTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 液体质量流量(kg/h)。
|
||
/// LabVIEW 中该量为常值;此处提供为可配置常量,实时计算直接使用,不再读取 Tag。
|
||
/// 若值<=0,则视为无效,将导致干度计算失败并置0。
|
||
/// 液冷媒流量=液体流量=液体质量流量
|
||
/// </summary>
|
||
public ShortValueTag LiqRefFlowTag { get; set; }
|
||
|
||
|
||
///// <summary>
|
||
///// 气相质量流量(kg/h),可选
|
||
///// </summary>
|
||
//public ShortValueTag GasMassFlowTag { get; set; }
|
||
|
||
///// <summary>
|
||
///// 液相质量流量(kg/h),可选
|
||
///// </summary>
|
||
//public ShortValueTag LiquidMassFlowTag { get; set; }
|
||
|
||
|
||
|
||
|
||
///// <summary>
|
||
///// 过热度
|
||
///// </summary>
|
||
//public double Superheat { get; set; }
|
||
|
||
///// <summary>
|
||
///// 过冷度
|
||
///// </summary>
|
||
//public double Subcool { get; set; }
|
||
|
||
///// <summary>
|
||
///// 制冷剂
|
||
///// </summary>
|
||
//public string Cryogen { get; set; } = "R134a";
|
||
|
||
/// <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>
|
||
/// PLC扫描线程
|
||
/// </summary>
|
||
private void RtScanDeviceStart()
|
||
{
|
||
CalcTask = Task.Run(async () =>
|
||
{
|
||
//Stopwatch stopwatch = new Stopwatch();
|
||
//物性的过热度和过冷度的相关物性计算
|
||
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 未使用
|
||
|
||
// 统一初始化(仅必要时),避免循环内重复 SETUP/SETPATH
|
||
if (!EnsureRefpropInitialized(out string initErrLoop))
|
||
{
|
||
// 初始化失败:保护性置零并进入下一轮
|
||
Superheat.PVModel.EngValue = 0;
|
||
Subcool.PVModel.EngValue = 0;
|
||
// 可选:记录 initErrLoop
|
||
}
|
||
else
|
||
{
|
||
// 在统一的锁内进行 REFPROP 计算,保证并发安全与状态一致性
|
||
lock (_refpropLock)
|
||
{
|
||
// 建立纯工质组分(摩尔分数)
|
||
double[] xLoc = new double[20]; xLoc[0] = 1.0;
|
||
double[] xliqLoc = new double[20];
|
||
double[] xvapLoc = new double[20];
|
||
double DlLoc = 0, DvLoc = 0;
|
||
string herr2 = new string(' ', 255);
|
||
long herrLen2 = 255;
|
||
|
||
// 计算过热度/过冷度(封装方法,BarA→kPa)
|
||
if (TryComputeSuperheatK_ByBarA(InhPressTag.PVModel.EngValue, InhTempTag.PVModel.EngValue, out double superheatK, out string shErr))
|
||
Superheat.PVModel.EngValue = superheatK;
|
||
else
|
||
{
|
||
Superheat.PVModel.EngValue = 0;
|
||
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;
|
||
Logger.Error($"过冷度计算失败:{scErr}");
|
||
}
|
||
|
||
// 干度计算(LabVIEW 严格流程,质量流量加权):
|
||
// A) 阀前两路相焓(TPRHO→THERM)
|
||
// B) 质量流量加权混合焓
|
||
// C) 吸气压力同压饱和焓(SATP→THERM)
|
||
// D) x = (h_mix - h_l)/(h_v - h_l)
|
||
try
|
||
{
|
||
// 输入校验:必要测点是否齐全
|
||
var missing = new List<string>();
|
||
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 (missing.Count > 0)
|
||
{
|
||
if (DrynessTag != null)
|
||
{
|
||
DrynessTag.PVModel.EngValue = 0;
|
||
}
|
||
Logger.Error($"质量流量加权干度:必要测点缺失,已将干度置0。缺失: {string.Join(", ", missing)}");
|
||
}
|
||
else
|
||
{
|
||
string dryErr;
|
||
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;
|
||
|
||
// 单位说明:
|
||
// - 液体质量流量 ml[kg/h] = LiqRefFlowTag.PVModel.EngValue
|
||
// - 气体质量流量 mg[kg/h] = VRVTag.PVModel.EngValue(VRV 位于气管,单位已为 kg/h)
|
||
|
||
double GasMassFlowKgPerH, LiquidMassFlowKgPerH;
|
||
|
||
LiquidMassFlowKgPerH = LiqRefFlowTag.PVModel.EngValue; // [kg/h]
|
||
//GasMassFlowKgPerH = Math.Max(0.0, VRVTag?.PVModel?.EngValue ?? 0.0); // [kg/h]
|
||
GasMassFlowKgPerH = (double)(VRVTag?.PVModel?.EngValue - LiquidMassFlowKgPerH);
|
||
//压力参数要求为BarA
|
||
double dryness = Step_ComputeDryness_LV_FlowWeighted(
|
||
GasMassFlowKgPerH,
|
||
LiquidMassFlowKgPerH,
|
||
ps, pg, tg, plv, tlv,
|
||
out dryErr
|
||
);
|
||
|
||
double val = (!double.IsNaN(dryness)) ? dryness : 0.0;
|
||
if (DrynessTag != null)
|
||
{
|
||
DrynessTag.PVModel.EngValue = DrynessTagIsPercent ? val * 100.0 : val;
|
||
}
|
||
|
||
if (double.IsNaN(dryness))
|
||
{
|
||
Logger.Error($"质量流量加权干度计算失败:{dryErr};已置0。输入: 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} ℃");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (DrynessTag != null) DrynessTag.PVModel.EngValue = 0;
|
||
Logger.Error("质量流量加权干度计算异常,已置0");
|
||
}
|
||
}
|
||
}
|
||
|
||
//p = Convert.ToDouble(textBox2.Text) * 100.0;//textBox2 Comp.吸气压力(BarA→kPa)
|
||
p = (InhPressTag.PVModel.EngValue) * 100.0;//textBox2 Comp.吸气压力(BarA→kPa)
|
||
kph = 1;
|
||
|
||
p1 = (TxvFrPressTag.PVModel.EngValue) * 100.0;//textBox3 Evap.膨胀阀前压力(BarA→kPa)
|
||
//p1 = Convert.ToDouble(textBox3.Text) * 100.0;//textBox3 Evap.膨胀阀前压力(BarA→kPa)
|
||
// 重复的 SATP 与干度计算已在上方 lock(_refpropLock) 中统一完成,此处移除重复调用
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
#region 过热度和过冷度相关计算
|
||
|
||
/// <summary>
|
||
/// 计算过热度(K)。输入为吸气压力[BarA]与吸气温度[℃],内部以 BarA→kPa 调用 SATP 求饱和温度。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算过冷度(K)。输入为膨胀阀前压力[BarA]与对应温度[℃],内部以 BarA→kPa 调用 SATP 求饱和温度。
|
||
/// </summary>
|
||
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 = preValveTempC - tSatC;
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 过热度和过冷度计算函数调用 风量的调用
|
||
/// </summary>
|
||
private void REFPROPSum()
|
||
{
|
||
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 = "";
|
||
|
||
|
||
// 统一初始化(必要时)
|
||
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 = TxvFrTempTag.PVModel.EngValue - (te1 - 273.15);
|
||
|
||
else
|
||
Subcool.PVModel.EngValue = 0;
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// STEP S0(LabVIEW 对应:初始化库)
|
||
/// 功能:确保 REFPROP 完成初始化(流体路径/工质/参考态)。
|
||
/// 结果:成功返回 true,否则返回 false 并带出错误信息。
|
||
/// 性能:幂等,实际初始化仅在必要时执行。
|
||
/// </summary>
|
||
private bool Step_S0_EnsureInitialized(out string error) => EnsureRefpropInitialized(out error);
|
||
|
||
/// <summary>
|
||
/// STEP Sx(LabVIEW 对应:INFO/WMOL 取摩尔质量)
|
||
/// 功能:获取当前工质的摩尔质量 wmm[g/mol]。
|
||
/// 结果:wmm_g_per_mol(g/mol)。
|
||
/// 说明:LabVIEW 常用 INFOdll 取 wmm,本实现用 WMOLdll 等价;纯工质场景下 x[0]=1。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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 的相路径,避免两相边界的相误判。
|
||
/// </summary>
|
||
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;
|
||
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) { 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// STEP S2(LabVIEW 对应:质量流量加权)
|
||
/// 功能:质量流量加权计算混合焓。
|
||
/// 结果:hMix_kJkg。
|
||
/// 限制:m_g + m_l 必须大于 0。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// STEP S4(LabVIEW 对应:按焓差计算干度)
|
||
/// 功能:x = (h_mix − h_l) / (h_v − h_l),并限幅到 [0,1]。
|
||
/// </summary>
|
||
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; }
|
||
x = Math.Max(0.0, Math.Min(1.0, val));
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 汇总步骤(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 调用在锁内,且无多余重复初始化;尽量少堆分配。
|
||
/// </summary>
|
||
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,
|
||
out string error)
|
||
{
|
||
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; }
|
||
|
||
// 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将体积流量[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,温度为 ℃。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 风量数据的计算
|
||
/// </summary>
|
||
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(使用现有“吸气压力/吸气温度”标签)
|
||
*/
|
||
|
||
/// <summary>
|
||
/// 干度计算返回结果模型。为实时轮询而设计,包含计算状态与关键物性量。
|
||
/// </summary>
|
||
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; // 错误或提示信息
|
||
}
|
||
|
||
/// <summary>
|
||
/// 干度计算的工况判定
|
||
/// </summary>
|
||
public enum DrynessState
|
||
{
|
||
Unknown = 0,
|
||
TwoPhase = 1,
|
||
Superheated = 2,
|
||
Subcooled = 3,
|
||
Supercritical = 4
|
||
}
|
||
|
||
/// <summary>
|
||
/// REFPROP 调用的并发锁。由于 REFPROP 为非托管库且内部通常为全局状态,
|
||
/// 在多线程/高频轮询调用时建议串行化访问,避免竞态与资源冲突。
|
||
/// </summary>
|
||
private readonly object _refpropLock = new object();
|
||
|
||
/// <summary>
|
||
/// REFPROP 初始化标记,避免重复 SETUP 开销
|
||
/// </summary>
|
||
private bool _rpInitialized = false;
|
||
|
||
/// <summary>
|
||
/// 已初始化的流体文件名(例如 R134A.FLD)
|
||
/// </summary>
|
||
private string _rpFluidFile = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 确保 REFPROP 完成初始化:设置流体路径、装载工质并建立参考态。
|
||
/// 该方法具备幂等性,多次调用只在必要时重新初始化。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算干度(T-P 闪蒸),输入为压力(MPa)与温度(℃)。
|
||
/// 调用逻辑:
|
||
/// 1) 通过 TPFLSHdll 获取整体态性质与摩尔基干度 q;
|
||
/// 2) 若 q∈[0,1],使用 QMASSdll 转换为质量基干度;
|
||
/// 3) 若 q 不在 [0,1](过冷/过热/超临界等),则以同压饱和液/气焓为基准,按 h=(1-x)*hl+x*hv 反推 x(并限幅到 [0,1])。
|
||
/// </summary>
|
||
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
|
||
double[] x = new double[20];
|
||
x[0] = 1.0;
|
||
|
||
// 外部温度(℃) -> K;压力(MPa) -> kPa
|
||
double tK = temperatureC + 273.15;
|
||
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;
|
||
|
||
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}";
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 便捷接口:仅返回干度数值 [0,1]。默认返回质量基干度;若质量基无效,则回退摩尔基;
|
||
/// 若均无效则返回 NaN,并可通过 <see cref="CalcDrynessByTP_MPaC"/> 获取详细错误原因。
|
||
/// </summary>
|
||
/// <param name="pressureMPa">压力(MPa)</param>
|
||
/// <param name="temperatureC">温度(℃)</param>
|
||
/// <param name="preferMassBasis">是否优先返回质量基干度(默认 true)</param>
|
||
public double GetDrynessByTP(double pressureMPa, double temperatureC, bool preferMassBasis = true)
|
||
{
|
||
var r = CalcDrynessByTP_MPaC(pressureMPa, temperatureC);
|
||
if (preferMassBasis)
|
||
{
|
||
if (!double.IsNaN(r.QMass)) return r.QMass;
|
||
if (!double.IsNaN(r.QMolar)) return r.QMolar;
|
||
}
|
||
else
|
||
{
|
||
if (!double.IsNaN(r.QMolar)) return r.QMolar;
|
||
if (!double.IsNaN(r.QMass)) return r.QMass;
|
||
}
|
||
return double.NaN;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 便捷接口(BarA 压力输入):仅返回干度数值 [0,1]。
|
||
/// 内部将 BarA 转为 MPa(1 bar = 0.1 MPa)。
|
||
/// </summary>
|
||
public double GetDrynessByTP_BarA(double pressureBarA, double temperatureC, bool preferMassBasis = true)
|
||
{
|
||
return GetDrynessByTP(pressureBarA / 10.0, temperatureC, preferMassBasis);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 详细结果版本(BarA 压力输入):返回包含状态与焓信息的结构体。
|
||
/// 内部将 BarA 转为 MPa(1 bar = 0.1 MPa)。
|
||
/// </summary>
|
||
public DrynessCalcResult CalcDrynessByTP_BarA_C(double pressureBarA, double temperatureC)
|
||
{
|
||
return CalcDrynessByTP_MPaC(pressureBarA / 10.0, temperatureC);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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 为常数。
|
||
/// </summary>
|
||
public double GetDrynessByTP_LV(double pressureMPa, double temperatureC)
|
||
{
|
||
if (!EnsureRefpropInitialized(out string initErr)) return double.NaN;
|
||
|
||
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 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;
|
||
|
||
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));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算指定 T(℃)/P(MPa) 下的质量比焓 h[kJ/kg](TPFLSH + WMOL 换算)。
|
||
/// </summary>
|
||
private bool TryComputeHmassKJkgByTP_MPaC(double pressureMPa, double temperatureC, 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 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)
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算指定 T(℃)/P(BarA) 下的质量比焓 h[kJ/kg](便捷包装)。
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算给定压力(MPa)下的同压饱和液/气焓(质量基,kJ/kg)。
|
||
/// </summary>
|
||
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)
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// LabVIEW 严格流程(质量流量加权):
|
||
/// 基于气/液两路阀前 T,P 求各自相焓,并用质量流量做加权得到 h_mix,再用吸气压力的饱和焓计算干度。
|
||
/// 本方法从 Tag 中取值:
|
||
/// - 吸气压力[BarA](用于同压饱和焓)
|
||
/// - 气路阀前压力[BarA]、气路阀前温度[℃]
|
||
/// - 膨胀阀前压力[BarA]、SUBCOOL出口温度[℃](作为液体阀前 P/T)
|
||
/// - 气体质量流量[kg/h]、液体质量流量[kg/h](至少两者之一>0)
|
||
/// 返回:干度 x∈[0,1];若缺少必要测点或计算失败,返回 NaN,并在 error 输出原因。
|
||
/// </summary>
|
||
|
||
|
||
#endregion
|
||
|
||
}
|
||
}
|