更新最新2026
This commit is contained in:
@@ -5,12 +5,14 @@ using OxyPlot.Axes;
|
||||
using OxyPlot.Series;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using TabletTester2025.Helpers;
|
||||
using TabletTester2025.Models;
|
||||
using TabletTester2025.Services;
|
||||
|
||||
@@ -30,11 +32,13 @@ namespace TabletTester2025.ViewModels
|
||||
private bool _isLoadingDissolution2SampleInterval;
|
||||
private bool _isLoadingDisintegrationTime;
|
||||
private bool _isLoadingDisintegrationSpeed;
|
||||
private bool _isUpdatingFriabilityWeightFromPlc;
|
||||
|
||||
private readonly List<double> _dissolution1Times = new();
|
||||
private readonly List<double> _dissolution1Values = new();
|
||||
private readonly List<double> _dissolution2Times = new();
|
||||
private readonly List<double> _dissolution2Values = new();
|
||||
public ObservableCollection<DissolutionSamplePoint> DissolutionSamplePoints { get; } = new();
|
||||
private DateTime _dissolution1StartTime = DateTime.MinValue;
|
||||
private DateTime _dissolution2StartTime = DateTime.MinValue;
|
||||
private bool _isDissolution1Running;
|
||||
@@ -58,6 +62,8 @@ namespace TabletTester2025.ViewModels
|
||||
[ObservableProperty] private bool _hardnessPass;
|
||||
[ObservableProperty] private double _hardnessAvg;
|
||||
[ObservableProperty] private double _hardnessRSD;
|
||||
[ObservableProperty] private double _hardnessInternalMin = 40;
|
||||
[ObservableProperty] private double _hardnessInternalMax = 60;
|
||||
|
||||
|
||||
//硬度新增
|
||||
@@ -113,7 +119,9 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
// 脆碎度新增
|
||||
[ObservableProperty] private double _friabilityTargetRpm = 25;
|
||||
[ObservableProperty] private int _friabilityTargetTimeSec = 240;
|
||||
[ObservableProperty] private int _friabilityTargetTimeSec = 4;
|
||||
[ObservableProperty] private int _friabilityTargetRounds = 100;
|
||||
[ObservableProperty] private double _friabilityMaxLossPercent = 1.0;
|
||||
[ObservableProperty] private bool _friabilityClockwise = true;
|
||||
[ObservableProperty] private bool _friabilityCounterClockwise;
|
||||
[ObservableProperty] private double _friabilityCurrentRpm;
|
||||
@@ -153,6 +161,7 @@ namespace TabletTester2025.ViewModels
|
||||
[ObservableProperty] private double _disintegrationTargetFreq = 31;
|
||||
[ObservableProperty] private double _disintegrationSpeedRpm = 31;
|
||||
[ObservableProperty] private double _disintegrationTimeMin = 15;
|
||||
[ObservableProperty] private string _disintegrationDosageForm = "普通片";
|
||||
public IAsyncRelayCommand StopDisintegrationCommand { get; }
|
||||
public IAsyncRelayCommand ResetDisintegrationCommand { get; }
|
||||
public IAsyncRelayCommand PrintDisintegrationCommand { get; }
|
||||
@@ -178,6 +187,7 @@ namespace TabletTester2025.ViewModels
|
||||
_db = db;
|
||||
_excel = excel;
|
||||
_alarm = alarm;
|
||||
LoadPharmaDefaults();
|
||||
|
||||
StartHardnessCommand = new AsyncRelayCommand(RunHardnessAsync);
|
||||
StartFriabilityCommand = new AsyncRelayCommand(RunFriabilityAsync);
|
||||
@@ -255,7 +265,7 @@ namespace TabletTester2025.ViewModels
|
||||
});
|
||||
|
||||
// 硬后退按钮命令
|
||||
HardnessForward = new AsyncRelayCommand(async () =>
|
||||
HardnessBack = new AsyncRelayCommand(async () =>
|
||||
{
|
||||
await _plc.WriteCoilAsync(_plcConfig.HardnessBack, true);
|
||||
await Task.Delay(100); // 脉冲宽度,和复位按钮保持一致
|
||||
@@ -272,18 +282,19 @@ namespace TabletTester2025.ViewModels
|
||||
Phase = TestPhase.Idle;
|
||||
});
|
||||
// 脆碎度命令
|
||||
StopFriabilityCommand = new AsyncRelayCommand(() => {
|
||||
StopFriabilityCommand = new AsyncRelayCommand(async () => {
|
||||
|
||||
//测试停止
|
||||
|
||||
Phase = TestPhase.Idle; return Task.CompletedTask;
|
||||
if (_plcConfig.FriabilityStartCoilStop != 0)
|
||||
await PulseCoilAsync(_plcConfig.FriabilityStartCoilStop);
|
||||
Phase = TestPhase.Idle;
|
||||
});
|
||||
ResetFriabilityCommand = new AsyncRelayCommand(() =>
|
||||
{
|
||||
FriabilityRemainingRounds = 100; // 剩余圈数重置为默认值(通常是100圈)
|
||||
FriabilityRemainingRounds = FriabilityTargetRounds;
|
||||
LossPercent = 0; // 失重率清零
|
||||
WeightBefore = 0; // 脆碎前重量清零
|
||||
WeightAfter = 0;// 脆碎后重量清零
|
||||
SetFriabilityWeightFromPlc(0, 0); // 清空界面结果,不向PLC写零
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
PrintFriabilityCommand = new AsyncRelayCommand(async () => await PrintReport("脆碎度"));
|
||||
@@ -308,6 +319,23 @@ namespace TabletTester2025.ViewModels
|
||||
_ = LoadDisintegrationTimeAsync();
|
||||
_ = LoadDisintegrationSpeedAsync();
|
||||
_ = LoadDissolutionTimesAsync();
|
||||
_ = LoadFriabilityWeightsAsync();
|
||||
}
|
||||
|
||||
private void LoadPharmaDefaults()
|
||||
{
|
||||
var p = App.CurrentPharmaParams;
|
||||
HardnessInternalMin = p.HardnessMin_N;
|
||||
HardnessInternalMax = p.HardnessMax_N;
|
||||
HardnessTestCount = Math.Max(1, p.HardnessTestCount);
|
||||
FriabilityTargetRpm = p.FriabilityTargetRpm > 0 ? p.FriabilityTargetRpm : 25;
|
||||
FriabilityTargetRounds = p.FriabilityTargetRounds > 0 ? p.FriabilityTargetRounds : 100;
|
||||
FriabilityMaxLossPercent = p.FriabilityMaxLossPercent;
|
||||
FriabilityRemainingRounds = FriabilityTargetRounds;
|
||||
DisintegrationDosageForm = string.IsNullOrWhiteSpace(p.DisintegrationDosageForm) ? "普通片" : p.DisintegrationDosageForm;
|
||||
int seconds = ResolveDisintegrationLimitSeconds();
|
||||
if (seconds > 0)
|
||||
DisintegrationTimeMin = seconds / 60.0;
|
||||
}
|
||||
|
||||
private async Task PrintReport(string testName)
|
||||
@@ -349,27 +377,17 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
private async Task UpdateDissolutionDataAsync()
|
||||
{
|
||||
bool updated = false;
|
||||
DissolutionRpm = await _plc.ReadFloatAsync(_plcConfig.DissolutionRpm);
|
||||
if (_plcConfig.DisintegrationTemp != 0)
|
||||
DisintegrationTemp = await _plc.ReadFloatAsync(_plcConfig.DisintegrationTemp);
|
||||
|
||||
if (_isDissolution1Running)
|
||||
{
|
||||
await CheckDissolutionSampleAsync(1);
|
||||
updated |= await ReadDissolutionChannelAsync(1);
|
||||
}
|
||||
|
||||
if (_isDissolution2Running)
|
||||
{
|
||||
await CheckDissolutionSampleAsync(2);
|
||||
updated |= await ReadDissolutionChannelAsync(2);
|
||||
}
|
||||
|
||||
UpdateDissolutionClock();
|
||||
|
||||
if (updated)
|
||||
DissolutionPlotModel.InvalidatePlot(true);
|
||||
}
|
||||
|
||||
private async Task CheckDissolutionSampleAsync(int channel)
|
||||
@@ -394,14 +412,22 @@ namespace TabletTester2025.ViewModels
|
||||
if (IsDissolutionSampleRequestActive(channel) || IsDissolutionSamplePromptOpen(channel))
|
||||
return;
|
||||
|
||||
if (GetNextPendingDissolutionSample(channel) == null)
|
||||
{
|
||||
await _plc.WriteCoilAsync(coilAddress, true);
|
||||
DissolutionCurveStatus = $"溶出{channel}已完成全部取样点";
|
||||
return;
|
||||
}
|
||||
|
||||
SetDissolutionSampleRequestActive(channel, true);
|
||||
SetDissolutionSamplePromptOpen(channel, true);
|
||||
|
||||
try
|
||||
{
|
||||
await ShowDissolutionSampleDialogAsync(channel);
|
||||
double percent = await ShowDissolutionSampleDialogAsync(channel);
|
||||
RecordDissolutionSample(channel, percent);
|
||||
await _plc.WriteCoilAsync(coilAddress, true);
|
||||
LocalAlarm = $"溶出{channel}已确认取样";
|
||||
LocalAlarm = $"溶出{channel}已记录取样结果";
|
||||
DissolutionCurveStatus = "";
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -442,14 +468,20 @@ namespace TabletTester2025.ViewModels
|
||||
_isDissolution2SamplePromptOpen = value;
|
||||
}
|
||||
|
||||
private async Task ShowDissolutionSampleDialogAsync(int channel)
|
||||
private async Task<double> ShowDissolutionSampleDialogAsync(int channel)
|
||||
{
|
||||
bool confirmed = await App.Current.Dispatcher.InvokeAsync(() =>
|
||||
double? result = await App.Current.Dispatcher.InvokeAsync<double?>(() =>
|
||||
{
|
||||
var nextSample = GetNextPendingDissolutionSample(channel);
|
||||
double elapsed = GetDissolutionElapsedMinutes(channel);
|
||||
string plannedText = nextSample == null
|
||||
? "未找到待记录取样点"
|
||||
: $"计划取样时间:{nextSample.ScheduledTimeMin:0.##} min";
|
||||
|
||||
var dialog = new Window
|
||||
{
|
||||
Title = $"溶出{channel}取样",
|
||||
Width = 420,
|
||||
Width = 460,
|
||||
SizeToContent = SizeToContent.Height,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
@@ -460,19 +492,66 @@ namespace TabletTester2025.ViewModels
|
||||
var panel = new StackPanel { Margin = new Thickness(24) };
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "请取样分析后,取样结束后点击“确定已取样”继续运行。",
|
||||
Text = $"请人工采集样品并完成分析,录入该时间点的溶出度(%)。\n{plannedText}\n当前运行时间:{elapsed:0.##} min",
|
||||
FontSize = 16,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(16, 42, 67)),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 0, 0, 18)
|
||||
Margin = new Thickness(0, 0, 0, 14)
|
||||
});
|
||||
|
||||
var button = new Button
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Content = "确定已取样",
|
||||
Width = 128,
|
||||
Text = "溶出度(%)",
|
||||
FontSize = 14,
|
||||
FontWeight = System.Windows.FontWeights.SemiBold,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(51, 65, 85)),
|
||||
Margin = new Thickness(0, 0, 0, 6)
|
||||
});
|
||||
|
||||
var input = new TextBox
|
||||
{
|
||||
Height = 42,
|
||||
FontSize = 18,
|
||||
Padding = new Thickness(10, 5, 10, 5),
|
||||
Text = nextSample?.Percent?.ToString("0.###") ?? "",
|
||||
Margin = new Thickness(0, 0, 0, 18)
|
||||
};
|
||||
NumericInput.SetIsEnabled(input, true);
|
||||
NumericInput.SetAllowDecimal(input, true);
|
||||
NumericInput.SetAllowNegative(input, false);
|
||||
panel.Children.Add(input);
|
||||
|
||||
var buttons = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Right
|
||||
};
|
||||
|
||||
var cancelButton = new Button
|
||||
{
|
||||
Content = "取消",
|
||||
Width = 96,
|
||||
Height = 38,
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
Background = Brushes.White,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(51, 65, 85)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(203, 213, 225)),
|
||||
BorderThickness = new Thickness(1),
|
||||
FontSize = 15,
|
||||
FontWeight = System.Windows.FontWeights.SemiBold,
|
||||
Cursor = System.Windows.Input.Cursors.Hand
|
||||
};
|
||||
cancelButton.Click += (_, _) =>
|
||||
{
|
||||
dialog.DialogResult = false;
|
||||
dialog.Close();
|
||||
};
|
||||
|
||||
var confirmButton = new Button
|
||||
{
|
||||
Content = "记录取样",
|
||||
Width = 112,
|
||||
Height = 38,
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
|
||||
Background = new SolidColorBrush(Color.FromRgb(21, 101, 169)),
|
||||
Foreground = Brushes.White,
|
||||
BorderThickness = new Thickness(0),
|
||||
@@ -480,19 +559,30 @@ namespace TabletTester2025.ViewModels
|
||||
FontWeight = System.Windows.FontWeights.SemiBold,
|
||||
Cursor = System.Windows.Input.Cursors.Hand
|
||||
};
|
||||
button.Click += (_, _) =>
|
||||
confirmButton.Click += (_, _) =>
|
||||
{
|
||||
if (!double.TryParse(input.Text, out double value) || !IsValidDissolutionPercent(value))
|
||||
{
|
||||
MessageBox.Show("请输入0-150之间的溶出度百分比。", "取样结果无效", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
input.Tag = value;
|
||||
dialog.DialogResult = true;
|
||||
dialog.Close();
|
||||
};
|
||||
|
||||
panel.Children.Add(button);
|
||||
buttons.Children.Add(cancelButton);
|
||||
buttons.Children.Add(confirmButton);
|
||||
panel.Children.Add(buttons);
|
||||
dialog.Content = panel;
|
||||
return dialog.ShowDialog() == true;
|
||||
return dialog.ShowDialog() == true && input.Tag is double value ? value : null;
|
||||
});
|
||||
|
||||
if (!confirmed)
|
||||
throw new InvalidOperationException("取样确认窗口已关闭,未写入确认线圈");
|
||||
if (!result.HasValue)
|
||||
throw new InvalidOperationException("取样结果未录入,未写入确认线圈");
|
||||
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
private async Task<bool> ReadDissolutionChannelAsync(int channel)
|
||||
@@ -562,6 +652,112 @@ namespace TabletTester2025.ViewModels
|
||||
series.Points.Add(new DataPoint(minutes, value));
|
||||
}
|
||||
|
||||
private void CreateDissolutionSampleSchedule(int channel)
|
||||
{
|
||||
RemoveDissolutionSamples(channel);
|
||||
|
||||
var sampleTimes = App.CurrentPharmaParams.DissolutionSampleTimes?
|
||||
.Where(t => t > 0)
|
||||
.Distinct()
|
||||
.OrderBy(t => t)
|
||||
.ToArray();
|
||||
|
||||
if (sampleTimes == null || sampleTimes.Length == 0)
|
||||
sampleTimes = new[] { 5, 10, 15, 30, 45, 60 };
|
||||
|
||||
foreach (int minute in sampleTimes)
|
||||
{
|
||||
DissolutionSamplePoints.Add(new DissolutionSamplePoint
|
||||
{
|
||||
Channel = channel,
|
||||
ScheduledTimeMin = minute
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveDissolutionSamples(int channel)
|
||||
{
|
||||
for (int i = DissolutionSamplePoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (DissolutionSamplePoints[i].Channel == channel)
|
||||
DissolutionSamplePoints.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
private DissolutionSamplePoint? GetNextPendingDissolutionSample(int channel)
|
||||
{
|
||||
return DissolutionSamplePoints
|
||||
.Where(s => s.Channel == channel && !s.Percent.HasValue)
|
||||
.OrderBy(s => s.ScheduledTimeMin)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private double GetDissolutionElapsedMinutes(int channel)
|
||||
{
|
||||
DateTime startTime = channel == 1 ? _dissolution1StartTime : _dissolution2StartTime;
|
||||
return startTime == DateTime.MinValue ? 0 : Math.Max(0, (DateTime.Now - startTime).TotalMinutes);
|
||||
}
|
||||
|
||||
private void RecordDissolutionSample(int channel, double percent)
|
||||
{
|
||||
var sample = GetNextPendingDissolutionSample(channel)
|
||||
?? new DissolutionSamplePoint
|
||||
{
|
||||
Channel = channel,
|
||||
ScheduledTimeMin = GetDissolutionElapsedMinutes(channel)
|
||||
};
|
||||
|
||||
if (!DissolutionSamplePoints.Contains(sample))
|
||||
DissolutionSamplePoints.Add(sample);
|
||||
|
||||
sample.ActualTimeMin = GetDissolutionElapsedMinutes(channel);
|
||||
sample.Percent = percent;
|
||||
sample.RecordedAt = DateTime.Now;
|
||||
|
||||
RefreshDissolutionSeries(channel);
|
||||
}
|
||||
|
||||
private void RefreshDissolutionSeries(int channel)
|
||||
{
|
||||
var times = channel == 1 ? _dissolution1Times : _dissolution2Times;
|
||||
var values = channel == 1 ? _dissolution1Values : _dissolution2Values;
|
||||
var series = channel == 1 ? _dissolution1Series : _dissolution2Series;
|
||||
var recordedSamples = DissolutionSamplePoints
|
||||
.Where(s => s.Channel == channel && s.Percent.HasValue)
|
||||
.OrderBy(s => s.ScheduledTimeMin)
|
||||
.ToList();
|
||||
|
||||
times.Clear();
|
||||
values.Clear();
|
||||
series.Points.Clear();
|
||||
|
||||
foreach (var sample in recordedSamples)
|
||||
{
|
||||
double time = sample.ScheduledTimeMin;
|
||||
double value = sample.Percent!.Value;
|
||||
times.Add(time);
|
||||
values.Add(value);
|
||||
series.Points.Add(new DataPoint(time, value));
|
||||
}
|
||||
|
||||
double rsquared = CalculateRSquared(times, values);
|
||||
double latestValue = recordedSamples.LastOrDefault()?.Percent ?? 0;
|
||||
if (channel == 1)
|
||||
{
|
||||
Dissolution1Percent = latestValue;
|
||||
Dissolution1RSquared = rsquared;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dissolution2Percent = latestValue;
|
||||
Dissolution2RSquared = rsquared;
|
||||
}
|
||||
|
||||
DissolutionPercent = latestValue;
|
||||
DissolutionRSquared = rsquared;
|
||||
DissolutionPlotModel.InvalidatePlot(true);
|
||||
}
|
||||
|
||||
private void UpdateDissolutionClock()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
@@ -723,6 +919,75 @@ namespace TabletTester2025.ViewModels
|
||||
return (int)Math.Min(int.MaxValue, Math.Round(value, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
partial void OnWeightBeforeChanged(double value)
|
||||
{
|
||||
if (_isUpdatingFriabilityWeightFromPlc)
|
||||
return;
|
||||
|
||||
_ = WriteFriabilityWeightAsync(_plcConfig.WeightBefore, value);
|
||||
}
|
||||
|
||||
partial void OnWeightAfterChanged(double value)
|
||||
{
|
||||
if (_isUpdatingFriabilityWeightFromPlc)
|
||||
return;
|
||||
|
||||
_ = WriteFriabilityWeightAsync(_plcConfig.WeightAfter, value);
|
||||
}
|
||||
|
||||
private async Task LoadFriabilityWeightsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isUpdatingFriabilityWeightFromPlc = true;
|
||||
|
||||
if (_plcConfig.WeightBefore != 0)
|
||||
{
|
||||
double before = await ReadFriabilityWeightAsync(_plcConfig.WeightBefore, "脆碎前重量");
|
||||
WeightBefore = before;
|
||||
}
|
||||
|
||||
if (_plcConfig.WeightAfter != 0)
|
||||
{
|
||||
double after = await ReadFriabilityWeightAsync(_plcConfig.WeightAfter, "脆碎后重量");
|
||||
WeightAfter = after;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_isUpdatingFriabilityWeightFromPlc = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteFriabilityWeightAsync(ushort registerAddress, double value)
|
||||
{
|
||||
if (registerAddress == 0 || !double.IsFinite(value) || value < 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await _plc.WriteFloatAsync(registerAddress, (float)value);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void SetFriabilityWeightFromPlc(double? weightBefore = null, double? weightAfter = null)
|
||||
{
|
||||
_isUpdatingFriabilityWeightFromPlc = true;
|
||||
try
|
||||
{
|
||||
if (weightBefore.HasValue)
|
||||
WeightBefore = weightBefore.Value;
|
||||
if (weightAfter.HasValue)
|
||||
WeightAfter = weightAfter.Value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingFriabilityWeightFromPlc = false;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnDisintegrationTimeMinChanged(double value)
|
||||
{
|
||||
if (_isLoadingDisintegrationTime || _plcConfig.DisintegrationTime == 0 || value <= 0)
|
||||
@@ -739,6 +1004,28 @@ namespace TabletTester2025.ViewModels
|
||||
_ = WriteDisintegrationSpeedAsync(value);
|
||||
}
|
||||
|
||||
partial void OnDisintegrationDosageFormChanged(string value)
|
||||
{
|
||||
int seconds = ResolveDisintegrationLimitSeconds(value);
|
||||
if (seconds > 0)
|
||||
DisintegrationTimeMin = seconds / 60.0;
|
||||
}
|
||||
|
||||
private int ResolveDisintegrationLimitSeconds(string? dosageForm = null)
|
||||
{
|
||||
string form = string.IsNullOrWhiteSpace(dosageForm) ? DisintegrationDosageForm : dosageForm;
|
||||
return form switch
|
||||
{
|
||||
"薄膜衣片" => 30 * 60,
|
||||
"糖衣片" => 60 * 60,
|
||||
"胶囊" => 30 * 60,
|
||||
"普通片" => 15 * 60,
|
||||
_ => App.CurrentPharmaParams.DisintegrationMaxSeconds > 0
|
||||
? App.CurrentPharmaParams.DisintegrationMaxSeconds
|
||||
: 15 * 60
|
||||
};
|
||||
}
|
||||
|
||||
private async Task LoadDisintegrationSpeedAsync()
|
||||
{
|
||||
if (_plcConfig.DisintegrationSpeed == 0)
|
||||
@@ -766,9 +1053,10 @@ namespace TabletTester2025.ViewModels
|
||||
try
|
||||
{
|
||||
_isLoadingDisintegrationTime = true;
|
||||
float value = await _plc.ReadFloatAsync(_plcConfig.DisintegrationTime);
|
||||
if (value > 0)
|
||||
DisintegrationTimeMin = value;
|
||||
int seconds = ResolveDisintegrationLimitSeconds();
|
||||
if (seconds > 0)
|
||||
DisintegrationTimeMin = seconds / 60.0;
|
||||
await WriteDisintegrationTimeAsync(DisintegrationTimeMin);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
@@ -821,13 +1109,13 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
int count = App.CurrentPharmaParams.HardnessTestCount;
|
||||
double min = App.CurrentPharmaParams.HardnessMin_N;
|
||||
double max = App.CurrentPharmaParams.HardnessMax_N;
|
||||
int count = Math.Max(1, HardnessTestCount);
|
||||
double min = HardnessInternalMin;
|
||||
double max = HardnessInternalMax;
|
||||
|
||||
|
||||
double currentSpeed = _hardnessSudu;
|
||||
double currentWeiyi = _hardnessWeiyi;
|
||||
double currentSpeed = HardnessSudu;
|
||||
double currentWeiyi = HardnessWeiyi;
|
||||
|
||||
|
||||
// 如果你需要把这3个值发给PLC,就在这里发一次(测试开始用当前参数)
|
||||
@@ -898,13 +1186,17 @@ namespace TabletTester2025.ViewModels
|
||||
{
|
||||
throw new InvalidOperationException("未配置脆碎度启动线圈地址");
|
||||
}
|
||||
WeightBefore = await ReadFriabilityWeightAsync(_plcConfig.WeightBefore, "脆碎前重量");
|
||||
double weightBefore = await ReadFriabilityWeightAsync(_plcConfig.WeightBefore, "脆碎前重量");
|
||||
SetFriabilityWeightFromPlc(weightBefore: weightBefore);
|
||||
if (WeightBefore <= 0)
|
||||
throw new InvalidOperationException("脆碎前重量必须大于0");
|
||||
|
||||
int totalRounds = Math.Max(1, FriabilityTargetRounds);
|
||||
double rpm = FriabilityTargetRpm > 0 ? FriabilityTargetRpm : 25;
|
||||
FriabilityTargetTimeSec = (int)Math.Ceiling(totalRounds / rpm * 60);
|
||||
FriabilityRemainingRounds = totalRounds;
|
||||
FriabilityCurrentRpm = rpm;
|
||||
await _plc.WriteCoilAsync(startCoil, true);
|
||||
int totalRounds = 100; // 药典标准脆碎度总圈数:100圈
|
||||
double rpm = FriabilityTargetRpm; // 界面设置的目标转速(r/min)
|
||||
int durationMs = (int)((totalRounds / rpm) * 60 * 1000); // 总运行时间(毫秒)
|
||||
|
||||
for (int i = 0; i < durationMs; i += 100)
|
||||
@@ -927,11 +1219,14 @@ namespace TabletTester2025.ViewModels
|
||||
if (Phase != TestPhase.Running)
|
||||
throw new InvalidOperationException("脆碎度测试已停止,未保存结果");
|
||||
|
||||
WeightAfter = await ReadFriabilityWeightAsync(_plcConfig.WeightAfter, "脆碎后重量");
|
||||
double weightAfter = await ReadFriabilityWeightAsync(_plcConfig.WeightAfter, "脆碎后重量");
|
||||
SetFriabilityWeightFromPlc(weightAfter: weightAfter);
|
||||
if (WeightAfter > WeightBefore)
|
||||
throw new InvalidOperationException("脆碎后重量不能大于初始重量");
|
||||
|
||||
FriabilityCurrentRpm = FriabilityTargetRpm;
|
||||
FriabilityCurrentRpm = rpm;
|
||||
LossPercent = (WeightBefore - WeightAfter) / WeightBefore * 100;//失重率
|
||||
FriabilityPass = LossPercent <= App.CurrentPharmaParams.FriabilityMaxLossPercent; //标准值
|
||||
FriabilityPass = LossPercent <= FriabilityMaxLossPercent; //标准值
|
||||
resultReady = true;
|
||||
// 标记测试为已完成
|
||||
Phase = TestPhase.Completed;
|
||||
@@ -946,7 +1241,7 @@ namespace TabletTester2025.ViewModels
|
||||
finally
|
||||
{
|
||||
Phase = TestPhase.Idle;
|
||||
FriabilityRemainingRounds = 100;
|
||||
FriabilityRemainingRounds = FriabilityTargetRounds;
|
||||
if (resultReady)
|
||||
await SaveBatchResult();
|
||||
}
|
||||
@@ -986,9 +1281,8 @@ namespace TabletTester2025.ViewModels
|
||||
await WriteDisintegrationSpeedAsync(DisintegrationSpeedRpm);
|
||||
await WriteDisintegrationTimeAsync(DisintegrationTimeMin);
|
||||
await PulseCoilAsync(_plcConfig.DisintegrationStartCoil);
|
||||
int maxSec = DisintegrationTimeMin > 0
|
||||
? (int)Math.Ceiling(DisintegrationTimeMin * 60)
|
||||
: App.CurrentPharmaParams.DisintegrationMaxSeconds;
|
||||
int maxSec = ResolveDisintegrationLimitSeconds();
|
||||
DisintegrationTimeMin = maxSec / 60.0;
|
||||
while (RemainingTubes > 0 && DisintegrationSeconds < maxSec && Phase == TestPhase.Running)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
@@ -1005,9 +1299,7 @@ namespace TabletTester2025.ViewModels
|
||||
finally
|
||||
{
|
||||
Phase = TestPhase.Idle;
|
||||
int maxSec = DisintegrationTimeMin > 0
|
||||
? (int)Math.Ceiling(DisintegrationTimeMin * 60)
|
||||
: App.CurrentPharmaParams.DisintegrationMaxSeconds;
|
||||
int maxSec = ResolveDisintegrationLimitSeconds();
|
||||
DisintegrationPass = !_discardDisintegrationResult && RemainingTubes == 0 && DisintegrationSeconds <= maxSec;
|
||||
|
||||
if (!_discardDisintegrationResult)
|
||||
@@ -1060,6 +1352,7 @@ namespace TabletTester2025.ViewModels
|
||||
DissolutionPass = false;
|
||||
ResetDissolutionChannel(1);
|
||||
ResetDissolutionSampleState(1);
|
||||
CreateDissolutionSampleSchedule(1);
|
||||
_dissolution1StartTime = DateTime.Now;
|
||||
_isDissolution1Running = true;
|
||||
DissolutionPlotModel.Title = "溶出曲线";
|
||||
@@ -1102,18 +1395,12 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
private async Task StartDissolution2Async()
|
||||
{
|
||||
if (_plcConfig.Dissolution2Percent == 0)
|
||||
{
|
||||
LocalAlarm = "溶出2溶出度寄存器未配置";
|
||||
DissolutionCurveStatus = LocalAlarm;
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentTest = TestType.Dissolution;
|
||||
Phase = TestPhase.Running;
|
||||
DissolutionPass = false;
|
||||
ResetDissolutionChannel(2);
|
||||
ResetDissolutionSampleState(2);
|
||||
CreateDissolutionSampleSchedule(2);
|
||||
_dissolution2StartTime = DateTime.Now;
|
||||
_isDissolution2Running = true;
|
||||
DissolutionPlotModel.Title = "溶出曲线";
|
||||
@@ -1156,6 +1443,8 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
private void ResetDissolutionChannel(int channel)
|
||||
{
|
||||
RemoveDissolutionSamples(channel);
|
||||
|
||||
if (channel == 1)
|
||||
{
|
||||
_dissolution1Times.Clear();
|
||||
@@ -1258,11 +1547,14 @@ namespace TabletTester2025.ViewModels
|
||||
HardnessMax = HardnessMax,
|
||||
HardnessMin = HardnessMin,
|
||||
HardnessTestCount = HardnessTestCount,
|
||||
HardnessInternalMin = HardnessInternalMin,
|
||||
HardnessInternalMax = HardnessInternalMax,
|
||||
|
||||
// 脆碎度
|
||||
FriabilityLoss = LossPercent,
|
||||
FriabilityTargetRpm = FriabilityTargetRpm,
|
||||
FriabilityTargetTimeSec = FriabilityTargetTimeSec,
|
||||
FriabilityTargetRounds = FriabilityTargetRounds,
|
||||
FriabilityClockwise = FriabilityClockwise,
|
||||
FriabilityRemainingRounds = FriabilityRemainingRounds,
|
||||
WeightBefore = WeightBefore,
|
||||
@@ -1273,6 +1565,8 @@ namespace TabletTester2025.ViewModels
|
||||
RemainingTubesAtEnd = RemainingTubes,
|
||||
DisintegrationTargetFreq = 0,
|
||||
DisintegrationTemp = DisintegrationTemp,
|
||||
DisintegrationDosageForm = DisintegrationDosageForm,
|
||||
DisintegrationLimitSeconds = ResolveDisintegrationLimitSeconds(),
|
||||
|
||||
// 溶出
|
||||
DissolutionChannel = CurrentTest == TestType.Dissolution ? _dissolutionResultChannel : "",
|
||||
@@ -1300,20 +1594,17 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
IsQualified = HardnessPass && FriabilityPass && DisintegrationPass && DissolutionPass
|
||||
};
|
||||
await Task.Run(() => _db.InsertBatch(batch));
|
||||
var dissolutionSamples = CurrentTest == TestType.Dissolution
|
||||
? DissolutionSamplePoints
|
||||
.Where(s => s.ChannelName == _dissolutionResultChannel && s.Percent.HasValue)
|
||||
.ToList()
|
||||
: new List<DissolutionSamplePoint>();
|
||||
|
||||
await Task.Run(() => _db.InsertBatch(batch, dissolutionSamples));
|
||||
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 获取当前测试项目是否合格
|
||||
bool currentPass = CurrentTest switch
|
||||
{
|
||||
TestType.Hardness => HardnessPass,
|
||||
TestType.Friability => FriabilityPass,
|
||||
TestType.Disintegration => DisintegrationPass,
|
||||
TestType.Dissolution => DissolutionPass,
|
||||
_ => false
|
||||
};
|
||||
string projectName = CurrentTest switch
|
||||
{
|
||||
TestType.Hardness => "硬度",
|
||||
@@ -1322,7 +1613,7 @@ namespace TabletTester2025.ViewModels
|
||||
TestType.Dissolution => string.IsNullOrWhiteSpace(_dissolutionResultChannel) ? "溶出" : _dissolutionResultChannel,
|
||||
_ => ""
|
||||
};
|
||||
LocalAlarm = currentPass ? $"{projectName}测试合格" : $"{projectName}测试不合格";
|
||||
LocalAlarm = $"{projectName}测试完成";
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user