From a8886911515de13a8d654f17001163494998ddea Mon Sep 17 00:00:00 2001
From: xyy <544939200@qq.com>
Date: Fri, 29 May 2026 15:32:13 +0800
Subject: [PATCH]
---
ASTM D7896-19瞬态热线法.csproj | 4 +
Models/AppConfig.cs | 5 +
ViewModels/D7896ViewModel - 副本.cs | 747 ++++++++++++++++++++++++++++
ViewModels/D7896ViewModel.cs | 237 +++++----
appsettings.json | 6 +-
5 files changed, 897 insertions(+), 102 deletions(-)
create mode 100644 ViewModels/D7896ViewModel - 副本.cs
diff --git a/ASTM D7896-19瞬态热线法.csproj b/ASTM D7896-19瞬态热线法.csproj
index a9a0674..8b5e8a8 100644
--- a/ASTM D7896-19瞬态热线法.csproj
+++ b/ASTM D7896-19瞬态热线法.csproj
@@ -9,6 +9,10 @@
true
+
+
+
+
diff --git a/Models/AppConfig.cs b/Models/AppConfig.cs
index 6b6e35d..ca8c6f4 100644
--- a/Models/AppConfig.cs
+++ b/Models/AppConfig.cs
@@ -59,6 +59,8 @@ public class TestParameters
public double ReferenceConductivity { get; set; } = 0.606;
public CalibrationCoefficients CalibrationCoefficients { get; set; } = new();
+
+
}
public class AppSettings
@@ -74,4 +76,7 @@ public class CalibrationCoefficients
public ushort PressureProtection { get; set; }
public ushort TemperatureCoefficient { get; set; }
public ushort ResistanceCoefficient { get; set; }
+
+ public double ThermalConductivityCorrection { get; set; } = 0.606;
+ public double ThermalDiffusivityCorrection { get; set; } = 19.9;
}
\ No newline at end of file
diff --git a/ViewModels/D7896ViewModel - 副本.cs b/ViewModels/D7896ViewModel - 副本.cs
new file mode 100644
index 0000000..b19953f
--- /dev/null
+++ b/ViewModels/D7896ViewModel - 副本.cs
@@ -0,0 +1,747 @@
+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 System;
+using System.Collections.ObjectModel;
+using System.Linq;
+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.00385; // /°C
+
+ // 加热功率 Q 计算相关
+ private double _heatingCurrent; // 实际加热电流平均值
+ private double _wireResistanceAvg; // 铂丝平均电阻
+
+ // 温升曲线数据
+ [ObservableProperty] private string _curveTitle = "温升曲线";
+ [ObservableProperty] private PlotModel _temperatureCurveModel;
+
+ // UI 绑定属性 (与之前一致)
+ public ObservableCollection 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 _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;
+
+
+
+ [ObservableProperty] private double _sampleDensity = 1000.0; // 新增,密度默认值1000 kg/m³(水)
+ int samples = 400; // 1秒 * 1000点/秒
+ double heatingDuration = 0.8; // 加热时间 0.8 秒(需与您的加热脉冲宽度一致)
+ double totalDuration = 1.6; // 总采样时间(加热 + 冷却)
+ 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 GetInitialResistanceAsync()
+ //{
+ // if (!await _plcService.IsConnectedAsync()) return 0;
+ // try
+ // {
+ // float rawResistance = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Resistance);
+ // return rawResistance;
+ // }
+ // catch { return 0; }
+ //}
+
+ [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(); // 丢弃结果
+
+ for (int i = 1; i <= _config.TestParameters.MeasurementCount; i++)
+ {
+ if (_stopRequested) break;
+
+ CurrentMeasurementIndex = i;
+ StatusMessage = $"正在执行第 {i} 次测量...";
+
+
+ // === 新增:在加热前,单独测量冷态初始电阻 R0 ===
+ StatusMessage = $"第 {i} 次测量:正在获取冷态电阻...";
+ await _th1963Ustd.PrepareBatchAsync(20);
+ await _th1953Ustd.PrepareBatchAsync(20);
+ await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
+ await Task.Delay(250); // 等待采集完成
+ double[] ustd_r0 = await _th1963Ustd.FetchBatchAsync();
+ double[] upt_r0 = await _th1953Ustd.FetchBatchAsync();
+
+ double sumR0 = 0;
+ int validR0Count = 0;
+ for (int j = 2; j < ustd_r0.Length; j++) // 跳过前2个不稳定点
+ {
+ if (ustd_r0[j] > 0.01)
+ {
+ sumR0 += upt_r0[j] / ustd_r0[j]; // R = Upt / I = Upt / (Ustd / 1Ω)
+ validR0Count++;
+ }
+ }
+ double dynamicR0 = validR0Count > 0 ? sumR0 / validR0Count : 2.34; // 给个默认值防错
+ Logger.Log($"冷态测量 R0 = {dynamicR0:F6} Ω");
+
+ // === 正式加热与采集 ===
+ await _th1963Ustd.PrepareBatchAsync(samples);
+ await _th1953Ustd.PrepareBatchAsync(samples);
+
+ // 启动加热脉冲 (PLC)
+ await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, true);
+
+ try { await Task.Delay(5, _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();
+
+ 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($"测量 {i}: 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;
+ }
+
+ // 计算本次测量的 λ 和 α (传入刚才测得的冷态 dynamicR0)
+ var (lambda, alpha, deltaT, coolingPoints) = ComputeThermalProperties(upt, ustd, timeArray, dynamicR0, CurrentTestTemperature);
+ Logger.Log($"测量 {i} 结果: λ={lambda:F6} W/(m·K), α={alpha:E6} m²/s");
+
+ GenerateTemperatureCurveFromData(timeArray, deltaT, coolingPoints);
+
+ var result = new MeasurementResult
+ {
+ Index = i,
+ ThermalConductivity = lambda,
+ ThermalDiffusivity = alpha
+ };
+ result.CalculateVhcAndCp(SampleDensity);
+ Application.Current.Dispatcher.Invoke(() => Measurements.Add(result));
+ StatusMessage = $"第 {i} 次测量完成,λ={lambda:F4} W/m·K";
+
+ 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)");
+ Logger.Log($"初始电阻 R0: {dynamicR0:F6} Ω");
+ Logger.Log("===========================================");
+
+ if (i < _config.TestParameters.MeasurementCount && !_stopRequested)
+ {
+ try { await Task.Delay(_config.TestParameters.IntervalSeconds * 1000, _testCts.Token); } catch (OperationCanceledException) { break; }
+ }
+ }
+
+
+ CalculateAverages();
+ StatusMessage = _stopRequested ? "测试已停止。" : "测试完成。";
+ }
+ 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 coolingPoints) ComputeThermalProperties(
+ double[] upt, double[] ustd, double[] time, double initialResistance, double bathTemp)
+ {
+ int n = Math.Min(upt.Length, ustd.Length);
+
+ // 【核心优化:滑动平均滤波,抹平万用表的高频噪声】
+ // 窗口大小设为 15(如果是400Hz采样,相当于约37毫秒的平滑窗口)
+ int windowSize = 15;
+ double[] smoothedUpt = 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;
+ for (int j = start; j <= end; j++) sum += upt[j];
+ smoothedUpt[i] = sum / (end - start + 1);
+ }
+
+ // 计算恒定电流(取 0.1s~0.7s 的 U_std 平均值)
+ int avgStart = FindIndex(time, 0.1);
+ int avgEnd = FindIndex(time, 0.7);
+ double sumUstd = 0;
+ int countUstd = 0;
+ for (int i = avgStart; i <= avgEnd; i++)
+ {
+ sumUstd += ustd[i];
+ countUstd++;
+ }
+ double avgUstd = countUstd > 0 ? sumUstd / countUstd : ustd.Average();
+ double constantCurrent = avgUstd / StandardResistor;
+
+ double[] ptResistance = new double[n];
+ double[] deltaT = new double[n];
+
+ for (int i = 0; i < n; i++)
+ {
+ // 使用滤波后的 smoothedUpt 计算电阻,彻底消除毛刺!
+ ptResistance[i] = smoothedUpt[i] / constantCurrent;
+ deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance);
+ }
+
+ // 时间零点补偿 (t0_shift)
+ double t0_shift = 0.030;
+
+ // 选取 0.15s ~ 0.70s 进行拟合
+ double tStart = 0.15;
+ double tEndHeating = 0.70;
+ int startIdx = FindIndex(time, tStart);
+ int endIdxHeating = FindIndex(time, tEndHeating);
+
+ var points = new List();
+ for (int i = startIdx; i <= endIdxHeating; i++)
+ {
+ double realTime = time[i] - t0_shift;
+ if (realTime > 0.001)
+ {
+ points.Add(new DataPoint(Math.Log(realTime), deltaT[i]));
+ }
+ }
+
+ (double slope, double intercept) = LinearRegression(points);
+
+ // 保护:如果滤波后斜率依然小于 0.01,说明数据彻底废了,给个合理兜底值
+ if (slope <= 0.01)
+ {
+ Logger.Log("警告: 滤波后斜率依然异常,启用兜底值 0.05!");
+ slope = 0.05;
+ }
+
+ // 计算热导率 λ
+ double avgResistance = ptResistance.Skip(startIdx).Take(endIdxHeating - startIdx + 1).Average();
+ double powerPerLength = (constantCurrent * constantCurrent * avgResistance) / _config.TestParameters.PlatinumWireLength;
+ double lambda = powerPerLength / (4 * Math.PI * slope);
+
+ // 计算热扩散率 α
+ double eulerGamma = 0.5772156649;
+ double wireRadius = 0.00003; // 30 微米 (0.03mm)
+
+ double alpha = (wireRadius * wireRadius / 4.0) * Math.Exp(eulerGamma) * Math.Exp(intercept / slope);
+
+ if (alpha <= 0 || double.IsNaN(alpha) || double.IsInfinity(alpha) || alpha > 1e-5)
+ alpha = 1.4e-7;
+
+ // 提取冷却曲线数据点
+ var coolingPoints = new List();
+ int coolingStartIdx = FindIndex(time, heatingDuration);
+ int coolingEndIdx = FindIndex(time, totalDuration);
+ for (int i = coolingStartIdx; i <= coolingEndIdx; i++)
+ {
+ if (deltaT[i] > 0.001) coolingPoints.Add(new DataPoint(time[i], deltaT[i]));
+ }
+
+ Logger.Log($"[调试] 滤波后拟合参数: Slope={slope:F4}, Intercept={intercept:F4}");
+
+ return (lambda, alpha, deltaT, coolingPoints);
+ }
+
+
+
+ ///
+ /// 最小二乘法线性回归,返回 (斜率, 截距)
+ ///
+ private (double slope, double intercept) LinearRegression(List 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);
+ }
+
+ ///
+ /// 查找时间数组中与目标时间最接近的索引
+ ///
+ 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;
+ }
+
+ /////
+ ///// 最小二乘法拟合斜率 (X轴为横坐标,Y轴为纵坐标) — 用于加热段 ln(t) vs ΔT
+ /////
+ //private double LeastSquaresSlope(List 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;
+ //}
+
+ /////
+ ///// 最小二乘法拟合斜率 (X轴为时间t,Y轴为 ln(ΔT)) — 用于冷却段
+ /////
+ //private double LeastSquaresSlopeOnTime(List 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 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 = "温升 (℃)" });
+ }
+
+ // 加热段曲线(红色)
+ var heatingSeries = new LineSeries
+ {
+ Title = $"第{CurrentMeasurementIndex}次测量 - 加热段",
+ Color = OxyColors.Red,
+ 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 = OxyColors.Blue,
+ 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;
+ }
+ try
+ {
+ var extraParams = new Dictionary
+ {
+ ["SampleVolume"] = SampleVolume,
+ ["BubbleRemoved"] = BubbleRemoved,
+ ["UsePressure"] = UsePressure,
+ ["PressureValue"] = PressureValue,
+ ["IsCleanConfirmed"] = IsCleanConfirmed,
+ ["CleanerName"] = CleanerName,
+ ["AmbientTemperature"] = AmbientTemperature,
+ ["AmbientCalibrated"] = AmbientCalibrated,
+ ["PlatinumCompatible"] = PlatinumCompatible,
+ ["LiquidReactivityNote"] = LiquidReactivityNote,
+ ["InitialResistance"] = PlatinumResistance
+ };
+ string reportPath = await _reportService.GenerateReportAsync(SampleId, TestTemperature, Measurements.ToList(),
+ AverageThermalConductivity, AverageThermalDiffusivity, AverageVolumetricHeatCapacity,
+ _config.TestParameters, extraParams);
+ MessageBox.Show($"报告已生成: {reportPath}", "成功");
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"生成报告失败: {ex.Message}", "错误");
+ }
+ }
+
+
+
+ [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 { }
+ }
+}
\ No newline at end of file
diff --git a/ViewModels/D7896ViewModel.cs b/ViewModels/D7896ViewModel.cs
index b19953f..7e8793e 100644
--- a/ViewModels/D7896ViewModel.cs
+++ b/ViewModels/D7896ViewModel.cs
@@ -86,11 +86,12 @@ public partial class D7896ViewModel : ObservableObject
[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 = 400; // 1秒 * 1000点/秒
- double heatingDuration = 0.8; // 加热时间 0.8 秒(需与您的加热脉冲宽度一致)
- double totalDuration = 1.6; // 总采样时间(加热 + 冷却)
+ int samples = 1000; // 1秒 * 1000点/秒
+ double heatingDuration = 1; // 加热时间 0.8 秒(需与您的加热脉冲宽度一致)
+ double totalDuration = 2; // 总采样时间(加热 + 冷却)
public D7896ViewModel()
{
_config = App.PlcConfig ?? new AppConfig();
@@ -272,29 +273,6 @@ public partial class D7896ViewModel : ObservableObject
CurrentMeasurementIndex = i;
StatusMessage = $"正在执行第 {i} 次测量...";
-
- // === 新增:在加热前,单独测量冷态初始电阻 R0 ===
- StatusMessage = $"第 {i} 次测量:正在获取冷态电阻...";
- await _th1963Ustd.PrepareBatchAsync(20);
- await _th1953Ustd.PrepareBatchAsync(20);
- await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
- await Task.Delay(250); // 等待采集完成
- double[] ustd_r0 = await _th1963Ustd.FetchBatchAsync();
- double[] upt_r0 = await _th1953Ustd.FetchBatchAsync();
-
- double sumR0 = 0;
- int validR0Count = 0;
- for (int j = 2; j < ustd_r0.Length; j++) // 跳过前2个不稳定点
- {
- if (ustd_r0[j] > 0.01)
- {
- sumR0 += upt_r0[j] / ustd_r0[j]; // R = Upt / I = Upt / (Ustd / 1Ω)
- validR0Count++;
- }
- }
- double dynamicR0 = validR0Count > 0 ? sumR0 / validR0Count : 2.34; // 给个默认值防错
- Logger.Log($"冷态测量 R0 = {dynamicR0:F6} Ω");
-
// === 正式加热与采集 ===
await _th1963Ustd.PrepareBatchAsync(samples);
await _th1953Ustd.PrepareBatchAsync(samples);
@@ -321,6 +299,24 @@ public partial class D7896ViewModel : ObservableObject
double[] ustd = await _th1963Ustd.FetchBatchAsync();
double[] upt = await _th1953Ustd.FetchBatchAsync();
+
+ // 动态计算初始电阻 R0(取第2~11点,跳过初始扰动)
+ double sumR0 = 0;
+ int r0Cnt = 0;
+ for (int j = 2; j < Math.Min(12, ustd.Length); j++)
+ {
+ if (ustd[j] > 0.01 && upt[j] > 0.01)
+ {
+ sumR0 += upt[j] / ustd[j];
+ r0Cnt++;
+ }
+ }
+ double dynamicR0 = r0Cnt > 0 ? sumR0 / r0Cnt : 2.34;
+ Logger.Log($"动态计算 R0 = {dynamicR0:F6} Ω (有效点数: {r0Cnt})");
+
+
+
+
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");
@@ -332,12 +328,18 @@ public partial class D7896ViewModel : ObservableObject
double[] timeArray = new double[ustd.Length];
for (int idx = 0; idx < timeArray.Length; idx++)
- {
timeArray[idx] = idx * totalDuration / samples;
- }
// 计算本次测量的 λ 和 α (传入刚才测得的冷态 dynamicR0)
var (lambda, alpha, deltaT, coolingPoints) = ComputeThermalProperties(upt, ustd, timeArray, dynamicR0, CurrentTestTemperature);
+
+
+ //var lambdaCorr = _config.TestParameters.CalibrationCoefficients.ThermalConductivityCorrection;
+ //var alphaCorr = _config.TestParameters.CalibrationCoefficients.ThermalDiffusivityCorrection;
+
+ //lambda *= lambdaCorr;
+ //alpha *= alphaCorr;
+
Logger.Log($"测量 {i} 结果: λ={lambda:F6} W/(m·K), α={alpha:E6} m²/s");
GenerateTemperatureCurveFromData(timeArray, deltaT, coolingPoints);
@@ -401,95 +403,128 @@ public partial class D7896ViewModel : ObservableObject
double[] upt, double[] ustd, double[] time, double initialResistance, double bathTemp)
{
int n = Math.Min(upt.Length, ustd.Length);
-
- // 【核心优化:滑动平均滤波,抹平万用表的高频噪声】
- // 窗口大小设为 15(如果是400Hz采样,相当于约37毫秒的平滑窗口)
- int windowSize = 15;
- double[] smoothedUpt = 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;
- for (int j = start; j <= end; j++) sum += upt[j];
- smoothedUpt[i] = sum / (end - start + 1);
- }
-
- // 计算恒定电流(取 0.1s~0.7s 的 U_std 平均值)
- int avgStart = FindIndex(time, 0.1);
- int avgEnd = FindIndex(time, 0.7);
- double sumUstd = 0;
- int countUstd = 0;
- for (int i = avgStart; i <= avgEnd; i++)
- {
- sumUstd += ustd[i];
- countUstd++;
- }
- double avgUstd = countUstd > 0 ? sumUstd / countUstd : ustd.Average();
- double constantCurrent = avgUstd / StandardResistor;
-
- double[] ptResistance = new double[n];
double[] deltaT = new double[n];
+ double[] ptResistance = new double[n];
+ double[] current = new double[n];
+ // 1. 瞬时计算
for (int i = 0; i < n; i++)
{
- // 使用滤波后的 smoothedUpt 计算电阻,彻底消除毛刺!
- ptResistance[i] = smoothedUpt[i] / constantCurrent;
- deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance);
- }
-
- // 时间零点补偿 (t0_shift)
- double t0_shift = 0.030;
-
- // 选取 0.15s ~ 0.70s 进行拟合
- double tStart = 0.15;
- double tEndHeating = 0.70;
- int startIdx = FindIndex(time, tStart);
- int endIdxHeating = FindIndex(time, tEndHeating);
-
- var points = new List();
- for (int i = startIdx; i <= endIdxHeating; i++)
- {
- double realTime = time[i] - t0_shift;
- if (realTime > 0.001)
+ current[i] = ustd[i] / StandardResistor;
+ if (current[i] > 0.001) // 降低阈值到 1mA
{
- points.Add(new DataPoint(Math.Log(realTime), deltaT[i]));
+ ptResistance[i] = upt[i] / current[i];
+ deltaT[i] = (ptResistance[i] - initialResistance) / (AlphaPt * initialResistance);
+ }
+ else
+ {
+ ptResistance[i] = double.NaN;
+ deltaT[i] = double.NaN;
}
}
+ // 1.5 滑动平均平滑(窗口5)
+ double[] smoothDeltaT = new double[n];
+ for (int i = 0; i < n; i++)
+ {
+ int start = Math.Max(0, i - 2);
+ int end = Math.Min(n - 1, i + 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;
+ }
+
+ // 2. 寻找温升峰值点(使用平滑后的数据)
+ int peakIdx = 5;
+ double maxDeltaT = 0;
+ for (int i = 5; i < n; i++)
+ {
+ if (!double.IsNaN(smoothDeltaT[i]) && smoothDeltaT[i] > maxDeltaT)
+ {
+ maxDeltaT = smoothDeltaT[i];
+ peakIdx = i;
+ }
+ }
+ Logger.Log($"最大温升 = {maxDeltaT:F4} ℃");
+
+ // 3. 固定时间窗口(0.1 ~ 0.8 秒),避开初始扰动和冷却段
+ double tStart = 0.15;
+ double tEnd = 0.4;
+ 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}");
+
+
+
+
+ // 4. 收集拟合点
+ var points = new List();
+ for (int i = startIdx; i <= endIdx; i++)
+ {
+ 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());
+ }
+
(double slope, double intercept) = LinearRegression(points);
-
- // 保护:如果滤波后斜率依然小于 0.01,说明数据彻底废了,给个合理兜底值
- if (slope <= 0.01)
+ if (slope <= 0.001)
{
- Logger.Log("警告: 滤波后斜率依然异常,启用兜底值 0.05!");
- slope = 0.05;
+ Logger.Log($"警告:拟合斜率 {slope:E} 过小或为负,测量无效");
+ return (0, 0, deltaT, new List());
}
- // 计算热导率 λ
- double avgResistance = ptResistance.Skip(startIdx).Take(endIdxHeating - startIdx + 1).Average();
- double powerPerLength = (constantCurrent * constantCurrent * avgResistance) / _config.TestParameters.PlatinumWireLength;
+ foreach (var p in points.Take(10))
+ Logger.Log($"ln(t)={p.X:F4}, ΔT={p.Y:F4}");
+
+
+
+
+ // 5. 计算功率
+ double sumPower = 0;
+ int validCount = 0;
+ for (int i = startIdx; i <= endIdx; i++)
+ {
+ if (!double.IsNaN(ptResistance[i]))
+ {
+ sumPower += current[i] * current[i] * ptResistance[i];
+ validCount++;
+ }
+ }
+ if (validCount == 0) return (0, 0, deltaT, new List());
+ double avgPower = sumPower / validCount;
+ double wireLength = _config.TestParameters.PlatinumWireLength;
+ double powerPerLength = avgPower / wireLength;
+
double lambda = powerPerLength / (4 * Math.PI * slope);
+ Logger.Log($"功率密度 = {powerPerLength:F3} W/m, 斜率 B = {slope:F5}");
- // 计算热扩散率 α
- double eulerGamma = 0.5772156649;
- double wireRadius = 0.00003; // 30 微米 (0.03mm)
-
- double alpha = (wireRadius * wireRadius / 4.0) * Math.Exp(eulerGamma) * Math.Exp(intercept / slope);
-
- if (alpha <= 0 || double.IsNaN(alpha) || double.IsInfinity(alpha) || alpha > 1e-5)
- alpha = 1.4e-7;
-
- // 提取冷却曲线数据点
- var coolingPoints = new List();
- int coolingStartIdx = FindIndex(time, heatingDuration);
- int coolingEndIdx = FindIndex(time, totalDuration);
- for (int i = coolingStartIdx; i <= coolingEndIdx; i++)
+ // 6. α 计算
+ double exponent = intercept / slope + EulerGamma;
+ if (exponent > 30) exponent = 30;
+ double alpha = (WireRadius * WireRadius / 4.0) * Math.Exp(exponent);
+ if (alpha <= 0 || double.IsNaN(alpha) || alpha > 1e-5)
{
- if (deltaT[i] > 0.001) coolingPoints.Add(new DataPoint(time[i], deltaT[i]));
+ Logger.Log($"警告:α 计算异常 ({alpha:E}),数据可能不可靠");
+ alpha = double.NaN;
}
+ Logger.Log($"热导率 λ = {lambda:F6} W/(m·K) | 热扩散率 α = {alpha:E6} m²/s | 截距/斜率 = {intercept / slope:F3}");
- Logger.Log($"[调试] 滤波后拟合参数: Slope={slope:F4}, Intercept={intercept:F4}");
+ // 冷却曲线
+ var coolingPoints = new List();
+ 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]));
return (lambda, alpha, deltaT, coolingPoints);
}
diff --git a/appsettings.json b/appsettings.json
index 8f0f064..3151042 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -25,7 +25,7 @@
"TestParameters": {
"MeasurementCount": 10,
"IntervalSeconds": 30,
- "PlatinumWireLength": 0.056, //铂丝长度(单位:米)
+ "PlatinumWireLength": 0.07, //铂丝长度(单位:米)
"PlatinumWireDiameter": 0.00006,
"ReportOutputPath": "Reports\\",
"DefaultSampleVolume": 40.0,
@@ -44,5 +44,9 @@
"WindowWidth": 1024,
"WindowHeight": 768,
"ThemeColor": "Blue"
+ },
+ "CalibrationCoefficients": {
+ "ThermalConductivityCorrection": 0.606,
+ "ThermalDiffusivityCorrection": 19.9
}
}
\ No newline at end of file