/** * */ package net.solarnetwork.node.io.rxtx; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import net.solarnetwork.node.DataCollector; /** * Abstract support for collecting data from a serial port stream. * * <p> * After constructing an instance of this class, you can configure various * communication settings from the class properties (e.g. * {@link #setStopBits(int)}). When the {@link #collectData()} method is called, * this class will start collecting data, until extending implementation * determines collection should stop. The collected data is held in memory. * </p> * * <p> * The {@code dataBits}, {@code stopBits}, and {@code parity} class properties * should be initialized to values corresponding to the constants defined in the * {@link SerialPort} class (e.g. {@link SerialPort#DATABITS_8}, etc.). * </p> * * <p> * The configurable properties of this class are: * </p> * * <dl class="class-properties"> * <dt>toggleDtr</dt> * <dd>If <em>true</em> then toggle the DTR setting before closing the * SerialPort. Defaults to {@code true}.</dd> * * <dt>toggleRts</dt> * <dd>If <em>true</em> then toggle the RTS setting before closing the * SerialPort. Defaults to {@code true}.</dd> * </dl> * * @author matt * @version 1.0 */ public abstract class AbstractSerialPortDataCollector extends SerialPortSupport implements DataCollector, SerialPortEventListener { private final ByteArrayOutputStream buffer; private boolean toggleDtr = true; private boolean toggleRts = true; private boolean collectData = false; private InputStream in = null; private boolean doneCollecting = false; /** * Construct with a port and default settings. * * @param port * the port */ public AbstractSerialPortDataCollector(SerialPort port, long maxWait, int bufferSize) { super(port, maxWait); buffer = new ByteArrayOutputStream(bufferSize); } @Override public int bytesRead() { return buffer.size(); } @Override public byte[] getCollectedData() { return buffer.toByteArray(); } @Override public void stopCollecting() { closeSerialPort(); } @Override public String getCollectedDataAsString() { return buffer.toString(); } @Override public void collectData() { setupSerialPortParameters(this); try { synchronized ( this ) { this.buffer.reset(); // open the input stream this.in = serialPort.getInputStream(); this.doneCollecting = false; this.collectData = true; // sleep until we have data this.wait(getMaxWait()); this.collectData = false; } if ( log.isWarnEnabled() && !doneCollecting ) { log.warn("Timeout collecting serial data"); buffer.reset(); } } catch ( InterruptedException e ) { log.warn("Interrupted, stopping data collection"); } catch ( IOException e ) { throw new RuntimeException(e); } finally { if ( this.in != null ) { if ( toggleDtr ) { serialPort.setDTR(!isDtr()); } if ( toggleRts ) { serialPort.setRTS(!isRts()); } try { this.in.close(); } catch ( IOException e ) { // ignore this one } } serialPort.removeEventListener(); } } @Override public final void serialEvent(SerialPortEvent event) { eventLog.trace("SerialPortEvent {}", event.getEventType()); if ( event.getEventType() != SerialPortEvent.DATA_AVAILABLE ) { return; } if ( !collectData ) { // drain the buffer drainInputStream(in); return; } boolean done; try { done = handleSerialEventInternal(event, in, buffer); } catch ( Exception e ) { done = true; } if ( done ) { synchronized ( this ) { doneCollecting = true; notifyAll(); } return; } } /** * Handle a serial event. * * <p> * Extending classes must implement this method to process the serial event. * This method will not be called unless the event is * </p> * * @param event * the serial event * @param in * the serial InputStream * @param buffer * a data buffer * @return <em>true</em> if data collection is complete and should end; * <em>false</em> to keep collecting data */ protected abstract boolean handleSerialEventInternal(SerialPortEvent event, InputStream in, ByteArrayOutputStream buffer); public boolean isToggleDtr() { return toggleDtr; } public void setToggleDtr(boolean toggleDtr) { this.toggleDtr = toggleDtr; } public boolean isToggleRts() { return toggleRts; } public void setToggleRts(boolean toggleRts) { this.toggleRts = toggleRts; } }