更新2026

This commit is contained in:
GukSang.Jin
2026-05-18 16:53:29 +08:00
parent bf4b491d0f
commit 865f1c087a
7 changed files with 500 additions and 261 deletions

View File

@@ -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));
}
}