/* * Copyright (c) 2011, Kustaa Nyholm / SpareTimeLabs * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ package purejavacomm; // FIXME move javadoc comments for input stream to SerialPort.java import java.io.*; import java.util.*; import com.sun.jna.Native; import jtermios.*; import static jtermios.JTermios.JTermiosLogging.*; import static jtermios.JTermios.*; public class PureJavaSerialPort extends SerialPort { final boolean USE_POLL; final boolean RAW_READ_MODE; private Thread m_Thread; private volatile SerialPortEventListener m_EventListener; private volatile OutputStream m_OutputStream; private volatile InputStream m_InputStream; private volatile int m_FD = -1; private volatile boolean m_HaveNudgePipe = false; private volatile int m_PipeWrFD = 0; private volatile int m_PipeRdFD = 0; private byte[] m_NudgeData = { 0 }; private volatile int m_BaudRate; private volatile int m_DataBits; private volatile int m_FlowControlMode; private volatile int m_Parity; private volatile int m_StopBits; private volatile Object m_ThresholdTimeoutLock = new Object(); private volatile boolean m_TimeoutThresholdChanged = true; private volatile boolean m_ReceiveTimeoutEnabled; private volatile int m_ReceiveTimeoutValue; private volatile int m_ReceiveTimeoutVTIME; private volatile boolean m_ReceiveThresholdEnabled; private volatile int m_ReceiveThresholdValue; private volatile boolean m_PollingReadMode; private volatile boolean m_NotifyOnDataAvailable; private volatile boolean m_DataAvailableNotified; private volatile boolean m_NotifyOnOutputEmpty; private volatile boolean m_OutputEmptyNotified; private volatile boolean m_NotifyOnRI; private volatile boolean m_NotifyOnCTS; private volatile boolean m_NotifyOnDSR; private volatile boolean m_NotifyOnCD; private volatile boolean m_NotifyOnOverrunError; private volatile boolean m_NotifyOnParityError; private volatile boolean m_NotifyOnFramingError; private volatile boolean m_NotifyOnBreakInterrupt; private volatile boolean m_ThreadRunning; private volatile boolean m_ThreadStarted; private int[] m_ioctl = { 0 }; private int m_ControlLineStates; // we cache termios in m_Termios because we don't rely on reading it back with tcgetattr() // which for Mac OS X / CRTSCTS does not work, it is also more efficient private Termios m_Termios = new Termios(); private int m_MinVTIME; private void sendDataEvents(boolean read, boolean write) { if (read && m_NotifyOnDataAvailable && !m_DataAvailableNotified) { m_DataAvailableNotified = true; m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.DATA_AVAILABLE, false, true)); } if (write && m_NotifyOnOutputEmpty && !m_OutputEmptyNotified) { m_OutputEmptyNotified = true; m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.OUTPUT_BUFFER_EMPTY, false, true)); } } private synchronized void sendNonDataEvents() { if (ioctl(m_FD, TIOCMGET, m_ioctl) < 0) return; // FIXME decide what to with errors in the background thread int oldstates = m_ControlLineStates; m_ControlLineStates = m_ioctl[0]; int newstates = m_ControlLineStates; int changes = oldstates ^ newstates; if (changes == 0) return; int line; if (m_NotifyOnCTS && (((line = TIOCM_CTS) & changes) != 0)) m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.CTS, (oldstates & line) != 0, (newstates & line) != 0)); if (m_NotifyOnDSR && (((line = TIOCM_DSR) & changes) != 0)) m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.DSR, (oldstates & line) != 0, (newstates & line) != 0)); if (m_NotifyOnRI && (((line = TIOCM_RI) & changes) != 0)) m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.RI, (oldstates & line) != 0, (newstates & line) != 0)); if (m_NotifyOnCD && (((line = TIOCM_CD) & changes) != 0)) m_EventListener.serialEvent(new SerialPortEvent(this, SerialPortEvent.CD, (oldstates & line) != 0, (newstates & line) != 0)); } @Override synchronized public void addEventListener(SerialPortEventListener eventListener) throws TooManyListenersException { checkState(); if (eventListener == null) throw new IllegalArgumentException("eventListener cannot be null"); if (m_EventListener != null) throw new TooManyListenersException(); m_EventListener = eventListener; if (!m_ThreadStarted) { m_ThreadStarted = true; m_Thread.start(); } } @Override synchronized public int getBaudRate() { checkState(); return m_BaudRate; } @Override synchronized public int getDataBits() { checkState(); return m_DataBits; } @Override synchronized public int getFlowControlMode() { checkState(); return m_FlowControlMode; } @Override synchronized public int getParity() { checkState(); return m_Parity; } @Override synchronized public int getStopBits() { checkState(); return m_StopBits; } @Override synchronized public boolean isCD() { checkState(); return getControlLineState(TIOCM_CD); } @Override synchronized public boolean isCTS() { checkState(); return getControlLineState(TIOCM_CTS); } @Override synchronized public boolean isDSR() { checkState(); return getControlLineState(TIOCM_DSR); } @Override synchronized public boolean isDTR() { checkState(); return getControlLineState(TIOCM_DTR); } @Override synchronized public boolean isRI() { checkState(); return getControlLineState(TIOCM_RI); } @Override synchronized public boolean isRTS() { checkState(); return getControlLineState(TIOCM_RTS); } @Override synchronized public void notifyOnBreakInterrupt(boolean x) { checkState(); m_NotifyOnBreakInterrupt = x; } @Override synchronized public void notifyOnCTS(boolean x) { checkState(); if (x) updateControlLineState(TIOCM_CTS); m_NotifyOnCTS = x; nudgePipe(); } @Override synchronized public void notifyOnCarrierDetect(boolean x) { checkState(); if (x) updateControlLineState(TIOCM_CD); m_NotifyOnCD = x; nudgePipe(); } @Override synchronized public void notifyOnDSR(boolean x) { checkState(); if (x) updateControlLineState(TIOCM_DSR); m_NotifyOnDSR = x; nudgePipe(); } @Override synchronized public void notifyOnDataAvailable(boolean x) { checkState(); m_NotifyOnDataAvailable = x; nudgePipe(); } @Override synchronized public void notifyOnFramingError(boolean x) { checkState(); m_NotifyOnFramingError = x; } @Override synchronized public void notifyOnOutputEmpty(boolean x) { checkState(); m_NotifyOnOutputEmpty = x; nudgePipe(); } @Override synchronized public void notifyOnOverrunError(boolean x) { checkState(); m_NotifyOnOverrunError = x; } @Override synchronized public void notifyOnParityError(boolean x) { checkState(); m_NotifyOnParityError = x; } @Override synchronized public void notifyOnRingIndicator(boolean x) { checkState(); if (x) updateControlLineState(TIOCM_RI); m_NotifyOnRI = x; nudgePipe(); } @Override synchronized public void removeEventListener() { checkState(); m_EventListener = null; } @Override synchronized public void sendBreak(int duration) { checkState(); // FIXME POSIX does not specify how duration is interpreted // Opengroup POSIX says: // If the terminal is using asynchronous serial data transmission, tcsendbreak() // shall cause transmission of a continuous stream of zero-valued bits for a specific duration. // If duration is 0, it shall cause transmission of zero-valued bits for at least 0.25 seconds, // and not more than 0.5 seconds. If duration is not 0, it shall send zero-valued bits for an implementation-defined period of time. // From the man page for Linux tcsendbreak: // The effect of a non-zero duration with tcsendbreak() varies. // SunOS specifies a break of duration*N seconds, // where N is at least 0.25, and not more than 0.5. Linux, AIX, DU, Tru64 send a break of duration milliseconds. // FreeBSD and NetBSD and HP-UX and MacOS ignore the value of duration. // Under Solaris and Unixware, tcsendbreak() with non-zero duration behaves like tcdrain(). tcsendbreak(m_FD, duration); } @Override synchronized public void setDTR(boolean x) { checkState(); setControlLineState(TIOCM_DTR, x); } @Override synchronized public void setRTS(boolean x) { checkState(); setControlLineState(TIOCM_RTS, x); } @Override synchronized public void disableReceiveFraming() { checkState(); // Not supported } @Override synchronized public void disableReceiveThreshold() { checkState(); synchronized (m_ThresholdTimeoutLock) { m_ReceiveThresholdEnabled = false; thresholdOrTimeoutChanged(); } } @Override synchronized public void disableReceiveTimeout() { checkState(); synchronized (m_ThresholdTimeoutLock) { m_ReceiveTimeoutEnabled = false; thresholdOrTimeoutChanged(); } } @Override synchronized public void enableReceiveThreshold(int value) throws UnsupportedCommOperationException { checkState(); if (value < 0) throw new IllegalArgumentException("threshold" + value + " < 0 "); if (RAW_READ_MODE && value > 255) throw new IllegalArgumentException("threshold" + value + " > 255 in raw read mode"); synchronized (m_ThresholdTimeoutLock) { m_ReceiveThresholdEnabled = true; m_ReceiveThresholdValue = value; thresholdOrTimeoutChanged(); } } @Override synchronized public void enableReceiveTimeout(int value) throws UnsupportedCommOperationException { if (value < 0) throw new IllegalArgumentException("threshold" + value + " < 0 "); if (value > 25500) throw new UnsupportedCommOperationException("threshold" + value + " > 25500 "); checkState(); synchronized (m_ThresholdTimeoutLock) { m_ReceiveTimeoutEnabled = true; m_ReceiveTimeoutValue = value; thresholdOrTimeoutChanged(); } } @Override synchronized public void enableReceiveFraming(int arg0) throws UnsupportedCommOperationException { checkState(); throw new UnsupportedCommOperationException("receive framing not supported/implemented"); } private void thresholdOrTimeoutChanged() { // only call if you hold the lock m_PollingReadMode = (m_ReceiveTimeoutEnabled && m_ReceiveTimeoutValue == 0) || (m_ReceiveThresholdEnabled && m_ReceiveThresholdValue == 0); m_ReceiveTimeoutVTIME = (m_ReceiveTimeoutValue + 99) / 100; // precalculate this so we don't need the division in read m_TimeoutThresholdChanged = true; } @Override synchronized public int getInputBufferSize() { checkState(); // Not supported return 0; } @Override synchronized public int getOutputBufferSize() { checkState(); // Not supported return 0; } @Override synchronized public void setFlowControlMode(int mode) throws UnsupportedCommOperationException { checkState(); synchronized (m_Termios) { m_Termios.c_iflag &= ~IXANY; if ((mode & (FLOWCONTROL_RTSCTS_IN | FLOWCONTROL_RTSCTS_OUT)) != 0) m_Termios.c_cflag |= CRTSCTS; else m_Termios.c_cflag &= ~CRTSCTS; if ((mode & FLOWCONTROL_XONXOFF_IN) != 0) m_Termios.c_iflag |= IXOFF; else m_Termios.c_iflag &= ~IXOFF; if ((mode & FLOWCONTROL_XONXOFF_OUT) != 0) m_Termios.c_iflag |= IXON; else m_Termios.c_iflag &= ~IXON; checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios)); m_FlowControlMode = mode; } } @Override synchronized public void setSerialPortParams(int baudRate, int dataBits, int stopBits, int parity) throws UnsupportedCommOperationException { checkState(); synchronized (m_Termios) { Termios prev = new Termios();// (termios); // save a copy in case we need to restore it prev.set(m_Termios); try { checkReturnCode(setspeed(m_FD, m_Termios, baudRate)); int db; switch (dataBits) { case SerialPort.DATABITS_5: db = CS5; break; case SerialPort.DATABITS_6: db = CS6; break; case SerialPort.DATABITS_7: db = CS7; break; case SerialPort.DATABITS_8: db = CS8; break; default: throw new UnsupportedCommOperationException("dataBits = " + dataBits); } int sb; switch (stopBits) { case SerialPort.STOPBITS_1: sb = 1; break; case SerialPort.STOPBITS_1_5: // This setting must have been copied from the Win32 API and // hasn't been properly thought through. 1.5 stop bits are // only valid with 5 data bits and replace the 2 stop bits // in this mode. This is a feature of the 16550 and even // documented on MSDN // As nobody is aware of course, we silently use 1.5 and 2 // stop bits interchangeably (just as the hardware does) // Many linux drivers follow this convention and termios // can't even differ between 1.5 and 2 stop bits sb = 2; break; case SerialPort.STOPBITS_2: sb = 2; break; default: throw new UnsupportedCommOperationException("stopBits = " + stopBits); } int fi = m_Termios.c_iflag & ~(INPCK | ISTRIP); int fc = m_Termios.c_cflag & ~(PARENB | CMSPAR | PARODD); switch (parity) { case SerialPort.PARITY_NONE: break; case SerialPort.PARITY_EVEN: fc |= PARENB; break; case SerialPort.PARITY_ODD: fc |= PARENB; fc |= PARODD; break; case SerialPort.PARITY_MARK: fc |= PARENB; fc |= CMSPAR; fc |= PARODD; break; case SerialPort.PARITY_SPACE: fc |= PARENB; fc |= CMSPAR; break; default: throw new UnsupportedCommOperationException("parity = " + parity); } // update the hardware fc &= ~CSIZE; /* Mask the character size bits */ fc |= db; /* Set data bits */ if (sb == 2) fc |= CSTOPB; else fc &= ~CSTOPB; m_Termios.c_cflag = fc; m_Termios.c_iflag = fi; if (tcsetattr(m_FD, TCSANOW, m_Termios) != 0) throw new UnsupportedCommOperationException("tcsetattr failed"); // Even if termios(3) tells us that tcsetattr succeeds if any change // has been made, not necessary all of them we cannot check them by reading back // and checking the result as not every driver/OS playes by the rules // finally everything went ok, so we can update our settings m_BaudRate = baudRate; m_Parity = parity; m_DataBits = dataBits; m_StopBits = stopBits; } catch (UnsupportedCommOperationException e) { m_Termios.set(prev); checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios)); throw e; } catch (IllegalStateException e) { m_Termios.set(prev); checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios)); if (e instanceof PureJavaIllegalStateException) { throw e; } else { throw new PureJavaIllegalStateException(e); } } } } /** * Gets the native file descriptor used by this port. * <p> * The file descriptor can be used in calls to JTermios functions. This * maybe useful in extreme cases where performance is more important than * convenience, for example using <code>JTermios.read(...)</code> instead of * <code>SerialPort.getInputStream().read(...)</code>. * <p> * Note that mixing direct JTermios read/write calls with SerialPort stream * read/write calls is at best fragile and likely to fail, which also * implies that when using JTermios directly then configuring the port, * especially termios.cc[VMIN] and termios.cc[VTIME] is the users * responsibility. * <p> * Below is a sketch of minimum necessary to perform a read using raw * JTermios functionality. * * <pre> * // import the JTermios functionality like this * import jtermios.*; * import static jtermios.JTermios.*; * * SerialPort port = ...; * * // cast the port to PureJavaSerialPort to get access to getNativeFileDescriptor * int FD = ((PureJavaSerialPort) port).getNativeFileDescriptor(); * * // timeout and threshold values * int messageLength = 25; // bytes * int timeout = 200; // msec * * // to initialize timeout and threshold first read current termios * Termios termios = new Termios(); * * if (0 != tcgetattr(FD, termios)) * errorHandling(); * * // then set VTIME and VMIN, note VTIME in 1/10th of sec and both max 255 * termios.c_cc[VTIME] = (byte) ((timeout+99) / 100); * termios.c_cc[VMIN] = (byte) messageLength; * * // update termios * if (0 != tcsetattr(FD, TCSANOW, termios)) * errorHandling(); * * ... * // allocate read buffer * byte[] readBuffer = new byte[messageLength]; * ... * * // then perform raw read, not this may block indefinitely * int n = read(FD, readBuffer, messageLength); * if (n < 0) * errorHandling(); * </pre> * * @return the native OS file descriptor as int */ public int getNativeFileDescriptor() { return m_FD; } @Override synchronized public OutputStream getOutputStream() throws IOException { checkState(); if (m_OutputStream == null) { m_OutputStream = new OutputStream() { // im_ for inner class member private byte[] im_Buffer = new byte[2048]; @Override final public void write(int b) throws IOException { checkState(); byte[] buf = { (byte) b }; write(buf, 0, 1); } @Override final public void write(byte[] buffer, int offset, int length) throws IOException { if (buffer == null) throw new IllegalArgumentException(); if (offset < 0 || length < 0 || offset + length > buffer.length) throw new IndexOutOfBoundsException("buffer.lengt " + buffer.length + " offset " + offset + " length " + length); checkState(); while (length > 0) { int n = buffer.length - offset; if (n > im_Buffer.length) n = im_Buffer.length; if (n > length) n = length; if (offset > 0) { System.arraycopy(buffer, offset, im_Buffer, 0, n); n = jtermios.JTermios.write(m_FD, im_Buffer, n); } else n = jtermios.JTermios.write(m_FD, buffer, n); if (n < 0) { PureJavaSerialPort.this.close(); throw new IOException(); } length -= n; offset += n; } m_OutputEmptyNotified = false; } @Override final public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override public void close() throws IOException { super.close(); } @Override final public void flush() throws IOException { checkState(); if (tcdrain(m_FD) < 0) { close(); throw new IOException(); } } }; } return m_OutputStream; } synchronized public InputStream getInputStream() throws IOException { checkState(); if (m_InputStream == null) { // NOTE: Windows and unixes are so different that it actually might // make sense to have the backend (ie JTermiosImpl) to provide // an InputStream that is optimal for the platform, instead of // trying to share of the InputStream logic here and force // Windows backend to conform to the the POSIX select()/ // read()/vtim/vtime model. See the amount of code here // and in windows.JTermiosImpl for select() and read(). // m_InputStream = new InputStream() { // im_ for inner class members private int[] im_Available = { 0 }; private byte[] im_Buffer = new byte[2048]; // this stuff is just cached/precomputed stuff to make read() faster private int im_VTIME = -1; private int im_VMIN = -1; private final jtermios.Pollfd[] im_ReadPollFD = new Pollfd[] { new Pollfd(), new Pollfd() }; private byte[] im_Nudge; private FDSet im_ReadFDSet; private TimeVal im_ReadTimeVal; private int im_PollFDn; private boolean im_ReceiveTimeoutEnabled; private int im_ReceiveTimeoutValue; private boolean im_ReceiveThresholdEnabled; private int im_ReceiveThresholdValue; private boolean im_PollingReadMode; private int im_ReceiveTimeoutVTIME; { // initialized block instead of construct in anonymous class im_ReadFDSet = newFDSet(); im_ReadTimeVal = new TimeVal(); im_ReadPollFD[0].fd = m_FD; im_ReadPollFD[0].events = POLLIN; im_ReadPollFD[1].fd = m_PipeRdFD; im_ReadPollFD[1].events = POLLIN; im_PollFDn = m_HaveNudgePipe ? 2 : 1; im_Nudge = new byte[1]; } @Override final public int available() throws IOException { checkState(); if (ioctl(m_FD, FIONREAD, im_Available) < 0) { PureJavaSerialPort.this.close(); System.out.println(Native.getLastError()); throw new IOException(); } return im_Available[0]; } @Override final public int read() throws IOException { checkState(); byte[] buf = { 0 }; int n = read(buf, 0, 1); return n > 0 ? buf[0] & 0xFF : -1; } @Override public void close() throws IOException { super.close(); } @Override final public int read(byte[] buffer, int offset, int length) throws IOException { // reads++; if (buffer == null) throw new IllegalArgumentException("buffer null"); if (length == 0) return 0; if (offset < 0 || length < 0 || offset + length > buffer.length) throw new IndexOutOfBoundsException("buffer.length " + buffer.length + " offset " + offset + " length " + length); if (RAW_READ_MODE) { if (m_TimeoutThresholdChanged) { // does not need the lock if we just check the value synchronized (m_ThresholdTimeoutLock) { int vtime = m_ReceiveTimeoutEnabled ? m_ReceiveTimeoutVTIME : 0; int vmin = m_ReceiveThresholdEnabled ? m_ReceiveThresholdValue : 1; synchronized (m_Termios) { m_Termios.c_cc[VTIME] = (byte) vtime; m_Termios.c_cc[VMIN] = (byte) vmin; checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios)); } m_TimeoutThresholdChanged = false; } } int bytesRead; if (offset > 0) { if (length < im_Buffer.length) bytesRead = jtermios.JTermios.read(m_FD, im_Buffer, length); else bytesRead = jtermios.JTermios.read(m_FD, im_Buffer, im_Buffer.length); if (bytesRead > 0) System.arraycopy(im_Buffer, 0, buffer, offset, bytesRead); } else bytesRead = jtermios.JTermios.read(m_FD, buffer, length); m_DataAvailableNotified = false; return bytesRead; } // End of raw read mode code if (m_FD < 0) // replaces checkState call failWithIllegalStateException(); if (m_TimeoutThresholdChanged) { // does not need the lock if we just check the alue synchronized (m_ThresholdTimeoutLock) { // capture these here under guard so that we get a coherent picture of the settings im_ReceiveTimeoutEnabled = m_ReceiveTimeoutEnabled; im_ReceiveTimeoutValue = m_ReceiveTimeoutValue; im_ReceiveThresholdEnabled = m_ReceiveThresholdEnabled; im_ReceiveThresholdValue = m_ReceiveThresholdValue; im_PollingReadMode = m_PollingReadMode; im_ReceiveTimeoutVTIME = m_ReceiveTimeoutVTIME; m_TimeoutThresholdChanged = false; } } int bytesLeft = length; int bytesReceived = 0; int minBytesRequired; // Note for optimal performance: message length == receive threshold == read length <= 255 // the best case execution path is marked with BEST below while (true) { // loops++; int vmin; int vtime; if (im_PollingReadMode) { minBytesRequired = 0; vmin = 0; vtime = 0; } else { if (im_ReceiveThresholdEnabled) minBytesRequired = im_ReceiveThresholdValue; // BEST else minBytesRequired = 1; if (minBytesRequired > bytesLeft) // in BEST case 'if' not taken minBytesRequired = bytesLeft; if (minBytesRequired <= 255) vmin = minBytesRequired; // BEST case else vmin = 255; // FIXME someone might change m_ReceiveTimeoutEnabled if (im_ReceiveTimeoutEnabled) vtime = im_ReceiveTimeoutVTIME; // BEST case else vtime = 0; } if (vmin != im_VMIN || vtime != im_VTIME) { // in BEST case 'if' not taken more than once for given InputStream instance // ioctls++; im_VMIN = vmin; im_VTIME = vtime; // This needs to be guarded with m_Termios so that these thing don't change on us synchronized (m_Termios) { m_Termios.c_cc[VTIME] = (byte) im_VTIME; m_Termios.c_cc[VMIN] = (byte) im_VMIN; checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios)); } } // Now wait for data to be available, except in raw read mode // and polling read modes. Following looks a bit longish // but there is actually not that much code to be executed boolean dataAvailable = false; boolean timedout = false; if (!im_PollingReadMode) { int n; // long T0 = System.nanoTime(); // do a select()/poll(), just in case this read was // called when no data is available // so that we will not hang for ever in a read int timeoutValue = im_ReceiveTimeoutEnabled ? im_ReceiveTimeoutValue : Integer.MAX_VALUE; if (USE_POLL) { // BEST case in Linux but not on // Windows or Mac OS X n = poll(im_ReadPollFD, im_PollFDn, timeoutValue); if (n < 0 || m_FD < 0) // the port closed while we were blocking in poll throw new IOException(); if ((im_ReadPollFD[1].revents & POLLIN) != 0) jtermios.JTermios.read(m_PipeRdFD, im_Nudge, 1); int re = im_ReadPollFD[0].revents; if ((re & POLLNVAL_OUT) != 0) throw new IOException(); dataAvailable = (re & POLLIN) != 0; } else { // this is a bit slower but then again it is unlikely // this gets executed in a low horsepower system FD_ZERO(im_ReadFDSet); FD_SET(m_FD, im_ReadFDSet); int maxFD = m_FD; if (m_HaveNudgePipe) { FD_SET(m_PipeRdFD, im_ReadFDSet); if (m_PipeRdFD > maxFD) maxFD = m_PipeRdFD; } if (timeoutValue >= 1000) { int t = timeoutValue / 1000; im_ReadTimeVal.tv_sec = t; im_ReadTimeVal.tv_usec = (timeoutValue - t * 1000) * 1000; } else { im_ReadTimeVal.tv_sec = 0; im_ReadTimeVal.tv_usec = timeoutValue * 1000; } n = select(maxFD + 1, im_ReadFDSet, null, null, im_ReadTimeVal); if (n < 0) throw new IOException(); if (m_FD < 0) // the port closed while we were // blocking in select throw new IOException(); dataAvailable = FD_ISSET(m_FD, im_ReadFDSet); } if (n == 0 && m_ReceiveTimeoutEnabled) timedout = true; } if (timedout) break; // At this point data is either available or we take our // chances in raw mode or this polling read which can't block int bytesRead = 0; if (dataAvailable || im_PollingReadMode) { if (offset > 0) { if (bytesLeft < im_Buffer.length) bytesRead = jtermios.JTermios.read(m_FD, im_Buffer, bytesLeft); else bytesRead = jtermios.JTermios.read(m_FD, im_Buffer, im_Buffer.length); if (bytesRead > 0) System.arraycopy(im_Buffer, 0, buffer, offset, bytesRead); } else // this the BEST case execution path bytesRead = jtermios.JTermios.read(m_FD, buffer, bytesLeft); // readtime += System.nanoTime() - T0; if (bytesRead == 0) timedout = true; } // Now we have read data and try to return as quickly as // possibly or we have timed out. if (bytesRead < 0) // an error occured throw new IOException(); bytesReceived += bytesRead; if (bytesReceived >= minBytesRequired) // BEST case this if is taken and we exit break; // we have read the minimum required and will return that if (timedout) break; // Ok, looks like we are in for an other loop, so update // the offset // and loop for some more offset += bytesRead; bytesLeft -= bytesRead; } m_DataAvailableNotified = false; return bytesReceived; } }; } return m_InputStream; } @Override synchronized public int getReceiveFramingByte() { checkState(); // Not supported return 0; } @Override synchronized public int getReceiveThreshold() { checkState(); return m_ReceiveThresholdValue; } @Override synchronized public int getReceiveTimeout() { checkState(); return m_ReceiveTimeoutValue; } @Override synchronized public boolean isReceiveFramingEnabled() { checkState(); // Not supported return false; } @Override synchronized public boolean isReceiveThresholdEnabled() { checkState(); return m_ReceiveThresholdEnabled; } @Override synchronized public boolean isReceiveTimeoutEnabled() { checkState(); return m_ReceiveTimeoutEnabled; } @Override synchronized public void setInputBufferSize(int arg0) { checkState(); // Not supported } @Override synchronized public void setOutputBufferSize(int arg0) { checkState(); // Not supported } private void nudgePipe() { if (m_HaveNudgePipe) write(m_PipeWrFD, m_NudgeData, 1); } @Override synchronized public void close() { int fd = m_FD; if (fd != -1) { m_FD = -1; try { if (m_InputStream != null) m_InputStream.close(); } catch (IOException e) { log = log && log(1, "m_InputStream.close threw an IOException %s\n", e.getMessage()); } finally { m_InputStream = null; } try { if (m_OutputStream != null) m_OutputStream.close(); } catch (IOException e) { log = log && log(1, "m_OutputStream.close threw an IOException %s\n", e.getMessage()); } finally { m_OutputStream = null; } nudgePipe(); int flags = fcntl(fd, F_GETFL, 0); flags |= O_NONBLOCK; int fcres = fcntl(fd, F_SETFL, flags); if (fcres != 0) // not much we can do if this fails, so just log it log = log && log(1, "fcntl(%d,%d,%d) returned %d\n", fd, F_SETFL, flags, fcres); if (m_Thread != null) m_Thread.interrupt(); int err = jtermios.JTermios.close(fd); if (err < 0) log = log && log(1, "JTermios.close returned %d, errno %d\n", err, errno()); if (m_HaveNudgePipe) { err = jtermios.JTermios.close(m_PipeRdFD); if (err < 0) log = log && log(1, "JTermios.close returned %d, errno %d\n", err, errno()); err = jtermios.JTermios.close(m_PipeWrFD); if (err < 0) log = log && log(1, "JTermios.close returned %d, errno %d\n", err, errno()); } long t0 = System.currentTimeMillis(); while (m_ThreadRunning) { try { Thread.sleep(5); if (System.currentTimeMillis() - t0 > 2000) break; } catch (InterruptedException e) { break; } } super.close(); } } /* package */PureJavaSerialPort(String name, int timeout) throws PortInUseException { super(); boolean usepoll = false; if (JTermios.canPoll()) { String key1 = "purejavacomm.use_poll"; String key2 = "purejavacomm.usepoll"; if (System.getProperty(key1) != null) { usepoll = Boolean.getBoolean(key1); log = log && log(1, "use of '%s' is deprecated, use '%s' instead\n", key1, key2); } else if (System.getProperty(key2) != null) usepoll = Boolean.getBoolean(key2); else usepoll = true; } USE_POLL = usepoll; RAW_READ_MODE = Boolean.getBoolean("purejavacomm.rawreadmode"); this.name = name; int tries = (timeout + 5) / 10; while ((m_FD = open(name, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) { int errno = errno(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } if (tries-- < 0) throw new PortInUseException("Unknown Application", errno); } m_MinVTIME = Integer.getInteger("purejavacomm.minvtime", 100); int flags = fcntl(m_FD, F_GETFL, 0); if (flags<0) checkReturnCode(flags); flags &= ~O_NONBLOCK; checkReturnCode(fcntl(m_FD, F_SETFL, flags)); m_BaudRate = 9600; m_DataBits = SerialPort.DATABITS_8; m_FlowControlMode = SerialPort.FLOWCONTROL_NONE; m_Parity = SerialPort.PARITY_NONE; m_StopBits = SerialPort.STOPBITS_1; checkReturnCode(tcgetattr(m_FD, m_Termios)); cfmakeraw(m_FD, m_Termios); m_Termios.c_cflag |= CLOCAL | CREAD; m_Termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); m_Termios.c_oflag &= ~OPOST; m_Termios.c_cc[VSTART] = (byte) DC1; m_Termios.c_cc[VSTOP] = (byte) DC3; m_Termios.c_cc[VMIN] = 0; m_Termios.c_cc[VTIME] = 0; checkReturnCode(tcsetattr(m_FD, TCSANOW, m_Termios)); try { setSerialPortParams(m_BaudRate, m_DataBits, m_StopBits, m_Parity); } catch (UnsupportedCommOperationException e) { // This really should not happen e.printStackTrace(); } try { setFlowControlMode(SerialPort.FLOWCONTROL_NONE); } catch (UnsupportedCommOperationException e) { // This really should not happen e.printStackTrace(); } int res = ioctl(m_FD, TIOCMGET, m_ioctl); if (res == 0) m_ControlLineStates = m_ioctl[0]; else log = log && log(1, "ioctl(TIOCMGET) returned %d, errno %d\n", res, errno()); String nudgekey = "purejavacomm.usenudgepipe"; if (System.getProperty(nudgekey) == null || Boolean.getBoolean(nudgekey)) { int[] pipes = new int[2]; if (pipe(pipes) == 0) { m_HaveNudgePipe = true; m_PipeRdFD = pipes[0]; m_PipeWrFD = pipes[1]; checkReturnCode(fcntl(m_PipeRdFD, F_SETFL, fcntl(m_PipeRdFD, F_GETFL, 0) | O_NONBLOCK)); } } Runnable runnable = new Runnable() { public void run() { try { m_ThreadRunning = true; // see: http://daniel.haxx.se/docs/poll-vs-select.html final int TIMEOUT = Integer.getInteger("purejavacomm.pollperiod", 10); TimeVal timeout = null; FDSet rset = null; FDSet wset = null; jtermios.Pollfd[] pollfd = null; byte[] nudge = null; if (USE_POLL) { pollfd = new Pollfd[] { new Pollfd(), new Pollfd() }; nudge = new byte[1]; pollfd[0].fd = m_FD; pollfd[1].fd = m_PipeRdFD; } else { rset = newFDSet(); wset = newFDSet(); timeout = new TimeVal(); int t = TIMEOUT * 1000; timeout.tv_sec = t / 1000000; timeout.tv_usec = t - timeout.tv_sec * 1000000; } while (m_FD >= 0) { boolean read = (m_NotifyOnDataAvailable && !m_DataAvailableNotified); boolean write = (m_NotifyOnOutputEmpty && !m_OutputEmptyNotified); int n = 0; boolean pollCtrlLines = m_NotifyOnCTS || m_NotifyOnDSR || m_NotifyOnRI || m_NotifyOnCD; if (read || write || (!pollCtrlLines && m_HaveNudgePipe)) { if (USE_POLL) { short e = 0; if (read) e |= POLLIN; if (write) e |= POLLOUT; pollfd[0].events = e; pollfd[1].events = POLLIN; if (m_HaveNudgePipe) n = poll(pollfd, 2, TIMEOUT); else n = poll(pollfd, 1, TIMEOUT); int re = pollfd[1].revents; if ((re & POLLNVAL) != 0) { log = log && log(1, "poll() returned POLLNVAL, errno %d\n", errno()); break; } if ((re & POLLIN) != 0) read(m_PipeRdFD, nudge, 1); re = pollfd[0].revents; if ((re & POLLNVAL) != 0) { log = log && log(1, "poll() returned POLLNVAL, errno %d\n", errno()); break; } read = read && (re & POLLIN) != 0; write = write && (re & POLLOUT) != 0; } else { FD_ZERO(rset); FD_ZERO(wset); if (read) FD_SET(m_FD, rset); if (write) FD_SET(m_FD, wset); if (m_HaveNudgePipe) FD_SET(m_PipeRdFD, rset); n = select(m_FD + 1, rset, wset, null, m_HaveNudgePipe ? null : timeout); read = read && FD_ISSET(m_FD, rset); write = write && FD_ISSET(m_FD, wset); } if (m_FD < 0) break; if (n < 0) { log = log && log(1, "select() or poll() returned %d, errno %d\n", n, errno()); close(); break; } } else { Thread.sleep(TIMEOUT); } if (m_EventListener != null) { if (read || write) sendDataEvents(read, write); if (pollCtrlLines) sendNonDataEvents(); } } } catch (InterruptedException ie) { } finally { m_ThreadRunning = false; } } }; m_Thread = new Thread(runnable, getName()); m_Thread.setDaemon(true); } synchronized private void updateControlLineState(int line) { checkState(); if (ioctl(m_FD, TIOCMGET, m_ioctl) == -1) throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMGET, m_ioctl) == -1"); m_ControlLineStates = (m_ioctl[0] & line) + (m_ControlLineStates & ~line); } synchronized private boolean getControlLineState(int line) { checkState(); if (ioctl(m_FD, TIOCMGET, m_ioctl) == -1) throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMGET, m_ioctl) == -1"); return (m_ioctl[0] & line) != 0; } synchronized private void setControlLineState(int line, boolean state) { checkState(); if (ioctl(m_FD, TIOCMGET, m_ioctl) == -1) throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMGET, m_ioctl) == -1"); if (state) m_ioctl[0] |= line; else m_ioctl[0] &= ~line; if (ioctl(m_FD, TIOCMSET, m_ioctl) == -1) throw new PureJavaIllegalStateException("ioctl(m_FD, TIOCMSET, m_ioctl) == -1"); } private void failWithIllegalStateException() { throw new PureJavaIllegalStateException("File descriptor is " + m_FD + " < 0, maybe closed by previous error condition"); } private void checkState() { if (m_FD < 0) failWithIllegalStateException(); } private void checkReturnCode(int code) { if (code != 0) { String msg = String.format("JTermios call returned %d at %s", code, lineno(1)); log = log && log(1, "%s\n", msg); try { close(); } catch (Exception e) { StackTraceElement st = e.getStackTrace()[0]; String msg2 = String.format("close threw %s at class %s line% d", e.getClass().getName(), st.getClassName(), st.getLineNumber()); log = log && log(1, "%s\n", msg2); } throw new PureJavaIllegalStateException(msg); } } /** * This is not part of the PureJavaComm API, this is purely for testing, do * not depend on this */ public boolean isInternalThreadRunning() { return m_ThreadRunning; } }