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 @@
-
+
+
+
+
+
+
+
+
diff --git a/Views/MainWindow.xaml b/Views/MainWindow.xaml
index 180bb1b..c73a93b 100644
--- a/Views/MainWindow.xaml
+++ b/Views/MainWindow.xaml
@@ -4,12 +4,85 @@
xmlns:local="clr-namespace:AciTester.ViewModels"
Title="ACI测试系统 - 中国药典2025装置3"
Height="768" Width="1024"
- WindowStartupLocation="CenterScreen">
+ WindowStartupLocation="CenterScreen"
+ Background="#F0F2F5">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -17,127 +90,192 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
diff --git a/Views/MainWindow.xaml.cs b/Views/MainWindow.xaml.cs
index ad100f1..62d61e8 100644
--- a/Views/MainWindow.xaml.cs
+++ b/Views/MainWindow.xaml.cs
@@ -39,5 +39,9 @@ namespace AciTester.Views
var win = new ConfigWindow { DataContext = configVm, Owner = this };
win.ShowDialog();
}
+
+
+
+
}
}
\ No newline at end of file