diff --git a/App.xaml b/App.xaml
index e1da920..c50692e 100644
--- a/App.xaml
+++ b/App.xaml
@@ -3,5 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
+
+
\ No newline at end of file
diff --git a/Converters/BoolToStringConverter.cs b/Converters/BoolToStringConverter.cs
new file mode 100644
index 0000000..480d5da
--- /dev/null
+++ b/Converters/BoolToStringConverter.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MembranePoreTester.Converters
+{
+ public class BoolToStringConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return (value is bool b && b) ? "开" : "关";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value?.ToString() == "开";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Helpers/ExportHelper.cs b/Helpers/ExportHelper.cs
index 510ee05..a771ade 100644
--- a/Helpers/ExportHelper.cs
+++ b/Helpers/ExportHelper.cs
@@ -3,6 +3,11 @@ using System.IO;
public static class ExportHelper
{
+ static ExportHelper()
+ {
+ // 设置许可上下文为非商业用途(开源项目/个人学习)
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+ }
public static void ExportBubblePoint(BubblePointEntity entity, string filePath)
{
using var package = new ExcelPackage();
diff --git a/Helpers/ITestRecord.cs b/Helpers/ITestRecord.cs
index 5bd3f6e..f688397 100644
--- a/Helpers/ITestRecord.cs
+++ b/Helpers/ITestRecord.cs
@@ -1,57 +1,141 @@
public interface ITestRecord
{
+ /// 主键ID,自增
int Id { get; set; }
+
+ /// 工位编号(1~3)
int StationId { get; set; }
+
+ /// 测试日期时间
DateTime TestDate { get; set; }
+
+ /// 测试人员姓名
string Tester { get; set; }
// 公共字段
}
+///
+/// 泡点法测试记录实体,用于数据库存储。
+/// 对应 GB/T 32361-2015 标准中的最大孔径测试记录。
+///
public class BubblePointEntity : ITestRecord
{
+ /// 主键ID,自增
public int Id { get; set; }
+
+ /// 工位编号(1~3)
public int StationId { get; set; }
+
+ /// 测试日期时间
public DateTime TestDate { get; set; }
- public string Tester { get; set; }
- public string SampleType { get; set; }
- public string SampleSpec { get; set; }
- public double RoomTemperature { get; set; }
- public double SoakingTime { get; set; }
- public string LiquidName { get; set; }
- public double LiquidSurfaceTension { get; set; }
- public string LiquidManufacturer { get; set; }
- public double BubblePointPressure { get; set; }
- public string PressureUnit { get; set; }
- public double MaxPoreSize { get; set; }
+
+ /// 测试人员姓名
+ public string? Tester { get; set; }
+
+ /// 膜类型(平板膜/中空纤维膜)
+ public string? SampleType { get; set; }
+
+ /// 样品规格(如直径、厚度等)
+ public string? SampleSpec { get; set; }
+
+ /// 测试时室温(°C)
+ public double? RoomTemperature { get; set; }
+
+ /// 膜在测试液体中的浸润时间(小时)
+ public double? SoakingTime { get; set; }
+
+ /// 测试液体名称(如“水”、“乙醇”等)
+ public string? LiquidName { get; set; }
+
+ /// 测试液体的表面张力(mN/m,25°C)
+ public double? LiquidSurfaceTension { get; set; }
+
+ /// 测试液体生产厂家(可选)
+ public string? LiquidManufacturer { get; set; }
+
+ /// 泡点压力数值
+ public double? BubblePointPressure { get; set; }
+
+ /// 泡点压力单位(Pa / cmHg / psi)
+ public string? PressureUnit { get; set; }
+
+ /// 计算得出的最大孔径(μm)
+ public double? MaxPoreSize { get; set; }
}
+///
+/// 孔分布测试记录实体,用于数据库存储。
+/// 对应 GB/T 32361-2015 标准中的平均流量法测试记录。
+///
public class PoreDistributionEntity : ITestRecord
{
+ /// 主键ID,自增
public int Id { get; set; }
+
+ /// 工位编号(1~3)
public int StationId { get; set; }
+
+ /// 测试日期时间
public DateTime TestDate { get; set; }
+
+ /// 测试人员姓名
public string Tester { get; set; }
+
+ /// 膜类型(平板膜/中空纤维膜)
public string SampleType { get; set; }
+
+ /// 样品规格(如直径、厚度等)
public string SampleSpec { get; set; }
+
+ /// 测试时室温(°C)
public double RoomTemperature { get; set; }
+
+ /// 膜在测试液体中的浸润时间(小时)
public double SoakingTime { get; set; }
+
+ /// 测试液体名称(如“水”、“乙醇”等)
public string LiquidName { get; set; }
+
+ /// 测试液体的表面张力(mN/m,25°C)
public double LiquidSurfaceTension { get; set; }
+
+ /// 测试液体生产厂家(可选)
public string LiquidManufacturer { get; set; }
+
+ /// 压力单位(Pa / cmHg / psi)
public string PressureUnit { get; set; }
+
+ /// 泡点压力(可选,用于记录)
public double BubblePointPressure { get; set; }
+
+ /// 计算得出的平均孔径(μm)
public double AveragePoreSize { get; set; }
- // 导航属性:数据点
+
+ /// 导航属性:该测试记录对应的所有压力-流量数据点
public List DataPoints { get; set; }
}
+///
+/// 孔分布测试的数据点实体,存储每个压力点对应的湿膜和干膜流量。
+/// 与 PoreDistributionEntity 构成一对多的关系。
+///
public class DataPointEntity
{
+ /// 主键ID,自增
public int Id { get; set; }
+
+ /// 关联的孔分布测试记录ID
public int PoreDistributionId { get; set; }
+
+ /// 测试压力值,单位由所属记录的 PressureUnit 决定
public double Pressure { get; set; }
+
+ /// 湿膜流量(L/min)
public double WetFlow { get; set; }
+
+ /// 干膜流量(L/min)
public double DryFlow { get; set; }
+ /// 导航属性:所属的孔分布测试记录
public PoreDistributionEntity PoreDistribution { get; set; }
}
\ No newline at end of file
diff --git a/Models/BubblePointRecord.cs b/Models/BubblePointRecord.cs
index 6551047..eb6ef0f 100644
--- a/Models/BubblePointRecord.cs
+++ b/Models/BubblePointRecord.cs
@@ -45,7 +45,7 @@ namespace MembranePoreTester.Models
}
}
}
- public string PressureUnit { get; set; } // Pa/cmHg/psi
+ public string PressureUnit { get; set; } = "Pa"; // Pa/cmHg/psi
public DateTime TestDate { get; set; } = DateTime.Now;
public string Tester { get; set; }
diff --git a/Models/PoreDistributionRecord.cs b/Models/PoreDistributionRecord.cs
index 8646c80..b29735a 100644
--- a/Models/PoreDistributionRecord.cs
+++ b/Models/PoreDistributionRecord.cs
@@ -57,7 +57,7 @@ namespace MembranePoreTester.Models
///
/// 测试人员姓名。
///
- public string Tester { get; set; }
+ public string Tester { get; set; } = "系统测试员";
// ---------- 计算结果(由计算逻辑填充) ----------
diff --git a/ViewModels/BubblePointViewModel.cs b/ViewModels/BubblePointViewModel.cs
index 80c037c..ab46089 100644
--- a/ViewModels/BubblePointViewModel.cs
+++ b/ViewModels/BubblePointViewModel.cs
@@ -158,16 +158,26 @@ namespace MembranePoreTester.ViewModels
});
}
-
private async Task ReadPlcAsync()
{
- if (IsDisposed) return;
-
- Record.BubblePointPressure = Record.BubbleCurrentPressure;
-
- OnPropertyChanged(nameof(Record.BubblePointPressure));
+ try
+ {
+ double rawPressure = await _plcService.ReadPressureAsync(StationId);
+ rawPressure = Math.Round((double)rawPressure, 2);
+ double pressure = rawPressure * _plcConfig.PressureFactor;
+ // 更新当前压力显示
+ Record.BubbleCurrentPressure = pressure;
+ // 同时将泡点压力设为该值(符合涨破测试逻辑)
+ Record.BubblePointPressure = pressure;
+ OnPropertyChanged(nameof(Record.BubblePointPressure));
+ OnPropertyChanged(nameof(Record.BubbleCurrentPressure));
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
}
@@ -207,12 +217,12 @@ namespace MembranePoreTester.ViewModels
{
StationId = StationId,
TestDate = Record.TestDate,
- Tester = Record.Tester,
- SampleType = Record.SampleType,
- SampleSpec = Record.SampleSpec,
+ Tester = Record.Tester ?? "系统操作员",
+ SampleType = Record.SampleType ?? "缺省值",
+ SampleSpec = Record.SampleSpec ?? "缺省值",
RoomTemperature = Record.RoomTemperature,
SoakingTime = Record.SoakingTime,
- LiquidName = Record.Liquid?.Name,
+ LiquidName = Record.Liquid?.Name ?? "缺省值",
LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0,
LiquidManufacturer = Record.LiquidManufacturer,
BubblePointPressure = Record.BubblePointPressure,
@@ -232,12 +242,12 @@ namespace MembranePoreTester.ViewModels
Record.SampleType = entity.SampleType;
Record.SampleSpec = entity.SampleSpec;
- Record.RoomTemperature = entity.RoomTemperature;
- Record.SoakingTime = entity.SoakingTime;
+ Record.RoomTemperature = entity.RoomTemperature ?? 0;
+ Record.SoakingTime = entity.SoakingTime ?? 0;
Record.Liquid = TestLiquid.Predefined.FirstOrDefault(l => l.Name == entity.LiquidName)
- ?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension };
+ ?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension ?? 0 };
Record.LiquidManufacturer = entity.LiquidManufacturer;
- Record.BubblePointPressure = entity.BubblePointPressure;
+ Record.BubblePointPressure = entity.BubblePointPressure ?? 0;
Record.PressureUnit = entity.PressureUnit;
Record.TestDate = entity.TestDate;
Record.Tester = entity.Tester;
@@ -316,8 +326,23 @@ namespace MembranePoreTester.ViewModels
};
if (saveFileDialog.ShowDialog() == true)
{
- // 转换为Entity后导出
- var entity = new BubblePointEntity { /* 从Record复制 */ };
+ // 将 Record 转换为 Entity
+ var entity = new BubblePointEntity
+ {
+ StationId = this.StationId,
+ TestDate = Record.TestDate,
+ Tester = Record.Tester,
+ SampleType = Record.SampleType,
+ SampleSpec = Record.SampleSpec,
+ RoomTemperature = Record.RoomTemperature,
+ SoakingTime = Record.SoakingTime,
+ LiquidName = Record.Liquid?.Name,
+ LiquidSurfaceTension = Record.Liquid?.SurfaceTension,
+ LiquidManufacturer = Record.LiquidManufacturer,
+ BubblePointPressure = Record.BubblePointPressure,
+ PressureUnit = Record.PressureUnit,
+ MaxPoreSize = Record.MaxPoreSize
+ };
ExportHelper.ExportBubblePoint(entity, saveFileDialog.FileName);
MessageBox.Show("导出成功");
}
diff --git a/ViewModels/PoreDistributionViewModel.cs b/ViewModels/PoreDistributionViewModel.cs
index b16d0b5..9f6f7a9 100644
--- a/ViewModels/PoreDistributionViewModel.cs
+++ b/ViewModels/PoreDistributionViewModel.cs
@@ -91,59 +91,44 @@ namespace MembranePoreTester.ViewModels
private async void AutoCollectTimer_Tick(object sender, EventArgs e)
{
- if (!IsActive) return; // 不在当前标签页,跳过采集
- await SafeExecuteAsync($"AutoCollect_Station{StationId}", async () =>
+ if (!IsActive) return;
+
+ try
{
- try
+ // 1. 读取当前压力
+ float rawPressure = await _plcService.ReadPressureAsync(StationId);
+ double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2);
+
+ // 2. 读取当前模式对应的流量
+ double flow = 0;
+ if (TestMode.Contains("湿膜"))
{
- // 1. 读取当前压力
- float rawPressure = await _plcService.ReadPressureAsync(StationId);
- double pressure = Math.Round(rawPressure, 2);
-
- double flow = 0;
- if (TestMode == "湿膜")
- {
- float rawFlow = await _plcService.ReadWetFlowAsync();
- flow = rawFlow;
- }
- else
- {
- float rawFlow = await _plcService.ReadDryFlowAsync();
- flow = rawFlow;
- }
-
- flow = Math.Round(flow, 2);
- // 3. 在 DataPoints 中查找是否存在相同压力的行(允许微小误差)
- var existing = Record.DataPoints.FirstOrDefault(p => Math.Abs(p.Pressure - pressure) < 0.001);
- if (existing != null)
- {
- // 更新对应列
- if (TestMode == "湿膜")
- existing.WetFlow = flow;
- else
- existing.DryFlow = flow;
- }
- else
- {
- // 新增一行
- var newPoint = new Models.DataPoint { Pressure = pressure };
- if (TestMode == "湿膜")
- newPoint.WetFlow = flow;
- else
- newPoint.DryFlow = flow;
- Record.DataPoints.Add(newPoint);
- }
-
- // 4. 刷新曲线
- UpdatePlot();
+ float rawFlow = await _plcService.ReadWetFlowAsync();
+ flow = Math.Round(rawFlow, 2);
}
- catch (Exception ex)
+ else
{
- System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}");
+ float rawFlow = await _plcService.ReadDryFlowAsync();
+ flow = Math.Round(rawFlow, 2);
}
- });
+
+ // 3. 直接新增一行(不查找相同压力)
+ var newPoint = new Models.DataPoint
+ {
+ Pressure = pressure,
+ WetFlow = TestMode.Contains("湿膜") ? flow : 0,
+ DryFlow = TestMode.Contains("干膜") ? flow : 0
+ };
+ Record.DataPoints.Add(newPoint);
+
+ // 4. 刷新曲线
+ UpdatePlot();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}");
+ }
}
-
///
/// 清空所有数据点,重置表格和曲线
///
@@ -285,7 +270,10 @@ namespace MembranePoreTester.ViewModels
Record.DataPoints.CollectionChanged += (s, e) => UpdatePlot();
+ // 加到构造函数最后即可
+ ClearAllCommand = new RelayCommand(ClearAllData);
+ RemoveDataPointCommand = new RelayCommand(RemoveSelectedDataPoint, () => SelectedDataPoint != null);
// 延迟2秒后读取,确保连接稳定
Task.Delay(2000).ContinueWith(async _ =>
@@ -295,6 +283,11 @@ namespace MembranePoreTester.ViewModels
}
+ private void ClearAllData()
+ {
+ ClearData();
+ }
+
private async Task ReadPressureModeAsync()
{
await SafeExecuteAsync($"ReadPressureModeAsync{StationId}", async () =>
@@ -338,7 +331,7 @@ namespace MembranePoreTester.ViewModels
// 始终读取压力
float rawPressure = await _plcService.ReadPressureAsync(StationId);
- double pressure = rawPressure;
+ double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2);
if (SelectedDataPoint != null)
{
@@ -495,14 +488,14 @@ namespace MembranePoreTester.ViewModels
{
StationId = this.StationId,
TestDate = Record.TestDate,
- Tester = Record.Tester,
+ Tester = Record.Tester ?? "系统操作员",
SampleType = Record.SampleType,
- SampleSpec = Record.SampleSpec,
+ SampleSpec = Record.SampleSpec ?? "缺省值",
RoomTemperature = Record.RoomTemperature,
SoakingTime = Record.SoakingTime,
LiquidName = Record.Liquid?.Name,
LiquidSurfaceTension = Record.Liquid?.SurfaceTension ?? 0,
- LiquidManufacturer = Record.LiquidManufacturer,
+ LiquidManufacturer = Record.LiquidManufacturer ?? "缺省值",
PressureUnit = Record.PressureUnit,
BubblePointPressure = Record.BubblePointPressure,
AveragePoreSize = AveragePoreSize, // 使用计算后的属性
@@ -685,10 +678,22 @@ namespace MembranePoreTester.ViewModels
}
+ public ICommand ClearAllCommand { get; }
+ // 修改命令初始化(构造函数中)
+ // 实现删除方法
+ private void RemoveSelectedDataPoint()
+ {
+ if (SelectedDataPoint != null)
+ {
+ Record.DataPoints.Remove(SelectedDataPoint);
+ SelectedDataPoint = null; // 清除选中状态
+ UpdatePlot(); // 刷新曲线
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Views/BubblePointView.xaml b/Views/BubblePointView.xaml
index b48e0c1..fdc4376 100644
--- a/Views/BubblePointView.xaml
+++ b/Views/BubblePointView.xaml
@@ -70,8 +70,8 @@
-
-
+
+
@@ -83,11 +83,16 @@
-
+
-
-
+
+
+
+
diff --git a/Views/HistoryWindow.xaml b/Views/HistoryWindow.xaml
index 2bd7442..9ebb627 100644
--- a/Views/HistoryWindow.xaml
+++ b/Views/HistoryWindow.xaml
@@ -1,8 +1,92 @@
+ Title="历史记录" Height="600" Width="1000"
+ WindowStartupLocation="CenterOwner" Loaded="Window_Loaded"
+ Background="#F5F7FA" FontFamily="Segoe UI">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -10,48 +94,53 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/Views/HistoryWindow.xaml.cs b/Views/HistoryWindow.xaml.cs
index 4b88b56..96667dc 100644
--- a/Views/HistoryWindow.xaml.cs
+++ b/Views/HistoryWindow.xaml.cs
@@ -145,6 +145,11 @@ namespace MembranePoreTester.Views
private void Close_Click(object sender, RoutedEventArgs e) => Close();
public static event EventHandler LoadRecordEvent;
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ Query_Click(sender, e);
+ }
}
public class LoadRecordEventArgs : EventArgs
diff --git a/Views/MainWindow.xaml b/Views/MainWindow.xaml
index 5ac1653..1f88c6d 100644
--- a/Views/MainWindow.xaml
+++ b/Views/MainWindow.xaml
@@ -3,64 +3,146 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MembranePoreTester.Views"
xmlns:viewModels="clr-namespace:MembranePoreTester.ViewModels"
+ xmlns:conv="clr-namespace:MembranePoreTester.Converters"
Title="膜孔径测试系统 (GB/T 32361-2015)"
Width="1024" Height="768"
- WindowStartupLocation="CenterScreen" KeyDown="Window_KeyDown">
+ WindowStartupLocation="CenterScreen" KeyDown="Window_KeyDown"
+ Background="#F5F7FA" FontFamily="Segoe UI">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
diff --git a/Views/MainWindow.xaml.cs b/Views/MainWindow.xaml.cs
index 33634e7..c92068b 100644
--- a/Views/MainWindow.xaml.cs
+++ b/Views/MainWindow.xaml.cs
@@ -103,5 +103,12 @@ namespace MembranePoreTester.Views
}
return null;
}
+
+ private void OpenValveControl_Click(object sender, RoutedEventArgs e)
+ {
+ var win = new ValveControlWindow();
+ win.Owner = this;
+ win.Show();
+ }
}
}
\ No newline at end of file
diff --git a/Views/ParameterWindow.xaml b/Views/ParameterWindow.xaml
index e0d5897..4dcdf90 100644
--- a/Views/ParameterWindow.xaml
+++ b/Views/ParameterWindow.xaml
@@ -87,29 +87,6 @@
-
-
diff --git a/Views/PoreDistributionView.xaml b/Views/PoreDistributionView.xaml
index 35327ca..55a8272 100644
--- a/Views/PoreDistributionView.xaml
+++ b/Views/PoreDistributionView.xaml
@@ -155,6 +155,13 @@
+
@@ -181,11 +188,12 @@
-
-
-
-
+
+
+
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
diff --git a/Views/ValveControlWindow.xaml b/Views/ValveControlWindow.xaml
new file mode 100644
index 0000000..00c7e04
--- /dev/null
+++ b/Views/ValveControlWindow.xaml
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Views/ValveControlWindow.xaml.cs b/Views/ValveControlWindow.xaml.cs
new file mode 100644
index 0000000..1f998e2
--- /dev/null
+++ b/Views/ValveControlWindow.xaml.cs
@@ -0,0 +1,181 @@
+using MembranePoreTester.Communication;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Threading;
+
+namespace MembranePoreTester.Views
+{
+ public partial class ValveControlWindow : Window
+ {
+ private readonly IPlcService _plcService;
+ private readonly PlcConfiguration _config;
+ private readonly DispatcherTimer _refreshTimer;
+
+ private readonly Dictionary> _valvesByStation = new()
+ {
+ [1] = new()
+ {
+ new ValveInfo { Name = "高压进气阀", CoilAddress = 100 },
+ new ValveInfo { Name = "低压进气阀", CoilAddress = 101 },
+ new ValveInfo { Name = "排气阀", CoilAddress = 102 },
+ new ValveInfo { Name = "低压保护阀", CoilAddress = 103 },
+ new ValveInfo { Name = "大流量阀", CoilAddress = 104 },
+ new ValveInfo { Name = "小流量阀", CoilAddress = 105 },
+ new ValveInfo { Name = "底部出气阀", CoilAddress = 106 },
+ new ValveInfo { Name = "顶部出气阀", CoilAddress = 107 }
+ },
+ [2] = new()
+ {
+ new ValveInfo { Name = "高压进气阀", CoilAddress = 108 },
+ new ValveInfo { Name = "低压进气阀", CoilAddress = 109 },
+ new ValveInfo { Name = "排气阀", CoilAddress = 110 },
+ new ValveInfo { Name = "低压保护阀", CoilAddress = 111 },
+ new ValveInfo { Name = "大流量阀", CoilAddress = 112 },
+ new ValveInfo { Name = "小流量阀", CoilAddress = 113 },
+ new ValveInfo { Name = "底部出气阀", CoilAddress = 114 },
+ new ValveInfo { Name = "顶部出气阀", CoilAddress = 115 }
+ },
+ [3] = new()
+ {
+ new ValveInfo { Name = "高压进气阀", CoilAddress = 116 },
+ new ValveInfo { Name = "低压进气阀", CoilAddress = 117 },
+ new ValveInfo { Name = "排气阀", CoilAddress = 118 },
+ new ValveInfo { Name = "低压保护阀", CoilAddress = 119 },
+ new ValveInfo { Name = "大流量阀", CoilAddress = 120 },
+ new ValveInfo { Name = "小流量阀", CoilAddress = 121 },
+ new ValveInfo { Name = "底部出气阀", CoilAddress = 122 },
+ new ValveInfo { Name = "顶部出气阀", CoilAddress = 123 }
+ }
+ };
+
+ public ValveControlWindow()
+ {
+ InitializeComponent();
+ _plcService = App.PlcService;
+ _config = App.PlcConfig;
+
+ _refreshTimer = new DispatcherTimer
+ {
+ Interval = TimeSpan.FromSeconds(1)
+ };
+ _refreshTimer.Tick += async (s, e) => await RefreshValveStates();
+ _refreshTimer.Start();
+
+ Loaded += (s, e) => ShowStation(1);
+ }
+
+ private void ShowStation(int station)
+ {
+ if (valveList == null) return; // 防御性检查
+ if (_valvesByStation.TryGetValue(station, out var valves))
+ {
+ valveList.ItemsSource = valves;
+ }
+ else
+ {
+ valveList.ItemsSource = null;
+ }
+ }
+
+ private async void CmbStation_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (valveList == null) return; // 初始化未完成
+ if (cmbStation.SelectedItem is ComboBoxItem item)
+ {
+ int station = int.Parse(item.Content.ToString().Replace("工位", ""));
+ ShowStation(station);
+ await RefreshValveStates();
+ }
+ }
+
+ private async Task RefreshValveStates()
+ {
+ if (valveList.ItemsSource is not List valves) return;
+
+ foreach (var valve in valves)
+ {
+ try
+ {
+ bool state = await _plcService.ReadCoilAsync((ushort)valve.CoilAddress);
+ valve.IsOn = state;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"读取线圈{valve.CoilAddress}失败: {ex.Message}");
+ }
+ }
+ }
+
+ private async void ToggleButton_Checked(object sender, RoutedEventArgs e)
+ {
+ if (sender is ToggleButton btn && btn.DataContext is ValveInfo valve)
+ {
+ await WriteCoil(valve.CoilAddress, true);
+ }
+ }
+
+ private async void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
+ {
+ if (sender is ToggleButton btn && btn.DataContext is ValveInfo valve)
+ {
+ await WriteCoil(valve.CoilAddress, false);
+ }
+ }
+
+ private async Task WriteCoil(int coilAddress, bool value)
+ {
+ try
+ {
+ await _plcService.WriteCoilAsync((ushort)coilAddress, value);
+ await RefreshValveStates();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"控制阀门失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private async void Refresh_Click(object sender, RoutedEventArgs e)
+ {
+ await RefreshValveStates();
+ }
+
+ protected override void OnClosed(EventArgs e)
+ {
+ _refreshTimer?.Stop();
+ base.OnClosed(e);
+ }
+
+ }
+
+ public class ValveInfo : INotifyPropertyChanged
+ {
+ public string Name { get; set; }
+ public int CoilAddress { get; set; }
+
+ private bool _isOn;
+ public bool IsOn
+ {
+ get => _isOn;
+ set
+ {
+ if (_isOn != value)
+ {
+ _isOn = value;
+ OnPropertyChanged(nameof(IsOn));
+ }
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/appsettings.json b/appsettings.json
index a6efc7f..a7093bc 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -20,7 +20,7 @@
"StopCoil": 7, // 停止线圈(M7),ON 停止测试
// 压力值系数(用于单位转换,例如 kPa 转 Pa 时设为 1000)
- "PressureFactor": 1.0,
+ "PressureFactor": 1000.0,
// 流量寄存器地址(每个工位共用,但需配合流量模式切换)
"WetFlowRegister": 12, // 湿膜流量寄存器起始地址(D2~D3)