2026-05-15 10:59:24 +08:00
|
|
|
|
using ASTM_D7896_Tester.Helpers;
|
2026-04-18 19:00:34 +08:00
|
|
|
|
using ASTM_D7896_Tester.Models;
|
|
|
|
|
|
using ASTM_D7896_Tester.Services;
|
2026-05-15 10:59:24 +08:00
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
|
|
using OxyPlot;
|
|
|
|
|
|
using OxyPlot.Axes;
|
|
|
|
|
|
using OxyPlot.Series;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
|
using System.Linq;
|
2026-05-20 13:49:45 +08:00
|
|
|
|
using System.Threading;
|
2026-05-15 10:59:24 +08:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows;
|
2026-04-18 19:00:34 +08:00
|
|
|
|
|
|
|
|
|
|
namespace ASTM_D7896_Tester.ViewModels;
|
|
|
|
|
|
|
|
|
|
|
|
public partial class D7896ViewModel : ObservableObject
|
|
|
|
|
|
{
|
2026-05-15 20:39:11 +08:00
|
|
|
|
private readonly IPlcService _plcService;
|
2026-04-18 19:00:34 +08:00
|
|
|
|
private AppConfig _config;
|
2026-05-15 20:39:11 +08:00
|
|
|
|
private readonly ReportService _reportService;
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 电压表服务
|
|
|
|
|
|
private Th1963LanService _th1963Ustd; // 6位半测量标准电阻电压 U_std
|
2026-05-26 19:37:04 +08:00
|
|
|
|
private Th1963LanService _th1953Ustd; // 6位半测量标准电阻电压 U_std
|
|
|
|
|
|
//private FiveHalfDmmService _fiveHalfUpt; // 5位半测量铂丝电压 U_pt
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
private CancellationTokenSource _testCts; // 用于停止测试
|
|
|
|
|
|
private bool _stopRequested;
|
|
|
|
|
|
|
|
|
|
|
|
// 后台监控定时器
|
|
|
|
|
|
private Timer? _monitorTimer;
|
|
|
|
|
|
|
|
|
|
|
|
// 常量: 标准电阻值 1Ω
|
|
|
|
|
|
private const double StandardResistor = 1.0;
|
|
|
|
|
|
|
|
|
|
|
|
// 铂丝电阻温度系数 (纯铂)
|
|
|
|
|
|
private const double AlphaPt = 0.00385; // /°C
|
|
|
|
|
|
|
|
|
|
|
|
// 加热功率 Q 计算相关
|
|
|
|
|
|
private double _heatingCurrent; // 实际加热电流平均值
|
|
|
|
|
|
private double _wireResistanceAvg; // 铂丝平均电阻
|
2026-05-15 21:10:42 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 温升曲线数据
|
|
|
|
|
|
[ObservableProperty] private string _curveTitle = "温升曲线";
|
|
|
|
|
|
[ObservableProperty] private PlotModel _temperatureCurveModel;
|
|
|
|
|
|
|
|
|
|
|
|
// UI 绑定属性 (与之前一致)
|
|
|
|
|
|
public ObservableCollection<string> ReferenceLiquids { get; } = new() { "蒸馏水", "甲苯", "乙二醇" };
|
2026-05-15 21:10:42 +08:00
|
|
|
|
[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 = "";
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
[ObservableProperty] private double _platinumResistance = 0.0;
|
|
|
|
|
|
[ObservableProperty] private double _chamberPressure = 0.0;
|
|
|
|
|
|
[ObservableProperty] private double _currentTestTemperature = 0.0;
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
2026-05-15 21:10:42 +08:00
|
|
|
|
[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;
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 实时电压显示(可选)
|
|
|
|
|
|
[ObservableProperty] private double _platinumVoltage;
|
|
|
|
|
|
[ObservableProperty] private double _standardResistorVoltage;
|
2026-05-15 10:59:24 +08:00
|
|
|
|
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
[ObservableProperty] private double _sampleDensity = 1000.0; // 新增,密度默认值1000 kg/m³(水)
|
2026-05-26 19:37:04 +08:00
|
|
|
|
double heatingDuration = 0.8; // 加热时间 0.8 秒(需与您的加热脉冲宽度一致)
|
|
|
|
|
|
double totalDuration = 1.6; // 总采样时间(加热 + 冷却)
|
2026-04-18 19:00:34 +08:00
|
|
|
|
public D7896ViewModel()
|
|
|
|
|
|
{
|
2026-05-15 21:10:42 +08:00
|
|
|
|
_config = App.PlcConfig ?? new AppConfig();
|
|
|
|
|
|
_plcService = App.PlcService;
|
2026-04-18 19:00:34 +08:00
|
|
|
|
_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;
|
2026-05-15 21:10:42 +08:00
|
|
|
|
|
2026-05-15 10:59:24 +08:00
|
|
|
|
IsCleanConfirmed = true;
|
|
|
|
|
|
BubbleRemoved = true;
|
|
|
|
|
|
PlatinumCompatible = true;
|
|
|
|
|
|
AmbientCalibrated = true;
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 初始化电压表服务
|
|
|
|
|
|
// TH1963 IP 地址需要根据实际配置修改,建议从配置文件读取
|
|
|
|
|
|
_th1963Ustd = new Th1963LanService();
|
2026-05-26 19:37:04 +08:00
|
|
|
|
|
|
|
|
|
|
_th1953Ustd = new Th1963LanService();
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
2026-05-20 13:49:45 +08:00
|
|
|
|
StartBackgroundMonitoring();
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 13:49:45 +08:00
|
|
|
|
private async void StartBackgroundMonitoring()
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(1000);
|
|
|
|
|
|
_monitorTimer = new Timer(async _ => await MonitorPlcValues(), null, 0, 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task MonitorPlcValues()
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (!await _plcService.IsConnectedAsync()) return;
|
2026-05-26 19:37:04 +08:00
|
|
|
|
if (Application.Current == null || Application.Current.Dispatcher == null) return;
|
2026-05-20 19:46:52 +08:00
|
|
|
|
try
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
float rawResistance = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Resistance);
|
|
|
|
|
|
double newResistance = rawResistance;
|
2026-05-26 19:37:04 +08:00
|
|
|
|
Application.Current?.Dispatcher.Invoke(() => PlatinumResistance = newResistance);
|
2026-05-15 21:10:42 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure);
|
2026-05-26 19:37:04 +08:00
|
|
|
|
Application.Current?.Dispatcher.Invoke(() => ChamberPressure = rawPressure);
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
float rawTemp = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Temperature);
|
2026-05-26 19:37:04 +08:00
|
|
|
|
Application.Current?.Dispatcher.Invoke(() => CurrentTestTemperature = rawTemp);
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
catch { }
|
2026-05-20 13:49:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
//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; }
|
|
|
|
|
|
//}
|
2026-05-15 21:10:42 +08:00
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task StartTestAsync()
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (IsTesting)
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show("测试正在进行中", "提示");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 前置检查
|
|
|
|
|
|
if (!IsCleanConfirmed || !BubbleRemoved || !PlatinumCompatible || !AmbientCalibrated)
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show("请完成所有测试前确认项", "前置条件未满足");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (SampleVolume <= 0)
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show("请输入有效的样品量", "参数错误");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (UsePressure && PressureValue <= 0)
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show("请设置有效的加压值", "参数错误");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 连接PLC和电压表
|
2026-04-18 19:00:34 +08:00
|
|
|
|
if (!await _plcService.IsConnectedAsync())
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (!await _plcService.ConnectAsync())
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show("无法连接到PLC", "错误");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-05-26 19:37:04 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
await _th1963Ustd.ConnectAsync("192.168.1.12", 45454); // 改为实际IP
|
|
|
|
|
|
await _th1963Ustd.ConfigureForHighSpeedDcvAsync();
|
2026-05-26 19:37:04 +08:00
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
await _th1953Ustd.ConnectAsync("192.168.1.13", 45454); // 改为实际IP
|
2026-05-26 19:37:04 +08:00
|
|
|
|
await _th1953Ustd.ConfigureForHighSpeedDcvAsync();
|
2026-05-20 19:46:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"电压表连接失败: {ex.Message}", "错误");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 21:10:42 +08:00
|
|
|
|
if (UsePressure)
|
|
|
|
|
|
{
|
2026-05-20 13:49:45 +08:00
|
|
|
|
StatusMessage = "正在加压...";
|
2026-05-15 21:10:42 +08:00
|
|
|
|
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, true);
|
2026-05-27 14:04:14 +08:00
|
|
|
|
|
|
|
|
|
|
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";
|
2026-05-15 21:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
//double initialResistance = await GetInitialResistanceAsync();
|
|
|
|
|
|
//if (initialResistance > 0)
|
|
|
|
|
|
// StatusMessage = $"初始电阻: {initialResistance:F4} Ω";
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
2026-04-18 19:00:34 +08:00
|
|
|
|
Measurements.Clear();
|
|
|
|
|
|
IsTesting = true;
|
2026-05-20 19:46:52 +08:00
|
|
|
|
_stopRequested = false;
|
|
|
|
|
|
_testCts = new CancellationTokenSource();
|
2026-04-18 19:00:34 +08:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-05-26 19:37:04 +08:00
|
|
|
|
|
|
|
|
|
|
// 预热:进行一次虚拟测量
|
|
|
|
|
|
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(); // 丢弃结果
|
|
|
|
|
|
|
2026-04-18 19:00:34 +08:00
|
|
|
|
for (int i = 1; i <= _config.TestParameters.MeasurementCount; i++)
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (_stopRequested) break;
|
|
|
|
|
|
|
2026-04-18 19:00:34 +08:00
|
|
|
|
CurrentMeasurementIndex = i;
|
|
|
|
|
|
StatusMessage = $"正在执行第 {i} 次测量...";
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 准备批量采集参数(每通道采样点数,采样率1000点/秒,加热时间1秒 -> 1000点)
|
2026-05-27 14:04:14 +08:00
|
|
|
|
int samples = 200; // 1秒 * 1000点/秒
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 预配置两台表:进入等待触发状态
|
|
|
|
|
|
await _th1963Ustd.PrepareBatchAsync(samples);
|
2026-05-26 19:37:04 +08:00
|
|
|
|
await _th1953Ustd.PrepareBatchAsync(samples);
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 启动加热脉冲 (PLC)
|
2026-05-15 20:39:11 +08:00
|
|
|
|
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, true);
|
2026-05-26 19:37:04 +08:00
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
|
|
|
|
|
|
try { await Task.Delay(5, _testCts.Token); } catch (OperationCanceledException) { break; }
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 同时发送触发信号给两台电压表
|
2026-05-26 19:37:04 +08:00
|
|
|
|
await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
|
2026-04-18 19:00:34 +08:00
|
|
|
|
|
2026-05-26 19:37:04 +08:00
|
|
|
|
// 等待加热结束
|
2026-05-27 14:04:14 +08:00
|
|
|
|
try { await Task.Delay((int)(heatingDuration * 1000), _testCts.Token); } catch (OperationCanceledException) { break; }
|
2026-04-18 19:00:34 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 停止加热
|
|
|
|
|
|
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false);
|
2026-04-18 19:00:34 +08:00
|
|
|
|
|
2026-05-26 19:37:04 +08:00
|
|
|
|
// 等待采集完成(剩余时间)
|
|
|
|
|
|
int remainingMs = (int)((totalDuration - heatingDuration) * 1000) + 100;
|
2026-05-27 14:04:14 +08:00
|
|
|
|
try { await Task.Delay(remainingMs, _testCts.Token); } catch (OperationCanceledException) { break; }
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取采集数据
|
|
|
|
|
|
double[] ustd = await _th1963Ustd.FetchBatchAsync();
|
2026-05-26 19:37:04 +08:00
|
|
|
|
double[] upt = await _th1953Ustd.FetchBatchAsync();
|
2026-05-27 14:04:14 +08:00
|
|
|
|
for (int j = 0; j < 20; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Logger.Log($"第{j}点: U_std={ustd[j]:F6} V, U_pt={upt[j]:F6} V");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-24 10:36:57 +08:00
|
|
|
|
|
2026-05-26 19:37:04 +08:00
|
|
|
|
StandardResistorVoltage = ustd.Average();
|
|
|
|
|
|
PlatinumVoltage = upt.Average();
|
|
|
|
|
|
// 添加日志:原始电压统计
|
|
|
|
|
|
Logger.Log($"测量 {i}: U_std 点数={ustd.Length}, 平均值={ustd.Average():F6} V, 最大值={ustd.Max():F6} V");
|
|
|
|
|
|
Logger.Log($"测量 {i}: U_pt 点数={upt.Length}, 平均值={upt.Average():F6} V, 最大值={upt.Max():F6} V");
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
|
|
|
|
|
double[] timeArray = new double[ustd.Length];
|
|
|
|
|
|
for (int idx = 0; idx < timeArray.Length; idx++)
|
|
|
|
|
|
{
|
2026-05-26 19:37:04 +08:00
|
|
|
|
timeArray[idx] = idx * totalDuration / samples;
|
2026-05-20 19:46:52 +08:00
|
|
|
|
}
|
2026-05-15 21:10:42 +08:00
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 动态计算初始电阻 R0(取前 10 个点的平均值,这期间温升很小)
|
|
|
|
|
|
int warmupPoints = Math.Min(10, ustd.Length);
|
|
|
|
|
|
double sumR0 = 0;
|
|
|
|
|
|
for (int j = 0; j < warmupPoints; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
double current = ustd[j] / StandardResistor;
|
|
|
|
|
|
double resistance = upt[j] / current;
|
|
|
|
|
|
sumR0 += resistance;
|
|
|
|
|
|
}
|
|
|
|
|
|
double dynamicR0 = sumR0 / warmupPoints;
|
|
|
|
|
|
Logger.Log($"动态计算 R0 = {dynamicR0:F6} Ω");
|
|
|
|
|
|
|
|
|
|
|
|
// 计算本次测量的 λ 和 α
|
|
|
|
|
|
var (lambda, alpha, deltaT, coolingPoints) = ComputeThermalProperties(upt, ustd, timeArray, dynamicR0, CurrentTestTemperature);
|
2026-05-26 19:37:04 +08:00
|
|
|
|
// 添加结果日志
|
|
|
|
|
|
Logger.Log($"测量 {i} 结果: λ={lambda:F6} W/(m·K), α={alpha:E6} m²/s");
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 生成温升曲线图
|
|
|
|
|
|
GenerateTemperatureCurveFromData(timeArray, deltaT, coolingPoints);
|
2026-05-15 21:10:42 +08:00
|
|
|
|
|
2026-04-18 19:00:34 +08:00
|
|
|
|
var result = new MeasurementResult
|
|
|
|
|
|
{
|
|
|
|
|
|
Index = i,
|
|
|
|
|
|
ThermalConductivity = lambda,
|
|
|
|
|
|
ThermalDiffusivity = alpha
|
|
|
|
|
|
};
|
2026-05-20 19:46:52 +08:00
|
|
|
|
//result.CalculateVhc();
|
|
|
|
|
|
result.CalculateVhcAndCp(SampleDensity);
|
2026-05-15 21:10:42 +08:00
|
|
|
|
Application.Current.Dispatcher.Invoke(() => Measurements.Add(result));
|
2026-04-18 19:00:34 +08:00
|
|
|
|
StatusMessage = $"第 {i} 次测量完成,λ={lambda:F4} W/m·K";
|
|
|
|
|
|
|
2026-05-26 19:37:04 +08:00
|
|
|
|
// 在 result.CalculateVhcAndCp(SampleDensity); 之后添加
|
|
|
|
|
|
Logger.Log($"========== 第 {i} 次测量详细数据 ==========");
|
|
|
|
|
|
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: {result.SpecificHeatCapacity:F2} J/(kg·K) (密度 = {SampleDensity:F1} kg/m³)");
|
2026-05-27 14:04:14 +08:00
|
|
|
|
Logger.Log($"初始电阻 R0: {dynamicR0:F6} Ω");
|
2026-05-26 19:37:04 +08:00
|
|
|
|
Logger.Log($"测试温度: {CurrentTestTemperature:F2} °C");
|
|
|
|
|
|
Logger.Log($"铂丝平均电阻: {PlatinumResistance:F6} Ω");
|
|
|
|
|
|
Logger.Log($"样品池压力: {ChamberPressure:F2} kPa");
|
|
|
|
|
|
Logger.Log("===========================================");
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (i < _config.TestParameters.MeasurementCount && !_stopRequested)
|
2026-05-27 14:04:14 +08:00
|
|
|
|
{
|
|
|
|
|
|
try { await Task.Delay(_config.TestParameters.IntervalSeconds * 1000, _testCts.Token); } catch (OperationCanceledException) { break; }
|
|
|
|
|
|
}
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CalculateAverages();
|
2026-05-20 19:46:52 +08:00
|
|
|
|
StatusMessage = _stopRequested ? "测试已停止。" : "测试完成。";
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
StatusMessage = $"测试出错: {ex.Message}";
|
|
|
|
|
|
MessageBox.Show($"测试过程中发生错误: {ex.Message}", "错误");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 停止加热,泄压
|
|
|
|
|
|
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false);
|
2026-05-15 21:10:42 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-04-18 19:00:34 +08:00
|
|
|
|
IsTesting = false;
|
2026-05-26 19:37:04 +08:00
|
|
|
|
//_fiveHalfUpt.Close();
|
2026-05-20 19:46:52 +08:00
|
|
|
|
_th1963Ustd.Dispose();
|
2026-05-26 19:37:04 +08:00
|
|
|
|
_th1953Ustd.Dispose();
|
2026-05-20 19:46:52 +08:00
|
|
|
|
_testCts?.Dispose();
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
///// <summary>
|
|
|
|
|
|
///// 根据采集到的电压序列计算热导率 λ、热扩散率 α、温升数组以及冷却曲线数据点
|
|
|
|
|
|
///// </summary>
|
|
|
|
|
|
//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[] current = new double[n];
|
|
|
|
|
|
// double[] ptResistance = new double[n];
|
|
|
|
|
|
// double[] deltaT = new double[n];
|
|
|
|
|
|
|
|
|
|
|
|
// // 1. 计算电流、铂丝电阻和温升
|
|
|
|
|
|
// for (int i = 0; i < n; i++)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// current[i] = ustd[i] / StandardResistor;
|
|
|
|
|
|
// ptResistance[i] = upt[i] / current[i];
|
|
|
|
|
|
// deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// // 添加日志:中间数据统计
|
|
|
|
|
|
// Logger.Log($"电流平均值: {current.Average():F6} A, 最大值: {current.Max():F6} A");
|
|
|
|
|
|
// Logger.Log($"铂丝电阻平均值: {ptResistance.Average():F6} Ω, 初始电阻: {initialResistance:F6} Ω");
|
|
|
|
|
|
// Logger.Log($"温升最大值: {deltaT.Max():F4} ℃, 平均值: {deltaT.Average():F4} ℃");
|
|
|
|
|
|
|
|
|
|
|
|
// // 2. ========== 加热段拟合 → 热导率 λ ==========
|
|
|
|
|
|
// double tStart = 0.1;
|
|
|
|
|
|
// double tEndHeating = 0.8;
|
|
|
|
|
|
// int startIdx = FindIndex(time, tStart);
|
|
|
|
|
|
// int endIdxHeating = FindIndex(time, tEndHeating);
|
|
|
|
|
|
// if (startIdx < 0) startIdx = 0;
|
|
|
|
|
|
// if (endIdxHeating >= n) endIdxHeating = n - 1;
|
|
|
|
|
|
|
|
|
|
|
|
// var heatingPoints = new List<DataPoint>();
|
|
|
|
|
|
// for (int i = startIdx; i <= endIdxHeating; i++)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// heatingPoints.Add(new DataPoint(Math.Log(time[i]), deltaT[i]));
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// double slope = LeastSquaresSlope(heatingPoints);
|
|
|
|
|
|
// if (slope <= 0) slope = 0.0001;
|
|
|
|
|
|
|
|
|
|
|
|
// Logger.Log($"加热段拟合斜率 B = {slope:F6} (ΔT vs ln(t))");
|
|
|
|
|
|
|
|
|
|
|
|
// double avgCurrentSq = current.Average(c => c * c);
|
|
|
|
|
|
// double avgResistance = ptResistance.Average();
|
|
|
|
|
|
// double powerPerLength = (avgCurrentSq * avgResistance) / _config.TestParameters.PlatinumWireLength;
|
|
|
|
|
|
// double lambda = powerPerLength / (4 * Math.PI * slope);
|
|
|
|
|
|
// Logger.Log($"单位长度加热功率 Q = {powerPerLength:F6} W/m");
|
|
|
|
|
|
// Logger.Log($"热导率 λ = {lambda:F6} W/(m·K)");
|
|
|
|
|
|
// // 3. ========== 冷却段拟合 → 热扩散率 α ==========
|
|
|
|
|
|
// double coolingStart = heatingDuration; // 0.8 秒
|
|
|
|
|
|
// double coolingEnd = totalDuration; // 1.6 秒
|
|
|
|
|
|
// int coolingStartIdx = FindIndex(time, coolingStart);
|
|
|
|
|
|
// int coolingEndIdx = FindIndex(time, coolingEnd);
|
|
|
|
|
|
// if (coolingStartIdx < 0) coolingStartIdx = n / 2;
|
|
|
|
|
|
// if (coolingEndIdx >= n) coolingEndIdx = n - 1;
|
|
|
|
|
|
|
|
|
|
|
|
// var coolingPointsForFit = new List<DataPoint>();
|
|
|
|
|
|
// for (int i = coolingStartIdx; i <= coolingEndIdx; i++)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// if (deltaT[i] > 0.001)
|
|
|
|
|
|
// coolingPointsForFit.Add(new DataPoint(time[i], Math.Log(deltaT[i])));
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// double coolingSlope = LeastSquaresSlopeOnTime(coolingPointsForFit);
|
|
|
|
|
|
// double tau = -1.0 / coolingSlope;
|
|
|
|
|
|
// double wireRadius = 0.00003; // 半径 = 直径0.06mm /2
|
|
|
|
|
|
// double alpha = (wireRadius * wireRadius) / (4.0 * tau);
|
|
|
|
|
|
// if (alpha <= 0 || double.IsNaN(alpha) || double.IsInfinity(alpha))
|
|
|
|
|
|
// alpha = 0.12e-6; // 默认值
|
|
|
|
|
|
|
|
|
|
|
|
// Logger.Log($"冷却段拟合斜率 D = {coolingSlope:F6} (lnΔT vs t)");
|
|
|
|
|
|
// Logger.Log($"时间常数 τ = {tau:F6} s");
|
|
|
|
|
|
// Logger.Log($"热扩散率 α = {alpha:E6} m²/s");
|
|
|
|
|
|
|
|
|
|
|
|
// // 准备冷却曲线数据点(用于绘图)
|
|
|
|
|
|
// var coolingPoints = new List<DataPoint>();
|
|
|
|
|
|
// for (int i = coolingStartIdx; i <= coolingEndIdx; i++)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// if (deltaT[i] > 0.001)
|
|
|
|
|
|
// coolingPoints.Add(new DataPoint(time[i], deltaT[i]));
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// return (lambda, alpha, deltaT, coolingPoints);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
/// <summary>
|
2026-05-26 20:58:48 +08:00
|
|
|
|
/// 根据采集到的电压序列计算热导率 λ 和热扩散率 α(标准截距法)
|
2026-05-20 19:46:52 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
private (double lambda, double alpha, double[] deltaT, List<DataPoint> coolingPoints) ComputeThermalProperties(
|
|
|
|
|
|
double[] upt, double[] ustd, double[] time, double initialResistance, double bathTemp)
|
2026-05-20 13:49:45 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
int n = Math.Min(upt.Length, ustd.Length);
|
|
|
|
|
|
double[] current = new double[n];
|
|
|
|
|
|
double[] ptResistance = new double[n];
|
|
|
|
|
|
double[] deltaT = new double[n];
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 计算电流、铂丝电阻和温升
|
|
|
|
|
|
for (int i = 0; i < n; i++)
|
2026-05-20 13:49:45 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
current[i] = ustd[i] / StandardResistor;
|
|
|
|
|
|
ptResistance[i] = upt[i] / current[i];
|
|
|
|
|
|
deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance);
|
2026-05-20 13:49:45 +08:00
|
|
|
|
}
|
2026-05-26 19:37:04 +08:00
|
|
|
|
Logger.Log($"电流平均值: {current.Average():F6} A, 最大值: {current.Max():F6} A");
|
|
|
|
|
|
Logger.Log($"铂丝电阻平均值: {ptResistance.Average():F6} Ω, 初始电阻: {initialResistance:F6} Ω");
|
|
|
|
|
|
Logger.Log($"温升最大值: {deltaT.Max():F4} ℃, 平均值: {deltaT.Average():F4} ℃");
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
// 2. 加热段拟合:选取有效时间窗口 (0.1s ~ 0.8s)
|
2026-05-20 19:46:52 +08:00
|
|
|
|
double tStart = 0.1;
|
|
|
|
|
|
double tEndHeating = 0.8;
|
|
|
|
|
|
int startIdx = FindIndex(time, tStart);
|
|
|
|
|
|
int endIdxHeating = FindIndex(time, tEndHeating);
|
|
|
|
|
|
if (startIdx < 0) startIdx = 0;
|
|
|
|
|
|
if (endIdxHeating >= n) endIdxHeating = n - 1;
|
|
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
var points = new List<DataPoint>();
|
2026-05-20 19:46:52 +08:00
|
|
|
|
for (int i = startIdx; i <= endIdxHeating; i++)
|
2026-05-20 13:49:45 +08:00
|
|
|
|
{
|
2026-05-26 20:58:48 +08:00
|
|
|
|
points.Add(new DataPoint(Math.Log(time[i]), deltaT[i]));
|
2026-05-20 13:49:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
// 最小二乘法拟合直线 ΔT = slope * ln(t) + intercept
|
|
|
|
|
|
(double slope, double intercept) = LinearRegression(points);
|
|
|
|
|
|
if (slope <= 0) slope = 1e-6;
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
Logger.Log($"加热段拟合斜率 S = {slope:F6}, 截距 B = {intercept:F6}");
|
2026-05-26 19:37:04 +08:00
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
// 3. 计算单位长度加热功率 q (W/m)
|
2026-05-20 19:46:52 +08:00
|
|
|
|
double avgCurrentSq = current.Average(c => c * c);
|
|
|
|
|
|
double avgResistance = ptResistance.Average();
|
|
|
|
|
|
double powerPerLength = (avgCurrentSq * avgResistance) / _config.TestParameters.PlatinumWireLength;
|
2026-05-26 20:58:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 4. 热导率 λ = q / (4π * slope)
|
2026-05-20 19:46:52 +08:00
|
|
|
|
double lambda = powerPerLength / (4 * Math.PI * slope);
|
2026-05-26 19:37:04 +08:00
|
|
|
|
Logger.Log($"热导率 λ = {lambda:F6} W/(m·K)");
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
// 5. 热扩散率 α(截距法)
|
|
|
|
|
|
double wireRadius = 0.00003; // 铂丝半径 0.03 mm
|
|
|
|
|
|
double eulerGamma = 0.5772156649;
|
|
|
|
|
|
double alpha = (wireRadius * wireRadius / 4.0) * Math.Exp(intercept / slope + eulerGamma);
|
2026-05-20 19:46:52 +08:00
|
|
|
|
if (alpha <= 0 || double.IsNaN(alpha) || double.IsInfinity(alpha))
|
2026-05-26 20:58:48 +08:00
|
|
|
|
alpha = 1e-7; // 合理默认值
|
2026-05-26 19:37:04 +08:00
|
|
|
|
Logger.Log($"热扩散率 α = {alpha:E6} m²/s");
|
|
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
// 冷却曲线数据点(仅用于绘图,不参与 α 计算)
|
2026-05-20 19:46:52 +08:00
|
|
|
|
var coolingPoints = new List<DataPoint>();
|
2026-05-26 20:58:48 +08:00
|
|
|
|
double coolingStart = heatingDuration;
|
|
|
|
|
|
double coolingEnd = totalDuration;
|
|
|
|
|
|
int coolingStartIdx = FindIndex(time, coolingStart);
|
|
|
|
|
|
int coolingEndIdx = FindIndex(time, coolingEnd);
|
|
|
|
|
|
if (coolingStartIdx < 0) coolingStartIdx = n / 2;
|
|
|
|
|
|
if (coolingEndIdx >= n) coolingEndIdx = n - 1;
|
2026-05-20 19:46:52 +08:00
|
|
|
|
for (int i = coolingStartIdx; i <= coolingEndIdx; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (deltaT[i] > 0.001)
|
|
|
|
|
|
coolingPoints.Add(new DataPoint(time[i], deltaT[i]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (lambda, alpha, deltaT, coolingPoints);
|
2026-05-20 13:49:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 20:58:48 +08:00
|
|
|
|
/// <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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查找时间数组中与目标时间最接近的索引
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int FindIndex(double[] timeArray, double targetTime)
|
2026-05-20 13:49:45 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
for (int i = 0; i < timeArray.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (timeArray[i] >= targetTime)
|
|
|
|
|
|
return i;
|
|
|
|
|
|
}
|
|
|
|
|
|
return timeArray.Length - 1;
|
|
|
|
|
|
}
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
///// <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;
|
|
|
|
|
|
//}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
///// <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;
|
|
|
|
|
|
//}
|
2026-05-20 13:49:45 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
private void GenerateTemperatureCurveFromData(double[] time, double[] deltaT, List<DataPoint> coolingPoints)
|
2026-05-15 10:59:24 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (TemperatureCurveModel == null)
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
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 = "温升 (℃)" });
|
2026-05-15 10:59:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 加热段曲线(红色)
|
|
|
|
|
|
var heatingSeries = new LineSeries
|
2026-05-15 10:59:24 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
Title = $"第{CurrentMeasurementIndex}次测量 - 加热段",
|
|
|
|
|
|
Color = OxyColors.Red,
|
2026-05-15 21:10:42 +08:00
|
|
|
|
StrokeThickness = 1.5
|
2026-05-15 10:59:24 +08:00
|
|
|
|
};
|
2026-05-20 19:46:52 +08:00
|
|
|
|
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);
|
2026-05-15 10:59:24 +08:00
|
|
|
|
|
2026-05-20 19:46:52 +08:00
|
|
|
|
// 冷却曲线(蓝色虚线)
|
|
|
|
|
|
if (coolingPoints != null && coolingPoints.Count > 0)
|
2026-05-15 10:59:24 +08:00
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
var coolingSeries = new LineSeries
|
|
|
|
|
|
{
|
|
|
|
|
|
Title = $"第{CurrentMeasurementIndex}次测量 - 冷却段",
|
|
|
|
|
|
Color = OxyColors.Blue,
|
|
|
|
|
|
StrokeThickness = 1.5,
|
|
|
|
|
|
LineStyle = LineStyle.Dash
|
|
|
|
|
|
};
|
|
|
|
|
|
foreach (var p in coolingPoints)
|
|
|
|
|
|
{
|
|
|
|
|
|
coolingSeries.Points.Add(p);
|
|
|
|
|
|
}
|
|
|
|
|
|
TemperatureCurveModel.Series.Add(coolingSeries);
|
2026-05-15 10:59:24 +08:00
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
|
2026-05-15 10:59:24 +08:00
|
|
|
|
TemperatureCurveModel.InvalidatePlot(true);
|
|
|
|
|
|
CurveTitle = $"已完成 {CurrentMeasurementIndex} 次测量";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-18 19:00:34 +08:00
|
|
|
|
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();
|
2026-05-15 21:10:42 +08:00
|
|
|
|
AverageThermalConductivity = AverageThermalDiffusivity = AverageVolumetricHeatCapacity = 0;
|
2026-04-18 19:00:34 +08:00
|
|
|
|
CurrentMeasurementIndex = 0;
|
|
|
|
|
|
StatusMessage = "已重置";
|
|
|
|
|
|
TestDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
2026-05-15 21:10:42 +08:00
|
|
|
|
TemperatureCurveModel = null;
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task GenerateReportAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Measurements.Count == 0)
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show("没有测试数据", "提示");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var extraParams = new Dictionary<string, object>
|
|
|
|
|
|
{
|
|
|
|
|
|
["SampleVolume"] = SampleVolume,
|
|
|
|
|
|
["BubbleRemoved"] = BubbleRemoved,
|
|
|
|
|
|
["UsePressure"] = UsePressure,
|
|
|
|
|
|
["PressureValue"] = PressureValue,
|
|
|
|
|
|
["IsCleanConfirmed"] = IsCleanConfirmed,
|
|
|
|
|
|
["CleanerName"] = CleanerName,
|
|
|
|
|
|
["AmbientTemperature"] = AmbientTemperature,
|
|
|
|
|
|
["AmbientCalibrated"] = AmbientCalibrated,
|
|
|
|
|
|
["PlatinumCompatible"] = PlatinumCompatible,
|
2026-05-20 13:49:45 +08:00
|
|
|
|
["LiquidReactivityNote"] = LiquidReactivityNote,
|
2026-05-20 19:46:52 +08:00
|
|
|
|
["InitialResistance"] = PlatinumResistance
|
2026-04-18 19:00:34 +08:00
|
|
|
|
};
|
2026-05-15 21:10:42 +08:00
|
|
|
|
string reportPath = await _reportService.GenerateReportAsync(SampleId, TestTemperature, Measurements.ToList(),
|
|
|
|
|
|
AverageThermalConductivity, AverageThermalDiffusivity, AverageVolumetricHeatCapacity,
|
|
|
|
|
|
_config.TestParameters, extraParams);
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show($"报告已生成: {reportPath}", "成功");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show($"生成报告失败: {ex.Message}", "错误");
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 14:04:14 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-18 19:00:34 +08:00
|
|
|
|
[RelayCommand]
|
2026-05-27 14:04:14 +08:00
|
|
|
|
private async Task StopTest()
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-27 14:04:14 +08:00
|
|
|
|
if (!IsTesting) return;
|
2026-05-20 19:46:52 +08:00
|
|
|
|
_stopRequested = true;
|
2026-05-27 14:04:14 +08:00
|
|
|
|
_testCts?.Cancel(); // 取消所有等待的 Delay
|
2026-05-20 19:46:52 +08:00
|
|
|
|
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 = "测试已停止。";
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
[RelayCommand] private async Task PressureCalibrationAsync() => await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.PressureCalibrationCoil, true);
|
|
|
|
|
|
[RelayCommand] private async Task ResistanceZeroAsync() => await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.ResistanceZeroCoil, true);
|
2026-04-18 19:00:34 +08:00
|
|
|
|
[RelayCommand]
|
2026-05-15 21:10:42 +08:00
|
|
|
|
private async Task InletValveControlAsync()
|
2026-04-18 19:00:34 +08:00
|
|
|
|
{
|
2026-05-15 21:10:42 +08:00
|
|
|
|
bool current = await _plcService.ReadCoilAsync(_config.PlcRegisterAddresses.InletValveCoil);
|
|
|
|
|
|
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.InletValveCoil, !current);
|
2026-05-20 13:49:45 +08:00
|
|
|
|
StatusMessage = $"进气阀已{(current ? "关闭" : "开启")}";
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
[RelayCommand]
|
2026-05-15 21:10:42 +08:00
|
|
|
|
private async Task OutletValveControlAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
bool current = await _plcService.ReadCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil);
|
|
|
|
|
|
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.OutletValveCoil, !current);
|
2026-05-20 13:49:45 +08:00
|
|
|
|
StatusMessage = $"排气阀已{(current ? "关闭" : "开启")}";
|
2026-05-15 21:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
[RelayCommand] private void ConfirmBubbleRemoved() => BubbleRemoved = true;
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void ConfirmClean()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(CleanerName))
|
|
|
|
|
|
{
|
2026-05-20 19:46:52 +08:00
|
|
|
|
MessageBox.Show("请输入清洁人员姓名", "提示");
|
2026-05-15 21:10:42 +08:00
|
|
|
|
return;
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
2026-05-15 21:10:42 +08:00
|
|
|
|
IsCleanConfirmed = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
[RelayCommand] private void ConfirmPlatinumCompatible() => PlatinumCompatible = true;
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task CalibrateAmbientAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
await EnsureConnected();
|
|
|
|
|
|
float temp = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Temperature);
|
2026-04-18 19:00:34 +08:00
|
|
|
|
AmbientTemperature = temp;
|
|
|
|
|
|
AmbientCalibrated = true;
|
|
|
|
|
|
StatusMessage = $"环境温度校准完成:{AmbientTemperature:F1} °C";
|
|
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
[RelayCommand] private async Task PerformSystemCalibrationAsync() { /* 系统校准逻辑待实现 */ }
|
2026-05-20 13:49:45 +08:00
|
|
|
|
private async Task EnsureConnected()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!await _plcService.IsConnectedAsync())
|
|
|
|
|
|
await _plcService.ConnectAsync();
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|
2026-05-20 19:46:52 +08:00
|
|
|
|
private async Task UpdateRealTimeParametersAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!await _plcService.IsConnectedAsync()) return;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure);
|
|
|
|
|
|
ChamberPressure = rawPressure / 10.0;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
|
|
|
|
|
}
|
2026-04-18 19:00:34 +08:00
|
|
|
|
}
|