Compare commits

...

33 Commits

Author SHA1 Message Date
xyy
afd044ce09 2026-06-17 14:31:52 +08:00
xyy
9c7698e9cc 2026-06-15 17:23:29 +08:00
xyy
75d9405770 2026-06-12 16:30:29 +08:00
xyy
db8d255fdd 2026-06-10 16:38:04 +08:00
xyy
87ca4b0a64 2026-06-09 14:54:14 +08:00
xyy
bc893c4d53 2026-06-08 18:59:05 +08:00
xyy
00d17dc0eb 2026-06-08 18:56:55 +08:00
xyy
785583a6c8 2026-06-08 18:54:07 +08:00
xyy
2ea0f95fec 2026-06-08 17:58:49 +08:00
xyy
a2d86b667e 2026-06-08 17:58:19 +08:00
xyy
fbab7e3c17 2026-06-08 17:50:08 +08:00
xyy
78f1cf038d 2026-06-08 17:40:43 +08:00
xyy
8681cc0163 2026-06-08 17:33:46 +08:00
xyy
ae5803431b Revert ""
This reverts commit 76c911084e.
2026-06-08 17:26:25 +08:00
xyy
76c911084e 2026-06-08 15:48:13 +08:00
xyy
08042e243f 2026-06-08 13:47:06 +08:00
xyy
d5f7d309a0 2026-06-08 09:41:28 +08:00
xyy
569ad194fb 2026-06-06 19:01:35 +08:00
xyy
8e0a31129e 2026-06-06 15:55:23 +08:00
xyy
c70178ffc3 2026-06-06 15:27:52 +08:00
xyy
fb14440399 2026-06-05 19:53:52 +08:00
xyy
598e292608 2026-06-05 19:25:39 +08:00
xyy
7e8416dfb3 2026-06-05 19:20:34 +08:00
xyy
f9403e4d65 2026-06-05 10:56:26 +08:00
xyy
e22818f8bb 2026-06-04 17:57:09 +08:00
xyy
39037644f1 2026-06-04 16:56:22 +08:00
xyy
d236877078 2026-06-04 16:54:35 +08:00
xyy
d1dea94fd2 2026-06-04 15:56:06 +08:00
xyy
915fbdaa11 2026-06-04 14:28:07 +08:00
xyy
fed8c9b6dd 2026-06-04 10:26:57 +08:00
xyy
9a3cfff6d8 2026-06-03 20:38:16 +08:00
xyy
70da9f7c6c 2026-06-03 19:53:11 +08:00
xyy
1c073df913 2026-06-03 19:52:57 +08:00
13 changed files with 740 additions and 326 deletions

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows7.0</TargetFramework>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<RootNamespace>ASTM_D7896_19瞬态热线法</RootNamespace>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
@@ -16,12 +16,13 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="EPPlus" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.16" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
<PackageReference Include="NModbus4.NetCore" Version="4.0.0" />
<PackageReference Include="NModbus4.NetCore" Version="3.0.0" />
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
<PackageReference Include="PdfSharpCore" Version="1.3.67" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,7 +1,7 @@
<Application x:Class="ASTM_D7896_Tester.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:ASTM_D7896_Tester.Converters"
xmlns:converters="clr-namespace:ASTM_D7896_Tester.Converters" StartupUri="Window1.xaml"
>
<Application.Resources>
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>

View File

@@ -46,7 +46,7 @@ public class TestParameters
public int MeasurementCount { get; set; } = 10;
[Range(5, 300)]
public int IntervalSeconds { get; set; } = 30;
public double PlatinumWireLength { get; set; } = 0.056; // 默认 5.6 cm
public double PlatinumWireLength { get; set; } = 0.040; // 默认 5.6 cm
public double PlatinumWireDiameter { get; set; } = 0.00006;
public string ReportOutputPath { get; set; } = "Reports\\";
@@ -74,8 +74,6 @@ public class TestParameters
public double FitStartTime { get; set; } = 0.25; // 默认0.35秒,避开早期扰动
public double FitEndTime { get; set; } = 0.60;
}
public class AppSettings
@@ -91,8 +89,6 @@ public class CalibrationCoefficients
public ushort PressureProtection { get; set; }
public ushort TemperatureCoefficient { get; set; }
public ushort ResistanceCoefficient { get; set; }
//public double ThermalConductivityCorrection { get; set; } = 69;//乙二醇
public double ThermalConductivityCorrection { get; set; } = 100;//乙二醇
public double ThermalDiffusivityCorrection { get; set; } = 19.9;
public double ThermalConductivityCorrection { get; set; } = 17.305349f;//蒸馏水 比热率修正
public double ThermalDiffusivityCorrection { get; set; } = 0.158682538;//导热率修正
}

View File

@@ -94,7 +94,7 @@ namespace ASTM_D7896_Tester.Services
await SendCommandAsync($"VOLT:DC:RANG {DefaultVoltageRange}");
// 3. 设置积分时间 0.02PLC(最快速度)
await SendCommandAsync("VOLT:DC:NPLC 0.02");
await SendCommandAsync("VOLT:DC:NPLC 0.1");
// 4. 关闭自动归零(提高速度)
await SendCommandAsync("VOLT:DC:ZERO:AUTO OFF");

View File

