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