From de4ed64198e8c7f3a39f497ae5133447c2afa60f Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Thu, 14 May 2026 17:53:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- COFTester/MainWindow.xaml | 77 ++++++++++++++++++++- COFTester/Services/TestDataRepository.cs | 30 ++++++-- COFTester/ViewModels/MainViewModel.cs | 87 +++++++++++++++++++++--- 3 files changed, 179 insertions(+), 15 deletions(-) diff --git a/COFTester/MainWindow.xaml b/COFTester/MainWindow.xaml index 84a9ef0..4359273 100644 --- a/COFTester/MainWindow.xaml +++ b/COFTester/MainWindow.xaml @@ -615,19 +615,90 @@ + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/COFTester/Services/TestDataRepository.cs b/COFTester/Services/TestDataRepository.cs index 9458b7b..3d33457 100644 --- a/COFTester/Services/TestDataRepository.cs +++ b/COFTester/Services/TestDataRepository.cs @@ -259,8 +259,18 @@ public sealed class TestDataRepository using var command = connection.CreateCommand(); command.CommandText = """ - SELECT run_id, run_index, completed_at, batch_number, test_mode, static_coefficient, - static_coefficient_1, static_coefficient_2, kinetic_coefficient, + SELECT run_id, run_index, completed_at, batch_number, test_mode, + COALESCE(NULLIF(static_coefficient, 0), ( + SELECT AVG(static_coefficient) + FROM reciprocating_records + WHERE run_id = test_runs.run_id AND static_coefficient IS NOT NULL + ), static_coefficient) AS static_coefficient, + static_coefficient_1, static_coefficient_2, + COALESCE(NULLIF(kinetic_coefficient, 0), ( + SELECT AVG(kinetic_coefficient) + FROM reciprocating_records + WHERE run_id = test_runs.run_id AND kinetic_coefficient IS NOT NULL + ), kinetic_coefficient) AS kinetic_coefficient, kinetic_coefficient_1, kinetic_coefficient_2, standard_deviation, standard_deviation_1, standard_deviation_2, peak_force_n, average_force_n, judgement, csv_export_path, report_export_path, sample_count @@ -291,8 +301,20 @@ public sealed class TestDataRepository SELECT run_id, run_index, completed_at, batch_number, product_code, test_mode, counterface_material, direction, sled_mass_grams, lift_speed_mm_per_min, lift_displacement_mm, speed_mm_per_min, travel_mm, replicate_count, - reciprocating_count, specimen_description, static_coefficient, static_coefficient_1, - static_coefficient_2, kinetic_coefficient, kinetic_coefficient_1, + reciprocating_count, specimen_description, + COALESCE(NULLIF(static_coefficient, 0), ( + SELECT AVG(static_coefficient) + FROM reciprocating_records + WHERE run_id = test_runs.run_id AND static_coefficient IS NOT NULL + ), static_coefficient) AS static_coefficient, + static_coefficient_1, + static_coefficient_2, + COALESCE(NULLIF(kinetic_coefficient, 0), ( + SELECT AVG(kinetic_coefficient) + FROM reciprocating_records + WHERE run_id = test_runs.run_id AND kinetic_coefficient IS NOT NULL + ), kinetic_coefficient) AS kinetic_coefficient, + kinetic_coefficient_1, kinetic_coefficient_2, standard_deviation, standard_deviation_1, standard_deviation_2, peak_force_n, average_force_n, judgement, csv_export_path, report_export_path, sample_count diff --git a/COFTester/ViewModels/MainViewModel.cs b/COFTester/ViewModels/MainViewModel.cs index fc97c82..f5bc008 100644 --- a/COFTester/ViewModels/MainViewModel.cs +++ b/COFTester/ViewModels/MainViewModel.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using LiveChartsCore; using LiveChartsCore.Drawing; @@ -34,6 +35,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable private const string EventLevelInfo = "INFO"; private const string EventLevelWarn = "WARN"; + private static readonly Regex PlcAddressPattern = new(@"\b[DM]\d+(?:=[^\s,。:;]*)?", RegexOptions.Compiled | RegexOptions.CultureInvariant); private const string ModbusTcpHost = "192.168.1.10"; private const int ModbusTcpPort = 502; private const byte ModbusSlaveAddress = 1; @@ -1025,6 +1027,19 @@ public sealed class MainViewModel : ObservableObject, IDisposable private void FinalizeRun() { + var finalStaticCoefficient = ResolveCompletedMeasurementValue( + CurrentStaticCoefficient, + ReciprocatingRecords.Select(record => record.StaticCoefficient)); + var finalKineticCoefficient = ResolveCompletedMeasurementValue( + CurrentKineticCoefficient, + ReciprocatingRecords.Select(record => record.KineticCoefficient)); + var finalStandardDeviation = ResolveCompletedMeasurementValue( + ResolveRepresentativeValue(GetActiveReplicateCount(), StandardDeviation1, StandardDeviation2), + BuildStandardDeviationFallback(ReciprocatingRecords.Select(record => record.StaticCoefficient))); + + CurrentStaticCoefficient = finalStaticCoefficient; + CurrentKineticCoefficient = finalKineticCoefficient; + var record = new RunRecord { RunId = _activeRunId, @@ -1032,13 +1047,13 @@ public sealed class MainViewModel : ObservableObject, IDisposable CompletedAt = DateTime.Now, BatchNumber = Recipe.BatchNumber, TestMode = Recipe.TestMode, - StaticCoefficient = CurrentStaticCoefficient, + StaticCoefficient = finalStaticCoefficient, StaticCoefficient1 = StaticCoefficient1, StaticCoefficient2 = StaticCoefficient2, - KineticCoefficient = CurrentKineticCoefficient, + KineticCoefficient = finalKineticCoefficient, KineticCoefficient1 = KineticCoefficient1, KineticCoefficient2 = KineticCoefficient2, - StandardDeviation = ResolveRepresentativeValue(GetActiveReplicateCount(), StandardDeviation1, StandardDeviation2), + StandardDeviation = finalStandardDeviation, StandardDeviation1 = StandardDeviation1, StandardDeviation2 = StandardDeviation2, PeakForceN = CurrentPeakForceN, @@ -1054,11 +1069,11 @@ public sealed class MainViewModel : ObservableObject, IDisposable _lastCompletedReciprocatingRecords = CloneReciprocatingRecords(ReciprocatingRecords); PersistLastCompletedRun(); ResetActiveRunContext(); + _machineRuntimeState = MachineRuntimeState.Idle; SelectedRunRecord = record; NextRunIndex++; UpdateDerivedValues(); - _machineRuntimeState = MachineRuntimeState.Idle; MachineState = "待机"; StateBrush = BrushFromHex("#6C8E78"); InterlockMessage = $"试验完成。COFs={record.StaticCoefficient:F3}, COFk={record.KineticCoefficient:F3}"; @@ -1243,6 +1258,41 @@ public sealed class MainViewModel : ObservableObject, IDisposable return IsFinite(selectedValue) ? selectedValue : 0; } + private static double ResolveCompletedMeasurementValue(double value, IEnumerable fallbackValues) + { + if (IsUsableMeasurementValue(value)) + { + return value; + } + + var values = fallbackValues + .Where(value => value is { } number && IsUsableMeasurementValue(number)) + .Select(value => value!.Value) + .ToArray(); + return values.Length == 0 ? 0 : values.Average(); + } + + private static IEnumerable BuildStandardDeviationFallback(IEnumerable values) + { + var samples = values + .Where(value => value is { } number && IsUsableMeasurementValue(number)) + .Select(value => value!.Value) + .ToArray(); + if (samples.Length <= 1) + { + yield break; + } + + var mean = samples.Average(); + var variance = samples.Sum(value => Math.Pow(value - mean, 2)) / samples.Length; + yield return Math.Sqrt(variance); + } + + private static bool IsUsableMeasurementValue(double value) + { + return IsFinite(value) && Math.Abs(value) > 0.0000001; + } + private void UpdateCurrentPoint(double x, double y) { var sample = new ObservablePoint(x, y); @@ -1530,7 +1580,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable _nextReciprocatingRecordIndex++; _lastReciprocatingCycleCompleteSignal = true; _reciprocatingRecordReadFailureLogged = false; - AddInfoEvent($"已记录第 {recordIndex} 次往复数据: COFs={record.StaticCoefficientLabel}, COFk={record.KineticCoefficientLabel}, Fs={record.StaticForceLabel}N, Fk={record.KineticForceLabel}N"); + AddInfoEvent($"已记录第 {recordIndex} 次往复结果。"); } catch (Exception ex) { @@ -1986,8 +2036,12 @@ public sealed class MainViewModel : ObservableObject, IDisposable StaticCoefficient2 = data.Run.StaticCoefficient2; KineticCoefficient2 = data.Run.KineticCoefficient2; StandardDeviation2 = data.Run.StandardDeviation2; - CurrentStaticCoefficient = ResolveRepresentativeValue(data.Recipe.ReplicateCount, StaticCoefficient1, StaticCoefficient2); - CurrentKineticCoefficient = ResolveRepresentativeValue(data.Recipe.ReplicateCount, KineticCoefficient1, KineticCoefficient2); + CurrentStaticCoefficient = ResolveCompletedMeasurementValue( + data.Run.StaticCoefficient, + data.ReciprocatingRecords.Select(record => record.StaticCoefficient)); + CurrentKineticCoefficient = ResolveCompletedMeasurementValue( + data.Run.KineticCoefficient, + data.ReciprocatingRecords.Select(record => record.KineticCoefficient)); TrialProgressPercent = Math.Min(100, CurrentDisplacementMm / Math.Max(data.Recipe.TravelMm, 1) * 100); UpdateReferenceLines(); @@ -2583,11 +2637,17 @@ public sealed class MainViewModel : ObservableObject, IDisposable private void AddEvent(string level, string message) { + var displayMessage = SanitizeSystemEventMessage(message); + if (string.IsNullOrWhiteSpace(displayMessage)) + { + return; + } + EventLog.Insert(0, new SystemEvent { Timestamp = DateTime.Now, Level = level, - Message = message + Message = displayMessage }); while (EventLog.Count > 20) @@ -2596,6 +2656,17 @@ public sealed class MainViewModel : ObservableObject, IDisposable } } + private static string SanitizeSystemEventMessage(string message) + { + var sanitized = PlcAddressPattern.Replace(message, string.Empty); + sanitized = sanitized + .Replace(" ", " ", StringComparison.Ordinal) + .Replace(" : ", ": ", StringComparison.Ordinal) + .Replace(",。", "。", StringComparison.Ordinal) + .Trim(); + return sanitized.TrimEnd(':', ':'); + } + private IEnumerable ValidateRecipe() { if (!_deviceConnectionService.IsConnected)