diff --git a/Footwear Test methodsfor wholeshoe Slipresistanceperformance/Services/SlipResistanceDeviceService.cs b/Footwear Test methodsfor wholeshoe Slipresistanceperformance/Services/SlipResistanceDeviceService.cs index 88b6da7..8275a6d 100644 --- a/Footwear Test methodsfor wholeshoe Slipresistanceperformance/Services/SlipResistanceDeviceService.cs +++ b/Footwear Test methodsfor wholeshoe Slipresistanceperformance/Services/SlipResistanceDeviceService.cs @@ -533,11 +533,21 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services var pressure = ConvertAdc(pressureRaw, pressureZero, pressureCoefficient); - // Keep each settings row paired with the same ADC channel used by the legacy zero-capture button. - var friction1 = ConvertAdc(friction1Raw, frictionZero1, frictionCoefficient1); - var friction2 = ConvertAdc(friction2Raw, frictionZero2, frictionCoefficient2); - var friction = (friction1 + friction2) * -1.0; - return AdcConversionResult.Valid(pressure, friction1, friction2, friction); + var namedFriction1 = ConvertAdc(friction1Raw, frictionZero1, frictionCoefficient1); + var namedFriction2 = ConvertAdc(friction2Raw, frictionZero2, frictionCoefficient2); + var namedFriction = (namedFriction1 + namedFriction2) * -1.0; + + var legacyFriction1 = ConvertAdc(friction2Raw, frictionZero1, frictionCoefficient2); + var legacyFriction2 = ConvertAdc(friction1Raw, frictionZero2, frictionCoefficient1); + var legacyFriction = (legacyFriction1 + legacyFriction2) * -1.0; + return AdcConversionResult.Valid( + pressure, + namedFriction1, + namedFriction2, + namedFriction, + legacyFriction1, + legacyFriction2, + legacyFriction); } private bool ValidateAdcSettings( @@ -606,14 +616,18 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services if (conversion.IsValid) { Log.Debug( - "ADC 采样:RawPressure={RawPressure}, RawFriction1={RawFriction1}, RawFriction2={RawFriction2}, Pressure={Pressure:F3} N, Friction1={Friction1:F3} N, Friction2={Friction2:F3} N, Friction={Friction:F3} N, Coefficients=[P:{PressureCoefficient}, F1:{FrictionCoefficient1}, F2:{FrictionCoefficient2}], Zeros=[P:{PressureZero}, F1:{FrictionZero1}, F2:{FrictionZero2}]", + "ADC 采样:RawPressure={RawPressure}, RawFriction1={RawFriction1}, RawFriction2={RawFriction2}, Pressure={Pressure:F3} N, FrictionMode=LegacyCross, FinalFriction={Friction:F3} N, LegacyFriction1={LegacyFriction1:F3} N, LegacyFriction2={LegacyFriction2:F3} N, NamedFriction1={NamedFriction1:F3} N, NamedFriction2={NamedFriction2:F3} N, NamedFriction={NamedFriction:F3} N, FrictionDelta={FrictionDelta:F3} N, Coefficients=[P:{PressureCoefficient}, F1:{FrictionCoefficient1}, F2:{FrictionCoefficient2}], Zeros=[P:{PressureZero}, F1:{FrictionZero1}, F2:{FrictionZero2}]", pressureRaw, friction1Raw, friction2Raw, conversion.Pressure, - conversion.Friction1, - conversion.Friction2, conversion.Friction, + conversion.LegacyFriction1, + conversion.LegacyFriction2, + conversion.NamedFriction1, + conversion.NamedFriction2, + conversion.NamedFriction, + conversion.Friction - conversion.NamedFriction, settings.NormalPressureCoefficient, settings.FrictionCoefficient1, settings.FrictionCoefficient2, @@ -734,16 +748,35 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services private readonly record struct AdcConversionResult( bool IsValid, double Pressure, - double Friction1, - double Friction2, + double NamedFriction1, + double NamedFriction2, + double NamedFriction, + double LegacyFriction1, + double LegacyFriction2, double Friction, string Error) { - public static AdcConversionResult Valid(double pressure, double friction1, double friction2, double friction) => - new(true, pressure, friction1, friction2, friction, string.Empty); + public static AdcConversionResult Valid( + double pressure, + double namedFriction1, + double namedFriction2, + double namedFriction, + double legacyFriction1, + double legacyFriction2, + double friction) => + new( + true, + pressure, + namedFriction1, + namedFriction2, + namedFriction, + legacyFriction1, + legacyFriction2, + friction, + string.Empty); public static AdcConversionResult Invalid(string error) => - new(false, 0, 0, 0, 0, error); + new(false, 0, 0, 0, 0, 0, 0, 0, error); } } } diff --git a/Footwear Test methodsfor wholeshoe Slipresistanceperformance/ViewModels/MainWindowViewModel.cs b/Footwear Test methodsfor wholeshoe Slipresistanceperformance/ViewModels/MainWindowViewModel.cs index 8a9f2b7..b306cb8 100644 --- a/Footwear Test methodsfor wholeshoe Slipresistanceperformance/ViewModels/MainWindowViewModel.cs +++ b/Footwear Test methodsfor wholeshoe Slipresistanceperformance/ViewModels/MainWindowViewModel.cs @@ -36,10 +36,13 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel private const double MinimumAnalysisLoadRatio = 0.8; // 预触发缓冲:滑动检测前持续缓存的力/位移历史长度,用于回溯真实滑动起点(保证第一个摩擦力峰值被采到)。 private const double PreTriggerWindowSeconds = 0.5; - // 滑动检测:载荷达标后,水平摩擦力较静置接触基线上升超过该值即判定已进入滑动。 - private const double SlidingFrictionTriggerRiseN = 5.0; - // 回溯滑动起点:从检测点向前回溯,直到摩擦力回到基线附近(该裕度内)即认定为 onset(t=0)。 - private const double SlidingOnsetFrictionMarginN = 2.0; + // 滑动检测:载荷达标后只接受明显陡升,避免低幅慢漂移提前把曲线 0 点定错。 + private const double SlidingFrictionTriggerRiseN = 35.0; + private const double SlidingFrictionTriggerLookbackSeconds = 0.12; + private const double SlidingFrictionTriggerSlopeN = 25.0; + private const double SlidingDisplacementFrictionRiseN = 20.0; + // 回溯滑动起点:在触发前短窗口内找摩擦最低点,贴近标准曲线中首峰前的真实滑动起点。 + private const double SlidingOnsetSearchBackSeconds = 0.12; // 曲线/分析窗口:滑动开始后只保留该时长,剔除加载段与滑动后回程,对应标准曲线图的有效区间。 private const double CurveEndSeconds = 1.0; private const int StaticPeakDropConfirmationPointCount = 3; @@ -900,26 +903,67 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel return; } - var frictionRose = samplePoint.HorizontalFrictionN - slidingBaselineFrictionN >= SlidingFrictionTriggerRiseN; + var frictionRiseFromBaseline = samplePoint.HorizontalFrictionN - slidingBaselineFrictionN; + var lookbackPoint = FindLookbackPoint(samplePoint.TimeSeconds - SlidingFrictionTriggerLookbackSeconds); + var frictionRiseFromLookback = lookbackPoint is null + ? 0 + : samplePoint.HorizontalFrictionN - lookbackPoint.HorizontalFrictionN; + var frictionRose = frictionRiseFromBaseline >= SlidingFrictionTriggerRiseN + && frictionRiseFromLookback >= SlidingFrictionTriggerSlopeN; var displacementMoved = Math.Abs(samplePoint.DisplacementMm - runStartDisplacementMm) >= SlidingStartDisplacementThresholdMm; - if (frictionRose || displacementMoved) + var displacementWithFriction = displacementMoved + && frictionRiseFromBaseline >= SlidingDisplacementFrictionRiseN; + if (frictionRose || displacementWithFriction) { - MarkSlidingStartFromBuffer(frictionRose ? "FrictionRise" : "DisplacementMove"); + MarkSlidingStartFromBuffer(frictionRose ? "FrictionSharpRise" : "DisplacementWithFrictionRise"); + return; + } + + if (displacementMoved) + { + Log.Debug( + "滑动起点候选未触发:TestNumber={TestNumber}, Reason=DisplacementWithoutFrictionRise, DisplacementDelta={DisplacementDelta:F3}mm, FrictionRiseFromBaseline={FrictionRiseFromBaseline:F3}N, FrictionRiseFromLookback={FrictionRiseFromLookback:F3}N, BaselineFriction={BaselineFriction:F3}N, CurrentFriction={CurrentFriction:F3}N", + TestNumber, + Math.Abs(samplePoint.DisplacementMm - runStartDisplacementMm), + frictionRiseFromBaseline, + frictionRiseFromLookback, + slidingBaselineFrictionN, + samplePoint.HorizontalFrictionN); } } - // 回溯预触发缓冲到摩擦力刚离开基线的那一刻设为 t=0,并把该时刻起的缓冲点补进曲线,保证第一个峰值被采到。 - private void MarkSlidingStartFromBuffer(string trigger) + private SlipDataPoint? FindLookbackPoint(double targetTime) { - var onsetIndex = preTriggerBuffer.Count - 1; - while (onsetIndex > 0 - && preTriggerBuffer[onsetIndex - 1].HorizontalFrictionN > slidingBaselineFrictionN + SlidingOnsetFrictionMarginN) + for (var index = preTriggerBuffer.Count - 1; index >= 0; index--) { - onsetIndex--; + if (preTriggerBuffer[index].TimeSeconds <= targetTime) + { + return preTriggerBuffer[index]; + } + } + + return preTriggerBuffer.Count > 0 ? preTriggerBuffer[0] : null; + } + + // 回溯预触发缓冲到摩擦力陡升前的低点设为 t=0,并把该时刻起的缓冲点补进曲线,保证第一个峰值被采到。 + private void MarkSlidingStartFromBuffer(string trigger) + { + var triggerPoint = preTriggerBuffer[^1]; + var earliestOnsetTime = triggerPoint.TimeSeconds - SlidingOnsetSearchBackSeconds; + var onsetIndex = preTriggerBuffer.Count - 1; + for (var index = preTriggerBuffer.Count - 1; index >= 0; index--) + { + if (preTriggerBuffer[index].TimeSeconds < earliestOnsetTime) + { + break; + } + + if (preTriggerBuffer[index].HorizontalFrictionN <= preTriggerBuffer[onsetIndex].HorizontalFrictionN) + { + onsetIndex = index; + } } - // 多回退一个点(≈基线水平)作为 t=0,使曲线起点的摩擦力接近基线、随后升至首峰,与标准曲线一致。 - onsetIndex = Math.Max(0, onsetIndex - 1); var onset = preTriggerBuffer[onsetIndex]; slidingStartTimeSeconds = onset.TimeSeconds; slidingStartDisplacementMm = onset.DisplacementMm; @@ -940,12 +984,17 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel CurrentStatus = "已检测到有效滑动开始:曲线从 0.000 s 开始记录"; Log.Information( - "检测到有效滑动开始并建立曲线零点:TestNumber={TestNumber}, Trigger={Trigger}, SlidingStartTime={SlidingStartTime:F3}s, BaselineFriction={BaselineFriction:F3}N, OnsetDisplacement={OnsetDisplacement:F3}mm, FlushedPointCount={FlushedPointCount}", + "检测到有效滑动开始并建立曲线零点:TestNumber={TestNumber}, Trigger={Trigger}, SlidingStartTime={SlidingStartTime:F3}s, TriggerTime={TriggerTime:F3}s, BaselineFriction={BaselineFriction:F3}N, OnsetFriction={OnsetFriction:F3}N, TriggerFriction={TriggerFriction:F3}N, FrictionRise={FrictionRise:F3}N, OnsetDisplacement={OnsetDisplacement:F3}mm, TriggerDisplacement={TriggerDisplacement:F3}mm, FlushedPointCount={FlushedPointCount}", TestNumber, trigger, onset.TimeSeconds, + triggerPoint.TimeSeconds, slidingBaselineFrictionN, + onset.HorizontalFrictionN, + triggerPoint.HorizontalFrictionN, + triggerPoint.HorizontalFrictionN - onset.HorizontalFrictionN, slidingStartDisplacementMm, + triggerPoint.DisplacementMm, currentRun.Count); }