Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <tt>ModbusRequest</tt>
* instance.
*
* @param functionCode the function code of the request as <tt>int</tt>.
*
* @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 <tt>ModbusResponse</tt>
* instance.
*
* @param functionCode the function code of the response as <tt>int</tt>.
*
* @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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();

/*
Expand All @@ -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));
}

Expand Down Expand Up @@ -402,6 +426,10 @@ protected ModbusRequest readRequestIn(AbstractModbusListener listener) throws Mo
}
}

protected int readUid() throws IOException {
return readByte();
}

Comment thread
da-Kai marked this conversation as resolved.
/**
* readResponse - Read the bytes for the response from the slave.
*
Expand All @@ -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();

/*
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.ghgande.j2mod.modbus.msg.ModbusResponse;

import java.util.Random;
import java.util.function.Function;

/**
* Interface defining a ModbusTransaction.
Expand All @@ -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<Integer, Long> retrySleepTimeCalculatorFunc = (count) -> (Modbus.RETRY_SLEEP_TIME / 2) + (long) (random.nextDouble() * Modbus.RETRY_SLEEP_TIME * count);

/**
* Returns the <tt>ModbusRequest</tt> instance
Expand Down Expand Up @@ -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<Integer, Long> 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.
Expand Down Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 23 additions & 16 deletions src/main/java/com/ghgande/j2mod/modbus/net/SerialConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*/
Expand Down
94 changes: 8 additions & 86 deletions src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -49,98 +46,23 @@ public class ModbusSlave {

private final Map<Integer, ProcessImage> processImages = new HashMap<Integer, ProcessImage>();

/**
* 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);
}

Comment thread
da-Kai marked this conversation as resolved.
/**
* 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<AbstractModbusListener> listenerFactory) {
Comment thread
da-Kai marked this conversation as resolved.
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();
}

/**
Expand Down
Loading