This commit is contained in:
xyy
2026-03-19 20:40:54 +08:00
parent df022fc848
commit 9fd2f13b1c
13 changed files with 348 additions and 62 deletions

View File

@@ -11,6 +11,20 @@
public double PressureFactor { get; set; } = 1.0;
public double WetFlowFactor { get; set; } = 1.0;
public double DryFlowFactor { get; set; } = 1.0;
public ushort PressureRegisterStation1 { get; set; }
public ushort PressureRegisterStation2 { get; set; }
public ushort PressureRegisterStation3 { get; set; }
public ushort PressureModeRegister { get; set; }
public ushort PressCoil { get; set; }
public ushort StartCoil { get; set; }
public ushort EnableCoil { get; set; }
public ushort StopCoil { get; set; }
}
@@ -19,9 +33,13 @@
public interface IPlcService
{
Task<float> ReadPressureAsync();
Task<float> ReadPressureAsync(int stationId); // 按工位读取压力
Task<float> ReadWetFlowAsync();
Task<float> ReadDryFlowAsync();
Task WriteCoilAsync(ushort coilAddress, bool value); // 写线圈
Task WriteRegisterAsync(ushort registerAddress, ushort value); // 写寄存器
Task<bool> ReadCoilAsync(ushort coilAddress); // 新增:读取线圈状态
}
}

View File

@@ -15,7 +15,8 @@
new TestLiquid { Name = "水", SurfaceTension = 72.0 },
new TestLiquid { Name = "石油馏分", SurfaceTension = 30.0 },
new TestLiquid { Name = "乙醇", SurfaceTension = 22.3 },
new TestLiquid { Name = "液态石蜡", SurfaceTension = 34.7 }
new TestLiquid { Name = "液态石蜡", SurfaceTension = 34.7 },
new TestLiquid { Name = "BSD16", SurfaceTension = 16.0 } // 新增
};
}
}

View File

