物性更改

This commit is contained in:
2025-10-12 23:17:04 +08:00
parent 3795dd3f9d
commit 2f7b7cf49e
6 changed files with 700 additions and 60 deletions

View File

@@ -1,9 +1,4 @@
using CapMachine.Wpf.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CapMachine.Wpf.CanDrive
{
@@ -12,6 +7,12 @@ namespace CapMachine.Wpf.CanDrive
/// </summary>
public class CanCmdData
{
/// <summary>
/// 指令数据改变Handler
/// 改变发送消息名称
/// </summary>
public event EventHandler<string>? CanCmdDataChangedHandler;
/// <summary>
/// 配置项名称-比如转速、功率限制等
/// </summary>
@@ -27,11 +28,30 @@ namespace CapMachine.Wpf.CanDrive
/// </summary>
public string? SignalName { get; set; }
private double _SignalCmdValue;
/// <summary>
/// 指令值
/// 没有的话,则给默认值
/// </summary>
public double SignalCmdValue { get; set; }
public double SignalCmdValue
{
get { return _SignalCmdValue; }
set
{
if (_SignalCmdValue != value)
{
_SignalCmdValue = value;
CanCmdDataChangedHandler!.BeginInvoke(this, MsgName!, null, null);
}
}
}
///// <summary>
///// 指令值
///// 没有的话,则给默认值
///// </summary>
//public double SignalCmdValue { get; set; }
///// <summary>
///// 逻辑规则Id

View File

@@ -540,6 +540,117 @@ namespace CapMachine.Wpf.CanDrive
/// </summary>
public List<CanCmdData> CmdData { get; set; } = new List<CanCmdData>();
/// <summary>
/// 加载要发送的数据
/// </summary>
/// <param name="cmdData"></param>
public void LoadCmdDataToDrive(List<CanCmdData> cmdData)
{
// Unsubscribe from events on the old CmdData items
if (CmdData != null && CmdData.Count > 0)
{
foreach (var cmd in CmdData)
{
cmd.CanCmdDataChangedHandler -= CmdData_CanCmdDataChangedHandler;
}
}
// Set the new data and subscribe to events
CmdData = cmdData;
foreach (var cmd in CmdData)
{
cmd.CanCmdDataChangedHandler += CmdData_CanCmdDataChangedHandler;
}
}
/// <summary>
/// 指令数据发生变化执行方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CmdData_CanCmdDataChangedHandler(object? sender, string e)
{
UpdateSchDataByCmdDataChanged();
}
/// <summary>
/// 指令数据发生变化执行更新调度表锁
/// </summary>
private readonly object SchUpdateLock = new object();
/// <summary>
/// 指令数据发生变化执行方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void UpdateSchDataByCmdDataChanged()
{
try
{
if (!IsCycleSend) return;
// 基础防御:确保 DBC/ 调度表 / 分组已经初始化
if (DBCHandle == 0 || SchCanMsg == null || GroupMsg == null)
{
return;
}
lock (SchUpdateLock)
{
//通过DBC进行对消息赋值
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
int CycleUpdateIndex = 0;
//循环给MSG赋值数据顺序是固定的跟初始时设置是一样的
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
//itemSignal.SignalCmdValue = random.Next(0, 100); //仿真测试数据使用
CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
SchCanMsg[CycleUpdateIndex] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG));
CycleUpdateIndex++;
}
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//释放申请的临时缓冲区
Marshal.FreeHGlobal(msgPtSend);
//CAN_UpdateSchedule 官网解释
// ---MsgTabIndex CAN调度表索引号
// ---MsgIndex 开始更新帧起始索引,若起始索引大于调度表帧数,则将帧添加到调度表后面
// ---pCanMsg 需要更新的CAN帧指针
// ---MsgNum pCanMsgTab里面包含的有效帧数
//CAN_UpdateSchedule中的MsgIndex表示当前的调度器中的帧Index序号
//因为调度表中的帧集合和控制帧的集合和要更新的帧集合都是同一个集合SchCanMsg
//默认1号调度表一个更新所有的帧数据
var ret = USB2CAN.CAN_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
if (ret == USB2CAN.CAN_SUCCESS)
{
IsSendOk = true;
Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)} ");
}
else
{
IsSendOk = false;
Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)}");
//return;
}
}
}
catch (Exception ex)
{
IsSendOk = false;
LoggerService.Info($"时间:{DateTime.Now.ToString()}-【MSG】-{ex.Message}");
}
}
/// <summary>
/// 循环发送数据
/// </summary>

View File

