6个物性参数和过热度和过冷度的验证OK

This commit is contained in:
2026-05-08 11:02:55 +08:00
parent 47834ea4dc
commit a8c5cda332
6 changed files with 1387 additions and 2381 deletions

View File

@@ -34,16 +34,9 @@ namespace CapMachine.Wpf.Services
public ILogService Logger { get; }
public MachineRtDataService MachineRtDataService { get; }
public IDialogService DialogService { get; }
private readonly object _refpropLock = new object();
private readonly PPCSuperheatSubcoolCalculator _superheatSubcoolCalculator;
private readonly PPCThermodynamicSixResultsCalculator _thermodynamicSixResultsCalculator;
private readonly EnthalpyDrynessCalculator _enthalpyDrynessCalculator;
private DateTime _lastSuperheatSubcoolErrorLogAt = DateTime.MinValue;
private string _lastSuperheatSubcoolErrorText = string.Empty;
private int _hvZeroStreak;
private readonly SuperheatSubcoolCalculator _superheatSubcoolCalculator;
private readonly ThermodynamicSixResultsCalculator _thermodynamicSixResultsCalculator;
/// <summary>
/// 标签中心
/// </summary>
@@ -114,22 +107,24 @@ namespace CapMachine.Wpf.Services
Subcool = TagManager.DicTags.GetValueOrDefault("过冷度[K]");
HeatingCapacity = TagManager.DicTags.GetValueOrDefault("制热量Qh[KW]")
HeatingCapacityTag = TagManager.DicTags.GetValueOrDefault("制热量Qh[KW]")
?? TagManager.DicTags.GetValueOrDefault("制热量Qh[W]");
COPHeat = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制热)[K]")
COPHeatTag = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制热)[K]")
?? TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制热COP)");
IsentrpEff = TagManager.DicTags.GetValueOrDefault("等熵效率ns[%]");
CoolCapacity = TagManager.DicTags.GetValueOrDefault("制冷量Qc[KW]")
IsentrpEffTag = TagManager.DicTags.GetValueOrDefault("等熵效率ns[%]");
CoolCapacityTag = TagManager.DicTags.GetValueOrDefault("制冷量Qc[KW]")
?? TagManager.DicTags.GetValueOrDefault("制冷量Qc[W]");
COPCool = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制冷)[K]")
COPCoolTag = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制冷)[K]")
?? TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制冷COP)");
VoltricEff = TagManager.DicTags.GetValueOrDefault("容积效率nv[%]");
VoltricEffTag = TagManager.DicTags.GetValueOrDefault("容积效率nv[%]");
_superheatSubcoolCalculator = new SuperheatSubcoolCalculator(_refpropLock);
_thermodynamicSixResultsCalculator = new ThermodynamicSixResultsCalculator(_refpropLock);
SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath");
SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen");
_superheatSubcoolCalculator = new PPCSuperheatSubcoolCalculator(_refpropLock);
_thermodynamicSixResultsCalculator = new PPCThermodynamicSixResultsCalculator(_refpropLock);
_enthalpyDrynessCalculator = new EnthalpyDrynessCalculator(_refpropLock);
ReloadTherdyH3TempOffset();
RtScanDeviceStart();
@@ -143,16 +138,16 @@ namespace CapMachine.Wpf.Services
try
{
string raw = ConfigHelper.GetValue(TherdyH3TempOffsetConfigKey);
if (!string.IsNullOrWhiteSpace(raw) && double.TryParse(raw, out var parsed))
if (!string.IsNullOrWhiteSpace(raw) && double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed))
{
offsetC = parsed;
}
}
catch (Exception ex)
catch
{
Logger?.Error($"读取 {TherdyH3TempOffsetConfigKey} 失败: {ex.Message}");
}
TherdyH3TempOffset_C = offsetC;
_thermodynamicSixResultsCalculator.SetH3TempOffset_C(offsetC);
}
@@ -170,6 +165,17 @@ namespace CapMachine.Wpf.Services
ConfigHelper.SetValue("Cryogen", SuperHeatCoolConfig.Cryogen);
}
private double _therdyH3TempOffset_C = -10.0;
public double TherdyH3TempOffset_C
{
get { return _therdyH3TempOffset_C; }
private set
{
_therdyH3TempOffset_C = value;
RaisePropertyChanged();
}
}
/// <summary>
/// 吸气压力
/// </summary>
@@ -261,27 +267,19 @@ namespace CapMachine.Wpf.Services
/// </summary>
public ITag LubeFlowTag { get; set; }
public ITag HeatingCapacity { get; set; }
public ITag COPHeat { get; set; }
public ITag IsentrpEff { get; set; }
public ITag CoolCapacity { get; set; }
public ITag COPCool { get; set; }
public ITag VoltricEff { get; set; }
public ITag? HeatingCapacityTag { get; set; }
/// <summary>
/// 风量数据-乘以系数的后的最终结果
/// </summary>
private double AirVolumeData { get; set; } = 0.0;
public ITag? COPHeatTag { get; set; }
/// <summary>
/// 风量数据-公式计算的原始数据
/// </summary>
private double AirVolumeDataSource { get; set; } = 0;
public ITag? IsentrpEffTag { get; set; }
public ITag? CoolCapacityTag { get; set; }
public ITag? COPCoolTag { get; set; }
public ITag? VoltricEffTag { get; set; }
/// <summary>
/// 风量喷嘴启用信息数据
/// </summary>
private string RozzleEnableInfo { get; set; } = "";
/// <summary>
/// 启用计算
@@ -293,7 +291,21 @@ namespace CapMachine.Wpf.Services
/// </summary>
private bool DebugLog { get; set; } = false;
private int _CurDisplacementCc;
/// <summary>
/// 当前的排量信息(供 UI 展示)
/// </summary>
public int CurDisplacementCc
{
get { return _CurDisplacementCc; }
set { _CurDisplacementCc = value; RaisePropertyChanged(); }
}
private double _HeatingCapacityQh_kW;
/// <summary>
/// 制热量 Qh [kW]
/// </summary>
public double HeatingCapacityQh_kW
{
get { return _HeatingCapacityQh_kW; }
@@ -301,6 +313,9 @@ namespace CapMachine.Wpf.Services
}
private double _COPHeating;
/// <summary>
/// 压缩机性能系数 COP制热[-]
/// </summary>
public double COPHeating
{
get { return _COPHeating; }
@@ -308,6 +323,9 @@ namespace CapMachine.Wpf.Services
}
private double _IsentropicEfficiencyPct;
/// <summary>
/// 等熵效率 ηs [%]
/// </summary>
public double IsentropicEfficiencyPct
{
get { return _IsentropicEfficiencyPct; }
@@ -315,6 +333,9 @@ namespace CapMachine.Wpf.Services
}
private double _CoolingCapacityQc_kW;
/// <summary>
/// 制冷量 Qc [kW]
/// </summary>
public double CoolingCapacityQc_kW
{
get { return _CoolingCapacityQc_kW; }
@@ -322,6 +343,9 @@ namespace CapMachine.Wpf.Services
}
private double _COPCooling;
/// <summary>
/// 压缩机性能系数 COP制冷[-]
/// </summary>
public double COPCooling
{
get { return _COPCooling; }
@@ -329,6 +353,9 @@ namespace CapMachine.Wpf.Services
}
private double _VolumetricEfficiencyPct;
/// <summary>
/// 容积效率 ηv [%]
/// </summary>
public double VolumetricEfficiencyPct
{
get { return _VolumetricEfficiencyPct; }
@@ -347,39 +374,30 @@ namespace CapMachine.Wpf.Services
await Task.Delay(300);
try
{
_ = TryUpdateSuperheatAndSubcool(out var superheatSubcoolErr);
if (!string.IsNullOrWhiteSpace(superheatSubcoolErr))
double[] x = new double[20];
double wm = 0.0;
// 幂等初始化:仅首次或工质/路径变化时执行 SETPATH/SETUP提高每秒循环效率
if (!EnsureRefpropInitialized(out var initErr))
{
var now = DateTime.UtcNow;
if (!string.Equals(superheatSubcoolErr, _lastSuperheatSubcoolErrorText, StringComparison.Ordinal)
|| (now - _lastSuperheatSubcoolErrorLogAt) >= TimeSpan.FromSeconds(5))
{
Logger?.Error($"过热度/过冷度计算失败: {superheatSubcoolErr}");
_lastSuperheatSubcoolErrorLogAt = now;
_lastSuperheatSubcoolErrorText = superheatSubcoolErr;
}
// 初始化失败,跳过本周期
Logger?.Error($"REFPROP 初始化失败: {initErr}");
continue;
}
//if (TryUpdateDryness(out var drynessErr))
//{
// if (!string.IsNullOrWhiteSpace(drynessErr))
// {
// Logger?.Error($"干度计算警告: {drynessErr}");
// }
//}
//else
//{
// if (!string.IsNullOrWhiteSpace(drynessErr))
// {
// Logger?.Error($"干度计算失败: {drynessErr}");
// }
//}
// WMOL 仅在需要时调用;若调用,需设置 x[0]=1.0(纯工质)
x[0] = 1.0;
IRefProp64.WMOLdll(x, ref wm);
UpdateSuperheatAndSubcool_BySatp();
if (TryUpdateThermodynamicSixResults(out var thermoErr))
{
if (!string.IsNullOrWhiteSpace(thermoErr))
{
//Logger?.Error($"六个物性结果计算警告: {thermoErr}");
//Logger?.Warn($"六个物性结果计算警告: {thermoErr}");
}
}
else
@@ -399,121 +417,6 @@ namespace CapMachine.Wpf.Services
});
}
private bool TryUpdateSuperheatAndSubcool(out string error)
{
error = string.Empty;
bool updated = false;
StringBuilder errorBuilder = new StringBuilder();
if (InhPressTag == null || InhTempTag == null || Superheat == null)
{
AppendCalculationError(errorBuilder, "缺少过热度计算标签");
}
else if (_superheatSubcoolCalculator.TryCalculateSuperheatK(InhPressTag.EngPvValue, InhTempTag.EngPvValue, out var superheatValue, out var superheatErr))
{
Superheat.EngPvValue = Math.Abs(superheatValue);
updated = true;
}
else
{
AppendCalculationError(errorBuilder, superheatErr);
}
if (TxvFrPressTag == null || TxvFrTempTag == null || Subcool == null)
{
AppendCalculationError(errorBuilder, "缺少过冷度计算标签");
}
else if (_superheatSubcoolCalculator.TryCalculateSubcoolK(TxvFrPressTag.EngPvValue, TxvFrTempTag.EngPvValue, out var subcoolValue, out var subcoolErr))
{
Subcool.EngPvValue = Math.Abs(subcoolValue);
updated = true;
}
else
{
AppendCalculationError(errorBuilder, subcoolErr);
}
error = errorBuilder.ToString();
return updated;
}
private static void AppendCalculationError(StringBuilder errorBuilder, string error)
{
if (string.IsNullOrWhiteSpace(error))
{
return;
}
if (errorBuilder.Length > 0)
{
errorBuilder.Append("");
}
errorBuilder.Append(error);
}
private bool TryUpdateDryness(out string error)
{
error = string.Empty;
bool updated = false;
StringBuilder errorBuilder = new StringBuilder();
if (GasPreValvePressTag == null || GasPreValveTempTag == null || TxvFrPressTag == null || TxvFrTempTag == null || InhPressTag == null)
{
AppendCalculationError(errorBuilder, "缺少干度计算压力/温度标签");
error = errorBuilder.ToString();
return false;
}
if (VRVTag == null || LiqRefFlowTag == null)
{
AppendCalculationError(errorBuilder, "缺少干度计算流量标签");
error = errorBuilder.ToString();
return false;
}
double lubeFlowKgPerH = 0.0;
if (LubeFlowTag != null)
{
lubeFlowKgPerH = LubeFlowTag.EngPvValue;
}
var drynessResult = _enthalpyDrynessCalculator.Calculate(
new EnthalpyDrynessCalculator.Input(
gasPreValvePressBarA: GasPreValvePressTag.EngPvValue,
gasPreValveTempC: GasPreValveTempTag.EngPvValue,
txvFrPressBarA: TxvFrPressTag.EngPvValue,
txvFrTempC: TxvFrTempTag.EngPvValue,
inhPressBarA: InhPressTag.EngPvValue,
vrvFlowKgPerH: VRVTag.EngPvValue,
liqRefFlowKgPerH: LiqRefFlowTag.EngPvValue,
lubeFlowKgPerH: lubeFlowKgPerH));
if (drynessResult.IsDryness1Success)
{
if (DrynessTag != null)
{
DrynessTag.EngPvValue = Math.Round(drynessResult.Dryness1_01, 4);
}
updated = true;
}
else
{
AppendCalculationError(errorBuilder, drynessResult.Error1);
}
if (drynessResult.IsDryness2Success)
{
DrynessTag2Value = Math.Round(drynessResult.Dryness2_01, 4);
updated = true;
}
else
{
AppendCalculationError(errorBuilder, drynessResult.Error2);
}
error = errorBuilder.ToString();
return updated;
}
/// <summary>
/// 按流程图更新制热量、COP(制热)、等熵效率、制冷量、COP(制冷)、容积效率。
@@ -532,132 +435,21 @@ namespace CapMachine.Wpf.Services
{
error = string.Empty;
double w_W = HVPwTag?.EngPvValue ?? double.NaN;
if (!double.IsNaN(w_W) && !double.IsInfinity(w_W) && w_W == 0)
if (InhPressTag == null || InhTempTag == null)
{
_hvZeroStreak = Math.Min(_hvZeroStreak + 1, 1000);
if (_hvZeroStreak < 3)
{
return false;
}
HeatingCapacityQh_kW = 0;
CoolingCapacityQc_kW = 0;
COPHeating = 0;
COPCooling = 0;
IsentropicEfficiencyPct = 0;
VolumetricEfficiencyPct = 0;
if (HeatingCapacity != null)
{
HeatingCapacity.EngPvValue = 0;
}
if (COPHeat != null)
{
COPHeat.EngPvValue = 0;
}
if (IsentrpEff != null)
{
IsentrpEff.EngPvValue = 0;
}
if (CoolCapacity != null)
{
CoolCapacity.EngPvValue = 0;
}
if (COPCool != null)
{
COPCool.EngPvValue = 0;
}
if (VoltricEff != null)
{
VoltricEff.EngPvValue = 0;
}
return true;
}
_hvZeroStreak = 0;
// 先把本周期所需的标签值组装为输入模型。
// PPCService 在这里负责完成“标签 -> 领域输入对象”的映射,
// 从而把计算类和 UI/标签层解耦。
if (!TryCreateThermodynamicSixResultsInput(out var input, out var inputErr))
{
error = inputErr;
error = "缺少吸气压力/吸气温度标签";
return false;
}
// 把输入模型交给独立计算类统一完成 6 个结果的热力计算。
// 此处不再在 PPCService 中展开公式,只保留流程调度职责。
if (!_thermodynamicSixResultsCalculator.TryCalculate(input, out var result, out error))
if (TxvFrPressTag == null || TxvFrTempTag == null)
{
error = "缺少膨胀阀前压力/膨胀阀前温度标签";
return false;
}
// 先把结果同步到服务属性,供界面绑定或其他业务读取。
HeatingCapacityQh_kW = result.HeatingCapacityQh_kW;
CoolingCapacityQc_kW = result.CoolingCapacityQc_kW;
COPHeating = result.COPHeating;
COPCooling = result.COPCooling;
IsentropicEfficiencyPct = result.IsentropicEfficiencyPct;
// 再把结果回写到对应工程量标签。
if (HeatingCapacity != null)
if (ExPressTag == null || ExTempTag == null)
{
HeatingCapacity.EngPvValue = string.Equals(HeatingCapacity.Unit, "W", StringComparison.OrdinalIgnoreCase)
? HeatingCapacityQh_kW * 1000.0
: HeatingCapacityQh_kW;
error = "缺少排气压力/排气温度标签";
return false;
}
if (COPHeat != null)
{
COPHeat.EngPvValue = COPHeating;
}
if (IsentrpEff != null)
{
IsentrpEff.EngPvValue = IsentropicEfficiencyPct;
}
if (CoolCapacity != null)
{
CoolCapacity.EngPvValue = string.Equals(CoolCapacity.Unit, "W", StringComparison.OrdinalIgnoreCase)
? CoolingCapacityQc_kW * 1000.0
: CoolingCapacityQc_kW;
}
if (COPCool != null)
{
COPCool.EngPvValue = COPCooling;
}
// 容积效率在旧逻辑中允许单独失败;
// 因此只有拿到有效数值时才回写,避免覆盖已有结果为 NaN/Inf。
if (!double.IsNaN(result.VolumetricEfficiencyPct) && !double.IsInfinity(result.VolumetricEfficiencyPct))
{
VolumetricEfficiencyPct = result.VolumetricEfficiencyPct;
if (VoltricEff != null)
{
VoltricEff.EngPvValue = VolumetricEfficiencyPct;
}
}
return true;
}
/// <summary>
/// 创建六个热力结果值计算所需的输入模型。
/// </summary>
/// <param name="input">
/// 输出输入模型。
/// 该对象是 <see cref="PPCThermodynamicSixResultsCalculator"/> 与 <see cref="PPCService"/> 之间的数据边界:
/// 由服务层负责从标签提取实时值,由计算层只消费该对象而不直接依赖标签。
/// </param>
/// <param name="error">失败原因,通常为缺少标签、缺少排量配置或关键输入不可用。</param>
/// <returns>是否创建成功。</returns>
private bool TryCreateThermodynamicSixResultsInput(out PPCThermodynamicSixResultsCalculationInput input, out string error)
{
input = new PPCThermodynamicSixResultsCalculationInput();
error = string.Empty;
// 依次校验本周期计算所需的全部标签。
// 这里不做公式计算,只保证独立计算类拿到的是一套完整输入。
if (HVPwTag == null)
{
error = "缺少 HV[W] 功率标签";
@@ -668,56 +460,228 @@ namespace CapMachine.Wpf.Services
error = "缺少总流量(冷媒流量)标签";
return false;
}
if (InhPressTag == null || InhTempTag == null)
double suctionPress_BarA = InhPressTag.EngPvValue;
double suctionTemp_C = InhTempTag.EngPvValue;
double dischargePress_BarA = ExPressTag.EngPvValue;
double dischargeTemp_C = ExTempTag.EngPvValue;
double txvFrPress_BarA = TxvFrPressTag.EngPvValue;
double txvFrTemp_C = TxvFrTempTag.EngPvValue;
double totalFlow_kg_h = VRVTag.EngPvValue;
double w_W = HVPwTag.EngPvValue;
double speed_rpm = SpeedTag?.EngPvValue ?? double.NaN;
if (!TryGetCompressorDisplacement_cc(out var disp_cc, out var dispErr))
{
error = "缺少吸气压力/吸气温度标签";
error = dispErr;
return false;
}
if (ExPressTag == null || ExTempTag == null)
{
error = "缺少排气压力/排气温度标签";
return false;
}
if (TxvFrPressTag == null || TxvFrTempTag == null)
{
error = "缺少膨胀阀前压力/膨胀阀前温度标签";
return false;
}
if (SpeedTag == null)
{
error = "缺少转速标签";
return false;
}
if (!TryGetCompressorDisplacement_cc(out var displacementCc, out _))
{
displacementCc = double.NaN;
}
double oilFlowKgPerH = 0.0;
if (LubeFlowTag != null)
//这里把输入数据写死计算出结果。
//#if DEBUG
// // 附件截图输入单位换算MPa -> BarAkW -> W
// suctionPress_BarA = 0.3 * 10.0;
// suctionTemp_C = 10.8;
// dischargePress_BarA = 1.502 * 10.0;
// dischargeTemp_C = 88.4;
// txvFrPress_BarA = 1.494 * 10.0;
// txvFrTemp_C = 45.0;
// totalFlow_kg_h = 100.1;
// w_W = 1.289 * 1000.0;
// speed_rpm = 3004;
// disp_cc = 35;
//#endif
if (!double.IsNaN(w_W) && !double.IsInfinity(w_W) && w_W == 0)
{
oilFlowKgPerH = LubeFlowTag.EngPvValue;
HeatingCapacityQh_kW = 0;
CoolingCapacityQc_kW = 0;
COPHeating = 0;
COPCooling = 0;
IsentropicEfficiencyPct = 0;
VolumetricEfficiencyPct = 0;
if (HeatingCapacityTag != null)
{
HeatingCapacityTag.EngPvValue = 0;
}
if (COPHeatTag != null)
{
COPHeatTag.EngPvValue = 0;
}
if (IsentrpEffTag != null)
{
IsentrpEffTag.EngPvValue = 0;
}
if (CoolCapacityTag != null)
{
CoolCapacityTag.EngPvValue = 0;
}
if (COPCoolTag != null)
{
COPCoolTag.EngPvValue = 0;
}
if (VoltricEffTag != null)
{
VoltricEffTag.EngPvValue = 0;
}
return true;
}
// 将实时标签值与配置值组装为独立计算类可直接消费的输入对象。
input = new PPCThermodynamicSixResultsCalculationInput
{
CompressorPowerW = HVPwTag.EngPvValue,
TotalMassFlowKgPerHour = VRVTag.EngPvValue,
OilMassFlowKgPerHour = oilFlowKgPerH,
SuctionPressureBarA = InhPressTag.EngPvValue,
SuctionTemperatureC = InhTempTag.EngPvValue,
DischargePressureBarA = ExPressTag.EngPvValue,
DischargeTemperatureC = ExTempTag.EngPvValue,
LiquidPressureBarA = TxvFrPressTag.EngPvValue,
LiquidTemperatureC = TxvFrTempTag.EngPvValue,
CompressorSpeedRpm = SpeedTag.EngPvValue,
CompressorDisplacementCc = displacementCc
};
var calcInput = new ThermodynamicSixResultsCalculator.Input(
suctionPress_BarA: suctionPress_BarA,
suctionTemp_C: suctionTemp_C,
dischargePress_BarA: dischargePress_BarA,
dischargeTemp_C: dischargeTemp_C,
txvFrPress_BarA: txvFrPress_BarA,
txvFrTemp_C: txvFrTemp_C,
hvPower_W: w_W,
totalFlow_kg_h: totalFlow_kg_h,
speed_rpm: speed_rpm,
displacement_cc: disp_cc);
if (!_thermodynamicSixResultsCalculator.TryCalculate(calcInput, out var r, out var calcErr))
{
error = calcErr;
return false;
}
HeatingCapacityQh_kW = r.HeatingCapacityQh_kW;
CoolingCapacityQc_kW = r.CoolingCapacityQc_kW;
COPHeating = r.COPHeating;
COPCooling = r.COPCooling;
IsentropicEfficiencyPct = r.IsentropicEfficiencyPct;
if (HeatingCapacityTag != null)
{
HeatingCapacityTag.EngPvValue = HeatingCapacityQh_kW * 1000.0;
}
if (COPHeatTag != null)
{
COPHeatTag.EngPvValue = COPHeating;
}
if (IsentrpEffTag != null)
{
IsentrpEffTag.EngPvValue = IsentropicEfficiencyPct;
}
if (CoolCapacityTag != null)
{
CoolCapacityTag.EngPvValue = CoolingCapacityQc_kW * 1000.0;
}
if (COPCoolTag != null)
{
COPCoolTag.EngPvValue = COPCooling;
}
if (double.IsNaN(r.VolumetricEfficiencyPct) || double.IsInfinity(r.VolumetricEfficiencyPct))
{
VolumetricEfficiencyPct = double.NaN;
error = calcErr;
return true;
}
VolumetricEfficiencyPct = r.VolumetricEfficiencyPct;
if (VoltricEffTag != null)
{
VoltricEffTag.EngPvValue = VolumetricEfficiencyPct;
}
error = calcErr;
return true;
}
private void UpdateSuperheatAndSubcool_BySatp()
{
_superheatSubcoolCalculator.Calculate(
inhPressBarA: InhPressTag.EngPvValue,
inhTempC: InhTempTag.EngPvValue,
txvFrPressBarA: TxvFrPressTag.EngPvValue,
txvFrTempC: TxvFrTempTag.EngPvValue,
out var superheatK,
out var subcoolK);
Superheat.EngPvValue = superheatK;
Subcool.EngPvValue = subcoolK;
}
// 若类中尚未定义,请添加全局互斥锁,串行化所有 REFPROP 调用
private static readonly object _refpropLock = new object();
// REFPROP 初始化状态(全局、幂等)
private static volatile bool _rpInitialized = false;
/// <summary>
/// 幂等初始化:设置流体路径/工质/参考态;确保全局只初始化一次。
/// 注意:所有 REFPROP 原生调用都需在 _refpropLock 下串行化,包括初始化调用。
/// </summary>
private bool EnsureRefpropInitialized(out string error)
{
error = string.Empty;
if (_rpInitialized) return true;
try
{
lock (_refpropLock)
{
if (_rpInitialized) return true; // 双检,避免并发二次初始化
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.FLD如需扩展可根据 configuredCryogen 选择不同文件
string hfldCore = configuredCryogen.Equals("R134a", StringComparison.OrdinalIgnoreCase)
? "R134A.FLD"
: "R134A.FLD";
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;
return true;
}
}
catch (Exception ex)
{
error = $"REFPROP 初始化异常: {ex.Message}";
Logger.Error(error);
_rpInitialized = false;
return false;
}
}
/// <summary>
/// 获取压缩机排量。
/// </summary>
@@ -775,5 +739,7 @@ namespace CapMachine.Wpf.Services
displacement_cc = defaultDisplacementCc;
return true;
}
}
}

