/* Send and receive bytes via the serial port. Copyright (c) 2001-2014 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.actor.lib.io.comm; import gnu.io.CommPortIdentifier; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import ptolemy.actor.TypedAtomicActor; import ptolemy.actor.TypedIOPort; import ptolemy.data.ArrayToken; import ptolemy.data.BooleanToken; import ptolemy.data.IntToken; import ptolemy.data.Token; import ptolemy.data.UnsignedByteToken; import ptolemy.data.expr.Parameter; import ptolemy.data.expr.StringParameter; import ptolemy.data.type.ArrayType; import ptolemy.data.type.BaseType; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.InternalErrorException; import ptolemy.kernel.util.NameDuplicationException; /////////////////////////////////////////////////////////////////// //// SerialComm /** Send and receive bytes via the serial port. The serial port and baud rate are specified by parameters.
This actor requires that the RXTX serial port API be installed from http://mfizz.com/oss/rxtx-for-java.
Under Windows, download the zip file, then do something like:
cp ~/src/rxtx/mfz-rxtx-2.2-20081207-win-x64/RXTXcomm.jar c:/Program\ Files/Java/jdk1.7.0_51/jre/lib/ext/ cp ~/src/rxtx/mfz-rxtx-2.2-20081207-win-x64/*.dll c:/Program\ Files/Java/jdk1.7.0_51/jre/bin
And then rerun (cd $PTII;./configure)
This actor can be used in most domains, but the parameters must be chosen carefully to match the domain semantics. This actor sets up a listener to the serial port, and when input data appears on the serial port, it calls fireAtCurrentTime() on the director. This behavior is useful in the DE domain, for example (although you will likely have to set the stopWhenQueueIsEmpty parameter of the director false).
Some domains, however, such as SDF, ignore the fireAtCurrentTime() call. Such domains, will typically fire this actor when its outputs are needed. Consequently, for use in such domains, you will likely want to set the blocking parameter of this actor to true. When this parameter is true, the fire() method first reads data on its input port (if any) and writes it to the serial port, and then blocks until sufficient input data are available at the serial port. It then reads that data from the serial port and packages it as a byte array to produce on the output of this actor.
The inputs and outputs of this actor are unsigned byte arrays. The minimumOutputSize parameter specifies the minimum number of bytes that are produced on the output in each firing. The maximumOutputSize parameter specifies the maximum number of bytes that are produced on the output on each firing. If these two numbers are equal, then when a firing produces data, it will always produce the same amount of data. Otherwise, the amount of data produced is nondeterministic.
The discardOldData parameter, if true, indicates that the fire() method may discard bytes. In particular, if there are more than maximumOutputSize bytes available on the serial port, then all but the most recent maximumOutputSize will be discarded.
For example, if you wish for this actor to produce only the most recent byte read on the serial port each time it fires, set discardOldData to true, blocking to true, and both minimumOutputSize and maximumOutputSize to 1.
If after firing there are additional data available on the input port, then the fire() method will call fireAtCurrentTime() on the director before returning.
FIXME: This actor has some key limitations.
* Before returning, if data is sent to the serial port, this * method calls flush(). However, the flush() method does not * wait for the hardware to complete the transmission, as this * might take many milliseconds (roughly 1mS for every 10 bytes * at 115200 baud). Consequently, the data will not have been * completely produced on the serial port when this returns. *
* If data is still available on the serial port when this returns, * then before returning it calls fireAtCurrentTime() on the director. *
* Before this method exits, it will either call fireAtCurrentTime() * on the director (if there is already enough input data on the serial * port to be able to fire again and produce the requisite number * of outputs), or it will start a thread that will wait until * there is enough data and then call fireAtCurrentTime(). * * @exception IllegalActionException Not thrown in this base class. */ @Override public void fire() throws IllegalActionException { super.fire(); // This needs to be synchronized on a single global static object. synchronized (PortListener.class) { try { // Produce output to the serial port first. if (dataToSend.isOutsideConnected() && dataToSend.hasToken(0)) { ArrayToken dataArrayToken = (ArrayToken) dataToSend.get(0); OutputStream out = _serialPort.getOutputStream(); int inputLength = dataArrayToken.length(); if (_debugging) { _debug("Writing bytes from the input port to the serial port: " + inputLength); } for (int j = 0; j < inputLength; j++) { UnsignedByteToken dataToken = (UnsignedByteToken) dataArrayToken .getElement(j); out.write(dataToken.byteValue()); } out.flush(); } InputStream in = _serialPort.getInputStream(); int bytesAvailable = in.available(); if (_debugging) { _debug("Number of input bytes available on the serial port: " + bytesAvailable); } // NOTE: This needs _minimumOutputSize to be at least 1. // FIXME: stopFire() is called if the serial port receives // a byte while we are in the wait() (by the serialEvent() // method, through fireAtCurrentTime(), I think). But we // don't want to exit the loop if stopFire() is called for // this reason. We want to continue waiting until there // is enough input data. But this means that if stopFire() // is called for some other reason, then this actor will // ignore the call. This is not quite right, and could // make the actor fail in domains where stopFire() is essential // (are there any?). while (bytesAvailable < _minimumOutputSize && _blocking && !_stopRequested /* && !_stopFireRequested */) { try { if (_debugging) { _debug("Blocking waiting for minimum number of bytes: " + _minimumOutputSize); } PortListener.class.wait(); bytesAvailable = in.available(); if (_debugging) { _debug("Number of input bytes available on the serial port: " + bytesAvailable); } } catch (InterruptedException ex) { throw new IllegalActionException(this, "Thread interrupted waiting for serial port data."); } } if (_debugging) { _debug("Number of input bytes available on the serial port: " + bytesAvailable); } if (bytesAvailable >= _minimumOutputSize) { // Read only if at least desired amount of data is present. if (_discardOldData && bytesAvailable > _maximumOutputSize) { // Skip excess bytes. int excess = bytesAvailable - _maximumOutputSize; if (_debugging) { _debug("Discarding input bytes: " + excess); } bytesAvailable -= (int) in.skip(excess); } int outputSize = bytesAvailable; if (outputSize > _maximumOutputSize) { outputSize = _maximumOutputSize; } byte[] dataBytes = new byte[outputSize]; if (_debugging) { _debug("Reading bytes from the serial port: " + outputSize); } int bytesRead = in.read(dataBytes, 0, outputSize); // FindBugs asks us to check the return value of in.read(). if (bytesRead != outputSize) { throw new IllegalActionException(this, "Read only " + bytesRead + " bytes, expecting" + outputSize + " bytes."); } Token[] dataTokens = new Token[outputSize]; for (int j = 0; j < outputSize; j++) { dataTokens[j] = new UnsignedByteToken(dataBytes[j]); } if (_debugging) { _debug("Producing byte array on the output port."); } dataReceived.broadcast(new ArrayToken( BaseType.UNSIGNED_BYTE, dataTokens)); } // If there are still enough bytes available to run again, // then call fireAtCurrentTime(). Otherwise, start a thread // to wait for enough bytes. int available = in.available(); if (available >= _minimumOutputSize) { if (_debugging) { _debug("Calling fireAtCurrentTime() to deal with additional bytes: " + available); } getDirector().fireAtCurrentTime(this); } else { // There is not enough serial data input to be able // to fire again. Set up a thread to listen until there // is, and to then call fireAtCurrentTime(). if (_debugging) { _debug("Starting a thread to wait for more bytes. Available: " + available); } Runnable runnable = new Runnable() { @Override public void run() { try { synchronized (PortListener.class) { if (_serialPort != null) { InputStream in = _serialPort .getInputStream(); int bytesAvailable = in.available(); while (bytesAvailable < _minimumOutputSize && !_stopRequested) { PortListener.class.wait(); bytesAvailable = in.available(); } // Once we get here, unless _stopRequested is set, // we have enough data to run again. if (!_directorFiredAtAlready) { _directorFiredAtAlready = true; getDirector().fireAtCurrentTime( SerialComm.this); } } } } catch (Exception ex) { throw new InternalErrorException(ex); } } }; Thread thread = new Thread(runnable); thread.start(); } } catch (IOException ex) { throw new IllegalActionException(this, ex, "I/O error."); } finally { _directorFiredAtAlready = false; } } } /** Perform resource allocation for this actor. * Specifically, open the serial port (setting the baud rate * and other communication settings) and then activate the * serial port's event listening resource, directing events to the * serialEvent() method of this actor. * @exception IllegalActionException Not thrown in this base class. */ @Override public void preinitialize() throws IllegalActionException { super.preinitialize(); _directorFiredAtAlready = false; try { String serialPortNameValue = serialPortName.stringValue(); CommPortIdentifier portID = CommPortIdentifier .getPortIdentifier(serialPortNameValue); synchronized (PortListener.class) { if (_serialPort == null || !toplevel().getName().equals( portID.getCurrentOwner())) { // NOTE: Do not do this in a static initializer so that // we don't initialize the port if there is no actor using it. // This is synchronized on the SerialComm class to prevent multiple actors // from trying to do this simultaneously. _serialPort = (SerialPort) portID.open( toplevel().getName(), 2000); if (_serialPortListener == null) { // This should only be done once. _serialPortListener = new PortListener(); } // The 2000 above is 2000mS to open the port, otherwise time out. int bits_per_second = ((IntToken) baudRate.getToken()) .intValue(); _serialPort.setSerialPortParams(bits_per_second, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); // NOTE: SerialPort only supports a single // listener. With this design, only one SerialComm actor // will be awakened in DE. This is fine if there is only // one SerialComm actor, but if there are more, it may // be unexpected. _serialPort.addEventListener(_serialPortListener); _serialPort.notifyOnDataAvailable(true); _serialPort.notifyOnDSR(true); // DataSetReady isDSR _serialPort.notifyOnCTS(true); // ClearToSend isCTS _serialPort.notifyOnCarrierDetect(true); // isCD _serialPort.notifyOnRingIndicator(true); // isRI } } } catch (Exception ex) { throw new IllegalActionException(this, ex, "Communication port initialization failed."); } } /** Override the base class to stop waiting for input data. */ @Override public void stop() { synchronized (PortListener.class) { super.stop(); PortListener.class.notifyAll(); } } /** Override the base class to stop waiting for input data. */ @Override public synchronized void stopFire() { super.stopFire(); synchronized (PortListener.class) { PortListener.class.notifyAll(); } } /** Close the serial port. * This method is invoked exactly once per execution * of an application. None of the other action methods should be * be invoked after it. * @exception IllegalActionException Not thrown in this base class. */ @Override public void wrapup() throws IllegalActionException { if (_serialPort != null) { // Strangely, this _must_ be called before closing the port. _serialPort.removeEventListener(); _serialPort.close(); _serialPort = null; } } /////////////////////////////////////////////////////////////////// //// private variables //// // The serial port. private static SerialPort _serialPort; // The listener for the serial port. private static PortListener _serialPortListener; // Threshold for reading serial port data. Don't read unless // at least this many bytes are available. private int _maximumOutputSize; // Threshold for reading serial port data. Don't read unless // at least this many bytes are available. private int _minimumOutputSize; // Indicator to discard older data. private boolean _discardOldData; // Whether this actor is blocking. private boolean _blocking; // True if fireAtCurrentTime() has been called on the direcror // but either the director has not yet fired this actor, or it has // been fired but fire() has not completed. Could be in wait(). private boolean _directorFiredAtAlready; /////////////////////////////////////////////////////////////////// //// inner classes //// /** The SerialPort class allows only one listener */ private static class PortListener implements SerialPortEventListener { /** React to an event from the serial port. This is the one and * only method required to implement SerialPortEventListener * (which this class implements). Notifies all threads that * are blocked on this PortListener class. */ @Override public void serialEvent(SerialPortEvent e) { synchronized (PortListener.class) { if (e.getEventType() == SerialPortEvent.DATA_AVAILABLE) { PortListener.class.notifyAll(); } } } } }