Subversion Repositories Programming Utils

Rev

Rev 96 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.rm5248.serial;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * A SerialPort represents a serial port on the system.
 *
 * All SerialPorts must extend this class.  It is unable to be instantiated, except
 * by subclasses.
 *
 * See the documentation for the subclasses on how to create
 * a specific instance of a SerialPort.  The two main subclasses are IOSerialPort and
 * NIOSerialPort.
 *
 * @author rm5248
 *
 */

public class SerialPort {
       
        //The first time this class is referenced, we need to load the library.
        static{
                loadNativeLibrary();
        }
       
        /**
         * Load the native library.
         *
         * There are two important system properties that can be set here:
         *
         * com.rm5248.javaserial.lib.path - give the directory name that the JNI code is located in
         * com.rm5248.javaserial.lib.name - explicitly give the name of the library(the default is 'javaserial')
         *
         * This is based largely off of SQLite-JDBC( https://github.com/xerial/sqlite-jdbc )
         */

        private static void loadNativeLibrary() {
                String nativeLibraryPath = System.getProperty( "com.rm5248.javaserial.lib.path" );
                String nativeLibraryName = System.getProperty( "com.rm5248.javaserial.lib.name" );
               
                if( nativeLibraryName == null ){
                        nativeLibraryName = System.mapLibraryName( "javaserial" );
                        if( nativeLibraryName.endsWith( "dylib" ) ){
                                //mac uses jnilib instead of dylib for some reason
                                nativeLibraryName.replace( "dylib", "jnilib" );
                        }
                }
               
                if( nativeLibraryPath != null ){
                        File libToLoad = new File(nativeLibraryPath, nativeLibraryName);
                        System.load( libToLoad.getAbsolutePath() );
                        return;
                }
               
                //if we get here, that means that we must extract the JNI from the jar
               
                try {
                        File extractedLib;
                        Path tempFolder;
                        String osName;
                        String arch;
                        InputStream library;
                       
                        osName = System.getProperty( "os.name" );
                        if( osName.contains( "Windows" ) ){
                                osName = "Windows";
                        }else if( osName.contains( "Mac" ) || osName.contains( "Darwin" ) ){
                                osName = "Mac";
                        }else if( osName.contains( "Linux" ) ){
                                osName = "Linux";
                        }else{
                                osName = osName.replaceAll( "\\W", "" );
                        }
                       
                        arch = System.getProperty( "os.arch" );
                        arch.replaceAll( "\\W", "" );
                       
                        //create the temp folder to extract the library to
                        tempFolder = Files.createTempDirectory( "jserial" );
                        tempFolder.toFile().deleteOnExit();
                       
                        extractedLib = new File( tempFolder.toFile(), nativeLibraryName );
                       
                        //now let's extract the proper library
                        library = SerialPort.class.getResourceAsStream( "NativeCode/" +
                                        osName + "/" + arch + "/" + nativeLibraryName );
                        Files.copy( library, extractedLib.toPath() );
                       
                        System.load( extractedLib.getAbsolutePath() );
                } catch (IOException e) {
                        throw new UnsatisfiedLinkError( "Unable to create temp directory or extract: " + e.getMessage() );
                }
        }
       
        /**
         * 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
        }
       
        /* 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.
         */

        protected int handle;
        /* Make sure we don't close ourselves twice */
        protected boolean closed;
        /* The name of the port that's currently open */
        private String portName;
        /* Cache of the last gotten serial line state */
        protected volatile SerialLineState state;
       
        /**
         * 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
         */

        protected 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);

                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;
        }
       
        /**
         * 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
         */

        protected 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;
                        this.closed = false;
                }else{
                        this.handle = openPort( portName, 9600, 8, 1, 0, 0 );
                }
        }

        /**
         * 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 );
        }
       
        public boolean isClosed(){
                return closed;
        }

        public void finalize(){
                close();
        }
       
        public void close(){
                if( closed ) return;
                closed = true;
                doClose();
        }
       

        /**
         * 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 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 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 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 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 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 ){
        }

        /**
         * Get the name of the serial port that this object represents.
         * @return
         */

        public String getPortName(){
                return portName;
        }
       
        /**
         * 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
         */

        protected 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 5;
        }
       
        /**
         * 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();

}