更新20260122

This commit is contained in:
GukSang.Jin
2026-06-12 10:56:30 +08:00
parent 04b401e7b8
commit 424a2d8b2d
2 changed files with 235 additions and 156 deletions

View File

@@ -103,6 +103,7 @@ public sealed class MainWindowViewModel : ObservableObject
private const double MinimumTorqueChangeThreshold = 0.05; private const double MinimumTorqueChangeThreshold = 0.05;
private const string TorqueUnit = "mN.m"; private const string TorqueUnit = "mN.m";
private static readonly TimeSpan RealtimeRefreshInterval = TimeSpan.FromMilliseconds(100); private static readonly TimeSpan RealtimeRefreshInterval = TimeSpan.FromMilliseconds(100);
private static readonly TimeSpan RealtimeDataFreshnessTimeout = TimeSpan.FromSeconds(3);
private static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(5); private static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(5);
private static readonly TimeSpan NoLoadCaptureDuration = TimeSpan.FromSeconds(3); private static readonly TimeSpan NoLoadCaptureDuration = TimeSpan.FromSeconds(3);
private static readonly TimeSpan SpeedTorqueStartConfirmationTimeout = TimeSpan.FromSeconds(5); private static readonly TimeSpan SpeedTorqueStartConfirmationTimeout = TimeSpan.FromSeconds(5);
@@ -220,6 +221,7 @@ public sealed class MainWindowViewModel : ObservableObject
private bool _isReadingParameterConfig; private bool _isReadingParameterConfig;
private bool _hasLoadedParameterConfigFromPlc; private bool _hasLoadedParameterConfigFromPlc;
private bool _lastRealtimeReadFailed; private bool _lastRealtimeReadFailed;
private DateTime _lastSuccessfulRealtimeReadAt = DateTime.MinValue;
private bool _isAutoStoppingDisplacement; private bool _isAutoStoppingDisplacement;
private bool _isAutoStoppingSpeedTorque; private bool _isAutoStoppingSpeedTorque;
private bool _isApplyingParameterConfigToInputs; private bool _isApplyingParameterConfigToInputs;
@@ -752,11 +754,10 @@ public sealed class MainWindowViewModel : ObservableObject
try try
{ {
PlcConnectionConfig config = _parameterConfig.ToPlcConnectionConfig(); PlcConnectionConfig config = _parameterConfig.ToPlcConnectionConfig();
Task<IReadOnlyDictionary<ushort, float>> registerReadTask = IReadOnlyDictionary<ushort, float> values =
_plcRegisterService.ReadFloatValuesAsync(config, RealtimeRegisterAddresses); await _plcRegisterService.ReadFloatValuesAsync(config, RealtimeRegisterAddresses);
Task<IReadOnlyDictionary<ushort, bool>> coilReadTask = IReadOnlyDictionary<ushort, bool> coilValues =
_plcCoilService.ReadCoilValuesAsync(config, RealtimeCoilAddresses); await _plcCoilService.ReadCoilValuesAsync(config, RealtimeCoilAddresses);
IReadOnlyDictionary<ushort, float> values = await registerReadTask;
double dialIndicator = ReadFloatValue(values, DialIndicatorRegister, "千分表显示"); double dialIndicator = ReadFloatValue(values, DialIndicatorRegister, "千分表显示");
double axialForce = ReadFloatValue(values, AxialForceDisplayRegister, "轴向力显示"); double axialForce = ReadFloatValue(values, AxialForceDisplayRegister, "轴向力显示");
@@ -788,12 +789,12 @@ public sealed class MainWindowViewModel : ObservableObject
UpdateSpeedTorqueDisplay(); UpdateSpeedTorqueDisplay();
UpdateNoLoadSpeedDisplay(); UpdateNoLoadSpeedDisplay();
IReadOnlyDictionary<ushort, bool> coilValues = await coilReadTask;
ApplyResetCoilValues(coilValues); ApplyResetCoilValues(coilValues);
ApplyAxialForceModeState(ReadCoilValue(coilValues, AxialForceModeCoil)); ApplyAxialForceModeState(ReadCoilValue(coilValues, AxialForceModeCoil));
bool isVentValveOpen = ReadCoilValue(coilValues, VentValveCoil); bool isVentValveOpen = ReadCoilValue(coilValues, VentValveCoil);
VentValveButtonText = isVentValveOpen ? "关闭气阀" : "通气阀"; VentValveButtonText = isVentValveOpen ? "关闭气阀" : "通气阀";
_lastSuccessfulRealtimeReadAt = DateTime.Now;
CaptureRealtimeSample(dialIndicator, coilValues); CaptureRealtimeSample(dialIndicator, coilValues);
FinalizeNoLoadSpeedRunIfDue(); FinalizeNoLoadSpeedRunIfDue();
QueueSnapshotIfDue(); QueueSnapshotIfDue();
@@ -837,7 +838,7 @@ public sealed class MainWindowViewModel : ObservableObject
{ {
if (!_lastRealtimeReadFailed) if (!_lastRealtimeReadFailed)
{ {
StatusText = $"实时数据读取失败:{OperatorMessageFormatter.FromException(ex)}"; StatusText = $"实时数据读取失败,当前读数为上次有效值{OperatorMessageFormatter.FromException(ex)}";
_lastRealtimeReadFailed = true; _lastRealtimeReadFailed = true;
Log.Warning(ex, "PLC实时数据读取失败后续相同故障将等待恢复后再记录"); Log.Warning(ex, "PLC实时数据读取失败后续相同故障将等待恢复后再记录");
} }
@@ -2433,6 +2434,12 @@ public sealed class MainWindowViewModel : ObservableObject
return; return;
} }
if (!TryGetRealtimeSpeed(out _))
{
StatusText = "实时数据未更新,已阻止空载转速记录。";
return;
}
try try
{ {
await _plcCoilService.WriteCoilAsync( await _plcCoilService.WriteCoilAsync(
@@ -3089,13 +3096,13 @@ public sealed class MainWindowViewModel : ObservableObject
private bool TryGetDialValue(out double value) private bool TryGetDialValue(out double value)
{ {
value = _dialIndicator; value = _dialIndicator;
return true; return HasFreshRealtimeData() && double.IsFinite(value);
} }
private bool TryGetAxialForceValue(out double value) private bool TryGetAxialForceValue(out double value)
{ {
value = _axialForce; value = _axialForce;
return true; return HasFreshRealtimeData() && double.IsFinite(value);
} }
private void UpdateRealtimeSpeedFromInput() private void UpdateRealtimeSpeedFromInput()
@@ -3244,13 +3251,19 @@ public sealed class MainWindowViewModel : ObservableObject
private bool TryGetRealtimeSpeed(out double value) private bool TryGetRealtimeSpeed(out double value)
{ {
value = _realtimeSpeed; value = _realtimeSpeed;
return true; return HasFreshRealtimeData() && double.IsFinite(value);
} }
private bool TryGetRealtimeTorque(out double value) private bool TryGetRealtimeTorque(out double value)
{ {
value = _realtimeTorque; value = _realtimeTorque;
return true; return HasFreshRealtimeData() && double.IsFinite(value);
}
private bool HasFreshRealtimeData()
{
return _lastSuccessfulRealtimeReadAt != DateTime.MinValue
&& DateTime.Now - _lastSuccessfulRealtimeReadAt <= RealtimeDataFreshnessTimeout;
} }
private void ApplyResetCoilValues(IReadOnlyDictionary<ushort, bool> coilValues) private void ApplyResetCoilValues(IReadOnlyDictionary<ushort, bool> coilValues)
@@ -3483,7 +3496,14 @@ public sealed class MainWindowViewModel : ObservableObject
private int GetTorqueSampleLimit() private int GetTorqueSampleLimit()
{ {
return MaxTorqueSampleCount; if (_parameterConfig.TorqueHoldTime <= 0)
{
return MaxTorqueSampleCount;
}
int requiredSamples = (int)Math.Ceiling(
_parameterConfig.TorqueHoldTime / RealtimeRefreshInterval.TotalSeconds) + 2;
return Math.Max(MaxTorqueSampleCount, requiredSamples);
} }
private void CaptureRealtimeSample(double dialIndicator, IReadOnlyDictionary<ushort, bool> coilValues) private void CaptureRealtimeSample(double dialIndicator, IReadOnlyDictionary<ushort, bool> coilValues)

View File

@@ -34,6 +34,7 @@ public interface IPlcRegisterService
public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterService public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterService
{ {
private readonly object _transactionLock = new(); private readonly object _transactionLock = new();
private readonly SemaphoreSlim _transportLock = new(1, 1);
private ushort _transactionId; private ushort _transactionId;
public async Task PulseCoilAsync(PlcConnectionConfig config, ushort coilAddress, CancellationToken cancellationToken = default) public async Task PulseCoilAsync(PlcConnectionConfig config, ushort coilAddress, CancellationToken cancellationToken = default)
@@ -94,10 +95,16 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
} }
IReadOnlyList<RegisterReadGroup> groups = CreateRegisterReadGroups(registerAddresses); IReadOnlyList<RegisterReadGroup> groups = CreateRegisterReadGroups(registerAddresses);
Task<ushort[]>[] readTasks = groups var groupRegisters = new ushort[groups.Count][];
.Select(group => ReadHoldingRegistersAsync(config, group.StartAddress, group.RegisterCount, cancellationToken)) for (int groupIndex = 0; groupIndex < groups.Count; groupIndex++)
.ToArray(); {
ushort[][] groupRegisters = await Task.WhenAll(readTasks); RegisterReadGroup group = groups[groupIndex];
groupRegisters[groupIndex] = await ReadHoldingRegistersAsync(
config,
group.StartAddress,
group.RegisterCount,
cancellationToken);
}
var values = new Dictionary<ushort, float>(); var values = new Dictionary<ushort, float>();
for (int groupIndex = 0; groupIndex < groups.Count; groupIndex++) for (int groupIndex = 0; groupIndex < groups.Count; groupIndex++)
@@ -199,183 +206,235 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
private async Task WriteSingleCoilAsync(PlcConnectionConfig config, ushort coilAddress, bool value, CancellationToken cancellationToken) private async Task WriteSingleCoilAsync(PlcConnectionConfig config, ushort coilAddress, bool value, CancellationToken cancellationToken)
{ {
using var client = new TcpClient(); await _transportLock.WaitAsync(cancellationToken);
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds));
await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token);
await using NetworkStream stream = client.GetStream();
ushort transactionId = NextTransactionId();
byte[] request = BuildWriteSingleCoilRequest(transactionId, config.UnitId, coilAddress, value);
await stream.WriteAsync(request, timeoutCts.Token);
byte[] response = new byte[12];
await ReadExactlyAsync(stream, response, timeoutCts.Token);
if (response[0] != request[0] || response[1] != request[1])
{ {
throw new InvalidOperationException("PLC 响应事务号不匹配。"); using var client = new TcpClient();
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds));
await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token);
await using NetworkStream stream = client.GetStream();
ushort transactionId = NextTransactionId();
byte[] request = BuildWriteSingleCoilRequest(transactionId, config.UnitId, coilAddress, value);
await stream.WriteAsync(request, timeoutCts.Token);
byte[] response = new byte[12];
await ReadExactlyAsync(stream, response, timeoutCts.Token);
ValidateFixedResponseHeader(response, request);
if (response[7] == 0x85)
{
throw new InvalidOperationException($"PLC 写线圈异常,功能码 0x05异常码 0x{response[8]:X2}。");
}
if (response[7] != 0x05)
{
throw new InvalidOperationException($"PLC 响应功能码无效0x{response[7]:X2}。");
}
if (!response.AsSpan(8, 4).SequenceEqual(request.AsSpan(8, 4)))
{
throw new InvalidOperationException("PLC 写线圈响应地址或值不匹配。");
}
} }
finally
if (response[7] == 0x85)
{ {
throw new InvalidOperationException($"PLC 写线圈异常,功能码 0x05异常码 0x{response[8]:X2}。"); _transportLock.Release();
}
if (response[7] != 0x05)
{
throw new InvalidOperationException($"PLC 响应功能码无效0x{response[7]:X2}。");
} }
} }
private async Task<ushort[]> ReadHoldingRegistersAsync(PlcConnectionConfig config, ushort startAddress, ushort numberOfPoints, CancellationToken cancellationToken) private async Task<ushort[]> ReadHoldingRegistersAsync(PlcConnectionConfig config, ushort startAddress, ushort numberOfPoints, CancellationToken cancellationToken)
{ {
using var client = new TcpClient(); await _transportLock.WaitAsync(cancellationToken);
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds));
await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token);
await using NetworkStream stream = client.GetStream();
ushort transactionId = NextTransactionId();
byte[] request = BuildReadHoldingRegistersRequest(transactionId, config.UnitId, startAddress, numberOfPoints);
await stream.WriteAsync(request, timeoutCts.Token);
byte[] header = new byte[7];
await ReadExactlyAsync(stream, header, timeoutCts.Token);
int remainingLength = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2)) - 1;
if (remainingLength <= 0)
{ {
throw new InvalidOperationException("PLC 响应长度无效。"); using var client = new TcpClient();
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds));
await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token);
await using NetworkStream stream = client.GetStream();
ushort transactionId = NextTransactionId();
byte[] request = BuildReadHoldingRegistersRequest(transactionId, config.UnitId, startAddress, numberOfPoints);
await stream.WriteAsync(request, timeoutCts.Token);
byte[] header = new byte[7];
await ReadExactlyAsync(stream, header, timeoutCts.Token);
int remainingLength = ValidateReadResponseHeader(header, request);
byte[] pdu = new byte[remainingLength];
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
if (pdu[0] == 0x83)
{
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
throw new InvalidOperationException($"PLC 读寄存器异常,功能码 0x03异常码 0x{exceptionCode:X2}。");
}
if (pdu[0] != 0x03)
{
throw new InvalidOperationException($"PLC 响应功能码无效0x{pdu[0]:X2}。");
}
int byteCount = pdu[1];
if (byteCount != numberOfPoints * 2 || pdu.Length < 2 + byteCount)
{
throw new InvalidOperationException("PLC 读寄存器响应字节数无效。");
}
ushort[] registers = new ushort[numberOfPoints];
for (int i = 0; i < registers.Length; i++)
{
registers[i] = BinaryPrimitives.ReadUInt16BigEndian(pdu.AsSpan(2 + i * 2, 2));
}
return registers;
} }
finally
byte[] pdu = new byte[remainingLength];
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
if (header[0] != request[0] || header[1] != request[1])
{ {
throw new InvalidOperationException("PLC 响应事务号不匹配。"); _transportLock.Release();
} }
if (pdu[0] == 0x83)
{
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
throw new InvalidOperationException($"PLC 读寄存器异常,功能码 0x03异常码 0x{exceptionCode:X2}。");
}
if (pdu[0] != 0x03)
{
throw new InvalidOperationException($"PLC 响应功能码无效0x{pdu[0]:X2}。");
}
int byteCount = pdu[1];
if (byteCount != numberOfPoints * 2 || pdu.Length < 2 + byteCount)
{
throw new InvalidOperationException("PLC 读寄存器响应字节数无效。");
}
ushort[] registers = new ushort[numberOfPoints];
for (int i = 0; i < registers.Length; i++)
{
registers[i] = BinaryPrimitives.ReadUInt16BigEndian(pdu.AsSpan(2 + i * 2, 2));
}
return registers;
} }
private async Task<bool[]> ReadCoilsAsync(PlcConnectionConfig config, ushort startAddress, ushort numberOfPoints, CancellationToken cancellationToken) private async Task<bool[]> ReadCoilsAsync(PlcConnectionConfig config, ushort startAddress, ushort numberOfPoints, CancellationToken cancellationToken)
{ {
using var client = new TcpClient(); await _transportLock.WaitAsync(cancellationToken);
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); try
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds)); {
using var client = new TcpClient();
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds));
await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token); await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token);
await using NetworkStream stream = client.GetStream(); await using NetworkStream stream = client.GetStream();
ushort transactionId = NextTransactionId(); ushort transactionId = NextTransactionId();
byte[] request = BuildReadCoilsRequest(transactionId, config.UnitId, startAddress, numberOfPoints); byte[] request = BuildReadCoilsRequest(transactionId, config.UnitId, startAddress, numberOfPoints);
await stream.WriteAsync(request, timeoutCts.Token); await stream.WriteAsync(request, timeoutCts.Token);
byte[] header = new byte[7]; byte[] header = new byte[7];
await ReadExactlyAsync(stream, header, timeoutCts.Token); await ReadExactlyAsync(stream, header, timeoutCts.Token);
int remainingLength = ValidateReadResponseHeader(header, request);
byte[] pdu = new byte[remainingLength];
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
if (pdu[0] == 0x81)
{
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
throw new InvalidOperationException($"PLC 读线圈异常,功能码 0x01异常码 0x{exceptionCode:X2}。");
}
if (pdu[0] != 0x01)
{
throw new InvalidOperationException($"PLC 响应功能码无效0x{pdu[0]:X2}。");
}
int expectedByteCount = (numberOfPoints + 7) / 8;
int byteCount = pdu[1];
if (byteCount != expectedByteCount || pdu.Length < 2 + byteCount)
{
throw new InvalidOperationException("PLC 读线圈响应字节数无效。");
}
bool[] coils = new bool[numberOfPoints];
for (int i = 0; i < coils.Length; i++)
{
coils[i] = (pdu[2 + i / 8] & (1 << (i % 8))) != 0;
}
return coils;
}
finally
{
_transportLock.Release();
}
}
private async Task WriteMultipleRegistersAsync(PlcConnectionConfig config, ushort startAddress, ushort[] values, CancellationToken cancellationToken)
{
await _transportLock.WaitAsync(cancellationToken);
try
{
using var client = new TcpClient();
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds));
await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token);
await using NetworkStream stream = client.GetStream();
ushort transactionId = NextTransactionId();
byte[] request = BuildWriteMultipleRegistersRequest(transactionId, config.UnitId, startAddress, values);
await stream.WriteAsync(request, timeoutCts.Token);
byte[] response = new byte[12];
await ReadExactlyAsync(stream, response, timeoutCts.Token);
ValidateFixedResponseHeader(response, request);
if (response[7] == 0x90)
{
throw new InvalidOperationException($"PLC 写寄存器异常,功能码 0x10异常码 0x{response[8]:X2}。");
}
if (response[7] != 0x10)
{
throw new InvalidOperationException($"PLC 响应功能码无效0x{response[7]:X2}。");
}
ushort responseStart = BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(8, 2));
ushort responseCount = BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(10, 2));
if (responseStart != startAddress || responseCount != values.Length)
{
throw new InvalidOperationException("PLC 写寄存器响应地址或数量不匹配。");
}
}
finally
{
_transportLock.Release();
}
}
private static int ValidateReadResponseHeader(byte[] header, byte[] request)
{
ValidateMbapHeader(header, request);
int remainingLength = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2)) - 1; int remainingLength = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2)) - 1;
if (remainingLength <= 0) if (remainingLength <= 0)
{ {
throw new InvalidOperationException("PLC 响应长度无效。"); throw new InvalidOperationException("PLC 响应长度无效。");
} }
byte[] pdu = new byte[remainingLength]; return remainingLength;
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
if (header[0] != request[0] || header[1] != request[1])
{
throw new InvalidOperationException("PLC 响应事务号不匹配。");
}
if (pdu[0] == 0x81)
{
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
throw new InvalidOperationException($"PLC 读线圈异常,功能码 0x01异常码 0x{exceptionCode:X2}。");
}
if (pdu[0] != 0x01)
{
throw new InvalidOperationException($"PLC 响应功能码无效0x{pdu[0]:X2}。");
}
int expectedByteCount = (numberOfPoints + 7) / 8;
int byteCount = pdu[1];
if (byteCount != expectedByteCount || pdu.Length < 2 + byteCount)
{
throw new InvalidOperationException("PLC 读线圈响应字节数无效。");
}
bool[] coils = new bool[numberOfPoints];
for (int i = 0; i < coils.Length; i++)
{
coils[i] = (pdu[2 + i / 8] & (1 << (i % 8))) != 0;
}
return coils;
} }
private async Task WriteMultipleRegistersAsync(PlcConnectionConfig config, ushort startAddress, ushort[] values, CancellationToken cancellationToken) private static void ValidateFixedResponseHeader(byte[] response, byte[] request)
{ {
using var client = new TcpClient(); ValidateMbapHeader(response, request);
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); if (BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(4, 2)) != 6)
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(config.TimeoutMilliseconds)); {
throw new InvalidOperationException("PLC 响应长度无效。");
}
}
await client.ConnectAsync(config.IpAddress, config.Port, timeoutCts.Token); private static void ValidateMbapHeader(ReadOnlySpan<byte> response, ReadOnlySpan<byte> request)
await using NetworkStream stream = client.GetStream(); {
if (response.Length < 7
ushort transactionId = NextTransactionId(); || response[0] != request[0]
byte[] request = BuildWriteMultipleRegistersRequest(transactionId, config.UnitId, startAddress, values); || response[1] != request[1])
await stream.WriteAsync(request, timeoutCts.Token);
byte[] response = new byte[12];
await ReadExactlyAsync(stream, response, timeoutCts.Token);
if (response[0] != request[0] || response[1] != request[1])
{ {
throw new InvalidOperationException("PLC 响应事务号不匹配。"); throw new InvalidOperationException("PLC 响应事务号不匹配。");
} }
if (response[7] == 0x90) if (response[2] != 0 || response[3] != 0)
{ {
throw new InvalidOperationException($"PLC 写寄存器异常,功能码 0x10异常码 0x{response[8]:X2}。"); throw new InvalidOperationException("PLC 响应协议标识无效。");
} }
if (response[7] != 0x10) if (response[6] != request[6])
{ {
throw new InvalidOperationException($"PLC 响应功能码无效0x{response[7]:X2}。"); throw new InvalidOperationException("PLC 响应站号不匹配。");
}
ushort responseStart = BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(8, 2));
ushort responseCount = BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(10, 2));
if (responseStart != startAddress || responseCount != values.Length)
{
throw new InvalidOperationException("PLC 写寄存器响应地址或数量不匹配。");
} }
} }