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 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 { 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(WriteConstantTempStartAsync); WriteDefrostStartCommand = new AsyncRelayCommand(WriteDefrostStartAsync); WriteDefrostTempSetCommand = new AsyncRelayCommand(WriteDefrostTempSetAsync); WriteDefrostTimeSetCommand = new AsyncRelayCommand(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 WriteConstantTempStartCommand { get; } public IAsyncRelayCommand WriteDefrostStartCommand { get; } public IAsyncRelayCommand WriteDefrostTempSetCommand { get; } public IAsyncRelayCommand 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 以及 MOC(Stage 3的D50=2.82μm,Stage 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); } } } }