diff --git a/NModbus/IO/EmptyTransport.cs b/NModbus/IO/EmptyTransport.cs index bdd0ef9..1b1d4f9 100644 --- a/NModbus/IO/EmptyTransport.cs +++ b/NModbus/IO/EmptyTransport.cs @@ -15,7 +15,7 @@ public override byte[] ReadRequest() throw new NotImplementedException(); } - public override IModbusMessage ReadResponse() + public override IModbusMessage ReadResponse(IModbusMessage request) { throw new NotImplementedException(); } diff --git a/NModbus/IO/ModbusAsciiTransport.cs b/NModbus/IO/ModbusAsciiTransport.cs index af238f0..3ebc8bf 100644 --- a/NModbus/IO/ModbusAsciiTransport.cs +++ b/NModbus/IO/ModbusAsciiTransport.cs @@ -45,7 +45,7 @@ public override byte[] ReadRequest() return ReadRequestResponse(); } - public override IModbusMessage ReadResponse() + public override IModbusMessage ReadResponse(IModbusMessage request) { return CreateResponse(ReadRequestResponse()); } diff --git a/NModbus/IO/ModbusIpTransport.cs b/NModbus/IO/ModbusIpTransport.cs index 023d076..a316b85 100644 --- a/NModbus/IO/ModbusIpTransport.cs +++ b/NModbus/IO/ModbusIpTransport.cs @@ -137,7 +137,7 @@ public override byte[] ReadRequest() return ReadRequestResponse(StreamResource, Logger); } - public override IModbusMessage ReadResponse() + public override IModbusMessage ReadResponse(IModbusMessage request) { return CreateMessageAndInitializeTransactionId(ReadRequestResponse(StreamResource, Logger)); } diff --git a/NModbus/IO/ModbusRtuTransport.cs b/NModbus/IO/ModbusRtuTransport.cs index 5aa53c6..c7cbe75 100644 --- a/NModbus/IO/ModbusRtuTransport.cs +++ b/NModbus/IO/ModbusRtuTransport.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -87,18 +88,20 @@ public override bool ChecksumsMatch(IModbusMessage message, byte[] messageFrame) return messageCrc == calculatedCrc; } - public override IModbusMessage ReadResponse() + public override IModbusMessage ReadResponse(IModbusMessage request) { - byte[] frame = ReadResponse(); + byte[] frame = ReadResponse(request); Logger.LogFrameRx(frame); return CreateResponse(frame); } - private byte[] ReadResponse() + private byte[] ReadResponse(IModbusMessage request) { - byte[] frameStart = Read(ResponseFrameStartLength); + byte[] frameStart = request != null ? + ReadStartFrameResponse(request) : + Read(ResponseFrameStartLength); byte[] frameEnd = Read(ResponseBytesToRead(frameStart)); byte[] frame = frameStart.Concat(frameEnd).ToArray(); @@ -107,7 +110,7 @@ private byte[] ReadResponse() public override void IgnoreResponse() { - byte[] frame = ReadResponse(); + byte[] frame = ReadResponse(null); Logger.LogFrameIgnoreRx(frame); } @@ -122,5 +125,71 @@ public override byte[] ReadRequest() return frame; } + + private byte[] ReadStartFrameResponse(IModbusMessage request) + { + const int HeaderLength = 2; + int frameHeadLength = ResponseFrameStartLength; + byte functionCode = request.FunctionCode; + byte slaveId = request.SlaveAddress; + int maxGarbageBytes = 1024; + + byte[] window = new byte[frameHeadLength]; + int count = 0; + int garbageCount = 0; + + while (true) + { + // Fill the sliding window buffer + while (count < frameHeadLength) + { + byte[] b = Read(1); + window[count++] = b[0]; + } + + int headIdx = -1; + if (slaveId == 0) + { + // Broadcast request: look for [1-247, functionCode] as valid header + for (int i = 0; i <= frameHeadLength - HeaderLength; i++) + { + if (window[i] >= 1 && window[i] <= 247 && window[i + 1] == functionCode) + { + headIdx = i; + break; + } + } + } + else + { + // Exact match: look for [slaveId, functionCode] + if (window[0] == slaveId && window[1] == functionCode) + headIdx = 0; + } + + if (headIdx >= 0) + { + // Extract the frame header starting from headIdx + byte[] frameHead = new byte[frameHeadLength]; + Array.Copy(window, headIdx, frameHead, 0, frameHeadLength - headIdx); + + // If there was garbage before the header, read extra bytes to complete the header + int bytesNeeded = headIdx; + if (bytesNeeded > 0) + { + byte[] rest = Read(bytesNeeded); + Array.Copy(rest, 0, frameHead, frameHeadLength - bytesNeeded, bytesNeeded); + } + return frameHead; + } + + // Slide the window: shift left by one byte, decrease count accordingly + Array.Copy(window, 1, window, 0, frameHeadLength - 1); + count = frameHeadLength - 1; + garbageCount++; + if (garbageCount > maxGarbageBytes) + throw new IOException("Too many garbage bytes, failed to find Modbus RTU frame header."); + } + } } } diff --git a/NModbus/IO/ModbusSerialTransport.cs b/NModbus/IO/ModbusSerialTransport.cs index a67d305..608820d 100644 --- a/NModbus/IO/ModbusSerialTransport.cs +++ b/NModbus/IO/ModbusSerialTransport.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.IO; using NModbus.Logging; +using NModbus.Message; namespace NModbus.IO { @@ -66,5 +67,69 @@ internal override void OnValidateResponse(IModbusMessage request, IModbusMessage { // no-op } + + public override bool ShouldRetryResponse(IModbusMessage request, IModbusMessage response) + { + if (request.FunctionCode != response.FunctionCode) + { + return true; + } + + if (request.SlaveAddress != 0) + { + if (response.SlaveAddress != request.SlaveAddress) + { + return true; + } + } + else + { + if (response.SlaveAddress < 1 || response.SlaveAddress > 247) + { + return true; + } + } + return false; + } + + public override void ValidateResponse(IModbusMessage request, IModbusMessage response) + { + // always check the function code and slave address, regardless of transport protocol + if (request.FunctionCode != response.FunctionCode) + { + string msg = $"Received response with unexpected Function Code. Expected {request.FunctionCode}, received {response.FunctionCode}."; + throw new IOException(msg); + } + + // Validate slave address + if (request.SlaveAddress != 0) + { + // Normal (non-broadcast) request: response address must match and be valid + if (response.SlaveAddress != request.SlaveAddress) + { + string msg = $"Response slave address does not match request. Expected {request.SlaveAddress}, received {response.SlaveAddress}."; + throw new IOException(msg); + } + } + else + { + // Broadcast request: only check that the response slave address is in valid range + if (response.SlaveAddress < 1 || response.SlaveAddress > 247) + { + string msg = $"Response slave address {response.SlaveAddress} is out of valid range (1~247) for a broadcast request."; + throw new IOException(msg); + } + } + + // message specific validation + var req = request as IModbusRequest; + + if (req != null) + { + req.ValidateResponse(response); + } + + OnValidateResponse(request, response); + } } } diff --git a/NModbus/IO/ModbusTransport.cs b/NModbus/IO/ModbusTransport.cs index 99a52bf..0d35193 100644 --- a/NModbus/IO/ModbusTransport.cs +++ b/NModbus/IO/ModbusTransport.cs @@ -134,7 +134,7 @@ public virtual T UnicastMessage(IModbusMessage message) do { readAgain = false; - response = ReadResponse(); + response = ReadResponse(message); var exceptionResponse = response as SlaveExceptionResponse; if (exceptionResponse != null) @@ -231,7 +231,7 @@ public virtual IModbusMessage CreateResponse(byte[] frame) return response; } - public void ValidateResponse(IModbusMessage request, IModbusMessage response) + public virtual void ValidateResponse(IModbusMessage request, IModbusMessage response) { // always check the function code and slave address, regardless of transport protocol if (request.FunctionCode != response.FunctionCode) @@ -260,7 +260,7 @@ public void ValidateResponse(IModbusMessage request, IModbusMessage response) /// /// Check whether we need to attempt to read another response before processing it (e.g. response was from previous request) /// - public bool ShouldRetryResponse(IModbusMessage request, IModbusMessage response) + public virtual bool ShouldRetryResponse(IModbusMessage request, IModbusMessage response) { // These checks are enforced in ValidateRequest, we don't want to retry for these if (request.FunctionCode != response.FunctionCode) @@ -291,7 +291,7 @@ public virtual bool OnShouldRetryResponse(IModbusMessage request, IModbusMessage public abstract byte[] ReadRequest(); - public abstract IModbusMessage ReadResponse() + public abstract IModbusMessage ReadResponse(IModbusMessage request) where T : IModbusMessage, new(); public abstract byte[] BuildMessageFrame(IModbusMessage message);