Files
FootwearTest-20260602/Footwear Test methodsfor wholeshoe Slipresistanceperformance/Services/SlipResistanceDeviceService.cs

725 lines
28 KiB
C#
Raw Normal View History

2026-06-02 18:14:01 +08:00
using Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models;
using NModbus;
using NModbus.IO;
2026-06-02 18:45:14 +08:00
using Serilog;
2026-06-02 18:14:01 +08:00
using System;
2026-06-03 15:35:55 +08:00
using System.Collections.Generic;
2026-06-02 18:14:01 +08:00
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();
2026-06-02 19:16:50 +08:00
private readonly object plcIoLock = new();
private readonly object adcIoLock = new();
2026-06-02 18:14:01 +08:00
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;
2026-06-03 15:35:55 +08:00
private DeviceSettings settings = new("0.00", "0.00", "0.30", "0", "0.00", "0", "0.00", "0", "0.00", "COM3", "COM4", 115200);
2026-06-02 18:14:01 +08:00
private SlipDeviceSnapshot snapshot = SlipDeviceSnapshot.Offline();
2026-06-02 18:45:14 +08:00
private DateTime lastAdcErrorLoggedAt = DateTime.MinValue;
private DateTime lastPlcErrorLoggedAt = DateTime.MinValue;
2026-06-03 15:35:55 +08:00
private DateTime lastAdcDiagnosticLoggedAt = DateTime.MinValue;
private DateTime lastAdcCalibrationWarningLoggedAt = DateTime.MinValue;
private string lastAdcCalibrationWarning = string.Empty;
2026-06-02 18:14:01 +08:00
private double verticalLoadN;
private double horizontalFrictionN;
private double displacementMm;
2026-06-02 18:45:14 +08:00
private int pressureRawValue;
private int frictionRawValue1;
private int frictionRawValue2;
private bool hasAdcRawValues;
2026-06-02 18:14:01 +08:00
private bool isTestRunning;
private bool isResetting;
2026-06-02 19:16:50 +08:00
private bool isAdcConnected;
private bool isPlcConnected;
private string adcLastError = string.Empty;
private string plcLastError = string.Empty;
2026-06-02 18:14:01 +08:00
public SlipDeviceSnapshot CurrentSnapshot
{
get
{
lock (sync)
{
return snapshot;
}
}
}
public void Start(DeviceSettings deviceSettings)
{
settings = deviceSettings;
2026-06-03 15:35:55 +08:00
LogAdcCalibrationWarningIfNeeded();
2026-06-02 18:14:01 +08:00
Stop();
try
{
2026-06-02 18:45:14 +08:00
Log.Information(
"启动防滑测试设备连接PLC={PlcPort}, ADC={AdcPort}, BaudRate={BaudRate}, SlaveId={SlaveId}",
settings.PlcPortName,
settings.AdcPortName,
settings.BaudRate,
SlaveId);
2026-06-02 18:14:01 +08:00
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;
2026-06-02 19:16:50 +08:00
SetAllDisconnected(string.Empty);
2026-06-02 18:14:01 +08:00
adcTask = Task.Run(() => PollAdc(token), token);
plcTask = Task.Run(() => PollPlc(token), token);
2026-06-02 18:45:14 +08:00
Log.Information("防滑测试设备连接成功ADC/PLC 轮询已启动");
2026-06-02 18:14:01 +08:00
}
catch (Exception ex)
{
2026-06-02 18:45:14 +08:00
Log.Error(
ex,
"防滑测试设备连接失败PLC={PlcPort}, ADC={AdcPort}, BaudRate={BaudRate}",
settings.PlcPortName,
settings.AdcPortName,
settings.BaudRate);
2026-06-02 19:16:50 +08:00
SetAllDisconnected(ex.Message);
2026-06-02 18:14:01 +08:00
Stop();
}
}
public void UpdateSettings(DeviceSettings deviceSettings)
{
settings = deviceSettings;
2026-06-03 15:35:55 +08:00
LogAdcCalibrationWarningIfNeeded();
2026-06-02 18:45:14 +08:00
Log.Debug(
"设备设置已更新PLC={PlcPort}, ADC={AdcPort}, BaudRate={BaudRate}, TestSpeed={TestSpeed}, ManualSpeed={ManualSpeed}, ManualDisplacement={ManualDisplacement}",
settings.PlcPortName,
settings.AdcPortName,
settings.BaudRate,
settings.TestSpeed,
settings.ManualSpeed,
settings.ManualDisplacement);
2026-06-02 18:14:01 +08:00
}
public Task PulseStartTestAsync() => PulseCoilAsync(StartTestCoil);
public Task PulseStopTestAsync() => PulseCoilAsync(StopTestCoil);
public Task PulseResetAsync() => PulseCoilAsync(ResetCoil);
2026-06-03 15:35:55 +08:00
public async Task ApplyOldLiftAsync()
2026-06-02 18:14:01 +08:00
{
await WriteCoilAsync(LowerCoil, false);
await WriteCoilAsync(LiftCoil, true);
2026-06-03 15:35:55 +08:00
Log.Information("提升按钮按老代码执行M{LowerCoil}=0, M{LiftCoil}=1", LowerCoil, LiftCoil);
2026-06-02 18:14:01 +08:00
}
2026-06-03 15:35:55 +08:00
public async Task ApplyOldLowerAsync()
2026-06-02 18:14:01 +08:00
{
await WriteCoilAsync(LiftCoil, false);
await WriteCoilAsync(LowerCoil, true);
2026-06-03 15:35:55 +08:00
Log.Information("下降按钮按老代码执行M{LiftCoil}=0, M{LowerCoil}=1", LiftCoil, LowerCoil);
2026-06-02 18:53:31 +08:00
}
2026-06-03 15:35:55 +08:00
public Task ToggleOldMoveLeftAsync() => ToggleCoilAsync(MoveLeftCoil);
2026-06-02 18:53:31 +08:00
2026-06-03 15:35:55 +08:00
public Task ToggleOldMoveRightAsync() => ToggleCoilAsync(MoveRightCoil);
2026-06-02 18:53:31 +08:00
public async Task StopAllMotionAsync()
{
await WriteCoilAsync(LiftCoil, false);
await WriteCoilAsync(LowerCoil, false);
await WriteCoilAsync(MoveLeftCoil, false);
await WriteCoilAsync(MoveRightCoil, false);
Log.Information("全部运动停止M{LiftCoil}=0, M{LowerCoil}=0, M{MoveLeftCoil}=0, M{MoveRightCoil}=0", LiftCoil, LowerCoil, MoveLeftCoil, MoveRightCoil);
2026-06-02 18:14:01 +08:00
}
public Task WriteManualSpeedAsync(double value) => WriteFloatRegisterAsync(ManualSpeedRegister, value);
public Task WriteTestSpeedAsync(double value) => WriteFloatRegisterAsync(TestSpeedRegister, value);
public Task WriteManualDisplacementAsync(double value) => WriteFloatRegisterAsync(ManualDisplacementRegister, value);
2026-06-02 18:45:14 +08:00
public Task<PlcDeviceParameters> ReadDeviceParametersAsync() =>
Task.Run(() =>
{
2026-06-02 19:16:50 +08:00
ushort[] manualSpeedWords;
ushort[] manualDisplacementWords;
ushort[] testSpeedWords;
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
manualSpeedWords = master.ReadHoldingRegisters(SlaveId, ManualSpeedRegister, 2);
manualDisplacementWords = master.ReadHoldingRegisters(SlaveId, ManualDisplacementRegister, 2);
testSpeedWords = master.ReadHoldingRegisters(SlaveId, TestSpeedRegister, 2);
}
2026-06-02 18:45:14 +08:00
var parameters = new PlcDeviceParameters(
UshortToFloat(manualSpeedWords[1], manualSpeedWords[0]),
UshortToFloat(manualDisplacementWords[1], manualDisplacementWords[0]),
UshortToFloat(testSpeedWords[1], testSpeedWords[0]));
Log.Information(
"读取 PLC 参数完成D{ManualSpeedRegister}={ManualSpeed:F3}, D{ManualDisplacementRegister}={ManualDisplacement:F3}, D{TestSpeedRegister}={TestSpeed:F3}",
ManualSpeedRegister,
parameters.ManualSpeed,
ManualDisplacementRegister,
parameters.ManualDisplacement,
TestSpeedRegister,
parameters.TestSpeed);
return parameters;
});
public AdcZeroCalibration CaptureCurrentAdcZero()
{
lock (sync)
{
if (!hasAdcRawValues)
{
throw new InvalidOperationException("ADC 尚未读取到有效原始数据");
}
Log.Information(
"采集 ADC 零点NormalPressureZero={NormalPressureZero}, FrictionZero1={FrictionZero1}, FrictionZero2={FrictionZero2}",
pressureRawValue,
frictionRawValue1,
frictionRawValue2);
return new AdcZeroCalibration(pressureRawValue, frictionRawValue1, frictionRawValue2);
}
}
2026-06-02 18:14:01 +08:00
public void Stop()
{
2026-06-02 18:45:14 +08:00
Log.Information("停止防滑测试设备连接与轮询");
2026-06-02 18:14:01 +08:00
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;
2026-06-02 19:16:50 +08:00
SetAllDisconnected(string.Empty);
2026-06-02 18:14:01 +08:00
}
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
{
2026-06-02 19:16:50 +08:00
ushort[] data;
lock (adcIoLock)
2026-06-02 18:14:01 +08:00
{
2026-06-02 19:16:50 +08:00
var master = adcMaster;
if (master is null)
{
return;
}
data = master.ReadHoldingRegisters(SlaveId, 0, 8);
2026-06-02 18:14:01 +08:00
}
var pressureRaw = UshortToInt(data[0], data[1]);
2026-06-05 15:40:48 +08:00
var friction1Raw = UshortToInt(data[2], data[3]);
var friction2Raw = UshortToInt(data[6], data[7]);
2026-06-02 18:14:01 +08:00
2026-06-03 15:35:55 +08:00
var conversion = ConvertAdcReadings(pressureRaw, friction1Raw, friction2Raw);
LogAdcDiagnostic(pressureRaw, friction1Raw, friction2Raw, conversion);
2026-06-02 18:14:01 +08:00
lock (sync)
{
2026-06-02 18:45:14 +08:00
pressureRawValue = pressureRaw;
frictionRawValue1 = friction1Raw;
frictionRawValue2 = friction2Raw;
hasAdcRawValues = true;
2026-06-03 15:35:55 +08:00
if (conversion.IsValid)
{
verticalLoadN = conversion.Pressure;
horizontalFrictionN = conversion.Friction;
adcLastError = string.Empty;
isAdcConnected = true;
}
else
{
verticalLoadN = 0;
horizontalFrictionN = 0;
adcLastError = conversion.Error;
isAdcConnected = false;
}
2026-06-02 18:14:01 +08:00
RefreshSnapshotLocked();
}
2026-06-03 15:35:55 +08:00
Thread.Sleep(conversion.IsValid ? 10 : 100);
2026-06-02 18:14:01 +08:00
}
catch (Exception ex)
{
2026-06-02 18:45:14 +08:00
LogPollingError("ADC", ex, ref lastAdcErrorLoggedAt);
2026-06-02 19:16:50 +08:00
SetAdcConnected(false, ex.Message);
2026-06-02 18:14:01 +08:00
Thread.Sleep(250);
}
}
}
private void PollPlc(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
2026-06-02 19:16:50 +08:00
ushort[] displacementWords;
bool[] coils;
lock (plcIoLock)
2026-06-02 18:14:01 +08:00
{
2026-06-02 19:16:50 +08:00
var master = plcMaster;
if (master is null)
{
return;
}
displacementWords = master.ReadHoldingRegisters(SlaveId, PlcDisplacementRegister, 2);
coils = master.ReadCoils(SlaveId, PlcStateCoilStart, 10);
2026-06-02 18:14:01 +08:00
}
lock (sync)
{
displacementMm = UshortToFloat(displacementWords[1], displacementWords[0]);
isTestRunning = coils.Length > 0 && coils[0];
isResetting = coils.Length > 9 && coils[9];
2026-06-02 19:16:50 +08:00
plcLastError = string.Empty;
isPlcConnected = true;
2026-06-02 18:14:01 +08:00
RefreshSnapshotLocked();
}
Thread.Sleep(10);
}
catch (Exception ex)
{
2026-06-02 18:45:14 +08:00
LogPollingError("PLC", ex, ref lastPlcErrorLoggedAt);
2026-06-02 19:16:50 +08:00
SetPlcConnected(false, ex.Message);
2026-06-02 18:14:01 +08:00
Thread.Sleep(250);
}
}
}
2026-06-03 15:35:55 +08:00
private Task PulseCoilAsync(ushort coil) =>
Task.Run(() =>
{
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
Log.Information("按老代码逻辑发送 PLC 脉冲线圈M{Coil}=true -> false", coil);
master.WriteSingleCoil(SlaveId, coil, true);
Log.Debug("写入 PLC 线圈M{Coil}=true", coil);
master.WriteSingleCoil(SlaveId, coil, false);
Log.Debug("写入 PLC 线圈M{Coil}=false", coil);
}
});
2026-06-02 18:14:01 +08:00
private Task ToggleCoilAsync(ushort coil) =>
Task.Run(() =>
{
2026-06-02 19:16:50 +08:00
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
var current = master.ReadCoils(SlaveId, coil, 1)[0];
master.WriteSingleCoil(SlaveId, coil, !current);
Log.Information("切换 PLC 线圈M{Coil}, Before={Before}, After={After}", coil, current, !current);
}
2026-06-02 18:14:01 +08:00
});
private Task WriteCoilAsync(ushort coil, bool value) =>
Task.Run(() =>
{
2026-06-02 19:16:50 +08:00
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
master.WriteSingleCoil(SlaveId, coil, value);
Log.Debug("写入 PLC 线圈M{Coil}={Value}", coil, value);
}
2026-06-02 18:14:01 +08:00
});
private Task WriteFloatRegisterAsync(ushort register, double value) =>
Task.Run(() =>
{
2026-06-02 19:16:50 +08:00
lock (plcIoLock)
{
var master = plcMaster ?? throw new InvalidOperationException("PLC 未连接");
master.WriteMultipleRegisters(SlaveId, register, SplitFloatToUShortArray((float)value));
Log.Information("写入 PLC 浮点寄存器D{Register}={Value:F3}", register, value);
}
2026-06-02 18:14:01 +08:00
});
2026-06-02 18:45:14 +08:00
private void LogPollingError(string source, Exception exception, ref DateTime lastLoggedAt)
{
var now = DateTime.UtcNow;
if (now - lastLoggedAt < TimeSpan.FromSeconds(5))
{
return;
}
lastLoggedAt = now;
Log.Error(
exception,
"{Source} 轮询失败PLC={PlcPort}, ADC={AdcPort}, BaudRate={BaudRate}",
source,
settings.PlcPortName,
settings.AdcPortName,
settings.BaudRate);
}
2026-06-02 19:16:50 +08:00
private void SetAdcConnected(bool connected, string error)
{
lock (sync)
{
isAdcConnected = connected;
adcLastError = error;
RefreshSnapshotLocked();
}
}
private void SetPlcConnected(bool connected, string error)
2026-06-02 18:14:01 +08:00
{
lock (sync)
{
2026-06-02 19:16:50 +08:00
isPlcConnected = connected;
plcLastError = error;
if (!connected)
{
isTestRunning = false;
isResetting = false;
}
RefreshSnapshotLocked();
}
}
private void SetAllDisconnected(string error)
{
lock (sync)
{
isAdcConnected = false;
isPlcConnected = false;
adcLastError = error;
plcLastError = error;
isTestRunning = false;
isResetting = false;
2026-06-02 18:14:01 +08:00
RefreshSnapshotLocked();
}
}
private void RefreshSnapshotLocked()
{
2026-06-02 19:16:50 +08:00
var connected = isAdcConnected && isPlcConnected;
var error = string.Empty;
if (!isAdcConnected && !string.IsNullOrWhiteSpace(adcLastError))
{
error = "ADC: " + adcLastError;
}
if (!isPlcConnected && !string.IsNullOrWhiteSpace(plcLastError))
{
error = string.IsNullOrWhiteSpace(error)
? "PLC: " + plcLastError
: error + "; PLC: " + plcLastError;
}
2026-06-02 18:14:01 +08:00
snapshot = new SlipDeviceSnapshot(
DateTime.Now,
verticalLoadN,
horizontalFrictionN,
displacementMm,
isTestRunning,
isResetting,
2026-06-02 19:16:50 +08:00
connected,
error);
2026-06-02 18:14:01 +08:00
}
2026-06-03 15:35:55 +08:00
private AdcConversionResult ConvertAdcReadings(int pressureRaw, int friction1Raw, int friction2Raw)
2026-06-02 18:14:01 +08:00
{
2026-06-03 15:35:55 +08:00
if (!ValidateAdcSettings(out var pressureZero, out var pressureCoefficient, out var frictionZero1, out var frictionCoefficient1, out var frictionZero2, out var frictionCoefficient2, out var error))
2026-06-02 18:14:01 +08:00
{
2026-06-03 15:35:55 +08:00
return AdcConversionResult.Invalid(error);
2026-06-02 18:14:01 +08:00
}
2026-06-03 15:35:55 +08:00
var pressure = ConvertAdc(pressureRaw, pressureZero, pressureCoefficient);
2026-06-05 15:40:48 +08:00
// Keep each settings row paired with the same ADC channel used by the legacy zero-capture button.
var friction1 = ConvertAdc(friction1Raw, frictionZero1, frictionCoefficient1);
var friction2 = ConvertAdc(friction2Raw, frictionZero2, frictionCoefficient2);
2026-06-03 15:35:55 +08:00
var friction = (friction1 + friction2) * -1.0;
return AdcConversionResult.Valid(pressure, friction1, friction2, friction);
2026-06-02 18:14:01 +08:00
}
2026-06-03 15:35:55 +08:00
private bool ValidateAdcSettings(
out double pressureZero,
out double pressureCoefficient,
out double frictionZero1,
out double frictionCoefficient1,
out double frictionZero2,
out double frictionCoefficient2,
out string error)
{
var invalid = new List<string>();
TryParseSetting(settings.NormalPressureZero, "正压力零点", false, invalid, out pressureZero);
TryParseSetting(settings.NormalPressureCoefficient, "正压力校准系数", true, invalid, out pressureCoefficient);
TryParseSetting(settings.FrictionZero1, "摩擦1零点", false, invalid, out frictionZero1);
TryParseSetting(settings.FrictionCoefficient1, "摩擦1校准系数", true, invalid, out frictionCoefficient1);
TryParseSetting(settings.FrictionZero2, "摩擦2零点", false, invalid, out frictionZero2);
TryParseSetting(settings.FrictionCoefficient2, "摩擦2校准系数", true, invalid, out frictionCoefficient2);
if (invalid.Count == 0)
{
error = string.Empty;
return true;
}
error = "ADC 校准参数无效:" + string.Join("、", invalid) + ";请填写旧机实际标定值后再测试";
return false;
}
private void LogAdcCalibrationWarningIfNeeded()
{
if (!ValidateAdcSettings(out _, out _, out _, out _, out _, out _, out var error))
{
var now = DateTime.UtcNow;
if (error == lastAdcCalibrationWarning && now - lastAdcCalibrationWarningLoggedAt < TimeSpan.FromSeconds(5))
{
return;
}
lastAdcCalibrationWarning = error;
lastAdcCalibrationWarningLoggedAt = now;
Log.Warning(
"{Error}. CurrentSettings: NormalPressureZero={NormalPressureZero}, NormalPressureCoefficient={NormalPressureCoefficient}, FrictionZero1={FrictionZero1}, FrictionCoefficient1={FrictionCoefficient1}, FrictionZero2={FrictionZero2}, FrictionCoefficient2={FrictionCoefficient2}",
error,
settings.NormalPressureZero,
settings.NormalPressureCoefficient,
settings.FrictionZero1,
settings.FrictionCoefficient1,
settings.FrictionZero2,
settings.FrictionCoefficient2);
return;
}
lastAdcCalibrationWarning = string.Empty;
}
private void LogAdcDiagnostic(int pressureRaw, int friction1Raw, int friction2Raw, AdcConversionResult conversion)
{
var now = DateTime.UtcNow;
if (now - lastAdcDiagnosticLoggedAt < TimeSpan.FromSeconds(conversion.IsValid ? 1 : 5))
{
return;
}
lastAdcDiagnosticLoggedAt = now;
if (conversion.IsValid)
{
Log.Debug(
"ADC 采样RawPressure={RawPressure}, RawFriction1={RawFriction1}, RawFriction2={RawFriction2}, Pressure={Pressure:F3} N, Friction1={Friction1:F3} N, Friction2={Friction2:F3} N, Friction={Friction:F3} N, Coefficients=[P:{PressureCoefficient}, F1:{FrictionCoefficient1}, F2:{FrictionCoefficient2}], Zeros=[P:{PressureZero}, F1:{FrictionZero1}, F2:{FrictionZero2}]",
pressureRaw,
friction1Raw,
friction2Raw,
conversion.Pressure,
conversion.Friction1,
conversion.Friction2,
conversion.Friction,
settings.NormalPressureCoefficient,
settings.FrictionCoefficient1,
settings.FrictionCoefficient2,
settings.NormalPressureZero,
settings.FrictionZero1,
settings.FrictionZero2);
}
else
{
Log.Warning(
"ADC 采样无效:{Error}. RawPressure={RawPressure}, RawFriction1={RawFriction1}, RawFriction2={RawFriction2}, Coefficients=[P:{PressureCoefficient}, F1:{FrictionCoefficient1}, F2:{FrictionCoefficient2}], Zeros=[P:{PressureZero}, F1:{FrictionZero1}, F2:{FrictionZero2}]",
conversion.Error,
pressureRaw,
friction1Raw,
friction2Raw,
settings.NormalPressureCoefficient,
settings.FrictionCoefficient1,
settings.FrictionCoefficient2,
settings.NormalPressureZero,
settings.FrictionZero1,
settings.FrictionZero2);
}
}
private static double ConvertAdc(int rawValue, double zero, double coefficient) =>
(rawValue - zero) / coefficient;
private static void TryParseSetting(string value, string label, bool requireNonZero, List<string> invalid, out double numericValue)
{
if (!TryParseDouble(value, out numericValue))
{
invalid.Add($"{label}=\"{value}\"");
return;
}
if (requireNonZero && Math.Abs(numericValue) < 0.0001)
{
invalid.Add($"{label}=0");
}
}
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);
2026-06-02 18:14:01 +08:00
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()
{
}
}
2026-06-03 15:35:55 +08:00
private readonly record struct AdcConversionResult(
bool IsValid,
double Pressure,
double Friction1,
double Friction2,
double Friction,
string Error)
{
public static AdcConversionResult Valid(double pressure, double friction1, double friction2, double friction) =>
new(true, pressure, friction1, friction2, friction, string.Empty);
public static AdcConversionResult Invalid(string error) =>
new(false, 0, 0, 0, 0, error);
}
2026-06-02 18:14:01 +08:00
}
}