@@ -56,6 +56,41 @@ namespace MembranePoreTester.Communication
public async Task<float> ReadDryFlowAsync() =>
await ReadFloatAsync(_config.DryFlowRegister) * (float)_config.DryFlowFactor;
public async Task WriteCoilAsync(ushort coilAddress, bool value)
{
await EnsureConnectedAsync();
await _master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value);
}
public async Task WriteRegisterAsync(ushort registerAddress, ushort value)
{
await EnsureConnectedAsync();
await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
}
public async Task<bool> ReadCoilAsync(ushort coilAddress)
{
await EnsureConnectedAsync();
bool[] result = await _master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
return result[0];
}
// 新增读取压力(根据工位)
public async Task<float> ReadPressureAsync(int stationId)
{
ushort startAddress = stationId switch
{
1 => _config.PressureRegisterStation1,
2 => _config.PressureRegisterStation2,
3 => _config.PressureRegisterStation3,
_ => throw new ArgumentException("Invalid station")
};
return await ReadFloatAsync(startAddress);
}
public void Dispose()
{
_master?.Dispose();

View File

@@ -103,7 +103,7 @@ namespace MembranePoreTester.ViewModels
{
try
{
float rawPressure = await _plcService.ReadPressureAsync();
float rawPressure = await _plcService.ReadPressureAsync(StationId);
Record.BubblePointPressure = rawPressure * _plcConfig.PressureFactor;
OnPropertyChanged(nameof(Record.BubblePointPressure));
}
@@ -127,6 +127,13 @@ namespace MembranePoreTester.ViewModels
ReportGenerator.GenerateBubblePointReport(Record);
}
public void UpdateBubblePointPressure(double pressure)
{
Record.BubblePointPressure = pressure;
OnPropertyChanged(nameof(Record.BubblePointPressure));
OnPropertyChanged(nameof(MaxPoreSize)); // 最大孔径依赖于压力
}
private int _stationId;

View File

@@ -1,4 +1,9 @@
using System.Collections.ObjectModel;
using MembranePoreTester.Communication;
using System.Collections.ObjectModel;
using System.ComponentModel; // 用于 PropertyChanged
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace MembranePoreTester.ViewModels
{
@@ -6,24 +11,137 @@ namespace MembranePoreTester.ViewModels
{
public ObservableCollection<StationItem> Stations { get; } = new();
public class StationItem : ViewModelBase
{
private readonly IPlcService _plcService;
private readonly PlcConfiguration _plcConfig;
private bool _isPressing;
private string _pressButtonText = "加压";
private string _highLowPressure = "低压";
private bool _enableStatus; // M21 状态
public string Name { get; set; }
public BubblePointViewModel BubblePointVM { get; set; }
public PoreDistributionViewModel PoreDistributionVM { get; set; }
public int StationId { get; set; }
public string HighLowPressure
{
get => _highLowPressure;
set
{
if (SetProperty(ref _highLowPressure, value))
{
// 当选择变化时,写入 PLC 压力模式寄存器
Task.Run(async () => await WritePressureModeAsync(value));
}
}
}
public string PressButtonText
{
get => _pressButtonText;
set => SetProperty(ref _pressButtonText, value);
}
// 使能状态(只读)
public bool EnableStatus
{
get => _enableStatus;
private set => SetProperty(ref _enableStatus, value);
}
// 使能状态显示文本
public string EnableStatusText => EnableStatus ? "运行中" : "未启动";
// 使能状态显示颜色(绿色表示运行中,灰色表示未启动)
public string EnableStatusColor => EnableStatus ? "Green" : "Gray";
// 定时器,用于轮询 M21 状态
private System.Windows.Threading.DispatcherTimer _timer;
public ICommand PressCommand { get; }
//public ICommand BurstCommand { get; }
public ICommand StartCommand { get; }
public ICommand StopCommand { get; }
public ICommand EnableCommand { get; } // 备用,但可以使用复选框直接绑定
public StationItem()
{
_plcService = App.PlcService;
_plcConfig = App.PlcConfig;
PressCommand = new RelayCommand(async () => await TogglePressAsync());
//BurstCommand = new RelayCommand(async () => await ReadBurstPressureAsync());
StartCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StartCoil, true));
StopCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StopCoil, true));
// 启动定时器,每秒读取一次 M21 状态
_timer = new System.Windows.Threading.DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += async (s, e) => await ReadEnableStatusAsync();
_timer.Start();
}
private async Task TogglePressAsync()
{
_isPressing = !_isPressing;
await WriteCoilAsync(_plcConfig.PressCoil, _isPressing);
PressButtonText = _isPressing ? "停止加压" : "加压";
}
private async Task ReadEnableStatusAsync()
{
try
{
bool status = await _plcService.ReadCoilAsync(_plcConfig.EnableCoil); // 读取 M21
EnableStatus = status;
}
catch (Exception ex)
{
// 读取出错时保持原状态或显示错误
System.Diagnostics.Debug.WriteLine($"读取使能状态失败: {ex.Message}");
}
}
private async Task WriteCoilAsync(ushort coil, bool value)
{
try
{
await _plcService.WriteCoilAsync(coil, value);
}
catch (Exception ex)
{
MessageBox.Show($"写线圈失败: {ex.Message}");
}
}
private async Task WritePressureModeAsync(string mode)
{
ushort val = mode == "高压" ? (ushort)1 : (ushort)0;
try
{
await _plcService.WriteRegisterAsync(_plcConfig.PressureModeRegister, val);
}
catch (Exception ex)
{
MessageBox.Show($"写压力模式失败: {ex.Message}");
}
}
}
public MainViewModel()
{
for (int i = 1; i <= 3; i++)
{
Stations.Add(new StationItem
var station = new StationItem
{
Name = $"工位 {i}",
BubblePointVM = new BubblePointViewModel { StationId = i },
PoreDistributionVM = new PoreDistributionViewModel { StationId = i }
});
PoreDistributionVM = new PoreDistributionViewModel { StationId = i },
StationId = i
};
Stations.Add(station);
}
}
}
public class StationItem
{
public string Name { get; set; }
public BubblePointViewModel BubblePointVM { get; set; }
public PoreDistributionViewModel PoreDistributionVM { get; set; }
}
}

View File

