/* =================================================================== * SerialPortConversationalDataCollector.java * * Created Aug 19, 2009 11:49:24 AM * * Copyright (c) 2009 Solarnetwork.net Dev Team. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * =================================================================== * $Id$ * =================================================================== */ 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 java.io.OutputStream; import net.solarnetwork.node.ConversationalDataCollector; import net.solarnetwork.node.DataCollector; /** * Serial port conversation-based {@link DataCollector} implementation. * * <p>Note this class is not thread-safe, in that * {@link #collectData(net.solarnetwork.node.ConversationalDataCollector.Moderator)} * should not be called by more than one thread at a time.</p> * * @author matt * @version $Revision$ $Date$ * @param <T> the datum type */ public class SerialPortConversationalDataCollector extends SerialPortSupport implements ConversationalDataCollector, SerialPortEventListener { private final long TIMEOUT_PADDING = 800; private byte[] magic; private int readSize; private DataListener listener; private InputStream in; private OutputStream out; private ByteArrayOutputStream buffer; private boolean listening = false; private boolean collecting = false; /** * Constructor. * * @param serialPort the SerialPort to use * @param maxWait the maximum number of milliseconds to wait when waiting * to read data */ public SerialPortConversationalDataCollector(SerialPort serialPort, long maxWait) { super(serialPort, maxWait); this.buffer = new ByteArrayOutputStream(); } @Override public <T> T collectData(Moderator<T> moderator) { setupSerialPortParameters(this); // open the input stream try { this.out = serialPort.getOutputStream(); this.in = serialPort.getInputStream(); return moderator.conductConversation(this); } catch ( IOException e ) { throw new RuntimeException(e); } finally { log.trace("Cleaning up port {}...", serialPort); if ( this.in != null ) { try { this.in.close(); } catch ( IOException e ) { // ignore this one } } if ( this.out != null ) { try { this.out.close(); } catch ( IOException e ) { // ignore this one } } serialPort.removeEventListener(); log.trace("Clean up port {} complete.", serialPort); } } @Override public void collectData() { throw new UnsupportedOperationException("Use the collectData(Moderator) method."); } /** * Speak and then listen for a response. * * <p>The {@code data} will be written to the serial port's * {@link OutputStream} and then this method will block until all * available data has been read from the serial port's * {@link InputStream}. Each invocation of this method will first * clear the internal data buffer, and all received response data * will be stored on the internal data buffer. Calling code can * access this buffer by calling {@link #getCollectedData()}.</p> * * @param data the data to write to the serial port */ public void speakAndListen(byte[] data) { try { // sleep until we have data synchronized (this) { this.buffer.reset(); listening = true; collecting = false; timeoutStart(); out.write(data); out.flush(); this.wait(getMaxWait()+TIMEOUT_PADDING); } } catch ( InterruptedException e ) { throw new RuntimeException(e); } catch ( IOException e ) { throw new RuntimeException(e); } finally { this.listening = false; } } @Override public void listen() { try { synchronized (this) { this.buffer.reset(); listening = true; collecting = false; timeoutStart(); this.wait(getMaxWait()+TIMEOUT_PADDING); } } catch ( InterruptedException e ) { throw new RuntimeException(e); } } @Override public void setListener(DataListener listener) { this.listener = listener; } @Override public void removeListener() { this.listener = null; } @Override public void listen(DataListener listener) { setListener(listener); listen(); } @Override public void speakAndListen(byte[] data, DataListener listener) { setListener(listener); speakAndListen(data); } @Override public void speakAndCollect(byte[] data, byte[] magicBytes, int readLength) { try { // sleep until we have data synchronized (this) { buffer.reset(); magic = magicBytes; readSize = readLength; listening = true; collecting = true; timeoutStart(); out.write(data); out.flush(); this.wait(getMaxWait()+TIMEOUT_PADDING); } } catch ( InterruptedException e ) { throw new RuntimeException(e); } catch ( IOException e ) { throw new RuntimeException(e); } finally { this.listening = false; this.collecting = false; } } @Override public void speak(byte[] data) { try { synchronized (this) { timeoutClear(); out.write(data); out.flush(); } } catch ( IOException e ) { throw new RuntimeException(e); } } @Override public void serialEvent(SerialPortEvent event) { if ( eventLog.isTraceEnabled() ) { eventLog.trace("SerialPortEvent {}; listening {}; collecting {}", new Object[] {event.getEventType(), listening, collecting}); } if ( !listening || event.getEventType() != SerialPortEvent.DATA_AVAILABLE) { return; } if ( collecting ) { boolean done; try { done = handleSerialEvent(event, in, buffer, magic, readSize); } catch (Exception e) { done = true; } if ( done ) { synchronized (this) { notifyAll(); } } return; } read(in, this.buffer, this.listener); synchronized (this) { notifyAll(); } } protected final void read(InputStream in, ByteArrayOutputStream sink, DataListener listener) { final byte[] buf = new byte[1024]; try { int len = -1; boolean reading = true; while ( reading ) { final int sinkSize = sink.size(); final int readSize = (listener == null ? buf.length : Math.min(buf.length, listener.getDesiredByteCount(this, sinkSize))); if ( (len = in.read(buf, 0, readSize)) > 0 ) { if ( listener == null ) { sink.write(buf, 0, len); reading = false; } else { if ( len > 0 && !listener.receivedData(this, buf, 0, len, sink, sink.size()) ) { reading = false;; } } } else if ( listener == null ) { reading = false; } } } catch ( IOException e ) { log.warn("IOException reading serial data: {}", e.getMessage()); } if ( eventLog.isTraceEnabled() ) { eventLog.trace("Finished reading data: {}", asciiDebugValue(sink.toByteArray())); } } @Override public int bytesRead() { return buffer.size(); } @Override public byte[] getCollectedData() { return buffer.toByteArray(); } @Override public String getCollectedDataAsString() { return buffer.toString(); } @Override public void stopCollecting() { closeSerialPort(); } }