添加项目文件。
This commit is contained in:
275
ViewModels/StationViewModel.cs
Normal file
275
ViewModels/StationViewModel.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using OxyPlot;
|
||||
using OxyPlot.Series;
|
||||
using TabletTester2025.Models;
|
||||
using TabletTester2025.Services;
|
||||
|
||||
namespace TabletTester2025.ViewModels
|
||||
{
|
||||
public partial class StationViewModel : ObservableObject
|
||||
{
|
||||
private readonly IPlcService _plc;
|
||||
private readonly PlcConfiguration _plcConfig;
|
||||
private readonly DatabaseService _db;
|
||||
private readonly ExcelExportService _excel;
|
||||
private readonly AlarmService _alarm;
|
||||
private DispatcherTimer _disintegrationTimer;
|
||||
|
||||
public int StationId { get; }
|
||||
|
||||
[ObservableProperty] private TestType _currentTest;
|
||||
[ObservableProperty] private TestPhase _phase = TestPhase.Idle;
|
||||
|
||||
// 硬度
|
||||
[ObservableProperty] private double _hardnessValue;
|
||||
[ObservableProperty] private bool _hardnessPass;
|
||||
[ObservableProperty] private double _hardnessAvg;
|
||||
[ObservableProperty] private double _hardnessRSD;
|
||||
private List<double> _hardnessResults = new();
|
||||
|
||||
// 脆碎度
|
||||
[ObservableProperty] private double _weightBefore;
|
||||
[ObservableProperty] private double _weightAfter;
|
||||
[ObservableProperty] private double _lossPercent;
|
||||
[ObservableProperty] private bool _friabilityPass;
|
||||
|
||||
// 崩解
|
||||
[ObservableProperty] private double _disintegrationTemp;
|
||||
[ObservableProperty] private int _disintegrationSeconds;
|
||||
[ObservableProperty] private bool _isBasketMovingUp;
|
||||
[ObservableProperty] private bool[] _tubesCompleted = new bool[6];
|
||||
[ObservableProperty] private int _remainingTubes;
|
||||
|
||||
// 溶出
|
||||
[ObservableProperty] private double _dissolutionRpm;
|
||||
[ObservableProperty] private double _dissolutionPercent;
|
||||
public PlotModel DissolutionPlotModel { get; }
|
||||
private LineSeries _dissolutionSeries;
|
||||
private DateTime _dissolutionStartTime;
|
||||
|
||||
// 命令
|
||||
public IAsyncRelayCommand StartHardnessCommand { get; }
|
||||
public IAsyncRelayCommand StartFriabilityCommand { get; }
|
||||
public IAsyncRelayCommand StartDisintegrationCommand { get; }
|
||||
public IAsyncRelayCommand StartDissolutionCommand { get; }
|
||||
public IAsyncRelayCommand ExportHistoryCommand { get; }
|
||||
|
||||
public StationViewModel(int id, IPlcService plc, PlcConfiguration plcConfig, DatabaseService db, ExcelExportService excel, AlarmService alarm)
|
||||
{
|
||||
StationId = id;
|
||||
_plc = plc;
|
||||
_plcConfig = plcConfig;
|
||||
_db = db;
|
||||
_excel = excel;
|
||||
_alarm = alarm;
|
||||
|
||||
StartHardnessCommand = new AsyncRelayCommand(RunHardnessAsync);
|
||||
StartFriabilityCommand = new AsyncRelayCommand(RunFriabilityAsync);
|
||||
StartDisintegrationCommand = new AsyncRelayCommand(RunDisintegrationAsync);
|
||||
StartDissolutionCommand = new AsyncRelayCommand(RunDissolutionAsync);
|
||||
ExportHistoryCommand = new AsyncRelayCommand(ExportHistoryAsync);
|
||||
|
||||
// 溶出曲线:时间 vs 溶出度
|
||||
DissolutionPlotModel = new PlotModel { Title = $"工位{StationId} 溶出曲线" };
|
||||
_dissolutionSeries = new LineSeries { Title = "溶出度 (%)", Color = OxyColors.Green };
|
||||
DissolutionPlotModel.Series.Add(_dissolutionSeries);
|
||||
}
|
||||
|
||||
public async Task UpdateRealTimeData()
|
||||
{
|
||||
if (Phase != TestPhase.Running) return;
|
||||
try
|
||||
{
|
||||
switch (CurrentTest)
|
||||
{
|
||||
case TestType.Hardness:
|
||||
HardnessValue = await _plc.ReadFloatAsync(_plcConfig.HardnessValue);
|
||||
break;
|
||||
case TestType.Disintegration:
|
||||
DisintegrationTemp = await _plc.ReadFloatAsync(_plcConfig.DisintegrationTemp);
|
||||
IsBasketMovingUp = await _plc.ReadCoilAsync(_plcConfig.DisintegrationMovingUpCoil);
|
||||
for (int i = 0; i < _plcConfig.DisintegrationCompleteCoils.Length; i++)
|
||||
{
|
||||
bool completed = await _plc.ReadCoilAsync(_plcConfig.DisintegrationCompleteCoils[i]);
|
||||
if (completed && !TubesCompleted[i])
|
||||
{
|
||||
TubesCompleted[i] = true;
|
||||
RemainingTubes = 6 - TubesCompleted.Count(c => c);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TestType.Dissolution:
|
||||
DissolutionRpm = await _plc.ReadFloatAsync(_plcConfig.DissolutionRpm);
|
||||
DissolutionPercent = await _plc.ReadFloatAsync(_plcConfig.DissolutionPercent);
|
||||
if (_dissolutionStartTime != DateTime.MinValue)
|
||||
{
|
||||
double minutes = (DateTime.Now - _dissolutionStartTime).TotalMinutes;
|
||||
_dissolutionSeries.Points.Add(new DataPoint(minutes, DissolutionPercent));
|
||||
if (_dissolutionSeries.Points.Count > 200) _dissolutionSeries.Points.RemoveAt(0);
|
||||
DissolutionPlotModel.InvalidatePlot(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task RunHardnessAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Phase != TestPhase.Idle) return;
|
||||
CurrentTest = TestType.Hardness;
|
||||
Phase = TestPhase.Running;
|
||||
_hardnessResults.Clear();
|
||||
|
||||
int count = App.CurrentPharmaParams.HardnessTestCount;
|
||||
double min = App.CurrentPharmaParams.HardnessMin_N;
|
||||
double max = App.CurrentPharmaParams.HardnessMax_N;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
await _plc.WriteCoilAsync(_plcConfig.HardnessStartCoil, true);
|
||||
bool completed = false;
|
||||
while (!completed && Phase == TestPhase.Running)
|
||||
{
|
||||
await Task.Delay(200);
|
||||
completed = await _plc.ReadCoilAsync(_plcConfig.HardnessCompleteCoil);
|
||||
}
|
||||
double val = await _plc.ReadFloatAsync(_plcConfig.HardnessValue);
|
||||
_hardnessResults.Add(val);
|
||||
HardnessValue = val;
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
HardnessAvg = _hardnessResults.Average();
|
||||
HardnessRSD = (StandardDeviation(_hardnessResults) / HardnessAvg) * 100;
|
||||
HardnessPass = HardnessAvg >= min && HardnessAvg <= max;
|
||||
Phase = TestPhase.Completed;
|
||||
await SaveBatchResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Phase = TestPhase.Error;
|
||||
System.Windows.MessageBox.Show($"硬度测试出错:{ex.Message}\n{ex.StackTrace}", "错误", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunFriabilityAsync()
|
||||
{
|
||||
if (Phase != TestPhase.Idle) return;
|
||||
CurrentTest = TestType.Friability;
|
||||
Phase = TestPhase.Running;
|
||||
|
||||
// 模拟称重(实际可通过串口天平)
|
||||
WeightBefore = 6.5;
|
||||
await _plc.WriteCoilAsync(_plcConfig.FriabilityStartCoil, true);
|
||||
await Task.Delay(TimeSpan.FromMinutes(4));
|
||||
WeightAfter = WeightBefore * (1 - 0.008);
|
||||
LossPercent = (WeightBefore - WeightAfter) / WeightBefore * 100;
|
||||
FriabilityPass = LossPercent <= App.CurrentPharmaParams.FriabilityMaxLossPercent;
|
||||
Phase = TestPhase.Completed;
|
||||
await SaveBatchResult();
|
||||
}
|
||||
|
||||
private async Task RunDisintegrationAsync()
|
||||
{
|
||||
if (Phase != TestPhase.Idle) return;
|
||||
CurrentTest = TestType.Disintegration;
|
||||
Phase = TestPhase.Running;
|
||||
TubesCompleted = new bool[6];
|
||||
RemainingTubes = 6;
|
||||
_disintegrationSeconds = 0;
|
||||
_disintegrationTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
_disintegrationTimer.Tick += (s, e) => { if (Phase == TestPhase.Running) DisintegrationSeconds = _disintegrationSeconds + 1; };
|
||||
_disintegrationTimer.Start();
|
||||
|
||||
await _plc.WriteCoilAsync(_plcConfig.DisintegrationStartCoil, true);
|
||||
int maxSec = App.CurrentPharmaParams.DisintegrationMaxSeconds;
|
||||
while (RemainingTubes > 0 && _disintegrationSeconds < maxSec && Phase == TestPhase.Running)
|
||||
await Task.Delay(500);
|
||||
_disintegrationTimer.Stop();
|
||||
Phase = TestPhase.Completed;
|
||||
await SaveBatchResult();
|
||||
}
|
||||
|
||||
private async Task RunDissolutionAsync()
|
||||
{
|
||||
if (Phase != TestPhase.Idle) return;
|
||||
CurrentTest = TestType.Dissolution;
|
||||
Phase = TestPhase.Running;
|
||||
_dissolutionStartTime = DateTime.Now;
|
||||
_dissolutionSeries.Points.Clear();
|
||||
await _plc.WriteCoilAsync(_plcConfig.DissolutionStartCoil, true);
|
||||
|
||||
var sampleTimes = App.CurrentPharmaParams.DissolutionSampleTimes;
|
||||
double prevMin = 0;
|
||||
foreach (var t in sampleTimes)
|
||||
{
|
||||
int delayMs = (int)((t - prevMin) * 60 * 1000);
|
||||
if (delayMs > 0) await Task.Delay(delayMs);
|
||||
double value = await _plc.ReadFloatAsync(_plcConfig.DissolutionPercent);
|
||||
DissolutionPercent = value;
|
||||
prevMin = t;
|
||||
// 弹出取样提示(可改为非阻塞提示)
|
||||
App.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
System.Windows.MessageBox.Show($"工位{StationId} 请在{t}分钟取样。当前溶出度: {value:F1}%");
|
||||
});
|
||||
}
|
||||
bool pass = DissolutionPercent >= App.CurrentPharmaParams.DissolutionMinPercentAt30min;
|
||||
Phase = TestPhase.Completed;
|
||||
await SaveBatchResult(pass);
|
||||
}
|
||||
|
||||
private async Task SaveBatchResult(bool? forcedQualified = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var batch = new TestBatch
|
||||
{
|
||||
TestTime = DateTime.Now,
|
||||
StationId = StationId,
|
||||
SampleName = $"样品-{StationId}",
|
||||
HardnessAvg = HardnessAvg,
|
||||
HardnessRSD = HardnessRSD,
|
||||
FriabilityLoss = LossPercent,
|
||||
DisintegrationTimeSec = DisintegrationSeconds,
|
||||
RemainingTubesAtEnd = RemainingTubes,
|
||||
DissolutionRate30Min = DissolutionPercent,
|
||||
IsQualified = forcedQualified ?? (HardnessPass && FriabilityPass && RemainingTubes == 0 && DissolutionPercent >= App.CurrentPharmaParams.DissolutionMinPercentAt30min)
|
||||
};
|
||||
await Task.Run(() => _db.InsertBatch(batch));
|
||||
if (!batch.IsQualified)
|
||||
_alarm.RaiseAlarm($"工位{StationId} 测试不合格");
|
||||
else
|
||||
_alarm.ClearAlarm();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Windows.MessageBox.Show($"保存测试结果失败:{ex.Message}\n{ex.InnerException?.Message}", "数据库错误", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportHistoryAsync()
|
||||
{
|
||||
var batches = await Task.Run(() => _db.GetBatches(StationId, 100));
|
||||
string path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"工位{StationId}_检测记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
|
||||
_excel.ExportToExcel(batches, path);
|
||||
System.Windows.MessageBox.Show($"导出成功: {path}");
|
||||
}
|
||||
|
||||
private double StandardDeviation(List<double> values)
|
||||
{
|
||||
if (values.Count == 0) return 0;
|
||||
double avg = values.Average();
|
||||
double sum = values.Sum(v => Math.Pow(v - avg, 2));
|
||||
return Math.Sqrt(sum / values.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user