更新曲线

This commit is contained in:
GukSang.Jin
2026-06-08 11:38:57 +08:00
parent c9d9dacf9a
commit 98f92e48ad
2 changed files with 169 additions and 117 deletions

View File

@@ -65,6 +65,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private List<SlipDataPoint> lastCompletedRun = [];
private DateTime lastRealtimeCurveTraceLoggedAt = DateTime.MinValue;
private double runStartDisplacementMm;
private double slidingStartDisplacementMm;
private double? slidingStartTimeSeconds;
[ObservableProperty]
@@ -224,7 +225,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
[
new Axis
{
Name = "压力 / 摩擦力 / 位移",
Name = "垂直载荷 / 水平摩擦力 (N)",
MinLimit = 0,
SeparatorsPaint = new SolidColorPaint(SKColor.Parse("#D7E0EA")) { StrokeThickness = 1 },
SubseparatorsPaint = new SolidColorPaint(SKColor.Parse("#EEF3F8")) { StrokeThickness = 1 },
@@ -236,6 +237,18 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
Padding = new LiveChartsCore.Drawing.Padding(2, 2, 3, 2)
},
new Axis
{
Name = "滑动位移 (mm)",
Position = LiveChartsCore.Measure.AxisPosition.End,
MinLimit = 0,
LabelsPaint = new SolidColorPaint(SKColor.Parse("#1D4ED8")),
NamePaint = new SolidColorPaint(SKColor.Parse("#1D4ED8")),
SeparatorsPaint = null,
TextSize = 11,
NameTextSize = 12,
Padding = new LiveChartsCore.Drawing.Padding(3, 2, 2, 2)
},
new Axis
{
Name = "摩擦系数",
Position = LiveChartsCore.Measure.AxisPosition.End,
@@ -243,7 +256,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
UnitWidth = 0.1,
LabelsPaint = new SolidColorPaint(SKColor.Parse("#7E22CE")),
NamePaint = new SolidColorPaint(SKColor.Parse("#7E22CE")),
SeparatorsPaint = new SolidColorPaint(SKColor.Parse("#F1E7FF")) { StrokeThickness = 1 },
SeparatorsPaint = null,
TextSize = 11,
NameTextSize = 12,
Padding = new LiveChartsCore.Drawing.Padding(3, 2, 2, 2)
@@ -255,10 +268,10 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
Log.Information("初始化主页面 ViewModel");
Series =
[
CreateLineSeries("垂直压力(N)", verticalLoadPoints, "#DC2626", 0, 0),
CreateLineSeries("水平摩擦力(N)", horizontalFrictionPoints, "#16A34A", 0, 0),
CreateLineSeries("摩擦系数", frictionCoefficientPoints, "#C026D3", 1, 0),
CreateLineSeries("位移(mm)", displacementPoints, "#2563EB", 0, 0)
CreateLineSeries("垂直载荷(N)", verticalLoadPoints, "#DC2626", 0),
CreateLineSeries("水平摩擦力(N)", horizontalFrictionPoints, "#16A34A", 0),
CreateLineSeries("滑动位移(mm)", displacementPoints, "#2563EB", 1),
CreateLineSeries("摩擦系数", frictionCoefficientPoints, "#C026D3", 2)
];
LoadDeviceSettings();
@@ -681,13 +694,14 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
frictionCoefficientPoints.Clear();
displacementPoints.Clear();
runStartDisplacementMm = deviceService.CurrentSnapshot.DisplacementMm;
slidingStartDisplacementMm = runStartDisplacementMm;
slidingStartTimeSeconds = null;
runStopwatch.Restart();
UploadProgress = 0;
lastRealtimeCurveTraceLoggedAt = DateTime.MinValue;
CurrentStatus = "测试运行:按标准采集垂直载荷、摩擦力、位移与摩擦系数";
CurrentStatus = "测试运行:等待检测有效滑动开始,曲线尚未记录";
Log.Information(
"测试开始TestNumber={TestNumber}, TargetLoad={TargetLoad}, TestSpeed={TestSpeed}, StartDisplacement={StartDisplacement:F3}mm",
"测试开始TestNumber={TestNumber}, TargetLoad={TargetLoad}, TestSpeed={TestSpeed}, StartDisplacement={StartDisplacement:F3}mm, CurveRecording=等待有效滑动开始",
TestNumber,
TargetLoadText,
TestSpeedText,
@@ -696,26 +710,40 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private void RecordPoint(SlipDeviceSnapshot device)
{
var time = runStopwatch.Elapsed.TotalSeconds;
if (currentRun.Count > 0 && time - currentRun[^1].TimeSeconds < SampleIntervalSeconds)
var elapsedSinceTestStart = runStopwatch.Elapsed.TotalSeconds;
var detectionPoint = new SlipDataPoint(
device.Timestamp,
elapsedSinceTestStart,
device.VerticalLoadN,
device.HorizontalFrictionN,
device.DisplacementMm,
device.FrictionCoefficient);
TryMarkSlidingStart(detectionPoint);
if (!slidingStartTimeSeconds.HasValue)
{
return;
}
var standardTime = Math.Max(0, elapsedSinceTestStart - slidingStartTimeSeconds.Value);
if (currentRun.Count > 0 && standardTime - currentRun[^1].TimeSeconds < SampleIntervalSeconds)
{
return;
}
var point = new SlipDataPoint(
device.Timestamp,
time,
standardTime,
device.VerticalLoadN,
device.HorizontalFrictionN,
device.DisplacementMm,
Math.Abs(device.DisplacementMm - slidingStartDisplacementMm),
device.FrictionCoefficient);
TryMarkSlidingStart(point);
currentRun.Add(point);
verticalLoadPoints.Add(new ObservablePoint(time, point.VerticalLoadN));
horizontalFrictionPoints.Add(new ObservablePoint(time, point.HorizontalFrictionN));
frictionCoefficientPoints.Add(new ObservablePoint(time, point.FrictionCoefficient));
displacementPoints.Add(new ObservablePoint(time, point.DisplacementMm));
verticalLoadPoints.Add(new ObservablePoint(standardTime, point.VerticalLoadN));
horizontalFrictionPoints.Add(new ObservablePoint(standardTime, point.HorizontalFrictionN));
displacementPoints.Add(new ObservablePoint(standardTime, point.DisplacementMm));
frictionCoefficientPoints.Add(new ObservablePoint(standardTime, point.FrictionCoefficient));
UploadProgress = Math.Min(99, currentRun.Count);
TraceRealtimeCurvePoint(point);
@@ -725,51 +753,52 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
{
runStopwatch.Stop();
LogRealtimeCurveSummary("测试停止");
if (!slidingStartTimeSeconds.HasValue)
{
Log.Warning(
"测试停止但未检测到有效滑动开始TestNumber={TestNumber}, StartDisplacement={StartDisplacement:F3}mm, DisplacementThreshold={DisplacementThreshold:F3}mm, MinimumAnalysisLoad={MinimumAnalysisLoad:F3}N, CurvePointCount={CurvePointCount}",
TestNumber,
runStartDisplacementMm,
SlidingStartDisplacementThresholdMm,
GetMinimumAnalysisLoad(),
currentRun.Count);
CurrentStatus = "测试已停止,但未检测到有效滑动开始,曲线未记录且未生成结果";
return;
}
if (currentRun.Count < 3)
{
Log.Warning("测试停止但采样点不足TestNumber={TestNumber}, PointCount={PointCount}", TestNumber, currentRun.Count);
Log.Warning(
"测试停止但滑动后采样点不足TestNumber={TestNumber}, SlidingDetectionDelay={SlidingDetectionDelay:F3}s, PointCount={PointCount}",
TestNumber,
slidingStartTimeSeconds.Value,
currentRun.Count);
CurrentStatus = "测试已停止,但有效采样点不足,未生成结果";
return;
}
lastCompletedRun = currentRun.ToList();
var minimumAnalysisLoad = GetMinimumAnalysisLoad();
if (!TryFindSlidingStart(currentRun, runStartDisplacementMm, minimumAnalysisLoad, out var slidingStartPoint))
{
Log.Warning(
"测试停止但未检测到有效滑动开始TestNumber={TestNumber}, PointCount={PointCount}, StartDisplacement={StartDisplacement:F3}mm, DisplacementThreshold={DisplacementThreshold:F3}mm, MinimumAnalysisLoad={MinimumAnalysisLoad:F3}N, FirstSample={FirstSample}, LastSample={LastSample}",
TestNumber,
currentRun.Count,
runStartDisplacementMm,
SlidingStartDisplacementThresholdMm,
minimumAnalysisLoad,
FormatDataPoint(currentRun[0]),
FormatDataPoint(currentRun[^1]));
CurrentStatus = "测试已停止,但未检测到有效滑动开始,未生成结果";
return;
}
var slidingStartTime = slidingStartPoint.TimeSeconds;
var staticPeak = FindStaticPeak(currentRun, slidingStartTime);
const double slidingStartTime = 0;
var staticPeak = FindStaticPeak(currentRun);
var peak = staticPeak.Point;
var dynamicWindowStart = slidingStartTime + DynamicWindowStartSeconds;
var dynamicWindowEnd = slidingStartTime + DynamicWindowEndSeconds;
var dynamicWindow = currentRun
.Where(point => point.TimeSeconds >= dynamicWindowStart && point.TimeSeconds <= dynamicWindowEnd)
.Where(IsInDynamicWindow)
.ToList();
if (dynamicWindow.Count < MinimumDynamicWindowPointCount)
{
var firstWindowPoint = dynamicWindow.FirstOrDefault();
var lastWindowPoint = dynamicWindow.LastOrDefault();
Log.Warning(
"测试停止但动摩擦窗口采样点不足TestNumber={TestNumber}, PointCount={PointCount}, SlidingStartTime={SlidingStartTime:F3}s, DynamicWindow=滑动开始后0.300-0.600s, DynamicWindowPointCount={DynamicWindowPointCount}, RequiredPointCount={RequiredPointCount}, ActualWindowStart={ActualWindowStart}, ActualWindowEnd={ActualWindowEnd}, FirstSampleTime={FirstSampleTime:F3}s, LastSampleTime={LastSampleTime:F3}s",
"测试停止但动摩擦窗口采样点不足TestNumber={TestNumber}, PointCount={PointCount}, SlidingDetectionDelay={SlidingDetectionDelay:F3}s, StandardTimeOrigin={StandardTimeOrigin:F3}s, DynamicWindow=滑动开始后0.300-0.600s, DynamicWindowPointCount={DynamicWindowPointCount}, RequiredPointCount={RequiredPointCount}, ActualWindowStart={ActualWindowStart}, ActualWindowEnd={ActualWindowEnd}, FirstSampleTime={FirstSampleTime:F3}s, LastSampleTime={LastSampleTime:F3}s",
TestNumber,
currentRun.Count,
slidingStartTimeSeconds.Value,
slidingStartTime,
dynamicWindow.Count,
MinimumDynamicWindowPointCount,
FormatRelativeTime(firstWindowPoint, slidingStartTime),
FormatRelativeTime(lastWindowPoint, slidingStartTime),
FormatStandardTime(firstWindowPoint),
FormatStandardTime(lastWindowPoint),
currentRun[0].TimeSeconds,
currentRun[^1].TimeSeconds);
CurrentStatus = "测试已停止0.3 s~0.6 s 有效采样点不足,未生成结果";
@@ -782,9 +811,9 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
if (peak.VerticalLoadN < minimumAnalysisLoad || dynamicLoad < minimumAnalysisLoad)
{
Log.Warning(
"测试停止但静/动摩擦窗口载荷不足TestNumber={TestNumber}, SlidingStartTime={SlidingStartTime:F3}s, MinimumAnalysisLoad={MinimumAnalysisLoad:F3}N, StaticLoad={StaticLoad:F3}N, DynamicAvgLoad={DynamicAvgLoad:F3}N, StaticPoint={StaticPoint}, DynamicPointCount={DynamicPointCount}",
"测试停止但静/动摩擦窗口载荷不足TestNumber={TestNumber}, SlidingDetectionDelay={SlidingDetectionDelay:F3}s, MinimumAnalysisLoad={MinimumAnalysisLoad:F3}N, StaticLoad={StaticLoad:F3}N, DynamicAvgLoad={DynamicAvgLoad:F3}N, StaticPoint={StaticPoint}, DynamicPointCount={DynamicPointCount}",
TestNumber,
slidingStartTime,
slidingStartTimeSeconds.Value,
minimumAnalysisLoad,
peak.VerticalLoadN,
dynamicLoad,
@@ -798,19 +827,20 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
var verdict = NeedsRetest(staticCoefficientValue, dynamicCoefficientValue) ? "需重测" : "有效";
var nextIndex = Samples.Count == 0 ? 1 : Samples.Max(sample => sample.Index) + 1;
var peakIndex = currentRun.IndexOf(peak) + 1;
var dynamicStart = dynamicWindow[0].TimeSeconds - slidingStartTime;
var dynamicEnd = dynamicWindow[^1].TimeSeconds - slidingStartTime;
var dynamicStart = dynamicWindow[0].TimeSeconds;
var dynamicEnd = dynamicWindow[^1].TimeSeconds;
Log.Information(
"静/动摩擦计算明细TestNumber={TestNumber}, PointCount={PointCount}, SlidingStartTime={SlidingStartTime:F3}s, SlidingStartDisplacement={SlidingStartDisplacement:F3}mm, StaticSearchWindow=滑动开始后首个峰值0.000-{StaticSearchEnd:F3}s, StaticPeakMode={StaticPeakMode}, StaticPointIndex={StaticPointIndex}, StaticTime={StaticTime:F3}s, StaticFriction={StaticFriction:F3}N, StaticLoad={StaticLoad:F3}N, StaticCoefficient={StaticCoefficient:F5}, DynamicWindow=滑动开始后{DynamicWindowStart:F3}-{DynamicWindowEnd:F3}s, DynamicActualWindow={DynamicActualStart:F3}-{DynamicActualEnd:F3}s, DynamicPointCount={DynamicPointCount}, DynamicAvgFriction={DynamicAvgFriction:F3}N, DynamicAvgLoad={DynamicAvgLoad:F3}N, DynamicCoefficient={DynamicCoefficient:F5}",
"静/动摩擦计算明细TestNumber={TestNumber}, PointCount={PointCount}, SlidingDetectionDelay={SlidingDetectionDelay:F3}s, StandardTimeOrigin={StandardTimeOrigin:F3}s, AbsoluteSlidingStartDisplacement={AbsoluteSlidingStartDisplacement:F3}mm, StaticSearchWindow=滑动开始后首个峰值0.000-{StaticSearchEnd:F3}s, StaticPeakMode={StaticPeakMode}, StaticPointIndex={StaticPointIndex}, StaticTime={StaticTime:F3}s, StaticFriction={StaticFriction:F3}N, StaticLoad={StaticLoad:F3}N, StaticCoefficient={StaticCoefficient:F5}, DynamicWindow=滑动开始后{DynamicWindowStart:F3}-{DynamicWindowEnd:F3}s, DynamicActualWindow={DynamicActualStart:F3}-{DynamicActualEnd:F3}s, DynamicPointCount={DynamicPointCount}, DynamicAvgFriction={DynamicAvgFriction:F3}N, DynamicAvgLoad={DynamicAvgLoad:F3}N, DynamicCoefficient={DynamicCoefficient:F5}",
TestNumber,
currentRun.Count,
slidingStartTimeSeconds.Value,
slidingStartTime,
slidingStartPoint.DisplacementMm,
slidingStartDisplacementMm,
StaticPeakSearchEndSeconds,
staticPeak.Mode,
peakIndex,
peak.TimeSeconds - slidingStartTime,
peak.TimeSeconds,
peak.HorizontalFrictionN,
peak.VerticalLoadN,
staticCoefficientValue,
@@ -924,12 +954,15 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
}
slidingStartTimeSeconds = point.TimeSeconds;
slidingStartDisplacementMm = point.DisplacementMm;
CurrentStatus = "已检测到有效滑动开始:曲线从 0.000 s 开始记录";
Log.Information(
"检测到有效滑动开始TestNumber={TestNumber}, SlidingStartTime={SlidingStartTime:F3}s, StartDisplacement={StartDisplacement:F3}mm, CurrentDisplacement={CurrentDisplacement:F3}mm, DisplacementDelta={DisplacementDelta:F3}mm, VerticalLoad={VerticalLoad:F3}N, MinimumAnalysisLoad={MinimumAnalysisLoad:F3}N",
"检测到有效滑动开始并建立曲线零点TestNumber={TestNumber}, SlidingStartTime={SlidingStartTime:F3}s, SlidingDetectionDelay={SlidingDetectionDelay:F3}s, StandardTimeOrigin=0.000s, StartDisplacement={StartDisplacement:F3}mm, SlidingStartDisplacement={SlidingStartDisplacement:F3}mm, DisplacementDelta={DisplacementDelta:F3}mm, VerticalLoad={VerticalLoad:F3}N, MinimumAnalysisLoad={MinimumAnalysisLoad:F3}N",
TestNumber,
point.TimeSeconds,
point.TimeSeconds,
runStartDisplacementMm,
point.DisplacementMm,
slidingStartDisplacementMm,
Math.Abs(point.DisplacementMm - runStartDisplacementMm),
point.VerticalLoadN,
minimumAnalysisLoad);
@@ -948,34 +981,15 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
return double.TryParse(numeric, NumberStyles.Float, CultureInfo.InvariantCulture, out load);
}
private static bool TryFindSlidingStart(
IReadOnlyList<SlipDataPoint> points,
double startDisplacementMm,
double minimumAnalysisLoad,
out SlipDataPoint slidingStartPoint)
{
foreach (var point in points)
{
if (IsSlidingStartPoint(point, startDisplacementMm, minimumAnalysisLoad))
{
slidingStartPoint = point;
return true;
}
}
slidingStartPoint = points[0];
return false;
}
private static bool IsSlidingStartPoint(SlipDataPoint point, double startDisplacementMm, double minimumAnalysisLoad) =>
point.VerticalLoadN >= minimumAnalysisLoad
&& Math.Abs(point.DisplacementMm - startDisplacementMm) >= SlidingStartDisplacementThresholdMm;
private static StaticPeakSelection FindStaticPeak(IReadOnlyList<SlipDataPoint> points, double slidingStartTime)
private static StaticPeakSelection FindStaticPeak(IReadOnlyList<SlipDataPoint> points)
{
var searchWindow = points
.Where(point => point.TimeSeconds >= slidingStartTime
&& point.TimeSeconds <= slidingStartTime + StaticPeakSearchEndSeconds)
.Where(point => point.TimeSeconds >= 0
&& point.TimeSeconds <= StaticPeakSearchEndSeconds)
.ToList();
if (searchWindow.Count == 0)
{
@@ -1110,12 +1124,12 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
TestNumber,
expectedCount,
point.TimeSeconds,
FormatRelativeTime(point, slidingStartTimeSeconds),
FormatStandardTime(point),
point.VerticalLoadN,
point.HorizontalFrictionN,
point.FrictionCoefficient,
point.DisplacementMm,
IsInDynamicWindow(point, slidingStartTimeSeconds),
IsInDynamicWindow(point),
expectedCount);
}
@@ -1133,26 +1147,16 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private static string FormatNullable(double? value) =>
value.HasValue ? value.Value.ToString("F3", CultureInfo.InvariantCulture) : "null";
private static string FormatRelativeTime(SlipDataPoint? point, double? startTime) =>
point is null || !startTime.HasValue
? "null"
: (point.TimeSeconds - startTime.Value).ToString("F3", CultureInfo.InvariantCulture);
private static string FormatStandardTime(SlipDataPoint? point) =>
point is null ? "null" : point.TimeSeconds.ToString("F3", CultureInfo.InvariantCulture);
private static bool IsInDynamicWindow(SlipDataPoint point, double? startTime)
{
if (!startTime.HasValue)
{
return false;
}
var standardTime = point.TimeSeconds - startTime.Value;
return standardTime >= DynamicWindowStartSeconds && standardTime <= DynamicWindowEndSeconds;
}
private static bool IsInDynamicWindow(SlipDataPoint point) =>
point.TimeSeconds >= DynamicWindowStartSeconds && point.TimeSeconds <= DynamicWindowEndSeconds;
private static string FormatDataPoint(SlipDataPoint? point) =>
point is null
? "null"
: $"Time={point.TimeSeconds.ToString("F3", CultureInfo.InvariantCulture)}s, Vertical={point.VerticalLoadN.ToString("F3", CultureInfo.InvariantCulture)}N, Friction={point.HorizontalFrictionN.ToString("F3", CultureInfo.InvariantCulture)}N, Coefficient={point.FrictionCoefficient.ToString("F5", CultureInfo.InvariantCulture)}, Displacement={point.DisplacementMm.ToString("F3", CultureInfo.InvariantCulture)}mm";
: $"StandardTime={point.TimeSeconds.ToString("F3", CultureInfo.InvariantCulture)}s, Vertical={point.VerticalLoadN.ToString("F3", CultureInfo.InvariantCulture)}N, Friction={point.HorizontalFrictionN.ToString("F3", CultureInfo.InvariantCulture)}N, Coefficient={point.FrictionCoefficient.ToString("F5", CultureInfo.InvariantCulture)}, SlidingDisplacement={point.DisplacementMm.ToString("F3", CultureInfo.InvariantCulture)}mm";
private async Task RunDeviceCommand(Task command, string successMessage)
{
@@ -1394,8 +1398,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
string name,
ObservableCollection<ObservablePoint> values,
string color,
int yAxis,
double smoothness) =>
int yAxis) =>
new()
{
Name = name,
@@ -1407,7 +1410,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
GeometryFill = null,
GeometryStroke = null,
GeometrySize = 0,
LineSmoothness = smoothness,
LineSmoothness = 0,
ScalesYAt = yAxis
};