@@ -1,4 +1,4 @@
using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace CapMachine.Wpf.PPCalculation
{
@@ -220,6 +220,24 @@ namespace CapMachine.Wpf.PPCalculation
ref double hjt // (OUTPUT) isenthalpic Joule-Thomson coefficient [K/kPa]
);
/********************************************************************************
* Solve for density given T and P using the most robust iterative routine.
* This mirrors the LabVIEW TPRHO DLL call path to obtain phase-specific density.
********************************************************************************/
[DllImport(@".\PPCalculation\REFPROP\REFPRP64.DLL", CharSet = CharSet.Ansi)]
public static extern void TPRHOdll
(
ref double t, // (INPUT) temperature [K]
ref double p, // (INPUT) pressure [kPa]
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] double[] x, // (INPUT) composition [array of mol frac]
ref long kph, // (INPUT) phase flag: 1=liquid, 2=vapor
ref long kguess, // (INPUT) initial guess: 0=let routine choose
ref double D, // (OUTPUT) molar density [mol/L]
ref long ierr, // (OUTPUT) error flag: 0=success
[MarshalAs(UnmanagedType.VBByRefStr)] ref string herr, // (OUTPUT) error string
ref long herrLen
);
/******************************************************************
* compute the transport properties of thermal conductivity and

View File

@@ -313,8 +313,8 @@ namespace CapMachine.Wpf.Services
{
if (CmdData.Count > 0)
{
ToomossCanDrive.CmdData = CmdData;
//把指令数据赋值给CAN 驱动
ToomossCanDrive.LoadCmdDataToDrive(CmdData);
if (ToomossCanDrive.SchEnable)
{

View File

@@ -13,6 +13,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using NLog;
namespace CapMachine.Wpf.Services
{
@@ -25,6 +26,7 @@ namespace CapMachine.Wpf.Services
/// 计算扫描 Task
/// </summary>
private static Task CalcTask { get; set; }
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public ConfigService ConfigService { get; }
private IEventAggregator _EventAggregator { get; set; }
@@ -32,6 +34,7 @@ namespace CapMachine.Wpf.Services
public SysRunService SysRunServer { get; }
public MachineRtDataService MachineRtDataService { get; }
public IDialogService DialogService { get; }
/// <summary>
/// 标签中心
/// </summary>
@@ -56,11 +59,12 @@ namespace CapMachine.Wpf.Services
//SpeedTag = TagManager.DicTags.GetValueOrDefault("转速[rpm]");
//ExPressTag = TagManager.DicTags.GetValueOrDefault("排气压力[BarA]");
//ExTempTag = TagManager.DicTags.GetValueOrDefault("排气温度[℃]");
if (TagManager.TryGetShortControlTagByName("吸气压力[BarA]", out ShortControlTag? InhPressShortControlTag))
if (TagManager.TryGetShortTagByName("吸气压力[BarA]", out ShortValueTag? InhPressShortControlTag))
{
InhPressTag = InhPressShortControlTag!;
}
if (TagManager.TryGetShortControlTagByName("吸气温度[℃]", out ShortControlTag? InhTempShortControlTag))
if (TagManager.TryGetShortTagByName("吸气温度[℃]", out ShortValueTag? InhTempShortControlTag))
{
InhTempTag = InhTempShortControlTag!;
}
@@ -74,7 +78,7 @@ namespace CapMachine.Wpf.Services
//TxvFrTempTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前温度[℃]")!;
//TxvFrPressTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前压力[BarA]")!;
if (TagManager.TryGetShortTagByName("膨胀阀前温度[℃]", out ShortValueTag? TxvFrTempShortTag))
if (TagManager.TryGetShortTagByName("SUBCOOL出口温度[℃]", out ShortValueTag? TxvFrTempShortTag))
{
TxvFrTempTag = TxvFrTempShortTag!;
}
@@ -83,6 +87,38 @@ namespace CapMachine.Wpf.Services
TxvFrPressTag = TxvFrPressShortTag!;
}
if (TagManager.TryGetShortTagByName("液冷媒流量[kg/h]", out ShortValueTag? LiqRefFlowShortTag))
{
LiqRefFlowTag = LiqRefFlowShortTag!;
}
if (TagManager.TryGetShortTagByName("冷媒流量[L/min]", out ShortValueTag? VRVShortTag))
{
VRVTag = VRVShortTag!;
}
// 气路阀前 P/T用于质量流量加权混合焓的计算LabVIEW 流程)
if (TagManager.TryGetShortTagByName("气路阀前压力[BarA]", out ShortValueTag? gasPreP))
{
GasPreValvePressTag = gasPreP!;
}
if (TagManager.TryGetShortTagByName("气路阀前温度[℃]", out ShortValueTag? gasPreT))
{
GasPreValveTempTag = gasPreT!;
}
//// 可选:气/液支路质量流量(若现场提供则可走“质量流量加权”分支)
//// 为兼容不同命名,尝试多个候选名称
//if (TagManager.TryGetShortTagByName("气体质量流量[kg/h]", out ShortValueTag? gasFlow1) ||
// TagManager.TryGetShortTagByName("气路质量流量[kg/h]", out gasFlow1))
//{
// GasMassFlowTag = gasFlow1!;
//}
//if (TagManager.TryGetShortTagByName("液体质量流量[kg/h]", out ShortValueTag? liqFlow1) ||
// TagManager.TryGetShortTagByName("液路质量流量[kg/h]", out liqFlow1))
//{
// LiquidMassFlowTag = liqFlow1!;
//}
//Cond1TempTag = TagManager.DicTags.GetValueOrDefault("冷凝器出口水温[℃]");
//CondInTempTag = TagManager.DicTags.GetValueOrDefault("冷凝器进口温度[℃]");
@@ -98,10 +134,11 @@ namespace CapMachine.Wpf.Services
Subcool = SubcoolShortTag!;
}
// 干度标签(如不存在则仅跳过写入,不抛异常)
if (TagManager.TryGetShortTagByName("干度[-]", out ShortValueTag? DrynessShortTag))
// 干度标签(如不存在则仅跳过写入,不抛异常)。优先使用“干度[%]”,其次“干度[-]”。
if (TagManager.TryGetShortTagByName("干度[%]", out ShortValueTag? drynessPct))
{
DrynessTag = DrynessShortTag!;
DrynessTag = drynessPct!;
DrynessTagIsPercent = true;
}
SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath");
@@ -128,12 +165,12 @@ namespace CapMachine.Wpf.Services
/// <summary>
/// 吸气压力
/// </summary>
public ShortControlTag InhPressTag { get; set; }
public ShortValueTag InhPressTag { get; set; }
/// <summary>
/// 吸气温度
/// </summary>
public ShortControlTag InhTempTag { get; set; }
public ShortValueTag InhTempTag { get; set; }
/// <summary>
/// 膨胀阀前温度
@@ -159,6 +196,49 @@ namespace CapMachine.Wpf.Services
/// 干度(无量纲 [-]
/// </summary>
public ShortValueTag DrynessTag { get; set; }
private bool DrynessTagIsPercent { get; set; } = false;
/// <summary>
/// 气路阀前压力BarA
/// </summary>
public ShortValueTag GasPreValvePressTag { get; set; }
/// <summary>
/// 气路阀前温度(℃)
/// </summary>
public ShortValueTag GasPreValveTempTag { get; set; }
/// <summary>
/// 冷媒流量
/// 需要计算的结果是气体质量流量kg/h=冷媒流量-液冷媒流量
///
/// LabVIEW 中该量为常值;此处提供为可配置常量,实时计算直接使用,不再读取 Tag。
/// 若值<=0则视为无效将导致干度计算失败并置0。
/// 气体流量=冷媒流量-液冷媒流量
///
/// </summary>
public ShortValueTag VRVTag { get; set; }
/// <summary>
/// 液体质量流量kg/h
/// LabVIEW 中该量为常值;此处提供为可配置常量,实时计算直接使用,不再读取 Tag。
/// 若值<=0则视为无效将导致干度计算失败并置0。
/// 液冷媒流量=液体流量=液体质量流量
/// </summary>
public ShortValueTag LiqRefFlowTag { get; set; }
///// <summary>
///// 气相质量流量kg/h可选
///// </summary>
//public ShortValueTag GasMassFlowTag { get; set; }
///// <summary>
///// 液相质量流量kg/h可选
///// </summary>
//public ShortValueTag LiquidMassFlowTag { get; set; }
///// <summary>
@@ -186,7 +266,6 @@ namespace CapMachine.Wpf.Services
/// </summary>
private double AirVolumeDataSource { get; set; } = 0;
/// <summary>
/// 风量喷嘴启用信息数据
/// </summary>
@@ -221,8 +300,6 @@ namespace CapMachine.Wpf.Services
double tk = 0.0, wm = 0.0, prevDeltaH = 0.0;//prevDeltaH 未使用
//textBox5.Text = "";
// 统一初始化(仅必要时),避免循环内重复 SETUP/SETPATH
if (!EnsureRefpropInitialized(out string initErrLoop))
{
@@ -244,44 +321,106 @@ namespace CapMachine.Wpf.Services
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);
// 计算过热度/过冷度封装方法BarA→kPa
if (TryComputeSuperheatK_ByBarA(InhPressTag.PVModel.EngValue, InhTempTag.PVModel.EngValue, out double superheatK, out string shErr))
Superheat.PVModel.EngValue = superheatK;
else
{
Superheat.PVModel.EngValue = 0;
Logger.Warn($"过热度计算失败:{shErr}");
}
// 计算过冷度:膨胀阀前压力的饱和温度
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);
if (TryComputeSubcoolK_ByBarA(TxvFrPressTag.PVModel.EngValue, TxvFrTempTag.PVModel.EngValue, out double subcoolK, out string scErr))
Subcool.PVModel.EngValue = subcoolK;
else
{
Subcool.PVModel.EngValue = 0;
Logger.Warn($"过冷度计算失败:{scErr}");
}
// 干度计算(可重入锁Monitor 为可重入锁,不会死锁)
// 干度计算(LabVIEW 严格流程,质量流量加权):
// A) 阀前两路相焓TPRHO→THERM
// B) 质量流量加权混合焓
// C) 吸气压力同压饱和焓SATP→THERM
// D) x = (h_mix - h_l)/(h_v - h_l)
try
{
double dryness = GetDrynessByTP(InhPressTag.PVModel.EngValue, InhTempTag.PVModel.EngValue, true);
// 输入校验:必要测点是否齐全
var missing = new List<string>();
if (InhPressTag == null) missing.Add("吸气压力[BarA]");
if (GasPreValvePressTag == null) missing.Add("气路阀前压力[BarA]");
if (GasPreValveTempTag == null) missing.Add("气路阀前温度[℃]");
if (TxvFrPressTag == null) missing.Add("膨胀阀前压力[BarA]");
if (TxvFrTempTag == null) missing.Add("SUBCOOL出口温度[℃]");
if (missing.Count > 0)
{
if (DrynessTag != null)
{
DrynessTag.PVModel.EngValue = double.IsNaN(dryness) ? 0.0 : dryness;
DrynessTag.PVModel.EngValue = 0;
}
Logger.Warn($"质量流量加权干度必要测点缺失已将干度置0。缺失: {string.Join(", ", missing)}");
}
else
{
string dryErr;
double ps = InhPressTag.PVModel.EngValue;//吸气压力
ps = 2.374421;//Mpa ->BarA
double pg = GasPreValvePressTag.PVModel.EngValue;//气路阀前压力
pg = 19.77032;//Mpa ->BarA
double tg = GasPreValveTempTag.PVModel.EngValue;//气路阀前温度
tg = 61.76869;
double plv = TxvFrPressTag.PVModel.EngValue;//膨胀阀前压力=液路阀前压力
plv = 19.26757;//Mpa ->BarA
double tlv = TxvFrTempTag.PVModel.EngValue;//膨胀阀前温度 = SUBCOOL出口温度 =液路阀前温度
tlv = 64.96428;
//液体流量=液冷媒流量 kg/h
//气体流量=冷媒流量-液体流量 kg/h
double GasMassFlowKgPerH, LiquidMassFlowKgPerH;
LiquidMassFlowKgPerH = LiqRefFlowTag.PVModel.EngValue;
GasMassFlowKgPerH= VRVTag.PVModel.EngValue- LiquidMassFlowKgPerH;
//LiquidMassFlowKgPerH = 214.3051;
//GasMassFlowKgPerH = 264.7956- 214.3051;
//压力参数要求为BarA
double dryness = Step_ComputeDryness_LV_FlowWeighted(
GasMassFlowKgPerH,
LiquidMassFlowKgPerH,
ps, pg, tg, plv, tlv,
out dryErr
);
double val = (!double.IsNaN(dryness)) ? dryness : 0.0;
if (DrynessTag != null)
{
DrynessTag.PVModel.EngValue = DrynessTagIsPercent ? val * 100.0 : val;
}
if (double.IsNaN(dryness))
{
Logger.Warn($"质量流量加权干度计算失败:{dryErr}已置0。输入: mg={GasMassFlowKgPerH:F3} kg/h, ml={LiquidMassFlowKgPerH:F3} kg/h, Ps={ps:F3} BarA, Pg={pg:F3} BarA, Tg={tg:F3} ℃, Pl={plv:F3} BarA, Tl={tlv:F3} ℃");
}
}
catch { /* 不中断主循环 */ }
}
catch (Exception ex)
{
if (DrynessTag != null) DrynessTag.PVModel.EngValue = 0;
Logger.Error(ex, "质量流量加权干度计算异常已置0");
}
}
}
//p = Convert.ToDouble(textBox2.Text) * 1000.0;//textBox2 Comp.吸气压力(Mpa
p = (InhPressTag.PVModel.EngValue) * 1000.0;//textBox2 Comp.吸气压力(Mpa
//p = Convert.ToDouble(textBox2.Text) * 100.0;//textBox2 Comp.吸气压力(BarA→kPa
p = (InhPressTag.PVModel.EngValue) * 100.0;//textBox2 Comp.吸气压力(BarA→kPa
kph = 1;
p1 = (TxvFrPressTag.PVModel.EngValue) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa
//p1 = Convert.ToDouble(textBox3.Text) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa
p1 = (TxvFrPressTag.PVModel.EngValue) * 100.0;//textBox3 Evap.膨胀阀前压力(BarA→kPa
//p1 = Convert.ToDouble(textBox3.Text) * 100.0;//textBox3 Evap.膨胀阀前压力(BarA→kPa
// 重复的 SATP 与干度计算已在上方 lock(_refpropLock) 中统一完成,此处移除重复调用
}
catch (Exception ex)
@@ -295,6 +434,50 @@ namespace CapMachine.Wpf.Services
#region
/// <summary>
/// 计算过热度K。输入为吸气压力[BarA]与吸气温度[℃],内部以 BarA→kPa 调用 SATP 求饱和温度。
/// </summary>
private bool TryComputeSuperheatK_ByBarA(double suctionPressureBarA, double suctionTempC, out double superheatK, out string error)
{
superheatK = double.NaN; error = string.Empty;
if (!EnsureRefpropInitialized(out error)) return false;
double[] x = new double[20]; x[0] = 1.0;
double pKPa = suctionPressureBarA * 100.0; // BarA -> kPa
double tSatK = 0, Dl = 0, Dv = 0;
long kph = 1, ierr = 0, herrLen = 255;
string herr = new string(' ', 255);
lock (_refpropLock)
{
IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSatK, ref Dl, ref Dv, new double[20], new double[20], ref ierr, ref herr, ref herrLen);
}
if (ierr != 0) { error = $"SATP 失败: {herr.Trim()} (ierr={ierr})"; return false; }
double tSatC = tSatK - 273.15;
superheatK = suctionTempC - tSatC;
return true;
}
/// <summary>
/// 计算过冷度K。输入为膨胀阀前压力[BarA]与对应温度[℃],内部以 BarA→kPa 调用 SATP 求饱和温度。
/// </summary>
private bool TryComputeSubcoolK_ByBarA(double preValvePressureBarA, double preValveTempC, out double subcoolK, out string error)
{
subcoolK = double.NaN; error = string.Empty;
if (!EnsureRefpropInitialized(out error)) return false;
double[] x = new double[20]; x[0] = 1.0;
double pKPa = preValvePressureBarA * 100.0; // BarA -> kPa
double tSatK = 0, Dl = 0, Dv = 0;
long kph = 1, ierr = 0, herrLen = 255;
string herr = new string(' ', 255);
lock (_refpropLock)
{
IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSatK, ref Dl, ref Dv, new double[20], new double[20], ref ierr, ref herr, ref herrLen);
}
if (ierr != 0) { error = $"SATP 失败: {herr.Trim()} (ierr={ierr})"; return false; }
double tSatC = tSatK - 273.15;
subcoolK = preValveTempC - tSatC;
return true;
}
/// <summary>
/// 过热度和过冷度计算函数调用 风量的调用
/// </summary>
@@ -358,6 +541,149 @@ namespace CapMachine.Wpf.Services
}
}
/// <summary>
/// STEP S0LabVIEW 对应:初始化库)
/// 功能:确保 REFPROP 完成初始化(流体路径/工质/参考态)。
/// 结果:成功返回 true否则返回 false 并带出错误信息。
/// 性能:幂等,实际初始化仅在必要时执行。
/// </summary>
private bool Step_S0_EnsureInitialized(out string error) => EnsureRefpropInitialized(out error);
/// <summary>
/// STEP SxLabVIEW 对应INFO/WMOL 取摩尔质量)
/// 功能:获取当前工质的摩尔质量 wmm[g/mol]。
/// 结果wmm_g_per_molg/mol
/// 说明LabVIEW 常用 INFOdll 取 wmm本实现用 WMOLdll 等价;纯工质场景下 x[0]=1。
/// </summary>
private bool Step_Sx_GetMolarMass_g_per_mol(out double wmm_g_per_mol, out string error)
{
wmm_g_per_mol = double.NaN; error = string.Empty;
if (!Step_S0_EnsureInitialized(out error)) return false;
double[] x = new double[20]; x[0] = 1.0;
double wm = 0;
lock (_refpropLock)
{
IRefProp64.WMOLdll(x, ref wm);
}
if (wm <= 0) { error = "WMOL 返回无效摩尔质量"; return false; }
wmm_g_per_mol = wm;
return true;
}
/// <summary>
/// STEP S1LabVIEW 对应:阀前两路“指定相别”的相焓计算)
/// 功能:按给定阀前 T(℃)/P(BarA) 和相别kph=1液相/2气相计算相焓 h[kJ/kg]。
/// 实现WMOL → wmm[g/mol]TPRHO(T,P,kph) → DTHERM(T,D) → h[J/mol]h[kJ/kg] = h / wmm。
/// 说明:严格对齐 LabVIEW 的相路径,避免两相边界的相误判。
/// </summary>
private bool Step_S1_GetPhaseHmassByTP_BarAC(double pressureBarA, double temperatureC, long kphPhase,
out double h_kJkg, out string error)
{
h_kJkg = double.NaN; error = string.Empty;
if (!EnsureRefpropInitialized(out error)) return false;
double[] x = new double[20]; x[0] = 1.0;
double wmm = 0; // g/mol
double tK = temperatureC + 273.15;
double pKPa = pressureBarA * 100.0;
double D = 0; long kguess = 0; long ierr = 0; string herr = new string(' ', 255); long ln = 255;
double pOut = 0, el = 0, hl = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0;
lock (_refpropLock)
{
IRefProp64.WMOLdll(x, ref wmm);
if (!(wmm > 0)) { error = "WMOL 返回无效摩尔质量"; return false; }
IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kphPhase, ref kguess, ref D, ref ierr, ref herr, ref ln);
if (ierr != 0) { error = $"TPRHO 失败: {herr.Trim()} (ierr={ierr})"; return false; }
double tLocal = tK; double DLocal = D;
IRefProp64.THERMdll(ref tLocal, ref DLocal, x, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt);
h_kJkg = hl / wmm; // J/mol ÷ g/mol = J/g = kJ/kg
return true;
}
}
/// <summary>
/// STEP S2LabVIEW 对应:质量流量加权)
/// 功能:质量流量加权计算混合焓。
/// 结果hMix_kJkg。
/// 限制m_g + m_l 必须大于 0。
/// </summary>
private bool Step_S2_ComputeMixEnthalpyByFlows(double mg_kg_h, double ml_kg_h, double hV_kJkg, double hL_kJkg,
out double hMix_kJkg, out string error)
{
error = string.Empty; hMix_kJkg = double.NaN;
double sum = mg_kg_h + ml_kg_h;
if (!(sum > 0)) { error = "质量流量和<=0"; return false; }
hMix_kJkg = (mg_kg_h * hV_kJkg + ml_kg_h * hL_kJkg) / sum;
return true;
}
/// <summary>
/// STEP S3LabVIEW 对应SATP→THERM 求同压饱和焓)
/// 功能:在给定压力 BarA 下,求饱和液/气的质量比焓。
/// 实现SATP(P) → Tsat,Dl,Dv,xliq,xvapTHERM(Tsat,Dl,xliq) 与 THERM(Tsat,Dv,xvap) → hl/hv[J/mol]
/// WMOL → wmm[g/mol],换算 hl/hv 到 kJ/kg。
/// </summary>
private bool Step_S3_GetSaturationEnthalpies_atPressure_BarA(double pressureBarA,
out double hl_kJkg, out double hv_kJkg, out string error)
{
return TryGetSaturationH_atPressure_MPa(pressureBarA / 10.0, out hl_kJkg, out hv_kJkg, out error);
}
/// <summary>
/// STEP S4LabVIEW 对应:按焓差计算干度)
/// 功能x = (h_mix h_l) / (h_v h_l),并限幅到 [0,1]。
/// </summary>
private bool Step_S4_ComputeDrynessFromEnthalpies(double hMix_kJkg, double hl_kJkg, double hv_kJkg,
out double x, out string error)
{
x = double.NaN; error = string.Empty;
double denom = hv_kJkg - hl_kJkg;
if (Math.Abs(denom) < 1e-9) { error = "饱和焓差过小,可能临界附近"; return false; }
double val = (hMix_kJkg - hl_kJkg) / denom;
if (double.IsNaN(val) || double.IsInfinity(val)) { error = "干度数值异常"; return false; }
x = Math.Max(0.0, Math.Min(1.0, val));
return true;
}
/// <summary>
/// 汇总步骤LabVIEW 主流程,质量流量加权):
/// - S1阀前气/液状态分别按 T、P 求相焓kJ/kg
/// - S2按质量流量加权混合焓 h_mixkJ/kg
/// - S3在吸气压力下求同压饱和焓 h_l/h_vkJ/kg
/// - S4x = (h_mix h_l)/(h_v h_l)
/// 返回:干度 x∈[0,1];失败返回 NaN并在 error 输出原因。
/// 性能:所有 REFPROP 调用在锁内,且无多余重复初始化;尽量少堆分配。
/// </summary>
private double Step_ComputeDryness_LV_FlowWeighted(
double mg_kg_h,
double ml_kg_h,
double ps_barA,
double pg_barA,
double tg_C,
double pl_barA,
double tl_C,
out string error)
{
error = string.Empty;
// S1两路相焓指定相别气相=2液相=1
if (!Step_S1_GetPhaseHmassByTP_BarAC(pg_barA, tg_C, 2, out double hV_kJkg, out string e1)) { error = $"气路相焓失败: {e1}"; return double.NaN; }
if (!Step_S1_GetPhaseHmassByTP_BarAC(pl_barA, tl_C, 1, out double hL_kJkg, out string e2)) { error = $"液路相焓失败: {e2}"; return double.NaN; }
// S2质量流量加权混合焓
if (!Step_S2_ComputeMixEnthalpyByFlows(mg_kg_h, ml_kg_h, hV_kJkg, hL_kJkg, out double hMix_kJkg, out string e3))
{ error = e3; return double.NaN; }
// S3吸气压力同压饱和焓
if (!Step_S3_GetSaturationEnthalpies_atPressure_BarA(ps_barA, out double hl_kJkg, out double hv_kJkg, out string e4))
{ error = e4; return double.NaN; }
// S4干度
if (!Step_S4_ComputeDrynessFromEnthalpies(hMix_kJkg, hl_kJkg, hv_kJkg, out double x, out string e5))
{ error = e5; return double.NaN; }
return x;
}
/// <summary>
/// 风量数据的计算
/// </summary>
@@ -749,16 +1075,137 @@ namespace CapMachine.Wpf.Services
}
/// <summary>
/// 基于当前标签(吸气压力[BarA]/吸气温度[℃])的干度计算便捷方法
/// 注意:为保持与现有过热度/过冷度计算一致,此处将压力值按 MPa 处理再乘以 1000 转 kPa。
/// 若现场标签单位确认为 BarA请在调用方转换成 MPa 后再调用上层 CalcDrynessByTP_MPaC以避免单位歧义。
/// LabVIEW 对齐版本:基于混合焓与同压饱和焓计算干度(质量基)
/// 步骤:
/// 1) TPFLSH(t,p) 获取总体焓 h_mix[J/mol]WMOL 得到 w_m[g/mol];换算 h_mix[kJ/kg] = h_mix[J/mol] / w_m[g/mol]
/// 2) SATP(p) 得到饱和温度 T_sat、饱和液/气密度 Dl/DvTHERM(T_sat, Dl/Dv) 分别得到 hl/hv[J/mol]
/// 换算 hl_mass/hv_mass[kJ/kg] = hl[J/mol]/w_m[g/mol], hv[J/mol]/w_m[g/mol]
/// 3) x = (h_mix - h_l_mass) / (h_v_mass - h_l_mass),并限幅到 [0,1]
/// 注意:单位 MPa/℃ 输入;内部统一转换为 kPa/K。纯工质场景下 molar mass 为常数。
/// </summary>
public DrynessCalcResult CalcDrynessAtInlet()
public double GetDrynessByTP_LV(double pressureMPa, double temperatureC)
{
double pressureMPa = InhPressTag?.PVModel?.EngValue ?? 0.0; // 与现有实现保持一致
double temperatureC = InhTempTag?.PVModel?.EngValue ?? 0.0;
return CalcDrynessByTP_MPaC(pressureMPa, temperatureC);
if (!EnsureRefpropInitialized(out string initErr)) return double.NaN;
double[] x = new double[20]; x[0] = 1.0;
double tK = temperatureC + 273.15;
double pKPa = pressureMPa * 1000.0;
double d = 0, Dl = 0, Dv = 0, q = 0, ee = 0, h = 0, ss = 0, cp = 0, cv = 0, w = 0;
double[] xliq = new double[20];
double[] xvap = new double[20];
string herr = new string(' ', 255);
long ierr = 0, ln = 255;
double wm = 0; // g/mol
double tSat = 0, DlSat = 0, DvSat = 0; long ierrSat = 0, kph = 1, herrLen = 255; string herrSat = new string(' ', 255);
double pOut = 0, el = 0, hl = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0;
double ev = 0, hv = 0, sv = 0, cvv = 0, cpv = 0, wv = 0, hjtv = 0;
lock (_refpropLock)
{
// 质量摩尔质量 g/mol纯工质恒定
IRefProp64.WMOLdll(x, ref wm);
// 1) TPFLSH 得 h_mix[J/mol]
IRefProp64.TPFLSHdll(ref tK, ref pKPa, x, ref d, ref Dl, ref Dv, xliq, xvap, ref q, ref ee, ref h, ref ss, ref cv, ref cp, ref w, ref ierr, ref herr, ref ln);
if (ierr != 0) return double.NaN;
double h_mix_kJkg = h / wm; // 见单位换算推导J/mol ÷ g/mol = J/g = kJ/kg
// 2) SATP+THERM 得到同压饱和 hl/hv[J/mol]
double pForSat = pKPa; ierrSat = 0;
IRefProp64.SATPdll(ref pForSat, x, ref kph, ref tSat, ref DlSat, ref DvSat, xliq, xvap, ref ierrSat, ref herrSat, ref herrLen);
if (ierrSat != 0) return double.NaN;
double tLocal = tSat; double DlLocal = DlSat; pOut = 0;
IRefProp64.THERMdll(ref tLocal, ref DlLocal, xliq, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt);
tLocal = tSat; double DvLocal = DvSat; pOut = 0;
IRefProp64.THERMdll(ref tLocal, ref DvLocal, xvap, ref pOut, ref ev, ref hv, ref sv, ref cvv, ref cpv, ref wv, ref hjtv);
double hl_kJkg = hl / wm;
double hv_kJkg = hv / wm;
double denom = hv_kJkg - hl_kJkg;
if (Math.Abs(denom) < 1e-9) return double.NaN;
double xdry = (h_mix_kJkg - hl_kJkg) / denom;
// 限幅 [0,1]
if (double.IsNaN(xdry) || double.IsInfinity(xdry)) return double.NaN;
return Math.Max(0.0, Math.Min(1.0, xdry));
}
}
/// <summary>
/// 计算指定 T(℃)/P(MPa) 下的质量比焓 h[kJ/kg]TPFLSH + WMOL 换算)。
/// </summary>
private bool TryComputeHmassKJkgByTP_MPaC(double pressureMPa, double temperatureC, out double h_kJkg, out string error)
{
h_kJkg = double.NaN; error = string.Empty;
if (!EnsureRefpropInitialized(out error)) return false;
double[] x = new double[20]; x[0] = 1.0;
double tK = temperatureC + 273.15;
double pKPa = pressureMPa * 1000.0;
double d = 0, Dl = 0, Dv = 0, q = 0, ee = 0, h = 0, ss = 0, cp = 0, cv = 0, w = 0; long ierr = 0, ln = 255;
string herr = new string(' ', 255);
double wm = 0;
lock (_refpropLock)
{
IRefProp64.WMOLdll(x, ref wm);
IRefProp64.TPFLSHdll(ref tK, ref pKPa, x, ref d, ref Dl, ref Dv, new double[20], new double[20], ref q, ref ee, ref h, ref ss, ref cv, ref cp, ref w, ref ierr, ref herr, ref ln);
if (ierr != 0) { error = $"TPFLSH 失败: {herr.Trim()} (ierr={ierr})"; return false; }
if (Math.Abs(wm) < 1e-12) { error = "WMOL 返回 0无效摩尔质量"; return false; }
h_kJkg = h / wm; // J/mol ÷ g/mol = J/g = kJ/kg
return true;
}
}
/// <summary>
/// 计算指定 T(℃)/P(BarA) 下的质量比焓 h[kJ/kg](便捷包装)。
/// </summary>
private bool TryComputeHmassKJkgByTP_BarAC(double pressureBarA, double temperatureC, out double h_kJkg, out string error)
{
return TryComputeHmassKJkgByTP_MPaC(pressureBarA / 10.0, temperatureC, out h_kJkg, out error);
}
/// <summary>
/// 计算给定压力(MPa)下的同压饱和液/气焓质量基kJ/kg
/// </summary>
private bool TryGetSaturationH_atPressure_MPa(double pressureMPa, out double hl_kJkg, out double hv_kJkg, out string error)
{
hl_kJkg = double.NaN; hv_kJkg = double.NaN; error = string.Empty;
if (!EnsureRefpropInitialized(out error)) return false;
double[] x = new double[20]; x[0] = 1.0;
double[] xliq = new double[20];
double[] xvap = new double[20];
double wm = 0; long kph = 1; string herrSat = new string(' ', 255); long herrLen = 255; long ierrSat = 0;
double pKPa = pressureMPa * 1000.0; double tSat = 0, DlSat = 0, DvSat = 0;
double pOut = 0, el = 0, hl = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0;
double ev = 0, hv = 0, sv = 0, cvv = 0, cpv = 0, wv = 0, hjtv = 0;
lock (_refpropLock)
{
IRefProp64.WMOLdll(x, ref wm);
IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSat, ref DlSat, ref DvSat, xliq, xvap, ref ierrSat, ref herrSat, ref herrLen);
if (ierrSat != 0) { error = $"SATP 失败: {herrSat.Trim()} (ierr={ierrSat})"; return false; }
double tLocal = tSat; double DlLocal = DlSat;
IRefProp64.THERMdll(ref tLocal, ref DlLocal, xliq, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt);
tLocal = tSat; double DvLocal = DvSat;
IRefProp64.THERMdll(ref tLocal, ref DvLocal, xvap, ref pOut, ref ev, ref hv, ref sv, ref cvv, ref cpv, ref wv, ref hjtv);
if (Math.Abs(wm) < 1e-12) { error = "WMOL 返回 0无效摩尔质量"; return false; }
hl_kJkg = hl / wm; hv_kJkg = hv / wm;
return true;
}
}
/// <summary>
/// LabVIEW 严格流程(质量流量加权):
/// 基于气/液两路阀前 T,P 求各自相焓,并用质量流量做加权得到 h_mix再用吸气压力的饱和焓计算干度。
/// 本方法从 Tag 中取值:
/// - 吸气压力[BarA](用于同压饱和焓)
/// - 气路阀前压力[BarA]、气路阀前温度[℃]
/// - 膨胀阀前压力[BarA]、SUBCOOL出口温度[℃](作为液体阀前 P/T
/// - 气体质量流量[kg/h]、液体质量流量[kg/h](至少两者之一>0
/// 返回:干度 x∈[0,1];若缺少必要测点或计算失败,返回 NaN并在 error 输出原因。
/// </summary>
#endregion

