/*******************************************************************************
* Copyright (c) 2011, 2016 Eurotech and/or its affiliates
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eurotech
* Red Hat Inc - Fix build warnings
*******************************************************************************/
package org.eclipse.kura.protocol.modbus;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.List;
import java.util.Properties;
import org.eclipse.kura.KuraConnectionStatus;
import org.eclipse.kura.comm.CommConnection;
import org.eclipse.kura.comm.CommURI;
import org.eclipse.kura.usb.UsbService;
import org.eclipse.kura.usb.UsbTtyDevice;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.io.ConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Modbus protocol implements a subset of the Modbus standard command set.
* It also provides for the extension of some data typing to allow register
* pairings to hold 32 bit data (see the configureDataMap for more detail).
* <p>
* The protocol supports RTU and ASCII mode operation.
*
*/
public class ModbusProtocolDevice implements ModbusProtocolDeviceService {
private static final Logger s_logger = LoggerFactory.getLogger(ModbusProtocolDevice.class);
private static final String GPIO_BASE_PATH = "/sys/class/gpio/";
private static final String GPIO_EXPORT_PATH = "export";
private ConnectionFactory m_connectionFactory;
private UsbService m_usbService;
static final String PROTOCOL_NAME = "modbus";
public static final String PROTOCOL_CONNECTION_TYPE_SERIAL = "SERIAL";
public static final String SERIAL_232 = "RS232";
public static final String SERIAL_485 = "RS485";
public static final String PROTOCOL_CONNECTION_TYPE_ETHER_TCP = "ETHERTCP";
private int m_respTout;
private int m_txMode;
private boolean m_serial485 = false;
private boolean m_connConfigd = false;
private boolean m_protConfigd = false;
private String m_connType = null;
private Communicate m_comm;
private Properties m_modbusProperties = null;
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.m_connectionFactory = connectionFactory;
}
public void unsetConnectionFactory(ConnectionFactory connectionFactory) {
this.m_connectionFactory = null;
}
public void setUsbService(UsbService usbService) {
this.m_usbService = usbService;
}
public void unsetUsbService(UsbService usbService) {
this.m_usbService = null;
}
private boolean serialPortExists() {
if (this.m_modbusProperties == null) {
return false;
}
String portName = this.m_modbusProperties.getProperty("port");
if (portName != null) {
if (portName.contains("/dev/")) {
File f = new File(portName);
if (f.exists()) {
return true;
}
} else {
List<UsbTtyDevice> utd = this.m_usbService.getUsbTtyDevices();
if (utd != null) {
for (UsbTtyDevice u : utd) {
if (portName.equals(u.getUsbPort())) {
// replace device number with tty
portName = u.getDeviceNode();
this.m_modbusProperties.setProperty("port", portName);
return true;
}
}
}
}
}
return false;
}
// ----------------------------------------------------------------
//
// Activation APIs
//
// ----------------------------------------------------------------
protected void activate(ComponentContext componentContext) {
s_logger.info("activate...");
}
protected void deactivate(ComponentContext componentContext) {
s_logger.info("deactivate...");
try {
disconnect();
} catch (ModbusProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* two connection types are available, PROTOCOL_CONNECTION_TYPE_SERIAL (SERIAL MODE 232 or 485)
* and
* PROTOCOL_CONNECTION_TYPE_ETHER_TCP. The required properties for the
* connection will vary with each connection.
* <p>
* <h3>PROTOCOL_CONNECTION_TYPE_SERIAL</h3>
* see {@link org.eclipse.kura.comm.CommConnection CommConnection} package for more
* detail.
* <table summary="" border="1">
* <tr>
* <th>Key</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>connectionType</td>
* <td>"SERIAL" (from PROTOCOL_CONNECTION_TYPE_SERIAL). This parameter
* indicates the connection type for the configuration. See
* {@link org.eclipse.kura.comm.CommConnection CommConnection} for more details on serial
* port configuration.
* </tr>
* <tr>
* <td>port</td>
* <td>the actual device port, such as "/dev/ttyUSB0" in linux</td>
* </tr>
* <tr>
* <td>serialMode</td>
* <td>SERIAL_232 or SERIAL_485</td>
* </tr>
* <tr>
* <td>baudRate</td>
* <td>baud rate to be configured for the port</td>
* </tr>
* <tr>
* <td>stopBits</td>
* <td>number of stop bits to be configured for the port</td>
* </tr>
* <tr>
* <td>parity</td>
* <td>parity mode to be configured for the port</td>
* </tr>
* <tr>
* <td>bitsPerWord</td>
* <td>only RTU mode supported, bitsPerWord must be 8</td>
* </tr>
* </table>
* <p>
* <h3>PROTOCOL_CONNECTION_TYPE_ETHER_TCP</h3>
* The Ethernet mode merely opens a socket and sends the full RTU mode
* Modbus packet over that socket connection and expects to receive a full
* RTU mode Modbus response, including the CRC bytes.
* <table summary="" border="1">
* <tr>
* <th>Key</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>connectionType</td>
* <td>"ETHERTCP" (from PROTOCOL_CONNECTION_TYPE_ETHER_TCP). This parameter
* indicates the connection type for the configurator.
* </tr>
* <tr>
* <td>ipAddress</td>
* <td>the 4 octet IP address of the field device (xxx.xxx.xxx.xxx)</td>
* </tr>
* <tr>
* <td>port</td>
* <td>port on the field device to connect to</td>
* </tr>
* </table>
*/
@Override
public void configureConnection(Properties connectionConfig) throws ModbusProtocolException {
if ((this.m_connType = connectionConfig.getProperty("connectionType")) == null) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
this.m_modbusProperties = connectionConfig;
String txMode;
String respTimeout;
if (this.m_protConfigd || (txMode = connectionConfig.getProperty("transmissionMode")) == null
|| (respTimeout = connectionConfig.getProperty("respTimeout")) == null) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
if (txMode.equals(ModbusTransmissionMode.RTU)) {
this.m_txMode = ModbusTransmissionMode.RTU_MODE;
} else if (txMode.equals(ModbusTransmissionMode.ASCII)) {
this.m_txMode = ModbusTransmissionMode.ASCII_MODE;
} else {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
this.m_respTout = Integer.parseInt(respTimeout);
if (this.m_respTout < 0) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
this.m_protConfigd = true;
if (this.m_connConfigd) {
this.m_comm.disconnect();
this.m_comm = null;
this.m_connConfigd = false;
}
if (this.m_connType.equals(PROTOCOL_CONNECTION_TYPE_SERIAL)) {
if (!serialPortExists()) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_AVAILABLE);
}
this.m_comm = new SerialCommunicate(this.m_connectionFactory, connectionConfig);
} else if (this.m_connType.equals(PROTOCOL_CONNECTION_TYPE_ETHER_TCP)) {
this.m_comm = new EthernetCommunicate(this.m_connectionFactory, connectionConfig);
} else {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
this.m_connConfigd = true;
}
/**
* get the name "modbus" for this protocol
*
* @return "modbus"
*/
@Override
public String getProtocolName() {
return "modbus";
}
@Override
public void connect() throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
this.m_comm.connect();
}
@Override
public void disconnect() throws ModbusProtocolException {
if (this.m_connConfigd) {
this.m_comm.disconnect();
this.m_comm = null;
this.m_connConfigd = false;
s_logger.info("Serial comm disconnected");
}
this.m_protConfigd = false;
}
@Override
public int getConnectStatus() {
if (!this.m_connConfigd) {
return KuraConnectionStatus.NEVERCONNECTED;
}
return this.m_comm.getConnectStatus();
}
/**
* The only constructor must be the configuration mechanism
*/
abstract private class Communicate {
abstract public void connect();
abstract public void disconnect() throws ModbusProtocolException;
abstract public int getConnectStatus();
abstract public byte[] msgTransaction(byte[] msg) throws ModbusProtocolException;
}
/**
* Installation of a serial connection to communicate, using javax.comm.SerialPort
* <p>
* <table summary="" border="1">
* <tr>
* <th>Key</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>port</td>
* <td>the actual device port, such as "/dev/ttyUSB0" in linux</td>
* </tr>
* <tr>
* <td>serialMode</td>
* <td>SERIAL_232 or SERIAL_485</td>
* </tr>
* <tr>
* <td>baudRate</td>
* <td>baud rate to be configured for the port</td>
* </tr>
* <tr>
* <td>stopBits</td>
* <td>number of stop bits to be configured for the port</td>
* </tr>
* <tr>
* <td>parity</td>
* <td>parity mode to be configured for the port</td>
* </tr>
* <tr>
* <td>bitsPerWord</td>
* <td>only RTU mode supported, bitsPerWord must be 8</td>
* </tr>
* </table>
* see {@link org.eclipse.kura.comm.CommConnection CommConnection} package for more
* detail.
*/
private final class SerialCommunicate extends Communicate {
InputStream in;
OutputStream out;
CommConnection conn = null;
FileWriter gpioModeSwitch;
public SerialCommunicate(ConnectionFactory connFactory, Properties connectionConfig)
throws ModbusProtocolException {
s_logger.debug("Configure serial connection");
String sPort;
String sBaud;
String sStop;
String sParity;
String sBits;
String gpioSwitchPin = null;
String gpioRsModePin = null;
if ((sPort = connectionConfig.getProperty("port")) == null
|| (sBaud = connectionConfig.getProperty("baudRate")) == null
|| (sStop = connectionConfig.getProperty("stopBits")) == null
|| (sParity = connectionConfig.getProperty("parity")) == null
|| (sBits = connectionConfig.getProperty("bitsPerWord")) == null) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
int baud = Integer.valueOf(sBaud).intValue();
int stop = Integer.valueOf(sStop).intValue();
int parity = Integer.valueOf(sParity).intValue();
int bits = Integer.valueOf(sBits).intValue();
if (connectionConfig.getProperty("serialMode") != null) {
ModbusProtocolDevice.this.m_serial485 = connectionConfig.getProperty("serialMode") == SERIAL_485;
if (ModbusProtocolDevice.this.m_serial485) {
if ((gpioSwitchPin = connectionConfig.getProperty("serialGPIOswitch")) == null
|| (gpioRsModePin = connectionConfig.getProperty("serialGPIOrsmode")) == null) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
}
}
String uri = new CommURI.Builder(sPort).withBaudRate(baud).withDataBits(bits).withStopBits(stop)
.withParity(parity).withTimeout(2000).build().toString();
try {
this.conn = (CommConnection) connFactory.createConnection(uri, 1, false);
} catch (IOException e1) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.CONNECTION_FAILURE, e1);
}
if (ModbusProtocolDevice.this.m_serial485) {
setupSerialGpio(gpioRsModePin, gpioSwitchPin);
}
// get the streams
try {
this.in = this.conn.openInputStream();
this.out = this.conn.openOutputStream();
if (ModbusProtocolDevice.this.m_serial485) {
this.gpioModeSwitch = new FileWriter(new File("/sys/class/gpio/" + gpioSwitchPin + "/value"));
}
} catch (Exception e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.CONNECTION_FAILURE, e);
}
}
/**
* Initializes the GPIO Serial Ports to RS485 mode with output direction.
* Communication can then be switched to RX or TX.
*
* @param gpioRsMode
* @param gpioSwitch
* @throws ModbusProtocolException
*/
private void setupSerialGpio(String gpioRsMode, String gpioSwitch) throws ModbusProtocolException {
final String[] requiredGpio = { gpioRsMode, /* port 3 RS mode */
gpioSwitch /* port TX<->RX switch */
};
for (String gpio : requiredGpio) {
File gpioFile = new File(GPIO_BASE_PATH + gpio);
if (!gpioFile.exists()) {
// # Pin is not exported, so do it now
FileWriter fwExport = null;
try {
fwExport = new FileWriter(new File(GPIO_BASE_PATH + GPIO_EXPORT_PATH));
// write only the PIN number
fwExport.write(gpio.replace("gpio", ""));
fwExport.flush();
s_logger.debug("Exported GPIO {}", gpio);
} catch (IOException e) {
s_logger.error("Exporting Error", e);
} finally {
if (fwExport != null) {
try {
fwExport.close();
} catch (IOException e) {
s_logger.error("Error closing export file", e);
}
}
}
// wait a little after exporting
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// After exporting the pin, set the direction to "out"
FileWriter fwDirection = null;
try {
fwDirection = new FileWriter(new File(GPIO_BASE_PATH + gpio + "/direction"));
fwDirection.write("out");
fwDirection.flush();
s_logger.debug("Direction GPIO {}", gpio);
} catch (IOException e) {
s_logger.error("Direction Error", e);
} finally {
if (fwDirection != null) {
try {
fwDirection.close();
} catch (IOException e) {
s_logger.error("Error closing export file", e);
}
}
}
// Switch to RS485 mode
FileWriter fwValue = null;
try {
fwValue = new FileWriter(new File(GPIO_BASE_PATH + gpio + "/value"));
fwValue.write("1");
fwValue.flush();
s_logger.debug("Value GPIO {}", gpio);
} catch (IOException e) {
s_logger.error("Value Error", e);
} finally {
if (fwValue != null) {
try {
fwValue.close();
} catch (IOException e) {
s_logger.error("Error closing value file", e);
}
}
}
}
}
/**
* Sets the GPIO Serial port to Transmit mode. Data can be written to OutputStream.
*
* @throws InterruptedException
*/
private void switchTX() throws ModbusProtocolException {
try {
this.gpioModeSwitch.write("1");
this.gpioModeSwitch.flush();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.CONNECTION_FAILURE,
"switchTX interrupted");
}
} catch (IOException e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.CONNECTION_FAILURE,
"switchTX IOException " + e.getMessage());
}
}
/**
* Sets the GPIO Serial port to Receive mode. Data can be read from InputStream.
*
* @throws InterruptedException
*/
private void switchRX() throws ModbusProtocolException {
try {
this.gpioModeSwitch.write("0");
this.gpioModeSwitch.flush();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.CONNECTION_FAILURE,
"switchRX interrupted");
}
} catch (IOException e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.CONNECTION_FAILURE,
"switchRX IOException " + e.getMessage());
}
}
@Override
public void connect() {
/*
* always connected
*/
}
@Override
public void disconnect() throws ModbusProtocolException {
if (this.conn != null) {
try {
this.conn.close();
s_logger.debug("Serial connection closed");
} catch (IOException e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE, e.getMessage());
}
this.conn = null;
}
if (ModbusProtocolDevice.this.m_serial485) {
if (this.gpioModeSwitch != null) {
try {
this.gpioModeSwitch.close();
} catch (IOException e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE, e.getMessage());
}
}
}
}
@Override
public int getConnectStatus() {
return KuraConnectionStatus.CONNECTED;
}
private byte asciiLrcCalc(byte[] msg, int len) {
char[] ac = new char[2];
ac[0] = (char) msg[len - 4];
ac[1] = (char) msg[len - 3];
String s = new String(ac);
byte lrc = (byte) Integer.parseInt(s, 16);
return lrc;
}
private int binLrcCalc(byte[] msg) {
int llrc = 0;
for (byte element : msg) {
llrc += element & 0xff;
}
llrc = (llrc ^ 0xff) + 1;
// byte lrc=(byte)(llrc & 0x0ff);
return llrc;
}
/**
* convertCommandToAscii: convert a binary command into a standard Modbus
* ASCII frame
*/
private byte[] convertCommandToAscii(byte[] msg) {
int lrc = binLrcCalc(msg);
char[] hexArray = "0123456789ABCDEF".toCharArray();
byte[] ab = new byte[msg.length * 2 + 5];
ab[0] = ':';
int v;
for (int i = 0; i < msg.length; i++) {
v = msg[i] & 0xff;
ab[i * 2 + 1] = (byte) hexArray[v >>> 4];
ab[i * 2 + 2] = (byte) hexArray[v & 0x0f];
}
v = lrc & 0x0ff;
ab[ab.length - 4] = (byte) hexArray[v >>> 4];
ab[ab.length - 3] = (byte) hexArray[v & 0x0f];
ab[ab.length - 2] = 13;
ab[ab.length - 1] = 10;
return ab;
}
/**
* convertAsciiResponseToBin: convert a standard Modbus frame to
* byte array
*/
private byte[] convertAsciiResponseToBin(byte[] msg, int len) {
int l = (len - 5) / 2;
byte[] ab = new byte[l];
char[] ac = new char[2];
// String s=new String(msg);
for (int i = 0; i < l; i++) {
ac[0] = (char) msg[i * 2 + 1];
ac[1] = (char) msg[i * 2 + 2];
// String s=new String(ac);
ab[i] = (byte) Integer.parseInt(new String(ac), 16);
}
return ab;
}
/**
* msgTransaction must be called with a byte array having two extra
* bytes for the CRC. It will return a byte array of the response to the
* message. Validation will include checking the CRC and verifying the
* command matches.
*/
@Override
public byte[] msgTransaction(byte[] msg) throws ModbusProtocolException {
byte[] cmd = null;
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.RTU_MODE) {
cmd = new byte[msg.length + 2];
for (int i = 0; i < msg.length; i++) {
cmd[i] = msg[i];
}
// Add crc calculation to end of message
int crc = Crc16.getCrc16(msg, msg.length, 0x0ffff);
cmd[msg.length] = (byte) crc;
cmd[msg.length + 1] = (byte) (crc >> 8);
} else if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE) {
cmd = convertCommandToAscii(msg);
}
// Send the message
try {
synchronized (this.out) {
synchronized (this.in) {
// flush input
if (ModbusProtocolDevice.this.m_serial485) {
switchRX();
}
while (this.in.available() > 0) {
this.in.read();
}
// send all data
if (ModbusProtocolDevice.this.m_serial485) {
switchTX();
}
this.out.write(cmd, 0, cmd.length);
this.out.flush();
// outputStream.waitAllSent(respTout);
// wait for and process response
if (ModbusProtocolDevice.this.m_serial485) {
switchRX();
}
byte[] response = new byte[262]; // response buffer
int respIndex = 0;
int minimumLength = 5; // default minimum message length
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE) {
minimumLength = 11;
}
int timeOut = ModbusProtocolDevice.this.m_respTout;
for (int maxLoop = 0; maxLoop < 1000; maxLoop++) {
boolean endFrame = false;
// while (respIndex < minimumLength) {
while (!endFrame) {
long start = System.currentTimeMillis();
while (this.in.available() == 0) {
try {
Thread.sleep(5); // avoid a high cpu load
} catch (InterruptedException e) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Thread interrupted");
}
long elapsed = System.currentTimeMillis() - start;
if (elapsed > timeOut) {
String failMsg = "Recv timeout";
s_logger.warn(failMsg + " : " + elapsed + " minimumLength=" + minimumLength
+ " respIndex=" + respIndex);
throw new ModbusProtocolException(ModbusProtocolErrorCode.RESPONSE_TIMEOUT,
failMsg);
}
}
// address byte must match first
if (respIndex == 0) {
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE) {
if ((response[0] = (byte) this.in.read()) == ':') {
respIndex++;
}
} else {
if ((response[0] = (byte) this.in.read()) == msg[0]) {
respIndex++;
}
}
} else {
response[respIndex++] = (byte) this.in.read();
}
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.RTU_MODE) {
timeOut = 100; // move to character timeout
if (respIndex >= minimumLength) {
endFrame = true;
}
} else {
if (response[respIndex - 1] == 10 && response[respIndex - 2] == 13) {
endFrame = true;
}
}
}
// if ASCII mode convert response
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE) {
byte lrcRec = asciiLrcCalc(response, respIndex);
response = convertAsciiResponseToBin(response, respIndex);
byte lrcCalc = (byte) binLrcCalc(response);
if (lrcRec != lrcCalc) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Bad LRC");
}
}
// Check first for an Exception response
if ((response[1] & 0x80) == 0x80) {
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE
|| Crc16.getCrc16(response, 5, 0xffff) == 0) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Exception response = " + Byte.toString(response[2]));
}
} else {
// then check for a valid message
switch (response[1]) {
case ModbusFunctionCodes.FORCE_SINGLE_COIL:
case ModbusFunctionCodes.PRESET_SINGLE_REG:
case ModbusFunctionCodes.FORCE_MULTIPLE_COILS:
case ModbusFunctionCodes.PRESET_MULTIPLE_REGS:
if (respIndex < 8) {
// wait for more data
minimumLength = 8;
} else if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE
|| Crc16.getCrc16(response, 8, 0xffff) == 0) {
byte[] ret = new byte[6];
for (int i = 0; i < 6; i++) {
ret[i] = response[i];
}
return ret;
}
break;
case ModbusFunctionCodes.READ_COIL_STATUS:
case ModbusFunctionCodes.READ_INPUT_STATUS:
case ModbusFunctionCodes.READ_INPUT_REGS:
case ModbusFunctionCodes.READ_HOLDING_REGS:
int byteCnt;
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE) {
byteCnt = (response[2] & 0xff) + 3;
} else {
byteCnt = (response[2] & 0xff) + 5;
}
if (respIndex < byteCnt) {
// wait for more data
minimumLength = byteCnt;
} else if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.ASCII_MODE
|| Crc16.getCrc16(response, byteCnt, 0xffff) == 0) {
byte[] ret = new byte[byteCnt];
for (int i = 0; i < byteCnt; i++) {
ret[i] = response[i];
}
return ret;
}
}
}
/*
* if required length then must have failed, drop
* first byte and try again
*/
if (respIndex >= minimumLength) {
respIndex--;
for (int i = 0; i < respIndex; i++) {
response[i] = response[i + 1];
}
minimumLength = 5; // reset minimum length
}
}
}
}
} catch (IOException e) {
// e.printStackTrace();
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE, e.getMessage());
}
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Too much activity on recv line");
}
}
/**
* Installation of an ethernet connection to communicate
*/
private final class EthernetCommunicate extends Communicate {
InputStream inputStream;
OutputStream outputStream;
Socket socket;
int port;
String ipAddress;
boolean connected = false;
public EthernetCommunicate(ConnectionFactory connFactory, Properties connectionConfig)
throws ModbusProtocolException {
s_logger.debug("Configure TCP connection");
String sPort;
if ((sPort = connectionConfig.getProperty("port")) == null
|| (this.ipAddress = connectionConfig.getProperty("ipAddress")) == null) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_CONFIGURATION);
}
this.port = Integer.valueOf(sPort).intValue();
ModbusProtocolDevice.this.m_connConfigd = true;
this.socket = new Socket();
}
@Override
public void connect() {
if (!ModbusProtocolDevice.this.m_connConfigd) {
s_logger.error("Can't connect, port not configured");
} else {
if (!this.connected) {
try {
this.socket = new Socket(this.ipAddress, this.port);
try {
this.inputStream = this.socket.getInputStream();
this.outputStream = this.socket.getOutputStream();
this.connected = true;
s_logger.info("TCP connected");
} catch (IOException e) {
disconnect();
s_logger.error("Failed to get socket streams: " + e);
}
} catch (IOException e) {
s_logger.error("Failed to connect to remote: " + e);
}
}
}
}
@Override
public void disconnect() {
if (ModbusProtocolDevice.this.m_connConfigd) {
if (this.connected) {
try {
if (!this.socket.isInputShutdown()) {
this.socket.shutdownInput();
}
if (!this.socket.isOutputShutdown()) {
this.socket.shutdownOutput();
}
this.socket.close();
} catch (IOException eClose) {
s_logger.error("Error closing TCP: " + eClose);
}
this.inputStream = null;
this.outputStream = null;
this.connected = false;
this.socket = null;
}
}
}
@Override
public int getConnectStatus() {
if (this.connected) {
return KuraConnectionStatus.CONNECTED;
} else if (ModbusProtocolDevice.this.m_connConfigd) {
return KuraConnectionStatus.DISCONNECTED;
} else {
return KuraConnectionStatus.NEVERCONNECTED;
}
}
@Override
public byte[] msgTransaction(byte[] msg) throws ModbusProtocolException {
byte[] cmd = null;
if (ModbusProtocolDevice.this.m_txMode == ModbusTransmissionMode.RTU_MODE) {
cmd = new byte[msg.length + 2];
for (int i = 0; i < msg.length; i++) {
cmd[i] = msg[i];
}
// Add crc calculation to end of message
int crc = Crc16.getCrc16(msg, msg.length, 0x0ffff);
cmd[msg.length] = (byte) crc;
cmd[msg.length + 1] = (byte) (crc >> 8);
} else {
throw new ModbusProtocolException(ModbusProtocolErrorCode.METHOD_NOT_SUPPORTED,
"Only RTU over TCP/IP supported");
}
// Check connection status and connect
connect();
if (!this.connected) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Cannot transact on closed socket");
}
// Send the message
try {
// flush input
while (this.inputStream.available() > 0) {
this.inputStream.read();
}
// send all data
this.outputStream.write(cmd, 0, cmd.length);
this.outputStream.flush();
} catch (IOException e) {
// Assume this means the socket is closed...make sure it is
s_logger.error("Socket disconnect in send: " + e);
disconnect();
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Send failure: " + e.getMessage());
}
// wait for and process response
byte[] response = new byte[262]; // response buffer
int respIndex = 0;
int minimumLength = 5; // default minimum message length
while (true) {
while (respIndex < minimumLength) {
try {
this.socket.setSoTimeout(ModbusProtocolDevice.this.m_respTout);
int resp = this.inputStream.read(response, respIndex, minimumLength - respIndex);
if (resp > 0) {
respIndex += resp;
} else {
s_logger.error("Socket disconnect in recv");
disconnect();
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Recv failure");
}
} catch (SocketTimeoutException e) {
String failMsg = "Recv timeout";
s_logger.warn(failMsg);
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE, failMsg);
} catch (IOException e) {
s_logger.error("Socket disconnect in recv: " + e);
disconnect();
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE, "Recv failure");
}
}
// Check first for an Exception response
if ((response[1] & 0x80) == 0x80) {
if (Crc16.getCrc16(response, 5, 0xffff) == 0) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE,
"Resp exception = " + Byte.toString(response[2]));
}
} else {
// then check for a valid message
switch (response[1]) {
case ModbusFunctionCodes.FORCE_SINGLE_COIL:
case ModbusFunctionCodes.PRESET_SINGLE_REG:
case ModbusFunctionCodes.FORCE_MULTIPLE_COILS:
case ModbusFunctionCodes.PRESET_MULTIPLE_REGS:
if (respIndex < 8) {
// wait for more data
minimumLength = 8;
} else if (Crc16.getCrc16(response, 8, 0xffff) == 0) {
byte[] ret = new byte[8];
for (int i = 0; i < 8; i++) {
ret[i] = response[i];
}
return ret;
}
break;
case ModbusFunctionCodes.READ_COIL_STATUS:
case ModbusFunctionCodes.READ_INPUT_STATUS:
case ModbusFunctionCodes.READ_INPUT_REGS:
case ModbusFunctionCodes.READ_HOLDING_REGS:
int byteCnt = (response[2] & 0xff) + 5;
if (respIndex < byteCnt) {
// wait for more data
minimumLength = byteCnt;
} else if (Crc16.getCrc16(response, byteCnt, 0xffff) == 0) {
byte[] ret = new byte[byteCnt];
for (int i = 0; i < byteCnt; i++) {
ret[i] = response[i];
}
return ret;
}
}
}
/*
* if required length then must have failed, drop first byte and
* try again
*/
if (respIndex >= minimumLength) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.TRANSACTION_FAILURE, "Error in recv");
}
}
}
}
@Override
public boolean[] readCoils(int unitAddr, int dataAddress, int count) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
boolean[] ret = new boolean[count];
int index = 0;
byte[] resp;
/*
* construct the command issue and get results
*/
byte[] cmd = new byte[6];
cmd[0] = (byte) unitAddr;
cmd[1] = (byte) ModbusFunctionCodes.READ_COIL_STATUS;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = (byte) (count / 256);
cmd[5] = (byte) (count % 256);
/*
* send the message and get the response
*/
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response (address & CRC already confirmed)
*/
if (resp.length < 3 || resp.length < (resp[2] & 0xff) + 3) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
if ((resp[2] & 0xff) == (count + 7) / 8) {
byte mask = 1;
int byteOffset = 3;
for (int j = 0; j < count; j++, index++) {
// get this point's value
if ((resp[byteOffset] & mask) == mask) {
ret[index] = true;
} else {
ret[index] = false;
}
// advance the mask and offset index
if ((mask <<= 1) == 0) {
mask = 1;
byteOffset++;
}
}
} else {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_ADDRESS);
}
return ret;
}
@Override
public boolean[] readDiscreteInputs(int unitAddr, int dataAddress, int count) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
boolean[] ret = new boolean[count];
int index = 0;
byte[] resp;
/*
* construct the command issue and get results
*/
byte[] cmd = new byte[6];
cmd[0] = (byte) unitAddr;
cmd[1] = (byte) ModbusFunctionCodes.READ_INPUT_STATUS;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = (byte) (count / 256);
cmd[5] = (byte) (count % 256);
/*
* send the message and get the response
*/
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response (address & CRC already confirmed)
*/
if (resp.length < 3 || resp.length < (resp[2] & 0xff) + 3) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
if ((resp[2] & 0xff) == (count + 7) / 8) {
byte mask = 1;
int byteOffset = 3;
for (int j = 0; j < count; j++, index++) {
// get this point's value
if ((resp[byteOffset] & mask) == mask) {
ret[index] = true;
} else {
ret[index] = false;
}
// advance the mask and offset index
if ((mask <<= 1) == 0) {
mask = 1;
byteOffset++;
}
}
} else {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_ADDRESS);
}
return ret;
}
@Override
public void writeSingleCoil(int unitAddr, int dataAddress, boolean data) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
byte[] resp;
byte[] cmd = new byte[6];
cmd[0] = (byte) unitAddr;
cmd[1] = ModbusFunctionCodes.FORCE_SINGLE_COIL;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = data == true ? (byte) 0xff : (byte) 0;
cmd[5] = 0;
/*
* send the message and get the response
*/
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response
*/
if (resp.length < 6) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
for (int i = 0; i < 6; i++) {
if (cmd[i] != resp[i]) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
}
}
@Override
public void writeMultipleCoils(int unitAddr, int dataAddress, boolean[] data) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
/*
* write multiple boolean values
*/
int localCnt = data.length;
int index = 0;
byte[] resp;
/*
* construct the command, issue and verify response
*/
int dataLength = (localCnt + 7) / 8;
byte[] cmd = new byte[dataLength + 7];
cmd[0] = (byte) unitAddr;
cmd[1] = ModbusFunctionCodes.FORCE_MULTIPLE_COILS;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = (byte) (localCnt / 256);
cmd[5] = (byte) (localCnt % 256);
cmd[6] = (byte) dataLength;
// put the data on the command
byte mask = 1;
int byteOffset = 7;
cmd[byteOffset] = 0;
for (int j = 0; j < localCnt; j++, index++) {
// get this point's value
if (data[index]) {
cmd[byteOffset] += mask;
}
// advance the mask and offset index
if ((mask <<= 1) == 0) {
mask = 1;
byteOffset++;
cmd[byteOffset] = 0;
}
}
/*
* send the message and get the response
*/
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response
*/
if (resp.length < 6) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
for (int j = 0; j < 6; j++) {
if (cmd[j] != resp[j]) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
}
}
@Override
public int[] readHoldingRegisters(int unitAddr, int dataAddress, int count) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
int[] ret = new int[count];
int index = 0;
byte[] resp;
/*
* construct the command issue and get results, putting the results
* away at index and then incrementing index for the next command
*/
byte[] cmd = new byte[6];
cmd[0] = (byte) unitAddr;
cmd[1] = (byte) ModbusFunctionCodes.READ_HOLDING_REGS;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = 0;
cmd[5] = (byte) count;
/*
* send the message and get the response
*/
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response (address & CRC already confirmed)
*/
if (resp.length < 3 || resp.length < (resp[2] & 0xff) + 3) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
if ((resp[2] & 0xff) == count * 2) {
int byteOffset = 3;
for (int j = 0; j < count; j++, index++) {
int val = resp[byteOffset + ModbusDataOrder.MODBUS_WORD_ORDER_BIG_ENDIAN.charAt(0) - '1'] & 0xff;
val <<= 8;
val += resp[byteOffset + ModbusDataOrder.MODBUS_WORD_ORDER_BIG_ENDIAN.charAt(1) - '1'] & 0xff;
ret[index] = val;
byteOffset += 2;
}
} else {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_ADDRESS);
}
return ret;
}
@Override
public int[] readInputRegisters(int unitAddr, int dataAddress, int count) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
int[] ret = new int[count];
int index = 0;
byte[] resp;
/*
* construct the command issue and get results, putting the results
* away at index and then incrementing index for the next command
*/
byte[] cmd = new byte[6];
cmd[0] = (byte) unitAddr;
cmd[1] = (byte) ModbusFunctionCodes.READ_INPUT_REGS;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = 0;
cmd[5] = (byte) count;
/*
* send the message and get the response
*/
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response (address & CRC already confirmed)
*/
if (resp.length < 3 || resp.length < (resp[2] & 0xff) + 3) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
if ((resp[2] & 0xff) == count * 2) {
int byteOffset = 3;
for (int j = 0; j < count; j++, index++) {
int val = resp[byteOffset + ModbusDataOrder.MODBUS_WORD_ORDER_BIG_ENDIAN.charAt(0) - '1'] & 0xff;
val <<= 8;
val += resp[byteOffset + ModbusDataOrder.MODBUS_WORD_ORDER_BIG_ENDIAN.charAt(1) - '1'] & 0xff;
ret[index] = val;
byteOffset += 2;
}
} else {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_ADDRESS);
}
return ret;
}
@Override
public void writeSingleRegister(int unitAddr, int dataAddress, int data) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
byte[] cmd = new byte[6];
cmd[0] = (byte) unitAddr;
cmd[1] = ModbusFunctionCodes.PRESET_SINGLE_REG;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = (byte) (data >> 8);
cmd[5] = (byte) data;
/*
* send the message and get the response
*/
byte[] resp = this.m_comm.msgTransaction(cmd);
/*
* process the response
*/
if (resp.length < 6) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
for (int i = 0; i < 6; i++) {
if (cmd[i] != resp[i]) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
}
}
@Override
public void writeMultipleRegister(int unitAddr, int dataAddress, int[] data) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
int localCnt = data.length;
/*
* construct the command, issue and verify response
*/
int dataLength = localCnt * 2;
byte[] cmd = new byte[dataLength + 7];
cmd[0] = (byte) unitAddr;
cmd[1] = ModbusFunctionCodes.PRESET_MULTIPLE_REGS;
cmd[2] = (byte) (dataAddress / 256);
cmd[3] = (byte) (dataAddress % 256);
cmd[4] = (byte) (localCnt / 256);
cmd[5] = (byte) (localCnt % 256);
cmd[6] = (byte) dataLength;
// put the data on the command
int byteOffset = 7;
int index = 0;
for (int j = 0; j < localCnt; j++, index++) {
cmd[byteOffset + ModbusDataOrder.MODBUS_WORD_ORDER_BIG_ENDIAN.charAt(0) - '1'] = (byte) (data[index] >> 8);
cmd[byteOffset + ModbusDataOrder.MODBUS_WORD_ORDER_BIG_ENDIAN.charAt(1) - '1'] = (byte) data[index];
byteOffset += 2;
}
/*
* send the message and get the response
*/
byte[] resp = this.m_comm.msgTransaction(cmd);
/*
* process the response
*/
if (resp.length < 6) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
for (int j = 0; j < 6; j++) {
if (cmd[j] != resp[j]) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
}
}
@Override
public boolean[] readExceptionStatus(int unitAddr) throws ModbusProtocolException {
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
boolean[] ret = new boolean[8];
int index = 0;
byte[] resp;
/*
* construct the command issue and get results
*/
byte[] cmd = new byte[2];
cmd[0] = (byte) unitAddr;
cmd[1] = (byte) ModbusFunctionCodes.READ_EXCEPTION_STATUS;
/*
* send the message and get the response
*/
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response (address & CRC already confirmed)
*/
if (resp.length < 3) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
byte mask = 1;
for (int j = 0; j < 8; j++, index++) {
// get this point's value
if ((resp[2] & mask) == mask) {
ret[index] = true;
} else {
ret[index] = false;
}
// advance the mask and offset index
if ((mask <<= 1) == 0) {
mask = 1;
}
}
return ret;
}
@Override
public ModbusCommEvent getCommEventCounter(int unitAddr) throws ModbusProtocolException {
ModbusCommEvent mce = new ModbusCommEvent();
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
/*
* construct the command issue and get results
*/
byte[] cmd = new byte[2];
cmd[0] = (byte) unitAddr;
cmd[1] = (byte) ModbusFunctionCodes.GET_COMM_EVENT_COUNTER;
/*
* send the message and get the response
*/
byte[] resp;
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response (address & CRC already confirmed)
*/
if (resp.length < 6) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
int val = resp[2] & 0xff;
val <<= 8;
val += resp[3] & 0xff;
mce.setStatus(val);
val = resp[4] & 0xff;
val <<= 8;
val += resp[5] & 0xff;
mce.setEventCount(val);
return mce;
}
@Override
public ModbusCommEvent getCommEventLog(int unitAddr) throws ModbusProtocolException {
ModbusCommEvent mce = new ModbusCommEvent();
if (!this.m_connConfigd) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.NOT_CONNECTED);
}
/*
* construct the command issue and get results
*/
byte[] cmd = new byte[2];
cmd[0] = (byte) unitAddr;
cmd[1] = (byte) ModbusFunctionCodes.GET_COMM_EVENT_LOG;
/*
* send the message and get the response
*/
byte[] resp;
resp = this.m_comm.msgTransaction(cmd);
/*
* process the response (address & CRC already confirmed)
*/
if (resp.length < (resp[2] & 0xff) + 3 || (resp[2] & 0xff) > 64 + 7) {
throw new ModbusProtocolException(ModbusProtocolErrorCode.INVALID_DATA_TYPE);
}
int val = resp[3] & 0xff;
val <<= 8;
val += resp[4] & 0xff;
mce.setStatus(val);
val = resp[5] & 0xff;
val <<= 8;
val += resp[6] & 0xff;
mce.setEventCount(val);
val = resp[7] & 0xff;
val <<= 8;
val += resp[8] & 0xff;
mce.setMessageCount(val);
int count = (resp[2] & 0xff) - 4;
int[] events = new int[count];
for (int j = 0; j < count; j++) {
int bval = resp[9 + j] & 0xff;
events[j] = bval;
}
mce.setEvents(events);
return mce;
}
}