From dffc181a8c7fa91efe1bfe99ae79002766c2acde Mon Sep 17 00:00:00 2001 From: Howaner Date: Thu, 5 Mar 2026 15:54:04 +0100 Subject: [PATCH 1/7] Changed visibility modifiers and moved code into seperate functions This makes it easier to unit test and it allows the injection of additional request/response types --- .../j2mod/modbus/io/ModbusRTUTransport.java | 36 ++++++++++++++--- .../modbus/net/AbstractModbusListener.java | 2 +- .../j2mod/modbus/net/SerialConnection.java | 39 +++++++++++-------- .../j2mod/modbus/slave/ModbusSlave.java | 2 +- .../j2mod/modbus/util/SerialParameters.java | 6 +++ 5 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java index a36a3d2b..1b48f7a3 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java +++ b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java @@ -53,20 +53,44 @@ public class ModbusRTUTransport extends ModbusSerialTransport { * @param out Output buffer to populate * @throws IOException If data cannot be read from the port */ - private void readRequestData(int byteCount, BytesOutputStream out) throws IOException { + protected void readRequestData(int byteCount, BytesOutputStream out) throws IOException { byteCount += 2; byte[] inpBuf = new byte[byteCount]; readBytes(inpBuf, byteCount); out.write(inpBuf, 0, byteCount); } + /** + * Factory method creating the required specialized ModbusRequest + * instance. + * + * @param functionCode the function code of the request as int. + * + * @return a ModbusRequest instance specific for the given function type. + */ + protected ModbusRequest createModbusRequest(int functionCode) { + return ModbusRequest.createModbusRequest(functionCode); + } + + /** + * Factory method creating the required specialized ModbusResponse + * instance. + * + * @param functionCode the function code of the response as int. + * + * @return a ModbusResponse instance specific for the given function code. + */ + protected ModbusResponse createModbusResponse(int functionCode) { + return ModbusResponse.createModbusResponse(functionCode); + } + /** * getRequest - Read a request, after the unit and function code * * @param function - Modbus function code * @param out - Byte stream buffer to hold actual message */ - private void getRequest(int function, BytesOutputStream out) throws IOException { + protected void getRequest(int function, BytesOutputStream out) throws IOException { int byteCount; byte[] inpBuf = new byte[256]; try { @@ -139,7 +163,7 @@ private void getRequest(int function, BytesOutputStream out) throws IOException * @param out The output buffer to put the result * @throws IOException If data cannot be read from the port */ - private void getResponse(int function, BytesOutputStream out) throws IOException { + protected void getResponse(int function, BytesOutputStream out) throws IOException { byte[] inpBuf = new byte[256]; try { if ((function & 0x80) == 0) { @@ -298,7 +322,7 @@ protected ModbusRequest readRequestIn(AbstractModbusListener listener) throws Mo byteInputOutputStream.writeByte(fc); // create request to acquire length of message - request = ModbusRequest.createModbusRequest(fc); + request = createModbusRequest(fc); request.setHeadless(); /* @@ -321,7 +345,7 @@ protected ModbusRequest readRequestIn(AbstractModbusListener listener) throws Mo int[] crc = ModbusUtil.calculateCRC(inBuffer, 0, dlength); // does not include CRC if (ModbusUtil.unsignedByteToInt(inBuffer[dlength]) != crc[0] || ModbusUtil.unsignedByteToInt(inBuffer[dlength + 1]) != crc[1]) { if (logger.isDebugEnabled()) { - logger.debug("CRC should be {}, {} inBuffer={}", Integer.toHexString(crc[0]), Integer.toHexString(crc[1]), + logger.debug("CRC should be {}, {} inBuffer={}", Integer.toHexString(crc[0]), Integer.toHexString(crc[1]), ModbusUtil.toHex(inBuffer, 0, dlength + 2)); } @@ -429,7 +453,7 @@ protected ModbusResponse readResponseIn() throws ModbusIOException { byteInputOutputStream.writeByte(fc); // create response to acquire length of message - response = ModbusResponse.createModbusResponse(fc); + response = createModbusResponse(fc); response.setHeadless(); /* diff --git a/src/main/java/com/ghgande/j2mod/modbus/net/AbstractModbusListener.java b/src/main/java/com/ghgande/j2mod/modbus/net/AbstractModbusListener.java index 7de1fabe..6854acd9 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/net/AbstractModbusListener.java +++ b/src/main/java/com/ghgande/j2mod/modbus/net/AbstractModbusListener.java @@ -144,7 +144,7 @@ public void setTimeout(int timeout) { * @param listener Listener that the request was received by * @throws ModbusIOException If there is an issue with the transport or transmission */ - void handleRequest(AbstractModbusTransport transport, AbstractModbusListener listener) throws ModbusIOException { + protected void handleRequest(AbstractModbusTransport transport, AbstractModbusListener listener) throws ModbusIOException { // Get the request from the transport. It will be processed // using an associated process image diff --git a/src/main/java/com/ghgande/j2mod/modbus/net/SerialConnection.java b/src/main/java/com/ghgande/j2mod/modbus/net/SerialConnection.java index 58e6fb9c..097f2652 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/net/SerialConnection.java +++ b/src/main/java/com/ghgande/j2mod/modbus/net/SerialConnection.java @@ -103,22 +103,7 @@ public synchronized void open() throws IOException { applyConnectionParameters(); if (!reopen) { - if (Modbus.SERIAL_ENCODING_ASCII.equals(parameters.getEncoding())) { - transport = new ModbusASCIITransport(); - } - else if (Modbus.SERIAL_ENCODING_RTU.equals(parameters.getEncoding())) { - transport = new ModbusRTUTransport(); - } - else { - transport = new ModbusRTUTransport(); - logger.warn("Unknown transport encoding [{}] - reverting to RTU", parameters.getEncoding()); - } - transport.setEcho(parameters.isEcho()); - transport.setTimeout(timeout); - - // Open the input and output streams for the connection. If they won't - // open, close the port before throwing an exception. - transport.setCommPort(this); + this.transport = this.initializeTransport(); } // Open the port so that we can get it's input stream int attempts = 0; @@ -159,6 +144,28 @@ public void serialEvent(SerialPortEvent event) { } } + protected ModbusSerialTransport initializeTransport() throws IOException { + ModbusSerialTransport transport; + if (Modbus.SERIAL_ENCODING_ASCII.equals(parameters.getEncoding())) { + transport = new ModbusASCIITransport(); + } + else if (Modbus.SERIAL_ENCODING_RTU.equals(parameters.getEncoding())) { + transport = new ModbusRTUTransport(); + } + else { + transport = new ModbusRTUTransport(); + logger.warn("Unknown transport encoding [{}] - reverting to RTU", parameters.getEncoding()); + } + transport.setEcho(parameters.isEcho()); + transport.setTimeout(timeout); + + // Open the input and output streams for the connection. If they won't + // open, close the port before throwing an exception. + transport.setCommPort(this); + + return transport; + } + /** * Applies the serial parameters to the actual hardware */ diff --git a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java index 43a8a379..bdc3ffd4 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java +++ b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java @@ -135,7 +135,7 @@ else if (this.type.is(ModbusSlaveType.TCP)) { listener = tcpListener; } else { - listener = new ModbusSerialListener(serialParams); + listener = serialParams.createModbusListener(); } listener.setAddress(address); diff --git a/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java b/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java index ca5601ac..cab8fb7d 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java +++ b/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java @@ -16,7 +16,9 @@ package com.ghgande.j2mod.modbus.util; import com.ghgande.j2mod.modbus.Modbus; +import com.ghgande.j2mod.modbus.net.AbstractModbusListener; import com.ghgande.j2mod.modbus.net.AbstractSerialConnection; +import com.ghgande.j2mod.modbus.net.ModbusSerialListener; import java.util.Arrays; import java.util.List; @@ -924,6 +926,10 @@ public boolean isRs485ControlDisabled() { return this.rs485DisableControl; } + public AbstractModbusListener createModbusListener() { + return new ModbusSerialListener(this); + } + @Override public String toString() { return "SerialParameters{" + From b92e52c30ef5eff75256b71a440ec83b2543d632 Mon Sep 17 00:00:00 2001 From: Howaner Date: Fri, 6 Mar 2026 11:38:04 +0100 Subject: [PATCH 2/7] Slave creation: Use factory for listener creation --- .../j2mod/modbus/slave/ModbusSlave.java | 94 ++----------------- .../modbus/slave/ModbusSlaveFactory.java | 27 +++++- .../j2mod/modbus/util/SerialParameters.java | 6 -- 3 files changed, 32 insertions(+), 95 deletions(-) diff --git a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java index bdc3ffd4..214ae691 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java +++ b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java @@ -17,18 +17,15 @@ import com.ghgande.j2mod.modbus.ModbusException; import com.ghgande.j2mod.modbus.net.AbstractModbusListener; -import com.ghgande.j2mod.modbus.net.ModbusSerialListener; -import com.ghgande.j2mod.modbus.net.ModbusTCPListener; -import com.ghgande.j2mod.modbus.net.ModbusUDPListener; import com.ghgande.j2mod.modbus.procimg.ProcessImage; import com.ghgande.j2mod.modbus.util.ModbusUtil; import com.ghgande.j2mod.modbus.util.SerialParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.InetAddress; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; /** * Class that implements a wrapper around a Slave Listener @@ -49,98 +46,23 @@ public class ModbusSlave { private final Map processImages = new HashMap(); - /** - * Creates a TCP modbus slave - * - * @param port Port to listen on if IP type - * @param poolSize Pool size for TCP slaves - * @param useRtuOverTcp True if the RTU protocol should be used over TCP - * @throws ModbusException If a problem occurs e.g. port already in use - */ - protected ModbusSlave(int port, int poolSize, boolean useRtuOverTcp) throws ModbusException { - this(ModbusSlaveType.TCP, null, port, poolSize, null, useRtuOverTcp, 0); - } - - /** - * Creates a TCP modbus slave - * - * @param address IP address to listen on - * @param port Port to listen on if IP type - * @param poolSize Pool size for TCP slaves - * @param useRtuOverTcp True if the RTU protocol should be used over TCP - * @throws ModbusException If a problem occurs e.g. port already in use - */ - protected ModbusSlave(InetAddress address, int port, int poolSize, boolean useRtuOverTcp, int maxIdleSeconds) throws ModbusException { - this(ModbusSlaveType.TCP, address, port, poolSize, null, useRtuOverTcp, maxIdleSeconds); - } - - /** - * Creates a UDP modbus slave - * - * @param port Port to listen on if IP type - * @param useRtuOverTcp True if the RTU protocol should be used over TCP - * @throws ModbusException If a problem occurs e.g. port already in use - */ - protected ModbusSlave(int port, boolean useRtuOverTcp) throws ModbusException { - this(ModbusSlaveType.UDP, null, port, 0, null, useRtuOverTcp, 0); - } - - /** - * Creates a UDP modbus slave - * - * @param address IP address to listen on - * @param port Port to listen on if IP type - * @param useRtuOverTcp True if the RTU protocol should be used over TCP - * @throws ModbusException If a problem occurs e.g. port already in use - */ - protected ModbusSlave(InetAddress address, int port, boolean useRtuOverTcp) throws ModbusException { - this(ModbusSlaveType.UDP, address, port, 0, null, useRtuOverTcp, 0); - } - - /** - * Creates a serial modbus slave - * - * @param serialParams Serial parameters for serial type slaves - * @throws ModbusException If a problem occurs e.g. port already in use - */ - protected ModbusSlave(SerialParameters serialParams) throws ModbusException { - this(ModbusSlaveType.SERIAL, null, 0, 0, serialParams, false, 0); - } - /** * Creates an appropriate type of listener * - * @param type Type of slave to create - * @param address IP address to listen on - * @param port Port to listen on if IP type - * @param poolSize Pool size for TCP slaves - * @param serialParams Serial parameters for serial type slaves - * @param useRtuOverTcp True if the RTU protocol should be used over TCP - * @param maxIdleSeconds Maximum idle seconds for TCP connection + * @param type Type of slave to create + * @param serialParams Serial parameters for serial type slaves + * @param listenerFactory method used to create the modbus listener */ - private ModbusSlave(ModbusSlaveType type, InetAddress address, int port, int poolSize, SerialParameters serialParams, boolean useRtuOverTcp, int maxIdleSeconds) { + protected ModbusSlave(ModbusSlaveType type, SerialParameters serialParams, Supplier listenerFactory) { this.type = type == null ? ModbusSlaveType.TCP : type; - this.port = port; this.serialParams = serialParams; // Create the listener - logger.debug("Creating {} listener", this.type); - if (this.type.is(ModbusSlaveType.UDP)) { - listener = new ModbusUDPListener(); - } - else if (this.type.is(ModbusSlaveType.TCP)) { - ModbusTCPListener tcpListener = new ModbusTCPListener(poolSize, useRtuOverTcp); - tcpListener.setMaxIdleSeconds(maxIdleSeconds); - listener = tcpListener; - } - else { - listener = serialParams.createModbusListener(); - } + this.listener = listenerFactory.get(); + this.listener.setTimeout(0); - listener.setAddress(address); - listener.setPort(port); - listener.setTimeout(0); + this.port = this.listener.getPort(); } /** diff --git a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java index 381fb984..de1a0aae 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java +++ b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java @@ -17,6 +17,9 @@ import com.ghgande.j2mod.modbus.ModbusException; import com.ghgande.j2mod.modbus.net.AbstractModbusListener; +import com.ghgande.j2mod.modbus.net.ModbusSerialListener; +import com.ghgande.j2mod.modbus.net.ModbusTCPListener; +import com.ghgande.j2mod.modbus.net.ModbusUDPListener; import com.ghgande.j2mod.modbus.util.ModbusUtil; import com.ghgande.j2mod.modbus.util.SerialParameters; @@ -24,6 +27,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; /** * This is a factory class that allows users to easily create and manage slaves.
@@ -98,7 +102,13 @@ public static synchronized ModbusSlave createTCPSlave(InetAddress address, int p return slaves.get(key); } else { - ModbusSlave slave = new ModbusSlave(address, port, poolSize, useRtuOverTcp, maxIdleSeconds); + ModbusSlave slave = new ModbusSlave(ModbusSlaveType.TCP, null, () -> { + ModbusTCPListener listener = new ModbusTCPListener(poolSize, useRtuOverTcp); + listener.setAddress(address); + listener.setPort(port); + listener.setMaxIdleSeconds(maxIdleSeconds); + return listener; + }); slaves.put(key, slave); return slave; } @@ -129,7 +139,7 @@ public static synchronized ModbusSlave createUDPSlave(InetAddress address, int p return slaves.get(key); } else { - ModbusSlave slave = new ModbusSlave(address, port, false); + ModbusSlave slave = new ModbusSlave(ModbusSlaveType.UDP, null, ModbusUDPListener::new); slaves.put(key, slave); return slave; } @@ -143,6 +153,17 @@ public static synchronized ModbusSlave createUDPSlave(InetAddress address, int p * @throws ModbusException If a problem occurs e.g. port already in use */ public static synchronized ModbusSlave createSerialSlave(SerialParameters serialParams) throws ModbusException { + return createSerialSlave(serialParams, () -> new ModbusSerialListener(serialParams)); + } + + /** + * Creates a serial modbus slave or returns the one already allocated to this port + * + * @param serialParams Serial parameters for serial type slaves + * @return new or existing Serial modbus slave associated with the port + * @throws ModbusException If a problem occurs e.g. port already in use + */ + public static synchronized ModbusSlave createSerialSlave(SerialParameters serialParams, Supplier listenerFactory) throws ModbusException { ModbusSlave slave; if (serialParams == null) { throw new ModbusException("Serial parameters are null"); @@ -163,7 +184,7 @@ else if (ModbusUtil.isBlank(serialParams.getPortName())) { // If we don;t have a slave, create one if (slave == null) { - slave = new ModbusSlave(serialParams); + slave = new ModbusSlave(ModbusSlaveType.SERIAL, serialParams, listenerFactory); slaves.put(ModbusSlaveType.SERIAL.getKey(serialParams.getPortName()), slave); } return slave; diff --git a/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java b/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java index cab8fb7d..ca5601ac 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java +++ b/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java @@ -16,9 +16,7 @@ package com.ghgande.j2mod.modbus.util; import com.ghgande.j2mod.modbus.Modbus; -import com.ghgande.j2mod.modbus.net.AbstractModbusListener; import com.ghgande.j2mod.modbus.net.AbstractSerialConnection; -import com.ghgande.j2mod.modbus.net.ModbusSerialListener; import java.util.Arrays; import java.util.List; @@ -926,10 +924,6 @@ public boolean isRs485ControlDisabled() { return this.rs485DisableControl; } - public AbstractModbusListener createModbusListener() { - return new ModbusSerialListener(this); - } - @Override public String toString() { return "SerialParameters{" + From cd43aaccecf514f392c62a0aa0325968fbdfbae0 Mon Sep 17 00:00:00 2001 From: Howaner Date: Fri, 6 Mar 2026 11:38:19 +0100 Subject: [PATCH 3/7] Added a way to override sleep time between send retries --- .../j2mod/modbus/io/ModbusTransaction.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java index 7fa3a903..ec85b496 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java +++ b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java @@ -22,6 +22,8 @@ import com.ghgande.j2mod.modbus.msg.ModbusResponse; import java.util.Random; +import java.util.function.Function; +import java.util.function.Supplier; /** * Interface defining a ModbusTransaction. @@ -43,6 +45,7 @@ public abstract class ModbusTransaction { int retries = Modbus.DEFAULT_RETRIES; private final Random random = new Random(System.nanoTime()); static int transactionID = Modbus.DEFAULT_TRANSACTION_ID; + Function retrySleepTimeCalculatorFunc = (count) -> (Modbus.RETRY_SLEEP_TIME / 2) + (long) (random.nextDouble() * Modbus.RETRY_SLEEP_TIME * count); /** * Returns the ModbusRequest instance @@ -104,6 +107,17 @@ public void setRetries(int retries) { this.retries = retries; } + /** + * Sets the method used for calculating sleep time between retries. + * @param retrySleepTimeCalculatorFunc the argument is the amount of retries, beginning with 1. The return value is the milliseconds to wait. + */ + public void setRetrySleepTimeCalculator(Function retrySleepTimeCalculatorFunc) { + if (retrySleepTimeCalculatorFunc == null) { + throw new IllegalArgumentException("retrySleepTimeCalculatorFunc must not be null"); + } + this.retrySleepTimeCalculatorFunc = retrySleepTimeCalculatorFunc; + } + /** * Tests whether the validity of a transaction * will be checked. @@ -151,8 +165,8 @@ public synchronized int getTransactionID() { * @param count Retry count * @return Random sleep time in milliseconds */ - long getRandomSleepTime(int count) { - return (Modbus.RETRY_SLEEP_TIME / 2) + (long) (random.nextDouble() * Modbus.RETRY_SLEEP_TIME * count); + protected long getRandomSleepTime(int count) { + return this.retrySleepTimeCalculatorFunc.apply(count); } /** From fa311e60dbed91d75d4ebc6e48c311e429036a23 Mon Sep 17 00:00:00 2001 From: Howaner Date: Tue, 10 Mar 2026 10:39:26 +0100 Subject: [PATCH 4/7] Improved logging --- .../ghgande/j2mod/modbus/io/ModbusRTUTransport.java | 13 ++++++++++--- .../j2mod/modbus/io/ModbusSerialTransport.java | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java index 1b48f7a3..59c4b4a3 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java +++ b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java @@ -426,6 +426,10 @@ protected ModbusRequest readRequestIn(AbstractModbusListener listener) throws Mo } } + protected int readUid() throws IOException { + return readByte(); + } + /** * readResponse - Read the bytes for the response from the slave. * @@ -439,15 +443,18 @@ protected ModbusResponse readResponseIn() throws ModbusIOException { ModbusResponse response; int dlength; + int uid = -1; + int fc = -1; + try { do { // 1. read to function code, create request and read function // specific bytes synchronized (byteInputStream) { - int uid = readByte(); + uid = readUid(); if (uid != -1) { - int fc = readByte(); + fc = readByte(); byteInputOutputStream.reset(); byteInputOutputStream.writeByte(uid); byteInputOutputStream.writeByte(fc); @@ -492,7 +499,7 @@ protected ModbusResponse readResponseIn() throws ModbusIOException { } catch (IOException ex) { // FIXME: This printout is wrong when reading response from other slave - throw new ModbusIOException("I/O exception - failed to read response for request [%s] - %s", ModbusUtil.toHex(lastRequest), ex.getMessage()); + throw new ModbusIOException(String.format("I/O exception - failed to read response for request [%s] - uid: %d - fc: %d - %s", ModbusUtil.toHex(lastRequest), uid, fc, ex.getMessage()), ex); } } } diff --git a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java index 317beb81..b6e646a8 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java +++ b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java @@ -426,7 +426,7 @@ void readBytes(byte[] buffer, int bytesToRead) throws IOException { if (commPort != null && commPort.isOpen()) { int cnt = commPort.readBytes(buffer, bytesToRead); if (cnt != bytesToRead) { - throw new IOException("Cannot read from serial port - truncated"); + throw new IOException(String.format("Cannot read from serial port - truncated (expected %d, got %d)", bytesToRead, cnt)); } } else { From e893f43a39b247a457eb62d3ca98f4d4d79af760 Mon Sep 17 00:00:00 2001 From: Howaner Date: Tue, 14 Apr 2026 15:33:29 +0200 Subject: [PATCH 5/7] Added bytes unit to "Cannot read from serial port" error message --- .../java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java index b6e646a8..487ad52b 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java +++ b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusSerialTransport.java @@ -426,7 +426,7 @@ void readBytes(byte[] buffer, int bytesToRead) throws IOException { if (commPort != null && commPort.isOpen()) { int cnt = commPort.readBytes(buffer, bytesToRead); if (cnt != bytesToRead) { - throw new IOException(String.format("Cannot read from serial port - truncated (expected %d, got %d)", bytesToRead, cnt)); + throw new IOException(String.format("Cannot read from serial port - truncated (expected %d bytes, got %d bytes)", bytesToRead, cnt)); } } else { From 90cd392c5546acfc6fd806779706546f0590da10 Mon Sep 17 00:00:00 2001 From: Howaner Date: Wed, 15 Apr 2026 14:36:08 +0200 Subject: [PATCH 6/7] Made retrySleepTimeCalculatorFunc private --- .../java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java index ec85b496..2e27c8f4 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java +++ b/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java @@ -23,7 +23,6 @@ import java.util.Random; import java.util.function.Function; -import java.util.function.Supplier; /** * Interface defining a ModbusTransaction. @@ -45,7 +44,7 @@ public abstract class ModbusTransaction { int retries = Modbus.DEFAULT_RETRIES; private final Random random = new Random(System.nanoTime()); static int transactionID = Modbus.DEFAULT_TRANSACTION_ID; - Function retrySleepTimeCalculatorFunc = (count) -> (Modbus.RETRY_SLEEP_TIME / 2) + (long) (random.nextDouble() * Modbus.RETRY_SLEEP_TIME * count); + private Function retrySleepTimeCalculatorFunc = (count) -> (Modbus.RETRY_SLEEP_TIME / 2) + (long) (random.nextDouble() * Modbus.RETRY_SLEEP_TIME * count); /** * Returns the ModbusRequest instance From 77012fe957ef79cb56abdd1ffb70c5a38b3bf747 Mon Sep 17 00:00:00 2001 From: Howaner Date: Thu, 16 Apr 2026 13:06:47 +0200 Subject: [PATCH 7/7] Corrected UDP Slave Listener --- .../com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java index de1a0aae..b6d02b8d 100644 --- a/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java +++ b/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java @@ -139,7 +139,12 @@ public static synchronized ModbusSlave createUDPSlave(InetAddress address, int p return slaves.get(key); } else { - ModbusSlave slave = new ModbusSlave(ModbusSlaveType.UDP, null, ModbusUDPListener::new); + ModbusSlave slave = new ModbusSlave(ModbusSlaveType.UDP, null, () -> { + ModbusUDPListener listener = new ModbusUDPListener(); + listener.setAddress(address); + listener.setPort(port); + return listener; + }); slaves.put(key, slave); return slave; }