1045 lines
44 KiB
C#
1045 lines
44 KiB
C#
using ASTM_D7896_Tester.Helpers;
|
||
using ASTM_D7896_Tester.Models;
|
||
using ASTM_D7896_Tester.Services;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using OxyPlot;
|
||
using OxyPlot.Axes;
|
||
using OxyPlot.Series;
|
||
using OxyPlot.Wpf;
|
||
using PdfSharpCore.Drawing;
|
||
using PdfSharpCore.Pdf;
|
||
using System;
|
||
using System.Collections.ObjectModel;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
|
||
namespace ASTM_D7896_Tester.ViewModels;
|
||
|
||
public partial class D7896ViewModel : ObservableObject
|
||
{
|
||
private readonly IPlcService _plcService;
|
||
private AppConfig _config;
|
||
private readonly ReportService _reportService;
|
||
|
||
// 电压表服务
|
||
private Th1963LanService _th1963Ustd; // 6位半测量标准电阻电压 U_std
|
||
private Th1963LanService _th1953Ustd; // 6位半测量标准电阻电压 U_std
|
||
//private FiveHalfDmmService _fiveHalfUpt; // 5位半测量铂丝电压 U_pt
|
||
|
||
private CancellationTokenSource _testCts; // 用于停止测试
|
||
private bool _stopRequested;
|
||
|
||
// 后台监控定时器
|
||
private Timer? _monitorTimer;
|
||
|
||
// 常量: 标准电阻值 1Ω
|
||
private const double StandardResistor = 1.0;
|
||
|
||
// 铂丝电阻温度系数 (纯铂)
|
||
private const double AlphaPt = 0.0040; // /°C
|
||
|
||
// 加热功率 Q 计算相关
|
||
private double _heatingCurrent; // 实际加热电流平均值
|
||
private double _wireResistanceAvg; // 铂丝平均电阻
|
||
|
||
// 温升曲线数据
|
||
[ObservableProperty] private string _curveTitle = "温升曲线";
|
||
[ObservableProperty] private PlotModel _temperatureCurveModel;
|
||
|
||
// UI 绑定属性 (与之前一致)
|
||
public ObservableCollection<string> ReferenceLiquids { get; } = new() { "蒸馏水", "甲苯", "乙二醇" };
|
||
[ObservableProperty] private string _sampleId = "未命名样品";
|
||
[ObservableProperty] private double _testTemperature = 25.0;
|
||
[ObservableProperty] private string _testDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||
[ObservableProperty] private bool _isTesting = false;
|
||
[ObservableProperty] private string _statusMessage = "就绪";
|
||
[ObservableProperty] private int _currentMeasurementIndex = 0;
|
||
[ObservableProperty] private ObservableCollection<MeasurementResult> _measurements = new();
|
||
[ObservableProperty] private double _averageThermalConductivity;
|
||
[ObservableProperty] private double _averageThermalDiffusivity;
|
||
[ObservableProperty] private double _averageVolumetricHeatCapacity;
|
||
|
||
[ObservableProperty] private double _sampleVolume = 40.0;
|
||
[ObservableProperty] private bool _bubbleRemoved = true;
|
||
[ObservableProperty] private bool _usePressure = false;
|
||
[ObservableProperty] private double _pressureValue = 0.0;
|
||
[ObservableProperty] private bool _isCleanConfirmed = true;
|
||
[ObservableProperty] private string _cleanerName = "";
|
||
[ObservableProperty] private double _ambientTemperature = 25.0;
|
||
[ObservableProperty] private bool _ambientCalibrated = true;
|
||
[ObservableProperty] private bool _platinumCompatible = true;
|
||
[ObservableProperty] private string _liquidReactivityNote = "";
|
||
|
||
[ObservableProperty] private double _platinumResistance = 0.0;
|
||
[ObservableProperty] private double _chamberPressure = 0.0;
|
||
[ObservableProperty] private double _currentTestTemperature = 0.0;
|
||
|
||
[ObservableProperty] private bool _isCalibrating = false;
|
||
[ObservableProperty] private string _calibrationStatus = "";
|
||
[ObservableProperty] private string _selectedReferenceLiquid = "蒸馏水";
|
||
[ObservableProperty] private double _referenceConductivity = 0.606;
|
||
[ObservableProperty] private double _measuredConductivity = 0.0;
|
||
[ObservableProperty] private double _calibrationErrorPercent = 0.0;
|
||
|
||
// 实时电压显示(可选)
|
||
[ObservableProperty] private double _platinumVoltage;
|
||
[ObservableProperty] private double _standardResistorVoltage;
|
||
|
||
|
||
private const double EulerGamma = 0.5772156649; // 欧拉常数
|
||
//private const double WireRadius = 0.00003; // 铂丝半径 (0.03 mm)
|
||
[ObservableProperty] private double _sampleDensity = 1000.0; // 新增,密度默认值1000 kg/m³(水)
|
||
int samples = 200; // 1秒 * 1000点/秒
|
||
double heatingDuration = 1; // 加热时间 0.8 秒(需与您的加热脉冲宽度一致)
|
||
double totalDuration = 2; // 总采样时间(加热 + 冷却)
|
||
public D7896ViewModel()
|
||
{
|
||
_config = App.PlcConfig ?? new AppConfig();
|
||
_plcService = App.PlcService;
|
||
_reportService = new ReportService(_config.TestParameters.ReportOutputPath);
|
||
|
||
SampleVolume = _config.TestParameters.DefaultSampleVolume;
|
||
UsePressure = _config.TestParameters.UsePressure;
|
||
PressureValue = _config.TestParameters.DefaultPressure;
|
||
SelectedReferenceLiquid = _config.TestParameters.ReferenceLiquid;
|
||
ReferenceConductivity = _config.TestParameters.ReferenceConductivity;
|
||
|
||
IsCleanConfirmed = true;
|
||
BubbleRemoved = true;
|
||
PlatinumCompatible = true;
|
||
AmbientCalibrated = true;
|
||
|
||
// 初始化电压表服务
|
||
// TH1963 IP 地址需要根据实际配置修改,建议从配置文件读取
|
||
_th1963Ustd = new Th1963LanService();
|
||
|
||
_th1953Ustd = new Th1963LanService();
|
||
|
||
StartBackgroundMonitoring();
|
||
}
|
||
|
||
private async void StartBackgroundMonitoring()
|
||
{
|
||
await Task.Delay(1000);
|
||
_monitorTimer = new Timer(async _ => await MonitorPlcValues(), null, 0, 1000);
|
||
}
|
||
|
||
private async Task MonitorPlcValues()
|
||
{
|
||
if (!await _plcService.IsConnectedAsync()) return;
|
||
if (Application.Current == null || Application.Current.Dispatcher == null) return;
|
||
try
|
||
{
|
||
float rawResistance = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Resistance);
|
||
double newResistance = rawResistance;
|
||
Application.Current?.Dispatcher.Invoke(() => PlatinumResistance = newResistance);
|
||
|
||
float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure);
|
||
Application.Current?.Dispatcher.Invoke(() => ChamberPressure = rawPressure);
|
||
|
||
float rawTemp = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Temperature);
|
||
Application.Current?.Dispatcher.Invoke(() => CurrentTestTemperature = rawTemp);
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
//private async Task<double> GetInitialResistanceAsync()
|
||
//{
|
||
// if (!await _plcService.IsConnectedAsync()) return 0;
|
||
// try
|
||
// {
|
||
// float rawResistance = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Resistance);
|
||
// return rawResistance;
|
||
// }
|
||
// catch { return 0; }
|
||
//}
|
||
// 在类成员变量区域添加
|
||
private int currentSettleMs = 200; // 电流稳定等待时间(毫秒)
|
||
[RelayCommand]
|
||
private async Task StartTestAsync()
|
||
{
|
||
if (IsTesting)
|
||
{
|
||
MessageBox.Show("测试正在进行中", "提示");
|
||
return;
|
||
}
|
||
|
||
// 前置检查
|
||
if (!IsCleanConfirmed || !BubbleRemoved || !PlatinumCompatible || !AmbientCalibrated)
|
||
{
|
||
MessageBox.Show("请完成所有测试前确认项", "前置条件未满足");
|
||
return;
|
||
}
|
||
if (SampleVolume <= 0)
|
||
{
|
||
MessageBox.Show("请输入有效的样品量", "参数错误");
|
||
return;
|
||
}
|
||
if (UsePressure && PressureValue <= 0)
|
||
{
|
||
MessageBox.Show("请设置有效的加压值", "参数错误");
|
||
return;
|
||
}
|
||
|
||
// 连接PLC和电压表
|
||
if (!await _plcService.IsConnectedAsync())
|
||
{
|
||
if (!await _plcService.ConnectAsync())
|
||
{
|
||
MessageBox.Show("无法连接到PLC", "错误");
|
||
return;
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
|
||
|
||
|
||
|
||
|
||
await _th1963Ustd.ConnectAsync("192.168.1.12", 45454); // 改为实际IP
|
||
await _th1963Ustd.ConfigureForHighSpeedDcvAsync();
|
||
|
||
await _th1953Ustd.ConnectAsync("192.168.1.13", 45454); // 改为实际IP
|
||
await _th1953Ustd.ConfigureForHighSpeedDcvAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"电压表连接失败: {ex.Message}", "错误");
|
||
return;
|
||
}
|
||
|
||
if (UsePressure)
|
||
{
|
||
StatusMessage = "正在加压...";
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, true);
|
||
|
||
const int pressureStableTimeoutMs = 10000; // 30秒超时
|
||
const double pressureTolerance = 5.0; // 允许误差 ±5 kPa
|
||
var startTime = DateTime.Now;
|
||
bool pressureReached = false;
|
||
|
||
while ((DateTime.Now - startTime).TotalMilliseconds < pressureStableTimeoutMs)
|
||
{
|
||
await Task.Delay(500); // 每0.5秒检测一次
|
||
await UpdateRealTimeParametersAsync();
|
||
if (ChamberPressure >= PressureValue - pressureTolerance)
|
||
{
|
||
pressureReached = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!pressureReached)
|
||
{
|
||
// 加压失败,关闭进气阀,中止测试
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, false);
|
||
MessageBox.Show($"加压超时,压力未能达到 {PressureValue} kPa(当前 {ChamberPressure:F1} kPa)", "错误");
|
||
return;
|
||
}
|
||
|
||
// 压力已达到,可关闭进气阀(或保持,看系统需求)
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, false);
|
||
StatusMessage = $"压力已稳定在 {ChamberPressure:F1} kPa";
|
||
}
|
||
|
||
//double initialResistance = await GetInitialResistanceAsync();
|
||
//if (initialResistance > 0)
|
||
// StatusMessage = $"初始电阻: {initialResistance:F4} Ω";
|
||
|
||
Measurements.Clear();
|
||
IsTesting = true;
|
||
_stopRequested = false;
|
||
_testCts = new CancellationTokenSource();
|
||
|
||
try
|
||
{
|
||
|
||
// 预热:进行一次虚拟测量
|
||
await _th1963Ustd.ConfigureForHighSpeedDcvAsync();
|
||
await _th1963Ustd.PrepareBatchAsync(10);
|
||
await _th1963Ustd.TriggerAsync();
|
||
await Task.Delay(100);
|
||
await _th1963Ustd.FetchBatchAsync(); // 丢弃结果
|
||
|
||
|
||
// 预热:进行一次虚拟测量
|
||
await _th1953Ustd.ConfigureForHighSpeedDcvAsync();
|
||
await _th1953Ustd.PrepareBatchAsync(10);
|
||
await _th1953Ustd.TriggerAsync();
|
||
await Task.Delay(100);
|
||
await _th1953Ustd.FetchBatchAsync(); // 丢弃结果
|
||
|
||
|
||
|
||
|
||
|
||
int requiredCount = _config.TestParameters.MeasurementCount; // 需要多少有效数据
|
||
int validCount = 0;
|
||
int attemptCount = 0;
|
||
int maxAttempts = requiredCount * 2; // 最多尝试次数,防止死循环
|
||
|
||
// 存储每次成功测量的结果(用于后续异常判断)
|
||
List<double> validLambdaList = new List<double>();
|
||
List<double> validAlphaList = new List<double>();
|
||
List<double> validCpList = new List<double>();
|
||
|
||
while (validCount < requiredCount && attemptCount < maxAttempts && !_stopRequested)
|
||
{
|
||
attemptCount++;
|
||
CurrentMeasurementIndex = attemptCount; // 显示当前尝试次数(不是有效次数)
|
||
StatusMessage = $"正在执行第 {attemptCount} 次测量(有效:{validCount}/{requiredCount})...";
|
||
|
||
// --- 步骤1:基线采集(加热前)---
|
||
await _th1963Ustd.PrepareBatchAsync(50);
|
||
await _th1953Ustd.PrepareBatchAsync(50);
|
||
await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
|
||
await Task.Delay(100);
|
||
double[] ustdBase = await _th1963Ustd.FetchBatchAsync();
|
||
double[] uptBase = await _th1953Ustd.FetchBatchAsync();
|
||
|
||
double dynamicR0 = 1.49; // 默认值
|
||
if (ustdBase != null && ustdBase.Length > 0 && uptBase != null && uptBase.Length > 0)
|
||
{
|
||
double sumR0 = 0; int cnt = 0;
|
||
for (int j = 0; j < ustdBase.Length; j++)
|
||
{
|
||
if (ustdBase[j] > 0.01 && uptBase[j] > 0.01)
|
||
{
|
||
sumR0 += uptBase[j] / ustdBase[j];
|
||
cnt++;
|
||
}
|
||
}
|
||
if (cnt > 0)
|
||
{
|
||
dynamicR0 = sumR0 / cnt;
|
||
Logger.Log($"基线采集 R0 = {dynamicR0:F6} Ω (有效点数: {cnt})");
|
||
}
|
||
else
|
||
{
|
||
Logger.Log("基线采集数据无效(电压过低),使用加热前瞬间点");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Logger.Log("基线采集未返回数据,将使用加热段早期点");
|
||
}
|
||
|
||
// --- 步骤2:正式加热采集 ---
|
||
await _th1963Ustd.PrepareBatchAsync(samples);
|
||
await _th1953Ustd.PrepareBatchAsync(samples);
|
||
|
||
|
||
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, true);
|
||
// 等待电流稳定(不触发采集)
|
||
try { await Task.Delay(currentSettleMs, _testCts.Token); } catch (OperationCanceledException) { break; }
|
||
// 电流稳定后,触发采集
|
||
await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
|
||
// 继续加热剩余时间(加热总时间 = 稳定等待时间 + 有效加热时间)
|
||
try { await Task.Delay((int)(heatingDuration * 1000), _testCts.Token); } catch (OperationCanceledException) { break; }
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false);
|
||
|
||
|
||
int remainingMs = (int)((totalDuration - heatingDuration) * 1000) + 100;
|
||
try { await Task.Delay(remainingMs, _testCts.Token); } catch (OperationCanceledException) { break; }
|
||
|
||
double[] ustd = await _th1963Ustd.FetchBatchAsync();
|
||
double[] upt = await _th1953Ustd.FetchBatchAsync();
|
||
|
||
if (dynamicR0 == 1.49) // 基线无效
|
||
{
|
||
double sumR0 = 0; int cnt = 0;
|
||
for (int j = 2; j < Math.Min(6, ustd.Length); j++)
|
||
{
|
||
if (ustd[j] > 0.01 && upt[j] > 0.01)
|
||
{
|
||
sumR0 += upt[j] / ustd[j];
|
||
cnt++;
|
||
}
|
||
}
|
||
if (cnt > 0)
|
||
{
|
||
dynamicR0 = sumR0 / cnt;
|
||
Logger.Log($"使用加热段第2~5点计算 R0 = {dynamicR0:F6} Ω");
|
||
}
|
||
}
|
||
|
||
// 记录原始电压(调试用)
|
||
for (int j = 0; j < 20 && j < ustd.Length; j++)
|
||
{
|
||
Logger.Log($"第{j}点: U_std={ustd[j]:F6} V, U_pt={upt[j]:F6} V");
|
||
}
|
||
|
||
StandardResistorVoltage = ustd.Average();
|
||
PlatinumVoltage = upt.Average();
|
||
Logger.Log($"测量 {attemptCount}: U_std 平均值={ustd.Average():F6} V, U_pt 平均值={upt.Average():F6} V");
|
||
|
||
double[] timeArray = new double[ustd.Length];
|
||
for (int idx = 0; idx < timeArray.Length; idx++)
|
||
timeArray[idx] = idx * totalDuration / samples;
|
||
|
||
var (lambda, alpha, deltaT, coolingPoints) = ComputeThermalProperties(upt, ustd, timeArray, dynamicR0, CurrentTestTemperature);
|
||
|
||
// 计算比热容 Cp
|
||
double vhc = lambda / alpha; // kJ/(m³·K)
|
||
double cp = vhc / SampleDensity; // J/(kg·K)
|
||
|
||
Logger.Log($"测量 {attemptCount} 结果: λ={lambda:F6} W/(m·K), α={alpha:E6} m²/s, Cp={cp:F2} J/(kg·K)");
|
||
|
||
// ---- 异常值检测 ----
|
||
bool isOutlier = false;
|
||
double deviationThreshold = 0.1; // 10% 偏差阈值
|
||
|
||
if (validCount >= 2) // 至少有两个有效数据后才开始剔除
|
||
{
|
||
double avgLambda = validLambdaList.Average();
|
||
double avgAlpha = validAlphaList.Average();
|
||
double avgCp = validCpList.Average();
|
||
|
||
if (Math.Abs(lambda - avgLambda) / avgLambda > deviationThreshold ||
|
||
Math.Abs(alpha - avgAlpha) / avgAlpha > deviationThreshold ||
|
||
Math.Abs(cp - avgCp) / avgCp > deviationThreshold)
|
||
{
|
||
isOutlier = true;
|
||
Logger.Log($"第 {attemptCount} 次测量结果异常(偏差过大),予以舍弃。λ={lambda:F4}, α={alpha:E4}, Cp={cp:F2}");
|
||
}
|
||
}
|
||
|
||
if (!isOutlier)
|
||
{
|
||
// 正常结果,添加到列表
|
||
validLambdaList.Add(lambda);
|
||
validAlphaList.Add(alpha);
|
||
validCpList.Add(cp);
|
||
validCount++;
|
||
|
||
GenerateTemperatureCurveFromData(timeArray, deltaT, coolingPoints);
|
||
|
||
var result = new MeasurementResult
|
||
{
|
||
Index = validCount, // 使用有效次数编号
|
||
ThermalConductivity = lambda,
|
||
ThermalDiffusivity = alpha
|
||
};
|
||
result.CalculateVhcAndCp(SampleDensity);
|
||
Application.Current.Dispatcher.Invoke(() => Measurements.Add(result));
|
||
StatusMessage = $"第 {validCount} 次测量完成,λ={lambda:F4} W/m·K";
|
||
|
||
Logger.Log($"========== 第 {validCount} 次测量详细数据 ==========");
|
||
Logger.Log($"热导率 λ: {lambda:F6} W/(m·K)");
|
||
Logger.Log($"热扩散率 α: {alpha:E6} m²/s");
|
||
Logger.Log($"体积热容 VHC: {result.VolumetricHeatCapacity:F2} kJ/(m³·K)");
|
||
Logger.Log($"比热容 Cp: {cp:F2} J/(kg·K)");
|
||
Logger.Log($"初始电阻 R0: {dynamicR0:F6} Ω");
|
||
Logger.Log("===========================================");
|
||
}
|
||
|
||
// 测量间隔(即使舍弃也等待,让样品恢复)
|
||
if (validCount < requiredCount && !_stopRequested && attemptCount < maxAttempts)
|
||
{
|
||
try { await Task.Delay(_config.TestParameters.IntervalSeconds * 100, _testCts.Token); } catch (OperationCanceledException) { break; }
|
||
}
|
||
}
|
||
|
||
if (validCount >= requiredCount)
|
||
{
|
||
CalculateAverages();
|
||
StatusMessage = "测试完成";
|
||
}
|
||
else
|
||
{
|
||
StatusMessage = $"测试中止。";
|
||
MessageBox.Show($"测试中止,未收集到足够有效数据({validCount}/{requiredCount})。请检查样品或仪器状态。", "提示");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
StatusMessage = $"测试出错: {ex.Message}";
|
||
MessageBox.Show($"测试过程中发生错误: {ex.Message}", "错误");
|
||
}
|
||
finally
|
||
{
|
||
// 停止加热,泄压
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false);
|
||
if (UsePressure)
|
||
{
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, false);
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, true);
|
||
await Task.Delay(1000);
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, false);
|
||
}
|
||
IsTesting = false;
|
||
//_fiveHalfUpt.Close();
|
||
_th1963Ustd.Dispose();
|
||
_th1953Ustd.Dispose();
|
||
_testCts?.Dispose();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private (double lambda, double alpha, double[] deltaT, List<DataPoint> coolingPoints) ComputeThermalProperties(
|
||
double[] upt, double[] ustd, double[] time, double initialResistance, double bathTemp)
|
||
{
|
||
int n = Math.Min(upt.Length, ustd.Length);
|
||
double[] deltaT = new double[n];
|
||
double[] ptResistance = new double[n];
|
||
double[] current = new double[n];
|
||
double tStart = _config.TestParameters.FitStartTime;
|
||
double tEnd = _config.TestParameters.FitEndTime;
|
||
|
||
// 1. 瞬时计算(先用传入的 initialResistance)
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
current[i] = ustd[i] / StandardResistor;
|
||
if (current[i] > 0.001)
|
||
{
|
||
ptResistance[i] = upt[i] / current[i];
|
||
deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance);
|
||
}
|
||
else
|
||
{
|
||
ptResistance[i] = double.NaN;
|
||
deltaT[i] = double.NaN;
|
||
}
|
||
}
|
||
|
||
// 滑动平均平滑(窗口大小改为 11)
|
||
int windowSize = 40; // 原为 5
|
||
double[] smoothDeltaT = new double[n];
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
int start = Math.Max(0, i - windowSize / 2);
|
||
int end = Math.Min(n - 1, i + windowSize / 2);
|
||
double sum = 0; int cnt = 0;
|
||
for (int j = start; j <= end; j++)
|
||
if (!double.IsNaN(deltaT[j])) { sum += deltaT[j]; cnt++; }
|
||
smoothDeltaT[i] = cnt > 0 ? sum / cnt : double.NaN;
|
||
}
|
||
|
||
// 寻找温升峰值点
|
||
double maxDeltaT = 0;
|
||
for (int i = 5; i < n; i++)
|
||
{
|
||
if (!double.IsNaN(smoothDeltaT[i]) && smoothDeltaT[i] > maxDeltaT)
|
||
maxDeltaT = smoothDeltaT[i];
|
||
}
|
||
Logger.Log($"最大温升 = {maxDeltaT:F4} ℃");
|
||
|
||
// 拟合窗口
|
||
int startIdx = FindIndex(time, tStart);
|
||
int endIdx = FindIndex(time, tEnd);
|
||
if (startIdx < 0) startIdx = 5;
|
||
if (endIdx >= n) endIdx = n - 1;
|
||
if (endIdx <= startIdx) endIdx = Math.Min(startIdx + 50, n - 1);
|
||
Logger.Log($"拟合窗口: startIdx={startIdx}, endIdx={endIdx}, 点数={endIdx - startIdx + 1}");
|
||
|
||
// 收集拟合点
|
||
var points = new List<DataPoint>();
|
||
for (int i = startIdx; i <= endIdx; i++)
|
||
{
|
||
if (i <= startIdx + 5) // 只打印前5个点
|
||
Logger.Log($"i={i}, time={time[i]:F6}, smoothDeltaT={smoothDeltaT[i]:F6}, deltaT={deltaT[i]:F6}");
|
||
if (!double.IsNaN(smoothDeltaT[i]) && smoothDeltaT[i] > 0 && time[i] > 0)
|
||
points.Add(new DataPoint(Math.Log(time[i]), smoothDeltaT[i]));
|
||
}
|
||
|
||
if (points.Count < 10)
|
||
{
|
||
Logger.Log($"警告:有效拟合点数仅 {points.Count},测量无效");
|
||
return (0, 0, deltaT, new List<DataPoint>());
|
||
}
|
||
|
||
(double slope, double intercept) = LinearRegression(points);
|
||
if (slope <= 0.001)
|
||
{
|
||
Logger.Log($"警告:拟合斜率 {slope:E} 过小或为负,测量无效");
|
||
return (0, 0, deltaT, new List<DataPoint>());
|
||
}
|
||
|
||
// 打印前10个拟合点
|
||
foreach (var p in points.Take(10))
|
||
Logger.Log($"ln(t)={p.X:F4}, ΔT={p.Y:F4}");
|
||
|
||
// 计算功率密度
|
||
double sumI = 0, sumR = 0;
|
||
int cntI = 0, cntR = 0;
|
||
for (int i = startIdx; i <= endIdx; i++)
|
||
{
|
||
if (!double.IsNaN(current[i]) && Math.Abs(current[i]) > 1e-12) { sumI += current[i]; cntI++; }
|
||
if (!double.IsNaN(ptResistance[i]) && ptResistance[i] > 1e-12) { sumR += ptResistance[i]; cntR++; }
|
||
}
|
||
if (cntI == 0 || cntR == 0) return (0, 0, deltaT, new List<DataPoint>());
|
||
double avgCurrent = sumI / cntI;
|
||
double avgResistance = sumR / cntR;
|
||
double wireLength = Math.Max(1e-12, _config.TestParameters.PlatinumWireLength);
|
||
double powerPerLength = (avgCurrent * avgCurrent * avgResistance) / wireLength;
|
||
|
||
double lambda = powerPerLength / (4 * Math.PI * slope);
|
||
if (_config.TestParameters.UseFixedLambda)
|
||
{
|
||
lambda = _config.TestParameters.FixedLambda;
|
||
Logger.Log($"使用固定 lambda={lambda:F6} W/(m·K)");
|
||
}
|
||
lambda *= _config.TestParameters.CalibrationCoefficients.ThermalDiffusivityCorrection;
|
||
Logger.Log($"constantCurrent(avg)={avgCurrent:E6} A, avgResistance={avgResistance:F6} Ω, powerPerLength={powerPerLength:E6} W/m, 斜率 B = {slope:F5}");
|
||
|
||
// 计算热扩散率
|
||
double exponent = intercept / slope + EulerGamma;
|
||
if (exponent > 30) exponent = 30;
|
||
double alpha = (_config.TestParameters.PlatinumWireDiameter / 2 * _config.TestParameters.PlatinumWireDiameter / 2 / 4.0) * Math.Exp(exponent);
|
||
|
||
alpha *= _config.TestParameters.CalibrationCoefficients.ThermalConductivityCorrection;
|
||
if (_config.TestParameters.UseFixedAlpha)
|
||
{
|
||
alpha = _config.TestParameters.FixedAlpha;
|
||
Logger.Log($"使用固定 alpha={alpha:E6} m²/s");
|
||
}
|
||
if (alpha <= 0 || double.IsNaN(alpha) || alpha > 1e-5)
|
||
{
|
||
Logger.Log($"警告:α 计算异常 ({alpha:E}),数据可能不可靠");
|
||
alpha = double.NaN;
|
||
}
|
||
Logger.Log($"热导率 λ = {lambda:F6} W/(m·K) | 热扩散率 α = {alpha:E6} m²/s | 截距/斜率 = {intercept / slope:F3}");
|
||
|
||
// 冷却曲线
|
||
var coolingPoints = new List<DataPoint>();
|
||
int coolStart = FindIndex(time, heatingDuration);
|
||
int coolEnd = FindIndex(time, totalDuration);
|
||
for (int i = coolStart; i <= coolEnd; i++)
|
||
if (!double.IsNaN(deltaT[i]) && deltaT[i] > 0.01)
|
||
coolingPoints.Add(new DataPoint(time[i], deltaT[i]));
|
||
|
||
// 导出CSV
|
||
//try
|
||
//{
|
||
// string tmp = Path.GetTempPath();
|
||
// string baseName = $"measure_{SampleId}_{DateTime.Now:yyyyMMdd_HHmmss}_{CurrentMeasurementIndex}";
|
||
// string dataPath = Path.Combine(tmp, baseName + ".csv");
|
||
// ExportMeasurementCsv(dataPath, time, ustd, upt, deltaT, startIdx, endIdx);
|
||
// Logger.Log($"已导出测量数据 CSV: {dataPath}");
|
||
|
||
// string winPath = Path.Combine(tmp, baseName + "_windows.csv");
|
||
// ExportCandidateWindowsCsv(winPath, time, deltaT, startIdx, endIdx);
|
||
// Logger.Log($"已导出候选拟合窗 CSV: {winPath}");
|
||
//}
|
||
//catch (Exception ex)
|
||
//{
|
||
// Logger.Log($"导出CSV失败: {ex.Message}");
|
||
//}
|
||
|
||
return (lambda, alpha, deltaT, coolingPoints);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 最小二乘法线性回归,返回 (斜率, 截距)
|
||
/// </summary>
|
||
private (double slope, double intercept) LinearRegression(List<DataPoint> points)
|
||
{
|
||
if (points.Count < 2) return (0.001, 0);
|
||
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
||
foreach (var p in points)
|
||
{
|
||
sumX += p.X;
|
||
sumY += p.Y;
|
||
sumXY += p.X * p.Y;
|
||
sumX2 += p.X * p.X;
|
||
}
|
||
double n = points.Count;
|
||
double denominator = n * sumX2 - sumX * sumX;
|
||
if (Math.Abs(denominator) < 1e-10) return (0.001, 0);
|
||
double slope = (n * sumXY - sumX * sumY) / denominator;
|
||
double intercept = (sumY - slope * sumX) / n;
|
||
return (slope, intercept);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查找时间数组中与目标时间最接近的索引
|
||
/// </summary>
|
||
private int FindIndex(double[] timeArray, double targetTime)
|
||
{
|
||
for (int i = 0; i < timeArray.Length; i++)
|
||
{
|
||
if (timeArray[i] >= targetTime)
|
||
return i;
|
||
}
|
||
return timeArray.Length - 1;
|
||
}
|
||
|
||
///// <summary>
|
||
///// 最小二乘法拟合斜率 (X轴为横坐标,Y轴为纵坐标) — 用于加热段 ln(t) vs ΔT
|
||
///// </summary>
|
||
//private double LeastSquaresSlope(List<DataPoint> points)
|
||
//{
|
||
// if (points.Count < 2) return 0.001;
|
||
// double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
||
// foreach (var p in points)
|
||
// {
|
||
// sumX += p.X;
|
||
// sumY += p.Y;
|
||
// sumXY += p.X * p.Y;
|
||
// sumX2 += p.X * p.X;
|
||
// }
|
||
// double n = points.Count;
|
||
// double denominator = n * sumX2 - sumX * sumX;
|
||
// if (Math.Abs(denominator) < 1e-10) return 0.001;
|
||
// double slope = (n * sumXY - sumX * sumY) / denominator;
|
||
// return slope;
|
||
//}
|
||
|
||
///// <summary>
|
||
///// 最小二乘法拟合斜率 (X轴为时间t,Y轴为 ln(ΔT)) — 用于冷却段
|
||
///// </summary>
|
||
//private double LeastSquaresSlopeOnTime(List<DataPoint> points)
|
||
//{
|
||
// if (points.Count < 2) return -1.0;
|
||
// double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
||
// foreach (var p in points)
|
||
// {
|
||
// sumX += p.X;
|
||
// sumY += p.Y;
|
||
// sumXY += p.X * p.Y;
|
||
// sumX2 += p.X * p.X;
|
||
// }
|
||
// double n = points.Count;
|
||
// double denominator = n * sumX2 - sumX * sumX;
|
||
// if (Math.Abs(denominator) < 1e-10) return -1.0;
|
||
// double slope = (n * sumXY - sumX * sumY) / denominator;
|
||
// return slope;
|
||
//}
|
||
|
||
private void GenerateTemperatureCurveFromData(double[] time, double[] deltaT, List<DataPoint> coolingPoints)
|
||
{
|
||
if (TemperatureCurveModel == null)
|
||
{
|
||
TemperatureCurveModel = new PlotModel { Title = "温升与冷却曲线", Background = OxyColors.White };
|
||
TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "时间 (s)" });
|
||
TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "温升 (℃)" });
|
||
}
|
||
|
||
// 根据当前测量索引计算色相 (0 ~ 1)
|
||
double hue = ((CurrentMeasurementIndex - 1) % 10) / 10.0; // 0, 0.1, 0.2 ... 0.9
|
||
// 加热曲线颜色:饱和度0.9,亮度0.8
|
||
var heatColor = OxyColor.FromHsv(hue, 0.9, 0.8);
|
||
// 冷却曲线颜色:色相偏移0.5(互补色),饱和度0.6,亮度0.8(较淡)
|
||
double coolHue = hue + 0.5;
|
||
if (coolHue >= 1.0) coolHue -= 1.0;
|
||
var coolColor = OxyColor.FromHsv(coolHue, 0.6, 0.8);
|
||
|
||
// 加热段曲线(红色系实线)
|
||
var heatingSeries = new LineSeries
|
||
{
|
||
Title = $"第{CurrentMeasurementIndex}次 - 加热",
|
||
Color = heatColor,
|
||
StrokeThickness = 1.5
|
||
};
|
||
for (int i = 0; i < time.Length && time[i] <= 1.0; i++)
|
||
{
|
||
heatingSeries.Points.Add(new DataPoint(time[i], deltaT[i]));
|
||
}
|
||
TemperatureCurveModel.Series.Add(heatingSeries);
|
||
|
||
// 冷却曲线(互补色,虚线)
|
||
if (coolingPoints != null && coolingPoints.Count > 0)
|
||
{
|
||
var coolingSeries = new LineSeries
|
||
{
|
||
Title = $"第{CurrentMeasurementIndex}次 - 冷却",
|
||
Color = coolColor,
|
||
StrokeThickness = 1.5,
|
||
LineStyle = LineStyle.Dash
|
||
};
|
||
foreach (var p in coolingPoints)
|
||
{
|
||
coolingSeries.Points.Add(p);
|
||
}
|
||
TemperatureCurveModel.Series.Add(coolingSeries);
|
||
}
|
||
|
||
TemperatureCurveModel.InvalidatePlot(true);
|
||
CurveTitle = $"已完成 {CurrentMeasurementIndex} 次测量";
|
||
}
|
||
|
||
|
||
private void CalculateAverages()
|
||
{
|
||
if (Measurements.Count == 0) return;
|
||
AverageThermalConductivity = Measurements.Average(m => m.ThermalConductivity);
|
||
AverageThermalDiffusivity = Measurements.Average(m => m.ThermalDiffusivity);
|
||
AverageVolumetricHeatCapacity = Measurements.Average(m => m.VolumetricHeatCapacity);
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void Reset()
|
||
{
|
||
Measurements.Clear();
|
||
AverageThermalConductivity = AverageThermalDiffusivity = AverageVolumetricHeatCapacity = 0;
|
||
CurrentMeasurementIndex = 0;
|
||
StatusMessage = "已重置";
|
||
TestDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||
TemperatureCurveModel = null;
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task GenerateReportAsync()
|
||
{
|
||
if (Measurements.Count == 0)
|
||
{
|
||
MessageBox.Show("没有测试数据", "提示");
|
||
return;
|
||
}
|
||
|
||
// 选择保存路径
|
||
var saveFileDialog = new Microsoft.Win32.SaveFileDialog
|
||
{
|
||
Filter = "PDF files (*.pdf)|*.pdf",
|
||
DefaultExt = ".pdf",
|
||
FileName = $"报告_{SampleId}_{DateTime.Now:yyyyMMdd_HHmmss}.pdf"
|
||
};
|
||
if (saveFileDialog.ShowDialog() != true)
|
||
return;
|
||
|
||
string pdfPath = saveFileDialog.FileName;
|
||
|
||
try
|
||
{
|
||
// 先在 UI 线程导出曲线图为字节数组
|
||
byte[] chartImageBytes = null;
|
||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||
{
|
||
if (TemperatureCurveModel != null && TemperatureCurveModel.Series.Count > 0)
|
||
{
|
||
using (var stream = new MemoryStream())
|
||
{
|
||
var exporter = new PngExporter { Width = 600, Height = 400 };
|
||
exporter.Export(TemperatureCurveModel, stream);
|
||
chartImageBytes = stream.ToArray();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 然后在后台线程生成 PDF(避免阻塞 UI)
|
||
await Task.Run(() => GeneratePdfReport(pdfPath, chartImageBytes));
|
||
MessageBox.Show($"报表已生成: {pdfPath}", "成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"生成报表失败: {ex.Message}", "错误");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
private void GeneratePdfReport(string filePath, byte[] chartImageBytes)
|
||
{
|
||
using (var document = new PdfDocument())
|
||
{
|
||
// 创建第一个页面
|
||
var page = document.AddPage();
|
||
page.Width = XUnit.FromMillimeter(210);
|
||
page.Height = XUnit.FromMillimeter(297);
|
||
var gfx = XGraphics.FromPdfPage(page);
|
||
|
||
var titleFont = new XFont("SimHei", 16, XFontStyle.Bold);
|
||
var headerFont = new XFont("SimHei", 12, XFontStyle.Bold);
|
||
var normalFont = new XFont("SimHei", 10, XFontStyle.Regular);
|
||
double yPosition = 30;
|
||
|
||
// 标题
|
||
gfx.DrawString("ASTM D7896-19 瞬态热线法测试报告", titleFont, XBrushes.Black,
|
||
new XRect(0, yPosition, page.Width, 30), XStringFormats.TopCenter);
|
||
yPosition += 40;
|
||
|
||
// 基础信息(只显示一次)
|
||
gfx.DrawString($"样品名称: {SampleId}", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"测试温度: {TestTemperature:F1} °C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"测试时间: {TestDateTime}", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"样品体积: {SampleVolume:F1} mL", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"样品密度: {SampleDensity:F0} kg/m^3", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"环境温度: {AmbientTemperature:F1} °C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"铂丝电阻温度系数: {AlphaPt:F4} /°C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"铂丝长度: {_config.TestParameters.PlatinumWireLength:F3} m", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
|
||
gfx.DrawString($"铂丝直径: {_config.TestParameters.PlatinumWireDiameter * 1000:F2} mm", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 35;
|
||
|
||
// 曲线图(如果有)
|
||
if (chartImageBytes != null)
|
||
{
|
||
using (var imgStream = new MemoryStream(chartImageBytes))
|
||
{
|
||
var image = XImage.FromStream(() => new MemoryStream(imgStream.ToArray()));
|
||
gfx.DrawImage(image, 40, yPosition, 500, 330);
|
||
yPosition += 350;
|
||
}
|
||
}
|
||
|
||
// 表格标题
|
||
gfx.DrawString("测量结果明细", headerFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 25;
|
||
|
||
// 表头(已替换特殊符号)
|
||
string[] headers = { "序号", "热导率λ(W/(m.K))", "热扩散率α(10^-7 m^2/s)", "体积热容VHC(kJ/(m^3.K))", "比热容Cp(J/(kg.K))" };
|
||
double[] colWidths = { 50, 115, 125, 125, 125 };
|
||
double startX = 40;
|
||
|
||
// 辅助函数:绘制表头(用于新页)
|
||
void DrawHeader(XGraphics g, double y)
|
||
{
|
||
for (int i = 0; i < headers.Length; i++)
|
||
{
|
||
double cellX = startX + colWidths.Take(i).Sum();
|
||
g.DrawRectangle(XPens.Black, cellX, y, colWidths[i], 20);
|
||
var textRect = new XRect(cellX, y, colWidths[i], 20);
|
||
g.DrawString(headers[i], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
|
||
}
|
||
}
|
||
|
||
// 绘制第一页的表头
|
||
DrawHeader(gfx, yPosition);
|
||
double currentRowY = yPosition + 20; // 表头高度20
|
||
const double rowHeight = 20;
|
||
const double bottomMargin = 50; // 页面底部留白
|
||
|
||
// 遍历所有测量数据
|
||
foreach (var m in Measurements)
|
||
{
|
||
// 检查是否需要换页
|
||
if (currentRowY + rowHeight > page.Height - bottomMargin)
|
||
{
|
||
gfx.Dispose();
|
||
page = document.AddPage();
|
||
page.Width = XUnit.FromMillimeter(210);
|
||
page.Height = XUnit.FromMillimeter(297);
|
||
gfx = XGraphics.FromPdfPage(page);
|
||
gfx.DrawString("ASTM D7896-19 瞬态热线法测试报告(续)", headerFont, XBrushes.Black,
|
||
new XRect(0, 30, page.Width, 30), XStringFormats.TopCenter);
|
||
currentRowY = 80;
|
||
DrawHeader(gfx, currentRowY - 20);
|
||
}
|
||
|
||
string[] rowData = {
|
||
m.Index.ToString(),
|
||
m.ThermalConductivity.ToString("F6"),
|
||
(m.ThermalDiffusivity * 1e7).ToString("F3"),
|
||
m.VolumetricHeatCapacity.ToString("F2"),
|
||
m.SpecificHeatCapacity.ToString("F2")
|
||
};
|
||
for (int j = 0; j < rowData.Length; j++)
|
||
{
|
||
double cellX = startX + colWidths.Take(j).Sum();
|
||
gfx.DrawRectangle(XPens.Black, cellX, currentRowY, colWidths[j], rowHeight);
|
||
var textRect = new XRect(cellX, currentRowY, colWidths[j], rowHeight);
|
||
gfx.DrawString(rowData[j], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
|
||
}
|
||
currentRowY += rowHeight;
|
||
}
|
||
|
||
// 添加空行,避免平均值与表格重叠
|
||
currentRowY += 60;
|
||
|
||
// 检查是否需要换页(平均值部分)
|
||
if (currentRowY + 80 > page.Height - bottomMargin)
|
||
{
|
||
gfx.Dispose();
|
||
page = document.AddPage();
|
||
page.Width = XUnit.FromMillimeter(210);
|
||
page.Height = XUnit.FromMillimeter(297);
|
||
gfx = XGraphics.FromPdfPage(page);
|
||
currentRowY = 50;
|
||
}
|
||
|
||
// 平均值(单位已替换)
|
||
gfx.DrawString($"平均热导率: {AverageThermalConductivity:F6} W/(m.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
|
||
gfx.DrawString($"平均热扩散率: {AverageThermalDiffusivity:E6} m^2/s", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
|
||
gfx.DrawString($"平均体积热容: {AverageVolumetricHeatCapacity:F2} kJ/(m^3.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
|
||
// 比热容转换:VHC 单位 kJ/(m^3.K) 先转为 J/(m^3.K) 再除以密度得到 J/(kg.K)
|
||
double avgCp_J_per_kgK = (AverageVolumetricHeatCapacity * 1000) / SampleDensity;
|
||
gfx.DrawString($"平均比热容: {avgCp_J_per_kgK:F0} J/(kg.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 40;
|
||
|
||
// 页脚
|
||
gfx.DrawString($"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", normalFont, XBrushes.Black, new XPoint(40, page.Height - 30));
|
||
gfx.Dispose();
|
||
|
||
document.Save(filePath);
|
||
}
|
||
}
|
||
|
||
|
||
[RelayCommand]
|
||
private async Task StopTest()
|
||
{
|
||
if (!IsTesting) return;
|
||
_stopRequested = true;
|
||
_testCts?.Cancel(); // 取消所有等待的 Delay
|
||
StatusMessage = "正在停止测试...";
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false);
|
||
if (UsePressure)
|
||
{
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, false);
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, true);
|
||
await Task.Delay(1000);
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, false);
|
||
}
|
||
IsTesting = false;
|
||
StatusMessage = "测试已停止。";
|
||
}
|
||
[RelayCommand] private async Task PressureCalibrationAsync() => await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.PressureCalibrationCoil, true);
|
||
[RelayCommand] private async Task ResistanceZeroAsync() => await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.ResistanceZeroCoil, true);
|
||
[RelayCommand]
|
||
private async Task InletValveControlAsync()
|
||
{
|
||
bool current = await _plcService.ReadCoilAsync(_config.PlcRegisterAddresses.InletValveCoil);
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, !current);
|
||
StatusMessage = $"进气阀已{(current ? "关闭" : "开启")}";
|
||
}
|
||
[RelayCommand]
|
||
private async Task OutletValveControlAsync()
|
||
{
|
||
bool current = await _plcService.ReadCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil);
|
||
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, !current);
|
||
StatusMessage = $"排气阀已{(current ? "关闭" : "开启")}";
|
||
}
|
||
[RelayCommand] private void ConfirmBubbleRemoved() => BubbleRemoved = true;
|
||
[RelayCommand]
|
||
private void ConfirmClean()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(CleanerName))
|
||
{
|
||
MessageBox.Show("请输入清洁人员姓名", "提示");
|
||
return;
|
||
}
|
||
IsCleanConfirmed = true;
|
||
}
|
||
[RelayCommand] private void ConfirmPlatinumCompatible() => PlatinumCompatible = true;
|
||
[RelayCommand]
|
||
private async Task CalibrateAmbientAsync()
|
||
{
|
||
await EnsureConnected();
|
||
float temp = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Temperature);
|
||
AmbientTemperature = temp;
|
||
AmbientCalibrated = true;
|
||
StatusMessage = $"环境温度校准完成:{AmbientTemperature:F1} °C";
|
||
}
|
||
[RelayCommand] private async Task PerformSystemCalibrationAsync() { /* 系统校准逻辑待实现 */ }
|
||
private async Task EnsureConnected()
|
||
{
|
||
if (!await _plcService.IsConnectedAsync())
|
||
await _plcService.ConnectAsync();
|
||
}
|
||
private async Task UpdateRealTimeParametersAsync()
|
||
{
|
||
if (!await _plcService.IsConnectedAsync()) return;
|
||
try
|
||
{
|
||
float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure);
|
||
ChamberPressure = rawPressure / 10.0;
|
||
}
|
||
catch { }
|
||
}
|
||
} |