@@ -6,6 +6,9 @@ using CommunityToolkit.Mvvm.Input;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using OxyPlot.Wpf;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using System;
using System.Collections.ObjectModel;
using System.IO;
@@ -38,7 +41,7 @@ public partial class D7896ViewModel : ObservableObject
private const double StandardResistor = 1.0;
// 铂丝电阻温度系数 (纯铂)
private const double AlphaPt = 0.00385; // /°C
private const double AlphaPt = 0.0040; // /°C
// 加热功率 Q 计算相关
private double _heatingCurrent; // 实际加热电流平均值
@@ -91,7 +94,7 @@ public partial class D7896ViewModel : ObservableObject
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 = 1000; // 1秒 * 1000点/秒
int samples = 200; // 1秒 * 1000点/秒
double heatingDuration = 1; // 加热时间 0.8 秒(需与您的加热脉冲宽度一致)
double totalDuration = 2; // 总采样时间(加热 + 冷却)
public D7896ViewModel()
@@ -155,7 +158,8 @@ public partial class D7896ViewModel : ObservableObject
// }
// catch { return 0; }
//}
// 在类成员变量区域添加
private int currentSettleMs = 200; // 电流稳定等待时间(毫秒)
[RelayCommand]
private async Task StartTestAsync()
{
@@ -279,7 +283,7 @@ public partial class D7896ViewModel : ObservableObject
int requiredCount = _config.TestParameters.MeasurementCount; // 需要多少有效数据
int validCount = 0;
int attemptCount = 0;
int maxAttempts = requiredCount * 2; // 最多尝试次数,防止死循环
int maxAttempts = requiredCount * 4; // 最多尝试次数,防止死循环
// 存储每次成功测量的结果(用于后续异常判断)
List<double> validLambdaList = new List<double>();
@@ -290,7 +294,7 @@ public partial class D7896ViewModel : ObservableObject
{
attemptCount++;
CurrentMeasurementIndex = attemptCount; // 显示当前尝试次数(不是有效次数)
StatusMessage = $"正在执行第 {attemptCount} 次测量(有效:{validCount}/{requiredCount}...";
//StatusMessage = $"正在执行第{validCount}次测量...";
// --- 步骤1基线采集加热前---
await _th1963Ustd.PrepareBatchAsync(50);
@@ -300,7 +304,7 @@ public partial class D7896ViewModel : ObservableObject
double[] ustdBase = await _th1963Ustd.FetchBatchAsync();
double[] uptBase = await _th1953Ustd.FetchBatchAsync();
double dynamicR0 = 2.45; // 默认值
double dynamicR0 = 1.49; // 默认值
if (ustdBase != null && ustdBase.Length > 0 && uptBase != null && uptBase.Length > 0)
{
double sumR0 = 0; int cnt = 0;
@@ -331,21 +335,25 @@ public partial class D7896ViewModel : ObservableObject
await _th1963Ustd.PrepareBatchAsync(samples);
await _th1953Ustd.PrepareBatchAsync(samples);
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, true);
try { await Task.Delay(5, _testCts.Token); } catch (OperationCanceledException) { break; }
// 等待电流稳定(不触发采集)
try { await Task.Delay(currentSettleMs, _testCts.Token); } catch (OperationCanceledException) { break; }
// 电流稳定后,触发采集
await Task.WhenAll(_th1963Ustd.TriggerAsync(), _th1953Ustd.TriggerAsync());
// 继续加热剩余时间(加热总时间 = 稳定等待时间 + 有效加热时间)
try { await Task.Delay((int)(heatingDuration * 1000), _testCts.Token); } catch (OperationCanceledException) { break; }
await _plcService.WriteCoilAsync(_config.PlcRegisterAddresses.StartCommand, false);
int remainingMs = (int)((totalDuration - heatingDuration) * 1000) + 100;
try { await Task.Delay(remainingMs, _testCts.Token); } catch (OperationCanceledException) { break; }
double[] ustd = await _th1963Ustd.FetchBatchAsync();
double[] upt = await _th1953Ustd.FetchBatchAsync();
if (dynamicR0 == 2.45) // 基线无效
if (dynamicR0 == 1.49) // 基线无效
{
double sumR0 = 0; int cnt = 0;
for (int j = 2; j < Math.Min(6, ustd.Length); j++)
@@ -383,11 +391,11 @@ public partial class D7896ViewModel : ObservableObject
double vhc = lambda / alpha; // kJ/(m³·K)
double cp = vhc / SampleDensity; // J/(kg·K)
Logger.Log($"测量 {attemptCount} 结果: λ={lambda:F6} W/(m·K), α={alpha:E6} m²/s, Cp={cp:F2} J/(kg·K)");
Logger.Log($"测量 {validCount} 结果: λ={lambda:F6} W/(m·K), α={alpha:E6} m²/s, Cp={cp:F2} J/(kg·K)");
// ---- 异常值检测 ----
bool isOutlier = false;
double deviationThreshold = 0.20; // 20% 偏差阈值
double deviationThreshold = 0.1; // 10% 偏差阈值
if (validCount >= 2) // 至少有两个有效数据后才开始剔除
{
@@ -436,7 +444,7 @@ public partial class D7896ViewModel : ObservableObject
// 测量间隔(即使舍弃也等待,让样品恢复)
if (validCount < requiredCount && !_stopRequested && attemptCount < maxAttempts)
{
try { await Task.Delay(_config.TestParameters.IntervalSeconds * 500, _testCts.Token); } catch (OperationCanceledException) { break; }
try { await Task.Delay(_config.TestParameters.IntervalSeconds * 150, _testCts.Token); } catch (OperationCanceledException) { break; }
}
}
@@ -503,12 +511,13 @@ public partial class D7896ViewModel : ObservableObject
}
}
// 滑动平均平滑(窗口5
// 滑动平均平滑(窗口大小改为 11
int windowSize = 40; // 原为 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);
int start = Math.Max(0, i - windowSize / 2);
int end = Math.Min(n - 1, i + windowSize / 2);
double sum = 0; int cnt = 0;
for (int j = start; j <= end; j++)
if (!double.IsNaN(deltaT[j])) { sum += deltaT[j]; cnt++; }
@@ -579,6 +588,7 @@ public partial class D7896ViewModel : ObservableObject
lambda = _config.TestParameters.FixedLambda;
Logger.Log($"使用固定 lambda={lambda:F6} W/(m·K)");
}
lambda *= _config.TestParameters.CalibrationCoefficients.ThermalDiffusivityCorrection;
Logger.Log($"constantCurrent(avg)={avgCurrent:E6} A, avgResistance={avgResistance:F6} Ω, powerPerLength={powerPerLength:E6} W/m, 斜率 B = {slope:F5}");
// 计算热扩散率
@@ -608,22 +618,22 @@ public partial class D7896ViewModel : ObservableObject
coolingPoints.Add(new DataPoint(time[i], deltaT[i]));
// 导出CSV
try
{
string tmp = Path.GetTempPath();
string baseName = $"measure_{SampleId}_{DateTime.Now:yyyyMMdd_HHmmss}_{CurrentMeasurementIndex}";
string dataPath = Path.Combine(tmp, baseName + ".csv");
ExportMeasurementCsv(dataPath, time, ustd, upt, deltaT, startIdx, endIdx);
Logger.Log($"已导出测量数据 CSV: {dataPath}");
//try
//{
// string tmp = Path.GetTempPath();
// string baseName = $"measure_{SampleId}_{DateTime.Now:yyyyMMdd_HHmmss}_{CurrentMeasurementIndex}";
// string dataPath = Path.Combine(tmp, baseName + ".csv");
// ExportMeasurementCsv(dataPath, time, ustd, upt, deltaT, startIdx, endIdx);
// Logger.Log($"已导出测量数据 CSV: {dataPath}");
string winPath = Path.Combine(tmp, baseName + "_windows.csv");
ExportCandidateWindowsCsv(winPath, time, deltaT, startIdx, endIdx);
Logger.Log($"已导出候选拟合窗 CSV: {winPath}");
}
catch (Exception ex)
{
Logger.Log($"导出CSV失败: {ex.Message}");
}
// string winPath = Path.Combine(tmp, baseName + "_windows.csv");
// ExportCandidateWindowsCsv(winPath, time, deltaT, startIdx, endIdx);
// Logger.Log($"已导出候选拟合窗 CSV: {winPath}");
//}
//catch (Exception ex)
//{
// Logger.Log($"导出CSV失败: {ex.Message}");
//}
return (lambda, alpha, deltaT, coolingPoints);
}
@@ -663,34 +673,6 @@ public partial class D7896ViewModel : ObservableObject
return timeArray.Length - 1;
}
private void ExportMeasurementCsv(string path, double[] time, double[] ustd, double[] upt, double[] deltaT, int fitStart, int fitEnd)
{
using var sw = new StreamWriter(path, false, Encoding.UTF8);
sw.WriteLine("index,time,U_std,U_pt,deltaT,fitWindow");
int n = Math.Min(Math.Min(time.Length, ustd.Length), deltaT.Length);
for (int i = 0; i < n; i++)
{
bool inFit = (i >= fitStart && i <= fitEnd);
sw.WriteLine($"{i},{time[i]:F6},{ustd[i]:F6},{upt[i]:F6},{(double.IsNaN(deltaT[i]) ? 0 : deltaT[i]):F6},{(inFit ? 1 : 0)}");
}
}
private void ExportCandidateWindowsCsv(string path, double[] time, double[] deltaT, int startIdx, int endIdx)
{
using var sw = new StreamWriter(path, false, Encoding.UTF8);
sw.WriteLine("s,e,t_start,t_end,meanDelta");
int n = time.Length;
int minPts = 10;
for (int s = startIdx; s <= endIdx - minPts; s++)
{
for (int e = s + minPts - 1; e <= endIdx; e++)
{
double mean = deltaT.Skip(s).Take(e - s + 1).Where(x => !double.IsNaN(x)).DefaultIfEmpty(0).Average();
sw.WriteLine($"{s},{e},{time[s]:F6},{time[e]:F6},{mean:F6}");
}
}
}
///// <summary>
///// 最小二乘法拟合斜率 (X轴为横坐标Y轴为纵坐标) — 用于加热段 ln(t) vs ΔT
///// </summary>
@@ -737,7 +719,7 @@ public partial class D7896ViewModel : ObservableObject
{
if (TemperatureCurveModel == null)
{
TemperatureCurveModel = new PlotModel { Title = "温升与冷却曲线", Background = OxyColors.White };
TemperatureCurveModel = new PlotModel { };
TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "时间 (s)" });
TemperatureCurveModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "温升 (℃)" });
}
@@ -782,7 +764,7 @@ public partial class D7896ViewModel : ObservableObject
}
TemperatureCurveModel.InvalidatePlot(true);
CurveTitle = $"已完成 {CurrentMeasurementIndex} 次测量";
//CurveTitle = $"已完成 {CurrentMeasurementIndex} 次测量";
}
@@ -802,7 +784,18 @@ public partial class D7896ViewModel : ObservableObject
CurrentMeasurementIndex = 0;
StatusMessage = "已重置";
TestDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
TemperatureCurveModel = null;
// 清空曲线:新建一个空白的 PlotModel 替换原来的
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 = "温升 (℃)" });
TemperatureCurveModel.InvalidatePlot(true);
CurveTitle = "温升曲线";
}
[RelayCommand]
@@ -813,35 +806,182 @@ public partial class D7896ViewModel : ObservableObject
MessageBox.Show("没有测试数据", "提示");
return;
}
// 选择保存路径
var saveFileDialog = new Microsoft.Win32.SaveFileDialog
{
Filter = "PDF files (*.pdf)|*.pdf",
DefaultExt = ".pdf",
FileName = $"报告_{SampleId}_{DateTime.Now:yyyyMMdd_HHmmss}.pdf"
};
if (saveFileDialog.ShowDialog() != true)
return;
string pdfPath = saveFileDialog.FileName;
try
{
var extraParams = new Dictionary<string, object>
// 先在 UI 线程导出曲线图为字节数组
byte[] chartImageBytes = null;
await Application.Current.Dispatcher.InvokeAsync(() =>
{
["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}", "成功");
if (TemperatureCurveModel != null && TemperatureCurveModel.Series.Count > 0)
{
using (var stream = new MemoryStream())
{
var exporter = new PngExporter { Width = 600, Height = 400 };
exporter.Export(TemperatureCurveModel, stream);
chartImageBytes = stream.ToArray();
}
}
});
// 然后在后台线程生成 PDF避免阻塞 UI
await Task.Run(() => GeneratePdfReport(pdfPath, chartImageBytes));
MessageBox.Show($"报表已生成: {pdfPath}", "成功");
}
catch (Exception ex)
{
MessageBox.Show($"生成报失败: {ex.Message}", "错误");
MessageBox.Show($"生成报失败: {ex.Message}", "错误");
}
}
private void GeneratePdfReport(string filePath, byte[] chartImageBytes)
{
using (var document = new PdfDocument())
{
// 创建第一个页面
var page = document.AddPage();
page.Width = XUnit.FromMillimeter(210);
page.Height = XUnit.FromMillimeter(297);
var gfx = XGraphics.FromPdfPage(page);
var titleFont = new XFont("SimHei", 16, XFontStyle.Bold);
var headerFont = new XFont("SimHei", 12, XFontStyle.Bold);
var normalFont = new XFont("SimHei", 10, XFontStyle.Regular);
double yPosition = 30;
// 标题
gfx.DrawString("ASTM D7896-19 瞬态热线法测试报告", titleFont, XBrushes.Black,
new XRect(0, yPosition, page.Width, 30), XStringFormats.TopCenter);
yPosition += 40;
// 基础信息(只显示一次)
gfx.DrawString($"样品名称: {SampleId}", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"测试温度: {TestTemperature:F1} °C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"测试时间: {TestDateTime}", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"样品体积: {SampleVolume:F1} mL", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"样品密度: {SampleDensity:F0} kg/m^3", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"环境温度: {AmbientTemperature:F1} °C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"铂丝电阻温度系数: {AlphaPt:F4} /°C", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"铂丝长度: {_config.TestParameters.PlatinumWireLength:F3} m", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 22;
gfx.DrawString($"铂丝直径: {_config.TestParameters.PlatinumWireDiameter * 1000:F2} mm", normalFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 35;
// 曲线图(如果有)
if (chartImageBytes != null)
{
using (var imgStream = new MemoryStream(chartImageBytes))
{
var image = XImage.FromStream(() => new MemoryStream(imgStream.ToArray()));
gfx.DrawImage(image, 40, yPosition, 500, 330);
yPosition += 350;
}
}
// 表格标题
gfx.DrawString("测量结果明细", headerFont, XBrushes.Black, new XPoint(40, yPosition)); yPosition += 25;
// 表头(已替换特殊符号)
string[] headers = { "序号", "热导率λ(W/(m.K))", "热扩散率α(10^-7 m^2/s)", "体积热容VHC(kJ/(m^3.K))", "比热容Cp(J/(kg.K))" };
double[] colWidths = { 50, 115, 125, 125, 125 };
double startX = 40;
// 辅助函数:绘制表头(用于新页)
void DrawHeader(XGraphics g, double y)
{
for (int i = 0; i < headers.Length; i++)
{
double cellX = startX + colWidths.Take(i).Sum();
g.DrawRectangle(XPens.Black, cellX, y, colWidths[i], 20);
var textRect = new XRect(cellX, y, colWidths[i], 20);
g.DrawString(headers[i], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
}
}
// 绘制第一页的表头
DrawHeader(gfx, yPosition);
double currentRowY = yPosition + 20; // 表头高度20
const double rowHeight = 20;
const double bottomMargin = 50; // 页面底部留白
// 遍历所有测量数据
foreach (var m in Measurements)
{
// 检查是否需要换页
if (currentRowY + rowHeight > page.Height - bottomMargin)
{
gfx.Dispose();
page = document.AddPage();
page.Width = XUnit.FromMillimeter(210);
page.Height = XUnit.FromMillimeter(297);
gfx = XGraphics.FromPdfPage(page);
gfx.DrawString("ASTM D7896-19 瞬态热线法测试报告(续)", headerFont, XBrushes.Black,
new XRect(0, 30, page.Width, 30), XStringFormats.TopCenter);
currentRowY = 80;
DrawHeader(gfx, currentRowY - 20);
}
string[] rowData = {
m.Index.ToString(),
m.ThermalConductivity.ToString("F6"),
(m.ThermalDiffusivity * 1e7).ToString("F3"),
m.VolumetricHeatCapacity.ToString("F2"),
m.SpecificHeatCapacity.ToString("F2")
};
for (int j = 0; j < rowData.Length; j++)
{
double cellX = startX + colWidths.Take(j).Sum();
gfx.DrawRectangle(XPens.Black, cellX, currentRowY, colWidths[j], rowHeight);
var textRect = new XRect(cellX, currentRowY, colWidths[j], rowHeight);
gfx.DrawString(rowData[j], normalFont, XBrushes.Black, textRect, XStringFormats.Center);
}
currentRowY += rowHeight;
}
// 添加空行,避免平均值与表格重叠
currentRowY += 60;
// 检查是否需要换页(平均值部分)
if (currentRowY + 80 > page.Height - bottomMargin)
{
gfx.Dispose();
page = document.AddPage();
page.Width = XUnit.FromMillimeter(210);
page.Height = XUnit.FromMillimeter(297);
gfx = XGraphics.FromPdfPage(page);
currentRowY = 50;
}
// 平均值(单位已替换)
gfx.DrawString($"平均热导率: {AverageThermalConductivity:F6} W/(m.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
gfx.DrawString($"平均热扩散率: {AverageThermalDiffusivity:E6} m^2/s", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
gfx.DrawString($"平均体积热容: {AverageVolumetricHeatCapacity:F2} kJ/(m^3.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 20;
// 比热容转换VHC 单位 kJ/(m^3.K) 先转为 J/(m^3.K) 再除以密度得到 J/(kg.K)
double avgCp_J_per_kgK = (AverageVolumetricHeatCapacity * 1000) / SampleDensity;
gfx.DrawString($"平均比热容: {avgCp_J_per_kgK:F0} J/(kg.K)", normalFont, XBrushes.Black, new XPoint(40, currentRowY)); currentRowY += 40;
// 页脚
gfx.DrawString($"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", normalFont, XBrushes.Black, new XPoint(40, page.Height - 30));
gfx.Dispose();
document.Save(filePath);
}
}
[RelayCommand]
private async Task StopTest()
{
@@ -909,7 +1049,7 @@ public partial class D7896ViewModel : ObservableObject
try
{
float rawPressure = await _plcService.ReadFloatAsync(_config.PlcRegisterAddresses.Pressure);
ChamberPressure = rawPressure / 10.0;
ChamberPressure = rawPressure;
}
catch { }
}

View File

@@ -4,7 +4,9 @@
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ASTM_D7896_Tester.Views"
mc:Ignorable="d"
xmlns:converters="clr-namespace:ASTM_D7896_Tester.Converters"
d:DesignHeight="768" d:DesignWidth="1024" Loaded="UserControl_Loaded">
<UserControl.Resources>
@@ -35,7 +37,7 @@
</Setter>
<Setter Property="BorderBrush" Value="#ADADAD"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="12,6"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
@@ -62,10 +64,10 @@
</Style.Triggers>
</Style>
<!-- 卡片阴影效果 -->
<DropShadowEffect x:Key="CardShadow" BlurRadius="8" ShadowDepth="2" Opacity="0.15" Color="Gray"/>
<!-- 卡片阴影 -->
<DropShadowEffect x:Key="CardShadow" BlurRadius="5" ShadowDepth="1" Opacity="0.1" Color="Gray"/>
<!-- 主按钮绿色渐变(修复:将资源定义在正确的位置) -->
<!-- 主按钮绿色渐变 -->
<LinearGradientBrush x:Key="PrimaryButtonBrush" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#4CAF50" Offset="0"/>
<GradientStop Color="#388E3C" Offset="1"/>
@@ -75,7 +77,7 @@
<Style TargetType="DataGrid">
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="RowHeight" Value="28"/>
<Setter Property="RowHeight" Value="26"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
<Setter Property="BorderBrush" Value="#E0E0E0"/>
@@ -95,243 +97,201 @@
<Setter Property="BorderThickness" Value="0,0,1,1"/>
<Setter Property="Foreground" Value="#333333"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Height" Value="28"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<!-- 逆布尔转换器(如果还没在App.xaml中定义,可以在这里定义) -->
<!-- 注意如果已经在App.xaml中定义了InverseBooleanConverter,请注释掉下面这一行 -->
<!-- <converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/> -->
<!-- 逆布尔转换器(请确保已在 App.xaml 或本资源中定义) -->
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
</UserControl.Resources>
<!-- 去掉滚动条,使用 Grid 直接布局,紧凑边距 -->
<Grid Background="#F2F2F2" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<!-- 样品信息 -->
<RowDefinition Height="Auto"/>
<!-- 仪表盘 -->
<RowDefinition Height="Auto"/>
<!-- 体积/密度/加压 -->
<RowDefinition Height="Auto"/>
<!-- 曲线图固定高度380 -->
<RowDefinition Height="Auto"/>
<!-- 数据表格(占满剩余高度) -->
<RowDefinition Height="Auto"/>
<!-- 平均值+按钮 -->
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#F2F2F2">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 1. 样品信息 + 复选框合并的4项确认 -->
<Border Grid.Row="0" Background="White" CornerRadius="6" Padding="10" Margin="0,0,0,8" Effect="{StaticResource CardShadow}">
<StackPanel>
<WrapPanel Margin="0,0,0,8">
<TextBlock Text="样品ID:" VerticalAlignment="Center" FontWeight="SemiBold" Margin="0,0,8,0"/>
<TextBox Text="{Binding SampleId}" Width="120" Margin="0,0,20,0"/>
<TextBlock Text="测试温度(℃):" VerticalAlignment="Center" FontWeight="SemiBold" Margin="0,0,8,0"/>
<TextBox Text="{Binding TestTemperature}" Width="70" IsReadOnly="True" Background="#F0F8FF" Margin="0,0,20,0"/>
<TextBlock Text="日期:" VerticalAlignment="Center" FontWeight="SemiBold" Margin="0,0,8,0"/>
<TextBox Text="{Binding TestDateTime}" Width="140" IsReadOnly="True" Background="#F0F8FF"/>
</WrapPanel>
<WrapPanel>
<!-- 四个确认复选框,默认勾选 -->
<CheckBox IsChecked="{Binding IsCleanConfirmed}" Content="采样池清洁 (7.1)" Margin="0,0,15,0"/>
<CheckBox IsChecked="{Binding BubbleRemoved}" Content="气泡清除 (7.6)" Margin="0,0,15,0"/>
<CheckBox IsChecked="{Binding PlatinumCompatible}" Content="铂兼容性 (1.4)" Margin="0,0,15,0"/>
<CheckBox IsChecked="{Binding AmbientCalibrated}" Content="环境温度校准 (8.1)" Margin="0,0,15,0"/>
<TextBlock Text="状态:" VerticalAlignment="Center" Margin="20,0,5,0"/>
<TextBox Text="{Binding StatusMessage}" Width="200" IsReadOnly="True" Background="#FFF7E6"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 2. 核心参数仪表盘(温度、电压、电阻、压力) -->
<Border Grid.Row="1" Background="White" CornerRadius="6" Padding="10" Margin="0,5" Effect="{StaticResource CardShadow}">
<WrapPanel>
<!-- 核心参数仪表盘 - 更新绑定属性 -->
<Border Background="#E8F0FE" Padding="6" CornerRadius="4" Margin="0,0,15,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="🌡️ 样品温度:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding CurrentTestTemperature, StringFormat=F2}" Width="60" IsReadOnly="True"/>
<TextBlock Text="℃" Margin="3,0,0,0"/>
</StackPanel>
</Border>
<Border Background="#E8F0FE" Padding="6" CornerRadius="4" Margin="0,0,15,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="⏲️ 压力:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding ChamberPressure, StringFormat=F2}" Width="60" IsReadOnly="True"/>
<TextBlock Text="kPa" Margin="3,0,0,0"/>
</StackPanel>
</Border>
<Border Background="#E8F0FE" Padding="6" CornerRadius="4" Margin="0,0,15,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="⚡ 电阻电压:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding StandardResistorVoltage, StringFormat=F10}" Width="120" IsReadOnly="True"/>
<TextBlock Text="V" Margin="3,0,0,0"/>
</StackPanel>
</Border>
<Border Background="#E8F0FE" Padding="6" CornerRadius="4" Margin="0,0,15,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="⚡ 铂丝电压:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding PlatinumVoltage, StringFormat=F4}" Width="70" IsReadOnly="True"/>
<TextBlock Text="V" Margin="3,0,0,0"/>
</StackPanel>
</Border>
<Border Background="#E8F0FE" Padding="6" CornerRadius="4" Margin="0,0,15,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="🔌 铂丝电阻:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding PlatinumResistance, StringFormat=F4}" Width="70" IsReadOnly="True"/>
<TextBlock Text="Ω" Margin="3,0,0,0"/>
</StackPanel>
</Border>
<!-- 1. 样品信息 + 复选框 -->
<Border Grid.Row="0" Background="White" CornerRadius="4" Padding="8" Margin="0,0,0,4" Effect="{StaticResource CardShadow}">
<StackPanel>
<WrapPanel Margin="0,0,0,5">
<TextBlock Text="样品ID:" VerticalAlignment="Center" FontWeight="SemiBold" Margin="0,0,8,0"/>
<TextBox Text="{Binding SampleId}" Width="120" Margin="0,0,15,0"/>
<TextBlock Text="测试温度(℃):" VerticalAlignment="Center" FontWeight="SemiBold" Margin="0,0,8,0"/>
<TextBox Text="{Binding TestTemperature}" Width="70" IsReadOnly="True" Background="#F0F8FF" Margin="0,0,15,0"/>
<TextBlock Text="日期:" VerticalAlignment="Center" FontWeight="SemiBold" Margin="0,0,8,0"/>
<TextBox Text="{Binding TestDateTime}" Width="140" IsReadOnly="True" Background="#F0F8FF"/>
</WrapPanel>
</Border>
<!-- 3. 样品体积 + 密度 + 加压设置 -->
<Border Grid.Row="2" Background="White" CornerRadius="6" Padding="10" Margin="0,5" Effect="{StaticResource CardShadow}">
<WrapPanel>
<TextBlock Text="样品体积 (mL):" VerticalAlignment="Center" Width="100"/>
<TextBox Text="{Binding SampleVolume}" Width="60" Margin="0,0,20,0"/>
<!-- 新增密度输入框 -->
<TextBlock Text="密度 (kg/m³):" VerticalAlignment="Center" Width="100"/>
<TextBox Text="{Binding SampleDensity}" Width="80" Margin="0,0,20,0" ToolTip="输入样品密度,用于计算比热容"/>
<CheckBox IsChecked="{Binding UsePressure}" Content="加压测试" VerticalAlignment="Center" Margin="0,0,15,0"/>
<TextBlock Text="压力 (kPa):" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBox Text="{Binding PressureValue}" Width="60" IsEnabled="{Binding UsePressure}" Margin="0,0,20,0"/>
<TextBlock Text="(蒸气压>33.8kPa时需加压)" Foreground="Gray" VerticalAlignment="Center"/>
<CheckBox IsChecked="{Binding IsCleanConfirmed}" Content="采样池清洁 (7.1)" Margin="0,0,12,0"/>
<CheckBox IsChecked="{Binding BubbleRemoved}" Content="气泡清除 (7.6)" Margin="0,0,12,0"/>
<CheckBox IsChecked="{Binding PlatinumCompatible}" Content="铂兼容性 (1.4)" Margin="0,0,12,0"/>
<CheckBox IsChecked="{Binding AmbientCalibrated}" Content="采样池温度校准 (8.1)" Margin="0,0,15,0"/>
<TextBlock Text="状态:" VerticalAlignment="Center" Margin="10,0,5,0"/>
<TextBox Text="{Binding StatusMessage}" Width="180" IsReadOnly="True" Background="#FFF7E6"/>
</WrapPanel>
</Border>
</StackPanel>
</Border>
<!-- 4. 温升曲线图(核心新增) -->
<Border Grid.Row="3" Background="White" CornerRadius="6" Padding="8" Margin="0,5" Effect="{StaticResource CardShadow}">
<StackPanel>
<TextBlock Text="{Binding CurveTitle}" FontWeight="Bold" FontSize="13" Margin="0,0,0,5" Foreground="#1E4F7A"/>
<oxy:PlotView Model="{Binding TemperatureCurveModel}" Height="350"/>
</StackPanel>
</Border>
<!-- 5. 测量数据表格(占用剩余高度) -->
<Border Grid.Row="4" Background="White" CornerRadius="6" Padding="8" Margin="0,5" Effect="{StaticResource CardShadow}">
<DataGrid ItemsSource="{Binding Measurements}" AutoGenerateColumns="False" HorizontalAlignment="Center" VerticalAlignment="Center"
CanUserAddRows="False" IsReadOnly="True"
HeadersVisibility="Column" GridLinesVisibility="Horizontal"
RowHeight="28" MinHeight="150">
<DataGrid.Columns>
<DataGridTextColumn Header="序号" Binding="{Binding Index}" Width="60">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="热导率 (W/m·K)" Binding="{Binding ThermalConductivity, StringFormat=F5}" Width="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="热扩散率 (m²/s)" Binding="{Binding ThermalDiffusivity, StringFormat=F10}" Width="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="体积热容 (kJ/m³·K)" Binding="{Binding VolumetricHeatCapacity, StringFormat=F2}" Width="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Border>
<!-- 6. 平均值 + 操作按钮 -->
<Border Grid.Row="5" Background="#E8F0FE" CornerRadius="6" Padding="10" Margin="0,5" Effect="{StaticResource CardShadow}">
<StackPanel>
<WrapPanel Margin="0,0,0,8">
<TextBlock Text="平均热导率:" Width="100" FontWeight="SemiBold"/>
<TextBox Text="{Binding AverageThermalConductivity, StringFormat=F5}" Width="100" IsReadOnly="True"/>
<TextBlock Text="W/m·K" Margin="6,0,30,0"/>
<TextBlock Text="平均热扩散率:" Width="120" FontWeight="SemiBold"/>
<TextBox Text="{Binding AverageThermalDiffusivity, StringFormat=F10}" Width="100" IsReadOnly="True"/>
<TextBlock Text="m²/s" Margin="6,0,30,0"/>
<TextBlock Text="平均体积热容:" Width="100" FontWeight="SemiBold"/>
<TextBox Text="{Binding AverageVolumetricHeatCapacity, StringFormat=F2}" Width="100" IsReadOnly="True"/>
<TextBlock Text="kJ/m³·K" Margin="6,0,0,0"/>
</WrapPanel>
<!-- 新增:压力校准、电阻归零、进排气按钮 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,8">
<Button Content="压力校准" Command="{Binding PressureCalibrationCommand}" Width="90" Margin="5"/>
<Button Content="电阻归零" Command="{Binding ResistanceZeroCommand}" Width="90" Margin="5"/>
<Button Content="进气阀" Command="{Binding InletValveControlCommand}" Width="80" Margin="5"/>
<Button Content="排气阀" Command="{Binding OutletValveControlCommand}" Width="80" Margin="5"/>
<!-- 2. 核心参数仪表盘 -->
<Border Grid.Row="1" Background="White" CornerRadius="4" Padding="6" Margin="0,4" Effect="{StaticResource CardShadow}">
<WrapPanel>
<Border Background="#E8F0FE" Padding="5" CornerRadius="4" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="🌡️ 样品温度:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding CurrentTestTemperature, StringFormat=F2}" Width="55" IsReadOnly="True"/>
<TextBlock Text="℃" Margin="3,0,0,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="▶ 开始测试" Command="{Binding StartTestCommand}" Width="130" Height="36" Margin="8"
IsEnabled="{Binding IsTesting, Converter={StaticResource InverseBooleanConverter}}"
Background="{StaticResource PrimaryButtonBrush}"/>
<Button Content="■ 停止测试" Command="{Binding StopTestCommand}" Width="130" Height="36" Margin="8" Background="Orange"/>
<Button Content="⟳ 重置" Command="{Binding ResetCommand}" Width="100" Height="36" Margin="8"/>
<Button Content="📄 生成报告" Command="{Binding GenerateReportCommand}" Width="110" Height="36" Margin="8"/>
</Border>
<Border Background="#E8F0FE" Padding="5" CornerRadius="4" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="⏲️ 压力:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding ChamberPressure, StringFormat=F2}" Width="55" IsReadOnly="True"/>
<TextBlock Text="kPa" Margin="3,0,0,0"/>
</StackPanel>
</StackPanel>
</Border>
</Border>
<Border Background="#E8F0FE" Padding="5" CornerRadius="4" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="⚡ 电阻电压:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding StandardResistorVoltage, StringFormat=F10}" Width="110" IsReadOnly="True"/>
<TextBlock Text="V" Margin="3,0,0,0"/>
</StackPanel>
</Border>
<Border Background="#E8F0FE" Padding="5" CornerRadius="4" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="⚡ 铂丝电压:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding PlatinumVoltage, StringFormat=F4}" Width="65" IsReadOnly="True"/>
<TextBlock Text="V" Margin="3,0,0,0"/>
</StackPanel>
</Border>
<Border Background="#E8F0FE" Padding="5" CornerRadius="4" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="🔌 铂丝电阻:" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBox Text="{Binding PlatinumResistance, StringFormat=F4}" Width="65" IsReadOnly="True"/>
<TextBlock Text="Ω" Margin="3,0,0,0"/>
</StackPanel>
</Border>
</WrapPanel>
</Border>
<!-- 7. 系统校准(可选,可收起到折叠区域,为节省空间放在底部) -->
<Border Grid.Row="6" Background="White" CornerRadius="6" Padding="8" Margin="0,5" Effect="{StaticResource CardShadow}">
<StackPanel>
<TextBlock Text="系统校准 (附录A3)" FontWeight="Bold" Margin="0,0,0,5"/>
<WrapPanel>
<TextBlock Text="参考液:" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding ReferenceLiquids}" SelectedItem="{Binding SelectedReferenceLiquid}" Width="100" Margin="5,0"/>
<TextBlock Text="参考热导率 (W/m·K):" VerticalAlignment="Center" Margin="10,0,5,0"/>
<TextBox Text="{Binding ReferenceConductivity}" Width="70" Margin="0,0,10,0"/>
<!--<Button Content="开始校准" Command="{Binding PerformSystemCalibrationCommand}" Width="100"/>-->
</WrapPanel>
<TextBlock Text="{Binding CalibrationStatus}" FontSize="11" Foreground="Blue" Margin="0,5,0,0"/>
</StackPanel>
</Border>
</Grid>
</ScrollViewer>
<!-- 3. 样品体积 + 密度 + 加压设置 -->
<Border Grid.Row="2" Background="White" CornerRadius="4" Padding="6" Margin="0,4" Effect="{StaticResource CardShadow}">
<WrapPanel>
<TextBlock Text="样品体积 (mL):" VerticalAlignment="Center" Width="95"/>
<TextBox Text="{Binding SampleVolume}" Width="60" Margin="0,0,20,0"/>
<TextBlock Text="密度 (kg/m³):" VerticalAlignment="Center" Width="95"/>
<TextBox Text="{Binding SampleDensity}" Width="80" Margin="0,0,20,0" ToolTip="输入样品密度,用于计算比热容"/>
<CheckBox IsChecked="{Binding UsePressure}" Content="加压测试" VerticalAlignment="Center" Margin="0,0,12,0"/>
<TextBlock Text="压力 (kPa):" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBox Text="{Binding PressureValue}" Width="60" IsEnabled="{Binding UsePressure}" Margin="0,0,15,0"/>
<TextBlock Text="(蒸气压>33.8kPa时需加压)" Foreground="Gray" VerticalAlignment="Center"/>
</WrapPanel>
</Border>
<!-- 4. 温升曲线图(加高,使用 * 行高) -->
<Border Grid.Row="3" Background="White" CornerRadius="4" Padding="6" Margin="0,4" Effect="{StaticResource CardShadow}">
<StackPanel>
<TextBlock Text="{Binding CurveTitle}" FontWeight="Bold" FontSize="13" Margin="0,0,0,3" Foreground="#1E4F7A"/>
<!-- 将高度从350提升到380并让控件随网格拉伸 -->
<oxy:PlotView Model="{Binding TemperatureCurveModel}" Height="220"/>
</StackPanel>
</Border>
<!-- 5. 测量数据表格(占据剩余高度,带滚动条,最小高度保证可见) -->
<Border Grid.Row="4" Background="White" CornerRadius="4" Padding="6" Margin="0,4" Effect="{StaticResource CardShadow}">
<DataGrid ItemsSource="{Binding Measurements}" AutoGenerateColumns="False"
CanUserAddRows="False" IsReadOnly="True"
HeadersVisibility="Column" GridLinesVisibility="Horizontal"
RowHeight="24"
Height="120"
VerticalAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<DataGrid.Columns>
<DataGridTextColumn Header="序号" Binding="{Binding Index}" Width="60">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="热导率 (W/m·K)" Binding="{Binding ThermalConductivity, StringFormat=F5}" Width="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="热扩散率 (m²/s)" Binding="{Binding ThermalDiffusivity, StringFormat=F10}" Width="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="体积热容 (kJ/m³·K)" Binding="{Binding VolumetricHeatCapacity, StringFormat=F2}" Width="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Border>
<!-- 6. 平均值 + 按钮(水平排列并放大) -->
<Border Grid.Row="5" Background="#E8F0FE" CornerRadius="4" Padding="8" Margin="0,4,0,0" Effect="{StaticResource CardShadow}">
<StackPanel>
<!-- 平均值显示行(保持不变) -->
<WrapPanel Margin="0,0,0,8">
<TextBlock Text="平均热导率:" Width="90" FontWeight="SemiBold"/>
<TextBox Text="{Binding AverageThermalConductivity, StringFormat=F5}" Width="90" IsReadOnly="True"/>
<TextBlock Text="W/m·K" Margin="5,0,25,0"/>
<TextBlock Text="平均热扩散率:" Width="110" FontWeight="SemiBold"/>
<TextBox Text="{Binding AverageThermalDiffusivity, StringFormat=F10}" Width="100" IsReadOnly="True"/>
<TextBlock Text="m²/s" Margin="5,0,25,0"/>
<TextBlock Text="平均体积热容:" Width="100" FontWeight="SemiBold"/>
<TextBox Text="{Binding AverageVolumetricHeatCapacity, StringFormat=F2}" Width="90" IsReadOnly="True"/>
<TextBlock Text="kJ/m³·K" Margin="5,0,0,0"/>
</WrapPanel>
<!-- 按钮区域:一排,放大按钮 -->
<WrapPanel HorizontalAlignment="Center" Margin="0,4">
<Button Content="压力校准" Command="{Binding PressureCalibrationCommand}"
Width="100" Height="36" Margin="5" FontSize="13"/>
<Button Content="电阻归零" Command="{Binding ResistanceZeroCommand}"
Width="100" Height="36" Margin="5" FontSize="13"/>
<Button Content="进气阀" Command="{Binding InletValveControlCommand}"
Width="90" Height="36" Margin="5" FontSize="13"/>
<Button Content="排气阀" Command="{Binding OutletValveControlCommand}"
Width="90" Height="36" Margin="5" FontSize="13"/>
<Button Content="▶ 开始测试" Command="{Binding StartTestCommand}"
Width="130" Height="40" Margin="5" FontSize="14" FontWeight="Bold"
IsEnabled="{Binding IsTesting, Converter={StaticResource InverseBooleanConverter}}"
Background="{StaticResource PrimaryButtonBrush}"/>
<Button Content="■ 停止测试" Command="{Binding StopTestCommand}"
Width="130" Height="40" Margin="5" FontSize="14" FontWeight="Bold"
Background="Orange"/>
<Button Content="⟳ 重置" Command="{Binding ResetCommand}"
Width="100" Height="36" Margin="5" FontSize="13"/>
<Button Content="📄 生成报告" Command="{Binding GenerateReportCommand}"
Width="110" Height="36" Margin="5" FontSize="13"/>
</WrapPanel>
</StackPanel>
</Border>
</Grid>
</UserControl>

