Files
CapMachine/CapMachine.Wpf/Services/PPCService.cs
2025-10-11 09:36:02 +08:00

767 lines
33 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.Shared.Controls;
using CapMachine.Wpf.Models.PPCalc;
using CapMachine.Wpf.Models.Tag;
using CapMachine.Wpf.PPCalculation;
using Prism.Events;
using Prism.Mvvm;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
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 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,
MachineRtDataService machineRtDataService, IDialogService dialogService)
{
ConfigService = configService;
//事件服务
_EventAggregator = eventAggregator;
DataRecordService = dataRecordService;
SysRunServer = sysRunService;
MachineRtDataService = machineRtDataService;
DialogService = dialogService;
TagManager = MachineRtDataService.TagManger;
//SpeedTag = TagManager.DicTags.GetValueOrDefault("转速[rpm]");
//ExPressTag = TagManager.DicTags.GetValueOrDefault("排气压力[BarA]");
//ExTempTag = TagManager.DicTags.GetValueOrDefault("排气温度[℃]");
if (TagManager.TryGetShortControlTagByName("吸气压力[BarA]", out ShortControlTag? InhPressShortControlTag))
{
InhPressTag = InhPressShortControlTag!;
}
if (TagManager.TryGetShortControlTagByName("吸气温度[℃]", out ShortControlTag? 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("膨胀阀前温度[℃]", out ShortValueTag? TxvFrTempShortTag))
{
TxvFrTempTag = TxvFrTempShortTag!;
}
if (TagManager.TryGetShortTagByName("膨胀阀前压力[BarA]", out ShortValueTag? TxvFrPressShortTag))
{
TxvFrPressTag = TxvFrPressShortTag!;
}
//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? DrynessShortTag))
{
DrynessTag = DrynessShortTag!;
}
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 ShortControlTag InhPressTag { get; set; }
/// <summary>
/// 吸气温度
/// </summary>
public ShortControlTag 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>
///// 过热度
///// </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 未使用
//textBox5.Text = "";
// 统一初始化(仅必要时),避免循环内重复 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;
// 计算过热度:吸气压力的饱和温度
double pForSat = (InhPressTag.PVModel.EngValue) * 1000.0; // MPa->kPa
double teSat = 0;
iErr = 0; // 确保按 ref 传入前已赋值
IRefProp64.SATPdll(ref pForSat, xLoc, ref kph, ref teSat, ref DlLoc, ref DvLoc, xliqLoc, xvapLoc, ref iErr, ref herr2, ref herrLen2);
if (iErr == 0)
Superheat.PVModel.EngValue = InhTempTag.PVModel.EngValue - (teSat - 273.15);
else
Superheat.PVModel.EngValue = 0;
// 计算过冷度:膨胀阀前压力的饱和温度
double p1ForSat = (TxvFrPressTag.PVModel.EngValue) * 1000.0; // MPa->kPa
double te1Sat = 0; iErr = 0; herr2 = new string(' ', 255); herrLen2 = 255; DlLoc = 0; DvLoc = 0;
IRefProp64.SATPdll(ref p1ForSat, xLoc, ref kph, ref te1Sat, ref DlLoc, ref DvLoc, xliqLoc, xvapLoc, ref iErr, ref herr2, ref herrLen2);
if (iErr == 0)
Subcool.PVModel.EngValue = TxvFrTempTag.PVModel.EngValue - (te1Sat - 273.15);
else
Subcool.PVModel.EngValue = 0;
// 干度计算可重入锁Monitor 为可重入锁,不会死锁)
try
{
double dryness = GetDrynessByTP(InhPressTag.PVModel.EngValue, InhTempTag.PVModel.EngValue, true);
if (DrynessTag != null)
{
DrynessTag.PVModel.EngValue = double.IsNaN(dryness) ? 0.0 : dryness;
}
}
catch { /* 不中断主循环 */ }
}
}
//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
// 重复的 SATP 与干度计算已在上方 lock(_refpropLock) 中统一完成,此处移除重复调用
}
catch (Exception ex)
{
//logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
}
}
});
}
#region
/// <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>
/// 风量数据的计算
/// </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 1T-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 转为 MPa1 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 转为 MPa1 bar = 0.1 MPa
/// </summary>
public DrynessCalcResult CalcDrynessByTP_BarA_C(double pressureBarA, double temperatureC)
{
return CalcDrynessByTP_MPaC(pressureBarA / 10.0, temperatureC);
}
/// <summary>
/// 基于当前标签(吸气压力[BarA]/吸气温度[℃])的干度计算便捷方法。
/// 注意:为保持与现有过热度/过冷度计算一致,此处将压力值按 MPa 处理再乘以 1000 转 kPa。
/// 若现场标签单位确认为 BarA请在调用方转换成 MPa 后再调用上层 CalcDrynessByTP_MPaC以避免单位歧义。
/// </summary>
public DrynessCalcResult CalcDrynessAtInlet()
{
double pressureMPa = InhPressTag?.PVModel?.EngValue ?? 0.0; // 与现有实现保持一致
double temperatureC = InhTempTag?.PVModel?.EngValue ?? 0.0;
return CalcDrynessByTP_MPaC(pressureMPa, temperatureC);
}
#endregion
}
}