更新20260622
This commit is contained in:
@@ -12,7 +12,11 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models
|
|||||||
bool IsConnected,
|
bool IsConnected,
|
||||||
string LastError)
|
string LastError)
|
||||||
{
|
{
|
||||||
public double FrictionCoefficient => Math.Abs(VerticalLoadN) > 0.0001
|
// 低载荷段(加载/抬升阶段)垂直力很小,H/V 会放大成无意义尖峰(现场曲线冲到 1.4)。
|
||||||
|
// GB/T 3903.6 初始压力为 50N,载荷低于该量级时摩擦系数无意义,置零以保证曲线干净。
|
||||||
|
public const double MinimumLoadForCoefficientN = 30.0;
|
||||||
|
|
||||||
|
public double FrictionCoefficient => Math.Abs(VerticalLoadN) >= MinimumLoadForCoefficientN
|
||||||
? HorizontalFrictionN / VerticalLoadN
|
? HorizontalFrictionN / VerticalLoadN
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
|
|||||||
private double verticalLoadN;
|
private double verticalLoadN;
|
||||||
private double horizontalFrictionN;
|
private double horizontalFrictionN;
|
||||||
private double displacementMm;
|
private double displacementMm;
|
||||||
|
|
||||||
|
// 轻量滑动平均:滤掉力信号的高频抖动,但窗口很短(默认 3 点≈30ms@100Hz)以保留静摩擦首峰,
|
||||||
|
// 满足 GB/T 3903.6 数据采集频率(≥30Hz、0.3~0.6s 内≥10 点)的前提下让曲线更接近标准。
|
||||||
|
private const int ForceFilterWindow = 3;
|
||||||
|
private readonly Queue<double> pressureFilterSamples = new();
|
||||||
|
private readonly Queue<double> frictionFilterSamples = new();
|
||||||
private int pressureRawValue;
|
private int pressureRawValue;
|
||||||
private int frictionRawValue1;
|
private int frictionRawValue1;
|
||||||
private int frictionRawValue2;
|
private int frictionRawValue2;
|
||||||
@@ -315,8 +321,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
|
|||||||
hasAdcRawValues = true;
|
hasAdcRawValues = true;
|
||||||
if (conversion.IsValid)
|
if (conversion.IsValid)
|
||||||
{
|
{
|
||||||
verticalLoadN = conversion.Pressure;
|
verticalLoadN = MovingAverage(pressureFilterSamples, conversion.Pressure);
|
||||||
horizontalFrictionN = conversion.Friction;
|
horizontalFrictionN = MovingAverage(frictionFilterSamples, conversion.Friction);
|
||||||
adcLastError = string.Empty;
|
adcLastError = string.Empty;
|
||||||
isAdcConnected = true;
|
isAdcConnected = true;
|
||||||
}
|
}
|
||||||
@@ -324,6 +330,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
|
|||||||
{
|
{
|
||||||
verticalLoadN = 0;
|
verticalLoadN = 0;
|
||||||
horizontalFrictionN = 0;
|
horizontalFrictionN = 0;
|
||||||
|
pressureFilterSamples.Clear();
|
||||||
|
frictionFilterSamples.Clear();
|
||||||
adcLastError = conversion.Error;
|
adcLastError = conversion.Error;
|
||||||
isAdcConnected = false;
|
isAdcConnected = false;
|
||||||
}
|
}
|
||||||
@@ -633,6 +641,23 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
|
|||||||
private static double ConvertAdc(int rawValue, double zero, double coefficient) =>
|
private static double ConvertAdc(int rawValue, double zero, double coefficient) =>
|
||||||
(rawValue - zero) / coefficient;
|
(rawValue - zero) / coefficient;
|
||||||
|
|
||||||
|
private static double MovingAverage(Queue<double> window, double sample)
|
||||||
|
{
|
||||||
|
window.Enqueue(sample);
|
||||||
|
while (window.Count > ForceFilterWindow)
|
||||||
|
{
|
||||||
|
window.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sum = 0.0;
|
||||||
|
foreach (var value in window)
|
||||||
|
{
|
||||||
|
sum += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / window.Count;
|
||||||
|
}
|
||||||
|
|
||||||
private static void TryParseSetting(string value, string label, bool requireNonZero, List<string> invalid, out double numericValue)
|
private static void TryParseSetting(string value, string label, bool requireNonZero, List<string> invalid, out double numericValue)
|
||||||
{
|
{
|
||||||
if (!TryParseDouble(value, out numericValue))
|
if (!TryParseDouble(value, out numericValue))
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
private const double StaticPeakDropToleranceN = 0.5;
|
private const double StaticPeakDropToleranceN = 0.5;
|
||||||
private const double SlidingStartDisplacementThresholdMm = 0.05;
|
private const double SlidingStartDisplacementThresholdMm = 0.05;
|
||||||
private const double MinimumAnalysisLoadRatio = 0.8;
|
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;
|
||||||
|
// 曲线/分析窗口:滑动开始后只保留该时长,剔除加载段与滑动后回程,对应标准曲线图的有效区间。
|
||||||
|
private const double CurveEndSeconds = 1.0;
|
||||||
private const int StaticPeakDropConfirmationPointCount = 3;
|
private const int StaticPeakDropConfirmationPointCount = 3;
|
||||||
private const int MinimumDynamicWindowPointCount = 10;
|
private const int MinimumDynamicWindowPointCount = 10;
|
||||||
private const int StandardTrialCount = 3;
|
private const int StandardTrialCount = 3;
|
||||||
@@ -52,6 +60,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
private readonly DispatcherTimer refreshTimer;
|
private readonly DispatcherTimer refreshTimer;
|
||||||
private readonly Stopwatch runStopwatch = new();
|
private readonly Stopwatch runStopwatch = new();
|
||||||
private readonly List<SlipDataPoint> currentRun = [];
|
private readonly List<SlipDataPoint> currentRun = [];
|
||||||
|
private readonly List<SlipDataPoint> preTriggerBuffer = [];
|
||||||
private readonly ObservableCollection<ObservablePoint> verticalLoadPoints = [];
|
private readonly ObservableCollection<ObservablePoint> verticalLoadPoints = [];
|
||||||
private readonly ObservableCollection<ObservablePoint> horizontalFrictionPoints = [];
|
private readonly ObservableCollection<ObservablePoint> horizontalFrictionPoints = [];
|
||||||
private readonly ObservableCollection<ObservablePoint> frictionCoefficientPoints = [];
|
private readonly ObservableCollection<ObservablePoint> frictionCoefficientPoints = [];
|
||||||
@@ -72,6 +81,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
private double runStartDisplacementMm;
|
private double runStartDisplacementMm;
|
||||||
private double slidingStartDisplacementMm;
|
private double slidingStartDisplacementMm;
|
||||||
private double? slidingStartTimeSeconds;
|
private double? slidingStartTimeSeconds;
|
||||||
|
private double slidingBaselineFrictionN;
|
||||||
|
private bool hasSlidingBaseline;
|
||||||
private int activeRunLubricantIndex;
|
private int activeRunLubricantIndex;
|
||||||
private DateTime lastLicenseCheckAt = DateTime.MinValue;
|
private DateTime lastLicenseCheckAt = DateTime.MinValue;
|
||||||
private bool isLicenseLockPending;
|
private bool isLicenseLockPending;
|
||||||
@@ -811,6 +822,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
private void BeginRun()
|
private void BeginRun()
|
||||||
{
|
{
|
||||||
currentRun.Clear();
|
currentRun.Clear();
|
||||||
|
preTriggerBuffer.Clear();
|
||||||
verticalLoadPoints.Clear();
|
verticalLoadPoints.Clear();
|
||||||
horizontalFrictionPoints.Clear();
|
horizontalFrictionPoints.Clear();
|
||||||
frictionCoefficientPoints.Clear();
|
frictionCoefficientPoints.Clear();
|
||||||
@@ -818,6 +830,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
runStartDisplacementMm = deviceService.CurrentSnapshot.DisplacementMm;
|
runStartDisplacementMm = deviceService.CurrentSnapshot.DisplacementMm;
|
||||||
slidingStartDisplacementMm = runStartDisplacementMm;
|
slidingStartDisplacementMm = runStartDisplacementMm;
|
||||||
slidingStartTimeSeconds = null;
|
slidingStartTimeSeconds = null;
|
||||||
|
slidingBaselineFrictionN = 0;
|
||||||
|
hasSlidingBaseline = false;
|
||||||
activeRunLubricantIndex = SelectedLubricantIndex;
|
activeRunLubricantIndex = SelectedLubricantIndex;
|
||||||
runStopwatch.Restart();
|
runStopwatch.Restart();
|
||||||
UploadProgress = 0;
|
UploadProgress = 0;
|
||||||
@@ -834,7 +848,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
private void RecordPoint(SlipDeviceSnapshot device)
|
private void RecordPoint(SlipDeviceSnapshot device)
|
||||||
{
|
{
|
||||||
var elapsedSinceTestStart = runStopwatch.Elapsed.TotalSeconds;
|
var elapsedSinceTestStart = runStopwatch.Elapsed.TotalSeconds;
|
||||||
var detectionPoint = new SlipDataPoint(
|
var samplePoint = new SlipDataPoint(
|
||||||
device.Timestamp,
|
device.Timestamp,
|
||||||
elapsedSinceTestStart,
|
elapsedSinceTestStart,
|
||||||
device.VerticalLoadN,
|
device.VerticalLoadN,
|
||||||
@@ -842,25 +856,114 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
device.DisplacementMm,
|
device.DisplacementMm,
|
||||||
device.FrictionCoefficient);
|
device.FrictionCoefficient);
|
||||||
|
|
||||||
TryMarkSlidingStart(detectionPoint);
|
// 滑动起点尚未确定:把样本缓存进滚动预触发缓冲,并尝试检测滑动起点。
|
||||||
if (!slidingStartTimeSeconds.HasValue)
|
if (!slidingStartTimeSeconds.HasValue)
|
||||||
|
{
|
||||||
|
UpdatePreTrigger(samplePoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardTime = elapsedSinceTestStart - slidingStartTimeSeconds.Value;
|
||||||
|
// 只保留滑动开始后 CurveEndSeconds 区间,剔除滑动结束后的回程/抬升段。
|
||||||
|
if (standardTime > CurveEndSeconds)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var standardTime = Math.Max(0, elapsedSinceTestStart - slidingStartTimeSeconds.Value);
|
AppendCurvePoint(samplePoint, standardTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滑动检测前持续缓存样本,并在载荷达标后用水平摩擦力陡升判定滑动起点。
|
||||||
|
private void UpdatePreTrigger(SlipDataPoint samplePoint)
|
||||||
|
{
|
||||||
|
preTriggerBuffer.Add(samplePoint);
|
||||||
|
var oldestAllowed = samplePoint.TimeSeconds - PreTriggerWindowSeconds;
|
||||||
|
while (preTriggerBuffer.Count > 0 && preTriggerBuffer[0].TimeSeconds < oldestAllowed)
|
||||||
|
{
|
||||||
|
preTriggerBuffer.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var minimumAnalysisLoad = GetMinimumAnalysisLoad();
|
||||||
|
if (samplePoint.VerticalLoadN < minimumAnalysisLoad)
|
||||||
|
{
|
||||||
|
// 载荷尚未到达规定压力(静置接触前),基线需在载荷达标后重新捕获。
|
||||||
|
hasSlidingBaseline = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSlidingBaseline)
|
||||||
|
{
|
||||||
|
// 载荷首次达标:以当前(仅法向加载、尚未水平滑动)的摩擦力为静置接触基线。
|
||||||
|
slidingBaselineFrictionN = samplePoint.HorizontalFrictionN;
|
||||||
|
hasSlidingBaseline = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var frictionRose = samplePoint.HorizontalFrictionN - slidingBaselineFrictionN >= SlidingFrictionTriggerRiseN;
|
||||||
|
var displacementMoved = Math.Abs(samplePoint.DisplacementMm - runStartDisplacementMm) >= SlidingStartDisplacementThresholdMm;
|
||||||
|
if (frictionRose || displacementMoved)
|
||||||
|
{
|
||||||
|
MarkSlidingStartFromBuffer(frictionRose ? "FrictionRise" : "DisplacementMove");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回溯预触发缓冲到摩擦力刚离开基线的那一刻设为 t=0,并把该时刻起的缓冲点补进曲线,保证第一个峰值被采到。
|
||||||
|
private void MarkSlidingStartFromBuffer(string trigger)
|
||||||
|
{
|
||||||
|
var onsetIndex = preTriggerBuffer.Count - 1;
|
||||||
|
while (onsetIndex > 0
|
||||||
|
&& preTriggerBuffer[onsetIndex - 1].HorizontalFrictionN > slidingBaselineFrictionN + SlidingOnsetFrictionMarginN)
|
||||||
|
{
|
||||||
|
onsetIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多回退一个点(≈基线水平)作为 t=0,使曲线起点的摩擦力接近基线、随后升至首峰,与标准曲线一致。
|
||||||
|
onsetIndex = Math.Max(0, onsetIndex - 1);
|
||||||
|
var onset = preTriggerBuffer[onsetIndex];
|
||||||
|
slidingStartTimeSeconds = onset.TimeSeconds;
|
||||||
|
slidingStartDisplacementMm = onset.DisplacementMm;
|
||||||
|
|
||||||
|
for (var index = onsetIndex; index < preTriggerBuffer.Count; index++)
|
||||||
|
{
|
||||||
|
var buffered = preTriggerBuffer[index];
|
||||||
|
var standardTime = buffered.TimeSeconds - onset.TimeSeconds;
|
||||||
|
if (standardTime > CurveEndSeconds)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendCurvePoint(buffered, standardTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
preTriggerBuffer.Clear();
|
||||||
|
|
||||||
|
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,
|
||||||
|
trigger,
|
||||||
|
onset.TimeSeconds,
|
||||||
|
slidingBaselineFrictionN,
|
||||||
|
slidingStartDisplacementMm,
|
||||||
|
currentRun.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把一个样本按标准时间(相对滑动起点)追加到数据与曲线集合,保持各曲线序列同步(节流到 SampleIntervalSeconds)。
|
||||||
|
private void AppendCurvePoint(SlipDataPoint source, double standardTime)
|
||||||
|
{
|
||||||
|
standardTime = Math.Max(0, standardTime);
|
||||||
if (currentRun.Count > 0 && standardTime - currentRun[^1].TimeSeconds < SampleIntervalSeconds)
|
if (currentRun.Count > 0 && standardTime - currentRun[^1].TimeSeconds < SampleIntervalSeconds)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var point = new SlipDataPoint(
|
var point = new SlipDataPoint(
|
||||||
device.Timestamp,
|
source.Timestamp,
|
||||||
standardTime,
|
standardTime,
|
||||||
device.VerticalLoadN,
|
source.VerticalLoadN,
|
||||||
device.HorizontalFrictionN,
|
source.HorizontalFrictionN,
|
||||||
Math.Abs(device.DisplacementMm - slidingStartDisplacementMm),
|
Math.Abs(source.DisplacementMm - slidingStartDisplacementMm),
|
||||||
device.FrictionCoefficient);
|
source.FrictionCoefficient);
|
||||||
|
|
||||||
currentRun.Add(point);
|
currentRun.Add(point);
|
||||||
verticalLoadPoints.Add(new ObservablePoint(standardTime, point.VerticalLoadN));
|
verticalLoadPoints.Add(new ObservablePoint(standardTime, point.VerticalLoadN));
|
||||||
@@ -1091,34 +1194,6 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
ResultSummary = $"近 3 次平均 静 {staticAverage:F2} / 动 {dynamicAverage:F2}";
|
ResultSummary = $"近 3 次平均 静 {staticAverage:F2} / 动 {dynamicAverage:F2}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryMarkSlidingStart(SlipDataPoint point)
|
|
||||||
{
|
|
||||||
if (slidingStartTimeSeconds.HasValue)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var minimumAnalysisLoad = GetMinimumAnalysisLoad();
|
|
||||||
if (!IsSlidingStartPoint(point, runStartDisplacementMm, minimumAnalysisLoad))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
slidingStartTimeSeconds = point.TimeSeconds;
|
|
||||||
slidingStartDisplacementMm = point.DisplacementMm;
|
|
||||||
CurrentStatus = "已检测到有效滑动开始:曲线从 0.000 s 开始记录";
|
|
||||||
Log.Information(
|
|
||||||
"检测到有效滑动开始并建立曲线零点: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,
|
|
||||||
slidingStartDisplacementMm,
|
|
||||||
Math.Abs(point.DisplacementMm - runStartDisplacementMm),
|
|
||||||
point.VerticalLoadN,
|
|
||||||
minimumAnalysisLoad);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double GetMinimumAnalysisLoad() =>
|
private double GetMinimumAnalysisLoad() =>
|
||||||
TryParseLoadValue(TargetLoadText, out var targetLoad)
|
TryParseLoadValue(TargetLoadText, out var targetLoad)
|
||||||
? Math.Max(1, targetLoad * MinimumAnalysisLoadRatio)
|
? Math.Max(1, targetLoad * MinimumAnalysisLoadRatio)
|
||||||
@@ -1132,10 +1207,6 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
|||||||
return double.TryParse(numeric, NumberStyles.Float, CultureInfo.InvariantCulture, out load);
|
return double.TryParse(numeric, NumberStyles.Float, CultureInfo.InvariantCulture, out load);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
private static StaticPeakSelection FindStaticPeak(IReadOnlyList<SlipDataPoint> points)
|
||||||
{
|
{
|
||||||
var searchWindow = points
|
var searchWindow = points
|
||||||
|
|||||||
Reference in New Issue
Block a user