Add logic to retry opening serial port connections if they are broken

pull/57/head
terrypacker 6 years ago
parent f622c6019e
commit 5c9753cd08

@ -10,6 +10,7 @@
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">

@ -2,4 +2,5 @@ eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

@ -1,5 +1,6 @@
*Release notes for 3.0.6*
* Add ability to validate slave ID in responses. Defaults to enabled for Ascii, RTU and Encapsulated TCP, disabled for Tcp and Udp
* Add retry to serial connections if the connection is broken try to re-open the serial port retries number of times
*Release notes for 3.0.4*
* TcpMaster now allows optionally controlling transaction id.

@ -5,12 +5,12 @@
*
* Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
* @author Matthew Lohbihler
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@ -60,7 +60,7 @@ public class TcpMaster extends ModbusMaster {
private static final int RETRY_PAUSE_MAX = 1000;
// Configuration fields.
private final Log LOG = LogFactory.getLog(TcpMaster.class);
private final Log LOG = LogFactory.getLog(TcpMaster.class);
private short nextTransactionId = 0;
private final IpParameters ipParameters;
private final boolean keepAlive;
@ -73,7 +73,7 @@ public class TcpMaster extends ModbusMaster {
/**
* <p>Constructor for TcpMaster.</p>
*
*
* @param params
* @param keepAlive
* @param autoIncrementTransactionId
@ -84,11 +84,11 @@ public class TcpMaster extends ModbusMaster {
this.keepAlive = keepAlive;
this.autoIncrementTransactionId = autoIncrementTransactionId;
}
/**
* <p>Constructor for TcpMaster.</p>
* Default to not validating the slave id in responses
*
*
* @param params a {@link com.serotonin.modbus4j.ip.IpParameters} object.
* @param keepAlive a boolean.
* @param autoIncrementTransactionId a boolean.
@ -96,11 +96,11 @@ public class TcpMaster extends ModbusMaster {
public TcpMaster(IpParameters params, boolean keepAlive, boolean autoIncrementTransactionId) {
this(params, keepAlive, autoIncrementTransactionId, false);
}
/**
* <p>Constructor for TcpMaster.</p>
*
*
* Default to auto increment transaction id
* Default to not validating the slave id in responses
*
@ -119,7 +119,7 @@ public class TcpMaster extends ModbusMaster {
public void setNextTransactionId(short id) {
this.nextTransactionId = id;
}
/**
* <p>Getter for the field <code>nextTransactionId</code>.</p>
*
@ -158,9 +158,9 @@ public class TcpMaster extends ModbusMaster {
openConnection();
if(conn == null){
LOG.debug("Connection null: " + ipParameters.getPort());
}
LOG.debug("Connection null: " + ipParameters.getPort());
}
}
catch (Exception e) {
closeConnection();
@ -178,56 +178,56 @@ public class TcpMaster extends ModbusMaster {
}
if(LOG.isDebugEnabled()){
StringBuilder sb = new StringBuilder();
for (byte b : Arrays.copyOfRange(ipRequest.getMessageData(),0,ipRequest.getMessageData().length)) {
sb.append(String.format("%02X ", b));
}
LOG.debug("Encap Request: " + sb.toString());
StringBuilder sb = new StringBuilder();
for (byte b : Arrays.copyOfRange(ipRequest.getMessageData(),0,ipRequest.getMessageData().length)) {
sb.append(String.format("%02X ", b));
}
LOG.debug("Encap Request: " + sb.toString());
}
// Send the request to get the response.
// Send the request to get the response.
IpMessageResponse ipResponse;
LOG.debug("Sending on port: " + ipParameters.getPort());
LOG.debug("Sending on port: " + ipParameters.getPort());
try {
if(conn == null){
LOG.debug("Connection null: " + ipParameters.getPort());
}
if(conn == null){
LOG.debug("Connection null: " + ipParameters.getPort());
}
ipResponse = (IpMessageResponse) conn.send(ipRequest);
if (ipResponse == null)
return null;
if(LOG.isDebugEnabled()){
StringBuilder sb = new StringBuilder();
for (byte b : Arrays.copyOfRange(ipResponse.getMessageData(),0,ipResponse.getMessageData().length)) {
sb.append(String.format("%02X ", b));
}
LOG.debug("Response: " + sb.toString());
StringBuilder sb = new StringBuilder();
for (byte b : Arrays.copyOfRange(ipResponse.getMessageData(),0,ipResponse.getMessageData().length)) {
sb.append(String.format("%02X ", b));
}
LOG.debug("Response: " + sb.toString());
}
return ipResponse.getModbusResponse();
}
catch (Exception e) {
LOG.debug("Exception: " + e.getMessage() + " " + e.getLocalizedMessage());
LOG.debug("Exception: " + e.getMessage() + " " + e.getLocalizedMessage());
if (keepAlive) {
LOG.debug("KeepAlive - reconnect!");
LOG.debug("KeepAlive - reconnect!");
// The connection may have been reset, so try to reopen it and attempt the message again.
try {
LOG.debug("Modbus4J: Keep-alive connection may have been reset. Attempting to re-open.");
LOG.debug("Modbus4J: Keep-alive connection may have been reset. Attempting to re-open.");
openConnection();
ipResponse = (IpMessageResponse) conn.send(ipRequest);
if (ipResponse == null)
return null;
if(LOG.isDebugEnabled()){
StringBuilder sb = new StringBuilder();
for (byte b : Arrays.copyOfRange(ipResponse.getMessageData(),0,ipResponse.getMessageData().length)) {
sb.append(String.format("%02X ", b));
}
LOG.debug("Response: " + sb.toString());
StringBuilder sb = new StringBuilder();
for (byte b : Arrays.copyOfRange(ipResponse.getMessageData(),0,ipResponse.getMessageData().length)) {
sb.append(String.format("%02X ", b));
}
LOG.debug("Response: " + sb.toString());
}
return ipResponse.getModbusResponse();
}
catch (Exception e2) {
closeConnection();
LOG.debug("Exception: " + e2.getMessage() + " " + e2.getLocalizedMessage());
LOG.debug("Exception: " + e2.getMessage() + " " + e2.getLocalizedMessage());
throw new ModbusTransportException(e2, request.getSlaveId());
}
}
@ -268,7 +268,7 @@ public class TcpMaster extends ModbusMaster {
if (retries <= 0)
throw e;
// System.out.println("Modbus4J: Open connection failed, trying again.");
retries--;
// Pause for a bit.

@ -5,12 +5,12 @@
*
* Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
* @author Matthew Lohbihler
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.sero.messaging.EpollStreamTransport;
import com.serotonin.modbus4j.sero.messaging.MessageControl;
import com.serotonin.modbus4j.sero.messaging.StreamTransport;
import com.serotonin.modbus4j.sero.messaging.Transport;
@ -36,19 +37,22 @@ import com.serotonin.modbus4j.sero.messaging.Transport;
* @version 5.0.0
*/
abstract public class SerialMaster extends ModbusMaster {
private final Log LOG = LogFactory.getLog(SerialMaster.class);
// Runtime fields.
private static final int RETRY_PAUSE_START = 50;
private static final int RETRY_PAUSE_MAX = 1000;
private final Log LOG = LogFactory.getLog(SerialMaster.class);
// Runtime fields.
protected boolean serialPortOpen;
protected SerialPortWrapper wrapper;
protected Transport transport;
/**
* <p>Constructor for SerialMaster.</p>
*
*
* Default to validating the slave id in responses
*
* @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
@ -56,7 +60,7 @@ abstract public class SerialMaster extends ModbusMaster {
public SerialMaster(SerialPortWrapper wrapper) {
this(wrapper, true);
}
/**
* <p>Constructor for SerialMaster.</p>
* @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
@ -71,28 +75,90 @@ abstract public class SerialMaster extends ModbusMaster {
@Override
public void init() throws ModbusInitException {
try {
this.wrapper.open();
if (getePoll() != null)
transport = new EpollStreamTransport(wrapper.getInputStream(), wrapper.getOutputStream(),
getePoll());
else
transport = new StreamTransport(wrapper.getInputStream(), wrapper.getOutputStream());
this.openConnection(null);
}
catch (Exception e) {
throw new ModbusInitException(e);
}
}
/**
* Open the serial port and initialize the transport, ensure
* connection is closed first
*
* @param conn
* @throws Exception
*/
protected void openConnection(MessageControl toClose) throws Exception {
// Make sure any existing connection is closed.
closeConnection(toClose);
// Try 'retries' times to get the socket open.
int retries = getRetries();
int retryPause = RETRY_PAUSE_START;
while (true) {
try {
this.wrapper.open();
this.serialPortOpen = true;
if (getePoll() != null) {
transport = new EpollStreamTransport(wrapper.getInputStream(),
wrapper.getOutputStream(),
getePoll());
}else {
transport = new StreamTransport(wrapper.getInputStream(),
wrapper.getOutputStream());
}
break;
}catch(Exception e) {
//Ensure port is closed before we try to reopen or bail out
close();
if (retries <= 0)
throw e;
retries--;
// Pause for a bit.
try {
Thread.sleep(retryPause);
}
catch (InterruptedException e1) {
// ignore
}
retryPause *= 2;
if (retryPause > RETRY_PAUSE_MAX)
retryPause = RETRY_PAUSE_MAX;
}
}
}
/**
* Close serial port
* @param conn
*/
protected void closeConnection(MessageControl conn) {
closeMessageControl(conn);
try {
if(serialPortOpen) {
wrapper.close();
serialPortOpen = false;
}
}
catch (Exception e) {
getExceptionHandler().receivedException(e);
}
transport = null;
}
/**
* <p>close.</p>
*/
public void close() {
try {
wrapper.close();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
wrapper.close();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
}

@ -5,12 +5,12 @@
*
* Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
* @author Matthew Lohbihler
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@ -20,7 +20,8 @@
*/
package com.serotonin.modbus4j.serial.ascii;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
@ -39,21 +40,23 @@ import com.serotonin.modbus4j.sero.messaging.StreamTransport;
* @version 5.0.0
*/
public class AsciiMaster extends SerialMaster {
private final Log LOG = LogFactory.getLog(SerialMaster.class);
private MessageControl conn;
/**
* <p>Constructor for AsciiMaster.</p>
*
*
* Default to validating the slave id in responses
*
*
* @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
*/
public AsciiMaster(SerialPortWrapper wrapper) {
super(wrapper, true);
}
/**
*
*
* @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
* @param validateResponse - confirm that requested slave id is the same in the response
*/
@ -64,21 +67,26 @@ public class AsciiMaster extends SerialMaster {
/** {@inheritDoc} */
@Override
public void init() throws ModbusInitException {
super.init();
AsciiMessageParser asciiMessageParser = new AsciiMessageParser(true);
conn = getMessageControl();
try {
conn.start(transport, asciiMessageParser, null, new SerialWaitingRoomKeyFactory());
if (getePoll() == null)
((StreamTransport) transport).start("Modbus ASCII master");
openConnection(null);
}
catch (IOException e) {
catch (Exception e) {
throw new ModbusInitException(e);
}
initialized = true;
}
@Override
protected void openConnection(MessageControl toClose) throws Exception {
super.openConnection(toClose);
AsciiMessageParser asciiMessageParser = new AsciiMessageParser(true);
this.conn = getMessageControl();
this.conn.start(transport, asciiMessageParser, null, new SerialWaitingRoomKeyFactory());
if (getePoll() == null) {
((StreamTransport) transport).start("Modbus ASCII master");
}
}
/** {@inheritDoc} */
@Override
public void destroy() {
@ -102,7 +110,18 @@ public class AsciiMaster extends SerialMaster {
return asciiResponse.getModbusResponse();
}
catch (Exception e) {
throw new ModbusTransportException(e, request.getSlaveId());
try {
LOG.debug("Connection may have been reset. Attempting to re-open.");
openConnection(conn);
asciiResponse = (AsciiMessageResponse) conn.send(asciiRequest);
if (asciiResponse == null)
return null;
return asciiResponse.getModbusResponse();
}catch(Exception e2) {
closeConnection(conn);
LOG.debug("Failed to re-connect", e);
throw new ModbusTransportException(e2, request.getSlaveId());
}
}
}
}

@ -5,12 +5,12 @@
*
* Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
* @author Matthew Lohbihler
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@ -20,7 +20,8 @@
*/
package com.serotonin.modbus4j.serial.rtu;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
@ -40,24 +41,26 @@ import com.serotonin.modbus4j.sero.messaging.StreamTransport;
* @version 5.0.0
*/
public class RtuMaster extends SerialMaster {
private final Log LOG = LogFactory.getLog(RtuMaster.class);
// Runtime fields.
private MessageControl conn;
/**
* <p>Constructor for RtuMaster.</p>
*
*
* Default to validating the slave id in responses
*
*
* @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
*/
public RtuMaster(SerialPortWrapper wrapper) {
super(wrapper, true);
}
/**
* <p>Constructor for RtuMaster.</p>
*
*
* @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
* @param validateResponse - confirm that requested slave id is the same in the response
*/
@ -68,21 +71,28 @@ public class RtuMaster extends SerialMaster {
/** {@inheritDoc} */
@Override
public void init() throws ModbusInitException {
super.init();
RtuMessageParser rtuMessageParser = new RtuMessageParser(true);
conn = getMessageControl();
try {
conn.start(transport, rtuMessageParser, null, new SerialWaitingRoomKeyFactory());
if (getePoll() == null)
((StreamTransport) transport).start("Modbus RTU master");
openConnection(null);
}
catch (IOException e) {
catch (Exception e) {
throw new ModbusInitException(e);
}
initialized = true;
}
/** {@inheritDoc} */
@Override
protected void openConnection(MessageControl toClose) throws Exception {
super.openConnection(toClose);
RtuMessageParser rtuMessageParser = new RtuMessageParser(true);
this.conn = getMessageControl();
this.conn.start(transport, rtuMessageParser, null, new SerialWaitingRoomKeyFactory());
if (getePoll() == null) {
((StreamTransport) transport).start("Modbus RTU master");
}
}
/** {@inheritDoc} */
@Override
public void destroy() {
@ -106,13 +116,21 @@ public class RtuMaster extends SerialMaster {
return rtuResponse.getModbusResponse();
}
catch (Exception e) {
throw new ModbusTransportException(e, request.getSlaveId());
}
finally {
try {
LOG.debug("Connection may have been reset. Attempting to re-open.");
openConnection(conn);
rtuResponse = (RtuMessageResponse) conn.send(rtuRequest);
if (rtuResponse == null)
return null;
return rtuResponse.getModbusResponse();
}catch(Exception e2) {
closeConnection(conn);
LOG.debug("Failed to re-connect", e);
throw new ModbusTransportException(e2, request.getSlaveId());
}
}
}
/**
* RTU Spec:
* For baud greater than 19200
@ -130,7 +148,7 @@ public class RtuMaster extends SerialMaster {
return 1750000l; //Nanoseconds
}
else {
float charTime = computeCharacterTime(wrapper);
float charTime = computeCharacterTime(wrapper);
return (long) (charTime * 3.5f);
}
}
@ -152,12 +170,12 @@ public class RtuMaster extends SerialMaster {
return 750000l; //Nanoseconds
}
else {
float charTime = computeCharacterTime(wrapper);
float charTime = computeCharacterTime(wrapper);
return (long) (charTime * 1.5f);
}
}
/**
* Compute the time it takes to transmit 1 character with
* the provided Serial Parameters.
@ -178,18 +196,18 @@ public class RtuMaster extends SerialMaster {
//Compute the char size
float charBits = wrapper.getDataBits();
switch (wrapper.getStopBits()) {
case 1:
//Strangely this results in 0 stop bits.. in JSSC code
break;
case 2:
charBits += 2f;
break;
case 3:
//1.5 stop bits
charBits += 1.5f;
break;
default:
throw new ShouldNeverHappenException("Unknown stop bit size: " + wrapper.getStopBits());
case 1:
//Strangely this results in 0 stop bits.. in JSSC code
break;
case 2:
charBits += 2f;
break;
case 3:
//1.5 stop bits
charBits += 1.5f;
break;
default:
throw new ShouldNeverHappenException("Unknown stop bit size: " + wrapper.getStopBits());
}
if (wrapper.getParity() > 0)

@ -4,8 +4,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.serotonin.modbus4j.sero.epoll.Modbus4JInputStreamCallback;
import com.serotonin.modbus4j.sero.epoll.InputStreamEPollWrapper;
import com.serotonin.modbus4j.sero.epoll.Modbus4JInputStreamCallback;
/**
* First, instatiate with the streams and epoll. Then add a data consumer, or create a message control and pass this as
@ -34,20 +34,25 @@ public class EpollStreamTransport implements Transport {
}
/** {@inheritDoc} */
@Override
public void setConsumer(final DataConsumer consumer) {
epoll.add(in, new Modbus4JInputStreamCallback() {
@Override
public void terminated() {
removeConsumer();
}
@Override
public void ioException(IOException e) {
consumer.handleIOException(e);
}
@Override
public void input(byte[] buf, int len) {
consumer.data(buf, len);
}
@Override
public void closed() {
removeConsumer();
}
@ -57,6 +62,7 @@ public class EpollStreamTransport implements Transport {
/**
* <p>removeConsumer.</p>
*/
@Override
public void removeConsumer() {
epoll.remove(in);
}
@ -67,12 +73,14 @@ public class EpollStreamTransport implements Transport {
* @param data an array of {@link byte} objects.
* @throws java.io.IOException if any.
*/
@Override
public void write(byte[] data) throws IOException {
out.write(data);
out.flush();
}
/** {@inheritDoc} */
@Override
public void write(byte[] data, int len) throws IOException {
out.write(data, 0, len);
out.flush();

Loading…
Cancel
Save