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
}
}