64
Window1.xaml Normal file
View File

@@ -0,0 +1,64 @@
<Window x:Class="ConstantCurrentControl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SN-5A 恒流源控制" Height="500" Width="650"
WindowStartupLocation="CenterScreen">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 串口设置 -->
<GroupBox Header="串口设置" Grid.Row="0" Margin="5">
<StackPanel Orientation="Horizontal" Margin="5">
<Label Content="端口:"/>
<ComboBox x:Name="cmbPort" Width="80" IsEditable="True" Text="COM2"/>
<Label Content="波特率:" Margin="10,0,0,0"/>
<ComboBox x:Name="cmbBaudrate" Width="80" SelectedIndex="1">
<ComboBoxItem>9600</ComboBoxItem>
<ComboBoxItem>19200</ComboBoxItem>
<ComboBoxItem>38400</ComboBoxItem>
<ComboBoxItem>115200</ComboBoxItem>
</ComboBox>
<Button x:Name="btnConnect" Content="连接" Click="BtnConnect_Click" Width="60" Margin="10,0,0,0"/>
<Button x:Name="btnDisconnect" Content="断开" Click="BtnDisconnect_Click" Width="60" Margin="5,0,0,0" IsEnabled="False"/>
</StackPanel>
</GroupBox>
<!-- 参数设置 -->
<GroupBox Header="输出参数(最优稳定性)" Grid.Row="1" Margin="5">
<StackPanel Margin="5">
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Label Content="频率:"/>
<RadioButton x:Name="rb50Hz" Content="50 Hz" GroupName="Freq" IsChecked="True" Margin="5,0,15,0"/>
<RadioButton x:Name="rb60Hz" Content="60 Hz" GroupName="Freq"/>
<Label Content="设定电流 (A):" Margin="20,0,0,0"/>
<TextBox x:Name="txtSetCurrent" Text="1" Width="80" TextAlignment="Center"/>
<Label Content="A"/>
<Button x:Name="btnSet" Content="直接设定" Click="BtnSet_Click" Width="80" Margin="10,0,0,0"/>
<Button x:Name="btnStop" Content="禁止输出" Click="BtnStop_Click" Width="80" Margin="5,0,0,0"/>
</StackPanel>
<CheckBox x:Name="chkSmooth" Content="启用平滑过渡 (步进0.05A, 间隔200ms)" IsChecked="True" Margin="0,5,0,0"/>
</StackPanel>
</GroupBox>
<!-- 日志 -->
<GroupBox Header="通讯日志" Grid.Row="3" Margin="5">
<ListBox x:Name="lstLog" Height="120" FontFamily="Consolas"/>
</GroupBox>
<!-- 说明 -->
<TextBlock Grid.Row="4" TextWrapping="Wrap" FontSize="11" Foreground="Gray" Margin="5">
※ 协议12字节命令 (FE FE + 识别码 + 电流高/低 + 模式+频率+跟踪 + FF FF)
※ 开启跟踪 (byte8=1) 和 PC调节 (byte6=1) 可获最低噪声与最小温漂
※ 平滑过渡可避免电流突变产生的冲击噪声
</TextBlock>
</Grid>
</Window>