View File

@@ -1,409 +0,0 @@
using CapMachine.Core;
using CapMachine.Wpf.PPCalculation;
using System;
namespace CapMachine.Wpf.Services
{
/// <summary>
/// 过热度 / 过冷度联合计算的输入数据。
/// </summary>
/// <remarks>
/// 该输入对象只承载计算所需的原始测点数据,不负责任何单位换算或有效性判断。
/// 计算时各字段的单位约定如下:
/// - <see cref="SuctionPressureBarA"/>:吸气压力,单位 BarA绝压
/// - <see cref="SuctionTemperatureC"/>:吸气温度,单位 ℃
/// - <see cref="LiquidPressureBarA"/>:膨胀阀前液路压力,单位 BarA绝压
/// - <see cref="LiquidTemperatureC"/>:膨胀阀前液路温度,单位 ℃
/// </remarks>
public sealed class PPCSuperheatSubcoolCalculationInput
{
/// <summary>
/// 吸气压力,单位 BarA绝压
/// </summary>
public double SuctionPressureBarA { get; set; }
/// <summary>
/// 吸气温度,单位 ℃。
/// </summary>
public double SuctionTemperatureC { get; set; }
/// <summary>
/// 液路压力(膨胀阀前压力),单位 BarA绝压
/// </summary>
public double LiquidPressureBarA { get; set; }
/// <summary>
/// 液路温度(膨胀阀前温度),单位 ℃。
/// </summary>
public double LiquidTemperatureC { get; set; }
}
/// <summary>
/// 过热度 / 过冷度联合计算结果。
/// </summary>
/// <remarks>
/// 本类仅承载计算后的结果值,默认初始化为 <see cref="double.NaN"/>
/// 以便调用方区分“尚未赋值”和“计算结果为有效数值”两种状态。
/// </remarks>
public sealed class PPCSuperheatSubcoolCalculationResult
{
/// <summary>
/// 过热度结果,单位 K。
/// 数值公式:吸气实际温度 - 吸气压力对应饱和温度。
/// </summary>
public double SuperheatK { get; set; } = double.NaN;
/// <summary>
/// 过冷度结果,单位 K。
/// 数值公式:液路实际温度 - 液路压力对应饱和温度。
/// </summary>
public double SubcoolK { get; set; } = double.NaN;
}
/// <summary>
/// 过热度 / 过冷度独立计算类。
/// </summary>
/// <remarks>
/// 该类只负责过热度和过冷度的数学/物性计算本身,不直接访问标签,也不负责写回 UI 或实时数据。
/// 为避免不同计算类共享同一套底层实现后在后续维护中相互影响,
/// 本类将自身所需的 REFPROP 初始化与饱和温度查询过程内聚在类内私有 support 中。
///
/// 当前实现保持与原有 <c>PPCService</c> 中的流程一致:
/// 1. 压力由 BarA 换算到 MPa
/// 2. 通过 SATP 按压力求饱和温度 <c>Tsat</c>
/// 3. 将 REFPROP 返回的 K 温度换算回 ℃
/// 4. 用“实测温度 - 饱和温度”得到过热度/过冷度
/// </remarks>
public sealed class PPCSuperheatSubcoolCalculator
{
/// <summary>
/// 底层物性计算支持对象。
/// 提供 REFPROP 初始化、SATP 饱和性质查询等公共能力。
/// </summary>
private readonly LocalCalculationSupport _support;
/// <summary>
/// 初始化过热度 / 过冷度计算类。
/// </summary>
public PPCSuperheatSubcoolCalculator(object refpropLock)
{
_support = new LocalCalculationSupport(refpropLock);
}
/// <summary>
/// 一次性计算过热度和过冷度。
/// </summary>
/// <param name="input">输入测点数据,包含吸气侧和液路侧压力/温度。</param>
/// <param name="result">输出计算结果对象,成功时包含过热度与过冷度。</param>
/// <param name="error">失败原因。任一子步骤失败时返回具体错误描述。</param>
/// <returns>
/// - <see langword="true"/>:过热度和过冷度都计算成功
/// - <see langword="false"/>:任一结果计算失败
/// </returns>
public bool TryCalculate(PPCSuperheatSubcoolCalculationInput input, out PPCSuperheatSubcoolCalculationResult result, out string error)
{
// 先创建结果对象,并将错误文本置空,保持 TryXXX 风格的一致性。
result = new PPCSuperheatSubcoolCalculationResult();
error = string.Empty;
// 先算过热度。
// 只要过热度失败,就直接结束本次联合计算,并把失败原因透传给调用方。
if (!TryCalculateSuperheatK(input.SuctionPressureBarA, input.SuctionTemperatureC, out var superheatK, out var superheatErr))
{
error = superheatErr;
return false;
}
// 再算过冷度。
// 保持与原逻辑一致:两个结果都必须成功,联合计算才算成功。
if (!TryCalculateSubcoolK(input.LiquidPressureBarA, input.LiquidTemperatureC, out var subcoolK, out var subcoolErr))
{
error = subcoolErr;
return false;
}
// 两个结果均成功后,写入输出对象。
result.SuperheatK = superheatK;
result.SubcoolK = subcoolK;
return true;
}
/// <summary>
/// 计算过热度,单位 K。
/// </summary>
/// <param name="suctionPressureBarA">吸气压力,单位 BarA绝压。</param>
/// <param name="suctionTemperatureC">吸气温度,单位 ℃。</param>
/// <param name="superheatK">过热度输出,单位 K。</param>
/// <param name="error">失败原因,如 REFPROP 未初始化、SATP 查询失败或结果异常。</param>
/// <returns>是否计算成功。</returns>
/// <remarks>
/// 计算过程保持与原 <c>PPCService</c> 一致:
/// 1. 吸气压力由 BarA 转为 MPa
/// 2. 用 SATP 按压力求出该压力下的饱和温度 <c>Tsat</c>K
/// 3. 把饱和温度换算为 ℃
/// 4. 过热度 = 吸气实测温度 - 饱和温度
/// </remarks>
public bool TryCalculateSuperheatK(double suctionPressureBarA, double suctionTemperatureC, out double superheatK, out string error)
{
// 默认输出 NaN表示当前尚未得到有效结果。
superheatK = double.NaN;
error = string.Empty;
if (double.IsNaN(suctionPressureBarA) || double.IsInfinity(suctionPressureBarA) || suctionPressureBarA <= 0)
{
error = "吸气压力无效";
return false;
}
// REFPROP 相关函数调用前先做幂等初始化。
if (!_support.EnsureRefpropInitialized(out var initErr))
{
error = initErr;
return false;
}
// 现有物性 helper 约定输入压力为 MPa因此这里把 BarA 转成 MPa。
double pressureMPa = suctionPressureBarA * 0.1;
if (pressureMPa <= 0)
{
error = "吸气压力无效";
return false;
}
// 按压力求饱和温度。
// 本计算只关心 Tsat因此 Dl/Dv 结果不使用,用 out _ 丢弃。
if (!_support.TrySATP_SaturationByP_MPa(pressureMPa, out var tSatK, out _, out _, out var satErr))
{
error = satErr;
return false;
}
// REFPROP 返回的饱和温度是 K这里换算成 ℃后再与实测温度做差。
// 公式与原实现完全一致Superheat = T_actual - T_sat。
superheatK = suctionTemperatureC - (tSatK - 273.15);
// 防御性检查:避免把 NaN / Infinity 写回上层标签。
if (double.IsNaN(superheatK) || double.IsInfinity(superheatK))
{
error = "过热度结果异常";
return false;
}
if (Math.Abs(superheatK) > 100.0)
{
error = $"过热度结果超范围: {superheatK}";
return false;
}
return true;
}
/// <summary>
/// 计算过冷度,单位 K。
/// </summary>
/// <param name="liquidPressureBarA">液路压力(膨胀阀前压力),单位 BarA绝压。</param>
/// <param name="liquidTemperatureC">液路温度(膨胀阀前温度),单位 ℃。</param>
/// <param name="subcoolK">过冷度输出,单位 K。</param>
/// <param name="error">失败原因,如 REFPROP 未初始化、SATP 查询失败或结果异常。</param>
/// <returns>是否计算成功。</returns>
/// <remarks>
/// 当前实现与原有逻辑保持一致,公式写法为:
/// <c>Subcool = T_actual - T_sat</c>
/// 即“液路实测温度减去对应压力下的饱和温度”。
/// </remarks>
public bool TryCalculateSubcoolK(double liquidPressureBarA, double liquidTemperatureC, out double subcoolK, out string error)
{
// 默认输出 NaN表示当前尚未得到有效结果。
subcoolK = double.NaN;
error = string.Empty;
if (double.IsNaN(liquidPressureBarA) || double.IsInfinity(liquidPressureBarA) || liquidPressureBarA <= 0)
{
error = "液路压力无效";
return false;
}
// REFPROP 相关函数调用前先做幂等初始化。
if (!_support.EnsureRefpropInitialized(out var initErr))
{
error = initErr;
return false;
}
// 现有 SATP helper 约定压力单位为 MPa因此先由 BarA 转成 MPa。
double pressureMPa = liquidPressureBarA * 0.1;
if (pressureMPa <= 0)
{
error = "液路压力无效";
return false;
}
// 查询该液路压力下的饱和温度 Tsat。
if (!_support.TrySATP_SaturationByP_MPa(pressureMPa, out var tSatK, out _, out _, out var satErr))
{
error = satErr;
return false;
}
// 与原实现保持一致:直接用实测液路温度减去饱和温度。
subcoolK = liquidTemperatureC - (tSatK - 273.15);
// 防御性检查:避免异常数值继续上传。
if (double.IsNaN(subcoolK) || double.IsInfinity(subcoolK))
{
error = "过冷度结果异常";
return false;
}
if (Math.Abs(subcoolK) > 100.0)
{
error = $"过冷度结果超范围: {subcoolK}";
return false;
}
return true;
}
/// <summary>
/// 过热度 / 过冷度计算类私有的底层物性支持实现。
/// </summary>
/// <remarks>
/// 该实现只服务当前计算类,目的是把本类依赖的 REFPROP 调用过程固定在本文件内部,
/// 避免后续为了其他计算类调整共享 support 时,影响已经验算通过的过热度 / 过冷度结果。
/// </remarks>
private sealed class LocalCalculationSupport : IPPCCalculationSupport
{
private readonly object _refpropLock;
private static volatile bool _rpInitialized;
public LocalCalculationSupport(object refpropLock)
{
_refpropLock = refpropLock ?? throw new ArgumentNullException(nameof(refpropLock));
}
public bool EnsureRefpropInitialized(out string error)
{
error = string.Empty;
if (_rpInitialized)
{
return true;
}
try
{
lock (_refpropLock)
{
if (_rpInitialized)
{
return true;
}
string hpath = ConfigHelper.GetValue("FluidsPath");
if (string.IsNullOrWhiteSpace(hpath))
{
hpath = @".\PPCalculation\REFPROP\FLUIDS";
}
string configuredCryogen = ConfigHelper.GetValue("Cryogen");
if (string.IsNullOrWhiteSpace(configuredCryogen))
{
configuredCryogen = "R134a";
}
string hfldCore = configuredCryogen.Equals("R134a", StringComparison.OrdinalIgnoreCase)
? "R134A.FLD"
: "R134A.FLD";
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;
long hfmixLen = hfmix.Length;
long hrfLen = hrf.Length;
long 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;
return true;
}
}
catch (Exception ex)
{
error = $"REFPROP 初始化异常: {ex.Message}";
_rpInitialized = false;
return false;
}
}
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;
double[] x = new double[20];
x[0] = 1.0;
long kph = 1;
double Dl = 0;
double Dv = 0;
double[] xliq = new double[20];
double[] xvap = new double[20];
long ierr = 0;
long 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;
}
public bool TryTPRHO_VaporDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) => throw new NotSupportedException();
public bool TryTHERM_VaporEntropy_ByTD(double temperatureC, double densityMolPerL, out double entropy_kJ_per_kgK, out string error) => throw new NotSupportedException();
public bool TryTPRHO_LiquidDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) => throw new NotSupportedException();
public bool TryTHERM_LiquidEnthalpy_ByTD(double temperatureC, double densityMolPerL, out double h_liq_kJ_per_kg, out string error) => throw new NotSupportedException();
public bool TryTHERM_Enthalpy_kJkg_ByT_K_D(double temperatureK, double densityMolPerL, out double h_kJ_per_kg, out string error) => throw new NotSupportedException();
public bool TryConvertMolarDensityToSpecificVolume(double d_molL, out double v_m3kg, out string error) => throw new NotSupportedException();
public bool TryGetIsentropicOutletEnthalpy_h2s_ByP2AndS1_BarA(double dischargePressureBarA, double suctionEntropy_kJkgK, out double h2s_kJkg, out string error) => throw new NotSupportedException();
}
}
}

File diff suppressed because it is too large Load Diff