305 lines
11 KiB
C#
305 lines
11 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.ObjectModel;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using System.Windows;
|
|||
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|||
|
|
using CommunityToolkit.Mvvm.Input;
|
|||
|
|
using AciTester.Models;
|
|||
|
|
using AciTester.Services;
|
|||
|
|
using AciTester.Views;
|
|||
|
|
|
|||
|
|
namespace AciTester.ViewModels
|
|||
|
|
{
|
|||
|
|
public partial class MainViewModel : ObservableObject
|
|||
|
|
{
|
|||
|
|
public readonly IPlcService _plcService;
|
|||
|
|
private readonly IReportService _reportService;
|
|||
|
|
public readonly PlcConfiguration _config;
|
|||
|
|
private CancellationTokenSource _testCts;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private bool isConnected;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private string connectionStatus = "未连接";
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private float currentFlow;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private bool isPumpRunning;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private bool isTesting;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private int sampleTimeSeconds = 60; // 默认采样60秒
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private int remainingSeconds;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private ObservableCollection<StageData> stages;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private TestResult currentResult;
|
|||
|
|
|
|||
|
|
// 在 MainViewModel 中添加
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private RealTimeData realTime;
|
|||
|
|
|
|||
|
|
[ObservableProperty]
|
|||
|
|
private CalibrationConfig calibration;
|
|||
|
|
|
|||
|
|
private Timer _dataTimer;
|
|||
|
|
public MainViewModel()
|
|||
|
|
{
|
|||
|
|
_config = new PlcConfiguration();
|
|||
|
|
_plcService = new ModbusTcpPlcService(_config);
|
|||
|
|
_reportService = new ExcelReportService();
|
|||
|
|
|
|||
|
|
// 初始化ACI 8级 + 滤膜
|
|||
|
|
Stages = new ObservableCollection<StageData>
|
|||
|
|
{
|
|||
|
|
new StageData { StageName = "Stage 0", CutoffDiameter = 9.0 },
|
|||
|
|
new StageData { StageName = "Stage 1", CutoffDiameter = 5.8 },
|
|||
|
|
new StageData { StageName = "Stage 2", CutoffDiameter = 4.7 },
|
|||
|
|
new StageData { StageName = "Stage 3", CutoffDiameter = 3.3 },
|
|||
|
|
new StageData { StageName = "Stage 4", CutoffDiameter = 2.1 },
|
|||
|
|
new StageData { StageName = "Stage 5", CutoffDiameter = 1.1 },
|
|||
|
|
new StageData { StageName = "Stage 6", CutoffDiameter = 0.7 },
|
|||
|
|
new StageData { StageName = "Stage 7", CutoffDiameter = 0.4 },
|
|||
|
|
new StageData { StageName = "Filter", CutoffDiameter = 0.0 }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
ConnectCommand = new AsyncRelayCommand(ConnectAsync);
|
|||
|
|
DisconnectCommand = new RelayCommand(Disconnect);
|
|||
|
|
StartTestCommand = new AsyncRelayCommand(StartTestAsync);
|
|||
|
|
CalculateCommand = new RelayCommand(CalculateResult);
|
|||
|
|
ExportReportCommand = new AsyncRelayCommand(ExportReportAsync);
|
|||
|
|
|
|||
|
|
// 在构造函数中初始化
|
|||
|
|
RealTime = new RealTimeData();
|
|||
|
|
Calibration = new CalibrationConfig();
|
|||
|
|
|
|||
|
|
|
|||
|
|
//this.Loaded += (s, e) =>
|
|||
|
|
//{
|
|||
|
|
// var keyGesture = new KeyGesture(Key.P, ModifierKeys.Control);
|
|||
|
|
// var inputBinding = new InputBinding(new RelayCommand(OpenConfigWindow), keyGesture);
|
|||
|
|
// this.InputBindings.Add(inputBinding);
|
|||
|
|
//};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public IAsyncRelayCommand ConnectCommand { get; }
|
|||
|
|
public IRelayCommand DisconnectCommand { get; }
|
|||
|
|
public IAsyncRelayCommand StartTestCommand { get; }
|
|||
|
|
public IRelayCommand CalculateCommand { get; }
|
|||
|
|
public IAsyncRelayCommand ExportReportCommand { get; }
|
|||
|
|
|
|||
|
|
private async Task ConnectAsync()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
await _plcService.EnsureConnectedAsync();
|
|||
|
|
IsConnected = true;
|
|||
|
|
ConnectionStatus = "已连接";
|
|||
|
|
_ = Task.Run(ReadFlowLoop);
|
|||
|
|
_ = Task.Run(ReadRealTimeLoop); // 新增
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
MessageBox.Show($"连接失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|||
|
|
IsConnected = false;
|
|||
|
|
ConnectionStatus = "连接失败";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void Disconnect()
|
|||
|
|
{
|
|||
|
|
_plcService.Dispose();
|
|||
|
|
IsConnected = false;
|
|||
|
|
ConnectionStatus = "未连接";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task ReadFlowLoop()
|
|||
|
|
{
|
|||
|
|
while (IsConnected)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var flow = await _plcService.ReadFloatAsync(_config.FlowRegister);
|
|||
|
|
Application.Current.Dispatcher.Invoke(() => CurrentFlow = flow);
|
|||
|
|
await Task.Delay(1000);
|
|||
|
|
}
|
|||
|
|
catch { break; }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task StartTestAsync()
|
|||
|
|
{
|
|||
|
|
if (!IsConnected)
|
|||
|
|
{
|
|||
|
|
await ConnectAsync();
|
|||
|
|
if (!IsConnected) return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (IsTesting) return;
|
|||
|
|
|
|||
|
|
IsTesting = true;
|
|||
|
|
_testCts = new CancellationTokenSource();
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 启动真空泵
|
|||
|
|
await _plcService.WriteCoilAsync(_config.PumpCoil, true);
|
|||
|
|
IsPumpRunning = true;
|
|||
|
|
|
|||
|
|
// 倒计时
|
|||
|
|
RemainingSeconds = SampleTimeSeconds;
|
|||
|
|
for (int i = 0; i < SampleTimeSeconds; i++)
|
|||
|
|
{
|
|||
|
|
if (_testCts.Token.IsCancellationRequested) break;
|
|||
|
|
await Task.Delay(1000);
|
|||
|
|
RemainingSeconds--;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 停止泵
|
|||
|
|
await _plcService.WriteCoilAsync(_config.PumpCoil, false);
|
|||
|
|
IsPumpRunning = false;
|
|||
|
|
|
|||
|
|
MessageBox.Show($"采样完成!\n实际采样时间: {SampleTimeSeconds} 秒\n请进行称重并录入数据。",
|
|||
|
|
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
MessageBox.Show($"测试异常: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|||
|
|
// 尝试停止泵
|
|||
|
|
try { await _plcService.WriteCoilAsync(_config.PumpCoil, false); } catch { }
|
|||
|
|
IsPumpRunning = false;
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
IsTesting = false;
|
|||
|
|
_testCts?.Dispose();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void CalculateResult()
|
|||
|
|
{
|
|||
|
|
double totalMass = 0;
|
|||
|
|
foreach (var stage in Stages)
|
|||
|
|
totalMass += stage.NetWeight;
|
|||
|
|
|
|||
|
|
if (totalMass <= 0)
|
|||
|
|
{
|
|||
|
|
MessageBox.Show("总质量为零,无法计算", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 微细粒子剂量:截止直径 ≤ 5μm 的级 + 滤膜
|
|||
|
|
double fineMass = 0;
|
|||
|
|
foreach (var stage in Stages)
|
|||
|
|
{
|
|||
|
|
if (stage.CutoffDiameter <= 5.0 && stage.CutoffDiameter > 0)
|
|||
|
|
fineMass += stage.NetWeight;
|
|||
|
|
}
|
|||
|
|
fineMass += Stages[8].NetWeight; // 滤膜
|
|||
|
|
|
|||
|
|
double fpd = fineMass * 1000; // mg
|
|||
|
|
double fpf = (fineMass / totalMass) * 100;
|
|||
|
|
|
|||
|
|
CurrentResult = new TestResult
|
|||
|
|
{
|
|||
|
|
TestTime = DateTime.Now,
|
|||
|
|
TotalMass = totalMass,
|
|||
|
|
FineParticleDose = fpd,
|
|||
|
|
FineParticleFraction = fpf,
|
|||
|
|
Stages = Stages.ToList(),
|
|||
|
|
FlowRate = CurrentFlow,
|
|||
|
|
Temperature = RealTime.Temperature, // 新增
|
|||
|
|
DifferentialPressure = RealTime.DifferentialPressure // 新增
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
MessageBox.Show($"计算完成\n总质量: {totalMass:F4} g\n微细粒子剂量: {fpd:F2} mg\n微细粒子分数: {fpf:F2}%",
|
|||
|
|
"计算结果", MessageBoxButton.OK, MessageBoxImage.Information);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task ExportReportAsync()
|
|||
|
|
{
|
|||
|
|
if (CurrentResult == null)
|
|||
|
|
{
|
|||
|
|
MessageBox.Show("请先计算测试结果", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var saveDialog = new Microsoft.Win32.SaveFileDialog
|
|||
|
|
{
|
|||
|
|
Filter = "Excel文件|*.xlsx",
|
|||
|
|
FileName = $"ACI_Test_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx"
|
|||
|
|
};
|
|||
|
|
if (saveDialog.ShowDialog() == true)
|
|||
|
|
{
|
|||
|
|
await _reportService.GenerateReportAsync(CurrentResult, saveDialog.FileName);
|
|||
|
|
MessageBox.Show("报告已保存", "完成", MessageBoxButton.OK, MessageBoxImage.Information);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 启动定时读取(连接成功后)
|
|||
|
|
private async Task ReadRealTimeLoop()
|
|||
|
|
{
|
|||
|
|
while (IsConnected)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 读取原始流量
|
|||
|
|
var rawFlow = await _plcService.ReadFloatAsync(_config.FlowRegister);
|
|||
|
|
// 校准流量 = 原始流量 * 流量系数
|
|||
|
|
var calibrated = rawFlow * Calibration.FlowCalibration;
|
|||
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|||
|
|
{
|
|||
|
|
RealTime.RawFlow = rawFlow;
|
|||
|
|
RealTime.CalibratedFlow = calibrated;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 温度
|
|||
|
|
var temp = await _plcService.ReadFloatAsync(_config.TemperatureReg);
|
|||
|
|
var calibratedTemp = temp * Calibration.TemperatureCalibration;
|
|||
|
|
Application.Current.Dispatcher.Invoke(() => RealTime.Temperature = calibratedTemp);
|
|||
|
|
|
|||
|
|
// 泵端压力
|
|||
|
|
var pumpPres = await _plcService.ReadFloatAsync(_config.PumpPressureReg);
|
|||
|
|
var calibratedPump = pumpPres * Calibration.PumpPressureCalibration;
|
|||
|
|
Application.Current.Dispatcher.Invoke(() => RealTime.PumpPressure = calibratedPump);
|
|||
|
|
|
|||
|
|
// 撞击器端压力
|
|||
|
|
var impPres = await _plcService.ReadFloatAsync(_config.ImpactorPressureReg);
|
|||
|
|
var calibratedImp = impPres * Calibration.ImpactorPressureCalibration;
|
|||
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|||
|
|
{
|
|||
|
|
RealTime.ImpactorPressure = calibratedImp;
|
|||
|
|
RealTime.DifferentialPressure = calibratedImp - calibratedPump;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 流量报警检查
|
|||
|
|
if (calibrated < Calibration.FlowLowLimit || calibrated > Calibration.FlowHighLimit)
|
|||
|
|
{
|
|||
|
|
// 可触发界面警告
|
|||
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|||
|
|
MessageBox.Show($"流量异常: {calibrated:F2} L/min", "警告", MessageBoxButton.OK, MessageBoxImage.Warning));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
await Task.Delay(1000);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|