View File

@@ -1156,6 +1156,50 @@
"BlockIndex": 102
}
},
{
"Id": 521,
"Name": "气路阀前压力[BarA]",
"NameNoUnit": "气路阀前压力",
"EnName": "GasFrPress",
"Group": "采集",
"MinValue": 0,
"MaxValue": 1000,
"IsMeter": false,
"DecimalPoint": 2,
"Precision": 100,
"Unit": "BarA",
"DataType": "Short",
"RWInfo": "PLCRead",
"PVModel": {
"Address": "VW15106",
"EngValue": 0,
"EngValueStr": "",
"Block": "PV",
"BlockIndex": 106
}
},
{
"Id": 522,
"Name": "气路阀前温度[℃]",
"NameNoUnit": "气路阀前温度",
"EnName": "GasFrTemp",
"Group": "采集",
"MinValue": 0,
"MaxValue": 10000,
"IsMeter": false,
"DecimalPoint": 1,
"Precision": 10,
"Unit": "℃",
"DataType": "Short",
"RWInfo": "PLCRead",
"PVModel": {
"Address": "VW15104",
"EngValue": 0,
"EngValueStr": "",
"Block": "PV",
"BlockIndex": 104
}
},
{
"Id": 530,
"Name": "干度[%]",
@@ -1169,13 +1213,13 @@
"Precision": 1,
"Unit": "%",
"DataType": "Short",
"RWInfo": "PLCRead",
"RWInfo": "PCCalcu",
"PVModel": {
"Address": "VW15104",
"Address": "VW15108",
"EngValue": 0,
"EngValueStr": "",
"Block": "PV",
"BlockIndex": 104
"BlockIndex": 108
}
},
{
@@ -1191,13 +1235,13 @@
"Precision": 1,
"Unit": "K",
"DataType": "Short",
"RWInfo": "PLCRead",
"RWInfo": "PCCalcu",
"PVModel": {
"Address": "VW15106",
"Address": "VW15110",
"EngValue": 0,
"EngValueStr": "",
"Block": "PV",
"BlockIndex": 106
"BlockIndex": 110
}
},
{
@@ -1213,13 +1257,13 @@
"Precision": 1,
"Unit": "K",
"DataType": "Short",
"RWInfo": "PLCRead",
"RWInfo": "PCCalcu",
"PVModel": {
"Address": "VW15108",
"Address": "VW15112",
"EngValue": 0,
"EngValueStr": "",
"Block": "PV",
"BlockIndex": 108
"BlockIndex": 112
}
}
]