更新2026
This commit is contained in:
@@ -386,7 +386,7 @@ namespace TabletTester2025.ViewModels
|
||||
if (completed && !TubesCompleted[i])
|
||||
{
|
||||
TubesCompleted[i] = true;
|
||||
RemainingTubes = 6 - TubesCompleted.Count(c => c);
|
||||
RemainingTubes = TubesCompleted.Length - TubesCompleted.Count(c => c);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -405,16 +405,28 @@ namespace TabletTester2025.ViewModels
|
||||
DisintegrationTemp = await _plc.ReadFloatAsync(_plcConfig.DisintegrationTemp);
|
||||
|
||||
if (_isDissolution1Running)
|
||||
{
|
||||
await ReadDissolutionChannelAsync(1);
|
||||
await CheckDissolutionSampleAsync(1);
|
||||
}
|
||||
|
||||
if (_isDissolution2Running)
|
||||
{
|
||||
await ReadDissolutionChannelAsync(2);
|
||||
await CheckDissolutionSampleAsync(2);
|
||||
}
|
||||
|
||||
UpdateDissolutionClock();
|
||||
}
|
||||
|
||||
private async Task CheckDissolutionSampleAsync(int channel)
|
||||
{
|
||||
if (channel == 2 && _plcConfig.Dissolution2Percent == 0)
|
||||
{
|
||||
DissolutionCurveStatus = "溶出2溶出度寄存器未配置,未启用取样计算";
|
||||
return;
|
||||
}
|
||||
|
||||
ushort coilAddress = channel == 1
|
||||
? _plcConfig.Dissolution1SampleAckCoil
|
||||
: _plcConfig.Dissolution2SampleAckCoil;
|
||||
@@ -1130,41 +1142,115 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
private async Task RunHardnessAsync()
|
||||
{
|
||||
// 点击启动才执行一次
|
||||
if (Phase != TestPhase.Idle) return;
|
||||
CurrentTest = TestType.Hardness;
|
||||
Phase = TestPhase.Running;
|
||||
HardnessPass = false;
|
||||
_hardnessResults.Clear();
|
||||
HardnessCurrentCount = 0;
|
||||
HardnessAvg = 0;
|
||||
HardnessRSD = 0;
|
||||
HardnessMax = 0;
|
||||
HardnessMin = 0;
|
||||
|
||||
try
|
||||
{
|
||||
int count = Math.Max(1, HardnessTestCount);
|
||||
double min = HardnessInternalMin;
|
||||
double max = HardnessInternalMax;
|
||||
ushort completeCoil = ResolveHardnessCompleteCoil();
|
||||
if (completeCoil == 0)
|
||||
throw new InvalidOperationException("硬度完成线圈未配置");
|
||||
if (_plcConfig.HardnessMax == 0)
|
||||
throw new InvalidOperationException("硬度最大力寄存器未配置");
|
||||
|
||||
await _plc.WriteFloatAsync(_plcConfig.HardnessSudu, (float)HardnessSudu);
|
||||
await _plc.WriteFloatAsync(_plcConfig.HardnessWeiyi, (float)HardnessWeiyi);
|
||||
|
||||
double currentSpeed = HardnessSudu;
|
||||
double currentWeiyi = HardnessWeiyi;
|
||||
|
||||
if (await _plc.ReadCoilAsync(completeCoil))
|
||||
await WaitForCoilStateAsync(completeCoil, false, TimeSpan.FromSeconds(10), "硬度完成信号未复位");
|
||||
|
||||
// 写入PLC
|
||||
await _plc.WriteFloatAsync(_plcConfig.HardnessSudu, (float)currentSpeed);
|
||||
await _plc.WriteFloatAsync(_plcConfig.HardnessWeiyi, (float)currentWeiyi);
|
||||
await _plc.WriteCoilAsync(_plcConfig.HardnessStartCoil, true);//启动
|
||||
await PulseCoilAsync(_plcConfig.HardnessStartCoil);
|
||||
|
||||
while (Phase == TestPhase.Running && _hardnessResults.Count < count)
|
||||
{
|
||||
await WaitForCoilStateAsync(completeCoil, true, TimeSpan.FromSeconds(120), "等待硬度完成信号超时");
|
||||
double value = await ReadHardnessResultAsync();
|
||||
_hardnessResults.Add(value);
|
||||
ApplyHardnessStatistics(count);
|
||||
await WaitForCoilStateAsync(completeCoil, false, TimeSpan.FromSeconds(10), "硬度完成信号未回落");
|
||||
}
|
||||
|
||||
|
||||
if (_hardnessResults.Count < count)
|
||||
throw new InvalidOperationException("硬度测试已停止,未保存结果");
|
||||
|
||||
ApplyHardnessStatistics(count);
|
||||
Phase = TestPhase.Completed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
await App.Current.Dispatcher.InvokeAsync(() =>
|
||||
MessageBox.Show($"硬度测试出错: {ex.Message}"));
|
||||
Phase = TestPhase.Error;
|
||||
}
|
||||
finally
|
||||
{
|
||||
bool resultReady = Phase == TestPhase.Completed;
|
||||
Phase = TestPhase.Idle;
|
||||
await SaveBatchResult();
|
||||
if (resultReady)
|
||||
await SaveBatchResult();
|
||||
}
|
||||
}
|
||||
|
||||
private ushort ResolveHardnessCompleteCoil()
|
||||
{
|
||||
return _plcConfig.HardnessOver != 0
|
||||
? _plcConfig.HardnessOver
|
||||
: _plcConfig.HardnessCompleteCoil;
|
||||
}
|
||||
|
||||
private async Task<double> ReadHardnessResultAsync()
|
||||
{
|
||||
double value = await _plc.ReadFloatAsync(_plcConfig.HardnessMax);
|
||||
if (!double.IsFinite(value) || value <= 0)
|
||||
throw new InvalidOperationException("硬度最大力数据异常");
|
||||
|
||||
HardnessValue = value;
|
||||
HardnessShishilizhi = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
private async Task WaitForCoilStateAsync(ushort coilAddress, bool expectedState, TimeSpan timeout, string timeoutMessage)
|
||||
{
|
||||
DateTime deadline = DateTime.Now.Add(timeout);
|
||||
while (Phase == TestPhase.Running && DateTime.Now <= deadline)
|
||||
{
|
||||
if (await _plc.ReadCoilAsync(coilAddress) == expectedState)
|
||||
return;
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
if (Phase != TestPhase.Running)
|
||||
throw new InvalidOperationException("硬度测试已停止,未保存结果");
|
||||
|
||||
throw new TimeoutException(timeoutMessage);
|
||||
}
|
||||
|
||||
private void ApplyHardnessStatistics(int requiredCount)
|
||||
{
|
||||
var stats = TestCalculationService.CalculateHardness(
|
||||
_hardnessResults,
|
||||
HardnessInternalMin,
|
||||
HardnessInternalMax,
|
||||
requiredCount);
|
||||
|
||||
HardnessAvg = stats.Average;
|
||||
HardnessRSD = stats.RsdPercent;
|
||||
HardnessMax = stats.Maximum;
|
||||
HardnessMin = stats.Minimum;
|
||||
HardnessCurrentCount = stats.Count;
|
||||
HardnessPass = stats.IsPass;
|
||||
}
|
||||
|
||||
/// 脆碎度测试主逻辑(实时状态显示)
|
||||
|
||||
private async Task RunFriabilityAsync()
|
||||
@@ -1197,7 +1283,7 @@ namespace TabletTester2025.ViewModels
|
||||
FriabilityTargetTimeSec = (int)Math.Ceiling(totalRounds / rpm * 60);
|
||||
FriabilityRemainingRounds = totalRounds;
|
||||
FriabilityCurrentRpm = rpm;
|
||||
await _plc.WriteCoilAsync(startCoil, true);
|
||||
await PulseCoilAsync(startCoil);
|
||||
int durationMs = (int)((totalRounds / rpm) * 60 * 1000); // 总运行时间(毫秒)
|
||||
|
||||
for (int i = 0; i < durationMs; i += 100)
|
||||
@@ -1222,11 +1308,8 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
double weightAfter = await ReadFriabilityWeightAsync(_plcConfig.WeightAfter, "脆碎后重量");
|
||||
SetFriabilityWeightFromPlc(weightAfter: weightAfter);
|
||||
if (WeightAfter > WeightBefore)
|
||||
throw new InvalidOperationException("脆碎后重量不能大于初始重量");
|
||||
|
||||
FriabilityCurrentRpm = rpm;
|
||||
LossPercent = (WeightBefore - WeightAfter) / WeightBefore * 100;//失重率
|
||||
LossPercent = TestCalculationService.CalculateFriabilityLossPercent(WeightBefore, WeightAfter);
|
||||
FriabilityPass = LossPercent <= FriabilityMaxLossPercent; //标准值
|
||||
resultReady = true;
|
||||
// 标记测试为已完成
|
||||
@@ -1241,6 +1324,12 @@ namespace TabletTester2025.ViewModels
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_plcConfig.FriabilityStartCoil != 0)
|
||||
{
|
||||
try { await _plc.WriteCoilAsync(_plcConfig.FriabilityStartCoil, false); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
Phase = TestPhase.Idle;
|
||||
FriabilityRemainingRounds = FriabilityTargetRounds;
|
||||
if (resultReady)
|
||||
@@ -1266,14 +1355,17 @@ namespace TabletTester2025.ViewModels
|
||||
CurrentTest = TestType.Disintegration;
|
||||
Phase = TestPhase.Running;
|
||||
DisintegrationPass = false; // 添加这一行
|
||||
TubesCompleted = new bool[6];
|
||||
RemainingTubes = 6;
|
||||
int tubeCount = Math.Max(1, _plcConfig.DisintegrationCompleteCoils.Length);
|
||||
TubesCompleted = new bool[tubeCount];
|
||||
RemainingTubes = tubeCount;
|
||||
DisintegrationSeconds = 0;
|
||||
bool resultReady = false;
|
||||
DateTime startedAt = DateTime.Now;
|
||||
_disintegrationTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
_disintegrationTimer.Tick += (s, e) =>
|
||||
{
|
||||
if (Phase == TestPhase.Running)
|
||||
DisintegrationSeconds++;
|
||||
DisintegrationSeconds = Math.Max(0, (int)Math.Floor((DateTime.Now - startedAt).TotalSeconds));
|
||||
};
|
||||
_disintegrationTimer.Start();
|
||||
|
||||
@@ -1286,10 +1378,13 @@ namespace TabletTester2025.ViewModels
|
||||
DisintegrationTimeMin = maxSec / 60.0;
|
||||
while (RemainingTubes > 0 && DisintegrationSeconds < maxSec && Phase == TestPhase.Running)
|
||||
{
|
||||
DisintegrationSeconds = Math.Max(0, (int)Math.Floor((DateTime.Now - startedAt).TotalSeconds));
|
||||
await Task.Delay(500);
|
||||
}
|
||||
DisintegrationSeconds = Math.Max(0, (int)Math.Floor((DateTime.Now - startedAt).TotalSeconds));
|
||||
_disintegrationTimer.Stop();
|
||||
Phase = TestPhase.Completed;
|
||||
resultReady = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1303,7 +1398,7 @@ namespace TabletTester2025.ViewModels
|
||||
int maxSec = ResolveDisintegrationLimitSeconds();
|
||||
DisintegrationPass = !_discardDisintegrationResult && RemainingTubes == 0 && DisintegrationSeconds <= maxSec;
|
||||
|
||||
if (!_discardDisintegrationResult)
|
||||
if (resultReady && !_discardDisintegrationResult)
|
||||
await SaveBatchResult();
|
||||
|
||||
_discardDisintegrationResult = false;
|
||||
@@ -1312,6 +1407,7 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
private async Task StopDisintegrationAsync()
|
||||
{
|
||||
_discardDisintegrationResult = CurrentTest == TestType.Disintegration && Phase == TestPhase.Running;
|
||||
try
|
||||
{
|
||||
await PulseCoilAsync(_plcConfig.DisintegrationStopCoil);
|
||||
@@ -1401,7 +1497,10 @@ namespace TabletTester2025.ViewModels
|
||||
DissolutionPass = false;
|
||||
ResetDissolutionChannel(2);
|
||||
ResetDissolutionSampleState(2);
|
||||
CreateDissolutionSampleSchedule(2);
|
||||
if (_plcConfig.Dissolution2Percent != 0)
|
||||
CreateDissolutionSampleSchedule(2);
|
||||
else
|
||||
DissolutionCurveStatus = "溶出2溶出度寄存器未配置,仅执行设备控制,不保存计算结果";
|
||||
_dissolution2StartTime = DateTime.Now;
|
||||
_isDissolution2Running = true;
|
||||
DissolutionPlotModel.Title = "溶出曲线";
|
||||
@@ -1487,7 +1586,14 @@ namespace TabletTester2025.ViewModels
|
||||
}
|
||||
|
||||
_dissolutionResultChannel = $"溶出{channel}";
|
||||
_dissolutionResultRate30Min = GetDissolutionRateAt30Min(times, values);
|
||||
if (!TestCalculationService.TryGetDissolutionRateAt30Min(times, values, out double rate30Min))
|
||||
{
|
||||
DissolutionCurveStatus = $"溶出{channel}缺少有效30min溶出度,未保存结果";
|
||||
LocalAlarm = DissolutionCurveStatus;
|
||||
return;
|
||||
}
|
||||
|
||||
_dissolutionResultRate30Min = rate30Min;
|
||||
_dissolutionResultRSquared = CalculateRSquared(times, values);
|
||||
DissolutionPercent = _dissolutionResultRate30Min;
|
||||
DissolutionRSquared = _dissolutionResultRSquared;
|
||||
@@ -1500,26 +1606,6 @@ namespace TabletTester2025.ViewModels
|
||||
await SaveBatchResult();
|
||||
}
|
||||
|
||||
private static double GetDissolutionRateAt30Min(List<double> times, List<double> values)
|
||||
{
|
||||
if (values.Count == 0)
|
||||
return 0;
|
||||
|
||||
int index = 0;
|
||||
double nearestDistance = double.MaxValue;
|
||||
for (int i = 0; i < times.Count; i++)
|
||||
{
|
||||
double distance = Math.Abs(times[i] - 30);
|
||||
if (distance < nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return values[index];
|
||||
}
|
||||
|
||||
private async Task RunDissolutionAsync()
|
||||
{
|
||||
await StartDissolution1Async();
|
||||
@@ -1545,7 +1631,8 @@ namespace TabletTester2025.ViewModels
|
||||
// 硬度
|
||||
HardnessAvg = HardnessAvg,
|
||||
HardnessRSD = HardnessRSD,
|
||||
|
||||
HardnessMax = HardnessMax,
|
||||
HardnessMin = HardnessMin,
|
||||
HardnessTestCount = HardnessTestCount,
|
||||
HardnessInternalMin = HardnessInternalMin,
|
||||
HardnessInternalMax = HardnessInternalMax,
|
||||
@@ -1592,7 +1679,12 @@ namespace TabletTester2025.ViewModels
|
||||
_ => ""
|
||||
},
|
||||
|
||||
IsQualified = HardnessPass && FriabilityPass && DisintegrationPass && DissolutionPass
|
||||
IsQualified = TestCalculationService.ResolveCurrentTestQualified(
|
||||
CurrentTest,
|
||||
HardnessPass,
|
||||
FriabilityPass,
|
||||
DisintegrationPass,
|
||||
DissolutionPass)
|
||||
};
|
||||
var dissolutionSamples = CurrentTest == TestType.Dissolution
|
||||
? DissolutionSamplePoints
|
||||
@@ -1625,22 +1717,7 @@ namespace TabletTester2025.ViewModels
|
||||
|
||||
private double CalculateRSquared(List<double> timeMinutes, List<double> concentration)
|
||||
{
|
||||
if (timeMinutes.Count < 2 || timeMinutes.Count != concentration.Count) return 0;
|
||||
int n = timeMinutes.Count;
|
||||
double sumX = timeMinutes.Sum();
|
||||
double sumY = concentration.Sum();
|
||||
double sumXY = timeMinutes.Zip(concentration, (x, y) => x * y).Sum();
|
||||
double sumX2 = timeMinutes.Select(x => x * x).Sum();
|
||||
double sumY2 = concentration.Select(y => y * y).Sum();
|
||||
|
||||
double numerator = (n * sumXY - sumX * sumY);
|
||||
double denominator = Math.Sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
|
||||
if (denominator <= 0 || double.IsNaN(denominator))
|
||||
return 0;
|
||||
|
||||
double r = numerator / denominator;
|
||||
double result = r * r;
|
||||
return double.IsFinite(result) ? result : 0;
|
||||
return TestCalculationService.CalculateRSquared(timeMinutes, concentration);
|
||||
}
|
||||
|
||||
private async Task ExportHistoryAsync()
|
||||
@@ -1656,7 +1733,7 @@ namespace TabletTester2025.ViewModels
|
||||
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);
|
||||
return values.Count < 2 ? 0 : Math.Sqrt(sum / (values.Count - 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user