diff --git a/Helpers/PlcConfiguration.cs b/Helpers/PlcConfiguration.cs index 7a744ef..023e91e 100644 --- a/Helpers/PlcConfiguration.cs +++ b/Helpers/PlcConfiguration.cs @@ -17,8 +17,7 @@ namespace MembranePoreTester.Communication 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 压力寄存器起始地址 @@ -35,7 +34,6 @@ namespace MembranePoreTester.Communication // ========== 运维参数(用户可设置) ========== public ushort PressureUpperLimit { get; set; } = 300; // 加压上限 D300 public ushort PressureRate { get; set; } = 280; // 加压速率 D280 - public ushort PressureCoeff { get; set; } = 282; // 加压系数 D282 // 高压/低压系数(每个工位独立) public ushort HPCoeff1 { get; set; } = 3120; // 工位1 高压系数 @@ -56,15 +54,29 @@ namespace MembranePoreTester.Communication // 流量模式选择寄存器(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; - // 校准系数(零点/量程)寄存器地址 - 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 High1 { get; set; } // 工位1 + public ushort High2 { get; set; } // 工位1 + public ushort High3 { get; set; } // 工位1 + public ushort Low1 { get; set; } // 工位1 + public ushort Low2 { get; set; } // 工位1 + public ushort Low3 { get; set; } // 工位1 + + + + + public ushort SmallFlow1 { get; set; } // 工位1 + public ushort SmallFlow2 { get; set; } // 工位1 + public ushort SmallFlow3 { get; set; } // 工位1 + public ushort BigFlow1 { get; set; } // 工位1 + public ushort BigFlow2 { get; set; } // 工位1 + public ushort BigFlow3 { get; set; } // 工位1 + + } /// diff --git a/Service/ModbusTcpPlcService.cs b/Service/ModbusTcpPlcService.cs index f72db32..dddf5f6 100644 --- a/Service/ModbusTcpPlcService.cs +++ b/Service/ModbusTcpPlcService.cs @@ -55,6 +55,7 @@ namespace MembranePoreTester.Communication public async Task WriteRegisterAsync(ushort registerAddress, ushort value) { await EnsureConnectedAsync(); + await Task.Delay(100); await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value); } @@ -62,6 +63,7 @@ namespace MembranePoreTester.Communication public async Task ReadCoilAsync(ushort coilAddress) { await EnsureConnectedAsync(); + await Task.Delay(100); bool[] result = await _master?.ReadCoilsAsync(_config.SlaveId, coilAddress, 1); return result[0]; } @@ -70,6 +72,7 @@ namespace MembranePoreTester.Communication public async Task ReadHoldingRegistersAsync(ushort startAddress, ushort count) { await EnsureConnectedAsync(); + await Task.Delay(100); return await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count); } @@ -77,12 +80,14 @@ namespace MembranePoreTester.Communication { await EnsureConnectedAsync(); int val = (int)value; + await Task.Delay(100); await _master.WriteMultipleRegistersAsync(1, registerAddress, intToushorts(val)); } public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value) { await EnsureConnectedAsync(); + await Task.Delay(100); await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray((float)value)); } diff --git a/ViewModels/BubblePointViewModel.cs b/ViewModels/BubblePointViewModel.cs index c0ea5b8..80c037c 100644 --- a/ViewModels/BubblePointViewModel.cs +++ b/ViewModels/BubblePointViewModel.cs @@ -86,6 +86,7 @@ namespace MembranePoreTester.ViewModels public ICommand GenerateReportCommand { get; } public ICommand OpenPressureCalibCommand { get; } + public ICommand OpenPressureCalibCommand2 { get; } @@ -107,21 +108,28 @@ namespace MembranePoreTester.ViewModels SaveCommand = new RelayCommand(SaveToDatabase); ExportCommand = new RelayCommand(ExportToExcel); OpenPressureCalibCommand = new RelayCommand(OpenPressureCalibration); - + OpenPressureCalibCommand2 = new RelayCommand(OpenPressureCalibration2); // 启动定时器,每秒读取一次 _timer = new System.Windows.Threading.DispatcherTimer(); _timer.Interval = TimeSpan.FromSeconds(1); - _timer.Tick += async (s, e) => await ReadCurrentPlcAsync(); + _timer.Tick += async (s, e) => + { + if (StationId > 0 && !IsDisposed) + { + await ReadCurrentPlcAsync(); + } + }; _timer.Start(); - // 立即读取一次 - Task.Run(async () => - { - await ReadCurrentPlcAsync(); - } - ); + //// 立即读取一次 + //Task.Run(async () => + //{ + // await Task.Delay(1000); // 等待2秒确保PLC连接稳定 + // await ReadCurrentPlcAsync(); + //} + //); } public override void Dispose() @@ -137,32 +145,29 @@ namespace MembranePoreTester.ViewModels private async Task ReadCurrentPlcAsync() { - try + await SafeExecuteAsync("ReadCurrentPressure", async () => { + if (IsDisposed || _plcService == null) return; + + await Task.Delay(100); // 小延迟避免过快读取 float rawPressure = await _plcService.ReadPressureAsync(StationId); - Record.BubbleCurrentPressure =Math.Round( rawPressure,2); + Record.BubbleCurrentPressure = Math.Round(rawPressure, 2); 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 - { - Record.BubblePointPressure = Record.BubbleCurrentPressure; + if (IsDisposed) return; + + Record.BubblePointPressure = Record.BubbleCurrentPressure; + + OnPropertyChanged(nameof(Record.BubblePointPressure)); + - OnPropertyChanged(nameof(Record.BubblePointPressure)); - } - catch (Exception ex) - { - MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } } @@ -248,18 +253,58 @@ namespace MembranePoreTester.ViewModels private void OpenPressureCalibration() { - // 使用简单的输入框获取新零点系数和量程系数 - string zeroStr = Microsoft.VisualBasic.Interaction.InputBox("请输入压力零点系数", "压力校准", "0"); - string spanStr = Microsoft.VisualBasic.Interaction.InputBox("请输入压力量程系数", "压力校准", "1"); - if (float.TryParse(zeroStr, out float zero) && float.TryParse(spanStr, out float span)) + ushort address = new ushort(); + switch (StationId) { - Task.Run(async () => - { - await WriteFloatAsync(_plcConfig.PressureCalibZero, zero); - await WriteFloatAsync(_plcConfig.PressureCalibSpan, span); - MessageBox.Show("压力校准系数已写入", "完成"); - }); + case 1: + { + address = _plcConfig.High1; + break; + } + case 2: + { + address = _plcConfig.High2; + break; + } + case 3: + { + address = _plcConfig.High3; + break; + } } + + Task.Run(async () => + { + await _plcService.WriteCoilAsync(address, true); + }); + } + + private void OpenPressureCalibration2() + { + ushort address = new ushort(); + switch (StationId) + { + case 1: + { + address = _plcConfig.Low1; + break; + } + case 2: + { + address = _plcConfig.Low2; + break; + } + case 3: + { + address = _plcConfig.Low3; + break; + } + } + + Task.Run(async () => + { + await _plcService.WriteCoilAsync(address, true); + }); } private void ExportToExcel() diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 325e48d..3caed85 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -165,22 +165,25 @@ namespace MembranePoreTester.ViewModels } private async Task ReadPressureModeAsync() { - try + await SafeExecuteAsync($"ReadPressureModeAsync{StationId}", async () => { - ushort[] values = await _plcService.ReadHoldingRegistersAsync(_plcConfig.PressureModeRegister, 1); - ushort val = values[0]; - string newValue = val == 0 ? "高压" : "低压"; - - Application.Current.Dispatcher.Invoke(() => + try { - // 更新选中项 - HighLowPressure = newValue; - }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"读取压力模式失败: {ex.Message}"); - } + ushort[] values = await _plcService.ReadHoldingRegistersAsync(_plcConfig.PressureModeRegister, 1); + ushort val = values[0]; + string newValue = val == 0 ? "高压" : "低压"; + + Application.Current.Dispatcher.Invoke(() => + { + // 更新选中项 + HighLowPressure = newValue; + }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"读取压力模式失败: {ex.Message}"); + } + }); } private async Task TogglePressAsync() @@ -204,33 +207,38 @@ namespace MembranePoreTester.ViewModels MessageBox.Show($"读取加压状态失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } - + // 在 StationItem 类中添加字段 + private bool _lastEnableReadFailed = false; + private DateTime _lastEnableErrorTime = DateTime.MinValue; private async Task ReadEnableStatusAsync() { - try + await SafeExecuteAsync($"ReadEnableStatus_Station{StationId}", async () => { - bool status = await _plcService.ReadCoilAsync(_plcConfig.EnableCoil); // 读取 M21 - EnableStatus = status; - - - bool pressStatus = await _plcService.ReadCoilAsync(_plcConfig.PressCoil);//这里也要更新加压的按钮状态 - - _isPressing = pressStatus; - - // 在UI线程更新按钮文字 - Application.Current.Dispatcher.Invoke(() => + try { - PressButtonText = pressStatus ? "停止加压" : "加压"; - }); + bool status = await _plcService.ReadCoilAsync(_plcConfig.EnableCoil); // 读取 M21 + EnableStatus = status; + + + bool pressStatus = await _plcService.ReadCoilAsync(_plcConfig.PressCoil);//这里也要更新加压的按钮状态 + + _isPressing = pressStatus; + + // 在UI线程更新按钮文字 + Application.Current.Dispatcher.Invoke(() => + { + PressButtonText = pressStatus ? "停止加压" : "加压"; + }); - } - catch (Exception ex) - { - // 读取出错时保持原状态或显示错误 - System.Diagnostics.Debug.WriteLine($"读取使能状态失败: {ex.Message}"); - } + } + catch (Exception ex) + { + // 读取出错时保持原状态或显示错误 + System.Diagnostics.Debug.WriteLine($"读取使能状态失败: {ex.Message}"); + } + }); } private async Task WriteCoilAsync(ushort coil, bool value) @@ -283,7 +291,7 @@ namespace MembranePoreTester.ViewModels public MainViewModel() { - for (int i = 1; i <= 1; i++) + for (int i = 1; i <= 3; i++) { var station = new StationItem { diff --git a/ViewModels/PoreDistributionViewModel.cs b/ViewModels/PoreDistributionViewModel.cs index 579bf15..b16d0b5 100644 --- a/ViewModels/PoreDistributionViewModel.cs +++ b/ViewModels/PoreDistributionViewModel.cs @@ -92,53 +92,56 @@ namespace MembranePoreTester.ViewModels private async void AutoCollectTimer_Tick(object sender, EventArgs e) { if (!IsActive) return; // 不在当前标签页,跳过采集 - try + await SafeExecuteAsync($"AutoCollect_Station{StationId}", async () => { - // 1. 读取当前压力 - float rawPressure = await _plcService.ReadPressureAsync(StationId); - double pressure = Math.Round(rawPressure,2); + try + { + // 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) - { - // 更新对应列 + double flow = 0; if (TestMode == "湿膜") - existing.WetFlow = flow; + { + float rawFlow = await _plcService.ReadWetFlowAsync(); + flow = rawFlow; + } 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); - } + { + float rawFlow = await _plcService.ReadDryFlowAsync(); + flow = rawFlow; + } - // 4. 刷新曲线 - UpdatePlot(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}"); - } + 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(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}"); + } + }); } /// @@ -278,7 +281,7 @@ namespace MembranePoreTester.ViewModels SaveCommand = new RelayCommand(SaveToDatabase); ExportCommand = new RelayCommand(ExportToExcel); OpenFlowCalibCommand = new RelayCommand(OpenFlowCalibration); - + OpenFlowCalibCommand2 = new RelayCommand(OpenFlowCalibration2); Record.DataPoints.CollectionChanged += (s, e) => UpdatePlot(); @@ -294,22 +297,25 @@ namespace MembranePoreTester.ViewModels private async Task ReadPressureModeAsync() { - try + await SafeExecuteAsync($"ReadPressureModeAsync{StationId}", async () => { - ushort[] values = await _plcService.ReadHoldingRegistersAsync(_plcConfig.FlowModeRegister, 1); - ushort val = values[0]; - string newValue = val == 0 ? "大流量" : "小流量"; - - Application.Current.Dispatcher.Invoke(() => + try { - // 更新选中项 - HighLowPressure = newValue; - }); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"读取流量模式失败: {ex.Message}"); - } + ushort[] values = await _plcService.ReadHoldingRegistersAsync(_plcConfig.FlowModeRegister, 1); + ushort val = values[0]; + string newValue = val == 0 ? "大流量" : "小流量"; + + Application.Current.Dispatcher.Invoke(() => + { + // 更新选中项 + HighLowPressure = newValue; + }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"读取流量模式失败: {ex.Message}"); + } + }); } public string HighLowPressure @@ -327,11 +333,12 @@ namespace MembranePoreTester.ViewModels private async Task ReadPlcAsync() { - try + await SafeExecuteAsync($"ManualRead_Station{StationId}", async () => { + // 始终读取压力 float rawPressure = await _plcService.ReadPressureAsync(StationId); - double pressure = rawPressure * _plcConfig.PressureFactor; + double pressure = rawPressure; if (SelectedDataPoint != null) { @@ -340,12 +347,12 @@ namespace MembranePoreTester.ViewModels if (TestMode == "湿膜") { float rawWet = await _plcService.ReadWetFlowAsync(); - SelectedDataPoint.WetFlow = rawWet * _plcConfig.WetFlowFactor; + SelectedDataPoint.WetFlow = rawWet; } else { float rawDry = await _plcService.ReadDryFlowAsync(); - SelectedDataPoint.DryFlow = rawDry * _plcConfig.DryFlowFactor; + SelectedDataPoint.DryFlow = rawDry; } } else @@ -355,20 +362,19 @@ namespace MembranePoreTester.ViewModels if (TestMode == "湿膜") { float rawWet = await _plcService.ReadWetFlowAsync(); - newPoint.WetFlow = rawWet * _plcConfig.WetFlowFactor; + newPoint.WetFlow = rawWet; } else { float rawDry = await _plcService.ReadDryFlowAsync(); - newPoint.DryFlow = rawDry * _plcConfig.DryFlowFactor; + newPoint.DryFlow = rawDry; } Record.DataPoints.Add(newPoint); } - } - catch (Exception ex) - { - MessageBox.Show($"读取PLC失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } + + + + }); } private void AddDataPoint() @@ -422,17 +428,58 @@ namespace MembranePoreTester.ViewModels private void OpenFlowCalibration() { - string zeroStr = Microsoft.VisualBasic.Interaction.InputBox("请输入流量零点系数", "流量校准", "0"); - string spanStr = Microsoft.VisualBasic.Interaction.InputBox("请输入流量量程系数", "流量校准", "1"); - if (float.TryParse(zeroStr, out float zero) && float.TryParse(spanStr, out float span)) + ushort address = new ushort(); + switch (StationId) { - Task.Run(async () => - { - await WriteFloatAsync(_plcConfig.FlowCalibZero, zero); - await WriteFloatAsync(_plcConfig.FlowCalibSpan, span); - MessageBox.Show("流量校准系数已写入", "完成"); - }); + case 1: + { + address = _plcConfig.BigFlow1; + break; + } + case 2: + { + address = _plcConfig.BigFlow2; + break; + } + case 3: + { + address = _plcConfig.BigFlow3; + break; + } } + + Task.Run(async () => + { + await _plcService.WriteCoilAsync(address, true); + }); + } + + private void OpenFlowCalibration2() + { + ushort address = new ushort(); + switch (StationId) + { + case 1: + { + address = _plcConfig.SmallFlow1; + break; + } + case 2: + { + address = _plcConfig.SmallFlow2; + break; + } + case 3: + { + address = _plcConfig.SmallFlow3; + break; + } + } + + Task.Run(async () => + { + await _plcService.WriteCoilAsync(address, true); + }); } private int _stationId; @@ -514,6 +561,8 @@ namespace MembranePoreTester.ViewModels public ICommand OpenFlowCalibCommand { get; } + + public ICommand OpenFlowCalibCommand2 { get; } public ICommand ExportCommand { get; } diff --git a/ViewModels/ViewModelBase.cs b/ViewModels/ViewModelBase.cs index 1d235d9..2932ae5 100644 --- a/ViewModels/ViewModelBase.cs +++ b/ViewModels/ViewModelBase.cs @@ -1,5 +1,6 @@ using MembranePoreTester.Communication; using System.ComponentModel; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace MembranePoreTester.ViewModels @@ -58,6 +59,75 @@ namespace MembranePoreTester.ViewModels new System.Windows.DependencyObject()); } + + + + + + + + + + // ========== 错误抑制机制 ========== + private class ErrorRecord + { + public bool HasError { get; set; } + public DateTime LastErrorTime { get; set; } + } + + private readonly Dictionary _errorRecords = new(); + + /// + /// 安全执行异步操作,自动抑制重复错误 + /// + protected async Task SafeExecuteAsync(string operationKey, Func action, int suppressSeconds = 3) + { + // 检查是否需要抑制 + if (_errorRecords.TryGetValue(operationKey, out var record) && record.HasError) + { + if ((DateTime.Now - record.LastErrorTime).TotalSeconds < suppressSeconds) + { + return false; + } + } + + try + { + await action(); + + // 成功,清除错误记录 + if (_errorRecords.TryGetValue(operationKey, out var successRecord)) + { + successRecord.HasError = false; + } + return true; + } + catch (Exception ex) + { + // 记录错误 + if (!_errorRecords.TryGetValue(operationKey, out var errorRecord)) + { + errorRecord = new ErrorRecord(); + _errorRecords[operationKey] = errorRecord; + } + + errorRecord.HasError = true; + errorRecord.LastErrorTime = DateTime.Now; + + // 静默记录到输出窗口 + Debug.WriteLine($"[{DateTime.Now:HH:mm:ss}] {operationKey} 失败: {ex.Message}"); + return false; + } + } + + + + + + + + + // 新增:检查是否已释放 protected bool IsDisposed => _disposed; diff --git a/Views/BubblePointView.xaml b/Views/BubblePointView.xaml index 322dc52..b48e0c1 100644 --- a/Views/BubblePointView.xaml +++ b/Views/BubblePointView.xaml @@ -94,7 +94,8 @@ -