diff --git a/Helpers/PlcConfiguration.cs b/Helpers/PlcConfiguration.cs
index 4d26440..7a744ef 100644
--- a/Helpers/PlcConfiguration.cs
+++ b/Helpers/PlcConfiguration.cs
@@ -1,79 +1,105 @@
-namespace MembranePoreTester.Communication
+using System;
+
+namespace MembranePoreTester.Communication
{
+ ///
+ /// PLC 配置类,用于存储从 appsettings.json 读取的 Modbus 参数和寄存器地址。
+ ///
public class PlcConfiguration
{
- public string IpAddress { get; set; }
- public int Port { get; set; }
- public byte SlaveId { get; set; } = 1; // 从站地址,默认1
- public ushort PressureRegister { get; set; }
- public ushort WetFlowRegister { get; set; }
- public ushort DryFlowRegister { get; set; }
- public double PressureFactor { get; set; } = 1.0;
- public double WetFlowFactor { get; set; } = 1.0;
- public double DryFlowFactor { get; set; } = 1.0;
+ // ========== 网络连接参数 ==========
+ public string IpAddress { get; set; } // PLC IP 地址
+ public int Port { get; set; } // Modbus TCP 端口
+ public byte SlaveId { get; set; } = 1; // 从站地址(默认1)
+ // 以下属性用于与上位机交互(但实际按工位读取,此处保留兼容)
+ public ushort PressureRegister { get; set; } // 不再使用,保留兼容
+ public ushort WetFlowRegister { get; set; } // 湿膜流量寄存器起始地址
+ public ushort DryFlowRegister { get; set; } // 干膜流量寄存器起始地址
+ 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; } // 工位1 压力寄存器起始地址
+ public ushort PressureRegisterStation2 { get; set; } // 工位2
+ public ushort PressureRegisterStation3 { get; set; } // 工位3
- 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; }
+ // ========== 控制线圈 ==========
+ public ushort PressureModeRegister { get; set; } // 高压/低压模式寄存器
+ public ushort PressCoil { get; set; } // 加压线圈(M100)
+ public ushort StartCoil { get; set; } // 启动线圈(M20)
+ public ushort EnableCoil { get; set; } // 使能线圈(M21,只读状态)
+ public ushort StopCoil { get; set; } // 停止线圈(M7)
+ // ========== 运维参数(用户可设置) ==========
+ public ushort PressureUpperLimit { get; set; } = 300; // 加压上限 D300
+ public ushort PressureRate { get; set; } = 280; // 加压速率 D280
+ public ushort PressureCoeff { get; set; } = 282; // 加压系数 D282
-
-
-
- public ushort PressureUpperLimit { get; set; } = 300;
- public ushort PressureRate { get; set; } = 280;
- public ushort PressureCoeff { get; set; } = 282;
- public ushort HPCoeff1 { get; set; } = 3120;
- public ushort LPCoeff1 { get; set; } = 3122;
- public ushort HPCoeff2 { get; set; } = 3124;
+ // 高压/低压系数(每个工位独立)
+ public ushort HPCoeff1 { get; set; } = 3120; // 工位1 高压系数
+ public ushort LPCoeff1 { get; set; } = 3122; // 工位1 低压系数
+ public ushort HPCoeff2 { get; set; } = 3124; // 工位2
public ushort LPCoeff2 { get; set; } = 3126;
- public ushort HPCoeff3 { get; set; } = 3128;
+ public ushort HPCoeff3 { get; set; } = 3128; // 工位3
public ushort LPCoeff3 { get; set; } = 3130;
- public ushort LargeFlowCoeff1 { get; set; } = 3048;
- public ushort SmallFlowCoeff1 { get; set; } = 380;
- public ushort LargeFlowCoeff2 { get; set; } = 1218;
+
+ // 大/小流量系数(每个工位独立)
+ public ushort LargeFlowCoeff1 { get; set; } = 3048; // 工位1 大流量系数
+ public ushort SmallFlowCoeff1 { get; set; } = 380; // 工位1 小流量系数
+ public ushort LargeFlowCoeff2 { get; set; } = 1218; // 工位2
public ushort SmallFlowCoeff2 { get; set; } = 1318;
- public ushort LargeFlowCoeff3 { get; set; } = 1418;
+ public ushort LargeFlowCoeff3 { get; set; } = 1418; // 工位3
public ushort SmallFlowCoeff3 { get; set; } = 1468;
- public ushort FlowModeRegister1 { get; set; } = 5; // 工位1流量模式 (0=大,1=小)
+
+ // 流量模式选择寄存器(0=大流量,1=小流量)
+
+ public ushort FlowModeRegister { get; set; } = 4; // 工位1 流量模式
+ public ushort FlowModeRegister1 { get; set; } = 5; // 工位1 流量模式
public ushort FlowModeRegister2 { get; set; } = 6;
public ushort FlowModeRegister3 { get; set; } = 7;
- // 校准系数地址(需与PLC约定)
- public ushort PressureCalibZero { get; set; } = 3200;
- public ushort PressureCalibSpan { get; set; } = 3202;
- public ushort FlowCalibZero { get; set; } = 3204;
- public ushort FlowCalibSpan { get; set; } = 3206;
-
-
-
-
+ // 校准系数(零点/量程)寄存器地址
+ public ushort PressureCalibZero { get; set; } = 3200; // 压力零点系数
+ public ushort PressureCalibSpan { get; set; } = 3202; // 压力量程系数
+ public ushort FlowCalibZero { get; set; } = 3204; // 流量零点系数
+ public ushort FlowCalibSpan { get; set; } = 3206; // 流量量程系数
}
-
-
-
-
+ ///
+ /// PLC 服务接口,定义与 Modbus 设备通信的方法。
+ ///
public interface IPlcService
{
- Task ReadPressureAsync(int stationId); // 按工位读取压力
+ /// 读取指定工位的压力(浮点数)
+ /// 工位号 1~3
+ Task ReadPressureAsync(int stationId);
+
+ /// 读取湿膜流量(浮点数)
Task ReadWetFlowAsync();
+
+ /// 读取干膜流量(浮点数)
Task ReadDryFlowAsync();
- Task WriteCoilAsync(ushort coilAddress, bool value); // 写线圈
- Task WriteRegisterAsync(ushort registerAddress, ushort value); // 写寄存器
- Task ReadCoilAsync(ushort coilAddress); // 新增:读取线圈状态
+ /// 写入线圈(如 M 元件)
+ Task WriteCoilAsync(ushort coilAddress, bool value);
+ /// 写入单个寄存器(16位)
+ Task WriteRegisterAsync(ushort registerAddress, ushort value);
+ /// 读取线圈状态(如 M 元件的 ON/OFF)
+ Task ReadCoilAsync(ushort coilAddress);
+
+ /// 读取连续多个保持寄存器(16位)
Task ReadHoldingRegistersAsync(ushort startAddress, ushort count);
- Task WriteSingleRegisterAsync(ushort registerAddress, ushort value);
- }
+ /// 写入单个保持寄存器(16位)
+ Task WriteSingleRegisterAsync(ushort registerAddress, ushort value);
+
+
+ Task WriteMultipleRegistersAsync(ushort registerAddress, float value);
+
+ float UshortToFloat(ushort P1, ushort P2);
+ }
}
\ No newline at end of file
diff --git a/Models/BubblePointRecord.cs b/Models/BubblePointRecord.cs
index a73258f..6f5abcb 100644
--- a/Models/BubblePointRecord.cs
+++ b/Models/BubblePointRecord.cs
@@ -1,6 +1,9 @@
-namespace MembranePoreTester.Models
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace MembranePoreTester.Models
{
- public class BubblePointRecord
+ public class BubblePointRecord: INotifyPropertyChanged
{
public string SampleType { get; set; } // 平板膜/中空纤维膜
public string SampleSpec { get; set; } // 规格
@@ -8,7 +11,28 @@
public double SoakingTime { get; set; } // 浸润时间(小时)
public TestLiquid Liquid { get; set; } // 测试液体
public string LiquidManufacturer { get; set; } // 生产厂家
- public double BubblePointPressure { get; set; } // 泡点压力(数值)
+
+
+ private double _bubblePointPressure;
+
+ public double BubblePointPressure // 泡点压力(数值)
+ {
+ get => _bubblePointPressure;
+ set
+ {
+ if (_bubblePointPressure != value)
+ {
+ _bubblePointPressure = value;
+ OnPropertyChanged(); // 需要触发通知
+ }
+ }
+ }
+
+
+
+
+
+ public double BubbleCurrentPressure { get; set; } // 泡点压力(数值)
public string PressureUnit { get; set; } // Pa/cmHg/psi
public DateTime TestDate { get; set; } = DateTime.Now;
public string Tester { get; set; }
@@ -26,5 +50,14 @@
_ => 0
};
}
+
+
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
}
}
\ No newline at end of file
diff --git a/Service/ModbusTcpPlcService.cs b/Service/ModbusTcpPlcService.cs
index d304d17..f72db32 100644
--- a/Service/ModbusTcpPlcService.cs
+++ b/Service/ModbusTcpPlcService.cs
@@ -1,7 +1,8 @@
-using System;
+using Modbus.Device;
+using System;
+using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
-using Modbus.Device;
namespace MembranePoreTester.Communication
{
@@ -30,31 +31,19 @@ namespace MembranePoreTester.Communication
private async Task ReadFloatAsync(ushort startAddress)
{
await EnsureConnectedAsync();
-
- // 读取两个寄存器(从站地址由配置指定)
- ushort[] registers = await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, 2);
-
- // 将两个16位寄存器合并为32位浮点数(大端)
- byte[] bytes = new byte[4];
- bytes[0] = (byte)(registers[0] >> 8);
- bytes[1] = (byte)(registers[0] & 0xFF);
- bytes[2] = (byte)(registers[1] >> 8);
- bytes[3] = (byte)(registers[1] & 0xFF);
-
- if (BitConverter.IsLittleEndian)
- Array.Reverse(bytes); // 如果系统是小端,需要反转字节顺序
-
- return BitConverter.ToSingle(bytes, 0);
+ var registers = await ReadHoldingRegistersAsync(startAddress, 2);
+ return UshortToFloat(registers[1], registers[0]);
}
+
public async Task ReadPressureAsync() =>
- await ReadFloatAsync(_config.PressureRegister) * (float)_config.PressureFactor;
+ await ReadFloatAsync(_config.PressureRegister);
public async Task ReadWetFlowAsync() =>
- await ReadFloatAsync(_config.WetFlowRegister) * (float)_config.WetFlowFactor;
+ await ReadFloatAsync(_config.WetFlowRegister);
public async Task ReadDryFlowAsync() =>
- await ReadFloatAsync(_config.DryFlowRegister) * (float)_config.DryFlowFactor;
+ await ReadFloatAsync(_config.DryFlowRegister);
public async Task WriteCoilAsync(ushort coilAddress, bool value)
@@ -73,7 +62,7 @@ namespace MembranePoreTester.Communication
public async Task ReadCoilAsync(ushort coilAddress)
{
await EnsureConnectedAsync();
- bool[] result = await _master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
+ bool[] result = await _master?.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
return result[0];
}
@@ -87,9 +76,65 @@ namespace MembranePoreTester.Communication
public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
{
await EnsureConnectedAsync();
- await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
+ int val = (int)value;
+ await _master.WriteMultipleRegistersAsync(1, registerAddress, intToushorts(val));
}
+ public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value)
+ {
+ await EnsureConnectedAsync();
+ await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray((float)value));
+ }
+
+ ///
+ /// Int转为ushort数组发送
+ ///
+ ///
+ /// 返回ushort数组
+ private ushort[] intToushorts(int res)
+ {
+ ushort ust1 = (ushort)(res >> 16);
+ ushort ust2 = (ushort)res;
+ return new ushort[] { ust2, ust1 };
+ }
+
+
+ ///
+ /// Float转为Ushort数组发送
+ ///
+ ///
+ /// 返回ushort数组
+ public ushort[] SplitFloatToUShortArray(float value)
+ {
+ byte[] floatBytes = BitConverter.GetBytes(value);
+ ushort[] ushortArray = new ushort[floatBytes.Length / 2];
+
+ for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++)
+ {
+ ushortArray[j] = BitConverter.ToUInt16(floatBytes, i);
+ }
+
+ return ushortArray;
+ }
+
+ ///
+ /// ushort转为float类型
+ ///
+ ///
+ ///
+ /// float型数据
+ public float UshortToFloat(ushort P1, ushort P2)
+ {
+ int intSign, intSignRest, intExponent, intExponentRest;
+ float faResult, faDigit;
+ intSign = P1 / 32768;
+ intSignRest = P1 % 32768;
+ intExponent = intSignRest / 128;
+ intExponentRest = intSignRest % 128;
+ faDigit = (float)(intExponentRest * 65536 + P2) / 8388608;
+ faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1);
+ return faResult;
+ }
// 新增读取压力(根据工位)
public async Task ReadPressureAsync(int stationId)
diff --git a/ViewModels/BubblePointViewModel.cs b/ViewModels/BubblePointViewModel.cs
index 2086fd4..f7b76df 100644
--- a/ViewModels/BubblePointViewModel.cs
+++ b/ViewModels/BubblePointViewModel.cs
@@ -87,6 +87,10 @@ namespace MembranePoreTester.ViewModels
public ICommand OpenPressureCalibCommand { get; }
+
+
+ private System.Windows.Threading.DispatcherTimer _timer; // 添加定时器字段
+
public BubblePointViewModel()
{
@@ -99,15 +103,47 @@ namespace MembranePoreTester.ViewModels
SaveCommand = new RelayCommand(SaveToDatabase);
ExportCommand = new RelayCommand(ExportToExcel);
OpenPressureCalibCommand = new RelayCommand(OpenPressureCalibration);
+
+
+
+ // 启动定时器,每秒读取一次
+ _timer = new System.Windows.Threading.DispatcherTimer();
+ _timer.Interval = TimeSpan.FromSeconds(1);
+ _timer.Tick += async (s, e) => await ReadCurrentPlcAsync();
+ _timer.Start();
+
+ // 立即读取一次
+ Task.Run(async () => await ReadCurrentPlcAsync());
+ }
+
+ public override void Dispose()
+ {
+ _timer?.Stop();
+ base.Dispose();
+ }
+
+ private async Task ReadCurrentPlcAsync()
+ {
+ try
+ {
+ float rawPressure = await _plcService.ReadPressureAsync(StationId);
+ Record.BubbleCurrentPressure = rawPressure;
+ OnPropertyChanged(nameof(Record.BubbleCurrentPressure));
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
}
private async Task ReadPlcAsync()
{
+ if (IsDisposed) return; // 改3:添加这行检查
try
{
- float rawPressure = await _plcService.ReadPressureAsync(StationId);
- Record.BubblePointPressure = rawPressure * _plcConfig.PressureFactor;
+ Record.BubblePointPressure = Record.BubbleCurrentPressure;
+
OnPropertyChanged(nameof(Record.BubblePointPressure));
}
catch (Exception ex)
diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs
index 35c7c93..4624f57 100644
--- a/ViewModels/MainViewModel.cs
+++ b/ViewModels/MainViewModel.cs
@@ -1,9 +1,11 @@
using MembranePoreTester.Communication;
using System.Collections.ObjectModel;
using System.ComponentModel; // 用于 PropertyChanged
+using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
+using static OfficeOpenXml.ExcelErrorValue;
namespace MembranePoreTester.ViewModels
{
@@ -117,15 +119,19 @@ namespace MembranePoreTester.ViewModels
private async Task WritePressureModeAsync(string mode)
{
- ushort val = mode == "高压" ? (ushort)1 : (ushort)0;
+ if (IsDisposed) return;
+
+ ushort val = mode.ToString().Contains("高压") ? (ushort)0 : (ushort)1;
try
{
- await _plcService.WriteRegisterAsync(_plcConfig.PressureModeRegister, val);
+
+ await _plcService.WriteSingleRegisterAsync(_plcConfig.PressureModeRegister, val);
}
catch (Exception ex)
{
MessageBox.Show($"写压力模式失败: {ex.Message}");
}
+
}
@@ -144,7 +150,7 @@ namespace MembranePoreTester.ViewModels
public MainViewModel()
{
- for (int i = 1; i <= 3; i++)
+ for (int i = 1; i <= 1; i++)
{
var station = new StationItem
{
diff --git a/ViewModels/PoreDistributionViewModel.cs b/ViewModels/PoreDistributionViewModel.cs
index 1a5a625..ba7052a 100644
--- a/ViewModels/PoreDistributionViewModel.cs
+++ b/ViewModels/PoreDistributionViewModel.cs
@@ -347,22 +347,16 @@ namespace MembranePoreTester.ViewModels
set => SetProperty(ref _testMode, value);
}
- private int _selectedFlowModeIndex; // 0=大流量,1=小流量
- public int SelectedFlowModeIndex
+ private string _selectedFlowModeIndex; // 0=大流量,1=小流量
+ public string SelectedFlowModeIndex
{
get => _selectedFlowModeIndex;
set
{
if (SetProperty(ref _selectedFlowModeIndex, value))
{
- ushort reg = StationId switch
- {
- 1 => _plcConfig.FlowModeRegister1,
- 2 => _plcConfig.FlowModeRegister2,
- 3 => _plcConfig.FlowModeRegister3,
- _ => 0
- };
- Task.Run(async () => await _plcService.WriteSingleRegisterAsync(reg, (ushort)value));
+ // 当选择变化时,写入 PLC 压力模式寄存器
+ Task.Run(async () => await WriteFlowModeAsync(value));
}
}
}
@@ -375,6 +369,18 @@ namespace MembranePoreTester.ViewModels
set => SetProperty(ref _plotModel, value);
}
+ private async Task WriteFlowModeAsync(string mode)
+ {
+ float val = mode == "大流量" ? 0.0f : 1.0f;
+ try
+ {
+ await _plcService.WriteMultipleRegistersAsync(_plcConfig.PressureModeRegister, val);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"写流量模式失败: {ex.Message}");
+ }
+ }
private void UpdatePlot()
{
diff --git a/ViewModels/ViewModelBase.cs b/ViewModels/ViewModelBase.cs
index 86f3e4f..b5219a1 100644
--- a/ViewModels/ViewModelBase.cs
+++ b/ViewModels/ViewModelBase.cs
@@ -4,8 +4,11 @@ using System.Runtime.CompilerServices;
namespace MembranePoreTester.ViewModels
{
- public class ViewModelBase : INotifyPropertyChanged
+ public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
+ private bool _disposed = false;
+ protected CancellationTokenSource _cts = new CancellationTokenSource();
+
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
@@ -30,5 +33,35 @@ namespace MembranePoreTester.ViewModels
await App.PlcService.WriteSingleRegisterAsync(address, high);
await App.PlcService.WriteSingleRegisterAsync((ushort)(address + 1), low);
}
+
+ // 新增:安全执行异步方法(自动处理对象释放)
+ protected async Task SafeExecuteAsync(Func action)
+ {
+ if (_disposed) return;
+ try
+ {
+ await action();
+ }
+ catch (OperationCanceledException)
+ {
+ // 正常取消
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"异步执行失败: {ex.Message}");
+ }
+ }
+
+ // 新增:检查是否已释放
+ protected bool IsDisposed => _disposed;
+
+ public virtual void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ _cts?.Cancel();
+ _cts?.Dispose();
+ _cts = null;
+ }
}
}
\ No newline at end of file
diff --git a/Views/BubblePointView.xaml b/Views/BubblePointView.xaml
index 2d75cc8..40cd46d 100644
--- a/Views/BubblePointView.xaml
+++ b/Views/BubblePointView.xaml
@@ -58,7 +58,11 @@
-->
-
+
+
+
+
+
diff --git a/Views/MainWindow.xaml b/Views/MainWindow.xaml
index 150711f..2edca57 100644
--- a/Views/MainWindow.xaml
+++ b/Views/MainWindow.xaml
@@ -29,7 +29,7 @@
低压
- 高压
+ 高压
diff --git a/Views/ParameterWindow.xaml b/Views/ParameterWindow.xaml
index 5cdfd9e..1b26347 100644
--- a/Views/ParameterWindow.xaml
+++ b/Views/ParameterWindow.xaml
@@ -87,7 +87,7 @@
-
+
-
+
diff --git a/Views/ParameterWindow.xaml.cs b/Views/ParameterWindow.xaml.cs
index 324ba42..f58ac4d 100644
--- a/Views/ParameterWindow.xaml.cs
+++ b/Views/ParameterWindow.xaml.cs
@@ -1,10 +1,12 @@
-using MembranePoreTester.Communication;
-using MembranePoreTester.ViewModels;
-using System;
+using System;
+using System.Collections.Generic;
using System.Windows;
+using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
+using MembranePoreTester.Communication;
+using MembranePoreTester.ViewModels;
namespace MembranePoreTester.Views
{
@@ -13,7 +15,9 @@ namespace MembranePoreTester.Views
private readonly IPlcService _plcService;
private readonly PlcConfiguration _config;
private DispatcherTimer _autoRefreshTimer;
- private bool _isEditing = false; // 用户是否正在编辑任何文本框
+
+ // 用于快速查找文本框对应的写入地址和方法(可根据需要扩展)
+ private readonly Dictionary _textBoxMapping;
public ParameterWindow()
{
@@ -21,12 +25,39 @@ namespace MembranePoreTester.Views
_plcService = App.PlcService;
_config = App.PlcConfig;
- // 为所有文本框注册焦点事件(在XAML中设置事件,或在此处统一查找)
+ // 初始化文本框到寄存器地址的映射
+ _textBoxMapping = new Dictionary
+ {
+ [txtPressureUpper] = (_config.PressureUpperLimit, "加压上限"),
+ [txtPressureRate] = (_config.PressureRate, "加压速率"),
+ [txtPressureCoeff] = (_config.PressureCoeff, "加压系数"),
+
+ [txtHPCoeff1] = (_config.HPCoeff1, "工位1高压系数"),
+ [txtLPCoeff1] = (_config.LPCoeff1, "工位1低压系数"),
+ [txtHPCoeff2] = (_config.HPCoeff2, "工位2高压系数"),
+ [txtLPCoeff2] = (_config.LPCoeff2, "工位2低压系数"),
+ [txtHPCoeff3] = (_config.HPCoeff3, "工位3高压系数"),
+ [txtLPCoeff3] = (_config.LPCoeff3, "工位3低压系数"),
+
+ [txtLargeFlow1] = (_config.LargeFlowCoeff1, "工位1大流量系数"),
+ [txtSmallFlow1] = (_config.SmallFlowCoeff1, "工位1小流量系数"),
+ [txtLargeFlow2] = (_config.LargeFlowCoeff2, "工位2大流量系数"),
+ [txtSmallFlow2] = (_config.SmallFlowCoeff2, "工位2小流量系数"),
+ [txtLargeFlow3] = (_config.LargeFlowCoeff3, "工位3大流量系数"),
+ [txtSmallFlow3] = (_config.SmallFlowCoeff3, "工位3小流量系数"),
+
+ //[txtPressureZero] = (_config.PressureCalibZero, "压力零点"),
+ //[txtPressureSpan] = (_config.PressureCalibSpan, "压力量程"),
+ //[txtFlowZero] = (_config.FlowCalibZero, "流量零点"),
+ //[txtFlowSpan] = (_config.FlowCalibSpan, "流量量程"),
+ };
+
+ // 为所有文本框注册焦点事件
RegisterTextBoxEvents();
Loaded += async (s, e) =>
{
- // 启动自动刷新定时器(每秒1次)
+ // 启动定时器
_autoRefreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
@@ -35,152 +66,109 @@ namespace MembranePoreTester.Views
_autoRefreshTimer.Start();
// 首次加载时读取一次
- await ReadParametersAsync();
+ await LoadParameters();
};
Closed += (s, e) => _autoRefreshTimer?.Stop();
}
- ///
- /// 为所有文本框注册焦点事件,用于检测编辑状态
- ///
private void RegisterTextBoxEvents()
{
- // 查找当前窗口中所有文本框(可根据实际布局调整)
- var textBoxes = FindVisualChildren(this);
- foreach (var tb in textBoxes)
+ foreach (var tb in _textBoxMapping.Keys)
{
- tb.GotFocus += (s, e) => _isEditing = true;
- tb.LostFocus += (s, e) => _isEditing = false;
+ tb.GotFocus += (s, e) => { /* 什么都不做,只是为了让自动刷新跳过当前焦点的文本框 */ };
+ tb.LostFocus += async (s, e) =>
+ {
+ // 失去焦点时,将当前文本框的值写入PLC
+ var (address, name) = _textBoxMapping[tb];
+ if (float.TryParse(tb.Text, out float value))
+ {
+ try
+ {
+ await WriteFloatAsync(address, value);
+ // 可选:显示短暂提示(避免频繁弹窗)
+ System.Diagnostics.Debug.WriteLine($"{name} 已写入: {value}");
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"写入 {name} 失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+ else
+ {
+ // 输入无效,可提示或恢复原值
+ MessageBox.Show($"请输入有效的数值", "输入错误", MessageBoxButton.OK, MessageBoxImage.Warning);
+ // 重新从PLC读取该参数恢复原值
+ await UpdateSingleTextBox(tb, address);
+ }
+ };
}
}
///
- /// 定时器事件:如果用户未在编辑,则刷新参数
+ /// 更新单个文本框的值(从PLC读取)
///
+ private async Task UpdateSingleTextBox(TextBox tb, ushort address)
+ {
+ try
+ {
+ float val = await ReadFloatAsync(address);
+ tb.Text = val.ToString("F3");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"更新文本框失败: {ex.Message}");
+ }
+ }
+
private async void AutoRefreshTimer_Tick(object sender, EventArgs e)
{
- if (!_isEditing)
+ // 只更新当前没有获得焦点的文本框
+ await Dispatcher.InvokeAsync(async () =>
{
- await ReadParametersAsync();
- }
+ foreach (var kv in _textBoxMapping)
+ {
+ var tb = kv.Key;
+ var address = kv.Value.address;
+ if (!tb.IsFocused)
+ {
+ try
+ {
+ float val = await ReadFloatAsync(address);
+ tb.Text = val.ToString("F3");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"自动刷新 {kv.Value.paramName} 失败: {ex.Message}");
+ }
+ }
+ }
+ });
}
- private async Task ReadParametersAsync()
+ ///
+ /// 一次性加载所有参数(用于窗口启动)
+ ///
+ private async Task LoadParameters()
{
- try
+ foreach (var kv in _textBoxMapping)
{
- // 加压控制
- txtPressureUpper.Text = (await ReadFloatAsync(_config.PressureUpperLimit)).ToString("F3");
- txtPressureRate.Text = (await ReadFloatAsync(_config.PressureRate)).ToString("F3");
- txtPressureCoeff.Text = (await ReadFloatAsync(_config.PressureCoeff)).ToString("F3");
-
- // 高压/低压系数
- txtHPCoeff1.Text = (await ReadFloatAsync(_config.HPCoeff1)).ToString("F3");
- txtLPCoeff1.Text = (await ReadFloatAsync(_config.LPCoeff1)).ToString("F3");
- txtHPCoeff2.Text = (await ReadFloatAsync(_config.HPCoeff2)).ToString("F3");
- txtLPCoeff2.Text = (await ReadFloatAsync(_config.LPCoeff2)).ToString("F3");
- txtHPCoeff3.Text = (await ReadFloatAsync(_config.HPCoeff3)).ToString("F3");
- txtLPCoeff3.Text = (await ReadFloatAsync(_config.LPCoeff3)).ToString("F3");
-
- // 流量系数
- txtLargeFlow1.Text = (await ReadFloatAsync(_config.LargeFlowCoeff1)).ToString("F3");
- txtSmallFlow1.Text = (await ReadFloatAsync(_config.SmallFlowCoeff1)).ToString("F3");
- txtLargeFlow2.Text = (await ReadFloatAsync(_config.LargeFlowCoeff2)).ToString("F3");
- txtSmallFlow2.Text = (await ReadFloatAsync(_config.SmallFlowCoeff2)).ToString("F3");
- txtLargeFlow3.Text = (await ReadFloatAsync(_config.LargeFlowCoeff3)).ToString("F3");
- txtSmallFlow3.Text = (await ReadFloatAsync(_config.SmallFlowCoeff3)).ToString("F3");
-
- // 校准参数
- txtPressureZero.Text = (await ReadFloatAsync(_config.PressureCalibZero)).ToString("F3");
- txtPressureSpan.Text = (await ReadFloatAsync(_config.PressureCalibSpan)).ToString("F3");
- txtFlowZero.Text = (await ReadFloatAsync(_config.FlowCalibZero)).ToString("F3");
- txtFlowSpan.Text = (await ReadFloatAsync(_config.FlowCalibSpan)).ToString("F3");
- }
- catch (Exception ex)
- {
- // 静默处理,避免频繁弹窗干扰(可记录日志)
- System.Diagnostics.Debug.WriteLine($"自动读取参数失败: {ex.Message}");
+ await UpdateSingleTextBox(kv.Key, kv.Value.address);
}
}
- private async void WriteParameters_Click(object sender, RoutedEventArgs e)
- {
- try
- {
- // 加压控制
- await WriteFloatAsync(_config.PressureUpperLimit, ParseFloat(txtPressureUpper.Text));
- await WriteFloatAsync(_config.PressureRate, ParseFloat(txtPressureRate.Text));
- await WriteFloatAsync(_config.PressureCoeff, ParseFloat(txtPressureCoeff.Text));
-
- // 高压/低压系数
- await WriteFloatAsync(_config.HPCoeff1, ParseFloat(txtHPCoeff1.Text));
- await WriteFloatAsync(_config.LPCoeff1, ParseFloat(txtLPCoeff1.Text));
- await WriteFloatAsync(_config.HPCoeff2, ParseFloat(txtHPCoeff2.Text));
- await WriteFloatAsync(_config.LPCoeff2, ParseFloat(txtLPCoeff2.Text));
- await WriteFloatAsync(_config.HPCoeff3, ParseFloat(txtHPCoeff3.Text));
- await WriteFloatAsync(_config.LPCoeff3, ParseFloat(txtLPCoeff3.Text));
-
- // 流量系数
- await WriteFloatAsync(_config.LargeFlowCoeff1, ParseFloat(txtLargeFlow1.Text));
- await WriteFloatAsync(_config.SmallFlowCoeff1, ParseFloat(txtSmallFlow1.Text));
- await WriteFloatAsync(_config.LargeFlowCoeff2, ParseFloat(txtLargeFlow2.Text));
- await WriteFloatAsync(_config.SmallFlowCoeff2, ParseFloat(txtSmallFlow2.Text));
- await WriteFloatAsync(_config.LargeFlowCoeff3, ParseFloat(txtLargeFlow3.Text));
- await WriteFloatAsync(_config.SmallFlowCoeff3, ParseFloat(txtSmallFlow3.Text));
-
- // 校准参数
- await WriteFloatAsync(_config.PressureCalibZero, ParseFloat(txtPressureZero.Text));
- await WriteFloatAsync(_config.PressureCalibSpan, ParseFloat(txtPressureSpan.Text));
- await WriteFloatAsync(_config.FlowCalibZero, ParseFloat(txtFlowZero.Text));
- await WriteFloatAsync(_config.FlowCalibSpan, ParseFloat(txtFlowSpan.Text));
-
- MessageBox.Show("参数写入成功");
- }
- catch (Exception ex)
- {
- MessageBox.Show($"写入参数失败: {ex.Message}");
- }
- }
-
- private float ParseFloat(string text) => float.TryParse(text, out var val) ? val : 0;
-
+ // 以下方法与之前相同,但保留用于写入(已在LostFocus中调用)
private async Task ReadFloatAsync(ushort address)
{
var registers = await ((ModbusTcpPlcService)_plcService).ReadHoldingRegistersAsync(address, 2);
- byte[] bytes = new byte[4];
- bytes[0] = (byte)(registers[0] >> 8);
- bytes[1] = (byte)(registers[0] & 0xFF);
- bytes[2] = (byte)(registers[1] >> 8);
- bytes[3] = (byte)(registers[1] & 0xFF);
- if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
- return BitConverter.ToSingle(bytes, 0);
+ return ((ModbusTcpPlcService)_plcService).UshortToFloat(registers[1], registers[0]);
}
private async Task WriteFloatAsync(ushort address, float value)
{
- byte[] 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 ((ModbusTcpPlcService)_plcService).WriteRegisterAsync(address, high);
- await ((ModbusTcpPlcService)_plcService).WriteRegisterAsync((ushort)(address + 1), low);
+ await ((ModbusTcpPlcService)_plcService).WriteMultipleRegistersAsync(address, value);
}
private void Close_Click(object sender, RoutedEventArgs e) => Close();
-
- ///
- /// 辅助方法:查找视觉树中的所有指定类型子元素
- ///
- private static IEnumerable FindVisualChildren(DependencyObject depObj) where T : DependencyObject
- {
- if (depObj == null) yield break;
- for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
- {
- var child = VisualTreeHelper.GetChild(depObj, i);
- if (child is T t) yield return t;
- foreach (var childOfChild in FindVisualChildren(child)) yield return childOfChild;
- }
- }
}
}
\ No newline at end of file
diff --git a/appsettings.json b/appsettings.json
index de71ada..86f0069 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -1,58 +1,65 @@
{
"PlcSettings": {
- "IpAddress": "192.168.1.10",
- "Port": 502,
- "SlaveId": 1,
- "PressureRegisterStation1": 6,
- "PressureRegisterStation2": 8,
- "PressureRegisterStation3": 10,
- "PressureModeRegister": 200, // 高压/低压选择寄存器(0=低压,1=高压)
- "PressCoil": 100, // 加压线圈(M100)
- "StartCoil": 20, // 启动线圈(M20)
- "EnableCoil": 21, // 使能线圈(M21)
- "StopCoil": 7, // 停止线圈(M7)
+ // Modbus TCP 连接参数
+ "IpAddress": "192.168.1.10", // PLC 的 IP 地址
+ "Port": 502, // Modbus TCP 端口号(默认502)
+ "SlaveId": 1, // PLC 从站地址(通常为1)
+
+ // 压力寄存器地址(每个工位占用 2 个连续 D 寄存器,存储 32 位浮点数)
+ "PressureRegisterStation1": 6, // 工位1 压力寄存器起始地址(D6~D7)
+ "PressureRegisterStation2": 8, // 工位2 压力寄存器起始地址(D8~D9)
+ "PressureRegisterStation3": 10, // 工位3 压力寄存器起始地址(D10~D11)
+
+ // 压力模式选择(高压/低压)写入寄存器
+ "PressureModeRegister": 2, // 压力模式寄存器(0=低压,1=高压)
+
+ // 线圈控制(M 元件)
+ "PressCoil": 100, // 加压线圈(M100),ON 开始加压,OFF 停止
+ "StartCoil": 20, // 启动线圈(M20),ON 启动测试程序
+ "EnableCoil": 21, // 使能线圈(M21),状态反馈(只读)
+ "StopCoil": 7, // 停止线圈(M7),ON 停止测试
+
+ // 压力值系数(用于单位转换,例如 kPa 转 Pa 时设为 1000)
"PressureFactor": 1.0,
- "WetFlowRegister": 2,
- "DryFlowRegister": 4,
- "WetFlowFactor": 1.0,
- "DryFlowFactor": 1.0,
-
-
-
-
- "PressureUpperLimit": 300,
- "PressureRate": 280,
- "PressureCoeff": 282,
- "HPCoeff1": 3120,
- "LPCoeff1": 3122,
- "HPCoeff2": 3124,
- "LPCoeff2": 3126,
- "HPCoeff3": 3128,
- "LPCoeff3": 3130,
- "LargeFlowCoeff1": 3048,
- "SmallFlowCoeff1": 380,
- "LargeFlowCoeff2": 1218,
- "SmallFlowCoeff2": 1318,
- "LargeFlowCoeff3": 1418,
- "SmallFlowCoeff3": 1468,
- "FlowModeRegister1": 5,
- "FlowModeRegister2": 6,
- "FlowModeRegister3": 7,
- "PressureCalibZero": 3200,
- "PressureCalibSpan": 3202,
- "FlowCalibZero": 3204,
- "FlowCalibSpan": 3206
-
-
-
-
-
-
-
-
-
+
+ // 流量寄存器地址(每个工位共用,但需配合流量模式切换)
+ "WetFlowRegister": 2, // 湿膜流量寄存器起始地址(D2~D3)
+ "DryFlowRegister": 4, // 干膜流量寄存器起始地址(D4~D5)
+ "WetFlowFactor": 1.0, // 湿膜流量系数
+ "DryFlowFactor": 1.0, // 干膜流量系数
+
+ // ========== 运维参数设置(可读写) ==========
+ "PressureUpperLimit": 300, // 加压上限(D300)
+ "PressureRate": 280, // 加压速率(D280)
+ "PressureCoeff": 282, // 加压系数(D282)
+
+ // 高压/低压系数(每个工位独立)
+ "HPCoeff1": 3120, // 工位1 高压系数(D3120)
+ "LPCoeff1": 3122, // 工位1 低压系数(D3122)
+ "HPCoeff2": 3124, // 工位2 高压系数(D3124)
+ "LPCoeff2": 3126, // 工位2 低压系数(D3126)
+ "HPCoeff3": 3128, // 工位3 高压系数(D3128)
+ "LPCoeff3": 3130, // 工位3 低压系数(D3130)
+
+ // 大/小流量系数(每个工位独立)
+ "LargeFlowCoeff1": 3048, // 工位1 大流量系数(D3048)
+ "SmallFlowCoeff1": 380, // 工位1 小流量系数(D380)
+ "LargeFlowCoeff2": 1218, // 工位2 大流量系数(D1218)
+ "SmallFlowCoeff2": 1318, // 工位2 小流量系数(D1318)
+ "LargeFlowCoeff3": 1418, // 工位3 大流量系数(D1418)
+ "SmallFlowCoeff3": 1468, // 工位3 小流量系数(D1468)
+ "FlowModeRegister": 4,
+ // 流量模式选择(大流量/小流量)写入寄存器
+ "FlowModeRegister1": 5, // 工位1 流量模式(0=大流量,1=小流量)
+ "FlowModeRegister2": 6, // 工位2 流量模式
+ "FlowModeRegister3": 7, // 工位3 流量模式
+ // 校准系数(零点/量程)寄存器地址(约定,若PLC未分配可自定义)
+ "PressureCalibZero": 3200, // 压力零点系数(D3200~D3201)
+ "PressureCalibSpan": 3202, // 压力量程系数(D3202~D3203)
+ "FlowCalibZero": 3204, // 流量零点系数(D3204~D3205)
+ "FlowCalibSpan": 3206 // 流量量程系数(D3206~D3207)
}
}
\ No newline at end of file
diff --git a/分离膜孔径测试仪1.gxw b/分离膜孔径测试仪1.gxw
new file mode 100644
index 0000000..0aee309
Binary files /dev/null and b/分离膜孔径测试仪1.gxw differ
diff --git a/分离膜孔径测试仪.gxw b/分离膜孔径测试仪111.gxw
similarity index 90%
rename from 分离膜孔径测试仪.gxw
rename to 分离膜孔径测试仪111.gxw
index 6114a38..9b09b5c 100644
Binary files a/分离膜孔径测试仪.gxw and b/分离膜孔径测试仪111.gxw differ