This commit is contained in:
GukSang.Jin
2026-05-20 10:16:02 +08:00
parent 070463ae8e
commit 61420da42e
5 changed files with 281 additions and 129 deletions

View File

@@ -46,6 +46,10 @@ namespace TabletTester2025.ViewModels
public ObservableCollection<DissolutionSamplePoint> DissolutionSamplePoints { get; } = new();
private DateTime _dissolution1StartTime = DateTime.MinValue;
private DateTime _dissolution2StartTime = DateTime.MinValue;
private DateTime _dissolution1LastRunUpdate = DateTime.MinValue;
private DateTime _dissolution2LastRunUpdate = DateTime.MinValue;
private double _dissolution1ElapsedRunMinutes;
private double _dissolution2ElapsedRunMinutes;
private bool _isDissolution1Running;
private bool _isDissolution2Running;
private bool _dissolution1SampleRequestActive;
@@ -155,6 +159,10 @@ namespace TabletTester2025.ViewModels
[ObservableProperty] private double _dissolutionMinPercentAt30Min = 80;
[ObservableProperty] private double _dissolutionElapsedTime;
[ObservableProperty] private double _dissolutionCountdown;
[ObservableProperty] private double _dissolution1ElapsedTime;
[ObservableProperty] private double _dissolution2ElapsedTime;
[ObservableProperty] private double _dissolution1Countdown;
[ObservableProperty] private double _dissolution2Countdown;
[ObservableProperty] private double _dissolutionRSquared;
[ObservableProperty] private double _dissolution1RSquared;
[ObservableProperty] private double _dissolution2RSquared;
@@ -441,41 +449,47 @@ namespace TabletTester2025.ViewModels
private async Task CheckDissolutionSampleAsync(int channel)
{
ushort coilAddress = channel == 1
? _plcConfig.Dissolution1SampleAckCoil
: _plcConfig.Dissolution2SampleAckCoil;
if (coilAddress == 0)
{
DissolutionCurveStatus = $"溶出{channel}取样确认线圈未配置";
return;
}
bool sampleRequested = await _plc.ReadCoilAsync(coilAddress);
if (!sampleRequested)
{
SetDissolutionSampleRequestActive(channel, false);
return;
}
if (IsDissolutionSampleRequestActive(channel) || IsDissolutionSamplePromptOpen(channel))
return;
AccumulateDissolutionRunTime(channel, DateTime.Now);
var nextSample = GetNextPendingDissolutionSample(channel);
if (nextSample == null || GetDissolutionElapsedMinutes(channel) + 0.0001 < nextSample.ScheduledTimeMin)
return;
SetDissolutionSampleRequestActive(channel, true);
SetDissolutionSamplePromptOpen(channel, true);
PauseDissolutionRunClock(channel);
try
{
await PulseCoilAsync(ResolveDissolutionStopCoil(channel));
double percent = await ShowDissolutionSampleDialogAsync(channel);
RecordDissolutionSample(channel, percent);
await _plc.WriteCoilAsync(coilAddress, false);
SetDissolutionSampleRequestActive(channel, false);
LocalAlarm = $"溶出{channel}已记录取样结果";
DissolutionCurveStatus = "";
if (IsDissolutionChannelComplete(channel))
{
await FinalizeDissolutionChannelAsync(channel);
SetDissolutionRunning(channel, false);
ResetDissolutionSampleState(channel);
Phase = IsAnyDissolutionRunning() ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
}
else
{
await PulseCoilAsync(ResolveDissolutionStartCoil(channel));
ResumeDissolutionRunClock(channel);
}
}
catch (Exception ex)
{
SetDissolutionSampleRequestActive(channel, false);
SetDissolutionRunning(channel, false);
Phase = IsAnyDissolutionRunning() ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
await App.Current.Dispatcher.InvokeAsync(() =>
MessageBox.Show($"溶出{channel}取样确认失败:{ex.Message}", "取样确认失败", MessageBoxButton.OK, MessageBoxImage.Error));
}
@@ -511,6 +525,96 @@ namespace TabletTester2025.ViewModels
_isDissolution2SamplePromptOpen = value;
}
private ushort ResolveDissolutionStartCoil(int channel)
{
return channel == 1 ? _plcConfig.Dissolution1StartCoil : _plcConfig.Dissolution2StartCoil;
}
private ushort ResolveDissolutionStopCoil(int channel)
{
return channel == 1 ? _plcConfig.Dissolution1StopCoil : _plcConfig.Dissolution2StopCoil;
}
private bool IsAnyDissolutionRunning()
{
return _isDissolution1Running || _isDissolution2Running;
}
private void SetDissolutionRunning(int channel, bool value)
{
if (channel == 1)
_isDissolution1Running = value;
else
_isDissolution2Running = value;
}
private void ResetDissolutionRunClock(int channel)
{
if (channel == 1)
{
_dissolution1ElapsedRunMinutes = 0;
_dissolution1LastRunUpdate = DateTime.MinValue;
Dissolution1ElapsedTime = 0;
Dissolution1Countdown = 0;
}
else
{
_dissolution2ElapsedRunMinutes = 0;
_dissolution2LastRunUpdate = DateTime.MinValue;
Dissolution2ElapsedTime = 0;
Dissolution2Countdown = 0;
}
UpdateDissolutionClock();
}
private void ResumeDissolutionRunClock(int channel)
{
if (channel == 1)
_dissolution1LastRunUpdate = DateTime.Now;
else
_dissolution2LastRunUpdate = DateTime.Now;
UpdateDissolutionClock();
}
private void PauseDissolutionRunClock(int channel)
{
AccumulateDissolutionRunTime(channel, DateTime.Now);
if (channel == 1)
_dissolution1LastRunUpdate = DateTime.MinValue;
else
_dissolution2LastRunUpdate = DateTime.MinValue;
UpdateDissolutionClock();
}
private void AccumulateDissolutionRunTime(int channel, DateTime now)
{
if (channel == 1)
{
if (_isDissolution1Running && _dissolution1LastRunUpdate != DateTime.MinValue)
{
_dissolution1ElapsedRunMinutes = Math.Max(0, _dissolution1ElapsedRunMinutes + (now - _dissolution1LastRunUpdate).TotalMinutes);
_dissolution1LastRunUpdate = now;
}
}
else
{
if (_isDissolution2Running && _dissolution2LastRunUpdate != DateTime.MinValue)
{
_dissolution2ElapsedRunMinutes = Math.Max(0, _dissolution2ElapsedRunMinutes + (now - _dissolution2LastRunUpdate).TotalMinutes);
_dissolution2LastRunUpdate = now;
}
}
}
private bool IsDissolutionChannelComplete(int channel)
{
return GetNextPendingDissolutionSample(channel) == null
|| GetDissolutionElapsedMinutes(channel) + 0.0001 >= GetDissolutionDurationMinutes(channel);
}
private async Task<double> ShowDissolutionSampleDialogAsync(int channel)
{
double? result = await App.Current.Dispatcher.InvokeAsync<double?>(() =>
@@ -623,7 +727,7 @@ namespace TabletTester2025.ViewModels
});
if (!result.HasValue)
throw new InvalidOperationException("取样结果未录入,未写入确认线圈");
throw new InvalidOperationException("取样结果未录入");
return result.Value;
}
@@ -694,35 +798,28 @@ namespace TabletTester2025.ViewModels
private List<double> ResolveDissolutionSampleTimes(int channel)
{
int durationMin = channel == 1 ? Dissolution1TimeMin : Dissolution2TimeMin;
double intervalMin = channel == 1 ? Dissolution1SampleIntervalMin : Dissolution2SampleIntervalMin;
durationMin = Math.Max(1, durationMin);
var configuredTimes = App.CurrentPharmaParams.DissolutionSampleTimes?
.Where(t => t > 0)
.Select(t => (double)t)
.ToList();
var times = configuredTimes != null && configuredTimes.Count > 0
? configuredTimes
: GenerateIntervalSampleTimes(durationMin, intervalMin);
times.Add(Math.Min(30, durationMin));
if (durationMin >= 30)
times.Add(30);
times.Add(durationMin);
return times
.Where(t => t > 0 && t <= durationMin)
.Distinct()
.OrderBy(t => t)
.ToList();
return GenerateIntervalSampleTimes(
GetDissolutionDurationMinutes(channel),
GetDissolutionIntervalMinutes(channel));
}
private static List<double> GenerateIntervalSampleTimes(int durationMin, double intervalMin)
private double GetDissolutionDurationMinutes(int channel)
{
return Math.Max(1, channel == 1 ? Dissolution1TimeMin : Dissolution2TimeMin);
}
private double GetDissolutionIntervalMinutes(int channel)
{
double intervalMin = channel == 1 ? Dissolution1SampleIntervalMin : Dissolution2SampleIntervalMin;
return double.IsFinite(intervalMin) && intervalMin > 0 ? intervalMin : 5;
}
private static List<double> GenerateIntervalSampleTimes(double durationMin, double intervalMin)
{
if (!double.IsFinite(intervalMin) || intervalMin <= 0)
intervalMin = 5;
if (!double.IsFinite(durationMin) || durationMin <= 0)
durationMin = 1;
var times = new List<double>();
for (double time = intervalMin; time <= durationMin + 0.0001; time += intervalMin)
@@ -753,8 +850,7 @@ namespace TabletTester2025.ViewModels
private double GetDissolutionElapsedMinutes(int channel)
{
DateTime startTime = channel == 1 ? _dissolution1StartTime : _dissolution2StartTime;
return startTime == DateTime.MinValue ? 0 : Math.Max(0, (DateTime.Now - startTime).TotalMinutes);
return channel == 1 ? _dissolution1ElapsedRunMinutes : _dissolution2ElapsedRunMinutes;
}
private void RecordDissolutionSample(int channel, double percent)
@@ -820,24 +916,44 @@ namespace TabletTester2025.ViewModels
private void UpdateDissolutionClock()
{
var now = DateTime.Now;
double elapsed1 = _isDissolution1Running && _dissolution1StartTime != DateTime.MinValue
? (now - _dissolution1StartTime).TotalMinutes
: 0;
double elapsed2 = _isDissolution2Running && _dissolution2StartTime != DateTime.MinValue
? (now - _dissolution2StartTime).TotalMinutes
: 0;
AccumulateDissolutionRunTime(1, now);
AccumulateDissolutionRunTime(2, now);
double elapsed1 = _dissolution1ElapsedRunMinutes;
double elapsed2 = _dissolution2ElapsedRunMinutes;
Dissolution1ElapsedTime = elapsed1;
Dissolution2ElapsedTime = elapsed2;
DissolutionElapsedTime = Math.Max(elapsed1, elapsed2);
Dissolution1Countdown = ResolveDissolutionCountdown(1);
Dissolution2Countdown = ResolveDissolutionCountdown(2);
var remaining = new List<double>();
if (_isDissolution1Running)
remaining.Add(Math.Max(0, Dissolution1TimeMin - elapsed1));
remaining.Add(Dissolution1Countdown);
if (_isDissolution2Running)
remaining.Add(Math.Max(0, Dissolution2TimeMin - elapsed2));
remaining.Add(Dissolution2Countdown);
DissolutionCountdown = remaining.Count == 0 ? 0 : remaining.Min();
}
private double ResolveDissolutionCountdown(int channel)
{
if (channel == 1 && !_isDissolution1Running)
return 0;
if (channel == 2 && !_isDissolution2Running)
return 0;
double elapsed = GetDissolutionElapsedMinutes(channel);
double totalRemaining = Math.Max(0, GetDissolutionDurationMinutes(channel) - elapsed);
var nextSample = GetNextPendingDissolutionSample(channel);
if (nextSample == null)
return totalRemaining;
return Math.Max(0, Math.Min(nextSample.ScheduledTimeMin - elapsed, totalRemaining));
}
partial void OnDissolution1TimeMinChanged(int value)
{
if (_isLoadingDissolution1Time || _plcConfig.Dissolution1Time == 0 || value <= 0)
@@ -927,7 +1043,7 @@ namespace TabletTester2025.ViewModels
try
{
_isLoadingDissolution1SampleInterval = true;
int value = await _plc.ReadIntAsync(_plcConfig.Dissolution1SampleInterval);
double value = await _plc.ReadFloatAsync(_plcConfig.Dissolution1SampleInterval);
if (value > 0)
{
Dissolution1SampleIntervalMin = value;
@@ -946,7 +1062,7 @@ namespace TabletTester2025.ViewModels
try
{
_isLoadingDissolution2SampleInterval = true;
int value = await _plc.ReadIntAsync(_plcConfig.Dissolution2SampleInterval);
double value = await _plc.ReadFloatAsync(_plcConfig.Dissolution2SampleInterval);
if (value > 0)
Dissolution2SampleIntervalMin = value;
}
@@ -965,7 +1081,7 @@ namespace TabletTester2025.ViewModels
try
{
await _plc.WriteRegisterAsync(registerAddress, (ushort)Math.Clamp(ToCompatibleSampleInterval(value), 1, ushort.MaxValue));
await _plc.WriteFloatAsync(registerAddress, (float)value);
}
catch { }
}
@@ -1735,9 +1851,11 @@ namespace TabletTester2025.ViewModels
DissolutionPass = false;
ResetDissolutionChannel(1);
ResetDissolutionSampleState(1);
ResetDissolutionRunClock(1);
CreateDissolutionSampleSchedule(1);
_dissolution1StartTime = DateTime.Now;
_isDissolution1Running = true;
ResumeDissolutionRunClock(1);
DissolutionPlotModel.Title = "溶出曲线";
await WriteDissolutionTimeAsync(_plcConfig.Dissolution1Time, Dissolution1TimeMin);
await WriteDissolutionSampleIntervalAsync(_plcConfig.Dissolution1SampleInterval, Dissolution1SampleIntervalMin);
@@ -1748,6 +1866,7 @@ namespace TabletTester2025.ViewModels
{
try
{
PauseDissolutionRunClock(1);
await PulseCoilAsync(_plcConfig.Dissolution1StopCoil);
await FinalizeDissolutionChannelAsync(1);
}
@@ -1755,6 +1874,7 @@ namespace TabletTester2025.ViewModels
{
_isDissolution1Running = false;
ResetDissolutionSampleState(1);
_dissolution1LastRunUpdate = DateTime.MinValue;
Phase = _isDissolution2Running ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
}
@@ -1771,6 +1891,7 @@ namespace TabletTester2025.ViewModels
_isDissolution1Running = false;
ResetDissolutionChannel(1);
ResetDissolutionSampleState(1);
ResetDissolutionRunClock(1);
Phase = _isDissolution2Running ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
}
@@ -1786,9 +1907,11 @@ namespace TabletTester2025.ViewModels
DissolutionPass = false;
ResetDissolutionChannel(2);
ResetDissolutionSampleState(2);
ResetDissolutionRunClock(2);
CreateDissolutionSampleSchedule(2);
_dissolution2StartTime = DateTime.Now;
_isDissolution2Running = true;
ResumeDissolutionRunClock(2);
DissolutionPlotModel.Title = "溶出曲线";
await WriteDissolutionTimeAsync(_plcConfig.Dissolution2Time, Dissolution2TimeMin);
await WriteDissolutionSampleIntervalAsync(_plcConfig.Dissolution2SampleInterval, Dissolution2SampleIntervalMin);
@@ -1799,6 +1922,7 @@ namespace TabletTester2025.ViewModels
{
try
{
PauseDissolutionRunClock(2);
await PulseCoilAsync(_plcConfig.Dissolution2StopCoil);
await FinalizeDissolutionChannelAsync(2);
}
@@ -1806,6 +1930,7 @@ namespace TabletTester2025.ViewModels
{
_isDissolution2Running = false;
ResetDissolutionSampleState(2);
_dissolution2LastRunUpdate = DateTime.MinValue;
Phase = _isDissolution1Running ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
}
@@ -1822,6 +1947,7 @@ namespace TabletTester2025.ViewModels
_isDissolution2Running = false;
ResetDissolutionChannel(2);
ResetDissolutionSampleState(2);
ResetDissolutionRunClock(2);
Phase = _isDissolution1Running ? TestPhase.Running : TestPhase.Idle;
UpdateDissolutionClock();
}

View File

@@ -449,6 +449,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="当前参数" Grid.Row="0">
@@ -476,7 +477,36 @@
</WrapPanel>
</GroupBox>
<GroupBox Header="测试结果" Grid.Row="1">
<GroupBox Header="运行计时" Grid.Row="1">
<UniformGrid Columns="4">
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出1累计(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution1ElapsedTime, StringFormat=F1}" Foreground="#2E7D32" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出1倒计时(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution1Countdown, StringFormat=F1}" Foreground="#2E7D32" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出2累计(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution2ElapsedTime, StringFormat=F1}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
<Border Style="{StaticResource MetricCard}">
<StackPanel>
<TextBlock Text="溶出2倒计时(min)" Style="{StaticResource MetricLabel}"/>
<TextBlock Text="{Binding Dissolution2Countdown, StringFormat=F1}" Foreground="#1565C0" Style="{StaticResource MetricValue}"/>
</StackPanel>
</Border>
</UniformGrid>
</GroupBox>
<GroupBox Header="测试结果" Grid.Row="2">
<UniformGrid Columns="4">
<Border Style="{StaticResource MetricCard}">
<StackPanel>
@@ -505,7 +535,7 @@
</UniformGrid>
</GroupBox>
<GroupBox Header="溶出双曲线和R²值" Grid.Row="2">
<GroupBox Header="溶出双曲线和R²值" Grid.Row="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="240"/>
@@ -533,7 +563,7 @@
</Grid>
</GroupBox>
<GroupBox Header="取样记录" Grid.Row="3">
<GroupBox Header="取样记录" Grid.Row="4">
<DataGrid ItemsSource="{Binding DissolutionSamplePoints}"
AutoGenerateColumns="False"
HeadersVisibility="Column"

View File

@@ -123,32 +123,11 @@
<StackPanel>
<WrapPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="剂型规格:" Style="{StaticResource ParamLabel}"/>
<ComboBox x:Name="DisintegrationDosageFormBox"
Width="180"
Height="40"
FontSize="15"
SelectionChanged="DisintegrationDosageFormBox_SelectionChanged">
<ComboBoxItem Content="普通片" Tag="900"/>
<ComboBoxItem Content="薄膜衣片" Tag="1800"/>
<ComboBoxItem Content="糖衣片" Tag="3600"/>
<ComboBoxItem Content="胶囊" Tag="1800"/>
</ComboBox>
<TextBlock Text="崩解时间(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationTimeMinBox"/>
</StackPanel>
<!--<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="最长崩解时间(秒):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationMaxSecBox" helpers:NumericInput.AllowDecimal="False"/>
</StackPanel>-->
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="升降频率(次/min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationSpeedBox"/>
</StackPanel>
<!--<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="介质温度(℃):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="DisintegrationTempBox"/>
</StackPanel>-->
</WrapPanel>
<TextBlock Text="默认升降频率30-32次/min介质温度37±1℃。不同剂型按药典或品种正文规定时限执行。"
<TextBlock Text="崩解时间按具体品种正文或企业批准标准设置,保存后立即写入设备。"
Style="{StaticResource StandardNote}"/>
</StackPanel>
</GroupBox>
@@ -170,11 +149,11 @@
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出1取样间隔(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution1IntervalBox" helpers:NumericInput.AllowDecimal="False"/>
<TextBox x:Name="Dissolution1IntervalBox" helpers:NumericInput.AllowDecimal="True"/>
</StackPanel>
<StackPanel Style="{StaticResource ParamRow}">
<TextBlock Text="溶出2取样间隔(min):" Style="{StaticResource ParamLabel}"/>
<TextBox x:Name="Dissolution2IntervalBox" helpers:NumericInput.AllowDecimal="False"/>
<TextBox x:Name="Dissolution2IntervalBox" helpers:NumericInput.AllowDecimal="True"/>
</StackPanel>
</WrapPanel>
<TextBlock Text="默认普通制剂通常取6片溶出介质温度37±0.5℃;转速和取样间隔应按具体品种正文或企业批准标准录入。"

View File

@@ -25,14 +25,11 @@ namespace TabletTester2025
await LoadPlcFloatToTextBoxAsync(ResolveFriabilityRpmRegister(), FriabilityRpmBox);
await LoadPlcIntToTextBoxAsync(ResolveFriabilityRoundsRegister(), FriabilityRoundsBox);
// 崩解
await LoadPlcFloatToTextBoxAsync(ResolveDisintegrationSpeedRegister(), DisintegrationSpeedBox);
// 溶出度
await LoadPlcFloatToTextBoxAsync(ResolveDissolution1SpeedRegister(), Dissolution1SpeedBox);
await LoadPlcFloatToTextBoxAsync(ResolveDissolution2SpeedRegister(), Dissolution2SpeedBox);
await LoadPlcIntToTextBoxAsync(ResolveDissolution1IntervalRegister(), Dissolution1IntervalBox);
await LoadPlcIntToTextBoxAsync(ResolveDissolution2IntervalRegister(), Dissolution2IntervalBox);
await LoadPlcFloatToTextBoxAsync(ResolveDissolution1IntervalRegister(), Dissolution1IntervalBox);
await LoadPlcFloatToTextBoxAsync(ResolveDissolution2IntervalRegister(), Dissolution2IntervalBox);
}
private void LoadSettings()
@@ -43,9 +40,7 @@ namespace TabletTester2025
//FriabilityTimeBox.Text = ResolveFriabilityTargetTimeMin(p).ToString("0.###");
FriabilityRoundsBox.Text = ResolveFriabilityTargetRounds(p).ToString();
FriabilityMaxLossBox.Text = p.FriabilityMaxLossPercent.ToString();
SelectDisintegrationDosageForm(p.DisintegrationDosageForm);
//DisintegrationMaxSecBox.Text = p.DisintegrationMaxSeconds.ToString();
DisintegrationSpeedBox.Text = p.DisintegrationSpeedRpm.ToString();
DisintegrationTimeMinBox.Text = ResolveDisintegrationTimeMin(p).ToString("0.###");
//DisintegrationTempBox.Text = p.DisintegrationTemperatureC.ToString();
//DissolutionTempBox.Text = p.DissolutionTemperatureC.ToString();
Dissolution1IntervalBox.Text = p.Dissolution1SampleIntervalMin.ToString();
@@ -62,7 +57,12 @@ namespace TabletTester2025
StandardVersion = current.StandardVersion,
HardnessMin_N = current.HardnessMin_N,
HardnessMax_N = current.HardnessMax_N,
FriabilityTargetTimeMin = current.FriabilityTargetTimeMin,
DissolutionMinPercentAt30min = current.DissolutionMinPercentAt30min,
DisintegrationDosageForm = current.DisintegrationDosageForm,
DisintegrationSpeedRpm = current.DisintegrationSpeedRpm,
DisintegrationTemperatureC = current.DisintegrationTemperatureC,
DissolutionTemperatureC = current.DissolutionTemperatureC,
Dissolution1TimeMin = current.Dissolution1TimeMin,
Dissolution2TimeMin = current.Dissolution2TimeMin,
DissolutionSampleTimes = current.DissolutionSampleTimes?.ToArray() ?? Array.Empty<int>()
@@ -75,22 +75,24 @@ namespace TabletTester2025
//p.FriabilityTargetTimeMin = ParseFiniteDouble(FriabilityTimeBox.Text, "脆碎度试验时间");
p.FriabilityTargetRounds = ParsePositiveInt(FriabilityRoundsBox.Text, "脆碎圈数");
p.FriabilityMaxLossPercent = ParseFiniteDouble(FriabilityMaxLossBox.Text, "最大失重率");
p.DisintegrationDosageForm = GetSelectedDisintegrationDosageForm();
//p.DisintegrationMaxSeconds = int.Parse(DisintegrationMaxSecBox.Text); //崩解最长时间
p.DisintegrationSpeedRpm = ParseFiniteDouble(DisintegrationSpeedBox.Text, "崩解升降频率");
double disintegrationTimeMin = ParsePositiveDouble(DisintegrationTimeMinBox.Text, "崩解时间");
p.DisintegrationMaxSeconds = ToDisintegrationSeconds(disintegrationTimeMin);
//p.DisintegrationTemperatureC = ParseFiniteDouble(DisintegrationTempBox.Text, "崩解介质温度");
//p.DissolutionTemperatureC = ParseFiniteDouble(DissolutionTempBox.Text, "溶出介质温度");
double dissolution1Speed = ParsePositiveDouble(Dissolution1SpeedBox.Text, "溶出速度1");
double dissolution2Speed = ParsePositiveDouble(Dissolution2SpeedBox.Text, "溶出速度2");
p.Dissolution1SampleIntervalMin = ParsePositiveInt(Dissolution1IntervalBox.Text, "溶出1取样间隔");
p.Dissolution2SampleIntervalMin = ParsePositiveInt(Dissolution2IntervalBox.Text, "溶出2取样间隔");
p.Dissolution1SampleIntervalMin = ParsePositiveDouble(Dissolution1IntervalBox.Text, "溶出1取样间隔");
p.Dissolution2SampleIntervalMin = ParsePositiveDouble(Dissolution2IntervalBox.Text, "溶出2取样间隔");
ValidateParameters(p);
await WriteHardnessPressureAsync(hardnessPressure);
await WriteHardnessDamageThresholdAsync(hardnessDamageThreshold);
await WriteFriabilityRpmAsync(friabilityRpm);
await WriteDisintegrationTimeAsync(disintegrationTimeMin);
await WriteDissolution1SpeedAsync(dissolution1Speed);
await WriteDissolution2SpeedAsync(dissolution2Speed);
await WriteDissolution1IntervalAsync(p.Dissolution1SampleIntervalMin);
await WriteDissolution2IntervalAsync(p.Dissolution2SampleIntervalMin);
App.CurrentPharmaParams = p;
App.SaveCurrentPharmaParameters();
@@ -110,33 +112,6 @@ namespace TabletTester2025
Close();
}
private void DisintegrationDosageFormBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//if (DisintegrationDosageFormBox.SelectedItem is ComboBoxItem item && item.Tag is string seconds)
// DisintegrationMaxSecBox.Text = seconds;
}
private void SelectDisintegrationDosageForm(string dosageForm)
{
foreach (ComboBoxItem item in DisintegrationDosageFormBox.Items)
{
if (string.Equals(item.Content?.ToString(), dosageForm, StringComparison.OrdinalIgnoreCase))
{
DisintegrationDosageFormBox.SelectedItem = item;
return;
}
}
DisintegrationDosageFormBox.SelectedIndex = 0;
}
private string GetSelectedDisintegrationDosageForm()
{
return DisintegrationDosageFormBox.SelectedItem is ComboBoxItem item
? item.Content?.ToString() ?? "普通片"
: "普通片";
}
private static void ValidateParameters(PharmaParameters p)
{
if (!double.IsFinite(p.HardnessMin_N) || !double.IsFinite(p.HardnessMax_N))
@@ -306,9 +281,21 @@ namespace TabletTester2025
return App.PlcConfig.FriabilityTestTime != 0 ? App.PlcConfig.FriabilityTestTime : (ushort)410;
}
private static ushort ResolveDisintegrationSpeedRegister()
private static ushort ResolveDisintegrationTimeRegister()
{
return App.PlcConfig.DisintegrationSpeed != 0 ? App.PlcConfig.DisintegrationSpeed : (ushort)330;
return App.PlcConfig.DisintegrationTime != 0 ? App.PlcConfig.DisintegrationTime : (ushort)420;
}
private static async Task WriteDisintegrationTimeAsync(double value)
{
ushort registerAddress = ResolveDisintegrationTimeRegister();
if (registerAddress == 0)
throw new InvalidOperationException("崩解时间PLC寄存器地址未配置。");
await App.PlcService.WriteRegisterAsync(registerAddress, (ushort)Math.Clamp(
(int)Math.Round(value, MidpointRounding.AwayFromZero),
1,
ushort.MaxValue));
}
private static async Task WriteDissolution1SpeedAsync(double value)
@@ -329,6 +316,24 @@ namespace TabletTester2025
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static async Task WriteDissolution1IntervalAsync(double value)
{
ushort registerAddress = ResolveDissolution1IntervalRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出1取样间隔PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static async Task WriteDissolution2IntervalAsync(double value)
{
ushort registerAddress = ResolveDissolution2IntervalRegister();
if (registerAddress == 0)
throw new InvalidOperationException("溶出2取样间隔PLC寄存器地址未配置。");
await App.PlcService.WriteFloatAsync(registerAddress, (float)value);
}
private static ushort ResolveDissolution1SpeedRegister()
{
return App.PlcConfig.Dissolution1Speed != 0 ? App.PlcConfig.Dissolution1Speed : (ushort)340;
@@ -368,5 +373,17 @@ namespace TabletTester2025
return 100;
}
private static double ResolveDisintegrationTimeMin(PharmaParameters p)
{
return p.DisintegrationMaxSeconds > 0
? p.DisintegrationMaxSeconds / 60.0
: 15.0;
}
private static int ToDisintegrationSeconds(double minutes)
{
return Math.Max(1, (int)Math.Round(minutes * 60, MidpointRounding.AwayFromZero));
}
}
}

View File

@@ -67,8 +67,8 @@
"Dissolution2SampleAckCoil": 34,
"Dissolution1Time": 430,
"Dissolution2Time": 440,
"Dissolution1SampleInterval": 432, // 溶出1取样间隔int类型
"Dissolution2SampleInterval": 442 // 溶出2取样间隔int类型
"Dissolution1SampleInterval": 432, // 溶出1取样间隔float类型
"Dissolution2SampleInterval": 442 // 溶出2取样间隔float类型
},
"PharmaStandard": {
"StandardVersion": "中国药典2025",