更新数据接口
This commit is contained in:
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user