using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models; using Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services; using LiveChartsCore; using LiveChartsCore.Defaults; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Painting; using Serilog; using SkiaSharp; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModels { public partial class MainWindowViewModel : ViewModelBase, IDisposable { private const double SampleIntervalSeconds = 1.0 / 50.0; private const double DynamicWindowStartSeconds = 0.3; private const double DynamicWindowEndSeconds = 0.6; private const int MinimumDynamicWindowPointCount = 10; private const int StandardTrialCount = 3; private readonly SlipResistanceDeviceService deviceService = new(); private readonly SlipExcelExportService excelExportService = new(); private readonly DispatcherTimer refreshTimer; private readonly Stopwatch runStopwatch = new(); private readonly List currentRun = []; private readonly ObservableCollection verticalLoadPoints = []; private readonly ObservableCollection horizontalFrictionPoints = []; private readonly ObservableCollection frictionCoefficientPoints = []; private readonly ObservableCollection displacementPoints = []; private bool isLoadingDeviceSettings; private bool wasRunning; private List lastCompletedRun = []; [ObservableProperty] private string testNumber = $"SLIP-{DateTime.Now:yyyyMMdd-HHmm}"; [ObservableProperty] private string operatorName = string.Empty; [ObservableProperty] private string methodName = "GB/T 3903.6-2024"; [ObservableProperty] private string reportName = string.Empty; [ObservableProperty] private string sampleFeature = "整鞋样品 / 瓷砖接触面 / 水平测试"; [ObservableProperty] private string currentStatus = "设备连接中,等待 PLC 与 ADC 实时数据"; [ObservableProperty] private int uploadProgress; [ObservableProperty] private string manualDistance = "0"; [ObservableProperty] private int selectedSampleIndex; [ObservableProperty] private bool isSettingsDialogOpen; [ObservableProperty] private string manualSpeed = "0.00"; [ObservableProperty] private string manualDisplacement = "0.00"; [ObservableProperty] private string testSpeed = "0.30"; [ObservableProperty] private string normalPressureZero = "0"; [ObservableProperty] private string normalPressureCoefficient = "0.00"; [ObservableProperty] private string frictionZero1 = "0"; [ObservableProperty] private string frictionCoefficient1 = "0.00"; [ObservableProperty] private string frictionZero2 = "0"; [ObservableProperty] private string frictionCoefficient2 = "0.00"; [ObservableProperty] private string plcPortName = "COM7"; [ObservableProperty] private string adcPortName = "COM8"; [ObservableProperty] private int baudRate = 115200; [ObservableProperty] private int selectedShoeSizeIndex; [ObservableProperty] private int selectedLubricantIndex; [ObservableProperty] private int selectedModeIndex = 2; [ObservableProperty] private int selectedSurfaceIndex; [ObservableProperty] private string targetLoadText = "400 N"; [ObservableProperty] private string actualLoadText = "0.0 N"; [ObservableProperty] private string deviceStatus = "离线"; [ObservableProperty] private string activeMode = "水平测试模式"; [ObservableProperty] private string resultSummary = "等待 3 次有效试验"; [ObservableProperty] private string staticCoefficient = "0.00"; [ObservableProperty] private string dynamicCoefficient = "0.00"; [ObservableProperty] private string verticalPressure = "0.0"; [ObservableProperty] private string horizontalForce = "0.0"; [ObservableProperty] private string frictionCoefficient = "0.000"; [ObservableProperty] private string distance = "0.0"; public string TestSpeedText => $"{TestSpeed} m/s"; public string SampleRateText { get; } = "50 Hz"; public string BatchNumber { get; } = DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); public string UploadProgressText => $"{UploadProgress}%"; public string StandardReference { get; } = "GB/T 3903.6-2024:实时采集、静/动摩擦系数、三次平均与重测判定"; public ObservableCollection Samples { get; } = []; public DrawMarginFrame ChartFrame { get; } = new() { Fill = new SolidColorPaint(SKColor.Parse("#FFFFFF")), Stroke = new SolidColorPaint(SKColor.Parse("#CBD5E1")) { StrokeThickness = 1.4f } }; public SolidColorPaint LegendTextPaint { get; } = new(SKColor.Parse("#334155")); public ISeries[] Series { get; } public Axis[] XAxes { get; } = [ new Axis { Name = "时间(s)", MinLimit = 0, UnitWidth = 0.1, SeparatorsPaint = new SolidColorPaint(SKColor.Parse("#D7E0EA")) { StrokeThickness = 1 }, SubseparatorsPaint = new SolidColorPaint(SKColor.Parse("#EEF3F8")) { StrokeThickness = 1 }, SubseparatorsCount = 4, LabelsPaint = new SolidColorPaint(SKColor.Parse("#475569")), NamePaint = new SolidColorPaint(SKColor.Parse("#334155")), TextSize = 12, NameTextSize = 13, Padding = new LiveChartsCore.Drawing.Padding(4, 3, 4, 3) } ]; public Axis[] YAxes { get; } = [ new Axis { Name = "压力 / 摩擦力 / 位移", MinLimit = 0, SeparatorsPaint = new SolidColorPaint(SKColor.Parse("#D7E0EA")) { StrokeThickness = 1 }, SubseparatorsPaint = new SolidColorPaint(SKColor.Parse("#EEF3F8")) { StrokeThickness = 1 }, SubseparatorsCount = 4, LabelsPaint = new SolidColorPaint(SKColor.Parse("#475569")), NamePaint = new SolidColorPaint(SKColor.Parse("#334155")), TextSize = 11, NameTextSize = 12, Padding = new LiveChartsCore.Drawing.Padding(2, 2, 3, 2) }, new Axis { Name = "摩擦系数", Position = LiveChartsCore.Measure.AxisPosition.End, MinLimit = 0, UnitWidth = 0.1, LabelsPaint = new SolidColorPaint(SKColor.Parse("#7E22CE")), NamePaint = new SolidColorPaint(SKColor.Parse("#7E22CE")), SeparatorsPaint = new SolidColorPaint(SKColor.Parse("#F1E7FF")) { StrokeThickness = 1 }, TextSize = 11, NameTextSize = 12, Padding = new LiveChartsCore.Drawing.Padding(3, 2, 2, 2) } ]; public MainWindowViewModel() { 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) ]; LoadDeviceSettings(); UpdateTargetLoad(); deviceService.Start(CurrentSettings()); _ = LoadPlcParametersAsync(); refreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(20) }; refreshTimer.Tick += (_, _) => RefreshFromDevice(); refreshTimer.Start(); } [RelayCommand] private async Task Clear() { await RunDeviceCommand(deviceService.PulseResetAsync(), "已发送复位指令 M90"); } [RelayCommand] private void Preload() { CurrentStatus = "请在 0.2 s 内施加目标垂直载荷,载荷达到后启动滑动测试"; } [RelayCommand] private async Task StartTest() { BeginRun(); await RunDeviceCommand(deviceService.PulseStartTestAsync(), "已发送测试启动指令 M80,等待 M81 运行状态"); } [RelayCommand] private async Task StopTest() { await RunDeviceCommand(deviceService.PulseStopTestAsync(), "已发送测试停止指令 M83"); } [RelayCommand] private void ExportReport() { var points = currentRun.Count > 0 ? currentRun.ToList() : lastCompletedRun; if (points.Count == 0) { Log.Warning("请求导出 Excel 时没有可导出的实时采样数据:TestNumber={TestNumber}", TestNumber); CurrentStatus = "没有可导出的实时采样数据,请先完成一次测试"; return; } try { var report = new SlipReportExport( TestNumber, OperatorName, MethodName, ReportName, SampleFeature, CurrentShoeSize(), CurrentLubricant(), CurrentMode(), CurrentSurface(), TargetLoadText, ActualLoadText, TestSpeedText, StandardReference, Samples.ToList(), points); var path = excelExportService.Export(report); UploadProgress = 100; CurrentStatus = $"Excel 已导出:{path}"; } catch (Exception ex) { Log.Error(ex, "Excel 导出失败:TestNumber={TestNumber}, PointCount={PointCount}", TestNumber, points.Count); CurrentStatus = $"Excel 导出失败:{ex.Message}"; } } public Task StartLiftMotionAsync() => RunDeviceCommand(deviceService.StartLiftAsync(), "垂直架提升中,松开停止"); public Task StopLiftMotionAsync() => RunDeviceCommand(deviceService.StopLiftAsync(), "垂直架提升已停止"); public Task StartLowerMotionAsync() => RunDeviceCommand(deviceService.StartLowerAsync(), "垂直架下降中,松开停止"); public Task StopLowerMotionAsync() => RunDeviceCommand(deviceService.StopLowerAsync(), "垂直架下降已停止"); public Task StartMoveLeftMotionAsync() => RunDeviceCommand(deviceService.StartMoveLeftAsync(), "水平板左移中,松开停止"); public Task StopMoveLeftMotionAsync() => RunDeviceCommand(deviceService.StopMoveLeftAsync(), "水平板左移已停止"); public Task StartMoveRightMotionAsync() => RunDeviceCommand(deviceService.StartMoveRightAsync(), "水平板右移中,松开停止"); public Task StopMoveRightMotionAsync() => RunDeviceCommand(deviceService.StopMoveRightAsync(), "水平板右移已停止"); public Task StopAllMotionAsync() => RunDeviceCommand(deviceService.StopAllMotionAsync(), "全部运动已停止"); [RelayCommand] private void DeleteSelectedSample() { var sample = Samples.FirstOrDefault(x => x.Index == SelectedSampleIndex); if (sample is null) { CurrentStatus = $"未找到序号 {SelectedSampleIndex} 的实验数据"; return; } Samples.Remove(sample); UpdateResultSummary(); CurrentStatus = $"已删除序号 {SelectedSampleIndex} 的实验数据"; } [RelayCommand] private void ShowSettingsDialog() { IsSettingsDialogOpen = true; } [RelayCommand] private void CloseSettingsDialog() { IsSettingsDialogOpen = false; deviceService.UpdateSettings(CurrentSettings()); } [RelayCommand] private void CalibrateNormalPressureZero() { try { var zero = deviceService.CaptureCurrentAdcZero(); NormalPressureZero = zero.NormalPressureZero.ToString(CultureInfo.InvariantCulture); SaveAndApplySettings(); CurrentStatus = "正压力零点已按当前 ADC 原始值采集"; } catch (Exception ex) { Log.Error(ex, "正压力零点采集失败"); CurrentStatus = $"正压力零点采集失败:{ex.Message}"; } } [RelayCommand] private void CalibrateFrictionZero() { try { var zero = deviceService.CaptureCurrentAdcZero(); FrictionZero1 = zero.FrictionZero1.ToString(CultureInfo.InvariantCulture); FrictionZero2 = zero.FrictionZero2.ToString(CultureInfo.InvariantCulture); SaveAndApplySettings(); CurrentStatus = "摩擦零点已按当前 ADC 原始值采集"; } catch (Exception ex) { Log.Error(ex, "摩擦零点采集失败"); CurrentStatus = $"摩擦零点采集失败:{ex.Message}"; } } partial void OnUploadProgressChanged(int value) => OnPropertyChanged(nameof(UploadProgressText)); partial void OnManualSpeedChanged(string value) { SaveAndApplySettings(); WriteNumericSetting(value, deviceService.WriteManualSpeedAsync, "手动速度"); } partial void OnManualDisplacementChanged(string value) { SaveAndApplySettings(); WriteNumericSetting(value, deviceService.WriteManualDisplacementAsync, "手动位移"); } partial void OnTestSpeedChanged(string value) { OnPropertyChanged(nameof(TestSpeedText)); SaveAndApplySettings(); WriteNumericSetting(value, deviceService.WriteTestSpeedAsync, "测试速度"); } partial void OnNormalPressureZeroChanged(string value) => SaveAndApplySettings(); partial void OnNormalPressureCoefficientChanged(string value) => SaveAndApplySettings(); partial void OnFrictionZero1Changed(string value) => SaveAndApplySettings(); partial void OnFrictionCoefficient1Changed(string value) => SaveAndApplySettings(); partial void OnFrictionZero2Changed(string value) => SaveAndApplySettings(); partial void OnFrictionCoefficient2Changed(string value) => SaveAndApplySettings(); partial void OnPlcPortNameChanged(string value) => SaveAndApplySettings(); partial void OnAdcPortNameChanged(string value) => SaveAndApplySettings(); partial void OnBaudRateChanged(int value) => SaveAndApplySettings(); partial void OnSelectedShoeSizeIndexChanged(int value) => UpdateTargetLoad(); partial void OnSelectedModeIndexChanged(int value) { ActiveMode = CurrentMode(); } public void Dispose() { refreshTimer.Stop(); try { deviceService.StopAllMotionAsync().Wait(500); } catch (Exception ex) { Log.Warning(ex, "关闭窗口时停止全部运动失败"); } deviceService.Dispose(); } private void RefreshFromDevice() { var device = deviceService.CurrentSnapshot; DeviceStatus = device.IsConnected ? device.IsTestRunning ? "联机 / 测试中" : device.IsResetting ? "联机 / 复位中" : "联机 / 待机" : "离线"; VerticalPressure = device.VerticalLoadN.ToString("F1", CultureInfo.InvariantCulture); HorizontalForce = device.HorizontalFrictionN.ToString("F1", CultureInfo.InvariantCulture); FrictionCoefficient = device.FrictionCoefficient.ToString("F3", CultureInfo.InvariantCulture); Distance = device.DisplacementMm.ToString("F1", CultureInfo.InvariantCulture); ActualLoadText = $"{VerticalPressure} N"; if (!device.IsConnected && !string.IsNullOrWhiteSpace(device.LastError)) { CurrentStatus = $"设备离线:{device.LastError}"; } var isRecording = device.IsConnected && device.IsTestRunning; if (!wasRunning && isRecording) { BeginRun(); } if (isRecording) { RecordPoint(device); } if (wasRunning && !isRecording) { if (device.IsConnected) { CompleteRun(); } else { AbortRun("设备离线,本次采集已停止,未生成试验结果"); } } wasRunning = isRecording; } private void BeginRun() { currentRun.Clear(); verticalLoadPoints.Clear(); horizontalFrictionPoints.Clear(); frictionCoefficientPoints.Clear(); displacementPoints.Clear(); runStopwatch.Restart(); UploadProgress = 0; CurrentStatus = "测试运行:按标准采集垂直载荷、摩擦力、位移与摩擦系数"; Log.Information("测试开始:TestNumber={TestNumber}, TargetLoad={TargetLoad}, TestSpeed={TestSpeed}", TestNumber, TargetLoadText, TestSpeedText); } private void RecordPoint(SlipDeviceSnapshot device) { var time = runStopwatch.Elapsed.TotalSeconds; if (currentRun.Count > 0 && time - currentRun[^1].TimeSeconds < SampleIntervalSeconds) { return; } var point = new SlipDataPoint( device.Timestamp, time, device.VerticalLoadN, device.HorizontalFrictionN, device.DisplacementMm, device.FrictionCoefficient); 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)); UploadProgress = Math.Min(99, currentRun.Count); } private void CompleteRun() { runStopwatch.Stop(); if (currentRun.Count < 3) { Log.Warning("测试停止但采样点不足:TestNumber={TestNumber}, PointCount={PointCount}", TestNumber, currentRun.Count); CurrentStatus = "测试已停止,但有效采样点不足,未生成结果"; return; } lastCompletedRun = currentRun.ToList(); var peak = FindStaticPeak(currentRun); var dynamicWindow = currentRun .Where(point => point.TimeSeconds >= DynamicWindowStartSeconds && point.TimeSeconds <= DynamicWindowEndSeconds) .ToList(); if (dynamicWindow.Count < MinimumDynamicWindowPointCount) { Log.Warning( "测试停止但动摩擦窗口采样点不足:TestNumber={TestNumber}, PointCount={PointCount}, DynamicWindowPointCount={DynamicWindowPointCount}", TestNumber, currentRun.Count, dynamicWindow.Count); CurrentStatus = "测试已停止,0.3 s~0.6 s 有效采样点不足,未生成结果"; return; } var staticCoefficientValue = CalculateCoefficient(peak.HorizontalFrictionN, peak.VerticalLoadN); var dynamicForce = dynamicWindow.Average(point => point.HorizontalFrictionN); var dynamicLoad = dynamicWindow.Average(point => point.VerticalLoadN); var dynamicCoefficientValue = CalculateCoefficient(dynamicForce, dynamicLoad); var verdict = NeedsRetest(staticCoefficientValue, dynamicCoefficientValue) ? "需重测" : "有效"; var nextIndex = Samples.Count == 0 ? 1 : Samples.Max(sample => sample.Index) + 1; Samples.Insert(0, new TestSample( nextIndex, DateTime.Now.ToString("HH:mm:ss", CultureInfo.InvariantCulture), staticCoefficientValue.ToString("F2", CultureInfo.InvariantCulture), dynamicCoefficientValue.ToString("F2", CultureInfo.InvariantCulture), verdict, staticCoefficientValue, dynamicCoefficientValue)); StaticCoefficient = staticCoefficientValue.ToString("F2", CultureInfo.InvariantCulture); DynamicCoefficient = dynamicCoefficientValue.ToString("F2", CultureInfo.InvariantCulture); UpdateResultSummary(); UploadProgress = 100; CurrentStatus = verdict == "有效" ? "测试完成:已按标准生成静/动摩擦系数" : "测试完成:最近三次结果差异超过 10%,建议重新测试"; Log.Information( "测试完成:TestNumber={TestNumber}, PointCount={PointCount}, StaticCoefficient={StaticCoefficient:F3}, DynamicCoefficient={DynamicCoefficient:F3}, Verdict={Verdict}", TestNumber, currentRun.Count, staticCoefficientValue, dynamicCoefficientValue, verdict); } private void AbortRun(string message) { runStopwatch.Stop(); Log.Warning( "测试采集中止:TestNumber={TestNumber}, PointCount={PointCount}, Message={Message}", TestNumber, currentRun.Count, message); CurrentStatus = message; } private bool NeedsRetest(double staticCoefficientValue, double dynamicCoefficientValue) { var previous = Samples.Take(StandardTrialCount - 1).Reverse().ToList(); if (previous.Count < StandardTrialCount - 1) { return false; } var staticValues = previous.Select(sample => sample.StaticCoefficientValue).Append(staticCoefficientValue).ToArray(); var dynamicValues = previous.Select(sample => sample.DynamicCoefficientValue).Append(dynamicCoefficientValue).ToArray(); return HasSystematicTrendExceedingTenPercent(staticValues) || HasSystematicTrendExceedingTenPercent(dynamicValues); } private static bool HasSystematicTrendExceedingTenPercent(double[] values) { var isIncreasing = values.Zip(values.Skip(1), (left, right) => right > left).All(result => result); var isDecreasing = values.Zip(values.Skip(1), (left, right) => right < left).All(result => result); if (!isIncreasing && !isDecreasing) { return false; } var average = values.Average(); if (Math.Abs(average) < 0.0001) { return false; } return (values.Max() - values.Min()) / Math.Abs(average) > 0.10; } private void UpdateResultSummary() { var latest = Samples.Take(StandardTrialCount).ToList(); if (latest.Count == 0) { ResultSummary = "等待 3 次有效试验"; return; } if (latest.Count < StandardTrialCount) { ResultSummary = $"已完成 {latest.Count}/3 次有效试验"; return; } var staticAverage = latest.Average(sample => sample.StaticCoefficientValue); var dynamicAverage = latest.Average(sample => sample.DynamicCoefficientValue); ResultSummary = $"近 3 次平均 静 {staticAverage:F2} / 动 {dynamicAverage:F2}"; } private static SlipDataPoint FindStaticPeak(IReadOnlyList points) { var preDynamicWindow = points .Where(point => point.TimeSeconds <= DynamicWindowStartSeconds) .ToList(); return (preDynamicWindow.Count > 0 ? preDynamicWindow : points) .MaxBy(point => point.HorizontalFrictionN) ?? points[0]; } private static double CalculateCoefficient(double frictionForce, double verticalLoad) => Math.Abs(verticalLoad) > 0.0001 ? frictionForce / verticalLoad : 0; private async Task RunDeviceCommand(Task command, string successMessage) { try { await command; CurrentStatus = successMessage; } catch (Exception ex) { Log.Error(ex, "设备指令失败:{SuccessMessage}", successMessage); CurrentStatus = $"设备指令失败:{ex.Message}"; } } private void WriteNumericSetting(string value, Func writer, string label) { if (isLoadingDeviceSettings || !TryParseDouble(value, out var numericValue)) { return; } _ = RunDeviceCommand(writer(numericValue), $"{label}已写入 PLC"); } private void SaveAndApplySettings() { SaveDeviceSettings(); deviceService.UpdateSettings(CurrentSettings()); } private async Task LoadPlcParametersAsync() { try { var parameters = await deviceService.ReadDeviceParametersAsync(); isLoadingDeviceSettings = true; ManualSpeed = parameters.ManualSpeed.ToString("F2", CultureInfo.InvariantCulture); ManualDisplacement = parameters.ManualDisplacement.ToString("F2", CultureInfo.InvariantCulture); TestSpeed = parameters.TestSpeed.ToString("F2", CultureInfo.InvariantCulture); isLoadingDeviceSettings = false; SaveAndApplySettings(); } catch (Exception ex) { isLoadingDeviceSettings = false; Log.Warning(ex, "启动时读取 PLC 参数失败,将使用本地保存的设备设置"); } } private void LoadDeviceSettings() { if (!File.Exists(DeviceSettingsPath)) { return; } try { var json = File.ReadAllText(DeviceSettingsPath); var settings = JsonSerializer.Deserialize(json); if (settings is null) { return; } isLoadingDeviceSettings = true; ManualSpeed = settings.ManualSpeed ?? ManualSpeed; ManualDisplacement = settings.ManualDisplacement ?? ManualDisplacement; TestSpeed = settings.TestSpeed ?? TestSpeed; NormalPressureZero = settings.NormalPressureZero ?? NormalPressureZero; NormalPressureCoefficient = settings.NormalPressureCoefficient ?? NormalPressureCoefficient; FrictionZero1 = settings.FrictionZero1 ?? FrictionZero1; FrictionCoefficient1 = settings.FrictionCoefficient1 ?? FrictionCoefficient1; FrictionZero2 = settings.FrictionZero2 ?? FrictionZero2; FrictionCoefficient2 = settings.FrictionCoefficient2 ?? FrictionCoefficient2; PlcPortName = settings.PlcPortName ?? PlcPortName; AdcPortName = settings.AdcPortName ?? AdcPortName; BaudRate = settings.BaudRate > 0 ? settings.BaudRate : BaudRate; } catch (Exception ex) { Log.Warning(ex, "读取设备设置失败:Path={Path}", DeviceSettingsPath); } finally { isLoadingDeviceSettings = false; } } private void SaveDeviceSettings() { if (isLoadingDeviceSettings) { return; } try { Directory.CreateDirectory(Path.GetDirectoryName(DeviceSettingsPath)!); var json = JsonSerializer.Serialize(CurrentSettings(), new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(DeviceSettingsPath, json); } catch (Exception ex) { Log.Warning(ex, "保存设备设置失败:Path={Path}", DeviceSettingsPath); } } private DeviceSettings CurrentSettings() => new( ManualSpeed, ManualDisplacement, TestSpeed, NormalPressureZero, NormalPressureCoefficient, FrictionZero1, FrictionCoefficient1, FrictionZero2, FrictionCoefficient2, PlcPortName, AdcPortName, BaudRate); private void UpdateTargetLoad() { TargetLoadText = SelectedShoeSizeIndex switch { 1 => "350 N", 2 => "160 N", _ => "400 N" }; } private string CurrentShoeSize() => SelectedShoeSizeIndex switch { 1 => "205-250", 2 => "205以下", _ => "250(含)以上" }; private string CurrentLubricant() => SelectedLubricantIndex switch { 1 => "湿态 - 蒸馏水", 2 => "湿态 - 洗涤剂", 3 => "冰霜", _ => "干态" }; private string CurrentMode() => SelectedModeIndex switch { 0 => "后跟测试模式", 1 => "前掌测试模式", _ => "水平测试模式" }; private string CurrentSurface() => SelectedSurfaceIndex switch { 1 => "木地板", 2 => "石材", 3 => "冰霜表面", _ => "瓷砖" }; private static bool TryParseDouble(string value, out double numericValue) => double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out numericValue) || double.TryParse(value, NumberStyles.Float, CultureInfo.CurrentCulture, out numericValue); private static LineSeries CreateLineSeries( string name, ObservableCollection values, string color, int yAxis, double smoothness) => new() { Name = name, Values = values, MiniatureShapeSize = 8, MiniatureStrokeThickness = 2, Stroke = new SolidColorPaint(SKColor.Parse(color)) { StrokeThickness = 3.2f }, Fill = new SolidColorPaint(SKColors.Transparent), GeometryFill = new SolidColorPaint(SKColors.White), GeometryStroke = new SolidColorPaint(SKColor.Parse(color)) { StrokeThickness = 2 }, GeometrySize = 4, LineSmoothness = smoothness, ScalesYAt = yAxis }; private static string DeviceSettingsPath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FootwearSlipResistance", "device-settings.json"); } }