891 lines
34 KiB
C#
891 lines
34 KiB
C#
using CapMachine.Core;
|
||
using CapMachine.Wpf.Models.PPCalc;
|
||
using CapMachine.Wpf.Models.Tag;
|
||
using CapMachine.Wpf.PPCalculation;
|
||
using ImTools;
|
||
using MaterialDesignThemes.Wpf;
|
||
using NLog;
|
||
using NPOI.XWPF.UserModel;
|
||
using Prism.Events;
|
||
using Prism.Mvvm;
|
||
using Prism.Services.Dialogs;
|
||
using SixLabors.ImageSharp.ColorSpaces;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
|
||
namespace CapMachine.Wpf.Services
|
||
{
|
||
/// <summary>
|
||
/// 物性计算的服务
|
||
/// </summary>
|
||
public class PPCService : BindableBase
|
||
{
|
||
/// <summary>
|
||
/// 计算扫描 Task
|
||
/// </summary>
|
||
private static Task CalcTask { get; set; }
|
||
private static int _scanLoopStarted;
|
||
public ConfigService ConfigService { get; }
|
||
|
||
private IEventAggregator _EventAggregator { get; set; }
|
||
public DataRecordService DataRecordService { get; }
|
||
public SysRunService SysRunServer { get; }
|
||
public ILogService Logger { get; }
|
||
public MachineRtDataService MachineRtDataService { get; }
|
||
public IDialogService DialogService { get; }
|
||
|
||
private readonly PPCSuperheatSubcoolCalculator _superheatSubcoolCalculator;
|
||
private readonly PPCThermodynamicSixResultsCalculator _thermodynamicSixResultsCalculator;
|
||
|
||
/// <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("排气温度[℃]");
|
||
|
||
HVPwTag = TagManager.DicTags.GetValueOrDefault("HV[W]");
|
||
|
||
InhPressTag = TagManager.DicTags.GetValueOrDefault("吸气压力[BarA]");
|
||
|
||
InhTempTag = TagManager.DicTags.GetValueOrDefault("吸气温度[℃]");
|
||
|
||
//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]")!;
|
||
|
||
TxvFrTempTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前温度[℃]");
|
||
|
||
TxvFrPressTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前压力[BarA]");
|
||
|
||
LiqRefFlowTag = TagManager.DicTags.GetValueOrDefault("液冷媒流量[kg/h]");
|
||
|
||
//kg/h
|
||
|
||
VRVTag = TagManager.DicTags.GetValueOrDefault("冷媒流量[kg/h]");
|
||
|
||
//润滑油流量
|
||
|
||
LubeFlowTag = TagManager.DicTags.GetValueOrDefault("润滑油流量[kg/h]");
|
||
|
||
|
||
//Cond1TempTag = TagManager.DicTags.GetValueOrDefault("冷凝器出口水温[℃]");
|
||
//CondInTempTag = TagManager.DicTags.GetValueOrDefault("冷凝器进口温度[℃]");
|
||
|
||
//Superheat = TagManager.DicTags.GetValueOrDefault("过热度[K]");
|
||
//Subcool = TagManager.DicTags.GetValueOrDefault("过冷度[K]");
|
||
|
||
Superheat = TagManager.DicTags.GetValueOrDefault("过热度[K]");
|
||
|
||
Subcool = TagManager.DicTags.GetValueOrDefault("过冷度[K]");
|
||
|
||
HeatingCapacity = TagManager.DicTags.GetValueOrDefault("制热量Qh[W]");
|
||
COPHeat = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制热)");
|
||
IsentrpEff = TagManager.DicTags.GetValueOrDefault("等熵效率ns[%]");
|
||
CoolCapacity = TagManager.DicTags.GetValueOrDefault("制冷量Qc[W]");
|
||
COPCool = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制冷)");
|
||
VoltricEff = TagManager.DicTags.GetValueOrDefault("容积效率nv[%]");
|
||
|
||
|
||
SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath");
|
||
SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen");
|
||
|
||
_superheatSubcoolCalculator = new PPCSuperheatSubcoolCalculator();
|
||
_thermodynamicSixResultsCalculator = new PPCThermodynamicSixResultsCalculator();
|
||
|
||
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 ITag InhPressTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 转速标签
|
||
/// </summary>
|
||
public ITag? SpeedTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 排气压力
|
||
/// </summary>
|
||
public ITag? ExPressTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 排气温度
|
||
/// </summary>
|
||
public ITag? ExTempTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 压缩机功率(HV 电源)
|
||
/// </summary>
|
||
public ITag? HVPwTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 吸气温度
|
||
/// </summary>
|
||
public ITag InhTempTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 液体阀前温度
|
||
/// </summary>
|
||
public ITag TxvFrTempTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 液体阀前压力
|
||
/// </summary>
|
||
public ITag TxvFrPressTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 过热度
|
||
/// </summary>
|
||
public ITag Superheat { get; set; }
|
||
|
||
/// <summary>
|
||
/// 过冷度
|
||
/// </summary>
|
||
public ITag Subcool { get; set; }
|
||
|
||
/// <summary>
|
||
/// 干度(无量纲 [-])
|
||
/// </summary>
|
||
public ITag DrynessTag { get; set; }
|
||
|
||
private double _DrynessTag2Value;
|
||
/// <summary>
|
||
/// 干度2(无量纲 [-])
|
||
/// </summary>
|
||
public double DrynessTag2Value
|
||
{
|
||
get { return _DrynessTag2Value; }
|
||
set { _DrynessTag2Value = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 气路阀前压力(BarA)
|
||
/// </summary>
|
||
public ITag GasPreValvePressTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 气路阀前温度(℃)
|
||
/// </summary>
|
||
public ITag GasPreValveTempTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 冷媒流量kg/h
|
||
/// </summary>
|
||
public ITag VRVTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 液体流量(kg/h)
|
||
/// 液冷媒流量kg/h=液体流量kg/h
|
||
/// </summary>
|
||
public ITag LiqRefFlowTag { get; set; }
|
||
|
||
/// <summary>
|
||
/// 润滑油流量(kg/h)
|
||
/// </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; }
|
||
|
||
|
||
/// <summary>
|
||
/// 风量数据-乘以系数的后的最终结果
|
||
/// </summary>
|
||
private double AirVolumeData { get; set; } = 0.0;
|
||
|
||
/// <summary>
|
||
/// 风量数据-公式计算的原始数据
|
||
/// </summary>
|
||
private double AirVolumeDataSource { get; set; } = 0;
|
||
|
||
/// <summary>
|
||
/// 风量喷嘴启用信息数据
|
||
/// </summary>
|
||
private string RozzleEnableInfo { get; set; } = "";
|
||
|
||
/// <summary>
|
||
/// 启用计算
|
||
/// </summary>
|
||
private bool RtCalcEnable { get; set; } = true;
|
||
|
||
/// <summary>
|
||
/// 触发日志
|
||
/// </summary>
|
||
private bool DebugLog { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// PLC扫描线程
|
||
/// </summary>
|
||
private void RtScanDeviceStart()
|
||
{
|
||
if (System.Threading.Interlocked.CompareExchange(ref _scanLoopStarted, 1, 0) != 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
CalcTask = Task.Run(async () =>
|
||
{
|
||
while (RtCalcEnable)
|
||
{
|
||
await Task.Delay(1000);
|
||
try
|
||
{
|
||
if (!TryUpdateSuperheatAndSubcool(out var superheatSubcoolErr))
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(superheatSubcoolErr))
|
||
{
|
||
Logger?.Error($"过热度/过冷度计算失败: {superheatSubcoolErr}");
|
||
}
|
||
}
|
||
|
||
if (TryUpdateThermodynamicSixResults(out var thermoErr))
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(thermoErr))
|
||
{
|
||
Logger?.Error($"六个物性结果计算警告: {thermoErr}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(thermoErr))
|
||
{
|
||
Logger?.Error($"六个物性结果计算失败: {thermoErr}");
|
||
}
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新过热度与过冷度结果。
|
||
/// </summary>
|
||
/// <param name="error">
|
||
/// 错误汇总输出。
|
||
/// 当两个结果都失败时,返回拼接后的失败原因;
|
||
/// 当仅一个结果失败时,只返回该项失败原因;
|
||
/// 当至少有一项成功更新时,方法返回 <see langword="true"/>。
|
||
/// </param>
|
||
/// <returns>
|
||
/// 是否至少成功更新了一个结果。
|
||
/// 该方法保持原有行为:过热度和过冷度彼此独立,只要其中之一成功就返回 <see langword="true"/>。
|
||
/// </returns>
|
||
private bool TryUpdateSuperheatAndSubcool(out string error)
|
||
{
|
||
error = string.Empty;
|
||
bool updated = false;
|
||
StringBuilder errorBuilder = new StringBuilder();
|
||
|
||
// 第一段:收集过热度所需标签,并把实时值直接交给独立计算类。
|
||
// PPCService 只负责“取值 + 回写”,具体热力学过程由 PPCSuperheatSubcoolCalculator 承担。
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按图片的最终流程计算干度:
|
||
/// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml)
|
||
/// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1]
|
||
///
|
||
/// 入参单位:
|
||
/// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg
|
||
/// - mGas_kg_h, mLiq_kg_h 均为 kg/h
|
||
/// 返回:true 表示成功,输出 x∈[0,1] 与 h_mix;false 返回 error
|
||
/// </summary>
|
||
/// <param name="hVap_kJkg">气相质量焓 h_vap [kJ/kg]</param>
|
||
/// <param name="hLiq_kJkg">液相质量焓 h_liq [kJ/kg]</param>
|
||
/// <param name="mGas_kg_h">气体质量流量 mg [kg/h]</param>
|
||
/// <param name="mLiq_kg_h">液体质量流量 ml [kg/h]</param>
|
||
/// <param name="hSatL_kJkg">饱和液质量焓 h_l [kJ/kg]</param>
|
||
/// <param name="hSatV_kJkg">饱和气质量焓 h_v [kJ/kg]</param>
|
||
/// <param name="dryness">输出干度 x ∈ [0,1]</param>
|
||
/// <param name="hMix_kJkg">输出混合后总比焓 h_mix [kJ/kg]</param>
|
||
/// <param name="error">Err</param>
|
||
/// <returns></returns>
|
||
public bool TryComputeDrynessByEnthalpy(
|
||
double hVap_kJkg, // 气相质量焓 h_vap [kJ/kg]
|
||
double hLiq_kJkg, // 液相质量焓 h_liq [kJ/kg]
|
||
double mGas_kg_h, // 气体质量流量 mg [kg/h]
|
||
double mLiq_kg_h, // 液体质量流量 ml [kg/h]
|
||
double hSatL_kJkg, // 饱和液质量焓 h_l [kJ/kg]
|
||
double hSatV_kJkg, // 饱和气质量焓 h_v [kJ/kg]
|
||
out double dryness, // 输出干度 x ∈ [0,1]
|
||
out double hMix_kJkg, // 输出混合后总比焓 h_mix [kJ/kg]
|
||
out string error)
|
||
{
|
||
dryness = double.NaN;
|
||
hMix_kJkg = double.NaN;
|
||
error = string.Empty;
|
||
|
||
// 1) 合法性校验
|
||
if (double.IsNaN(hVap_kJkg) || double.IsNaN(hLiq_kJkg) || double.IsNaN(hSatL_kJkg) || double.IsNaN(hSatV_kJkg))
|
||
{
|
||
error = "输入焓值存在 NaN";
|
||
return false;
|
||
}
|
||
if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h))
|
||
{
|
||
error = "输入质量流量存在 NaN";
|
||
return false;
|
||
}
|
||
// 负值处理:小于 0 视为 0(避免传感器噪声或符号错误影响)
|
||
double mg = Math.Max(0.0, mGas_kg_h);
|
||
double ml = Math.Max(0.0, mLiq_kg_h);
|
||
double mSum = mg + ml;
|
||
if (mSum <= 0)
|
||
{
|
||
error = "气液质量流量之和为 0,无法进行加权混合焓计算";
|
||
return false;
|
||
}
|
||
|
||
// 2) 质量流量加权混合焓(严格按图片:上、下两路相加后除以总流量)
|
||
hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum;
|
||
|
||
// 3) 干度计算:x = (h_mix - h_l) / (h_v - h_l)
|
||
double denom = (hSatV_kJkg - hSatL_kJkg);
|
||
const double eps = 1e-9;
|
||
if (Math.Abs(denom) < eps)
|
||
{
|
||
error = "饱和气/液焓差过小,无法计算干度(可能接近临界点或输入异常)";
|
||
return false;
|
||
}
|
||
|
||
double x = (hMix_kJkg - hSatL_kJkg) / denom;
|
||
|
||
// 4) 限幅到 [0,1]
|
||
if (double.IsNaN(x) || double.IsInfinity(x))
|
||
{
|
||
error = "干度计算结果异常(NaN/Inf)";
|
||
return false;
|
||
}
|
||
dryness = Math.Min(1.0, Math.Max(0.0, x));
|
||
return true;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 按图片的最终流程计算干度2:
|
||
/// 干度2的计算:临时用作为对比使用
|
||
/// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml)
|
||
/// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1]
|
||
///
|
||
/// 入参单位:
|
||
/// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg
|
||
/// - mGas_kg_h, mLiq_kg_h 均为 kg/h
|
||
/// 返回:true 表示成功,输出 x∈[0,1] 与 h_mix;false 返回 error
|
||
/// </summary>
|
||
/// <param name="hVap_kJkg">气相质量焓 h_vap [kJ/kg]</param>
|
||
/// <param name="hLiq_kJkg">液相质量焓 h_liq [kJ/kg]</param>
|
||
/// <param name="mGas_kg_h">气体质量流量 mg [kg/h]</param>
|
||
/// <param name="mLiq_kg_h">液体质量流量 ml [kg/h]</param>
|
||
/// <param name="hSatL_kJkg">饱和液质量焓 h_l [kJ/kg]</param>
|
||
/// <param name="hSatV_kJkg">饱和气质量焓 h_v [kJ/kg]</param>
|
||
/// <param name="dryness">输出干度 x ∈ [0,1]</param>
|
||
/// <param name="hMix_kJkg">输出混合后总比焓 h_mix [kJ/kg]</param>
|
||
/// <param name="error">Err</param>
|
||
/// <returns></returns>
|
||
public bool TryComputeDrynessByEnthalpy2(
|
||
double hVap_kJkg, // 气相质量焓 h_vap [kJ/kg]
|
||
double hLiq_kJkg, // 液相质量焓 h_liq [kJ/kg]
|
||
double mGas_kg_h, // 气体质量流量 mg [kg/h]
|
||
double lubeFlow_kg_h, // 润滑油流量 mg [kg/h]
|
||
double mLiq_kg_h, // 液体质量流量 ml [kg/h]
|
||
double hSatL_kJkg, // 饱和液质量焓 h_l [kJ/kg]
|
||
double hSatV_kJkg, // 饱和气质量焓 h_v [kJ/kg]
|
||
out double dryness, // 输出干度 x ∈ [0,1]
|
||
out double hMix_kJkg, // 输出混合后总比焓 h_mix [kJ/kg]
|
||
out string error)
|
||
{
|
||
dryness = double.NaN;
|
||
hMix_kJkg = double.NaN;
|
||
error = string.Empty;
|
||
|
||
// 1) 合法性校验
|
||
if (double.IsNaN(hVap_kJkg) || double.IsNaN(hLiq_kJkg) || double.IsNaN(hSatL_kJkg) || double.IsNaN(hSatV_kJkg))
|
||
{
|
||
error = "输入焓值存在 NaN";
|
||
return false;
|
||
}
|
||
if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h))
|
||
{
|
||
error = "输入质量流量存在 NaN";
|
||
return false;
|
||
}
|
||
// 负值处理:小于 0 视为 0(避免传感器噪声或符号错误影响)
|
||
//double mg1 = Math.Max(0.0, mGas_kg_h);
|
||
double mg = Math.Max(0.0, mGas_kg_h) + Math.Max(0.0, lubeFlow_kg_h); // 这个是改动 气体流量再加上润滑油流量
|
||
double ml = Math.Max(0.0, mLiq_kg_h);
|
||
double mSum = mg + ml;
|
||
if (mSum <= 0)
|
||
{
|
||
error = "气液质量流量之和为 0,无法进行加权混合焓计算";
|
||
return false;
|
||
}
|
||
|
||
// 2) 质量流量加权混合焓(严格按图片:上、下两路相加后除以总流量)
|
||
hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum;
|
||
|
||
// 3) 干度计算:x = (h_mix - h_l) / (h_v - h_l)
|
||
double denom = (hSatV_kJkg - hSatL_kJkg);
|
||
const double eps = 1e-9;
|
||
if (Math.Abs(denom) < eps)
|
||
{
|
||
error = "饱和气/液焓差过小,无法计算干度(可能接近临界点或输入异常)";
|
||
return false;
|
||
}
|
||
|
||
double x = (hMix_kJkg - hSatL_kJkg) / denom;
|
||
|
||
// 4) 限幅到 [0,1]
|
||
if (double.IsNaN(x) || double.IsInfinity(x))
|
||
{
|
||
error = "干度计算结果异常(NaN/Inf)";
|
||
return false;
|
||
}
|
||
dryness = Math.Min(1.0, Math.Max(0.0, x));
|
||
return true;
|
||
}
|
||
|
||
|
||
|
||
|
||
///制热量、压缩机性能系数COP(制热)、等熵效率、制冷量、压缩机性能系数COP(制冷)、容积效率 计算
|
||
#region
|
||
|
||
private double _HeatingCapacityQh_kW;
|
||
/// <summary>
|
||
/// 制热量 Qh [kW]
|
||
/// </summary>
|
||
public double HeatingCapacityQh_kW
|
||
{
|
||
get { return _HeatingCapacityQh_kW; }
|
||
set { _HeatingCapacityQh_kW = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
private double _COPHeating;
|
||
/// <summary>
|
||
/// 压缩机性能系数 COP(制热)[-]
|
||
/// </summary>
|
||
public double COPHeating
|
||
{
|
||
get { return _COPHeating; }
|
||
set { _COPHeating = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
private double _IsentropicEfficiencyPct;
|
||
/// <summary>
|
||
/// 等熵效率 ηs [%]
|
||
/// </summary>
|
||
public double IsentropicEfficiencyPct
|
||
{
|
||
get { return _IsentropicEfficiencyPct; }
|
||
set { _IsentropicEfficiencyPct = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
private double _CoolingCapacityQc_kW;
|
||
/// <summary>
|
||
/// 制冷量 Qc [kW]
|
||
/// </summary>
|
||
public double CoolingCapacityQc_kW
|
||
{
|
||
get { return _CoolingCapacityQc_kW; }
|
||
set { _CoolingCapacityQc_kW = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
private double _COPCooling;
|
||
/// <summary>
|
||
/// 压缩机性能系数 COP(制冷)[-]
|
||
/// </summary>
|
||
public double COPCooling
|
||
{
|
||
get { return _COPCooling; }
|
||
set { _COPCooling = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
private double _VolumetricEfficiencyPct;
|
||
/// <summary>
|
||
/// 容积效率 ηv [%]
|
||
/// </summary>
|
||
public double VolumetricEfficiencyPct
|
||
{
|
||
get { return _VolumetricEfficiencyPct; }
|
||
set { _VolumetricEfficiencyPct = value; RaisePropertyChanged(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按流程图更新:制热量、COP(制热)、等熵效率、制冷量、COP(制冷)、容积效率。
|
||
/// </summary>
|
||
/// <param name="error">
|
||
/// 错误/警告信息输出。
|
||
/// - 当方法返回 <see langword="false"/> 时,<paramref name="error"/> 为失败原因,调用方应视为本周期计算无效。
|
||
/// - 当方法返回 <see langword="true"/> 但 <paramref name="error"/> 非空时,表示仅部分结果无法计算(例如缺少排量导致容积效率为 NaN)。
|
||
/// </param>
|
||
/// <returns>
|
||
/// 是否成功完成本周期的结果更新。
|
||
/// - <see langword="true"/>:至少已成功更新 Qh/Qc/COP/ηs 等主要结果;容积效率可能因缺失排量而为 NaN。
|
||
/// - <see langword="false"/>:关键输入或 REFPROP 计算失败,本周期结果不更新。
|
||
/// </returns>
|
||
private bool TryUpdateThermodynamicSixResults(out string error)
|
||
{
|
||
error = string.Empty;
|
||
|
||
// 先把本周期所需的标签值组装为输入模型。
|
||
// PPCService 在这里负责完成“标签 -> 领域输入对象”的映射,
|
||
// 从而把计算类和 UI/标签层解耦。
|
||
if (!TryCreateThermodynamicSixResultsInput(out var input, out var inputErr))
|
||
{
|
||
error = inputErr;
|
||
return false;
|
||
}
|
||
|
||
// 把输入模型交给独立计算类统一完成 6 个结果的热力计算。
|
||
// 此处不再在 PPCService 中展开公式,只保留流程调度职责。
|
||
if (!_thermodynamicSixResultsCalculator.TryCalculate(input, out var result, out error))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 先把结果同步到服务属性,供界面绑定或其他业务读取。
|
||
HeatingCapacityQh_kW = result.HeatingCapacityQh_kW;
|
||
CoolingCapacityQc_kW = result.CoolingCapacityQc_kW;
|
||
COPHeating = result.COPHeating;
|
||
COPCooling = result.COPCooling;
|
||
IsentropicEfficiencyPct = result.IsentropicEfficiencyPct;
|
||
|
||
// 再把结果回写到对应工程量标签
|
||
HeatingCapacity.EngPvValue = HeatingCapacityQh_kW * 1000;
|
||
COPHeat.EngPvValue = COPHeating;
|
||
IsentrpEff.EngPvValue = IsentropicEfficiencyPct;
|
||
CoolCapacity.EngPvValue = CoolingCapacityQc_kW * 1000;
|
||
COPCool.EngPvValue = COPCooling;
|
||
|
||
// 容积效率在旧逻辑中允许单独失败;
|
||
// 因此只有拿到有效数值时才回写,避免覆盖已有结果为 NaN/Inf。
|
||
if (!double.IsNaN(result.VolumetricEfficiencyPct) && !double.IsInfinity(result.VolumetricEfficiencyPct))
|
||
{
|
||
VolumetricEfficiencyPct = result.VolumetricEfficiencyPct;
|
||
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] 功率标签";
|
||
return false;
|
||
}
|
||
if (VRVTag == null)
|
||
{
|
||
error = "缺少总流量(冷媒流量)标签";
|
||
return false;
|
||
}
|
||
if (LubeFlowTag == null)
|
||
{
|
||
error = "缺少油流量标签";
|
||
return false;
|
||
}
|
||
if (InhPressTag == null || InhTempTag == null)
|
||
{
|
||
error = "缺少吸气压力/吸气温度标签";
|
||
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 var displacementErr))
|
||
{
|
||
error = displacementErr;
|
||
return false;
|
||
}
|
||
|
||
// 将实时标签值与配置值组装为独立计算类可直接消费的输入对象。
|
||
input = new PPCThermodynamicSixResultsCalculationInput
|
||
{
|
||
CompressorPowerW = HVPwTag.EngPvValue,
|
||
TotalMassFlowKgPerHour = VRVTag.EngPvValue,
|
||
OilMassFlowKgPerHour = LubeFlowTag.EngPvValue,
|
||
SuctionPressureBarA = InhPressTag.EngPvValue,
|
||
SuctionTemperatureC = InhTempTag.EngPvValue,
|
||
DischargePressureBarA = ExPressTag.EngPvValue,
|
||
DischargeTemperatureC = ExTempTag.EngPvValue,
|
||
LiquidPressureBarA = TxvFrPressTag.EngPvValue,
|
||
LiquidTemperatureC = TxvFrTempTag.EngPvValue,
|
||
CompressorSpeedRpm = SpeedTag.EngPvValue,
|
||
CompressorDisplacementCc = displacementCc
|
||
};
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取压缩机排量。
|
||
/// </summary>
|
||
/// <param name="displacement_cc">排量输出,单位 cc(cm³/rev)。</param>
|
||
/// <param name="error">失败原因(未配置、解析失败、数值不合法)。</param>
|
||
/// <returns>是否获取成功。</returns>
|
||
private bool TryGetCompressorDisplacement_cc(out double displacement_cc, out string error)
|
||
{
|
||
displacement_cc = double.NaN;
|
||
error = string.Empty;
|
||
const double defaultDisplacementCc = 35d;
|
||
|
||
if (TryGetCompressorDisplacementFromCurrentExperiment(out displacement_cc, out var experimentError))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (TryGetCompressorDisplacementFromConfig(out displacement_cc, out var configError))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
displacement_cc = defaultDisplacementCc;
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从当前选中的实验信息中读取并解析压缩机排量。
|
||
/// </summary>
|
||
/// <param name="displacement_cc">排量输出,单位 cc。</param>
|
||
/// <param name="error">失败原因。</param>
|
||
/// <returns>是否读取成功。</returns>
|
||
private bool TryGetCompressorDisplacementFromCurrentExperiment(out double displacement_cc, out string error)
|
||
{
|
||
displacement_cc = double.NaN;
|
||
error = string.Empty;
|
||
|
||
if (ConfigService.CurExpInfo == null)
|
||
{
|
||
error = "当前未选择实验信息,无法从试验信息读取压缩机排量";
|
||
return false;
|
||
}
|
||
|
||
var displacementText = ConfigService.CurExpInfo.CapDisplacement;
|
||
if (string.IsNullOrWhiteSpace(displacementText))
|
||
{
|
||
error = "当前试验信息未填写压缩机排量";
|
||
return false;
|
||
}
|
||
|
||
if (!TryParseCompressorDisplacementText(displacementText, out displacement_cc))
|
||
{
|
||
error = $"当前试验信息压缩机排量无法解析: {displacementText}";
|
||
return false;
|
||
}
|
||
|
||
if (displacement_cc <= 0)
|
||
{
|
||
error = $"当前试验信息压缩机排量<=0: {displacement_cc}";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从配置项读取并解析压缩机排量。
|
||
/// </summary>
|
||
/// <param name="displacement_cc">排量输出,单位 cc。</param>
|
||
/// <param name="error">失败原因。</param>
|
||
/// <returns>是否读取成功。</returns>
|
||
private bool TryGetCompressorDisplacementFromConfig(out double displacement_cc, out string error)
|
||
{
|
||
displacement_cc = double.NaN;
|
||
error = string.Empty;
|
||
|
||
const string key = "CompressorDisplacementCc";
|
||
string configValue;
|
||
try
|
||
{
|
||
configValue = ConfigHelper.GetValue(key);
|
||
}
|
||
catch
|
||
{
|
||
error = $"未配置压缩机排量,请在 App.config/appSettings 增加 {key}(单位 cc)";
|
||
return false;
|
||
}
|
||
|
||
if (!TryParseCompressorDisplacementText(configValue, out displacement_cc))
|
||
{
|
||
error = $"压缩机排量配置无法解析: {configValue}";
|
||
return false;
|
||
}
|
||
|
||
if (displacement_cc <= 0)
|
||
{
|
||
error = $"压缩机排量<=0: {displacement_cc}";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析排量文本为数值。
|
||
/// 支持纯数字与包含单位的文本(如 34.5、34.5cc、34,5 cm3)。
|
||
/// </summary>
|
||
/// <param name="displacementText">排量文本。</param>
|
||
/// <param name="displacementCc">解析后的排量数值,单位 cc。</param>
|
||
/// <returns>是否解析成功。</returns>
|
||
private static bool TryParseCompressorDisplacementText(string displacementText, out double displacementCc)
|
||
{
|
||
displacementCc = double.NaN;
|
||
if (string.IsNullOrWhiteSpace(displacementText))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var normalizedText = displacementText.Trim();
|
||
if (double.TryParse(normalizedText, out displacementCc))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (double.TryParse(normalizedText, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out displacementCc))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
var match = System.Text.RegularExpressions.Regex.Match(normalizedText, @"[-+]?\d+(?:[\.,]\d+)?");
|
||
if (!match.Success)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var numericText = match.Value.Replace(',', '.');
|
||
return double.TryParse(numericText, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out displacementCc);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|