253
Window1.xaml.cs Normal file
View File

@@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
namespace ConstantCurrentControl
{
public partial class Window1 : Window
{
private SerialPort _serialPort;
private bool _isConnected = false;
private DispatcherTimer _readTimer;
private List<byte> _receiveBuffer = new List<byte>(); // 接收缓存
public Window1()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
cmbPort.ItemsSource = SerialPort.GetPortNames();
if (cmbPort.Items.Count > 0) cmbPort.SelectedIndex = 0;
// 初始化定时器50ms 间隔UI 线程)
_readTimer = new DispatcherTimer();
_readTimer.Interval = TimeSpan.FromMilliseconds(50);
_readTimer.Tick += ReadTimer_Tick;
cmbPort.Text = "COM2";
}
#region
private async void BtnConnect_Click(object sender, RoutedEventArgs e)
{
if (cmbPort.SelectedItem == null)
{
MessageBox.Show("请选择串口号");
return;
}
try
{
string port = cmbPort.SelectedItem.ToString();
int baud = int.Parse((cmbBaudrate.SelectedItem as ComboBoxItem).Content.ToString());
_serialPort = new SerialPort(port, baud, Parity.None, 8, StopBits.One);
_serialPort.ReadTimeout = 100;
_serialPort.WriteTimeout = 1000;
_serialPort.Open();
_isConnected = true;
btnConnect.IsEnabled = false;
btnDisconnect.IsEnabled = true;
AddLog($"已连接 {port} @ {baud} bps");
// 清空接收缓存和串口缓冲区
_receiveBuffer.Clear();
_serialPort.DiscardInBuffer();
// 启动定时器读取
_readTimer.Start();
// 发送配置命令不跟踪模式byte8=0
await ConfigureOptimalMode();
}
catch (Exception ex)
{
MessageBox.Show($"连接失败: {ex.Message}");
_isConnected = false;
}
}
private void BtnDisconnect_Click(object sender, RoutedEventArgs e)
{
if (_readTimer != null) _readTimer.Stop();
if (_serialPort != null && _serialPort.IsOpen)
{
_serialPort.Close();
_serialPort.Dispose();
_serialPort = null;
}
_isConnected = false;
btnConnect.IsEnabled = true;
btnDisconnect.IsEnabled = false;
AddLog("已断开连接");
}
#endregion
#region
private void ReadTimer_Tick(object sender, EventArgs e)
{
if (!_isConnected || _serialPort == null || !_serialPort.IsOpen)
return;
try
{
int bytesToRead = _serialPort.BytesToRead;
if (bytesToRead > 0)
{
byte[] buffer = new byte[bytesToRead];
_serialPort.Read(buffer, 0, bytesToRead);
_receiveBuffer.AddRange(buffer);
// 解析完整帧12字节以 FE FE 开头FF FF 结尾)
ParseCompleteFrames();
}
}
catch (Exception ex)
{
AddLog($"读取错误: {ex.Message}");
}
}
private void ParseCompleteFrames()
{
for (int i = 0; i <= _receiveBuffer.Count - 12; i++)
{
if (_receiveBuffer[i] == 0xFE && _receiveBuffer[i + 1] == 0xFE &&
_receiveBuffer[i + 10] == 0xFF && _receiveBuffer[i + 11] == 0xFF)
{
byte[] frame = _receiveBuffer.Skip(i).Take(12).ToArray();
ProcessFrame(frame);
// 移除已处理的帧
_receiveBuffer.RemoveRange(0, i + 12);
i = -1; // 重新扫描
}
}
// 防止缓存无限增长
if (_receiveBuffer.Count > 200)
_receiveBuffer.RemoveRange(0, _receiveBuffer.Count - 200);
}
private void ProcessFrame(byte[] frame)
{
// 解析电流byte4, byte5
int rawCurrent = (frame[4] << 8) | frame[5];
double current = rawCurrent / 1000.0;
AddLog($"回采帧: {BitConverter.ToString(frame)} 电流={current:F3}A");
}
#endregion
#region
private async Task<bool> SendCommand(byte[] command)
{
if (!_isConnected || _serialPort == null || !_serialPort.IsOpen)
{
AddLog("串口未打开");
return false;
}
try
{
await _serialPort.BaseStream.WriteAsync(command, 0, command.Length);
AddLog($"发送: {BitConverter.ToString(command)}");
return true;
}
catch (Exception ex)
{
AddLog($"发送失败: {ex.Message}");
return false;
}
}
private async Task ConfigureOptimalMode()
{
byte[] configCmd = new byte[12];
configCmd[0] = 0xFE;
configCmd[1] = 0xFE;
configCmd[2] = 0x55;
configCmd[3] = 0xAA;
configCmd[4] = 0x00;
configCmd[5] = 0x00;
configCmd[6] = 0x01; // PC调节
configCmd[7] = rb50Hz.IsChecked == true ? (byte)1 : (byte)2;
configCmd[8] = 0x00; // 不跟踪(你实测稳定)
configCmd[9] = 0x00;
configCmd[10] = 0xFF;
configCmd[11] = 0xFF;
await SendCommand(configCmd);
}
#endregion
#region UI操作
private async void BtnSet_Click(object sender, RoutedEventArgs e)
{
if (!_isConnected)
{
MessageBox.Show("请先连接串口");
return;
}
if (!double.TryParse(txtSetCurrent.Text, out double target) || target < 0 || target > 5)
{
MessageBox.Show("电流需为 0~5.000 A");
return;
}
int raw = (int)(target * 1000);
byte cmdH = (byte)((raw >> 8) & 0xFF);
byte cmdL = (byte)(raw & 0xFF);
byte[] currentCmd = new byte[12];
currentCmd[0] = 0xFE;
currentCmd[1] = 0xFE;
currentCmd[2] = 0x55;
currentCmd[3] = 0xAA;
currentCmd[4] = cmdH;
currentCmd[5] = cmdL;
currentCmd[6] = 0x01;
currentCmd[7] = rb50Hz.IsChecked == true ? (byte)1 : (byte)2;
currentCmd[8] = 0x00; // 不跟踪
currentCmd[9] = 0x00;
currentCmd[10] = 0xFF;
currentCmd[11] = 0xFF;
await SendCommand(currentCmd);
AddLog($"设定电流 = {target:F3} A");
}
private async void BtnStop_Click(object sender, RoutedEventArgs e)
{
if (!_isConnected) return;
byte[] stopCmd = new byte[12];
stopCmd[0] = 0xFE;
stopCmd[1] = 0xFE;
stopCmd[2] = 0x55;
stopCmd[3] = 0xAA;
stopCmd[4] = 0x00;
stopCmd[5] = 0x00;
stopCmd[6] = 0x01;
stopCmd[7] = rb50Hz.IsChecked == true ? (byte)1 : (byte)2;
stopCmd[8] = 0x00; // 不跟踪
stopCmd[9] = 0x00;
stopCmd[10] = 0xFF;
stopCmd[11] = 0xFF;
await SendCommand(stopCmd);
AddLog("禁止输出 (0A)");
}
private void AddLog(string msg)
{
Dispatcher.Invoke(() =>
{
lstLog.Items.Insert(0, $"{DateTime.Now:HH:mm:ss} {msg}");
if (lstLog.Items.Count > 50) lstLog.Items.RemoveAt(lstLog.Items.Count - 1);
});
}
#endregion
}
}

View File

@@ -29,8 +29,8 @@
//"PlatinumWireDiameter": 0.000032,
//"PlatinumWireLength": 0.056, //铂丝长度(单位:米)
//"PlatinumWireDiameter": 0.00006,
"PlatinumWireLength": 0.056, //铂丝长度(单位:米)
"PlatinumWireDiameter": 0.00006,
"PlatinumWireLength": 0.035, //铂丝长度(单位:米)
"PlatinumWireDiameter": 0.00010,
"ReportOutputPath": "Reports\\",
"DefaultSampleVolume": 40.0,
"DefaultPressure": 0.0,

Binary file not shown.