更新数据接口
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||
<PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.4" />
|
||||
<PackageReference Include="NModbus" Version="3.0.83" />
|
||||
<PackageReference Include="SukiUI" Version="6.0.2" />
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models
|
||||
{
|
||||
public sealed record DeviceSettings(
|
||||
string ManualSpeed,
|
||||
string ManualDisplacement,
|
||||
string TestSpeed,
|
||||
string NormalPressureZero,
|
||||
string NormalPressureCoefficient,
|
||||
string FrictionZero1,
|
||||
string FrictionCoefficient1,
|
||||
string FrictionZero2,
|
||||
string FrictionCoefficient2,
|
||||
string PlcPortName,
|
||||
string AdcPortName,
|
||||
int BaudRate);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models
|
||||
{
|
||||
public sealed record SlipDataPoint(
|
||||
DateTime Timestamp,
|
||||
double TimeSeconds,
|
||||
double VerticalLoadN,
|
||||
double HorizontalFrictionN,
|
||||
double DisplacementMm,
|
||||
double FrictionCoefficient);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models
|
||||
{
|
||||
public sealed record SlipDeviceSnapshot(
|
||||
DateTime Timestamp,
|
||||
double VerticalLoadN,
|
||||
double HorizontalFrictionN,
|
||||
double DisplacementMm,
|
||||
bool IsTestRunning,
|
||||
bool IsResetting,
|
||||
bool IsConnected,
|
||||
string LastError)
|
||||
{
|
||||
public double FrictionCoefficient => Math.Abs(VerticalLoadN) > 0.0001
|
||||
? HorizontalFrictionN / VerticalLoadN
|
||||
: 0;
|
||||
|
||||
public static SlipDeviceSnapshot Offline(string error = "") =>
|
||||
new(DateTime.Now, 0, 0, 0, false, false, false, error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models
|
||||
{
|
||||
public sealed record SlipReportExport(
|
||||
string TestNumber,
|
||||
string OperatorName,
|
||||
string MethodName,
|
||||
string ReportName,
|
||||
string SampleFeature,
|
||||
string ShoeSize,
|
||||
string Lubricant,
|
||||
string TestMode,
|
||||
string Surface,
|
||||
string TargetLoad,
|
||||
string ActualLoad,
|
||||
string TestSpeed,
|
||||
string StandardReference,
|
||||
IReadOnlyList<TestSample> Results,
|
||||
IReadOnlyList<SlipDataPoint> Points);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models
|
||||
{
|
||||
public sealed record TestSample(
|
||||
int Index,
|
||||
string Time,
|
||||
string StaticCoefficient,
|
||||
string DynamicCoefficient,
|
||||
string Verdict,
|
||||
double StaticCoefficientValue,
|
||||
double DynamicCoefficientValue);
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
using DocumentFormat.OpenXml;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using A = DocumentFormat.OpenXml.Drawing;
|
||||
using C = DocumentFormat.OpenXml.Drawing.Charts;
|
||||
using Xdr = DocumentFormat.OpenXml.Drawing.Spreadsheet;
|
||||
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
|
||||
{
|
||||
public sealed class SlipExcelExportService
|
||||
{
|
||||
private const string InfoSheetName = "试验信息";
|
||||
private const string ResultSheetName = "结果汇总";
|
||||
private const string DataSheetName = "实时数据";
|
||||
|
||||
public string Export(SlipReportExport report)
|
||||
{
|
||||
var directory = GetExportDirectory();
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var safeNumber = MakeSafeFileName(string.IsNullOrWhiteSpace(report.TestNumber)
|
||||
? DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)
|
||||
: report.TestNumber);
|
||||
var path = Path.Combine(directory, $"{safeNumber}_防滑性能测试报告.xlsx");
|
||||
|
||||
using var document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook);
|
||||
var workbookPart = document.AddWorkbookPart();
|
||||
workbookPart.Workbook = new Workbook();
|
||||
var sheets = workbookPart.Workbook.AppendChild(new Sheets());
|
||||
|
||||
AddInfoSheet(workbookPart, sheets, 1, report);
|
||||
AddResultSheet(workbookPart, sheets, 2, report.Results);
|
||||
AddDataSheet(workbookPart, sheets, 3, report.Points);
|
||||
|
||||
workbookPart.Workbook.Save();
|
||||
return path;
|
||||
}
|
||||
|
||||
private static string GetExportDirectory()
|
||||
{
|
||||
var legacyDirectory = Path.Combine(@"D:\文件保存", DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture));
|
||||
if (Directory.Exists(@"D:\"))
|
||||
{
|
||||
return legacyDirectory;
|
||||
}
|
||||
|
||||
return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
"FootwearSlipResistance",
|
||||
DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private static void AddInfoSheet(WorkbookPart workbookPart, Sheets sheets, uint sheetId, SlipReportExport report)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
new object?[] { "字段", "内容" },
|
||||
new object?[] { "试验编号", report.TestNumber },
|
||||
new object?[] { "报告名称", report.ReportName },
|
||||
new object?[] { "操作人员", report.OperatorName },
|
||||
new object?[] { "测试方法", report.MethodName },
|
||||
new object?[] { "样品特征", report.SampleFeature },
|
||||
new object?[] { "鞋码区间/目标负荷", $"{report.ShoeSize} / {report.TargetLoad}" },
|
||||
new object?[] { "实际负荷", report.ActualLoad },
|
||||
new object?[] { "润滑介质", report.Lubricant },
|
||||
new object?[] { "测试模式", report.TestMode },
|
||||
new object?[] { "地面材质", report.Surface },
|
||||
new object?[] { "测试速度", report.TestSpeed },
|
||||
new object?[] { "标准依据", report.StandardReference },
|
||||
new object?[] { "导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) }
|
||||
};
|
||||
|
||||
AddWorksheet(workbookPart, sheets, sheetId, InfoSheetName, rows, "A", "B");
|
||||
}
|
||||
|
||||
private static void AddResultSheet(WorkbookPart workbookPart, Sheets sheets, uint sheetId, IReadOnlyList<TestSample> results)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
new object?[] { "序号", "时间", "静摩擦系数", "动摩擦系数", "判定" }
|
||||
};
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
rows.Add(new object?[]
|
||||
{
|
||||
result.Index,
|
||||
result.Time,
|
||||
result.StaticCoefficientValue,
|
||||
result.DynamicCoefficientValue,
|
||||
result.Verdict
|
||||
});
|
||||
}
|
||||
|
||||
AddWorksheet(workbookPart, sheets, sheetId, ResultSheetName, rows, "A", "E");
|
||||
}
|
||||
|
||||
private static void AddDataSheet(WorkbookPart workbookPart, Sheets sheets, uint sheetId, IReadOnlyList<SlipDataPoint> points)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
new object?[] { "时间(s)", "正压力(N)", "摩擦力(N)", "位移量(mm)", "摩擦系数" }
|
||||
};
|
||||
|
||||
foreach (var point in points)
|
||||
{
|
||||
rows.Add(new object?[]
|
||||
{
|
||||
point.TimeSeconds,
|
||||
point.VerticalLoadN,
|
||||
point.HorizontalFrictionN,
|
||||
point.DisplacementMm,
|
||||
point.FrictionCoefficient
|
||||
});
|
||||
}
|
||||
|
||||
var worksheetPart = AddWorksheet(workbookPart, sheets, sheetId, DataSheetName, rows, "A", "E");
|
||||
if (points.Count > 0)
|
||||
{
|
||||
AddChart(worksheetPart, points.Count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static WorksheetPart AddWorksheet(
|
||||
WorkbookPart workbookPart,
|
||||
Sheets sheets,
|
||||
uint sheetId,
|
||||
string sheetName,
|
||||
IReadOnlyList<IReadOnlyList<object?>> rows,
|
||||
string firstColumn,
|
||||
string lastColumn)
|
||||
{
|
||||
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
|
||||
var sheetData = new SheetData();
|
||||
var worksheet = new Worksheet();
|
||||
worksheet.Append(CreateColumns(firstColumn, lastColumn));
|
||||
worksheet.Append(sheetData);
|
||||
worksheetPart.Worksheet = worksheet;
|
||||
|
||||
for (var rowIndex = 0; rowIndex < rows.Count; rowIndex++)
|
||||
{
|
||||
var row = new Row { RowIndex = (uint)(rowIndex + 1) };
|
||||
for (var columnIndex = 0; columnIndex < rows[rowIndex].Count; columnIndex++)
|
||||
{
|
||||
row.Append(CreateCell(columnIndex + 1, rowIndex + 1, rows[rowIndex][columnIndex]));
|
||||
}
|
||||
|
||||
sheetData.Append(row);
|
||||
}
|
||||
|
||||
var relationshipId = workbookPart.GetIdOfPart(worksheetPart);
|
||||
sheets.Append(new Sheet
|
||||
{
|
||||
Id = relationshipId,
|
||||
SheetId = sheetId,
|
||||
Name = sheetName
|
||||
});
|
||||
|
||||
worksheetPart.Worksheet.Save();
|
||||
return worksheetPart;
|
||||
}
|
||||
|
||||
private static Columns CreateColumns(string firstColumn, string lastColumn)
|
||||
{
|
||||
var first = ColumnNameToIndex(firstColumn);
|
||||
var last = ColumnNameToIndex(lastColumn);
|
||||
var columns = new Columns();
|
||||
for (var index = first; index <= last; index++)
|
||||
{
|
||||
columns.Append(new Column
|
||||
{
|
||||
Min = (uint)index,
|
||||
Max = (uint)index,
|
||||
Width = index == 1 ? 15 : 18,
|
||||
CustomWidth = true
|
||||
});
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
private static Cell CreateCell(int columnIndex, int rowIndex, object? value)
|
||||
{
|
||||
var cell = new Cell { CellReference = $"{GetColumnName(columnIndex)}{rowIndex}" };
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
cell.DataType = CellValues.String;
|
||||
cell.CellValue = new CellValue(string.Empty);
|
||||
break;
|
||||
case int intValue:
|
||||
cell.CellValue = new CellValue(intValue);
|
||||
break;
|
||||
case double doubleValue:
|
||||
cell.CellValue = new CellValue(doubleValue.ToString("0.#####", CultureInfo.InvariantCulture));
|
||||
break;
|
||||
default:
|
||||
cell.DataType = CellValues.String;
|
||||
cell.CellValue = new CellValue(Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty);
|
||||
break;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
private static void AddChart(WorksheetPart worksheetPart, int lastRow)
|
||||
{
|
||||
var drawingsPart = worksheetPart.AddNewPart<DrawingsPart>();
|
||||
var drawingRelationshipId = worksheetPart.GetIdOfPart(drawingsPart);
|
||||
worksheetPart.Worksheet.Append(new DocumentFormat.OpenXml.Spreadsheet.Drawing { Id = drawingRelationshipId });
|
||||
|
||||
drawingsPart.WorksheetDrawing = new Xdr.WorksheetDrawing();
|
||||
var chartPart = drawingsPart.AddNewPart<ChartPart>();
|
||||
var chartRelationshipId = drawingsPart.GetIdOfPart(chartPart);
|
||||
chartPart.ChartSpace = CreateChartSpace(lastRow);
|
||||
chartPart.ChartSpace.Save();
|
||||
|
||||
var graphicFrame = new Xdr.GraphicFrame(
|
||||
new Xdr.NonVisualGraphicFrameProperties(
|
||||
new Xdr.NonVisualDrawingProperties { Id = 2U, Name = "防滑性能曲线图" },
|
||||
new Xdr.NonVisualGraphicFrameDrawingProperties()),
|
||||
new Xdr.Transform(
|
||||
new A.Offset { X = 0L, Y = 0L },
|
||||
new A.Extents { Cx = 0L, Cy = 0L }),
|
||||
new A.Graphic(
|
||||
new A.GraphicData(
|
||||
new C.ChartReference { Id = chartRelationshipId })
|
||||
{ Uri = "http://schemas.openxmlformats.org/drawingml/2006/chart" }));
|
||||
|
||||
var anchor = new Xdr.TwoCellAnchor(
|
||||
new Xdr.FromMarker(
|
||||
new Xdr.ColumnId("6"),
|
||||
new Xdr.ColumnOffset("0"),
|
||||
new Xdr.RowId("1"),
|
||||
new Xdr.RowOffset("0")),
|
||||
new Xdr.ToMarker(
|
||||
new Xdr.ColumnId("15"),
|
||||
new Xdr.ColumnOffset("0"),
|
||||
new Xdr.RowId("23"),
|
||||
new Xdr.RowOffset("0")),
|
||||
graphicFrame,
|
||||
new Xdr.ClientData());
|
||||
|
||||
drawingsPart.WorksheetDrawing.Append(anchor);
|
||||
drawingsPart.WorksheetDrawing.Save();
|
||||
worksheetPart.Worksheet.Save();
|
||||
}
|
||||
|
||||
private static C.ChartSpace CreateChartSpace(int lastRow)
|
||||
{
|
||||
const uint xAxisId = 48650112U;
|
||||
const uint yAxisId = 48672768U;
|
||||
|
||||
var scatterChart = new C.ScatterChart(
|
||||
new C.ScatterStyle { Val = C.ScatterStyleValues.LineMarker },
|
||||
CreateSeries(0, "正压力(N)", 2, lastRow),
|
||||
CreateSeries(1, "摩擦力(N)", 3, lastRow),
|
||||
CreateSeries(2, "位移量(mm)", 4, lastRow),
|
||||
CreateSeries(3, "摩擦系数", 5, lastRow),
|
||||
new C.AxisId { Val = xAxisId },
|
||||
new C.AxisId { Val = yAxisId });
|
||||
|
||||
var chart = new C.Chart(
|
||||
CreateTitle("防滑性能实时曲线"),
|
||||
new C.PlotArea(
|
||||
new C.Layout(),
|
||||
scatterChart,
|
||||
CreateValueAxis(xAxisId, yAxisId, C.AxisPositionValues.Bottom, "时间(s)"),
|
||||
CreateValueAxis(yAxisId, xAxisId, C.AxisPositionValues.Left, "载荷 / 摩擦力 / 位移 / 系数")),
|
||||
new C.Legend(
|
||||
new C.LegendPosition { Val = C.LegendPositionValues.Bottom },
|
||||
new C.Layout()),
|
||||
new C.PlotVisibleOnly { Val = true });
|
||||
|
||||
return new C.ChartSpace(
|
||||
new C.EditingLanguage { Val = "zh-CN" },
|
||||
chart);
|
||||
}
|
||||
|
||||
private static C.ScatterChartSeries CreateSeries(uint index, string title, int yColumnIndex, int lastRow)
|
||||
{
|
||||
var sheet = EscapeSheetName(DataSheetName);
|
||||
var xFormula = $"'{sheet}'!$A$2:$A${lastRow}";
|
||||
var yColumn = GetColumnName(yColumnIndex);
|
||||
var yFormula = $"'{sheet}'!${yColumn}$2:${yColumn}${lastRow}";
|
||||
var titleFormula = $"'{sheet}'!${yColumn}$1";
|
||||
|
||||
return new C.ScatterChartSeries(
|
||||
new C.Index { Val = index },
|
||||
new C.Order { Val = index },
|
||||
new C.SeriesText(new C.StringReference(new C.Formula(titleFormula))),
|
||||
new C.XValues(new C.NumberReference(new C.Formula(xFormula))),
|
||||
new C.YValues(new C.NumberReference(new C.Formula(yFormula))),
|
||||
new C.Smooth { Val = false });
|
||||
}
|
||||
|
||||
private static C.ValueAxis CreateValueAxis(uint axisId, uint crossingAxisId, C.AxisPositionValues position, string title) =>
|
||||
new(
|
||||
new C.AxisId { Val = axisId },
|
||||
new C.Scaling(new C.Orientation { Val = C.OrientationValues.MinMax }),
|
||||
new C.AxisPosition { Val = position },
|
||||
CreateTitle(title),
|
||||
new C.NumberingFormat { FormatCode = "0.00", SourceLinked = false },
|
||||
new C.MajorGridlines(),
|
||||
new C.CrossingAxis { Val = crossingAxisId },
|
||||
new C.Crosses { Val = C.CrossesValues.AutoZero },
|
||||
new C.TickLabelPosition { Val = C.TickLabelPositionValues.NextTo });
|
||||
|
||||
private static C.Title CreateTitle(string text) =>
|
||||
new(
|
||||
new C.ChartText(
|
||||
new C.RichText(
|
||||
new A.BodyProperties(),
|
||||
new A.ListStyle(),
|
||||
new A.Paragraph(
|
||||
new A.Run(
|
||||
new A.RunProperties { Language = "zh-CN", FontSize = 1100 },
|
||||
new A.Text(text))))),
|
||||
new C.Layout(),
|
||||
new C.Overlay { Val = false });
|
||||
|
||||
private static string MakeSafeFileName(string value)
|
||||
{
|
||||
foreach (var invalidChar in Path.GetInvalidFileNameChars())
|
||||
{
|
||||
value = value.Replace(invalidChar, '_');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string EscapeSheetName(string value) => value.Replace("'", "''");
|
||||
|
||||
private static string GetColumnName(int columnIndex)
|
||||
{
|
||||
var dividend = columnIndex;
|
||||
var columnName = string.Empty;
|
||||
while (dividend > 0)
|
||||
{
|
||||
var modulo = (dividend - 1) % 26;
|
||||
columnName = Convert.ToChar('A' + modulo) + columnName;
|
||||
dividend = (dividend - modulo) / 26;
|
||||
}
|
||||
|
||||
return columnName;
|
||||
}
|
||||
|
||||
private static int ColumnNameToIndex(string columnName)
|
||||
{
|
||||
var sum = 0;
|
||||
foreach (var character in columnName)
|
||||
{
|
||||
sum *= 26;
|
||||
sum += character - 'A' + 1;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
using Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models;
|
||||
using NModbus;
|
||||
using NModbus.IO;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
|
||||
{
|
||||
public sealed class SlipResistanceDeviceService : IDisposable
|
||||
{
|
||||
private const byte SlaveId = 1;
|
||||
private const ushort PlcDisplacementRegister = 402;
|
||||
private const ushort PlcStateCoilStart = 81;
|
||||
private const ushort StartTestCoil = 80;
|
||||
private const ushort StopTestCoil = 83;
|
||||
private const ushort ResetCoil = 90;
|
||||
private const ushort MoveLeftCoil = 1;
|
||||
private const ushort MoveRightCoil = 2;
|
||||
private const ushort LowerCoil = 4;
|
||||
private const ushort LiftCoil = 5;
|
||||
private const ushort ManualSpeedRegister = 302;
|
||||
private const ushort TestSpeedRegister = 310;
|
||||
private const ushort ManualDisplacementRegister = 320;
|
||||
|
||||
private readonly object sync = new();
|
||||
private readonly ModbusFactory modbusFactory = new();
|
||||
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
private Task? adcTask;
|
||||
private Task? plcTask;
|
||||
private SerialPort? plcPort;
|
||||
private SerialPort? adcPort;
|
||||
private IModbusSerialMaster? plcMaster;
|
||||
private IModbusSerialMaster? adcMaster;
|
||||
private DeviceSettings settings = new("0.00", "0.00", "0.30", "0", "0.00", "0", "0.00", "0", "0.00", "COM7", "COM8", 115200);
|
||||
private SlipDeviceSnapshot snapshot = SlipDeviceSnapshot.Offline();
|
||||
|
||||
private double verticalLoadN;
|
||||
private double horizontalFrictionN;
|
||||
private double displacementMm;
|
||||
private bool isTestRunning;
|
||||
private bool isResetting;
|
||||
private bool isConnected;
|
||||
private string lastError = string.Empty;
|
||||
|
||||
public SlipDeviceSnapshot CurrentSnapshot
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
return snapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Start(DeviceSettings deviceSettings)
|
||||
{
|
||||
settings = deviceSettings;
|
||||
Stop();
|
||||
|
||||
try
|
||||
{
|
||||
plcPort = CreatePort(settings.PlcPortName, settings.BaudRate);
|
||||
adcPort = CreatePort(settings.AdcPortName, settings.BaudRate);
|
||||
plcPort.Open();
|
||||
adcPort.Open();
|
||||
|
||||
plcMaster = modbusFactory.CreateRtuMaster(new SerialPortResource(plcPort));
|
||||
adcMaster = modbusFactory.CreateRtuMaster(new SerialPortResource(adcPort));
|
||||
plcMaster.Transport.ReadTimeout = 2000;
|
||||
adcMaster.Transport.ReadTimeout = 2000;
|
||||
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
var token = cancellationTokenSource.Token;
|
||||
SetConnected(true, string.Empty);
|
||||
adcTask = Task.Run(() => PollAdc(token), token);
|
||||
plcTask = Task.Run(() => PollPlc(token), token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetConnected(false, ex.Message);
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSettings(DeviceSettings deviceSettings)
|
||||
{
|
||||
settings = deviceSettings;
|
||||
}
|
||||
|
||||
public Task PulseStartTestAsync() => PulseCoilAsync(StartTestCoil);
|
||||
|
||||
public Task PulseStopTestAsync() => PulseCoilAsync(StopTestCoil);
|
||||
|
||||
public Task PulseResetAsync() => PulseCoilAsync(ResetCoil);
|
||||
|
||||
public Task ToggleMoveLeftAsync() => ToggleCoilAsync(MoveLeftCoil);
|
||||
|
||||
public Task ToggleMoveRightAsync() => ToggleCoilAsync(MoveRightCoil);
|
||||
|
||||
public async Task LiftAsync()
|
||||
{
|
||||
await WriteCoilAsync(LowerCoil, false);
|
||||
await WriteCoilAsync(LiftCoil, true);
|
||||
}
|
||||
|
||||
public async Task LowerAsync()
|
||||
{
|
||||
await WriteCoilAsync(LiftCoil, false);
|
||||
await WriteCoilAsync(LowerCoil, true);
|
||||
}
|
||||
|
||||
public Task WriteManualSpeedAsync(double value) => WriteFloatRegisterAsync(ManualSpeedRegister, value);
|
||||
|
||||
public Task WriteTestSpeedAsync(double value) => WriteFloatRegisterAsync(TestSpeedRegister, value);
|
||||
|
||||
public Task WriteManualDisplacementAsync(double value) => WriteFloatRegisterAsync(ManualDisplacementRegister, value);
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
|
||||
try
|
||||
{
|
||||
adcTask?.Wait(200);
|
||||
plcTask?.Wait(200);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
cancellationTokenSource?.Dispose();
|
||||
cancellationTokenSource = null;
|
||||
adcTask = null;
|
||||
plcTask = null;
|
||||
|
||||
plcMaster?.Dispose();
|
||||
adcMaster?.Dispose();
|
||||
plcMaster = null;
|
||||
adcMaster = null;
|
||||
|
||||
ClosePort(plcPort);
|
||||
ClosePort(adcPort);
|
||||
plcPort = null;
|
||||
adcPort = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
private static SerialPort CreatePort(string portName, int baudRate) =>
|
||||
new(portName, baudRate, Parity.None, 8, StopBits.One)
|
||||
{
|
||||
ReadTimeout = 2000,
|
||||
WriteTimeout = 2000
|
||||
};
|
||||
|
||||
private static void ClosePort(SerialPort? port)
|
||||
{
|
||||
if (port is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (port.IsOpen)
|
||||
{
|
||||
port.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
port.Dispose();
|
||||
}
|
||||
|
||||
private void PollAdc(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var master = adcMaster;
|
||||
if (master is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var data = master.ReadHoldingRegisters(SlaveId, 0, 8);
|
||||
var pressureRaw = UshortToInt(data[0], data[1]);
|
||||
var friction1Raw = UshortToInt(data[6], data[7]);
|
||||
var friction2Raw = UshortToInt(data[2], data[3]);
|
||||
|
||||
var pressure = ConvertAdc(pressureRaw, settings.NormalPressureZero, settings.NormalPressureCoefficient);
|
||||
|
||||
// Keep the old instrument channel wiring: ADC 6/7 uses zero 1 with coefficient 2,
|
||||
// and ADC 2/3 uses zero 2 with coefficient 1.
|
||||
var friction1 = ConvertAdc(friction1Raw, settings.FrictionZero1, settings.FrictionCoefficient2);
|
||||
var friction2 = ConvertAdc(friction2Raw, settings.FrictionZero2, settings.FrictionCoefficient1);
|
||||
var friction = (friction1 + friction2) * -1.0;
|
||||
|
||||
lock (sync)
|
||||
{
|
||||
verticalLoadN = pressure;
|
||||
horizontalFrictionN = friction;
|
||||
lastError = string.Empty;
|
||||
isConnected = true;
|
||||
RefreshSnapshotLocked();
|
||||
}
|
||||
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetConnected(false, ex.Message);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PollPlc(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var master = plcMaster;
|
||||
if (master is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var displacementWords = master.ReadHoldingRegisters(SlaveId, PlcDisplacementRegister, 2);
|
||||
var coils = master.ReadCoils(SlaveId, PlcStateCoilStart, 10);
|
||||
|
||||
lock (sync)
|
||||
{
|
||||
displacementMm = UshortToFloat(displacementWords[1], displacementWords[0]);
|
||||
isTestRunning = coils.Length > 0 && coils[0];
|
||||
isResetting = coils.Length > 9 && coils[9];
|
||||
lastError = string.Empty;
|
||||
isConnected = true;
|
||||
RefreshSnapshotLocked();
|
||||
}
|
||||
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetConnected(false, ex.Message);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PulseCoilAsync(ushort coil)
|
||||
{
|
||||
await WriteCoilAsync(coil, true);
|
||||
await Task.Delay(80);
|
||||
await WriteCoilAsync(coil, false);
|
||||
}
|
||||
|
||||
private Task ToggleCoilAsync(ushort coil) =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
|
||||
var current = master.ReadCoils(SlaveId, coil, 1)[0];
|
||||
master.WriteSingleCoil(SlaveId, coil, !current);
|
||||
});
|
||||
|
||||
private Task WriteCoilAsync(ushort coil, bool value) =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
|
||||
master.WriteSingleCoil(SlaveId, coil, value);
|
||||
});
|
||||
|
||||
private Task WriteFloatRegisterAsync(ushort register, double value) =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
|
||||
master.WriteMultipleRegisters(SlaveId, register, SplitFloatToUShortArray((float)value));
|
||||
});
|
||||
|
||||
private void SetConnected(bool connected, string error)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
isConnected = connected;
|
||||
lastError = error;
|
||||
RefreshSnapshotLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshSnapshotLocked()
|
||||
{
|
||||
snapshot = new SlipDeviceSnapshot(
|
||||
DateTime.Now,
|
||||
verticalLoadN,
|
||||
horizontalFrictionN,
|
||||
displacementMm,
|
||||
isTestRunning,
|
||||
isResetting,
|
||||
isConnected,
|
||||
lastError);
|
||||
}
|
||||
|
||||
private static double ConvertAdc(int rawValue, string zeroText, string coefficientText)
|
||||
{
|
||||
var zero = ParseDouble(zeroText);
|
||||
var coefficient = ParseDouble(coefficientText);
|
||||
if (Math.Abs(coefficient) < 0.0001)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (rawValue - zero) / coefficient;
|
||||
}
|
||||
|
||||
private static double ParseDouble(string value) =>
|
||||
double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var invariantValue)
|
||||
? invariantValue
|
||||
: double.TryParse(value, NumberStyles.Float, CultureInfo.CurrentCulture, out var localValue)
|
||||
? localValue
|
||||
: 0;
|
||||
|
||||
private static int UshortToInt(ushort first, ushort second)
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
BitConverter.GetBytes(first).CopyTo(bytes, 0);
|
||||
BitConverter.GetBytes(second).CopyTo(bytes, 2);
|
||||
return BitConverter.ToInt32(bytes, 0);
|
||||
}
|
||||
|
||||
private static float UshortToFloat(ushort first, ushort second)
|
||||
{
|
||||
var intSign = first / 32768;
|
||||
var intSignRest = first % 32768;
|
||||
var intExponent = intSignRest / 128;
|
||||
var intExponentRest = intSignRest % 128;
|
||||
var digit = (float)(intExponentRest * 65536 + second) / 8388608;
|
||||
return (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (digit + 1);
|
||||
}
|
||||
|
||||
private static ushort[] SplitFloatToUShortArray(float value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
return
|
||||
[
|
||||
BitConverter.ToUInt16(bytes, 0),
|
||||
BitConverter.ToUInt16(bytes, 2)
|
||||
];
|
||||
}
|
||||
|
||||
private sealed class SerialPortResource(SerialPort serialPort) : IStreamResource
|
||||
{
|
||||
public int InfiniteTimeout => SerialPort.InfiniteTimeout;
|
||||
|
||||
public int ReadTimeout
|
||||
{
|
||||
get => serialPort.ReadTimeout;
|
||||
set => serialPort.ReadTimeout = value;
|
||||
}
|
||||
|
||||
public int WriteTimeout
|
||||
{
|
||||
get => serialPort.WriteTimeout;
|
||||
set => serialPort.WriteTimeout = value;
|
||||
}
|
||||
|
||||
public void DiscardInBuffer() => serialPort.DiscardInBuffer();
|
||||
|
||||
public int Read(byte[] buffer, int offset, int count) => serialPort.Read(buffer, offset, count);
|
||||
|
||||
public void Write(byte[] buffer, int offset, int count) => serialPort.Write(buffer, offset, count);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
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 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
|
||||
public partial class MainWindowViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
private readonly SlipResistanceDeviceService deviceService = new();
|
||||
private readonly SlipExcelExportService excelExportService = new();
|
||||
private readonly DispatcherTimer refreshTimer;
|
||||
private readonly Stopwatch runStopwatch = new();
|
||||
private readonly List<SlipDataPoint> currentRun = [];
|
||||
private readonly ObservableCollection<ObservablePoint> verticalLoadPoints = [];
|
||||
private readonly ObservableCollection<ObservablePoint> horizontalFrictionPoints = [];
|
||||
private readonly ObservableCollection<ObservablePoint> frictionCoefficientPoints = [];
|
||||
private readonly ObservableCollection<ObservablePoint> displacementPoints = [];
|
||||
|
||||
private bool isLoadingDeviceSettings;
|
||||
private bool wasRunning;
|
||||
private List<SlipDataPoint> lastCompletedRun = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string testNumber = $"SLIP-{DateTime.Now:yyyyMMdd-HHmm}";
|
||||
@@ -33,10 +52,10 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
private string sampleFeature = "整鞋样品 / 瓷砖接触面 / 水平测试";
|
||||
|
||||
[ObservableProperty]
|
||||
private string currentStatus = "测试停止,请按“测试”键进行下次试验";
|
||||
private string currentStatus = "设备连接中,等待 PLC 与 ADC 实时数据";
|
||||
|
||||
[ObservableProperty]
|
||||
private int uploadProgress = 68;
|
||||
private int uploadProgress;
|
||||
|
||||
[ObservableProperty]
|
||||
private string manualDistance = "0";
|
||||
@@ -56,50 +75,85 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
[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";
|
||||
|
||||
public string TargetLoadText { get; } = "400 N";
|
||||
public string ActualLoadText { get; } = "400 N";
|
||||
[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.000";
|
||||
|
||||
[ObservableProperty]
|
||||
private string dynamicCoefficient = "0.000";
|
||||
|
||||
[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; } = "30 Hz";
|
||||
public string ActiveMode { get; } = "水平模式";
|
||||
public string DeviceStatus { get; } = "联机 / 待机";
|
||||
public string BatchNumber { get; } = DateTime.Now.ToString("yyyy-MM-dd");
|
||||
public string ResultSummary { get; } = "平均 0.62";
|
||||
public string BatchNumber { get; } = DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
public string UploadProgressText => $"{UploadProgress}%";
|
||||
public string StaticCoefficient { get; } = "0.66";
|
||||
public string VerticalPressure { get; } = "401.6";
|
||||
public string HorizontalForce { get; } = "248.9";
|
||||
public string FrictionCoefficient { get; } = "0.62";
|
||||
public string Distance { get; } = "218.4";
|
||||
public string StandardReference { get; } = "GB/T 3903.6-2024 7.1-8:速度、载荷、三次平均与系数计算";
|
||||
public string StandardReference { get; } = "GB/T 3903.6-2024 5.1.3、7.3.1.4、8.1-8.3:实时采集、静/动摩擦系数、三次平均与重测判定";
|
||||
|
||||
public ObservableCollection<TestSample> Samples { get; } =
|
||||
[
|
||||
new(16, "08:46:35", "0.58", "0.57", "有效"),
|
||||
new(15, "08:46:27", "0.71", "0.61", "有效"),
|
||||
new(14, "08:46:12", "0.66", "0.66", "有效"),
|
||||
new(13, "08:46:04", "0.77", "0.69", "有效"),
|
||||
new(12, "08:45:51", "0.62", "0.62", "有效"),
|
||||
new(11, "08:45:43", "0.74", "0.64", "有效"),
|
||||
new(10, "08:45:28", "0.64", "0.64", "有效"),
|
||||
new(9, "08:45:19", "0.73", "0.66", "有效"),
|
||||
new(8, "08:45:03", "0.57", "0.56", "有效"),
|
||||
new(7, "08:44:49", "0.65", "0.57", "有效"),
|
||||
new(6, "08:44:35", "0.56", "0.54", "有效"),
|
||||
new(5, "08:44:27", "0.63", "0.56", "有效"),
|
||||
new(4, "08:44:09", "0.56", "0.54", "有效"),
|
||||
new(3, "08:44:01", "0.63", "0.56", "有效"),
|
||||
new(2, "08:43:41", "0.55", "0.52", "有效"),
|
||||
new(1, "08:43:33", "0.62", "0.55", "有效"),
|
||||
];
|
||||
public ObservableCollection<TestSample> Samples { get; } = [];
|
||||
|
||||
public DrawMarginFrame ChartFrame { get; } = new()
|
||||
{
|
||||
@@ -109,74 +163,15 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
|
||||
public SolidColorPaint LegendTextPaint { get; } = new(SKColor.Parse("#334155"));
|
||||
|
||||
public ISeries[] Series { get; } =
|
||||
[
|
||||
new LineSeries<ObservablePoint>
|
||||
{
|
||||
Name = "垂直压力(N)",
|
||||
Values = CreatePressureSeries(),
|
||||
MiniatureShapeSize = 8,
|
||||
MiniatureStrokeThickness = 2,
|
||||
Stroke = new SolidColorPaint(SKColor.Parse("#DC2626")) { StrokeThickness = 3.6f },
|
||||
Fill = new SolidColorPaint(new SKColor(220, 38, 38, 18)),
|
||||
GeometryFill = new SolidColorPaint(SKColors.White),
|
||||
GeometryStroke = new SolidColorPaint(SKColor.Parse("#DC2626")) { StrokeThickness = 2 },
|
||||
GeometrySize = 5,
|
||||
LineSmoothness = 0.65,
|
||||
ScalesYAt = 0
|
||||
},
|
||||
new LineSeries<ObservablePoint>
|
||||
{
|
||||
Name = "水平拉力(N)",
|
||||
Values = CreatePullSeries(),
|
||||
MiniatureShapeSize = 8,
|
||||
MiniatureStrokeThickness = 2,
|
||||
Stroke = new SolidColorPaint(SKColor.Parse("#16A34A")) { StrokeThickness = 3.4f },
|
||||
Fill = new SolidColorPaint(new SKColor(22, 163, 74, 16)),
|
||||
GeometryFill = new SolidColorPaint(SKColors.White),
|
||||
GeometryStroke = new SolidColorPaint(SKColor.Parse("#16A34A")) { StrokeThickness = 2 },
|
||||
GeometrySize = 5,
|
||||
LineSmoothness = 0.65,
|
||||
ScalesYAt = 0
|
||||
},
|
||||
new LineSeries<ObservablePoint>
|
||||
{
|
||||
Name = "摩擦系数",
|
||||
Values = CreateFrictionSeries(),
|
||||
MiniatureShapeSize = 8,
|
||||
MiniatureStrokeThickness = 2,
|
||||
Stroke = new SolidColorPaint(SKColor.Parse("#C026D3")) { StrokeThickness = 3.4f },
|
||||
Fill = new SolidColorPaint(new SKColor(192, 38, 211, 16)),
|
||||
GeometryFill = new SolidColorPaint(SKColors.White),
|
||||
GeometryStroke = new SolidColorPaint(SKColor.Parse("#C026D3")) { StrokeThickness = 2 },
|
||||
GeometrySize = 5,
|
||||
LineSmoothness = 0.65,
|
||||
ScalesYAt = 1
|
||||
},
|
||||
new LineSeries<ObservablePoint>
|
||||
{
|
||||
Name = "距离(mm)",
|
||||
Values = CreateDistanceSeries(),
|
||||
MiniatureShapeSize = 8,
|
||||
MiniatureStrokeThickness = 2,
|
||||
Stroke = new SolidColorPaint(SKColor.Parse("#2563EB")) { StrokeThickness = 3.2f },
|
||||
Fill = new SolidColorPaint(new SKColor(37, 99, 235, 14)),
|
||||
GeometryFill = new SolidColorPaint(SKColors.White),
|
||||
GeometryStroke = new SolidColorPaint(SKColor.Parse("#2563EB")) { StrokeThickness = 2 },
|
||||
GeometrySize = 5,
|
||||
LineSmoothness = 0.35,
|
||||
ScalesYAt = 0
|
||||
}
|
||||
];
|
||||
public ISeries[] Series { get; }
|
||||
|
||||
public Axis[] XAxes { get; } =
|
||||
[
|
||||
new Axis
|
||||
{
|
||||
Name = "时间(s)",
|
||||
MinLimit = -0.10,
|
||||
MaxLimit = 0.62,
|
||||
UnitWidth = 0.05,
|
||||
MinLimit = 0,
|
||||
UnitWidth = 0.1,
|
||||
SeparatorsPaint = new SolidColorPaint(SKColor.Parse("#D7E0EA")) { StrokeThickness = 1 },
|
||||
SubseparatorsPaint = new SolidColorPaint(SKColor.Parse("#EEF3F8")) { StrokeThickness = 1 },
|
||||
SubseparatorsCount = 4,
|
||||
@@ -192,10 +187,8 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
[
|
||||
new Axis
|
||||
{
|
||||
Name = "压力 / 拉力 / 距离",
|
||||
Name = "压力 / 摩擦力 / 位移",
|
||||
MinLimit = 0,
|
||||
MaxLimit = 800,
|
||||
UnitWidth = 50,
|
||||
SeparatorsPaint = new SolidColorPaint(SKColor.Parse("#D7E0EA")) { StrokeThickness = 1 },
|
||||
SubseparatorsPaint = new SolidColorPaint(SKColor.Parse("#EEF3F8")) { StrokeThickness = 1 },
|
||||
SubseparatorsCount = 4,
|
||||
@@ -210,7 +203,6 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
Name = "摩擦系数",
|
||||
Position = LiveChartsCore.Measure.AxisPosition.End,
|
||||
MinLimit = 0,
|
||||
MaxLimit = 1.5,
|
||||
UnitWidth = 0.1,
|
||||
LabelsPaint = new SolidColorPaint(SKColor.Parse("#7E22CE")),
|
||||
NamePaint = new SolidColorPaint(SKColor.Parse("#7E22CE")),
|
||||
@@ -223,61 +215,98 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
Series =
|
||||
[
|
||||
CreateLineSeries("垂直压力(N)", verticalLoadPoints, "#DC2626", 0, 0.65),
|
||||
CreateLineSeries("水平摩擦力(N)", horizontalFrictionPoints, "#16A34A", 0, 0.65),
|
||||
CreateLineSeries("摩擦系数", frictionCoefficientPoints, "#C026D3", 1, 0.65),
|
||||
CreateLineSeries("位移(mm)", displacementPoints, "#2563EB", 0, 0.35)
|
||||
];
|
||||
|
||||
LoadDeviceSettings();
|
||||
UpdateTargetLoad();
|
||||
deviceService.Start(CurrentSettings());
|
||||
|
||||
refreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(33) };
|
||||
refreshTimer.Tick += (_, _) => RefreshFromDevice();
|
||||
refreshTimer.Start();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Clear()
|
||||
private async Task Clear()
|
||||
{
|
||||
UploadProgress = 0;
|
||||
CurrentStatus = "传感器已清零,垂直压力与水平拉力基线重置完成";
|
||||
await RunDeviceCommand(deviceService.PulseResetAsync(), "已发送复位指令 M90");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Preload()
|
||||
{
|
||||
UploadProgress = 34;
|
||||
CurrentStatus = "预压中:0.2s 内施加目标垂直载荷,等待滑动动作";
|
||||
CurrentStatus = "请在 0.2 s 内施加目标垂直载荷,载荷达到后启动滑动测试";
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void StartTest()
|
||||
private async Task StartTest()
|
||||
{
|
||||
UploadProgress = 86;
|
||||
CurrentStatus = "测试运行:正在采集峰值静摩擦力与 0.3s-0.6s 动摩擦力";
|
||||
BeginRun();
|
||||
await RunDeviceCommand(deviceService.PulseStartTestAsync(), "已发送测试启动指令 M80,等待 M81 运行状态");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void StopTest()
|
||||
private async Task StopTest()
|
||||
{
|
||||
UploadProgress = 68;
|
||||
CurrentStatus = "测试停止,请按“测试”键进行下次试验";
|
||||
await RunDeviceCommand(deviceService.PulseStopTestAsync(), "已发送测试停止指令 M83");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ExportReport()
|
||||
{
|
||||
UploadProgress = 100;
|
||||
CurrentStatus = "报告数据已整理:三次平均、静/动摩擦系数与试验条件可导出";
|
||||
var points = currentRun.Count > 0 ? currentRun.ToList() : lastCompletedRun;
|
||||
if (points.Count == 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
CurrentStatus = $"Excel 导出失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void LiftFast() => CurrentStatus = "垂直架快速上升";
|
||||
private async Task Lift() => await RunDeviceCommand(deviceService.LiftAsync(), "垂直架提升指令已发送 M5");
|
||||
|
||||
[RelayCommand]
|
||||
private void Lift() => CurrentStatus = "垂直架提升";
|
||||
private async Task Lower() => await RunDeviceCommand(deviceService.LowerAsync(), "垂直架下降指令已发送 M4");
|
||||
|
||||
[RelayCommand]
|
||||
private void Lower() => CurrentStatus = "垂直架下降";
|
||||
private async Task MoveLeft() => await RunDeviceCommand(deviceService.ToggleMoveLeftAsync(), "水平板左移状态已切换 M1");
|
||||
|
||||
[RelayCommand]
|
||||
private void LowerFast() => CurrentStatus = "垂直架快速下降";
|
||||
|
||||
[RelayCommand]
|
||||
private void MoveLeft() => CurrentStatus = "水平板向左移动";
|
||||
|
||||
[RelayCommand]
|
||||
private void MoveRight() => CurrentStatus = "水平板向右移动";
|
||||
private async Task MoveRight() => await RunDeviceCommand(deviceService.ToggleMoveRightAsync(), "水平板右移状态已切换 M2");
|
||||
|
||||
[RelayCommand]
|
||||
private void DeleteSelectedSample()
|
||||
@@ -290,6 +319,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
}
|
||||
|
||||
Samples.Remove(sample);
|
||||
UpdateResultSummary();
|
||||
CurrentStatus = $"已删除序号 {SelectedSampleIndex} 的实验数据";
|
||||
}
|
||||
|
||||
@@ -303,28 +333,263 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
private void CloseSettingsDialog()
|
||||
{
|
||||
IsSettingsDialogOpen = false;
|
||||
deviceService.UpdateSettings(CurrentSettings());
|
||||
}
|
||||
|
||||
partial void OnUploadProgressChanged(int value)
|
||||
partial void OnUploadProgressChanged(int value) => OnPropertyChanged(nameof(UploadProgressText));
|
||||
|
||||
partial void OnManualSpeedChanged(string value)
|
||||
{
|
||||
OnPropertyChanged(nameof(UploadProgressText));
|
||||
SaveAndApplySettings();
|
||||
WriteNumericSetting(value, deviceService.WriteManualSpeedAsync, "手动速度");
|
||||
}
|
||||
|
||||
partial void OnManualSpeedChanged(string value) => SaveDeviceSettings();
|
||||
|
||||
partial void OnManualDisplacementChanged(string value) => SaveDeviceSettings();
|
||||
partial void OnManualDisplacementChanged(string value)
|
||||
{
|
||||
SaveAndApplySettings();
|
||||
WriteNumericSetting(value, deviceService.WriteManualDisplacementAsync, "手动位移");
|
||||
}
|
||||
|
||||
partial void OnTestSpeedChanged(string value)
|
||||
{
|
||||
OnPropertyChanged(nameof(TestSpeedText));
|
||||
SaveDeviceSettings();
|
||||
SaveAndApplySettings();
|
||||
WriteNumericSetting(value, deviceService.WriteTestSpeedAsync, "测试速度");
|
||||
}
|
||||
|
||||
partial void OnNormalPressureCoefficientChanged(string value) => SaveDeviceSettings();
|
||||
partial void OnNormalPressureZeroChanged(string value) => SaveAndApplySettings();
|
||||
|
||||
partial void OnFrictionCoefficient1Changed(string value) => SaveDeviceSettings();
|
||||
partial void OnNormalPressureCoefficientChanged(string value) => SaveAndApplySettings();
|
||||
|
||||
partial void OnFrictionCoefficient2Changed(string value) => SaveDeviceSettings();
|
||||
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();
|
||||
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}";
|
||||
}
|
||||
|
||||
if (!wasRunning && device.IsTestRunning)
|
||||
{
|
||||
BeginRun();
|
||||
}
|
||||
|
||||
if (device.IsTestRunning)
|
||||
{
|
||||
RecordPoint(device);
|
||||
}
|
||||
|
||||
if (wasRunning && !device.IsTestRunning)
|
||||
{
|
||||
CompleteRun();
|
||||
}
|
||||
|
||||
wasRunning = device.IsTestRunning;
|
||||
}
|
||||
|
||||
private void BeginRun()
|
||||
{
|
||||
currentRun.Clear();
|
||||
verticalLoadPoints.Clear();
|
||||
horizontalFrictionPoints.Clear();
|
||||
frictionCoefficientPoints.Clear();
|
||||
displacementPoints.Clear();
|
||||
runStopwatch.Restart();
|
||||
UploadProgress = 0;
|
||||
CurrentStatus = "测试运行:按标准采集垂直载荷、摩擦力、位移与摩擦系数";
|
||||
}
|
||||
|
||||
private void RecordPoint(SlipDeviceSnapshot device)
|
||||
{
|
||||
var time = runStopwatch.Elapsed.TotalSeconds;
|
||||
if (currentRun.Count > 0 && time - currentRun[^1].TimeSeconds < 1.0 / 30.0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
CurrentStatus = "测试已停止,但有效采样点不足,未生成结果";
|
||||
return;
|
||||
}
|
||||
|
||||
lastCompletedRun = currentRun.ToList();
|
||||
var peak = FindStaticPeak(currentRun);
|
||||
var dynamicWindow = currentRun
|
||||
.Where(point => point.TimeSeconds >= 0.3 && point.TimeSeconds <= 0.6)
|
||||
.ToList();
|
||||
if (dynamicWindow.Count == 0)
|
||||
{
|
||||
dynamicWindow = currentRun.ToList();
|
||||
}
|
||||
|
||||
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("F3", CultureInfo.InvariantCulture),
|
||||
dynamicCoefficientValue.ToString("F3", CultureInfo.InvariantCulture),
|
||||
verdict,
|
||||
staticCoefficientValue,
|
||||
dynamicCoefficientValue));
|
||||
|
||||
StaticCoefficient = staticCoefficientValue.ToString("F3", CultureInfo.InvariantCulture);
|
||||
DynamicCoefficient = dynamicCoefficientValue.ToString("F3", CultureInfo.InvariantCulture);
|
||||
UpdateResultSummary();
|
||||
UploadProgress = 100;
|
||||
CurrentStatus = verdict == "有效"
|
||||
? "测试完成:已按标准生成静/动摩擦系数"
|
||||
: "测试完成:最近三次结果差异超过 10%,建议重新测试";
|
||||
}
|
||||
|
||||
private bool NeedsRetest(double staticCoefficientValue, double dynamicCoefficientValue)
|
||||
{
|
||||
var latest = Samples.Take(2).ToList();
|
||||
if (latest.Count < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var staticValues = latest.Select(sample => sample.StaticCoefficientValue).Append(staticCoefficientValue).ToArray();
|
||||
var dynamicValues = latest.Select(sample => sample.DynamicCoefficientValue).Append(dynamicCoefficientValue).ToArray();
|
||||
return ExceedsTenPercent(staticValues) || ExceedsTenPercent(dynamicValues);
|
||||
}
|
||||
|
||||
private static bool ExceedsTenPercent(double[] values)
|
||||
{
|
||||
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(3).ToList();
|
||||
if (latest.Count == 0)
|
||||
{
|
||||
ResultSummary = "等待 3 次有效试验";
|
||||
return;
|
||||
}
|
||||
|
||||
var staticAverage = latest.Average(sample => sample.StaticCoefficientValue);
|
||||
var dynamicAverage = latest.Average(sample => sample.DynamicCoefficientValue);
|
||||
ResultSummary = $"近 {latest.Count} 次平均 静 {staticAverage:F3} / 动 {dynamicAverage:F3}";
|
||||
}
|
||||
|
||||
private static SlipDataPoint FindStaticPeak(IReadOnlyList<SlipDataPoint> points)
|
||||
{
|
||||
for (var index = 1; index < points.Count - 1; index++)
|
||||
{
|
||||
var previous = points[index - 1].HorizontalFrictionN;
|
||||
var current = points[index].HorizontalFrictionN;
|
||||
var next = points[index + 1].HorizontalFrictionN;
|
||||
if (current >= previous && current >= next && current > 0)
|
||||
{
|
||||
return points[index];
|
||||
}
|
||||
}
|
||||
|
||||
return 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)
|
||||
{
|
||||
CurrentStatus = $"设备指令失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteNumericSetting(string value, Func<double, Task> writer, string label)
|
||||
{
|
||||
if (isLoadingDeviceSettings || !TryParseDouble(value, out var numericValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = RunDeviceCommand(writer(numericValue), $"{label}已写入 PLC");
|
||||
}
|
||||
|
||||
private void SaveAndApplySettings()
|
||||
{
|
||||
SaveDeviceSettings();
|
||||
deviceService.UpdateSettings(CurrentSettings());
|
||||
}
|
||||
|
||||
private void LoadDeviceSettings()
|
||||
{
|
||||
@@ -346,9 +611,15 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
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
|
||||
{
|
||||
@@ -369,14 +640,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(DeviceSettingsPath)!);
|
||||
var settings = new DeviceSettings(
|
||||
ManualSpeed,
|
||||
ManualDisplacement,
|
||||
TestSpeed,
|
||||
NormalPressureCoefficient,
|
||||
FrictionCoefficient1,
|
||||
FrictionCoefficient2);
|
||||
var json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
|
||||
var json = JsonSerializer.Serialize(CurrentSettings(), new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(DeviceSettingsPath, json);
|
||||
}
|
||||
catch
|
||||
@@ -384,54 +648,90 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
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<ObservablePoint> CreateLineSeries(
|
||||
string name,
|
||||
ObservableCollection<ObservablePoint> 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");
|
||||
|
||||
private static ObservablePoint[] CreatePressureSeries() =>
|
||||
[
|
||||
new(-0.10, 36), new(-0.08, 42), new(-0.06, 50), new(-0.04, 90),
|
||||
new(-0.02, 400), new(0.00, 405), new(0.05, 407), new(0.10, 409),
|
||||
new(0.20, 407), new(0.30, 412), new(0.40, 421), new(0.50, 426),
|
||||
new(0.62, 430)
|
||||
];
|
||||
|
||||
private static ObservablePoint[] CreatePullSeries() =>
|
||||
[
|
||||
new(-0.10, 0), new(-0.05, 0), new(0.00, 160), new(0.04, 226),
|
||||
new(0.08, 232), new(0.16, 236), new(0.26, 241), new(0.36, 248),
|
||||
new(0.46, 255), new(0.56, 260), new(0.62, 266)
|
||||
];
|
||||
|
||||
private static ObservablePoint[] CreateFrictionSeries() =>
|
||||
[
|
||||
new(-0.10, 0), new(-0.02, 0), new(0.00, 0.47), new(0.04, 0.55),
|
||||
new(0.08, 0.57), new(0.16, 0.56), new(0.26, 0.58), new(0.36, 0.59),
|
||||
new(0.46, 0.60), new(0.56, 0.61), new(0.62, 0.62)
|
||||
];
|
||||
|
||||
private static ObservablePoint[] CreateDistanceSeries() =>
|
||||
[
|
||||
new(-0.10, 0), new(0.00, 0), new(0.06, 20), new(0.12, 42),
|
||||
new(0.20, 72), new(0.30, 108), new(0.40, 145), new(0.50, 178),
|
||||
new(0.62, 218)
|
||||
];
|
||||
}
|
||||
|
||||
public sealed record TestSample(
|
||||
int Index,
|
||||
string Time,
|
||||
string StaticCoefficient,
|
||||
string DynamicCoefficient,
|
||||
string Verdict);
|
||||
|
||||
internal sealed record DeviceSettings(
|
||||
string ManualSpeed,
|
||||
string ManualDisplacement,
|
||||
string TestSpeed,
|
||||
string NormalPressureCoefficient,
|
||||
string FrictionCoefficient1,
|
||||
string FrictionCoefficient2);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<suki:SukiWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModels"
|
||||
xmlns:model="using:Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
|
||||
xmlns:suki="using:SukiUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -197,13 +198,13 @@
|
||||
<TextBox Grid.Column="3" Text="{Binding OperatorName}" Watermark="请输入"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Text="鞋码区间" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" SelectedIndex="0">
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" SelectedIndex="{Binding SelectedShoeSizeIndex}">
|
||||
<ComboBoxItem Content="250(含)以上 - 400N"/>
|
||||
<ComboBoxItem Content="205-250 - 350N"/>
|
||||
<ComboBoxItem Content="205以下 - 160N"/>
|
||||
</ComboBox>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="润滑介质" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="3" SelectedIndex="0">
|
||||
<ComboBox Grid.Row="1" Grid.Column="3" SelectedIndex="{Binding SelectedLubricantIndex}">
|
||||
<ComboBoxItem Content="干态"/>
|
||||
<ComboBoxItem Content="湿态 - 蒸馏水"/>
|
||||
<ComboBoxItem Content="湿态 - 洗涤剂"/>
|
||||
@@ -211,7 +212,7 @@
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="2" Text="测试模式" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" SelectedIndex="2">
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" SelectedIndex="{Binding SelectedModeIndex}">
|
||||
<ComboBoxItem Content="后跟测试模式"/>
|
||||
<ComboBoxItem Content="前掌测试模式"/>
|
||||
<ComboBoxItem Content="水平测试模式"/>
|
||||
@@ -222,11 +223,11 @@
|
||||
|
||||
<Grid Grid.Column="1" RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="Auto,*" RowSpacing="10" ColumnSpacing="10">
|
||||
<TextBlock Text="报告名称" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding ReportName}" Watermark="例: 白羊座防滑性能报告"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding ReportName}" Watermark="例: 防滑性能报告"/>
|
||||
<TextBlock Grid.Row="1" Text="样品特征" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SampleFeature}" Watermark="鞋底材料、花纹、尺码"/>
|
||||
<TextBlock Grid.Row="2" Text="地面材质" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" SelectedIndex="0">
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" SelectedIndex="{Binding SelectedSurfaceIndex}">
|
||||
<ComboBoxItem Content="瓷砖"/>
|
||||
<ComboBoxItem Content="木地板"/>
|
||||
<ComboBoxItem Content="石材"/>
|
||||
@@ -280,7 +281,7 @@
|
||||
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{Binding Samples}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TestSample">
|
||||
<DataTemplate x:DataType="model:TestSample">
|
||||
<Border BorderBrush="{StaticResource LineBrush}" BorderThickness="0,0,0,1" Padding="0,6">
|
||||
<Grid ColumnDefinitions="50,98,72,72">
|
||||
<TextBlock Text="{Binding Index}" HorizontalAlignment="Center" FontSize="15" Foreground="#334155"/>
|
||||
@@ -327,7 +328,7 @@
|
||||
<Button Content="复位" Classes="action" Command="{Binding ClearCommand}"/>
|
||||
<Button Grid.Column="1" Content="测试" Classes="success action" Command="{Binding StartTestCommand}"/>
|
||||
<Button Grid.Row="1" Content="停止" Classes="danger action" Command="{Binding StopTestCommand}"/>
|
||||
<Button Grid.Row="1" Grid.Column="1" Content="生成报告" Classes="primary action" Command="{Binding ExportReportCommand}"/>
|
||||
<Button Grid.Row="1" Grid.Column="1" Content="导出Excel" Classes="primary action" Command="{Binding ExportReportCommand}"/>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="2" Spacing="8">
|
||||
@@ -346,6 +347,34 @@
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="4"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
ClipToBounds="True">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="标准数据" FontWeight="SemiBold"/>
|
||||
<Border Classes="metric">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="*,Auto" RowSpacing="7">
|
||||
<TextBlock Text="设备状态" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding DeviceStatus}" FontWeight="SemiBold"/>
|
||||
<TextBlock Grid.Row="1" Text="正压力(N)" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding VerticalPressure}" FontWeight="SemiBold"/>
|
||||
<TextBlock Grid.Row="2" Text="摩擦力(N)" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding HorizontalForce}" FontWeight="SemiBold"/>
|
||||
<TextBlock Grid.Row="3" Text="位移(mm)" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Distance}" FontWeight="SemiBold"/>
|
||||
<TextBlock Grid.Row="4" Text="实时系数" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding FrictionCoefficient}" FontWeight="SemiBold"/>
|
||||
<TextBlock Grid.Row="5" Text="静摩擦系数" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBlock Grid.Row="5" Grid.Column="1" Text="{Binding StaticCoefficient}" FontWeight="SemiBold" Foreground="#5B21B6"/>
|
||||
<TextBlock Grid.Row="6" Text="动摩擦系数" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBlock Grid.Row="6" Grid.Column="1" Text="{Binding DynamicCoefficient}" FontWeight="SemiBold" Foreground="#047857"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding ResultSummary}" TextWrapping="Wrap" Classes="caption"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -353,7 +382,7 @@
|
||||
|
||||
<Grid IsVisible="{Binding IsSettingsDialogOpen}"
|
||||
Background="#800F172A">
|
||||
<Border Width="680"
|
||||
<Border Width="760"
|
||||
Background="{StaticResource PanelBrush}"
|
||||
BorderBrush="{StaticResource LineBrush}"
|
||||
BorderThickness="1"
|
||||
@@ -374,7 +403,7 @@
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
RowDefinitions="Auto,Auto,Auto,8,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,8,Auto,Auto,Auto"
|
||||
ColumnDefinitions="Auto,150,Auto,24,Auto,150"
|
||||
RowSpacing="10"
|
||||
ColumnSpacing="12">
|
||||
@@ -390,16 +419,34 @@
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Classes="setting-value" Text="{Binding TestSpeed, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="2" Text="m/s" Classes="setting-unit" VerticalAlignment="Center"/>
|
||||
|
||||
<Border Grid.Row="3" Grid.ColumnSpan="6" Height="1" Background="{StaticResource LineBrush}" Margin="0,4"/>
|
||||
<Label Grid.Row="3" Content="PLC串口" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" Classes="setting-value" Text="{Binding PlcPortName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="4" Content="正压力系数" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="4" Grid.Column="1" Classes="setting-value" Text="{Binding NormalPressureCoefficient, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Label Grid.Row="3" Grid.Column="4" Content="ADC串口" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="5" Classes="setting-value" Text="{Binding AdcPortName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="5" Content="摩擦1系数" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Classes="setting-value" Text="{Binding FrictionCoefficient1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Label Grid.Row="4" Content="波特率" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="4" Grid.Column="1" Classes="setting-value" Text="{Binding BaudRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="5" Grid.Column="4" Content="摩擦2系数" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="5" Classes="setting-value" Text="{Binding FrictionCoefficient2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Border Grid.Row="5" Grid.ColumnSpan="6" Height="1" Background="{StaticResource LineBrush}" Margin="0,4"/>
|
||||
|
||||
<Label Grid.Row="6" Content="正压力零点" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="6" Grid.Column="1" Classes="setting-value" Text="{Binding NormalPressureZero, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="6" Grid.Column="4" Content="正压力系数" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="6" Grid.Column="5" Classes="setting-value" Text="{Binding NormalPressureCoefficient, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="7" Content="摩擦1零点" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" Classes="setting-value" Text="{Binding FrictionZero1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="7" Grid.Column="4" Content="摩擦1系数" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="5" Classes="setting-value" Text="{Binding FrictionCoefficient1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="8" Content="摩擦2零点" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="8" Grid.Column="1" Classes="setting-value" Text="{Binding FrictionZero2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<Label Grid.Row="8" Grid.Column="4" Content="摩擦2系数" Classes="setting-label" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="8" Grid.Column="5" Classes="setting-value" Text="{Binding FrictionCoefficient2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using SukiUI.Controls;
|
||||
using System;
|
||||
using Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModels;
|
||||
|
||||
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Views
|
||||
{
|
||||
@@ -7,6 +9,15 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Views
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Closed += OnClosed;
|
||||
}
|
||||
|
||||
private void OnClosed(object? sender, EventArgs e)
|
||||
{
|
||||
if (DataContext is MainWindowViewModel viewModel)
|
||||
{
|
||||
viewModel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user