@@ -130,7 +130,7 @@ namespace MembranePoreTester.ViewModels
try
{
// 始终读取压力
float rawPressure = await _plcService.ReadPressureAsync();
float rawPressure = await _plcService.ReadPressureAsync(StationId);
double pressure = rawPressure * _plcConfig.PressureFactor;
if (SelectedDataPoint != null)

View File

@@ -0,0 +1,97 @@
using System.Windows.Input;
using MembranePoreTester.Communication;
using System.Threading.Tasks;
using System.Windows;
namespace MembranePoreTester.ViewModels
{
public class StationViewModel : ViewModelBase
{
private readonly IPlcService _plcService;
private readonly PlcConfiguration _plcConfig;
private bool _isPressing;
private string _pressButtonText = "加压";
private string _highLowPressure = "低压";
private bool _enableChecked;
public int StationId { get; set; }
public BubblePointViewModel BubblePointVM { get; } = new();
public PoreDistributionViewModel PoreDistributionVM { get; } = new();
public string HighLowPressure
{
get => _highLowPressure;
set => SetProperty(ref _highLowPressure, value);
}
public string PressButtonText
{
get => _pressButtonText;
set => SetProperty(ref _pressButtonText, value);
}
public bool EnableChecked
{
get => _enableChecked;
set => SetProperty(ref _enableChecked, value);
}
public ICommand PressCommand { get; }
public ICommand BurstCommand { get; }
public ICommand StartCommand { get; }
public ICommand StopCommand { get; }
public ICommand EnableCommand { get; } // 用于使能复选框
public StationViewModel()
{
_plcService = App.PlcService;
_plcConfig = App.PlcConfig;
BubblePointVM.StationId = StationId;
PoreDistributionVM.StationId = StationId;
// 初始化按钮文字
_pressButtonText = "加压"; // 直接设置字段,避免触发属性通知循环
PressButtonText = "加压"; // 或者通过属性设置
System.Diagnostics.Debug.WriteLine($"工位{StationId} PressButtonText初始值: {PressButtonText}");
PressCommand = new RelayCommand(async () => await TogglePressAsync());
BurstCommand = new RelayCommand(async () => await ReadBurstPressureAsync());
StartCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StartCoil, true));
StopCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.StopCoil, true));
// 使能复选框点击时写入对应线圈
EnableCommand = new RelayCommand(async () => await WriteCoilAsync(_plcConfig.EnableCoil, EnableChecked));
}
private async Task TogglePressAsync()
{
_isPressing = !_isPressing;
await WriteCoilAsync(_plcConfig.PressCoil, _isPressing);
PressButtonText = _isPressing ? "停止加压" : "加压";
}
private async Task ReadBurstPressureAsync()
{
try
{
float pressure = await ((ModbusTcpPlcService)_plcService).ReadPressureAsync(StationId);
BubblePointVM.UpdateBubblePointPressure(pressure * _plcConfig.PressureFactor);
MessageBox.Show($"涨破压力: {pressure} {BubblePointVM.Record.PressureUnit}", "提示");
}
catch (Exception ex)
{
MessageBox.Show($"读取压力失败: {ex.Message}");
}
}
private async Task WriteCoilAsync(ushort coil, bool value)
{
try
{
await ((ModbusTcpPlcService)_plcService).WriteCoilAsync(coil, value);
}
catch (Exception ex)
{
MessageBox.Show($"写线圈失败: {ex.Message}");
}
}
}
}

View File

@@ -54,8 +54,8 @@
<Label Content="表面张力(mN/m):"/>
<TextBox Text="{Binding CustomSurfaceTension}" Width="100" Margin="5"/>
</StackPanel>
<Label Content="生产厂家:" Margin="5"/>
<TextBox Text="{Binding Record.LiquidManufacturer}" Margin="5"/>
<!--<Label Content="生产厂家:" Margin="5"/>
<TextBox Text="{Binding Record.LiquidManufacturer}" Margin="5"/>-->
</StackPanel>
</GroupBox>
@@ -63,7 +63,7 @@
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Record.BubblePointPressure}" Width="150" Margin="5"/>
<Button Content="读取PLC" Command="{Binding ReadPlcCommand}" Padding="10,5" Margin="5"/>
<Button Content="涨破" Command="{Binding ReadPlcCommand}" Padding="10,5" Margin="5"/>
<ComboBox ItemsSource="{Binding PressureUnits}" SelectedItem="{Binding Record.PressureUnit}" Width="80" Margin="5"/>
<Button Content="计算最大孔径" Command="{Binding CalculateCommand}" Padding="10,5" Margin="5"/>
</StackPanel>

View File

