/*** * Copyright 2002-2010 jamod development team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***/ package net.wimpi.modbus.io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.wimpi.modbus.Modbus; import net.wimpi.modbus.ModbusIOException; import net.wimpi.modbus.msg.ModbusMessage; import net.wimpi.modbus.msg.ModbusRequest; import net.wimpi.modbus.msg.ModbusResponse; import net.wimpi.modbus.util.ModbusUtil; /** * Class that implements the ModbusRTU transport * flavor. * * @author John Charlton * @author Dieter Wimberger * * @version @version@ (@date@) */ public class ModbusRTUTransport extends ModbusSerialTransport { private static final Logger logger = LoggerFactory.getLogger(ModbusRTUTransport.class); private InputStream m_InputStream; // wrap into filter input private OutputStream m_OutputStream; // wrap into filter output private byte[] m_InBuffer; private BytesInputStream m_ByteIn; // to read message from private BytesOutputStream m_ByteInOut; // to buffer message to private BytesOutputStream m_ByteOut; // write frames private byte[] lastRequest = null; @Override public void writeMessage(ModbusMessage msg) throws ModbusIOException { try { int len; synchronized (m_ByteOut) { // first clear any input from the receive buffer to prepare // for the reply since RTU doesn't have message delimiters clearInput(); // write message to byte out m_ByteOut.reset(); msg.setHeadless(); msg.writeTo(m_ByteOut); len = m_ByteOut.size(); int[] crc = ModbusUtil.calculateCRC(m_ByteOut.getBuffer(), 0, len); m_ByteOut.writeByte(crc[0]); m_ByteOut.writeByte(crc[1]); // write message len = m_ByteOut.size(); byte buf[] = m_ByteOut.getBuffer(); m_OutputStream.write(buf, 0, len); // PDU + CRC m_OutputStream.flush(); logger.debug("Sent: {}", ModbusUtil.toHex(buf, 0, len)); // clears out the echoed message // for RS485 if (m_Echo) { readEcho(len); } lastRequest = new byte[len]; System.arraycopy(buf, 0, lastRequest, 0, len); } } catch (Exception ex) { throw new ModbusIOException("I/O failed to write"); } }// writeMessage // This is required for the slave that is not supported @Override public ModbusRequest readRequest() throws ModbusIOException { throw new RuntimeException("Operation not supported."); } // readRequest /** * Clear the input if characters are found in the input stream. * * @throws IOException */ public void clearInput() throws IOException { if (m_InputStream.available() > 0) { int len = m_InputStream.available(); byte buf[] = new byte[len]; m_InputStream.read(buf, 0, len); logger.debug("Clear input: {}", ModbusUtil.toHex(buf, 0, len)); } }// cleanInput @Override public ModbusResponse readResponse() throws ModbusIOException { boolean done = false; ModbusResponse response = null; int dlength = 0; try { do { // 1. read to function code, create request and read function specific bytes synchronized (m_ByteIn) { // Make ComPort blocking m_CommPort.enableReceiveThreshold(1); int uid = m_InputStream.read(); logger.trace("Managed to read at least one byte"); if (uid != -1) { int fc = m_InputStream.read(); m_ByteInOut.reset(); m_ByteInOut.writeByte(uid); m_ByteInOut.writeByte(fc); // create response 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. To avoid // moving frame timing to the serial input functions, we set the // timeout and to message specific parsing to read a response. getResponse(fc, m_ByteInOut); dlength = m_ByteInOut.size() - 2; // less the crc logger.debug("Response: {}", ModbusUtil.toHex(m_ByteInOut.getBuffer(), 0, dlength + 2)); m_ByteIn.reset(m_InBuffer, dlength); // check CRC int[] crc = ModbusUtil.calculateCRC(m_InBuffer, 0, dlength); // does not include CRC if (ModbusUtil.unsignedByteToInt(m_InBuffer[dlength]) != crc[0] || ModbusUtil.unsignedByteToInt(m_InBuffer[dlength + 1]) != crc[1]) { throw new IOException("CRC Error in received frame: " + dlength + " bytes: " + ModbusUtil.toHex(m_ByteIn.getBuffer(), 0, dlength)); } } else { throw new IOException("Error reading response (EOF)"); } // read response m_ByteIn.reset(m_InBuffer, dlength); if (response != null) { response.readFrom(m_ByteIn); } done = true; } // synchronized } while (!done); return response; } catch (Exception ex) { final String errMsg = "failed to read"; logger.error("Last request: {}", ModbusUtil.toHex(lastRequest)); logger.error("{}: {}", errMsg, ex.getMessage()); throw new ModbusIOException("I/O exception - " + errMsg); } finally { m_CommPort.disableReceiveThreshold(); } }// readResponse /** * Prepares the input and output streams of this * <tt>ModbusRTUTransport</tt> instance. * * @param in the input stream to be read from. * @param out the output stream to write to. * @throws IOException if an I\O error occurs. */ @Override public void prepareStreams(InputStream in, OutputStream out) throws IOException { m_InputStream = in; // new RTUInputStream(in); m_OutputStream = out; m_ByteOut = new BytesOutputStream(Modbus.MAX_MESSAGE_LENGTH); m_InBuffer = new byte[Modbus.MAX_MESSAGE_LENGTH]; m_ByteIn = new BytesInputStream(m_InBuffer); m_ByteInOut = new BytesOutputStream(m_InBuffer); } // prepareStreams @Override public void close() throws IOException { IOUtils.closeQuietly(m_InputStream); IOUtils.closeQuietly(m_OutputStream); super.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; setReceiveThreshold(1); bc = m_InputStream.read(); out.write(bc); // now get the specified number of bytes and the 2 CRC bytes setReceiveThreshold(bc + 2); inpBytes = m_InputStream.read(inpBuf, 0, bc + 2); out.write(inpBuf, 0, inpBytes); m_CommPort.disableReceiveThreshold(); if (inpBytes != bc + 2) { logger.error("awaited {} bytes, but received {}", (bc + 2), 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 = m_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 = m_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 = m_InputStream.read(inpBuf, 0, 8); out.write(inpBuf, 0, inpBytes); m_CommPort.disableReceiveThreshold(); break; case 0x18: // read the byte count word setReceiveThreshold(1); bc = m_InputStream.read(); out.write(bc); bc2 = m_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 = m_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 } // ModbusRTUTransport