Files
VacuumPressureMembranePoreS…/ViewModels/PoreDistributionViewModel.cs
2026-04-14 21:40:09 +08:00

1364 lines
48 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using MembranePoreTester.Communication;
using MembranePoreTester.Helpers;
using MembranePoreTester.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Input;
namespace MembranePoreTester.ViewModels
{
public class PoreDistributionViewModel : ViewModelBase, IStationViewModel
{
#region
public class PressureModeItem
{
public string Text { get; set; }
public int Value { get; set; }
public override string ToString()
{
return Text; // 直接返回要显示的文本
}
}
private PressureModeItem _selectedPressureMode;
private List<PressureModeItem> _pressureModeList;
public List<PressureModeItem> PressureModeList => _pressureModeList ??= new List<PressureModeItem>
{
new PressureModeItem { Text = "小流量", Value = 1 },
new PressureModeItem { Text = "大流量", Value = 0 }
};
public PressureModeItem SelectedPressureMode
{
get => _selectedPressureMode;
set
{
if (SetProperty(ref _selectedPressureMode, value))
{
Task.Run(async () => await WriteFlowModeAsync(value?.Text ?? "小流量"));
}
}
}
#endregion
// 添加字段
private System.Windows.Threading.DispatcherTimer _autoCollectTimer;
private bool _isCollecting = false;
// 添加公共方法供工位调用
//public void StartCollecting()
//{
// System.Diagnostics.Debug.WriteLine($"StartCollecting 工位{StationId}, IsActive={IsActive}");
// //if (!IsActive)
// //{
// // // 如果当前不在孔分布界面,不启动采集
// // return;
// //}
// if (_isCollecting) return;
// _isCollecting = true;
// _autoCollectTimer = new System.Windows.Threading.DispatcherTimer
// {
// Interval = TimeSpan.FromSeconds(1)
// };
// _autoCollectTimer.Tick += AutoCollectTimer_Tick;
// _autoCollectTimer.Start();
//}
public void StartCollecting()
{
// 不再依赖 IsActive定时器始终启动
if (_isCollecting) return;
_isCollecting = true;
_autoCollectTimer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_autoCollectTimer.Tick += AutoCollectTimer_Tick;
_autoCollectTimer.Start();
}
public void StopCollecting()
{
_isCollecting = false;
_autoCollectTimer?.Stop();
_autoCollectTimer = null;
}
private async void AutoCollectTimer_Tick(object sender, EventArgs e)
{
//if (!IsActive) return;
try
{
// 1. 读取当前压力
float rawPressure = await _plcService.ReadPressureAsync(StationId);
double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2);
// 2. 读取当前工位的加压上限(实时)
ushort upperLimitAddress = StationId == 1 ? _plcConfig.PressureUpperLimit
: StationId == 2 ? _plcConfig.PressureUpperLimit2
: _plcConfig.PressureUpperLimit3;
double pressureUpperLimit = await _plcService.ReadFloatAsync(upperLimitAddress);
// 3. 如果压力已达到或超过上限,停止采集
if (pressure >= pressureUpperLimit * 1000 - 10 * 1000 && pressure > 0)
{
StopCollecting();
return; // 不再添加当前数据点
}
// 2. 读取当前模式对应的流量
double flow = 0;
if (TestMode.Contains("湿膜"))
{
float rawFlow = await _plcService.ReadWetFlowAsync(StationId);
flow = Math.Round(ConvertFlowByMode(rawFlow), 3);
}
else
{
float rawFlow = await _plcService.ReadDryFlowAsync(StationId);
flow = Math.Round(ConvertFlowByMode(rawFlow), 3);
}
float Pressure = await _plcService.ReadPressureAsync(StationId);
CurrentPressure = Math.Round(rawPressure, 2);
if (pressure <= 0 || flow <= 0)
return;
CurrentFlow = flow;
// 3. 查找是否已有相同压力点(按两位小数匹配),有则更新对应流量,否则新增
var existing = Record.DataPoints.FirstOrDefault(p => Math.Abs(p.Pressure - pressure) < 0.001);
if (existing != null)
{
if (TestMode != null && TestMode.Contains("湿膜"))
existing.WetFlow = flow;
else
existing.DryFlow = flow;
}
else
{
var newPoint = new Models.DataPoint
{
Pressure = Math.Round(pressure, 2),
WetFlow = TestMode != null && TestMode.Contains("湿膜") ? flow : 0,
DryFlow = TestMode != null && TestMode.Contains("干膜") ? flow : 0
};
Record.DataPoints.Add(newPoint);
}
// 4. 刷新曲线
UpdatePlot();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"自动采集失败: {ex.Message}");
}
}
/// <summary>
/// 清空所有数据点,重置表格和曲线
/// </summary>
public void ClearData()
{
// 清空数据点集合
Record.DataPoints.Clear();
PlotModel = new PlotModel();
// 重置曲线
UpdatePlot();
// 重置计算结果
AveragePoreSize = 0;
RangePercentage = 0;
// 可选:显示提示
System.Diagnostics.Debug.WriteLine("孔分布数据已清空");
}
private bool _isActive;
public bool IsActive
{
get => _isActive;
set => SetProperty(ref _isActive, value);
}
private PoreDistributionRecord _record = new();
private TestLiquid _selectedLiquid;
private bool _isCustomLiquid;
private double _customSurfaceTension = 30.0;
private double _lowerPore = 0.05;
private double _upperPore = 0.65;
private double _rangePercentage;
// 添加字段
private readonly IPlcService _plcService;
private readonly PlcConfiguration _plcConfig;
private Models.DataPoint _selectedDataPoint;
// 添加属性
public Models.DataPoint SelectedDataPoint
{
get => _selectedDataPoint;
set => SetProperty(ref _selectedDataPoint, value);
}
public ICommand ReadPlcCommand { get; }
public PoreDistributionRecord Record
{
get => _record;
set
{
if (SetProperty(ref _record, value))
{
// 当 Record 替换时(例如从数据库加载),重新订阅其 DataPoints 集合
HookDataPointsCollection(_record?.DataPoints);
// 同步液体选择
if (_record?.Liquid != null)
{
var matchedLiquid = Liquids.FirstOrDefault(l => l.Name == _record.Liquid.Name);
SelectedLiquid = matchedLiquid ?? _record.Liquid;
}
else
{
SelectedLiquid = Liquids.FirstOrDefault();
}
}
}
}
public IReadOnlyList<TestLiquid> Liquids => TestLiquid.Predefined;
public List<string> PressureUnits => new() { "Pa", "cmHg", "psi" };
public List<string> MembraneTypes => new() { "平板膜", "中空纤维膜" };
public TestLiquid SelectedLiquid
{
get => _selectedLiquid;
set
{
if (SetProperty(ref _selectedLiquid, value))
{
Record.Liquid = value;
}
}
}
public bool IsCustomLiquid
{
get => _isCustomLiquid;
set
{
if (SetProperty(ref _isCustomLiquid, value))
{
UpdateCustomLiquid();
}
}
}
public double CustomSurfaceTension
{
get => _customSurfaceTension;
set
{
if (SetProperty(ref _customSurfaceTension, value))
{
UpdateCustomLiquid();
}
}
}
public double LowerPore
{
get => _lowerPore;
set => SetProperty(ref _lowerPore, value);
}
public double UpperPore
{
get => _upperPore;
set => SetProperty(ref _upperPore, value);
}
public double RangePercentage
{
get => _rangePercentage;
set => SetProperty(ref _rangePercentage, value);
}
public ICommand AddDataPointCommand { get; }
public ICommand RemoveDataPointCommand { get; }
public ICommand CalculateCommand { get; }
public ICommand GenerateReportCommand { get; }
private System.Windows.Threading.DispatcherTimer _timer; // 添加定时器字段
public PoreDistributionViewModel()
{
_plcService = App.PlcService;
_plcConfig = App.PlcConfig;
AddDataPointCommand = new RelayCommand(AddDataPoint);
RemoveDataPointCommand = new RelayCommand(RemoveDataPoint, () => Record.DataPoints.Any());
CalculateCommand = new RelayCommand(Calculate);
GenerateReportCommand = new RelayCommand(GenerateReport);
SelectedLiquid = Liquids[0];
Record.SampleType = "中空纤维膜";
Record.PressureUnit = PressureUnits[0]; // 默认 "Pa"
ReadPlcCommand = new RelayCommand(async () => await ReadPlcAsync());
SaveCommand = new RelayCommand(SaveToDatabase);
ExportCommand = new RelayCommand(ExportToExcel);
OpenFlowCalibCommand = new RelayCommand(OpenFlowCalibration);
OpenFlowCalibCommand2 = new RelayCommand(OpenFlowCalibration2);
// 订阅集合变更并为每个项订阅属性变化,保证 DataGrid 和 Plot 在项内容变化时同步刷新
HookDataPointsCollection(Record.DataPoints);
ClearAllCommand = new RelayCommand(ClearAllData);
RemoveDataPointCommand = new RelayCommand(RemoveSelectedDataPoint, () => SelectedDataPoint != null);
// 启动定时器,每秒读取一次
_timer = new System.Windows.Threading.DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += async (s, e) =>
{
if (StationId > 0 && !IsDisposed)
{
await ReadSpeedRateAsync();
}
};
_timer.Start();
// 延迟2秒后读取确保连接稳定
Task.Delay(2000).ContinueWith(async _ =>
{
await ReadPressureModeAsync();
}, TaskScheduler.Default);
}
private void ClearAllData()
{
ClearData();
}
private string _speedRate1;
public string SpeedRate1
{
get => _speedRate1;
set => SetProperty(ref _speedRate1, value);
}
private async Task ReadSpeedRateAsync()
{
try
{
float speedRate = 0;
ushort address = 0;
switch (StationId)
{
case 1:
address = _plcConfig.HPCoeff11;
speedRate = await _plcService.ReadFloatAsync(address);
SpeedRate1 = speedRate.ToString("F3");
break;
case 2:
address = _plcConfig.HPCoeff12;
speedRate = await _plcService.ReadFloatAsync(address);
SpeedRate1 = speedRate.ToString("F3");
break;
case 3:
address = _plcConfig.HPCoeff13;
speedRate = await _plcService.ReadFloatAsync(address);
SpeedRate1 = speedRate.ToString("F3");
break;
}
// 如果需要刷新 Record 中的绑定(可选)
OnPropertyChanged(nameof(SpeedRate1));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"读取加压速率失败: {ex.Message}");
}
}
private async Task ReadPressureModeAsync()
{
await SafeExecuteAsync($"ReadPressureModeAsync{StationId}", async () =>
{
try
{
ushort[] values = await _plcService.ReadHoldingRegistersAsync(StationId == 1 ? _plcConfig.FlowModeRegister : StationId == 2 ? _plcConfig.FlowModeRegister2 : _plcConfig.FlowModeRegister3, 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
{
get => _selectedPressureMode?.Text ?? "小流量";
set
{
var mode = PressureModeList.FirstOrDefault(m => m.Text == value);
if (mode != null)
{
SelectedPressureMode = mode;
}
}
}
private async Task ReadPlcAsync()
{
await SafeExecuteAsync($"ManualRead_Station{StationId}", async () =>
{
// 始终读取压力
float rawPressure = await _plcService.ReadPressureAsync(StationId);
double pressure = Math.Round(rawPressure * _plcConfig.PressureFactor, 2);
if (SelectedDataPoint != null)
{
// 更新选中行
SelectedDataPoint.Pressure = pressure;
if (TestMode == "湿膜")
{
float rawWet = await _plcService.ReadWetFlowAsync(StationId);
SelectedDataPoint.WetFlow = ConvertFlowByMode(rawWet);
}
else
{
float rawDry = await _plcService.ReadDryFlowAsync(StationId);
SelectedDataPoint.DryFlow = ConvertFlowByMode(rawDry);
}
}
else
{
// 新增一行
var newPoint = new Models.DataPoint { Pressure = pressure };
if (TestMode == "湿膜")
{
float rawWet = await _plcService.ReadWetFlowAsync(StationId);
newPoint.WetFlow = ConvertFlowByMode(rawWet);
}
else
{
float rawDry = await _plcService.ReadDryFlowAsync(StationId);
newPoint.DryFlow = ConvertFlowByMode(rawDry);
}
Record.DataPoints.Add(newPoint);
}
});
}
private void AddDataPoint()
{
Record.DataPoints.Add(new Models.DataPoint());
}
private void RemoveDataPoint()
{
if (Record.DataPoints.Any())
Record.DataPoints.RemoveAt(Record.DataPoints.Count - 1);
}
private void UpdateCustomLiquid()
{
if (IsCustomLiquid)
{
Record.Liquid = new TestLiquid
{
Name = "自定义",
SurfaceTension = CustomSurfaceTension
};
}
}
// 监听 DataPoints 集合与其子项的属性变更,确保 UI 同步更新
private void HookDataPointsCollection(ObservableCollection<Models.DataPoint> collection)
{
if (collection == null) return;
// 先解绑已有(避免重复订阅)
collection.CollectionChanged -= DataPoints_CollectionChanged;
collection.CollectionChanged += DataPoints_CollectionChanged;
// 为已有项订阅 PropertyChanged
foreach (var item in collection)
{
if (item is INotifyPropertyChanged inpc)
{
inpc.PropertyChanged -= DataPoint_PropertyChanged;
inpc.PropertyChanged += DataPoint_PropertyChanged;
}
}
}
private void DataPoints_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (var old in e.OldItems)
{
if (old is INotifyPropertyChanged inpc)
inpc.PropertyChanged -= DataPoint_PropertyChanged;
}
}
if (e.NewItems != null)
{
foreach (var nw in e.NewItems)
{
if (nw is INotifyPropertyChanged inpc)
{
inpc.PropertyChanged -= DataPoint_PropertyChanged;
inpc.PropertyChanged += DataPoint_PropertyChanged;
}
}
}
// 集合结构变化时刷新曲线
UpdatePlot();
}
private void DataPoint_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// 某个数据点属性改变时也刷新曲线
UpdatePlot();
}
// 添加私有字段和公共属性
private double _averagePoreSize;
public double AveragePoreSize
{
get => _averagePoreSize;
set => SetProperty(ref _averagePoreSize, value);
}
//修改 Calculate 方法:
private void Calculate()
{
// 先清洗数据并替换到绑定的集合中确保DataGrid和曲线显示清洗后的数据
var originalPoints = Record.DataPoints.ToList();
System.Diagnostics.Debug.WriteLine("=== 清洗前的数据点 ===");
foreach (var p in originalPoints)
{
System.Diagnostics.Debug.WriteLine($"P={p.Pressure}, Wet={p.WetFlow}, Dry={p.DryFlow}");
}
//var cleanedPoints = CleanDataPoints(originalPoints);
//if (cleanedPoints.Count < 2)
//{
// MessageBox.Show("有效数据点不足,至少需要 2 个数据点进行计算。");
// return;
//}
//int invalidCount = originalPoints.Count - cleanedPoints.Count;
// 用清洗后的点替换 ObservableCollection 的内容触发UI更新
Record.DataPoints.Clear();
foreach (var p in originalPoints)
Record.DataPoints.Add(p);
// 刷新曲线以显示清洗后的数据
UpdatePlot();
// 使用清洗后的集合进行计算
AveragePoreSize = PoreDistributionAnalysis.CalculateAveragePore(
Record.DataPoints, Record.PressureUnit, Record.Liquid);
RangePercentage = PoreDistributionAnalysis.CalculatePoreRangePercentage(
Record.DataPoints, Record.PressureUnit, Record.Liquid, LowerPore, UpperPore);
//if (invalidCount > 0)
//{
// MessageBox.Show($"已自动过滤 {invalidCount} 个无效数据点");
// System.Diagnostics.Debug.WriteLine($"已自动过滤 {invalidCount} 个无效数据点");
//}
}
//private void Calculate()
//{
// // 直接使用原始数据,不清洗
// if (Record.DataPoints.Count < 2)
// {
// MessageBox.Show("有效数据点不足,至少需要 2 个数据点进行计算。");
// return;
// }
// // 注意:不清洗,也不改变 Record.DataPoints 的顺序(但计算函数内部会自己排序)
// // 直接计算
// AveragePoreSize = PoreDistributionAnalysis.CalculateAveragePore(
// Record.DataPoints, Record.PressureUnit, Record.Liquid);
// RangePercentage = PoreDistributionAnalysis.CalculatePoreRangePercentage(
// Record.DataPoints, Record.PressureUnit, Record.Liquid, LowerPore, UpperPore);
// // 可选:提示用户当前使用了原始数据(不清洗)
// // MessageBox.Show("已使用原始数据(未清洗)进行计算");
//}
//private List<Models.DataPoint> CleanDataPoints(IEnumerable<Models.DataPoint> points)
//{
// return points
// .Where(p => p.Pressure > 0.001) // 移除压力接近0的点
// .Where(p => !(Math.Abs(p.WetFlow) < 0.001 && Math.Abs(p.DryFlow) < 0.001)) // 移除双零流量点
// .GroupBy(p => Math.Round(p.Pressure, 2)) // 按压力四舍五入分组(避免重复压力)
// .Select(g => new Models.DataPoint
// {
// Pressure = g.Key,
// WetFlow = g.Max(x => x.WetFlow), // 取最大值避免0覆盖有效值
// DryFlow = g.Max(x => x.DryFlow)
// })
// .OrderBy(p => p.Pressure)
// .ToList();
//}
private List<Models.DataPoint> CleanDataPoints(IEnumerable<Models.DataPoint> points)
{
// 第一步基础清洗移除压力≤0、双零流量、合并相同压力取最大流量
var cleaned = points
.Where(p => p.Pressure > 0.001)
.Where(p => !(Math.Abs(p.WetFlow) < 0.001 && Math.Abs(p.DryFlow) < 0.001))
.GroupBy(p => Math.Round(p.Pressure, 2))
.Select(g => new Models.DataPoint
{
Pressure = g.Key,
WetFlow = g.Max(x => x.WetFlow),
DryFlow = g.Max(x => x.DryFlow)
})
.OrderBy(p => p.Pressure)
.ToList();
if (cleaned.Count < 4) return cleaned; // 点数太少,不进行高级剔除
// 第二步仅对湿膜流量进行异常孤立点剔除干膜保持原样因为干膜可能有很多0和少量非零
var result = new List<Models.DataPoint>();
const double threshold = 3.0; // 孤立高点阈值(倍),比之前放宽
for (int i = 0; i < cleaned.Count; i++)
{
var current = cleaned[i];
bool isAbnormal = false;
// 仅检查中间的点(非首尾),且只检查湿膜流量
if (i > 0 && i < cleaned.Count - 1)
{
double prevWet = cleaned[i - 1].WetFlow;
double nextWet = cleaned[i + 1].WetFlow;
double maxNeighbor = Math.Max(prevWet, nextWet);
double minNeighbor = Math.Min(prevWet, nextWet);
// 如果当前湿膜流量远大于前后邻居的最大值孤立高峰且前后邻居都不为0
if (maxNeighbor > 0 && current.WetFlow > maxNeighbor * threshold)
isAbnormal = true;
// 如果当前湿膜流量远小于前后邻居的最小值(孤立低谷),一般很少见,但保留
if (minNeighbor > 0 && current.WetFlow < minNeighbor / threshold)
isAbnormal = true;
}
// 不剔除干膜中的非零点(即使孤立也不删)
if (!isAbnormal)
result.Add(current);
}
// 如果全部被误判为异常,则回退到原始清洗结果
return result.Count == 0 ? cleaned : result;
}
//private void GenerateReport()
//{
// ReportGenerator.GeneratePoreDistributionReport(Record);
//}
private void GenerateReport()
{
// 可选:先清洗数据再生成报告(但不改变原集合)
var cleanedPoints = CleanDataPoints(Record.DataPoints);
if (cleanedPoints.Count < 2)
{
MessageBox.Show("有效数据点不足,无法生成报告。");
return;
}
// 临时替换 Record 中的数据点用于报告(不影响界面)
var originalPoints = Record.DataPoints.ToList();
Record.DataPoints.Clear();
foreach (var p in cleanedPoints)
Record.DataPoints.Add(p);
ReportGenerator.GeneratePoreDistributionReport(Record, PlotModel);
// 恢复原始数据
Record.DataPoints.Clear();
foreach (var p in originalPoints)
Record.DataPoints.Add(p);
//// 清空现有数据
//Record.DataPoints.Clear();
//Random rand = new Random();
//// 生成湿膜数据模拟压力从0到100流量先增后减
//for (double pressure = 0; pressure <= 100; pressure += 5)
//{
// // 模拟流量曲线先上升后下降峰值在40-60之间
// double wetFlow = 0;
// if (pressure < 30)
// wetFlow = pressure * 1.5; // 上升段
// else if (pressure < 60)
// wetFlow = 45 - (pressure - 30) * 0.5; // 下降段
// else
// wetFlow = 30 - (pressure - 60) * 0.8; // 继续下降
// // 添加随机波动 (±15%)
// wetFlow = wetFlow * (0.85 + rand.NextDouble() * 0.3);
// wetFlow = Math.Round(Math.Max(0, wetFlow), 2);
// // 干膜数据(稍低于湿膜)
// double dryFlow = wetFlow * (0.6 + rand.NextDouble() * 0.3);
// dryFlow = Math.Round(dryFlow, 2);
// Record.DataPoints.Add(new Models.DataPoint
// {
// Pressure = Math.Round(pressure, 2),
// WetFlow = wetFlow,
// DryFlow = dryFlow
// });
//}
//// 设置一些测试基本参数
//Record.SampleType = "平板膜";
//Record.SampleSpec = "φ47mm";
//Record.RoomTemperature = 22.5;
//Record.SoakingTime = 2.0;
//Record.Liquid = TestLiquid.Predefined.FirstOrDefault();
//Record.LiquidManufacturer = "测试厂家";
//Record.PressureUnit = "kPa";
//Record.BubblePointPressure = 45.6;
//Record.AveragePoreSize = 0.856;
//Record.TestDate = DateTime.Now;
//Record.Tester = "测试员";
//// 刷新曲线显示
//UpdatePlot();
//ReportGenerator.GeneratePoreDistributionReport(Record, PlotModel);
}
private void OpenFlowCalibration()
{
// 弹出确认对话框
var result = MessageBox.Show(
"确定要进行流量校准吗此操作将向PLC写入校准信号。",
"确认校准",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
ushort address = new ushort();
switch (StationId)
{
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);
//MessageBox.Show("校准成功!");
});
}
private void OpenFlowCalibration2()
{
// 弹出确认对话框
var result = MessageBox.Show(
"确定要进行流量校准吗此操作将向PLC写入校准信号。",
"确认校准",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
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);
//MessageBox.Show("校准成功!");
});
}
private int _stationId;
public int StationId
{
get => _stationId;
set => SetProperty(ref _stationId, value);
}
// 初始化方法:在 MainViewModel 创建 StationItem 后调用,确保 StationId 已设置
public void Initialize(int stationId)
{
StationId = stationId;
// 延迟调用读取模式确保PLC连接稳定
Task.Run(async () =>
{
await Task.Delay(200);
await ReadPressureModeAsync();
});
}
public void SaveToDatabase()
{
var entity = new PoreDistributionEntity
{
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 ?? 0,
LiquidManufacturer = Record.LiquidManufacturer ?? "缺省值",
PressureUnit = Record.PressureUnit,
BubblePointPressure = Record.BubblePointPressure,
AveragePoreSize = AveragePoreSize, // 使用计算后的属性
DataPoints = Record.DataPoints.Select(dp => new DataPointEntity
{
Pressure = dp.Pressure,
WetFlow = dp.WetFlow,
DryFlow = dp.DryFlow
}).ToList()
};
using var db = new AppDbContext();
db.PoreDistributionRecords.Add(entity);
db.SaveChanges();
MessageBox.Show("保存成功!", "提示");
}
public void LoadFromDatabase(int recordId)
{
using var db = new AppDbContext();
var entity = db.PoreDistributionRecords
.Include(p => p.DataPoints)
.FirstOrDefault(p => p.Id == recordId);
if (entity == null) return;
Record.SampleType = entity.SampleType ?? "中空纤维膜";
Record.SampleSpec = entity.SampleSpec;
Record.RoomTemperature = entity.RoomTemperature;
Record.SoakingTime = entity.SoakingTime;
Record.Liquid = TestLiquid.Predefined.FirstOrDefault(l => l.Name == entity.LiquidName)
?? new TestLiquid { Name = entity.LiquidName, SurfaceTension = entity.LiquidSurfaceTension };
Record.LiquidManufacturer = entity.LiquidManufacturer;
Record.PressureUnit = entity.PressureUnit;
Record.BubblePointPressure = entity.BubblePointPressure;
Record.TestDate = entity.TestDate;
Record.Tester = entity.Tester;
Record.DataPoints.Clear();
foreach (var dp in entity.DataPoints)
{
Record.DataPoints.Add(new Models.DataPoint
{
Pressure = dp.Pressure,
WetFlow = dp.WetFlow,
DryFlow = dp.DryFlow
});
}
// 同步液体选择
if (Record.Liquid != null)
{
var matchedLiquid = Liquids.FirstOrDefault(l => l.Name == Record.Liquid.Name);
SelectedLiquid = matchedLiquid ?? Record.Liquid;
}
else
{
SelectedLiquid = Liquids.FirstOrDefault();
}
// 重新计算平均孔径和分布(触发计算命令)
Calculate();
}
public ICommand SaveCommand { get; }
public ICommand OpenFlowCalibCommand { get; }
public ICommand OpenFlowCalibCommand2 { get; }
public ICommand ExportCommand { get; }
private void ExportToExcel()
{
// 使用当前数据点(已通过手动删除或自动清洗,保证有效性)
var dataPoints = Record.DataPoints.ToList();
if (dataPoints.Count == 0)
{
MessageBox.Show("没有数据可导出。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var saveFileDialog = new SaveFileDialog
{
Filter = "Excel文件|*.xlsx",
FileName = $"孔分布_工位{StationId}_{DateTime.Now:yyyyMMddHHmmss}.xlsx"
};
if (saveFileDialog.ShowDialog() == true)
{
var entity = new PoreDistributionEntity
{
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 ?? 0,
LiquidManufacturer = Record.LiquidManufacturer ?? "缺省值",
PressureUnit = Record.PressureUnit,
BubblePointPressure = Record.BubblePointPressure,
AveragePoreSize = AveragePoreSize, // 已计算的平均孔径
DataPoints = dataPoints.Select(dp => new DataPointEntity
{
Pressure = dp.Pressure,
WetFlow = dp.WetFlow,
DryFlow = dp.DryFlow
}).ToList()
};
ExportHelper.ExportPoreDistribution(entity, saveFileDialog.FileName);
MessageBox.Show("导出成功", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private string _testMode = "湿膜"; // 默认湿膜
public string TestMode
{
get => _testMode;
set
{
if (SetProperty(ref _testMode, value))
{
// 切换测试模式时刷新曲线和相关逻辑
UpdatePlot();
}
}
}
private OxyPlot.PlotModel _plotModel;
public OxyPlot.PlotModel PlotModel
{
get => _plotModel;
set => SetProperty(ref _plotModel, value);
}
// 用于保护绘图更新,避免并发导致渲染丢失
private readonly object _plotLock = new();
private async Task WriteFlowModeAsync(string mode)
{
ushort val = mode.ToString().Contains("大流量") ? (ushort)0 : (ushort)1;
try
{
if (StationId == 1)
await _plcService.WriteSingleRegisterAsync(_plcConfig.FlowModeRegister, val);
else if (StationId == 2)
await _plcService.WriteSingleRegisterAsync(_plcConfig.FlowModeRegister2, val);
else if (StationId == 3)
await _plcService.WriteSingleRegisterAsync(_plcConfig.FlowModeRegister3, val);
}
catch (Exception ex)
{
MessageBox.Show($"写流量模式失败: {ex.Message}");
}
}
//private void UpdatePlot()
//{
// // 确保在 UI 线程执行
// if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess())
// {
// Application.Current.Dispatcher.BeginInvoke((Action)UpdatePlot);
// return;
// }
// var sorted = Record.DataPoints.OrderBy(p => p.Pressure).ToList();
// if (sorted.Count == 0)
// {
// PlotModel = null;
// return;
// }
// var model = new PlotModel
// {
// Title = "流量-压力曲线",
// TitleFontSize = 14,
// TitleFontWeight = 1
// };
// model.Axes.Add(new LinearAxis
// {
// Position = AxisPosition.Bottom,
// Title = "压力 (kPa)",
// TitleFontSize = 12
// });
// model.Axes.Add(new LinearAxis
// {
// Position = AxisPosition.Left,
// Title = "流量 (L/min)",
// TitleFontSize = 12,
// Minimum = 0,
// MajorGridlineStyle = LineStyle.Solid,
// MinorGridlineStyle = LineStyle.Dot
// });
// // 根据当前模式绘制对应的曲线(只绘制流量 > 0 的点)
// if (TestMode != null && TestMode.Contains("湿膜"))
// {
// var wetSeries = new LineSeries
// {
// Title = "湿膜流量 (Wet Flow)",
// Color = OxyColors.Blue,
// MarkerType = MarkerType.None,
// StrokeThickness = 1
// };
// foreach (var dp in sorted.Where(p => p.WetFlow > 0))
// wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow));
// if (wetSeries.Points.Count > 0)
// model.Series.Add(wetSeries);
// }
// else if (TestMode != null && TestMode.Contains("干膜"))
// {
// var drySeries = new LineSeries
// {
// Title = "干膜流量 (Dry Flow)",
// Color = OxyColors.Red,
// MarkerType = MarkerType.None,
// StrokeThickness = 1
// };
// foreach (var dp in sorted.Where(p => p.DryFlow > 0))
// drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow));
// if (drySeries.Points.Count > 0)
// model.Series.Add(drySeries);
// }
// else
// {
// // “全部”模式:同时显示两条曲线,但仍过滤掉 0 值点
// var wetSeries = new LineSeries
// {
// Title = "湿膜流量 (Wet Flow)",
// Color = OxyColors.Blue,
// MarkerType = MarkerType.None,
// StrokeThickness = 1
// };
// var drySeries = new LineSeries
// {
// Title = "干膜流量 (Dry Flow)",
// Color = OxyColors.Red,
// MarkerType = MarkerType.None,
// StrokeThickness = 1
// };
// foreach (var dp in sorted)
// {
// if (dp.WetFlow > 0) wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow));
// if (dp.DryFlow > 0) drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow));
// }
// if (wetSeries.Points.Count > 0) model.Series.Add(wetSeries);
// if (drySeries.Points.Count > 0) model.Series.Add(drySeries);
// }
// PlotModel = model;
//}
public void RefreshPlot()
{
UpdatePlot();
}
private void UpdatePlot()
{
// 确保在 UI 线程执行
if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.BeginInvoke((Action)UpdatePlot);
return;
}
var sorted = Record.DataPoints.OrderBy(p => p.Pressure).ToList();
//if (sorted.Count == 0)
//{
// PlotModel = null;
// return;
//}
var model = new PlotModel
{
Title = "流量-压力曲线",
TitleFontSize = 14,
TitleFontWeight = 1
};
model.Axes.Add(new LinearAxis
{
Position = AxisPosition.Bottom,
Title = "压力 (Pa)",
TitleFontSize = 12
});
model.Axes.Add(new LinearAxis
{
Position = AxisPosition.Left,
Title = "流量 (L/min)",
TitleFontSize = 12,
Minimum = 0,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
});
// 干膜半流量曲线(绿色虚线,每个压力点干膜流量的一半)
var halfDrySeries = new LineSeries
{
Title = "干膜半流量 (Half of Dry Flow)",
Color = OxyColors.Green,
MarkerType = MarkerType.None,
StrokeThickness = 1,
LineStyle = LineStyle.Dash
};
foreach (var dp in sorted.Where(p => p.DryFlow > 0))
{
halfDrySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow / 2.0));
}
// 根据当前模式绘制对应的曲线(只绘制流量 > 0 的点)
if (TestMode != null && TestMode.Contains("湿膜"))
{
var wetSeries = new LineSeries
{
Title = "湿膜流量 (Wet Flow)",
Color = OxyColors.Blue,
MarkerType = MarkerType.None,
StrokeThickness = 1
};
foreach (var dp in sorted.Where(p => p.WetFlow > 0))
wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow));
if (wetSeries.Points.Count > 0)
model.Series.Add(wetSeries);
// 湿膜模式下也显示半流量线(便于观察交点)
if (halfDrySeries.Points.Count > 0)
model.Series.Add(halfDrySeries);
}
else if (TestMode != null && TestMode.Contains("干膜"))
{
var drySeries = new LineSeries
{
Title = "干膜流量 (Dry Flow)",
Color = OxyColors.Red,
MarkerType = MarkerType.None,
StrokeThickness = 1
};
foreach (var dp in sorted.Where(p => p.DryFlow > 0))
drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow));
if (drySeries.Points.Count > 0)
model.Series.Add(drySeries);
if (halfDrySeries.Points.Count > 0)
model.Series.Add(halfDrySeries);
}
else
{
// “全部”模式:同时显示两条曲线,但仍过滤掉 0 值点
var wetSeries = new LineSeries
{
Title = "湿膜流量 (Wet Flow)",
Color = OxyColors.Blue,
MarkerType = MarkerType.None,
StrokeThickness = 1
};
var drySeries = new LineSeries
{
Title = "干膜流量 (Dry Flow)",
Color = OxyColors.Red,
MarkerType = MarkerType.None,
StrokeThickness = 1
};
foreach (var dp in sorted)
{
if (dp.WetFlow > 0) wetSeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.WetFlow));
if (dp.DryFlow > 0) drySeries.Points.Add(new OxyPlot.DataPoint(dp.Pressure, dp.DryFlow));
}
if (wetSeries.Points.Count > 0) model.Series.Add(wetSeries);
if (drySeries.Points.Count > 0) model.Series.Add(drySeries);
if (halfDrySeries.Points.Count > 0) model.Series.Add(halfDrySeries);
}
PlotModel = model;
}
public ICommand ClearAllCommand { get; }
private double _currentFlow;
public double CurrentFlow
{
get => _currentFlow;
set => SetProperty(ref _currentFlow, value);
}
private double _currentPressure;
public double CurrentPressure
{
get => _currentPressure;
set => SetProperty(ref _currentPressure, value);
}
// 修改命令初始化(构造函数中)
// 实现删除方法
private void RemoveSelectedDataPoint()
{
if (SelectedDataPoint != null)
{
Record.DataPoints.Remove(SelectedDataPoint);
SelectedDataPoint = null; // 清除选中状态
UpdatePlot(); // 刷新曲线
}
}
public List<string> TestModes { get; } = new List<string> {"全部", "湿膜", "干膜" };
private double ConvertFlowByMode(double rawFlow)
{
// 如果当前选择的是小流量模式则需要除以1000因为PLC可能存储的是mL/min
if (SelectedPressureMode != null && SelectedPressureMode.Text.Contains("小流量"))
return rawFlow / 1000.0;
else
return rawFlow;
}
}
}