package com.rm5248.serial;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A SerialPort object represents a SerialPort object on the system.
*
* When creating a SerialPort object, you give the SerialPort the name of the port that you
* wish to open.
*
* When opening a SerialPort and setting values, an {@link IllegalArgumentException} may be thrown
* if any of the values are NULL.
*
* When getting and settings the properties of the SerialPort using one of the {@code getXXX() }
* or {@code setXXX() } methods, an IllegalStateException will be thrown if the port has been closed.
*
* @author rm5248
*
*/
public class SerialPort
implements Closeable {
static{
System.
loadLibrary( "javaserial" );
}
/**
* Represents the BaudRate that the SerialPort uses.
*/
public enum BaudRate
{
/** Not available in Windows */
B0,
/** Not available in Windows */
B50,
/** Not available in Windows */
B75,
B110,
/** Not available in Windows */
B134,
/** Not available in Windows */
B150,
B200,
B300,
B600,
B1200,
/** Not available in Windows */
B1800,
B2400,
B4800,
B9600,
B38400,
B115200
}
/**
* Represents the number of bits that the serial port uses.
* Typically, this is 8-bit characters.
*/
public enum DataBits
{
DATABITS_5,
DATABITS_6,
DATABITS_7,
DATABITS_8
}
/**
* The number of stop bits for data. Typically this is 1.
*/
public enum StopBits
{
STOPBITS_1,
STOPBITS_2
}
/**
* The parity bit for the data. Typically None.
*/
public enum Parity
{
NONE,
EVEN,
ODD
}
/**
* The Flow control scheme for the data, typically None.
*/
public enum FlowControl
{
NONE,
HARDWARE,
SOFTWARE
}
private class SerialStateListener
implements Runnable{
private volatile boolean stop
;
private SerialChangeListener listen
;
SerialStateListener
( SerialChangeListener listen
){
stop =
false;
this.
listen = listen
;
}
@
Override
public void run
() {
while( !stop
){
synchronized( serialListenSync
){
try {
serialListenSync.
wait();
if( stop
){
break;
}
listen.
serialStateChanged( state
);
} catch (Exception e
) {}
}
}
}
void doStop
(){
stop =
true;
}
}
/* The handle to our internal data structure which keeps track of the port settings.
* We need a special structure, as on windows we have a HANDLE type, which is void*,
* yet on Linux we have a file descriptor, which is an int.
* This is not just a pointer to memory, because if we're running on a 64-bit
* system, then we might have problems putting it in 32-bits. Better safe than sorry.
*/
private int handle
;
/* The input stream that user code uses to read from the serial port. */
private SerialInputStream inputStream
;
/* The buffered serial input stream which filters out events for us. */
private BufferedSerialInputStream bis
;
/* The output stream that user code uses to write to the serial port. */
private SerialOutputStream outputStream
;
/* Make sure we don't close ourselves twice */
private boolean closed
;
/* The name of the port that's currently open */
private String portName
;
/* Cache of the last gotten serial line state */
private volatile SerialLineState state
;
/* Runs in the background to check for serial port events */
private SerialStateListener serialListen
;
/* Used for synchronizing serialListen */
private Object serialListenSync
;
/**
* Open the specified port, 9600 baud, 8 data bits, 1 stop bit, no parity, no flow control
*
* @param portName The name of the port to open
* @throws NoSuchPortException If this port does not exist
* @throws NotASerialPortException If the specified port is not a serial port
*/
public SerialPort
( String portName
) throws NoSuchPortException, NotASerialPortException
{
this( portName, BaudRate.
B9600 );
}
/**
* Open up a serial port, but allow the user to keep the current settings of the serial port.
*
* @param portName The port to open
* @param keepSettings If true, will simply open the serial port without setting anything. If false, this method
* acts the same as {@link #SerialPort(String) SerialPort( String portName ) }
* @throws NoSuchPortException If the port does not exist
* @throws NotASerialPortException If the port is not in fact a serial port
*/
public SerialPort
( String portName,
boolean keepSettings
) throws NoSuchPortException, NotASerialPortException
{
if( portName ==
null ){
throw new IllegalArgumentException( "portName must not be null" );
}
if( keepSettings
){
this.
handle = openPort
( portName
);
this.
portName = portName
;
inputStream =
new SerialInputStream
( handle
);
bis =
new BufferedSerialInputStream
( inputStream,
this );
outputStream =
new SerialOutputStream
( handle
);
closed =
false;
SerialLineState s =
new SerialLineState
();
int state = getSerialLineStateInternalNonblocking
();
// do some sort of bitwise operations here....
if( ( state
& 0x01
) > 0 ){
s.
carrierDetect =
true;
}
if( ( state
& (0x01
<< 1 ) ) > 0 ){
s.
clearToSend =
true;
}
if( ( state
& (0x01
<< 2 ) ) > 0 ){
s.
dataSetReady =
true;
}
if( ( state
& (0x01
<< 3 ) ) > 0 ){
s.
dataTerminalReady =
true;
}
if( ( state
& (0x01
<< 4 ) ) > 0 ){
s.
requestToSend =
true;
}
if( ( state
& (0x01
<< 5 ) ) > 0 ){
s.
ringIndicator =
true;
}
this.
state = s
;
serialListenSync =
new Object();
}else{
this.
handle = openPort
( portName,
9600,
8,
1,
0,
0 );
}
new Thread( bis,
"BufferedSerialReader" ).
start();
}
/**
* Open the specified port, no flow control
*
* @param portName The name of the port to open
* @param rate The Baud Rate to open this port at
* @throws NoSuchPortException If this port does not exist
* @throws NotASerialPortException If the specified port is not a serial port
*/
public SerialPort
( String portName, BaudRate rate
)throws NoSuchPortException, NotASerialPortException
{
this( portName, rate, DataBits.
DATABITS_8 );
}
/**
* Open the specified port, no flow control
*
* @param portName The name of the port to open
* @param rate The Baud Rate to open this port at
* @param data The number of data bits
* @throws NoSuchPortException If this port does not exist
* @throws NotASerialPortException If the specified port is not a serial port
*/
public SerialPort
( String portName, BaudRate rate, DataBits data
) throws NoSuchPortException, NotASerialPortException
{
this( portName, rate, data, StopBits.
STOPBITS_1 );
}
/**
* Open the specified port, no parity or flow control
*
* @param portName The name of the port to open
* @param rate The Baud Rate to open this port at
* @param data The number of data bits
* @param stop The number of stop bits
* @throws NoSuchPortException If this port does not exist
* @throws NotASerialPortException If the specified port is not a serial port
*/
public SerialPort
( String portName, BaudRate rate, DataBits data, StopBits stop
) throws NoSuchPortException, NotASerialPortException
{
this( portName, rate, data, stop, Parity.
NONE );
}
/**
* Open the specified port, no flow control
*
* @param portName The name of the port to open
* @param rate The Baud Rate to open this port at
* @param data The number of data bits
* @param stop The number of stop bits
* @param parity The parity of the line
* @throws NoSuchPortException If this port does not exist
* @throws NotASerialPortException If the specified port is not a serial port
*/
public SerialPort
( String portName, BaudRate rate, DataBits data, StopBits stop, Parity parity
) throws NoSuchPortException, NotASerialPortException
{
this( portName, rate, data, stop, parity, FlowControl.
NONE );
}
/**
* Open the specified port, defining all options
*
* @param portName The name of the port to open
* @param rate The Buad Rate to open this port at
* @param data The number of data bits
* @param stop The number of stop bits
* @param parity The parity of the line
* @param flow The flow control of the line
* @throws NoSuchPortException If this port does not exist
* @throws NotASerialPortException If the specified port is not a serial port
*/
public SerialPort
( String portName, BaudRate rate, DataBits data, StopBits stop, Parity parity, FlowControl flow
) throws NoSuchPortException, NotASerialPortException
{
int myRate =
0;
int myData =
0;
int myStop =
0;
int myParity =
0;
int myFlow =
0;
SerialLineState s
;
int state
;
//Check for null values in our arguments
if( portName ==
null ){
throw new IllegalArgumentException( "portName must not be null" );
}
if( rate ==
null ){
throw new IllegalArgumentException( "rate must not be null" );
}
if( data ==
null ){
throw new IllegalArgumentException( "data must not be null" );
}
if( stop ==
null ){
throw new IllegalArgumentException( "stop must not be null" );
}
if( parity ==
null ){
throw new IllegalArgumentException( "parity must not be null" );
}
if( flow ==
null ){
throw new IllegalArgumentException( "flow must not be null" );
}
//Okay, looks like we're good!
this.
portName = portName
;
closed =
false;
switch( rate
){
case B0 : myRate =
0; break;
case B50 : myRate =
50; break;
case B75 : myRate =
75; break;
case B110 : myRate =
110; break;
case B134 : myRate =
134; break;
case B150 : myRate =
150; break;
case B200 : myRate =
200; break;
case B300 : myRate =
300; break;
case B600 : myRate =
600; break;
case B1200 : myRate =
1200; break;
case B1800 : myRate =
1800; break;
case B2400 : myRate =
2400; break;
case B4800 : myRate =
4800; break;
case B9600 : myRate =
9600; break;
case B38400 : myRate =
38400; break;
case B115200: myRate =
115200; break;
}
switch( data
){
case DATABITS_5: myData =
5; break;
case DATABITS_6: myData =
6; break;
case DATABITS_7: myData =
7; break;
case DATABITS_8: myData =
8; break;
}
switch( stop
){
case STOPBITS_1: myStop =
1; break;
case STOPBITS_2: myStop =
2; break;
}
switch( parity
){
case NONE: myParity =
0; break;
case ODD: myParity =
1; break;
case EVEN: myParity =
2; break;
}
switch( flow
){
case NONE: myFlow =
0; break;
case HARDWARE: myFlow =
1; break;
case SOFTWARE: myFlow =
2; break;
}
handle = openPort
(portName, myRate, myData, myStop, myParity, myFlow
);
inputStream =
new SerialInputStream
( handle
);
bis =
new BufferedSerialInputStream
( inputStream,
this );
outputStream =
new SerialOutputStream
( handle
);
s =
new SerialLineState
();
state = getSerialLineStateInternalNonblocking
();
if( ( state
& 0x01
) > 0 ){
s.
carrierDetect =
true;
}
if( ( state
& (0x01
<< 1 ) ) > 0 ){
s.
clearToSend =
true;
}
if( ( state
& (0x01
<< 2 ) ) > 0 ){
s.
dataSetReady =
true;
}
if( ( state
& (0x01
<< 3 ) ) > 0 ){
s.
dataTerminalReady =
true;
}
if( ( state
& (0x01
<< 4 ) ) > 0 ){
s.
requestToSend =
true;
}
if( ( state
& (0x01
<< 5 ) ) > 0 ){
s.
ringIndicator =
true;
}
this.
state = s
;
serialListenSync =
new Object();
new Thread( bis,
"BufferedSerialReader" ).
start();
}
/**
* Set the Baud Rate for this port.
*
* @param rate
*/
public void setBaudRate
( BaudRate rate
){
int myRate =
0;
if( closed
){
throw new IllegalStateException( "Cannot set the BaudRate once the port has been closed." );
}
if( rate ==
null ){
throw new IllegalArgumentException( "rate must not be null" );
}
switch( rate
){
case B0 : myRate =
0; break;
case B50 : myRate =
50; break;
case B75 : myRate =
75; break;
case B110 : myRate =
110; break;
case B134 : myRate =
134; break;
case B150 : myRate =
150; break;
case B200 : myRate =
200; break;
case B300 : myRate =
300; break;
case B600 : myRate =
600; break;
case B1200 : myRate =
1200; break;
case B1800 : myRate =
1800; break;
case B2400 : myRate =
2400; break;
case B4800 : myRate =
4800; break;
case B9600 : myRate =
9600; break;
case B38400 : myRate =
38400; break;
case B115200: myRate =
115200; break;
}
setBaudRate
( myRate
);
}
/**
* Get the input stream to be used to read from this SerialPort.
*
* @return The input stream.
*/
public InputStream getInputStream
(){
if( closed
){
throw new IllegalStateException( "Cannot get the input stream once the port has been closed." );
}
return bis
;
}
/** Get the output stream used to write data to this device.
*
* @return The output stream.
*/
public OutputStream getOutputStream
(){
if( closed
){
throw new IllegalStateException( "Cannot get the output stream once the port has been closed." );
}
return outputStream
;
}
/**
* Close the serial port, and all input streams
*/
public void close
(){
if( closed
) return;
closed =
true;
doClose
();
if( serialListen
!=
null ){
serialListen.
doStop();
}
synchronized( serialListenSync
){
serialListenSync.
notify();
}
}
/**
* See if the port has been closed already.
* @return
*/
public boolean isClosed
(){
return closed
;
}
public void finalize
(){
close
();
}
/**
* Set the stop bits of the serial port, after the port has been opened.
*
* @param stop
*/
public void setStopBits
( StopBits stop
){
int myStop =
0;
if( closed
){
throw new IllegalStateException( "Cannot set the StopBits once the port has been closed." );
}
if( stop ==
null ){
throw new IllegalArgumentException( "stop must not be null" );
}
switch( stop
){
case STOPBITS_1: myStop =
1; break;
case STOPBITS_2: myStop =
2; break;
}
setStopBits
( myStop
);
}
/**
* Set the data bits size, after the port has been opened.
*
* @param data
*/
public void setDataSize
( DataBits data
){
int myData =
0;
if( closed
){
throw new IllegalStateException( "Cannot set the DataBits once the port has been closed." );
}
if( data ==
null ){
throw new IllegalArgumentException( "data must not be null" );
}
switch( data
){
case DATABITS_5: myData =
5; break;
case DATABITS_6: myData =
6; break;
case DATABITS_7: myData =
7; break;
case DATABITS_8: myData =
8; break;
}
setCharSize
( myData
);
}
/**
* Set the parity of the serial port, after the port has been opened.
*
* @param parity
*/
public void setParity
( Parity parity
){
int myParity =
0;
if( closed
){
throw new IllegalStateException( "Cannot set the parity once the port has been closed." );
}
if( parity ==
null ){
throw new IllegalArgumentException( "parity must not be null" );
}
switch( parity
){
case NONE: myParity =
0; break;
case ODD: myParity =
1; break;
case EVEN: myParity =
2; break;
}
setParity
( myParity
);
}
/**
* Get the serial line state for the specified serial port.
*
* @return
*/
public SerialLineState getSerialLineState
() throws IOException{
if( closed
){
throw new IllegalStateException( "Cannot get the serial line state once the port has been closed." );
}
int gotState = getSerialLineStateInternalNonblocking
();
SerialLineState s =
new SerialLineState
();
// do some sort of bitwise operations here....
if( ( gotState
& 0x01
) > 0 ){
s.
carrierDetect =
true;
}
if( ( gotState
& (0x01
<< 1 ) ) > 0 ){
s.
clearToSend =
true;
}
if( ( gotState
& (0x01
<< 2 ) ) > 0 ){
s.
dataSetReady =
true;
}
if( ( gotState
& (0x01
<< 3 ) ) > 0 ){
s.
dataTerminalReady =
true;
}
if( ( gotState
& (0x01
<< 4 ) ) > 0 ){
s.
requestToSend =
true;
}
if( ( gotState
& (0x01
<< 5 ) ) > 0 ){
s.
ringIndicator =
true;
}
return s
;
}
/**
* Set the serial line state to the parameters given.
*
* @param state
*/
public void setSerialLineState
( SerialLineState state
){
if( closed
){
throw new IllegalStateException( "Cannot set the serial line state once the port has been closed." );
}
setSerialLineStateInternal
( state
);
//Now, because Windows is weird, we need to post a serial changed event here.
//however, since we can only set DTR and RTS, only post an event if those are
//the things that changed
if( this.
state.
dataTerminalReady != state.
dataTerminalReady ||
this.
state.
requestToSend != state.
requestToSend ){
this.
postSerialChangedEvent( state
);
}
}
/**
* Get the baud rate of the serial port.
*
* @return
*/
public SerialPort.
BaudRate getBaudRate
(){
int baudRate
;
BaudRate toReturn
;
if( closed
){
throw new IllegalStateException( "Cannot get the baud rate once the port has been closed." );
}
baudRate = getBaudRateInternal
();
toReturn = BaudRate.
B0;
switch( baudRate
){
case 0 : toReturn = BaudRate.
B0 ; break;
case 50 : toReturn = BaudRate.
B50 ; break;
case 75 : toReturn = BaudRate.
B75 ; break;
case 110 : toReturn = BaudRate.
B110 ; break;
case 134 : toReturn = BaudRate.
B134 ; break;
case 150 : toReturn = BaudRate.
B150 ; break;
case 200 : toReturn = BaudRate.
B200 ; break;
case 300 : toReturn = BaudRate.
B300 ; break;
case 600 : toReturn = BaudRate.
B600 ; break;
case 1200 : toReturn = BaudRate.
B1200 ; break;
case 1800 : toReturn = BaudRate.
B1800 ; break;
case 2400 : toReturn = BaudRate.
B2400 ; break;
case 4800 : toReturn = BaudRate.
B4800 ; break;
case 9600 : toReturn = BaudRate.
B9600 ; break;
case 38400 : toReturn = BaudRate.
B38400 ; break;
case 115200: toReturn = BaudRate.
B115200; break;
}
return toReturn
;
}
/**
* Get the number of data bits.
*
* @return
*/
public SerialPort.
DataBits getDataBits
(){
int dataBits
;
DataBits bits
;
if( closed
){
throw new IllegalStateException( "Cannot get the data bits once the port has been closed." );
}
dataBits = getCharSizeInternal
();
bits = DataBits.
DATABITS_8;
switch( dataBits
){
case 8: bits = DataBits.
DATABITS_8; break;
case 7: bits = DataBits.
DATABITS_7; break;
case 6: bits = DataBits.
DATABITS_6; break;
case 5: bits = DataBits.
DATABITS_5; break;
}
return bits
;
}
/**
* Get the number of stop bits.
*
* @return
*/
public SerialPort.
StopBits getStopBits
(){
int stopBits
;
StopBits bits
;
if( closed
){
throw new IllegalStateException( "Cannot get stop bits once the port has been closed." );
}
stopBits = getStopBitsInternal
();
bits = StopBits.
STOPBITS_1;
switch( stopBits
){
case 1: bits = StopBits.
STOPBITS_1; break;
case 2: bits = StopBits.
STOPBITS_2; break;
}
return bits
;
}
/**
* Get the parity of the serial port.
*
* @return
*/
public SerialPort.
Parity getParity
(){
int parity
;
Parity par
;
if( closed
){
throw new IllegalStateException( "Cannot get the parity once the port has been closed." );
}
parity = getParityInternal
();
par = Parity.
NONE;
switch( parity
){
case 0: par = Parity.
NONE; break;
case 1: par = Parity.
ODD; break;
case 2: par = Parity.
EVEN; break;
}
return par
;
}
/**
* Get the flow control for the serial port.
*
* @return
*/
public SerialPort.
FlowControl getFlowControl
(){
int flowControl
;
FlowControl cont
;
if( closed
){
throw new IllegalStateException( "Cannot get the flow once the port has been closed." );
}
flowControl = getFlowControlInternal
();
cont = FlowControl.
NONE;
switch( flowControl
){
case 0: cont = FlowControl.
NONE; break;
case 1: cont = FlowControl.
HARDWARE; break;
case 2: cont = FlowControl.
SOFTWARE; break;
}
return cont
;
}
/**
* Set the flow control for the serial port
*
* @param flow
*/
public void setFlowControl
( FlowControl flow
){
if( closed
){
throw new IllegalStateException( "Cannot set flow once the port has been closed." );
}
switch( flow
){
case HARDWARE: setFlowControl
( 1 ); break;
case NONE: setFlowControl
( 0 ); break;
case SOFTWARE: setFlowControl
( 2 ); break;
}
}
/**
* Set the listener which will get events when there is activity on the serial port.
* Note that this activity does NOT include receive and transmit events - this is
* changes on the lines of the serial port, such as RI, DSR, and DTR.
*
* If listen is null, will remove the listener.
*
* @param listen The listener which gets events
*/
public void setSerialChangeListener
( SerialChangeListener listen
){
if( serialListen
!=
null ){
serialListen.
doStop();
}
if( listen
!=
null ){
serialListen =
new SerialStateListener
( listen
);
new Thread( serialListen,
"SerialListen" ).
start();
}
}
public String toString
(){
return "Serial Port " + portName +
":" + getBaudRate
();
}
/**
* Get the name of the serial port that this object represents.
* @return
*/
public String getPortName
(){
return portName
;
}
/**
* This method is called when the state of the serial lines is changed.
*
* @param newState
*/
void postSerialChangedEvent
( SerialLineState newState
){
if( !state.
equals( newState
) ){
state = newState
;
synchronized( serialListenSync
){
serialListenSync.
notify();
}
}
}
/**
* Open the specified port, return an internal handle to the data structure for this port.
*
* @param portName
* @return
*/
private native int openPort
( String portName,
int baudRate,
int dataBits,
int stopBits,
int parity,
int flowControl
) throws NoSuchPortException, NotASerialPortException
;
/**
* Open the specified port, return an internal handle for the data of this port.
* This method DOES NOT set any of the serial port settings
*
* @param portName The port to open
* @return
*/
private native int openPort
( String portName
) throws NoSuchPortException, NotASerialPortException
;
/**
* Close this port, release all native resources
*/
private native void doClose
();
/**
* Set the baud rate in the native code.
*
* @param baudRate
* @return
*/
private native boolean setBaudRate
( int baudRate
);
private native int getBaudRateInternal
();
/**
* Set the number of stop bits, once the port has been opened.
*
* @param stopBits
* @return
*/
private native boolean setStopBits
( int stopBits
);
private native int getStopBitsInternal
();
/**
* Set the character size, once the port has been opened.
* This should probably be called 'setDataBits'
*
* @param charSize
* @return
*/
private native boolean setCharSize
( int charSize
);
private native int getCharSizeInternal
();
/** Set the parity once the port has been opened.
*
* @param parity 0 = None, 1 = Odd, 2 = Even
* @return
*/
private native boolean setParity
( int parity
);
private native int getParityInternal
();
/** Set the flow control once the port has been opened.
*
*
* @param flowControl 0 = None, 1 = hardware, 2 = software
* @return
*/
private native boolean setFlowControl
( int flowControl
);
private native int getFlowControlInternal
();
/** Get the serial line state, but don't block when getting it
*
* @return
*/
private native int getSerialLineStateInternalNonblocking
();
/**
* Set the state of the serial line.
* @return
*/
private native int setSerialLineStateInternal
( SerialLineState s
);
//
// Static Methods
//
/**
* Get the major version of this library. For example, if this is version
* 0.2, this returns 0
*/
public static int getMajorVersion
(){
return 0;
}
/**
* Get the minor version of this library. For example, if this is version
* 0.2, this returns 2.
*/
public static int getMinorVersion
(){
return 3;
}
/**
* Get the major version of the native code. This should match up with
* {@link #getMajorVersion() getMajorVersion()}, although this is not
* guaranteed. For example, if this is version 0.2, this returns 0
*/
public static native int getMajorNativeVersion
();
/**
* Get the minor version of the native code. This should match up with
* {@link #getMinorVersion() getMinorVersion()}, although this is not
* guaranteed. For example, if this is version 0.2, this returns 2.
*/
public static native int getMinorNativeVersion
();
/**
* <p>
* Get an array of all the serial ports on the system. For example, on
* Windows this will return {@code { "COM1", "COM3", .... } } depending on how
* many serial devices you have plugged in. On Linux, this returns
* {@code { "/dev/ttyS0", "/dev/ttyUSB0", "/dev/symlink", ... } }
* It will not resolve symlinks, such that if there is a symlink
* from {@code /dev/symlink } to {@code /dev/ttyUSB0 }, they will both show up.
* </p>
* <p>
* <b>NOTE:</b> this will only return ports that you have permissions to
* open.
* </p>
*
* @return
*/
public static native String[] getSerialPorts
();
}