Files
Z173/ViewModels/MainViewModel.cs
2026-06-17 23:08:06 +08:00

644 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
namespace AciTester.ViewModels
{
public partial class MainViewModel : ObservableObject
{
public readonly IPlcService _plcService;
private readonly IReportService _reportService;
public readonly PlcConfiguration _config;
private CancellationTokenSource _testCts;
private bool _alarmShownLow = false;
private bool _alarmShownHigh = false;
public IAsyncRelayCommand OpenValveCommand { get; }
[ObservableProperty]
private bool isConnected;
[ObservableProperty]
private string connectionStatus = "未连接";
[ObservableProperty]
private float currentFlow;
[ObservableProperty]
private bool isPumpRunning;
[ObservableProperty]
private bool isTesting;
[ObservableProperty]
private bool canStopTest; // 控制“停止测试”按钮的启用状态
[ObservableProperty]
private int sampleTimeSeconds = 60;
[ObservableProperty]
private int remainingSeconds;
[ObservableProperty]
private ObservableCollection<StageData> stages;
[ObservableProperty]
private TestResult currentResult;
[ObservableProperty]
private RealTimeData realTime;
[ObservableProperty]
private CalibrationConfig calibration;
[ObservableProperty]
private bool constantTempStartEnabled = true; // 是否允许恒温启动除霜时为false
public IAsyncRelayCommand StartPumpCommand { get; }
public IAsyncRelayCommand StopPumpCommand { get; }
// 阀门/泵状态(用于界面显示)
[ObservableProperty]
private bool valveStatus; // true=开启, false=关闭
[ObservableProperty]
private bool pumpStatus; // true=运行, false=停止
public IAsyncRelayCommand CloseValveCommand { get; }
public MainViewModel()
{
_config = new PlcConfiguration();
_plcService = new ModbusTcpPlcService(_config);
_reportService = new ExcelReportService();
// NGI 装置三 - 依据《中国药典》2025年版通则0951 表3
// 包含预分离器 + Stage 1 ~ 7 + MOC共9个收集部件
Stages = new ObservableCollection<StageData>
{
new StageData { StageName = "预分离器", CutoffDiameter = 12.80 }, // 不计入粒径分布
new StageData { StageName = "Stage 1", CutoffDiameter = 14.30 },
new StageData { StageName = "Stage 2", CutoffDiameter = 4.88 },
new StageData { StageName = "Stage 3", CutoffDiameter = 2.185 },
new StageData { StageName = "Stage 4", CutoffDiameter = 1.207 },
new StageData { StageName = "Stage 5", CutoffDiameter = 0.608 },
new StageData { StageName = "Stage 6", CutoffDiameter = 0.323 },
new StageData { StageName = "Stage 7", CutoffDiameter = 0.206 },
new StageData { StageName = "MOC", CutoffDiameter = 0.070 }
};
ConnectCommand = new AsyncRelayCommand(ConnectAsync);
DisconnectCommand = new RelayCommand(Disconnect);
StartTestCommand = new AsyncRelayCommand(StartTestAsync);
StopTestCommand = new AsyncRelayCommand(StopTestAsync, () => IsTesting);
CalculateCommand = new RelayCommand(CalculateResult);
ExportReportCommand = new AsyncRelayCommand(ExportReportAsync);
// 写入命令
WriteConstantTempStartCommand = new AsyncRelayCommand<bool>(WriteConstantTempStartAsync);
WriteDefrostStartCommand = new AsyncRelayCommand<bool>(WriteDefrostStartAsync);
WriteDefrostTempSetCommand = new AsyncRelayCommand<float>(WriteDefrostTempSetAsync);
WriteDefrostTimeSetCommand = new AsyncRelayCommand<int>(WriteDefrostTimeSetAsync);
RealTime = new RealTimeData();
Calibration = new CalibrationConfig();
OpenValveCommand = new AsyncRelayCommand(async () =>
{
await _plcService.WriteCoilAsync(_config.ValveCoil, true);
ValveStatus = true;
});
CloseValveCommand = new AsyncRelayCommand(async () =>
{
await _plcService.WriteCoilAsync(_config.ValveCoil, false);
ValveStatus = false;
});
StartPumpCommand = new AsyncRelayCommand(async () =>
{
await _plcService.WriteCoilAsync(_config.PumpCoil, true);
PumpStatus = true;
});
StopPumpCommand = new AsyncRelayCommand(async () =>
{
await _plcService.WriteCoilAsync(_config.PumpCoil, false);
PumpStatus = false;
});
// 监听属性变化,当除霜启动时更新恒温启动使能
RealTime.PropertyChanged += async (s, e) =>
{
if (e.PropertyName == nameof(RealTime.DefrostTempSet))
{
await WriteDefrostTempSetAsync(RealTime.DefrostTempSet);
}
else if (e.PropertyName == nameof(RealTime.DefrostTimeSet))
{
await WriteDefrostTimeSetAsync(RealTime.DefrostTimeSet);
}
};
}
public IAsyncRelayCommand StopTestCommand { get; }
public IAsyncRelayCommand ConnectCommand { get; }
public IRelayCommand DisconnectCommand { get; }
public IAsyncRelayCommand StartTestCommand { get; }
public IRelayCommand CalculateCommand { get; }
public IAsyncRelayCommand ExportReportCommand { get; }
public IAsyncRelayCommand<bool> WriteConstantTempStartCommand { get; }
public IAsyncRelayCommand<bool> WriteDefrostStartCommand { get; }
public IAsyncRelayCommand<float> WriteDefrostTempSetCommand { get; }
public IAsyncRelayCommand<int> WriteDefrostTimeSetCommand { get; }
private async Task StopTestAsync()
{
if (!IsTesting) return;
try
{
// 停止真空泵
await _plcService.WriteCoilAsync(_config.PumpCoil, false);
IsPumpRunning = false;
}
catch (Exception ex)
{
MessageBox.Show($"停止泵失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
// 重置状态
IsTesting = false;
CanStopTest = false;
_testCts?.Cancel(); // 取消倒计时
RemainingSeconds = 0;
// 更新按钮状态
StopTestCommand.NotifyCanExecuteChanged();
}
//MessageBox.Show("测试已手动停止,请进行称重并录入数据。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
private async Task ConnectAsync()
{
try
{
await _plcService.EnsureConnectedAsync();
IsConnected = true;
ConnectionStatus = "已连接";
_ = Task.Run(ReadFlowLoop);
_ = Task.Run(ReadRealTimeLoop);
_ = Task.Run(ReadAlarmLoop); // 报警循环
_ = Task.Run(ReadDefrostStatusLoop); // 除霜相关状态
}
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 ReadRealTimeLoop()
{
while (IsConnected)
{
try
{
var rawFlow = await _plcService.ReadFloatAsync(_config.FlowRegister);
var calibrated = rawFlow * Calibration.FlowCalibration;
var temp = await _plcService.ReadFloatAsync(_config.TemperatureReg);
var calibratedTemp = temp * Calibration.TemperatureCalibration;
var pumpPres = await _plcService.ReadFloatAsync(_config.PumpPressureReg);
var calibratedPump = pumpPres * Calibration.PumpPressureCalibration;
var impPres = await _plcService.ReadFloatAsync(_config.ImpactorPressureReg);
var calibratedImp = impPres * Calibration.ImpactorPressureCalibration;
Application.Current.Dispatcher.Invoke(() =>
{
RealTime.RawFlow = rawFlow;
RealTime.CalibratedFlow = calibrated;
RealTime.Temperature = calibratedTemp;
RealTime.PumpPressure = calibratedPump;
RealTime.ImpactorPressure = calibratedImp;
RealTime.DifferentialPressure = calibratedImp - calibratedPump;
});
// 在 ReadRealTimeLoop 中添加
var valve = await _plcService.ReadCoilAsync(_config.ValveCoil);
var pump = await _plcService.ReadCoilAsync(_config.PumpCoil);
Application.Current.Dispatcher.Invoke(() =>
{
ValveStatus = valve;
PumpStatus = pump;
});
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);
}
}
private async Task ReadAlarmLoop()
{
while (IsConnected)
{
try
{
var lowAlarm = await _plcService.ReadCoilAsync(_config.AcLowPressureAlarmCoil);
var highAlarm = await _plcService.ReadCoilAsync(_config.AcHighPressureAlarmCoil);
Application.Current.Dispatcher.Invoke(() =>
{
RealTime.AcLowPressureAlarm = lowAlarm;
RealTime.AcHighPressureAlarm = highAlarm;
});
if (lowAlarm && !_alarmShownLow)
{
_alarmShownLow = true;
Application.Current.Dispatcher.Invoke(() =>
{
var result = MessageBox.Show("空调低压报警,请检查空调情况", "报警", MessageBoxButton.OK, MessageBoxImage.Error);
if (result == MessageBoxResult.OK)
{
_ = _plcService.WriteCoilAsync(_config.AcLowPressureAlarmCoil, false);
_alarmShownLow = false;
}
});
}
if (highAlarm && !_alarmShownHigh)
{
_alarmShownHigh = true;
Application.Current.Dispatcher.Invoke(() =>
{
var result = MessageBox.Show("空调高压报警,请检查空调情况", "报警", MessageBoxButton.OK, MessageBoxImage.Error);
if (result == MessageBoxResult.OK)
{
_ = _plcService.WriteCoilAsync(_config.AcHighPressureAlarmCoil, false);
_alarmShownHigh = false;
}
});
}
}
catch { }
await Task.Delay(500);
}
}
private async Task ReadDefrostStatusLoop()
{
while (IsConnected)
{
try
{
// 读取 D50 空调启动倒计时
var countdown = await _plcService.ReadInt32Async(_config.AcStartupCountdownReg);
// 读取 D302 除霜温度设置
var defrostTemp = await _plcService.ReadFloatAsync(_config.DefrostTempSetReg);
// 读取 D310 除霜时间设置
var defrostTime = await _plcService.ReadInt32Async(_config.DefrostTimeSetReg);
// 读取 D42 D40 除霜计时
var min = await _plcService.ReadInt32Async(_config.DefrostMinuteReg);
var sec = await _plcService.ReadInt32Async(_config.DefrostSecondReg);
// 读取 M4 M19
var constTemp = await _plcService.ReadCoilAsync(_config.ConstantTempStartCoil);
var defrostStart = await _plcService.ReadCoilAsync(_config.DefrostStartCoil);
Application.Current.Dispatcher.Invoke(() =>
{
RealTime.AcStartupCountdown = countdown;
RealTime.DefrostTempSet = defrostTemp;
RealTime.DefrostTimeSet = defrostTime;
RealTime.DefrostMinute = min;
RealTime.DefrostSecond = sec;
RealTime.ConstantTempStart = constTemp;
RealTime.DefrostStart = defrostStart;
});
}
catch { }
await Task.Delay(1000);
}
}
private async Task WriteConstantTempStartAsync(bool value)
{
if (!IsConnected) return;
if (RealTime.DefrostStart)
{
MessageBox.Show("设备正在除霜,不能进行恒温操作", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
await _plcService.WriteCoilAsync(_config.ConstantTempStartCoil, value);
}
private async Task WriteDefrostStartAsync(bool value)
{
if (!IsConnected) return;
await _plcService.WriteCoilAsync(_config.DefrostStartCoil, value);
if (value)
{
// 立即更新界面使能
ConstantTempStartEnabled = false;
}
else
{
ConstantTempStartEnabled = true;
}
}
private async Task WriteDefrostTempSetAsync(float value)
{
if (!IsConnected) return;
await _plcService.WriteMultipleRegistersAsync(_config.DefrostTempSetReg, value);
}
private async Task WriteDefrostTimeSetAsync(int value)
{
if (!IsConnected) return;
await _plcService.WriteInt32Async(_config.DefrostTimeSetReg, value);
}
private async Task StartTestAsync()
{
if (!IsConnected)
{
await ConnectAsync();
if (!IsConnected) return;
}
if (IsTesting) return;
IsTesting = true;
_testCts = new CancellationTokenSource();
StopTestCommand.NotifyCanExecuteChanged();
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;
// }
// // 1. 计算各级占比和累积分布8个层级
// double[] diamArray = new double[8];
// for (int i = 0; i < 8; i++)
// diamArray[i] = Stages[i].CutoffDiameter;
// double[] percentages = new double[8];
// double[] cumulatives = new double[8];
// double sum = 0;
// for (int i = 0; i < 8; i++)
// {
// percentages[i] = Stages[i].NetWeight / totalMass * 100;
// sum += percentages[i];
// cumulatives[i] = sum;
// }
// // 2. 插值函数与之前相同但只使用8个点
// double Interpolate(double targetCum)
// {
// if (targetCum <= cumulatives[0]) return diamArray[0];
// if (targetCum >= cumulatives[7]) return diamArray[7];
// for (int i = 0; i < 7; i++)
// {
// if (cumulatives[i] <= targetCum && cumulatives[i + 1] >= targetCum)
// {
// double d1 = diamArray[i];
// double d2 = diamArray[i + 1];
// double c1 = cumulatives[i];
// double c2 = cumulatives[i + 1];
// double logD1 = Math.Log(d1);
// double logD2 = Math.Log(d2);
// double logD = logD1 + (targetCum - c1) * (logD2 - logD1) / (c2 - c1);
// return Math.Exp(logD);
// }
// }
// return diamArray[7];
// }
// double d10 = Interpolate(10);
// double d50 = Interpolate(50);
// double d90 = Interpolate(90);
// // 3. GSD (D84/D16)
// double d16 = Interpolate(16);
// double d84 = Interpolate(84);
// double gsd = (d16 > 0 && d84 > 0) ? d84 / d16 : 0;
// // 4. 微细粒子剂量(FPD)和分数(FPF)
// // NGI中截止直径 ≤ 5μm 的层级为 Stage 3~7 以及 MOCStage 3的D50=2.82μmStage 7=0.34μm全部≤5μm
// // 因此统计 Stage 3,4,5,6,7 和 MOC 的质量
// double fineMass = 0;
// for (int i = 2; i < 8; i++) // i=2 对应Stage 3数组索引从0开始
// fineMass += Stages[i].NetWeight;
// double fpd = fineMass * 1000; // mg
// double fpf = (fineMass / totalMass) * 100;
// // 5. 赋值给 CurrentResult
// CurrentResult = new TestResult
// {
// TestTime = DateTime.Now,
// TotalMass = totalMass,
// FineParticleDose = fpd,
// FineParticleFraction = fpf,
// Stages = Stages.ToList(),
// FlowRate = CurrentFlow,
// Temperature = RealTime.Temperature,
// DifferentialPressure = RealTime.DifferentialPressure,
// D10 = d10,
// D50 = d50,
// D90 = d90,
// GSD = gsd
// };
// MessageBox.Show($"计算完成\n总质量: {totalMass:F4} g\n微细粒子剂量: {fpd:F2} mg\n微细粒子分数: {fpf:F2}%\nD50: {d50:F2} μm",
// "计算结果", MessageBoxButton.OK, MessageBoxImage.Information);
//}
private void CalculateResult()
{
// 排除预分离器索引0只计算 Stage 1 ~ MOC索引1~8
double totalMass = 0;
for (int i = 1; i < 9; i++)
totalMass += Stages[i].NetWeight;
if (totalMass <= 0)
{
MessageBox.Show("总质量为零,无法计算", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 提取 Stage 1 ~ MOC 的直径和质量索引1~8
double[] diamArray = new double[8];
double[] netWeights = new double[8];
for (int i = 0; i < 8; i++)
{
diamArray[i] = Stages[i + 1].CutoffDiameter;
netWeights[i] = Stages[i + 1].NetWeight;
}
// 计算占比和累积分布
double[] percentages = new double[8];
double[] cumulatives = new double[8];
double sum = 0;
for (int i = 0; i < 8; i++)
{
percentages[i] = netWeights[i] / totalMass * 100;
sum += percentages[i];
cumulatives[i] = sum;
}
// 插值函数只针对8个数据点
double Interpolate(double targetCum)
{
if (targetCum <= cumulatives[0]) return diamArray[0];
if (targetCum >= cumulatives[7]) return diamArray[7];
for (int i = 0; i < 7; i++)
{
if (cumulatives[i] <= targetCum && cumulatives[i + 1] >= targetCum)
{
double d1 = diamArray[i];
double d2 = diamArray[i + 1];
double c1 = cumulatives[i];
double c2 = cumulatives[i + 1];
double logD1 = Math.Log(d1);
double logD2 = Math.Log(d2);
double logD = logD1 + (targetCum - c1) * (logD2 - logD1) / (c2 - c1);
return Math.Exp(logD);
}
}
return diamArray[7];
}
double d10 = Interpolate(10);
double d50 = Interpolate(50);
double d90 = Interpolate(90);
// GSD
double d16 = Interpolate(16);
double d84 = Interpolate(84);
double gsd = (d16 > 0 && d84 > 0) ? d84 / d16 : 0;
// FPD: Stage 3 ~ MOC索引3~8
double fineMass = 0;
for (int i = 3; i < 9; i++)
fineMass += Stages[i].NetWeight;
double fpd = fineMass * 1000;
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,
D10 = d10,
D50 = d50,
D90 = d90,
GSD = gsd
};
MessageBox.Show($"计算完成\n总质量: {totalMass:F4} g\n微细粒子剂量: {fpd:F2} mg\n微细粒子分数: {fpf:F2}%\nD50: {d50:F2} μm",
"计算结果", 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);
}
}
}
}