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.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace CapMachine.Wpf.Services
{
///
/// 物性计算的服务
///
public class PPCService : BindableBase
{
///
/// 计算扫描 Task
///
private static Task CalcTask { get; set; }
public ConfigService ConfigService { get; }
private IEventAggregator _EventAggregator { get; set; }
public DataRecordService DataRecordService { get; }
public SysRunService SysRunServer { get; }
public ILogService Logger { get; }
public MachineRtDataService MachineRtDataService { get; }
public IDialogService DialogService { get; }
private readonly 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;
///
/// 标签中心
///
public TagManager TagManager { get; set; }
///
/// 实例化
///
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("吸气温度[℃]");
TxvFrTempTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前温度[℃]");
TxvFrPressTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前压力[BarA]");
GasPreValvePressTag = TagManager.DicTags.GetValueOrDefault("气路阀前压力[BarA]");
GasPreValveTempTag = TagManager.DicTags.GetValueOrDefault("气路阀前温度[℃]");
DrynessTag = TagManager.DicTags.GetValueOrDefault("干度[-]");
if (DrynessTag == null)
{
DrynessTag = TagManager.DicTags.GetValueOrDefault("干度");
}
VRVTag = TagManager.DicTags.GetValueOrDefault("冷媒流量[kg/h]");
if (VRVTag == null)
{
VRVTag = TagManager.DicTags.GetValueOrDefault("冷媒流量[kg/h]");
}
LiqRefFlowTag = TagManager.DicTags.GetValueOrDefault("液冷媒流量[kg/h]");
if (LiqRefFlowTag == null)
{
LiqRefFlowTag = TagManager.DicTags.GetValueOrDefault("液体流量[kg/h]");
}
LubeFlowTag = TagManager.DicTags.GetValueOrDefault("润滑油流量[kg/h]");
if (LubeFlowTag == null)
{
LubeFlowTag = TagManager.DicTags.GetValueOrDefault("润滑油流量[kg/h]");
}
Superheat = TagManager.DicTags.GetValueOrDefault("过热度[K]");
Subcool = TagManager.DicTags.GetValueOrDefault("过冷度[K]");
HeatingCapacity = TagManager.DicTags.GetValueOrDefault("制热量Qh[KW]")
?? TagManager.DicTags.GetValueOrDefault("制热量Qh[W]");
COPHeat = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制热)[K]")
?? TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制热COP)");
IsentrpEff = TagManager.DicTags.GetValueOrDefault("等熵效率ns[%]");
CoolCapacity = TagManager.DicTags.GetValueOrDefault("制冷量Qc[KW]")
?? TagManager.DicTags.GetValueOrDefault("制冷量Qc[W]");
COPCool = TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制冷)[K]")
?? TagManager.DicTags.GetValueOrDefault("压缩机性能系数(制冷COP)");
VoltricEff = TagManager.DicTags.GetValueOrDefault("容积效率nv[%]");
SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath");
SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen");
_superheatSubcoolCalculator = new PPCSuperheatSubcoolCalculator(_refpropLock);
_thermodynamicSixResultsCalculator = new PPCThermodynamicSixResultsCalculator(_refpropLock);
_enthalpyDrynessCalculator = new EnthalpyDrynessCalculator(_refpropLock);
ReloadTherdyH3TempOffset();
RtScanDeviceStart();
}
private const string TherdyH3TempOffsetConfigKey = "Therdy_H3TempOffset_C";
public void ReloadTherdyH3TempOffset()
{
double offsetC = -10.0;
try
{
string raw = ConfigHelper.GetValue(TherdyH3TempOffsetConfigKey);
if (!string.IsNullOrWhiteSpace(raw) && double.TryParse(raw, out var parsed))
{
offsetC = parsed;
}
}
catch (Exception ex)
{
Logger?.Error($"读取 {TherdyH3TempOffsetConfigKey} 失败: {ex.Message}");
}
_thermodynamicSixResultsCalculator.SetH3TempOffset_C(offsetC);
}
///
/// 当前的配置
///
public SuperHeatCoolConfigModel SuperHeatCoolConfig { get; set; } = new SuperHeatCoolConfigModel();
///
/// 保存配置信息
///
public void SaveSuperHeatCoolConfig()
{
ConfigHelper.SetValue("FluidsPath", SuperHeatCoolConfig.FluidsPath);
ConfigHelper.SetValue("Cryogen", SuperHeatCoolConfig.Cryogen);
}
///
/// 吸气压力
///
public ITag InhPressTag { get; set; }
///
/// 转速标签
///
public ITag? SpeedTag { get; set; }
///
/// 排气压力
///
public ITag? ExPressTag { get; set; }
///
/// 排气温度
///
public ITag? ExTempTag { get; set; }
///
/// 压缩机功率(HV 电源)
///
public ITag? HVPwTag { get; set; }
///
/// 吸气温度
///
public ITag InhTempTag { get; set; }
///
/// 液体阀前温度
///
public ITag TxvFrTempTag { get; set; }
///
/// 液体阀前压力
///
public ITag TxvFrPressTag { get; set; }
///
/// 过热度
///
public ITag Superheat { get; set; }
///
/// 过冷度
///
public ITag Subcool { get; set; }
///
/// 干度(无量纲 [-])
///
public ITag DrynessTag { get; set; }
private double _DrynessTag2Value;
///
/// 干度2(无量纲 [-])
///
public double DrynessTag2Value
{
get { return _DrynessTag2Value; }
set { _DrynessTag2Value = value; RaisePropertyChanged(); }
}
///
/// 气路阀前压力(BarA)
///
public ITag GasPreValvePressTag { get; set; }
///
/// 气路阀前温度(℃)
///
public ITag GasPreValveTempTag { get; set; }
///
/// 冷媒流量kg/h
///
public ITag VRVTag { get; set; }
///
/// 液体流量(kg/h)
/// 液冷媒流量kg/h=液体流量kg/h
///
public ITag LiqRefFlowTag { get; set; }
///
/// 润滑油流量(kg/h)
///
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; }
///
/// 风量数据-乘以系数的后的最终结果
///
private double AirVolumeData { get; set; } = 0.0;
///
/// 风量数据-公式计算的原始数据
///
private double AirVolumeDataSource { get; set; } = 0;
///
/// 风量喷嘴启用信息数据
///
private string RozzleEnableInfo { get; set; } = "";
///
/// 启用计算
///
private bool RtCalcEnable { get; set; } = true;
///
/// 触发日志
///
private bool DebugLog { get; set; } = false;
private double _HeatingCapacityQh_kW;
public double HeatingCapacityQh_kW
{
get { return _HeatingCapacityQh_kW; }
set { _HeatingCapacityQh_kW = value; RaisePropertyChanged(); }
}
private double _COPHeating;
public double COPHeating
{
get { return _COPHeating; }
set { _COPHeating = value; RaisePropertyChanged(); }
}
private double _IsentropicEfficiencyPct;
public double IsentropicEfficiencyPct
{
get { return _IsentropicEfficiencyPct; }
set { _IsentropicEfficiencyPct = value; RaisePropertyChanged(); }
}
private double _CoolingCapacityQc_kW;
public double CoolingCapacityQc_kW
{
get { return _CoolingCapacityQc_kW; }
set { _CoolingCapacityQc_kW = value; RaisePropertyChanged(); }
}
private double _COPCooling;
public double COPCooling
{
get { return _COPCooling; }
set { _COPCooling = value; RaisePropertyChanged(); }
}
private double _VolumetricEfficiencyPct;
public double VolumetricEfficiencyPct
{
get { return _VolumetricEfficiencyPct; }
set { _VolumetricEfficiencyPct = value; RaisePropertyChanged(); }
}
///
/// PLC扫描线程
///
private void RtScanDeviceStart()
{
CalcTask = Task.Run(async () =>
{
while (RtCalcEnable)
{
await Task.Delay(300);
try
{
_ = TryUpdateSuperheatAndSubcool(out var superheatSubcoolErr);
if (!string.IsNullOrWhiteSpace(superheatSubcoolErr))
{
var now = DateTime.UtcNow;
if (!string.Equals(superheatSubcoolErr, _lastSuperheatSubcoolErrorText, StringComparison.Ordinal)
|| (now - _lastSuperheatSubcoolErrorLogAt) >= TimeSpan.FromSeconds(5))
{
Logger?.Error($"过热度/过冷度计算失败: {superheatSubcoolErr}");
_lastSuperheatSubcoolErrorLogAt = now;
_lastSuperheatSubcoolErrorText = superheatSubcoolErr;
}
}
//if (TryUpdateDryness(out var drynessErr))
//{
// if (!string.IsNullOrWhiteSpace(drynessErr))
// {
// Logger?.Error($"干度计算警告: {drynessErr}");
// }
//}
//else
//{
// if (!string.IsNullOrWhiteSpace(drynessErr))
// {
// Logger?.Error($"干度计算失败: {drynessErr}");
// }
//}
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()));
}
}
});
}
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;
}
///
/// 按流程图更新:制热量、COP(制热)、等熵效率、制冷量、COP(制冷)、容积效率。
///
///
/// 错误/警告信息输出。
/// - 当方法返回 时, 为失败原因,调用方应视为本周期计算无效。
/// - 当方法返回 但 非空时,表示仅部分结果无法计算(例如缺少排量导致容积效率为 NaN)。
///
///
/// 是否成功完成本周期的结果更新。
/// - :至少已成功更新 Qh/Qc/COP/ηs 等主要结果;容积效率可能因缺失排量而为 NaN。
/// - :关键输入或 REFPROP 计算失败,本周期结果不更新。
///
private bool TryUpdateThermodynamicSixResults(out string error)
{
error = string.Empty;
double w_W = HVPwTag?.EngPvValue ?? double.NaN;
if (!double.IsNaN(w_W) && !double.IsInfinity(w_W) && w_W == 0)
{
_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;
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;
// 再把结果回写到对应工程量标签。
if (HeatingCapacity != null)
{
HeatingCapacity.EngPvValue = string.Equals(HeatingCapacity.Unit, "W", StringComparison.OrdinalIgnoreCase)
? HeatingCapacityQh_kW * 1000.0
: HeatingCapacityQh_kW;
}
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;
}
///
/// 创建六个热力结果值计算所需的输入模型。
///
///
/// 输出输入模型。
/// 该对象是 与 之间的数据边界:
/// 由服务层负责从标签提取实时值,由计算层只消费该对象而不直接依赖标签。
///
/// 失败原因,通常为缺少标签、缺少排量配置或关键输入不可用。
/// 是否创建成功。
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 (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 _))
{
displacementCc = double.NaN;
}
double oilFlowKgPerH = 0.0;
if (LubeFlowTag != null)
{
oilFlowKgPerH = LubeFlowTag.EngPvValue;
}
// 将实时标签值与配置值组装为独立计算类可直接消费的输入对象。
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
};
return true;
}
///
/// 获取压缩机排量。
///
/// 排量输出,单位 cc(cm³/rev)。
/// 失败原因(未配置、解析失败、数值不合法)。
/// 是否获取成功。
private bool TryGetCompressorDisplacement_cc(out double displacement_cc, out string error)
{
displacement_cc = double.NaN;
error = string.Empty;
const double defaultDisplacementCc = 35d;
const string key = "CompressorDisplacementCc";
static bool TryParseDisplacementCc(string? text, out double displacementCc)
{
displacementCc = double.NaN;
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
var normalized = text.Trim().ToLowerInvariant();
normalized = normalized.Replace(',', '.');
var match = Regex.Match(normalized, @"[-+]?\d+(\.\d+)?");
if (!match.Success)
{
return false;
}
if (!double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var v))
{
return false;
}
displacementCc = v;
return true;
}
try
{
string raw = ConfigHelper.GetValue(key);
if (TryParseDisplacementCc(raw, out var cfgCc) && cfgCc > 0)
{
displacement_cc = cfgCc;
return true;
}
}
catch
{
// 忽略配置读取异常,走默认回退值
}
displacement_cc = defaultDisplacementCc;
return true;
}
}
}