From 2dadfe38613d4503352b20e6037768c8bca820a5 Mon Sep 17 00:00:00 2001 From: xyy <544939200@qq.com> Date: Tue, 16 Jun 2026 11:53:02 +0800 Subject: [PATCH] --- App.xaml | 1 + App.xaml.cs | 2 + Converters/BoolToYesNoConverter.cs | 18 ++ Models/CalibrationConfig.cs | 3 + Models/PlcConfiguration.cs | 17 ++ Models/RealTimeData.cs | 40 ++++ Services/ModbusTcpPlcService.cs | 18 ++ ViewModels/MainViewModel.cs | 270 ++++++++++++++++------- Views/ConfigWindow.xaml | 10 +- Views/MainWindow.xaml | 338 ++++++++++++++++++++--------- Views/MainWindow.xaml.cs | 4 + 11 files changed, 541 insertions(+), 180 deletions(-) create mode 100644 Converters/BoolToYesNoConverter.cs diff --git a/App.xaml b/App.xaml index 6c952a0..5096cf4 100644 --- a/App.xaml +++ b/App.xaml @@ -8,5 +8,6 @@ + \ No newline at end of file diff --git a/App.xaml.cs b/App.xaml.cs index 8954430..2d1ee38 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -11,4 +11,6 @@ namespace AciTester { } + + } diff --git a/Converters/BoolToYesNoConverter.cs b/Converters/BoolToYesNoConverter.cs new file mode 100644 index 0000000..8c2c01c --- /dev/null +++ b/Converters/BoolToYesNoConverter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace AciTester.Converters +{ + public class BoolToYesNoConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => (value is bool b && b) ? "正在除霜" : ""; + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => throw new NotImplementedException(); + } +} diff --git a/Models/CalibrationConfig.cs b/Models/CalibrationConfig.cs index 28a9ce3..e176541 100644 --- a/Models/CalibrationConfig.cs +++ b/Models/CalibrationConfig.cs @@ -21,5 +21,8 @@ namespace AciTester.Models [ObservableProperty] private float flowHighLimit = 32.0f; // D1332 高限 + + [ObservableProperty] + private float temperatureProtect = 40.0f; // D1084 } } \ No newline at end of file diff --git a/Models/PlcConfiguration.cs b/Models/PlcConfiguration.cs index 2584ec3..c881c0e 100644 --- a/Models/PlcConfiguration.cs +++ b/Models/PlcConfiguration.cs @@ -55,6 +55,20 @@ namespace AciTester.Models public ushort ImpactorPressureCalibCoil { get; set; } = 1303; // M1303 + + + + public ushort AcLowPressureAlarmCoil { get; set; } = 1001; // M1001 + public ushort AcHighPressureAlarmCoil { get; set; } = 1002; // M1002 + public ushort AcStartupCountdownReg { get; set; } = 50; // D50 (双整数) + public ushort DefrostTempSetReg { get; set; } = 302; // D302 (浮点) + public ushort DefrostTimeSetReg { get; set; } = 310; // D310 (双整数) + public ushort DefrostMinuteReg { get; set; } = 42; // D42 (双整数) + public ushort DefrostSecondReg { get; set; } = 40; // D40 (双整数) + public ushort TempProtectReg { get; set; } = 1084; // D1084 (浮点) + public ushort ConstantTempStartCoil { get; set; } = 4; // M4 + public ushort DefrostStartCoil { get; set; } = 19; // M19 + } /// @@ -97,5 +111,8 @@ namespace AciTester.Models void Dispose(); bool IsConnected { get; } // 新增 + + Task ReadInt32Async(ushort startAddress); + Task WriteInt32Async(ushort startAddress, int value); } } diff --git a/Models/RealTimeData.cs b/Models/RealTimeData.cs index d8b2347..6558fc9 100644 --- a/Models/RealTimeData.cs +++ b/Models/RealTimeData.cs @@ -21,5 +21,45 @@ namespace AciTester.Models [ObservableProperty] private float differentialPressure; // 压差 = impactorPressure - pumpPressure + + + + // 新增 + [ObservableProperty] + private int acStartupCountdown; // D50 + + private float _defrostTempSet; + public float DefrostTempSet + { + get => _defrostTempSet; + set => SetProperty(ref _defrostTempSet, value); + } + + private int _defrostTimeSet; + public int DefrostTimeSet + { + get => _defrostTimeSet; + set => SetProperty(ref _defrostTimeSet, value); + } // D302 + + + + [ObservableProperty] + private int defrostMinute; // D42 + + [ObservableProperty] + private int defrostSecond; // D40 + + [ObservableProperty] + private bool constantTempStart; // M4 + + [ObservableProperty] + private bool defrostStart; // M19 + + [ObservableProperty] + private bool acLowPressureAlarm; // M1001 + + [ObservableProperty] + private bool acHighPressureAlarm; // M1002 } } \ No newline at end of file diff --git a/Services/ModbusTcpPlcService.cs b/Services/ModbusTcpPlcService.cs index a8d4f63..77eb6c2 100644 --- a/Services/ModbusTcpPlcService.cs +++ b/Services/ModbusTcpPlcService.cs @@ -188,6 +188,24 @@ namespace AciTester.Services _tcpClient?.Close(); _tcpClient?.Dispose(); } + + + public async Task ReadInt32Async(ushort startAddress) + { + await EnsureConnectedAsync(); + var regs = await ReadHoldingRegistersAsync(startAddress, 2); + return (regs[0] << 16) | regs[1]; + } + + public async Task WriteInt32Async(ushort startAddress, int value) + { + await EnsureConnectedAsync(); + var bytes = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) Array.Reverse(bytes); + ushort high = (ushort)((bytes[0] << 8) | bytes[1]); + ushort low = (ushort)((bytes[2] << 8) | bytes[3]); + await _master.WriteMultipleRegistersAsync(_config.SlaveId, startAddress, new ushort[] { high, low }); + } } diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 379b0d3..8ba630c 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -7,7 +7,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using AciTester.Models; using AciTester.Services; -using AciTester.Views; namespace AciTester.ViewModels { @@ -17,6 +16,8 @@ namespace AciTester.ViewModels private readonly IReportService _reportService; public readonly PlcConfiguration _config; private CancellationTokenSource _testCts; + private bool _alarmShownLow = false; + private bool _alarmShownHigh = false; [ObservableProperty] private bool isConnected; @@ -34,7 +35,7 @@ namespace AciTester.ViewModels private bool isTesting; [ObservableProperty] - private int sampleTimeSeconds = 60; // 默认采样60秒 + private int sampleTimeSeconds = 60; [ObservableProperty] private int remainingSeconds; @@ -45,21 +46,21 @@ namespace AciTester.ViewModels [ObservableProperty] private TestResult currentResult; - // 在 MainViewModel 中添加 [ObservableProperty] private RealTimeData realTime; [ObservableProperty] private CalibrationConfig calibration; - private Timer _dataTimer; + [ObservableProperty] + private bool constantTempStartEnabled = true; // 是否允许恒温启动(除霜时为false) + public MainViewModel() { _config = new PlcConfiguration(); _plcService = new ModbusTcpPlcService(_config); _reportService = new ExcelReportService(); - // 初始化ACI 8级 + 滤膜 Stages = new ObservableCollection { new StageData { StageName = "Stage 0", CutoffDiameter = 9.0 }, @@ -79,17 +80,27 @@ namespace AciTester.ViewModels 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(); - - //this.Loaded += (s, e) => - //{ - // var keyGesture = new KeyGesture(Key.P, ModifierKeys.Control); - // var inputBinding = new InputBinding(new RelayCommand(OpenConfigWindow), keyGesture); - // this.InputBindings.Add(inputBinding); - //}; + // 监听属性变化,当除霜启动时更新恒温启动使能 + 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 ConnectCommand { get; } @@ -97,6 +108,10 @@ namespace AciTester.ViewModels 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 ConnectAsync() { @@ -106,7 +121,9 @@ namespace AciTester.ViewModels IsConnected = true; ConnectionStatus = "已连接"; _ = Task.Run(ReadFlowLoop); - _ = Task.Run(ReadRealTimeLoop); // 新增 + _ = Task.Run(ReadRealTimeLoop); + _ = Task.Run(ReadAlarmLoop); // 报警循环 + _ = Task.Run(ReadDefrostStatusLoop); // 除霜相关状态 } catch (Exception ex) { @@ -137,6 +154,162 @@ namespace AciTester.ViewModels } } + 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; + }); + + 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) @@ -152,11 +325,9 @@ namespace AciTester.ViewModels try { - // 启动真空泵 await _plcService.WriteCoilAsync(_config.PumpCoil, true); IsPumpRunning = true; - // 倒计时 RemainingSeconds = SampleTimeSeconds; for (int i = 0; i < SampleTimeSeconds; i++) { @@ -165,7 +336,6 @@ namespace AciTester.ViewModels RemainingSeconds--; } - // 停止泵 await _plcService.WriteCoilAsync(_config.PumpCoil, false); IsPumpRunning = false; @@ -175,7 +345,6 @@ namespace AciTester.ViewModels catch (Exception ex) { MessageBox.Show($"测试异常: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - // 尝试停止泵 try { await _plcService.WriteCoilAsync(_config.PumpCoil, false); } catch { } IsPumpRunning = false; } @@ -198,16 +367,15 @@ namespace AciTester.ViewModels 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; // 滤膜 + fineMass += Stages[8].NetWeight; - double fpd = fineMass * 1000; // mg + double fpd = fineMass * 1000; double fpf = (fineMass / totalMass) * 100; CurrentResult = new TestResult @@ -218,8 +386,8 @@ namespace AciTester.ViewModels FineParticleFraction = fpf, Stages = Stages.ToList(), FlowRate = CurrentFlow, - Temperature = RealTime.Temperature, // 新增 - DifferentialPressure = RealTime.DifferentialPressure // 新增 + Temperature = RealTime.Temperature, + DifferentialPressure = RealTime.DifferentialPressure }; MessageBox.Show($"计算完成\n总质量: {totalMass:F4} g\n微细粒子剂量: {fpd:F2} mg\n微细粒子分数: {fpf:F2}%", @@ -245,61 +413,5 @@ namespace AciTester.ViewModels 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); - } - } - } } \ No newline at end of file diff --git a/Views/ConfigWindow.xaml b/Views/ConfigWindow.xaml index be8f194..74e877d 100644 --- a/Views/ConfigWindow.xaml +++ b/Views/ConfigWindow.xaml @@ -11,6 +11,7 @@ + @@ -41,7 +42,14 @@ - + + + + + + + +