This commit is contained in:
xyy
2026-06-16 11:53:02 +08:00
parent 24763126ef
commit 2dadfe3861
11 changed files with 541 additions and 180 deletions

View File

@@ -8,5 +8,6 @@
<converters:BoolToStringConverter x:Key="BoolToStringConverter"/>
<converters:InverseBoolConverter x:Key="InverseBoolConverter"/>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<converters:BoolToYesNoConverter x:Key="BoolToYesNoConverter"/>
</Application.Resources>
</Application>

View File

@@ -11,4 +11,6 @@ namespace AciTester
{
}
}

View File

@@ -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();
}
}

View File

@@ -21,5 +21,8 @@ namespace AciTester.Models
[ObservableProperty]
private float flowHighLimit = 32.0f; // D1332 高限
[ObservableProperty]
private float temperatureProtect = 40.0f; // D1084
}
}

View File

@@ -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
}
/// <summary>
@@ -97,5 +111,8 @@ namespace AciTester.Models
void Dispose();
bool IsConnected { get; } // 新增
Task<int> ReadInt32Async(ushort startAddress);
Task WriteInt32Async(ushort startAddress, int value);
}
}

View File

@@ -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
}
}

View File

@@ -188,6 +188,24 @@ namespace AciTester.Services
_tcpClient?.Close();
_tcpClient?.Dispose();
}
public async Task<int> 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 });
}
}

View File

@@ -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<StageData>
{
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<bool>(WriteConstantTempStartAsync);
WriteDefrostStartCommand = new AsyncRelayCommand<bool>(WriteDefrostStartAsync);
WriteDefrostTempSetCommand = new AsyncRelayCommand<float>(WriteDefrostTempSetAsync);
WriteDefrostTimeSetCommand = new AsyncRelayCommand<int>(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<bool> WriteConstantTempStartCommand { get; }
public IAsyncRelayCommand<bool> WriteDefrostStartCommand { get; }
public IAsyncRelayCommand<float> WriteDefrostTempSetCommand { get; }
public IAsyncRelayCommand<int> 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);
}
}
}
}

View File

@@ -11,6 +11,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<GroupBox Header="流量校准" Grid.Row="0">
@@ -41,7 +42,14 @@
<TextBox Text="{Binding Calibration.ImpactorPressureCalibration}" Width="100"/>
</StackPanel>
</GroupBox>
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10">
<GroupBox Header="温度保护" Grid.Row="4" Margin="0,10">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="温度保护值(℃):" Width="120"/>
<TextBox Text="{Binding Calibration.TemperatureProtect}" Width="100"/>
</StackPanel>
</GroupBox>
<StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10">
<Button Command="{Binding LoadConfigCommand}" Content="读取" Width="80" Margin="5"/>
<Button Command="{Binding SaveConfigCommand}" Content="保存" Width="80" Margin="5"/>
<Button Click="CloseWindow" Content="关闭" Width="80" Margin="5"/>

View File

