/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.freedomotic.plugins.devices.modbus.gateways;
import jssc.SerialPort;
import java.io.IOException;
import java.io.InputStream;
/**
* Class that wraps a {@link SerialPort} to provide {@link InputStream}
* functionality. This stream also provides support for performing blocking
* reads with timeouts.
* <br>
* It is instantiated by passing the constructor a {@link SerialPort} instance.
* Do not create multiple streams for the same serial port unless you implement
* your own synchronization.
*
* @author Charles Hache <chalz@member.fsf.org>
*
* Attribution: https://github.com/therealchalz/java-simple-serial-connector
*
*/
public class SerialInputStream extends InputStream {
private SerialPort serialPort;
private int defaultTimeout = 0;
/**
* Instantiates a SerialInputStream for the given {@link SerialPort} Do not
* create multiple streams for the same serial port unless you implement
* your own synchronization.
*
* @param sp The serial port to stream.
*/
public SerialInputStream(SerialPort sp) {
serialPort = sp;
}
/**
* Set the default timeout (ms) of this SerialInputStream. This affects
* subsequent calls to {@link #read()}, {@link #blockingRead(int[])}, and
* {@link #blockingRead(int[], int, int)} The default timeout can be 'unset'
* by setting it to 0.
*
* @param time The timeout in milliseconds.
*/
public void setTimeout(int time) {
defaultTimeout = time;
}
/**
* Reads the next byte from the port. If the timeout of this stream has been
* set, then this method blocks until data is available or until the timeout
* has been hit. If the timeout is not set or has been set to 0, then this
* method blocks indefinitely.
*/
@Override
public int read() throws IOException {
return read(defaultTimeout);
}
/**
* The same contract as {@link #read()}, except overrides this stream's
* default timeout with the given timeout in milliseconds.
*
* @param timeout The timeout in milliseconds.
* @return The read byte.
* @throws IOException On serial port error or timeout
*/
public int read(int timeout) throws IOException {
byte[] buf = new byte[1];
try {
if (timeout > 0) {
buf = serialPort.readBytes(1, timeout);
} else {
buf = serialPort.readBytes(1);
}
return buf[0];
} catch (Exception e) {
throw new IOException(e);
}
}
/**
* Non-blocking read of up to buf.length bytes from the stream. This call
* behaves as read(buf, 0, buf.length) would.
*
* @param buf The buffer to fill.
* @return The number of bytes read, which can be 0.
* @throws IOException on error.
*/
@Override
public int read(byte[] buf) throws IOException {
return read(buf, 0, buf.length);
}
/**
* Non-blocking read of up to length bytes from the stream. This method
* returns what is immediately available in the input buffer.
*
* @param buf The buffer to fill.
* @param offset The offset into the buffer to start copying data.
* @param length The maximum number of bytes to read.
* @return The actual number of bytes read, which can be 0.
* @throws IOException on error.
*/
@Override
public int read(byte[] buf, int offset, int length) throws IOException {
if (buf.length < offset + length) {
length = buf.length - offset;
}
int available = this.available();
if (available > length) {
available = length;
}
try {
byte[] readBuf = serialPort.readBytes(available);
System.arraycopy(readBuf, 0, buf, offset, length);
return readBuf.length;
} catch (Exception e) {
throw new IOException(e);
}
}
/**
* Blocks until buf.length bytes are read, an error occurs, or the default
* timeout is hit (if specified). This behaves as blockingRead(buf, 0,
* buf.length) would.
*
* @param buf The buffer to fill with data.
* @return The number of bytes read.
* @throws IOException On error or timeout.
*/
public int blockingRead(byte[] buf) throws IOException {
return blockingRead(buf, 0, buf.length, defaultTimeout);
}
/**
* The same contract as {@link #blockingRead(byte[])} except overrides this
* stream's default timeout with the given one.
*
* @param buf The buffer to fill.
* @param timeout The timeout in milliseconds.
* @return The number of bytes read.
* @throws IOException On error or timeout.
*/
public int blockingRead(byte[] buf, int timeout) throws IOException {
return blockingRead(buf, 0, buf.length, timeout);
}
/**
* Blocks until length bytes are read, an error occurs, or the default
* timeout is hit (if specified). Saves the data into the given buffer at
* the specified offset. If the stream's timeout is not set, behaves as
* {@link #read(byte[], int, int)} would.
*
* @param buf The buffer to fill.
* @param offset The offset in buffer to save the data.
* @param length The number of bytes to read.
* @return the number of bytes read.
* @throws IOException on error or timeout.
*/
public int blockingRead(byte[] buf, int offset, int length) throws IOException {
return blockingRead(buf, offset, length, defaultTimeout);
}
/**
* The same contract as {@link #blockingRead(byte[], int, int)} except
* overrides this stream's default timeout with the given one.
*
* @param buf The buffer to fill.
* @param offset Offset in the buffer to start saving data.
* @param length The number of bytes to read.
* @param timeout The timeout in milliseconds.
* @return The number of bytes read.
* @throws IOException On error or timeout.
*/
public int blockingRead(byte[] buf, int offset, int length, int timeout) throws IOException {
if (buf.length < offset + length) {
throw new IOException("Not enough buffer space for serial data");
}
if (timeout < 1) {
return read(buf, offset, length);
}
try {
byte[] readBuf = serialPort.readBytes(length, timeout);
System.arraycopy(readBuf, 0, buf, offset, length);
return readBuf.length;
} catch (Exception e) {
throw new IOException(e);
}
}
@Override
public int available() throws IOException {
int ret;
try {
ret = serialPort.getInputBufferBytesCount();
if (ret >= 0) {
return ret;
}
throw new IOException("Error checking available bytes from the serial port.");
} catch (Exception e) {
throw new IOException("Error checking available bytes from the serial port.");
}
}
}