2026-06-13 14:16:34 +08:00
|
|
|
|
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;
|
2026-06-16 11:53:02 +08:00
|
|
|
|
private bool _alarmShownLow = false;
|
|
|
|
|
|
private bool _alarmShownHigh = false;
|
2026-06-16 20:50:05 +08:00
|
|
|
|
public IAsyncRelayCommand OpenValveCommand { get; }
|
2026-06-13 14:16:34 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool isConnected;
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private string connectionStatus = "未连接";
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private float currentFlow;
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool isPumpRunning;
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool isTesting;
|
|
|
|
|
|
|
2026-06-16 20:50:05 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool canStopTest; // 控制“停止测试”按钮的启用状态
|
|
|
|
|
|
|
2026-06-13 14:16:34 +08:00
|
|
|
|
[ObservableProperty]
|
2026-06-16 11:53:02 +08:00
|
|
|
|
private int sampleTimeSeconds = 60;
|
2026-06-13 14:16:34 +08:00
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private int remainingSeconds;
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private ObservableCollection<StageData> stages;
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private TestResult currentResult;
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private RealTimeData realTime;
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private CalibrationConfig calibration;
|
|
|
|
|
|
|
2026-06-16 11:53:02 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool constantTempStartEnabled = true; // 是否允许恒温启动(除霜时为false)
|
|
|
|
|
|
|
2026-06-16 20:50:05 +08:00
|
|
|
|
public IAsyncRelayCommand StartPumpCommand { get; }
|
|
|
|
|
|
public IAsyncRelayCommand StopPumpCommand { get; }
|
|
|
|
|
|
// 阀门/泵状态(用于界面显示)
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool valveStatus; // true=开启, false=关闭
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool pumpStatus; // true=运行, false=停止
|
|
|
|
|
|
|
|
|
|
|
|
public IAsyncRelayCommand CloseValveCommand { get; }
|
|
|
|
|
|
|
2026-06-13 14:16:34 +08:00
|
|
|
|
public MainViewModel()
|
|
|
|
|
|
{
|
|
|
|
|
|
_config = new PlcConfiguration();
|
|
|
|
|
|
_plcService = new ModbusTcpPlcService(_config);
|
|
|
|
|
|
_reportService = new ExcelReportService();
|
|
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// NGI 装置三 - 依据《中国药典》2025年版通则0951 表3
|
|
|
|
|
|
// 包含预分离器 + Stage 1 ~ 7 + MOC,共9个收集部件
|
2026-06-13 14:16:34 +08:00
|
|
|
|
Stages = new ObservableCollection<StageData>
|
2026-06-17 23:08:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
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 }
|
|
|
|
|
|
};
|
2026-06-13 14:16:34 +08:00
|
|
|
|
|
|
|
|
|
|
ConnectCommand = new AsyncRelayCommand(ConnectAsync);
|
|
|
|
|
|
DisconnectCommand = new RelayCommand(Disconnect);
|
|
|
|
|
|
StartTestCommand = new AsyncRelayCommand(StartTestAsync);
|
2026-06-16 20:50:05 +08:00
|
|
|
|
StopTestCommand = new AsyncRelayCommand(StopTestAsync, () => IsTesting);
|
2026-06-13 14:16:34 +08:00
|
|
|
|
CalculateCommand = new RelayCommand(CalculateResult);
|
|
|
|
|
|
ExportReportCommand = new AsyncRelayCommand(ExportReportAsync);
|
|
|
|
|
|
|
2026-06-16 11:53:02 +08:00
|
|
|
|
// 写入命令
|
|
|
|
|
|
WriteConstantTempStartCommand = new AsyncRelayCommand<bool>(WriteConstantTempStartAsync);
|
|
|
|
|
|
WriteDefrostStartCommand = new AsyncRelayCommand<bool>(WriteDefrostStartAsync);
|
|
|
|
|
|
WriteDefrostTempSetCommand = new AsyncRelayCommand<float>(WriteDefrostTempSetAsync);
|
|
|
|
|
|
WriteDefrostTimeSetCommand = new AsyncRelayCommand<int>(WriteDefrostTimeSetAsync);
|
|
|
|
|
|
|
2026-06-13 14:16:34 +08:00
|
|
|
|
RealTime = new RealTimeData();
|
|
|
|
|
|
Calibration = new CalibrationConfig();
|
2026-06-16 20:50:05 +08:00
|
|
|
|
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;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-13 14:16:34 +08:00
|
|
|
|
|
2026-06-16 11:53:02 +08:00
|
|
|
|
// 监听属性变化,当除霜启动时更新恒温启动使能
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-06-13 14:16:34 +08:00
|
|
|
|
|
2026-06-16 20:50:05 +08:00
|
|
|
|
}
|
2026-06-16 21:18:46 +08:00
|
|
|
|
|
2026-06-16 20:50:05 +08:00
|
|
|
|
public IAsyncRelayCommand StopTestCommand { get; }
|
2026-06-13 14:16:34 +08:00
|
|
|
|
public IAsyncRelayCommand ConnectCommand { get; }
|
|
|
|
|
|
public IRelayCommand DisconnectCommand { get; }
|
|
|
|
|
|
public IAsyncRelayCommand StartTestCommand { get; }
|
|
|
|
|
|
public IRelayCommand CalculateCommand { get; }
|
|
|
|
|
|
public IAsyncRelayCommand ExportReportCommand { get; }
|
2026-06-16 11:53:02 +08:00
|
|
|
|
public IAsyncRelayCommand<bool> WriteConstantTempStartCommand { get; }
|
|
|
|
|
|
public IAsyncRelayCommand<bool> WriteDefrostStartCommand { get; }
|
|
|
|
|
|
public IAsyncRelayCommand<float> WriteDefrostTempSetCommand { get; }
|
|
|
|
|
|
public IAsyncRelayCommand<int> WriteDefrostTimeSetCommand { get; }
|
2026-06-13 14:16:34 +08:00
|
|
|
|
|
2026-06-16 20:50:05 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-13 14:16:34 +08:00
|
|
|
|
private async Task ConnectAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await _plcService.EnsureConnectedAsync();
|
|
|
|
|
|
IsConnected = true;
|
|
|
|
|
|
ConnectionStatus = "已连接";
|
|
|
|
|
|
_ = Task.Run(ReadFlowLoop);
|
2026-06-16 11:53:02 +08:00
|
|
|
|
_ = Task.Run(ReadRealTimeLoop);
|
|
|
|
|
|
_ = Task.Run(ReadAlarmLoop); // 报警循环
|
|
|
|
|
|
_ = Task.Run(ReadDefrostStatusLoop); // 除霜相关状态
|
2026-06-13 14:16:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
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; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-16 11:53:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-16 20:50:05 +08:00
|
|
|
|
// 在 ReadRealTimeLoop 中添加
|
|
|
|
|
|
var valve = await _plcService.ReadCoilAsync(_config.ValveCoil);
|
|
|
|
|
|
var pump = await _plcService.ReadCoilAsync(_config.PumpCoil);
|
|
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
ValveStatus = valve;
|
|
|
|
|
|
PumpStatus = pump;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-16 11:53:02 +08:00
|
|
|
|
if (calibrated < Calibration.FlowLowLimit || calibrated > Calibration.FlowHighLimit)
|
|
|
|
|
|
{
|
2026-06-16 20:50:05 +08:00
|
|
|
|
//Application.Current.Dispatcher.Invoke(() =>
|
|
|
|
|
|
// MessageBox.Show($"流量异常: {calibrated:F2} L/min", "警告", MessageBoxButton.OK, MessageBoxImage.Warning));
|
2026-06-16 11:53:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 14:16:34 +08:00
|
|
|
|
private async Task StartTestAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!IsConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
await ConnectAsync();
|
|
|
|
|
|
if (!IsConnected) return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (IsTesting) return;
|
|
|
|
|
|
|
|
|
|
|
|
IsTesting = true;
|
|
|
|
|
|
_testCts = new CancellationTokenSource();
|
2026-06-16 21:18:46 +08:00
|
|
|
|
|
|
|
|
|
|
StopTestCommand.NotifyCanExecuteChanged();
|
2026-06-13 14:16:34 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
//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);
|
|
|
|
|
|
//}
|
2026-06-13 14:16:34 +08:00
|
|
|
|
private void CalculateResult()
|
|
|
|
|
|
{
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// 排除预分离器(索引0),只计算 Stage 1 ~ MOC(索引1~8)
|
2026-06-13 14:16:34 +08:00
|
|
|
|
double totalMass = 0;
|
2026-06-17 23:08:06 +08:00
|
|
|
|
for (int i = 1; i < 9; i++)
|
|
|
|
|
|
totalMass += Stages[i].NetWeight;
|
2026-06-13 14:16:34 +08:00
|
|
|
|
|
|
|
|
|
|
if (totalMass <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("总质量为零,无法计算", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// 提取 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;
|
|
|
|
|
|
}
|
2026-06-16 21:18:46 +08:00
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// 计算占比和累积分布
|
|
|
|
|
|
double[] percentages = new double[8];
|
|
|
|
|
|
double[] cumulatives = new double[8];
|
2026-06-16 21:18:46 +08:00
|
|
|
|
double sum = 0;
|
2026-06-17 23:08:06 +08:00
|
|
|
|
for (int i = 0; i < 8; i++)
|
2026-06-16 21:18:46 +08:00
|
|
|
|
{
|
2026-06-17 23:08:06 +08:00
|
|
|
|
percentages[i] = netWeights[i] / totalMass * 100;
|
2026-06-16 21:18:46 +08:00
|
|
|
|
sum += percentages[i];
|
|
|
|
|
|
cumulatives[i] = sum;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// 插值函数(只针对8个数据点)
|
2026-06-16 21:18:46 +08:00
|
|
|
|
double Interpolate(double targetCum)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (targetCum <= cumulatives[0]) return diamArray[0];
|
2026-06-17 23:08:06 +08:00
|
|
|
|
if (targetCum >= cumulatives[7]) return diamArray[7];
|
|
|
|
|
|
for (int i = 0; i < 7; i++)
|
2026-06-16 21:18:46 +08:00
|
|
|
|
{
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-17 23:08:06 +08:00
|
|
|
|
return diamArray[7];
|
2026-06-16 21:18:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double d10 = Interpolate(10);
|
|
|
|
|
|
double d50 = Interpolate(50);
|
|
|
|
|
|
double d90 = Interpolate(90);
|
|
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// GSD
|
2026-06-16 21:18:46 +08:00
|
|
|
|
double d16 = Interpolate(16);
|
|
|
|
|
|
double d84 = Interpolate(84);
|
|
|
|
|
|
double gsd = (d16 > 0 && d84 > 0) ? d84 / d16 : 0;
|
|
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// FPD: Stage 3 ~ MOC(索引3~8)
|
2026-06-13 14:16:34 +08:00
|
|
|
|
double fineMass = 0;
|
2026-06-17 23:08:06 +08:00
|
|
|
|
for (int i = 3; i < 9; i++)
|
|
|
|
|
|
fineMass += Stages[i].NetWeight;
|
2026-06-13 14:16:34 +08:00
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
double fpd = fineMass * 1000;
|
2026-06-13 14:16:34 +08:00
|
|
|
|
double fpf = (fineMass / totalMass) * 100;
|
|
|
|
|
|
|
2026-06-17 23:08:06 +08:00
|
|
|
|
// 保存结果
|
2026-06-13 14:16:34 +08:00
|
|
|
|
CurrentResult = new TestResult
|
|
|
|
|
|
{
|
|
|
|
|
|
TestTime = DateTime.Now,
|
|
|
|
|
|
TotalMass = totalMass,
|
|
|
|
|
|
FineParticleDose = fpd,
|
|
|
|
|
|
FineParticleFraction = fpf,
|
|
|
|
|
|
Stages = Stages.ToList(),
|
|
|
|
|
|
FlowRate = CurrentFlow,
|
2026-06-16 11:53:02 +08:00
|
|
|
|
Temperature = RealTime.Temperature,
|
2026-06-16 21:18:46 +08:00
|
|
|
|
DifferentialPressure = RealTime.DifferentialPressure,
|
|
|
|
|
|
D10 = d10,
|
|
|
|
|
|
D50 = d50,
|
|
|
|
|
|
D90 = d90,
|
|
|
|
|
|
GSD = gsd
|
2026-06-13 14:16:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-16 21:18:46 +08:00
|
|
|
|
MessageBox.Show($"计算完成\n总质量: {totalMass:F4} g\n微细粒子剂量: {fpd:F2} mg\n微细粒子分数: {fpf:F2}%\nD50: {d50:F2} μm",
|
2026-06-13 14:16:34 +08:00
|
|
|
|
"计算结果", 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|