@@ -9,14 +9,14 @@
<Window.DataContext>
<viewModels:MainViewModel />
</Window.DataContext>
<DockPanel LastChildFill="True">
<DockPanel>
<!-- 全局历史记录按钮 -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5">
<Button Content="历史记录" Click="OpenHistory_Click" Padding="10,5" Margin="5"/>
</StackPanel>
<TabControl ItemsSource="{Binding Stations}">
<!-- 工位选项卡 -->
<TabControl x:Name="stationTabControl" ItemsSource="{Binding Stations}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
@@ -24,42 +24,43 @@
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- 每个工位内部再嵌套一个TabControl切换测试类型 -->
<TabControl>
<TabItem Header="泡点法测试最大孔径">
<local:BubblePointView DataContext="{Binding BubblePointVM}"/>
</TabItem>
<TabItem Header="孔分布测试">
<local:PoreDistributionView DataContext="{Binding PoreDistributionVM}"/>
</TabItem>
</TabControl>
<DockPanel>
<!-- 工位控制栏 -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5">
<ComboBox SelectedItem="{Binding HighLowPressure}" Width="80" Margin="5">
<ComboBoxItem IsSelected="True">低压</ComboBoxItem>
<ComboBoxItem>高压</ComboBoxItem>
</ComboBox>
<Button Content="启动" Command="{Binding StartCommand}" Width="80" Margin="5"/>
<Button Content="停止" Command="{Binding StopCommand}" Width="80" Margin="5"/>
<Button Content="{Binding PressButtonText}" Command="{Binding PressCommand}" Width="80" Margin="5"/>
<!--<Button Content="涨破" Command="{Binding BurstCommand}" Width="80" Margin="5"/>-->
<!--<CheckBox Content="使能" IsChecked="{Binding EnableChecked}" Margin="5"/>-->
<!-- 原来的复选框 -->
<!-- <CheckBox Content="使能" IsChecked="{Binding EnableChecked}" Margin="5"/> -->
<!-- 替换为状态指示灯 -->
<StackPanel Orientation="Horizontal" Margin="5">
<Ellipse Width="12" Height="12" Fill="{Binding EnableStatusColor}" Margin="2"/>
<TextBlock Text="{Binding EnableStatusText}" VerticalAlignment="Center"/>
</StackPanel>
</StackPanel>
<!-- 测试类型选项卡 -->
<TabControl>
<TabItem Header="泡点法测试最大孔径">
<local:BubblePointView DataContext="{Binding BubblePointVM}"/>
</TabItem>
<TabItem Header="孔分布测试">
<local:PoreDistributionView DataContext="{Binding PoreDistributionVM}"/>
</TabItem>
</TabControl>
</DockPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
<!--<Window x:Class="MembranePoreTester.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MembranePoreTester.Views"
Title="膜孔径测试系统 (GB/T 32361-2015)"
Width="1024" Height="768"
WindowStartupLocation="CenterScreen">
<TabControl>
<TabItem Header="泡点法测试最大孔径">
<local:BubblePointView/>
</TabItem>
<TabItem Header="孔分布测试">
<local:PoreDistributionView/>
</TabItem>
</TabControl>
</Window>-->

View File

@@ -35,8 +35,9 @@ namespace MembranePoreTester.Views
var mainVM = DataContext as MainViewModel;
if (mainVM == null) return;
// 获取当前选中的工位(可选
int currentStation = 1; // 可获取当前Tab索引+1
// 获取当前选中的工位索引(假设选项卡控件是 TabControl名称为 stationTabControl
// 需要在 XAML 中为 TabControl 设置 x:Name="stationTabControl"
int currentStation = stationTabControl.SelectedIndex + 1; // 假设索引从0开始
var historyWin = new HistoryWindow { SelectedStation = currentStation };
historyWin.ShowDialog();
}

View File

@@ -1,12 +1,19 @@
{
"PlcSettings": {
"IpAddress": "192.168.1.100",
"IpAddress": "192.168.1.10",
"Port": 502,
"SlaveId": 1,
"PressureRegister": 0,
"PressureRegisterStation1": 6,
"PressureRegisterStation2": 8,
"PressureRegisterStation3": 10,
"PressureModeRegister": 200, // 高压/低压选择寄存器0=低压1=高压)
"PressCoil": 100, // 加压线圈M100
"StartCoil": 20, // 启动线圈M20
"EnableCoil": 21, // 使能线圈M21
"StopCoil": 7, // 停止线圈M7
"PressureFactor": 1.0,
"WetFlowRegister": 2,
"DryFlowRegister": 4,
"PressureFactor": 1.0,
"WetFlowFactor": 1.0,
"DryFlowFactor": 1.0
}

Binary file not shown.

View File

@@ -16,6 +16,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
<PackageReference Include="NModbus4.NetCore" Version="4.0.0" />
<PackageReference Include="SunnyUI" Version="3.9.3" />
</ItemGroup>
<ItemGroup>