更新
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user