更新20260122
This commit is contained in:
@@ -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)
|
||||||
@@ -3482,10 +3495,17 @@ public sealed class MainWindowViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int GetTorqueSampleLimit()
|
private int GetTorqueSampleLimit()
|
||||||
|
{
|
||||||
|
if (_parameterConfig.TorqueHoldTime <= 0)
|
||||||
{
|
{
|
||||||
return MaxTorqueSampleCount;
|
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)
|
||||||
{
|
{
|
||||||
var sample = new RealtimeSamplePayload
|
var sample = new RealtimeSamplePayload
|
||||||
|
|||||||
@@ -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++)
|
||||||
@@ -198,6 +205,9 @@ 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)
|
||||||
|
{
|
||||||
|
await _transportLock.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
using var client = new TcpClient();
|
using var client = new TcpClient();
|
||||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
@@ -212,11 +222,7 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
|
|
||||||
byte[] response = new byte[12];
|
byte[] response = new byte[12];
|
||||||
await ReadExactlyAsync(stream, response, timeoutCts.Token);
|
await ReadExactlyAsync(stream, response, timeoutCts.Token);
|
||||||
|
ValidateFixedResponseHeader(response, request);
|
||||||
if (response[0] != request[0] || response[1] != request[1])
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("PLC 响应事务号不匹配。");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response[7] == 0x85)
|
if (response[7] == 0x85)
|
||||||
{
|
{
|
||||||
@@ -227,9 +233,22 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException($"PLC 响应功能码无效:0x{response[7]:X2}。");
|
throw new InvalidOperationException($"PLC 响应功能码无效:0x{response[7]:X2}。");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!response.AsSpan(8, 4).SequenceEqual(request.AsSpan(8, 4)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("PLC 写线圈响应地址或值不匹配。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_transportLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
{
|
||||||
|
await _transportLock.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
using var client = new TcpClient();
|
using var client = new TcpClient();
|
||||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
@@ -244,20 +263,11 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
|
|
||||||
byte[] header = new byte[7];
|
byte[] header = new byte[7];
|
||||||
await ReadExactlyAsync(stream, header, timeoutCts.Token);
|
await ReadExactlyAsync(stream, header, timeoutCts.Token);
|
||||||
int remainingLength = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2)) - 1;
|
int remainingLength = ValidateReadResponseHeader(header, request);
|
||||||
if (remainingLength <= 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("PLC 响应长度无效。");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] pdu = new byte[remainingLength];
|
byte[] pdu = new byte[remainingLength];
|
||||||
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
|
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
|
||||||
|
|
||||||
if (header[0] != request[0] || header[1] != request[1])
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("PLC 响应事务号不匹配。");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pdu[0] == 0x83)
|
if (pdu[0] == 0x83)
|
||||||
{
|
{
|
||||||
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
|
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
|
||||||
@@ -283,8 +293,16 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
|
|
||||||
return registers;
|
return registers;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_transportLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
{
|
||||||
|
await _transportLock.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
using var client = new TcpClient();
|
using var client = new TcpClient();
|
||||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
@@ -299,20 +317,11 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
|
|
||||||
byte[] header = new byte[7];
|
byte[] header = new byte[7];
|
||||||
await ReadExactlyAsync(stream, header, timeoutCts.Token);
|
await ReadExactlyAsync(stream, header, timeoutCts.Token);
|
||||||
int remainingLength = BinaryPrimitives.ReadUInt16BigEndian(header.AsSpan(4, 2)) - 1;
|
int remainingLength = ValidateReadResponseHeader(header, request);
|
||||||
if (remainingLength <= 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("PLC 响应长度无效。");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] pdu = new byte[remainingLength];
|
byte[] pdu = new byte[remainingLength];
|
||||||
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
|
await ReadExactlyAsync(stream, pdu, timeoutCts.Token);
|
||||||
|
|
||||||
if (header[0] != request[0] || header[1] != request[1])
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("PLC 响应事务号不匹配。");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pdu[0] == 0x81)
|
if (pdu[0] == 0x81)
|
||||||
{
|
{
|
||||||
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
|
byte exceptionCode = pdu.Length > 1 ? pdu[1] : (byte)0;
|
||||||
@@ -339,8 +348,16 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
|
|
||||||
return coils;
|
return coils;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_transportLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task WriteMultipleRegistersAsync(PlcConnectionConfig config, ushort startAddress, ushort[] values, CancellationToken cancellationToken)
|
private async Task WriteMultipleRegistersAsync(PlcConnectionConfig config, ushort startAddress, ushort[] values, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _transportLock.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
using var client = new TcpClient();
|
using var client = new TcpClient();
|
||||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
@@ -355,11 +372,7 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
|
|
||||||
byte[] response = new byte[12];
|
byte[] response = new byte[12];
|
||||||
await ReadExactlyAsync(stream, response, timeoutCts.Token);
|
await ReadExactlyAsync(stream, response, timeoutCts.Token);
|
||||||
|
ValidateFixedResponseHeader(response, request);
|
||||||
if (response[0] != request[0] || response[1] != request[1])
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("PLC 响应事务号不匹配。");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response[7] == 0x90)
|
if (response[7] == 0x90)
|
||||||
{
|
{
|
||||||
@@ -378,6 +391,52 @@ public sealed class ModbusTcpPlcCoilService : IPlcCoilService, IPlcRegisterServi
|
|||||||
throw new InvalidOperationException("PLC 写寄存器响应地址或数量不匹配。");
|
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;
|
||||||
|
if (remainingLength <= 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("PLC 响应长度无效。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return remainingLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateFixedResponseHeader(byte[] response, byte[] request)
|
||||||
|
{
|
||||||
|
ValidateMbapHeader(response, request);
|
||||||
|
if (BinaryPrimitives.ReadUInt16BigEndian(response.AsSpan(4, 2)) != 6)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("PLC 响应长度无效。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateMbapHeader(ReadOnlySpan<byte> response, ReadOnlySpan<byte> request)
|
||||||
|
{
|
||||||
|
if (response.Length < 7
|
||||||
|
|| response[0] != request[0]
|
||||||
|
|| response[1] != request[1])
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("PLC 响应事务号不匹配。");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response[2] != 0 || response[3] != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("PLC 响应协议标识无效。");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response[6] != request[6])
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("PLC 响应站号不匹配。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<RegisterReadGroup> CreateRegisterReadGroups(IReadOnlyCollection<ushort> registerAddresses)
|
private static IReadOnlyList<RegisterReadGroup> CreateRegisterReadGroups(IReadOnlyCollection<ushort> registerAddresses)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user