@@ -4,12 +4,85 @@
xmlns:local="clr-namespace:AciTester.ViewModels"
Title="ACI测试系统 - 中国药典2025装置3"
Height="768" Width="1024"
WindowStartupLocation="CenterScreen">
WindowStartupLocation="CenterScreen"
Background="#F0F2F5">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid Margin="10">
<Window.Resources>
<!-- 按钮样式 -->
<Style TargetType="Button">
<Setter Property="Height" Value="40"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Background" Value="#2C7DA0"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#1F5E7A"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#CCCCCC"/>
<Setter Property="Foreground" Value="#666666"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#1F5E7A"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- 文本框样式 -->
<Style TargetType="TextBox">
<Setter Property="Height" Value="35"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderBrush" Value="#CCCCCC"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<!-- 切换按钮样式 -->
<Style TargetType="ToggleButton">
<Setter Property="Height" Value="40"/>
<Setter Property="Width" Value="80"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Background" Value="#E74C3C"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#27AE60"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- DataGrid 样式 -->
<Style TargetType="DataGrid">
<Setter Property="FontSize" Value="13"/>
<Setter Property="RowHeight" Value="30"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
<Setter Property="AlternatingRowBackground" Value="#F9F9F9"/>
</Style>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Height" Value="35"/>
<Setter Property="Background" Value="#E9F0F5"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
@@ -17,127 +90,192 @@
</Grid.RowDefinitions>
<!-- 状态栏 (Row 0) -->
<StatusBar Grid.Row="0">
<StatusBarItem>
<TextBlock Text="状态:"/>
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="{Binding ConnectionStatus}" Foreground="{Binding IsConnected, Converter={StaticResource BoolToColorConverter}}"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Text="流量:"/>
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="{Binding CurrentFlow, StringFormat='{}{0:F2} L/min'}"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Text="泵状态:"/>
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="{Binding IsPumpRunning, Converter={StaticResource BoolToStringConverter}}"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Text="倒计时:" Visibility="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter}}"/>
</StatusBarItem>
<StatusBarItem>
<TextBlock Text="{Binding RemainingSeconds, StringFormat='{}{0} s'}" Visibility="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter}}"/>
</StatusBarItem>
</StatusBar>
<Border Grid.Row="0" Background="#2C3E50" CornerRadius="5" Padding="10" Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="状态:" Foreground="White" FontSize="14" VerticalAlignment="Center"/>
<TextBlock Text="{Binding ConnectionStatus}" Foreground="{Binding IsConnected, Converter={StaticResource BoolToColorConverter}}" FontSize="14" FontWeight="Bold" Margin="5,0,20,0"/>
<Separator Background="White" Width="1" Margin="5,0"/>
<TextBlock Text="流量:" Foreground="White" FontSize="14" Margin="15,0,0,0"/>
<TextBlock Text="{Binding CurrentFlow, StringFormat='{}{0:F2} L/min'}" Foreground="White" FontSize="14" FontWeight="Bold" Margin="5,0,20,0"/>
<Separator Background="White" Width="1" Margin="5,0"/>
<TextBlock Text="泵状态:" Foreground="White" FontSize="14" Margin="15,0,0,0"/>
<TextBlock Text="{Binding IsPumpRunning, Converter={StaticResource BoolToStringConverter}}" Foreground="White" FontSize="14" FontWeight="Bold" Margin="5,0,20,0"/>
<Separator Background="White" Width="1" Margin="5,0"/>
<TextBlock Text="倒计时:" Foreground="White" FontSize="14" Margin="15,0,0,0" Visibility="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TextBlock Text="{Binding RemainingSeconds, StringFormat='{}{0} s'}" Foreground="Orange" FontSize="14" FontWeight="Bold" Margin="5,0,0,0" Visibility="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter}}"/>
</StackPanel>
</Border>
<!-- 实时监测参数 (Row 1) -->
<GroupBox Header="实时监测参数" Grid.Row="1" Margin="0,5">
<StackPanel>
<Grid>
<GroupBox Header="实时监测参数" Grid.Row="1" Margin="0,5" FontWeight="Bold" FontSize="14">
<StackPanel Margin="10">
<Grid Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Vertical">
<TextBlock Text="流量 (L/min)" FontWeight="Bold"/>
<TextBlock Text="{Binding RealTime.CalibratedFlow, StringFormat='{}{0:F2}'}" FontSize="18" Foreground="Blue"/>
<TextBlock Text="(目标: 28.3)" FontSize="10" Foreground="Gray"/>
<Border Grid.Column="0" Background="#E3F2FD" CornerRadius="8" Padding="10" Margin="5">
<StackPanel>
<TextBlock Text="流量 (L/min)" FontWeight="Bold" FontSize="14" TextAlignment="Center"/>
<TextBlock Text="{Binding RealTime.CalibratedFlow, StringFormat='{}{0:F2}'}" FontSize="28" Foreground="#1565C0" TextAlignment="Center"/>
<TextBlock Text="(目标: 28.3)" FontSize="12" Foreground="Gray" TextAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock Text="温度 (℃)" FontWeight="Bold"/>
<TextBlock Text="{Binding RealTime.Temperature, StringFormat='{}{0:F1}'}" FontSize="18"/>
</Border>
<Border Grid.Column="1" Background="#FFF3E0" CornerRadius="8" Padding="10" Margin="5">
<StackPanel>
<TextBlock Text="温度 (℃)" FontWeight="Bold" FontSize="14" TextAlignment="Center"/>
<TextBlock Text="{Binding RealTime.Temperature, StringFormat='{}{0:F1}'}" FontSize="28" Foreground="#E65100" TextAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Vertical">
<TextBlock Text="泵端压力 (kPa)" FontWeight="Bold"/>
<TextBlock Text="{Binding RealTime.PumpPressure, StringFormat='{}{0:F2}'}" FontSize="18"/>
</Border>
<Border Grid.Column="2" Background="#E8F5E9" CornerRadius="8" Padding="10" Margin="5">
<StackPanel>
<TextBlock Text="泵端压力 (kPa)" FontWeight="Bold" FontSize="14" TextAlignment="Center"/>
<TextBlock Text="{Binding RealTime.PumpPressure, StringFormat='{}{0:F2}'}" FontSize="28" Foreground="#2E7D32" TextAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Column="3" Orientation="Vertical">
<TextBlock Text="撞击器端压力 (kPa)" FontWeight="Bold"/>
<TextBlock Text="{Binding RealTime.ImpactorPressure, StringFormat='{}{0:F2}'}" FontSize="18"/>
</Border>
<Border Grid.Column="3" Background="#FCE4EC" CornerRadius="8" Padding="10" Margin="5">
<StackPanel>
<TextBlock Text="撞击器端压力 (kPa)" FontWeight="Bold" FontSize="14" TextAlignment="Center"/>
<TextBlock Text="{Binding RealTime.ImpactorPressure, StringFormat='{}{0:F2}'}" FontSize="28" Foreground="#C2185B" TextAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
<Separator Margin="0,5"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="压差: " FontWeight="Bold"/>
<TextBlock Text="{Binding RealTime.DifferentialPressure, StringFormat='{}{0:F2} kPa'}" Foreground="DarkRed"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10">
<TextBlock Text="压差: " FontWeight="Bold" FontSize="16"/>
<TextBlock Text="{Binding RealTime.DifferentialPressure, StringFormat='{}{0:F2} kPa'}" FontSize="16" FontWeight="Bold" Foreground="#D32F2F"/>
</StackPanel>
</StackPanel>
</GroupBox>
<!-- 主内容 (Row 2) -->
<Grid Grid.Row="2" Margin="0,10">
<!-- 空调与除霜控制 (Row 2) -->
<GroupBox Header="空调与除霜控制" Grid.Row="2" Margin="0,5" FontWeight="Bold" FontSize="14">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="#FFF9C4" CornerRadius="8" Padding="10" Margin="5">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="空调启动倒计时:" FontSize="14" Width="140" VerticalAlignment="Center"/>
<TextBlock Text="{Binding RealTime.AcStartupCountdown, StringFormat='{}{0} 秒'}" FontSize="24" FontWeight="Bold" Foreground="#F57C00"/>
</StackPanel>
<Separator Margin="0,10"/>
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="恒温启动" Width="80" FontSize="14" VerticalAlignment="Center"/>
<ToggleButton IsChecked="{Binding RealTime.ConstantTempStart, Mode=TwoWay}"
Command="{Binding WriteConstantTempStartCommand}"
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
IsEnabled="{Binding ConstantTempStartEnabled}"/>
<TextBlock Text="(除霜时禁用)" FontSize="11" Foreground="Gray" Margin="10,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="除霜启动" Width="80" FontSize="14" VerticalAlignment="Center"/>
<ToggleButton IsChecked="{Binding RealTime.DefrostStart, Mode=TwoWay}"
Command="{Binding WriteDefrostStartCommand}"
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}"/>
<TextBlock Text="{Binding RealTime.DefrostStart, Converter={StaticResource BoolToYesNoConverter}}" Foreground="Red" FontWeight="Bold" Margin="10,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
</StackPanel>
</Border>
<Border Grid.Column="1" Background="#E0F2F1" CornerRadius="8" Padding="10" Margin="5">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="除霜温度设置(℃)" Width="130" FontSize="14" VerticalAlignment="Center"/>
<TextBox Text="{Binding RealTime.DefrostTempSet, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="除霜时间设置(秒)" Width="130" FontSize="14" VerticalAlignment="Center"/>
<TextBox Text="{Binding RealTime.DefrostTimeSet, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="除霜计时" Width="130" FontSize="14" VerticalAlignment="Center"/>
<TextBlock Text="{Binding RealTime.DefrostMinute, StringFormat='{}{0}分'}" FontSize="18" FontWeight="Bold" Foreground="#00796B" Margin="0,0,10,0"/>
<TextBlock Text="{Binding RealTime.DefrostSecond, StringFormat='{}{0}秒'}" FontSize="18" FontWeight="Bold" Foreground="#00796B"/>
</StackPanel>
</StackPanel>
</Border>
</Grid>
</GroupBox>
<!-- 主内容 (Row 3) -->
<Grid Grid.Row="3" Margin="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="280"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧控制区 -->
<StackPanel Grid.Column="0" Margin="5">
<GroupBox Header="通讯控制">
<Border Grid.Column="0" Background="White" CornerRadius="8" Padding="10" Margin="0,0,10,0" BorderBrush="#DDDDDD" BorderThickness="1">
<StackPanel>
<Button Command="{Binding ConnectCommand}" Content="连接PLC" Width="100" Margin="5"/>
<Button Command="{Binding DisconnectCommand}" Content="断开连接" Width="100" Margin="5"/>
<GroupBox Header="通讯控制" Margin="0,0,0,10">
<StackPanel>
<Button Command="{Binding ConnectCommand}" Content="连接PLC" Height="45"/>
<Button Command="{Binding DisconnectCommand}" Content="断开连接" Height="45"/>
</StackPanel>
</GroupBox>
<GroupBox Header="采样参数" Margin="0,10">
<GroupBox Header="采样参数" Margin="0,0,0,10">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="采样时间(秒):" VerticalAlignment="Center" Width="100"/>
<TextBox Text="{Binding SampleTimeSeconds}" Width="60" IsEnabled="{Binding IsTesting, Converter={StaticResource InverseBoolConverter}}"/>
<StackPanel Orientation="Horizontal" Margin="0,5">
<TextBlock Text="采样时间(秒):" Width="100" VerticalAlignment="Center" FontSize="14"/>
<TextBox Text="{Binding SampleTimeSeconds}" Width="80" IsEnabled="{Binding IsTesting, Converter={StaticResource InverseBoolConverter}}"/>
</StackPanel>
<Button Command="{Binding StartTestCommand}" Content="开始测试" Width="100" Margin="5"
IsEnabled="{Binding IsTesting, Converter={StaticResource InverseBoolConverter}}"/>
<TextBlock Text="测试进行中..." Visibility="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Button Command="{Binding StartTestCommand}" Content="开始测试" Height="45" Margin="0,10,0,0" IsEnabled="{Binding IsTesting, Converter={StaticResource InverseBoolConverter}}"/>
<TextBlock Text="测试进行中..." Visibility="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter}}" Foreground="Orange" FontWeight="Bold" FontSize="14" Margin="0,5,0,0" HorizontalAlignment="Center"/>
</StackPanel>
</GroupBox>
<GroupBox Header="数据分析">
<StackPanel>
<Button Command="{Binding CalculateCommand}" Content="计算结果" Width="100" Margin="5"/>
<Button Command="{Binding ExportReportCommand}" Content="导出报告" Width="100" Margin="5"/>
<Button Command="{Binding CalculateCommand}" Content="计算结果" Height="45"/>
<Button Command="{Binding ExportReportCommand}" Content="导出报告" Height="45"/>
</StackPanel>
</GroupBox>
</StackPanel>
</Border>
<!-- 右侧称重数据表格 -->
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Auto">
<DataGrid ItemsSource="{Binding Stages}" AutoGenerateColumns="False" CanUserAddRows="False">
<Border Grid.Column="1" Background="White" CornerRadius="8" Padding="10" BorderBrush="#DDDDDD" BorderThickness="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<DataGrid ItemsSource="{Binding Stages}" AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="False">
<DataGrid.Columns>
<DataGridTextColumn Header="层级" Binding="{Binding StageName}" IsReadOnly="True"/>
<DataGridTextColumn Header="截止直径(μm)" Binding="{Binding CutoffDiameter, StringFormat='{}{0:F1}'}" IsReadOnly="True"/>
<DataGridTextColumn Header="测前质量(g)" Binding="{Binding InitialWeight, StringFormat='{}{0:F4}'}"/>
<DataGridTextColumn Header="测后质量(g)" Binding="{Binding FinalWeight, StringFormat='{}{0:F4}'}"/>
<DataGridTextColumn Header="净重(g)" Binding="{Binding NetWeight, StringFormat='{}{0:F6}'}" IsReadOnly="True"/>
<DataGridTextColumn Header="层级" Binding="{Binding StageName}" Width="80" IsReadOnly="True"/>
<DataGridTextColumn Header="截止直径(μm)" Binding="{Binding CutoffDiameter, StringFormat='{}{0:F1}'}" Width="100" IsReadOnly="True"/>
<DataGridTextColumn Header="测前质量(g)" Binding="{Binding InitialWeight, StringFormat='{}{0:F4}'}" Width="120">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="测后质量(g)" Binding="{Binding FinalWeight, StringFormat='{}{0:F4}'}" Width="120">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="净重(g)" Binding="{Binding NetWeight, StringFormat='{}{0:F6}'}" Width="120" IsReadOnly="True">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</Border>
</Grid>
<!-- 结果显示 (Row 3) -->
<Border Grid.Row="3" BorderBrush="Gray" BorderThickness="1" Margin="0,5,0,0" Padding="5">
<!-- 结果显示 (Row 4) -->
<Border Grid.Row="4" Background="#E8F5E9" CornerRadius="8" Padding="12" Margin="0,10,0,0" BorderBrush="#A5D6A7" BorderThickness="1">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="微细粒子剂量(FPD): " FontWeight="Bold"/>
<TextBlock Text="{Binding CurrentResult.FineParticleDose, StringFormat='{}{0:F2} mg'}" Margin="5,0,20,0"/>
<TextBlock Text="微细粒子分数(FPF): " FontWeight="Bold"/>
<TextBlock Text="{Binding CurrentResult.FineParticleFraction, StringFormat='{}{0:F2} %'}"/>
<TextBlock Text="微细粒子剂量(FPD): " FontWeight="Bold" FontSize="16"/>
<TextBlock Text="{Binding CurrentResult.FineParticleDose, StringFormat='{}{0:F2} mg'}" FontSize="16" FontWeight="Bold" Foreground="#1565C0" Margin="5,0,30,0"/>
<TextBlock Text="微细粒子分数(FPF): " FontWeight="Bold" FontSize="16"/>
<TextBlock Text="{Binding CurrentResult.FineParticleFraction, StringFormat='{}{0:F2} %'}" FontSize="16" FontWeight="Bold" Foreground="#1565C0"/>
</StackPanel>
</Border>
</Grid>

View File

@@ -39,5 +39,9 @@ namespace AciTester.Views
var win = new ConfigWindow { DataContext = configVm, Owner = this };
win.ShowDialog();
}
}
}