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 a36a3d2..59c4b4a 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));
}
@@ -402,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.
*
@@ -415,21 +443,24 @@ 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);
// create response to acquire length of message
- response = ModbusResponse.createModbusResponse(fc);
+ response = createModbusResponse(fc);
response.setHeadless();
/*
@@ -468,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 317beb8..487ad52 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 bytes, got %d bytes)", bytesToRead, cnt));
}
}
else {
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 7fa3a90..2e27c8f 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,7 @@
import com.ghgande.j2mod.modbus.msg.ModbusResponse;
import java.util.Random;
+import java.util.function.Function;
/**
* Interface defining a ModbusTransaction.
@@ -43,6 +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;
+ private Function retrySleepTimeCalculatorFunc = (count) -> (Modbus.RETRY_SLEEP_TIME / 2) + (long) (random.nextDouble() * Modbus.RETRY_SLEEP_TIME * count);
/**
* Returns the ModbusRequest instance
@@ -104,6 +106,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 +164,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);
}
/**
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 7de1fab..6854acd 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 58e6fb9..097f265 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 43a8a37..214ae69 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 = new ModbusSerialListener(serialParams);
- }
+ 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 381fb98..b6d02b8 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,12 @@ 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 listener = new ModbusUDPListener();
+ listener.setAddress(address);
+ listener.setPort(port);
+ return listener;
+ });
slaves.put(key, slave);
return slave;
}
@@ -143,6 +158,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 +189,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;