package org.openmuc.framework.driver.modbus.rtutcp.bonino;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.ModbusIOException;
import net.wimpi.modbus.io.BytesInputStream;
import net.wimpi.modbus.io.BytesOutputStream;
import net.wimpi.modbus.io.ModbusTransport;
import net.wimpi.modbus.msg.ModbusMessage;
import net.wimpi.modbus.msg.ModbusRequest;
import net.wimpi.modbus.msg.ModbusResponse;
import net.wimpi.modbus.util.ModbusUtil;
/**
* @author bonino
*
* https://github.com/dog-gateway/jamod-rtu-over-tcp
*
*/
public class ModbusRTUTCPTransport implements ModbusTransport {
public static final String logId = "[ModbusRTUTCPTransport]: ";
// The input stream from which reading the Modbus frames
private DataInputStream inputStream;
// The output stream to which writing the Modbus frames
private DataOutputStream outputStream;
// The Bytes output stream to use as output buffer for Modbus frames
private BytesOutputStream outputBuffer;
// The BytesInputStream wrapper for the transport input stream
private BytesInputStream inputBuffer;
// The last request sent over the transport ?? useful ??
private byte[] lastRequest = null;
// the socket used by this transport
private Socket socket;
// the read timeout timer
private Timer readTimeoutTimer;
// the read timout
private final int readTimeout = 5000; // ms
// the timeou flag
private boolean isTimedOut;
/**
* @param socket
* the client socket to close
*
* @throws IOException
* if a I/O exception occurs
*
*/
public ModbusRTUTCPTransport(Socket socket) throws IOException {
// prepare the input and output streams...
if (socket != null) {
setSocket(socket);
}
// set the timed out flag at false
this.isTimedOut = false;
}
/**
* Stores the given {@link Socket} instance and prepares the related streams to use them for Modbus RTU over TCP
* communication.
*
* @param socket
* the client socket
* @throws IOException
* if a I/O exception occurs
*/
public void setSocket(Socket socket) throws IOException {
if (this.socket != null) {
// TODO: handle clean closure of the streams
this.outputBuffer.close();
this.inputBuffer.close();
this.inputStream.close();
this.outputStream.close();
}
// store the socket used by this transport
this.socket = socket;
// get the input and output streams
this.inputStream = new DataInputStream(socket.getInputStream());
this.outputStream = new DataOutputStream(this.socket.getOutputStream());
// prepare the buffers
this.outputBuffer = new BytesOutputStream(Modbus.MAX_MESSAGE_LENGTH);
this.inputBuffer = new BytesInputStream(Modbus.MAX_MESSAGE_LENGTH);
}
/**
* writes the given ModbusMessage over the physical transport handled by this object.
*
* @param msg
* the {@link ModbusMessage} to be written on the transport.
*/
@Override
public synchronized void writeMessage(ModbusMessage msg) throws ModbusIOException {
try {
// atomic access to the output buffer
synchronized (this.outputBuffer) {
// reset the output buffer
this.outputBuffer.reset();
// prepare the message for "virtual" serial transport
msg.setHeadless();
// write the message to the output buffer
msg.writeTo(this.outputBuffer);
// compute the CRC
int[] crc = ModbusUtil.calculateCRC(this.outputBuffer.getBuffer(), 0, this.outputBuffer.size());
// write the CRC on the output buffer
this.outputBuffer.writeByte(crc[0]);
this.outputBuffer.writeByte(crc[1]);
// store the buffer length
int bufferLength = this.outputBuffer.size();
// store the raw output buffer reference
byte rawBuffer[] = this.outputBuffer.getBuffer();
// write the buffer on the socket
this.outputStream.write(rawBuffer, 0, bufferLength); // PDU +
// CRC
this.outputStream.flush();
// debug
// if (Modbus.debug)
System.out.println("Sent: " + ModbusUtil.toHex(rawBuffer, 0, bufferLength));
// store the written buffer as the last request
this.lastRequest = new byte[bufferLength];
System.arraycopy(rawBuffer, 0, this.lastRequest, 0, bufferLength);
// sleep for the time needed to receive the request at the other
// point of the connection
Thread.sleep(bufferLength);
}
} catch (Exception ex) {
throw new ModbusIOException("I/O failed to write");
}
}// writeMessage
// This is required for the slave that is not supported
@Override
public synchronized ModbusRequest readRequest() throws ModbusIOException {
throw new RuntimeException("Operation not supported.");
} // readRequest
@Override
/**
* Lazy implementation: avoid CRC validation...
*/
public synchronized ModbusResponse readResponse() throws ModbusIOException {
// the received response
ModbusResponse response = null;
// reset the timed out flag
this.isTimedOut = false;
// init and start the timeout timer
this.readTimeoutTimer = new Timer();
this.readTimeoutTimer.schedule(new TimerTask() {
@Override
public void run() {
isTimedOut = true;
}
}, this.readTimeout);
try {
// atomic access to the input buffer
synchronized (inputBuffer) {
// clean the input buffer
inputBuffer.reset(new byte[Modbus.MAX_MESSAGE_LENGTH]);
// sleep for the time needed to receive the first part of the
// response
int available = this.inputStream.available();
while ((available < 4) && (!this.isTimedOut)) {
Thread.yield(); // 1ms * #bytes (4bytes in the worst case)
available = this.inputStream.available();
if (Modbus.debug) {
System.out.println("Available bytes: " + available);
}
}
// check if timedOut
if (this.isTimedOut) {
throw new ModbusIOException("I/O exception - read timeout.\n");
}
// get a reference to the inner byte buffer
byte inBuffer[] = this.inputBuffer.getBuffer();
// read the first 2 bytes from the input stream
this.inputStream.read(inBuffer, 0, 2);
// this.inputStream.readFully(inBuffer);
// read the progressive id
int packetId = inputBuffer.readUnsignedByte();
// debug
System.out.println(ModbusRTUTCPTransport.logId + "Read packet with progressive id: " + packetId);
// read the function code
int functionCode = inputBuffer.readUnsignedByte();
// debug
System.out.println(" uid: " + packetId + ", function code: " + functionCode);
// compute the number of bytes composing the message (including
// the CRC = 2bytes)
int packetLength = computePacketLength(functionCode);
// sleep for the time needed to receive the first part of the
// response
while ((this.inputStream.available() < (packetLength - 3)) && (!this.isTimedOut)) {
try {
Thread.sleep(10);
} catch (InterruptedException ie) {
// do nothing
System.err.println("Sleep interrupted while waiting for response body...\n" + ie);
}
}
// check if timedOut
if (this.isTimedOut) {
throw new ModbusIOException("I/O exception - read timeout.\n");
}
// read the remaining bytes
this.inputStream.read(inBuffer, 3, packetLength);
// debug
System.out.println(
" bytes: " + ModbusUtil.toHex(inBuffer, 0, packetLength) + ", desired length: " + packetLength);
// compute the CRC
int crc[] = ModbusUtil.calculateCRC(inBuffer, 0, packetLength - 2);
// check the CRC against the received one...
if (ModbusUtil.unsignedByteToInt(inBuffer[packetLength - 2]) != crc[0]
|| ModbusUtil.unsignedByteToInt(inBuffer[packetLength - 1]) != crc[1]) {
throw new IOException("CRC Error in received frame: " + packetLength + " bytes: "
+ ModbusUtil.toHex(inBuffer, 0, packetLength));
}
// reset the input buffer to the given packet length (excluding
// the CRC)
this.inputBuffer.reset(inBuffer, packetLength - 2);
// create the response
response = ModbusResponse.createModbusResponse(functionCode);
response.setHeadless();
// read the response
response.readFrom(inputBuffer);
}
} catch (IOException e) {
// debug
System.err.println(ModbusRTUTCPTransport.logId + "Error while reading from socket: " + e);
// clean the input stream
try {
while (this.inputStream.read() != -1) {
;
}
} catch (IOException e1) {
// debug
System.err.println(ModbusRTUTCPTransport.logId + "Error while emptying input buffer from socket: " + e);
}
// wrap and re-throw
throw new ModbusIOException("I/O exception - failed to read.\n" + e);
}
// reset the timeout timer
this.readTimeoutTimer.cancel();
// return the response read from the socket stream
return response;
/*-------------------------- SERIAL IMPLEMENTATION -----------------------------------
try
{
do
{
// block the input stream
synchronized (byteInputStream)
{
// get the packet uid
int uid = inputStream.read();
if (Modbus.debug)
System.out.println(ModbusRTUTCPTransport.logId + "UID: " + uid);
// if the uid is valid (i.e., > 0) continue
if (uid != -1)
{
// get the function code
int fc = inputStream.read();
if (Modbus.debug)
System.out.println(ModbusRTUTCPTransport.logId + "Function code: " + uid);
//bufferize the response
byteOutputStream.reset();
byteOutputStream.writeByte(uid);
byteOutputStream.writeByte(fc);
// create the Modbus Response object to acquire length of message
response = ModbusResponse.createModbusResponse(fc);
response.setHeadless();
// With Modbus RTU, there is no end frame. Either we
// assume the message is complete as is or we must do
// function specific processing to know the correct length.
//bufferize the response according to the given function code
getResponse(fc, byteOutputStream);
//compute the response length without considering the CRC
dlength = byteOutputStream.size() - 2; // less the crc
//debug
if (Modbus.debug)
System.out.println("Response: "
+ ModbusUtil.toHex(byteOutputStream.getBuffer(), 0, dlength + 2));
//TODO: check if needed (restore the buffer state, cursor at 0, same content)
byteInputStream.reset(inputBuffer, dlength);
// cmopute the buffer CRC
int[] crc = ModbusUtil.calculateCRC(inputBuffer, 0, dlength);
// check the CRC against the received one...
if (ModbusUtil.unsignedByteToInt(inputBuffer[dlength]) != crc[0]
|| ModbusUtil.unsignedByteToInt(inputBuffer[dlength + 1]) != crc[1])
{
throw new IOException("CRC Error in received frame: " + dlength + " bytes: "
+ ModbusUtil.toHex(byteInputStream.getBuffer(), 0, dlength));
}
}
else
{
throw new IOException("Error reading response");
}
// restore the buffer state, cursor at 0, same content
byteInputStream.reset(inputBuffer, dlength);
//actually read the response
if (response != null)
{
response.readFrom(byteInputStream);
}
//flag completion...
done = true;
}// synchronized
}
while (!done);
return response;
}
catch (Exception ex)
{
System.err.println("Last request: " + ModbusUtil.toHex(lastRequest));
System.err.println(ex.getMessage());
throw new ModbusIOException("I/O exception - failed to read");
}
------------------------------------------------------------------------------*/
}// readResponse
private int computePacketLength(int functionCode) throws IOException {
// packet length by function code:
int length = 0;
switch (functionCode) {
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x0C:
case 0x11: // report slave ID version and run/stop state
case 0x14: // read log entry (60000 memory reference)
case 0x15: // write log entry (60000 memory reference)
case 0x17: {
// get a reference to the inner byte buffer
byte inBuffer[] = this.inputBuffer.getBuffer();
this.inputStream.read(inBuffer, 2, 1);
int dataLength = this.inputBuffer.readUnsignedByte();
length = dataLength + 5; // UID+FC+CRC(2bytes)
break;
}
case 0x05:
case 0x06:
case 0x0B:
case 0x0F:
case 0x10: {
// read status: only the CRC remains after address and
// function code
length = 6;
break;
}
case 0x07:
case 0x08: {
length = 3;
break;
}
case 0x16: {
length = 8;
break;
}
case 0x18: {
// get a reference to the inner byte buffer
byte inBuffer[] = this.inputBuffer.getBuffer();
this.inputStream.read(inBuffer, 2, 2);
length = this.inputBuffer.readUnsignedShort() + 6;// UID+FC+CRC(2bytes)
break;
}
case 0x83: {
// error code
length = 5;
break;
}
}
return length;
}
@Override
public void close() throws IOException {
inputStream.close();
outputStream.close();
}// close
/*
* private void getResponse(int fn, BytesOutputStream out) throws IOException { int bc = -1, bc2 = -1, bcw = -1; int
* inpBytes = 0; byte inpBuf[] = new byte[256];
*
* try { switch (fn) { case 0x01: case 0x02: case 0x03: case 0x04: case 0x0C: case 0x11: // report slave ID version
* and run/stop state case 0x14: // read log entry (60000 memory reference) case 0x15: // write log entry (60000
* memory reference) case 0x17: // read the byte count; bc = inputStream.read(); out.write(bc); // now get the
* specified number of bytes and the 2 CRC bytes setReceiveThreshold(bc + 2); inpBytes = inputStream.read(inpBuf, 0,
* bc + 2); out.write(inpBuf, 0, inpBytes); m_CommPort.disableReceiveThreshold(); if (inpBytes != bc + 2) {
* System.out.println("Error: looking for " + (bc + 2) + " bytes, received " + inpBytes); } break; case 0x05: case
* 0x06: case 0x0B: case 0x0F: case 0x10: // read status: only the CRC remains after address and // function code
* setReceiveThreshold(6); inpBytes = inputStream.read(inpBuf, 0, 6); out.write(inpBuf, 0, inpBytes);
* m_CommPort.disableReceiveThreshold(); break; case 0x07: case 0x08: // read status: only the CRC remains after
* address and // function code setReceiveThreshold(3); inpBytes = inputStream.read(inpBuf, 0, 3); out.write(inpBuf,
* 0, inpBytes); m_CommPort.disableReceiveThreshold(); break; case 0x16: // eight bytes in addition to the address
* and function codes setReceiveThreshold(8); inpBytes = inputStream.read(inpBuf, 0, 8); out.write(inpBuf, 0,
* inpBytes); m_CommPort.disableReceiveThreshold(); break; case 0x18: // read the byte count word bc =
* inputStream.read(); out.write(bc); bc2 = inputStream.read(); out.write(bc2); bcw = ModbusUtil.makeWord(bc, bc2);
* // now get the specified number of bytes and the 2 CRC bytes setReceiveThreshold(bcw + 2); inpBytes =
* inputStream.read(inpBuf, 0, bcw + 2); out.write(inpBuf, 0, inpBytes); m_CommPort.disableReceiveThreshold();
* break; } } catch (IOException e) { m_CommPort.disableReceiveThreshold(); throw new IOException(
* "getResponse serial port exception"); } }// getResponse
*/
}