All Downloads are FREE. Search and download functionalities are using the official Maven repository.

purejavacomm.PureJavaSerialPort Maven / Gradle / Ivy

Go to download

FiloFirmata is a client library for the Firmata protocol used with hardware project boards.

There is a newer version: 0.1.11
Show newest version
/*
 * 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 java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

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 ScheduledExecutorService executor;
	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_NotifyOnPortClosed;
	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));
	}

	private void sendPortClosedEvents() {
		if (executor == null || executor.isShutdown()) {
			return;
		}

		final PureJavaSerialPort portRef = this;
		Runnable portNotifyRunnable = new Runnable() {
			@Override
			public void run() {
				if (m_EventListener != null && m_NotifyOnPortClosed) {
					m_EventListener.serialEvent(new SerialPortEvent(portRef, SerialPortEvent.PORT_CLOSED, false, true));
				}
				executor.shutdown();
			}
		};
		executor.schedule(portNotifyRunnable, 500, TimeUnit.MILLISECONDS);
	}

	@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();
	}

	synchronized public void notifyOnPortClosed(boolean x) {
		checkState();
		if (x) {
			executor = Executors.newSingleThreadScheduledExecutor();
		}
		else if (executor != null) {
			executor.shutdown();
		}
		m_NotifyOnPortClosed = x;
	}

	@Override
	public void removeEventListener() {
		if (executor != null) {
			executor.shutdown();
		}
		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.
	 * 

* 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 JTermios.read(...) instead of * SerialPort.getInputStream().read(...). *

* 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. *

* Below is a sketch of minimum necessary to perform a read using raw * JTermios functionality. * *

	 * 
	 * 		// 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 less than 0)
	 * 			errorHandling();
	 * 
	 * 
* * @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(); sendPortClosedEvents(); } } /* 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) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } if (tries-- < 0) throw new PortInUseException("Unknown Application"); } m_MinVTIME = Integer.getInteger("purejavacomm.minvtime", 100); int flags = fcntl(m_FD, F_GETFL, 0); 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, -1); 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy