package org.myrobotlab.serial; import java.io.IOException; import java.io.InterruptedIOException; import java.util.HashMap; import org.myrobotlab.framework.QueueStats; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.service.interfaces.SerialDataListener; import org.slf4j.Logger; /** * * @author Grog * */ public abstract class Port implements Runnable, SerialControl { public final static Logger log = LoggerFactory.getLogger(Port.class); String portName; String threadName; // needs to be owned by Serial transient HashMap<String, SerialDataListener> listeners = null; // transient CountDownLatch opened = null; // transient CountDownLatch closed = null; final transient Object lock = new Object(); static int pIndex = 0; // thread related transient Thread readingThread = null; boolean listening = false; QueueStats stats = new QueueStats(); // hardware serial port details // default convention over configuration // int rate = 57600; int rate = 115200; int dataBits = 8; int stopBits = 1; int parity = 0; int txErrors; int rxErrors; boolean isOpen = false; // necessary - to be able to invoke // "nameless" port implementation to query "hardware" ports // overloading a "Port" and a PortQuery - :P public Port() { } public Port(String portName) { this.stats.name = portName; this.portName = portName; stats.interval = 1000; } public Port(String portName, int rate, int dataBits, int stopBits, int parity) throws IOException { this(portName); this.rate = rate; this.dataBits = dataBits; this.stopBits = stopBits; this.parity = parity; } public void close() { // closed = new CountDownLatch(1); listening = false; if (readingThread != null) { readingThread.interrupt(); } readingThread = null; /* * try { closed.await(); } catch (Exception e) { Logging.logError(e); } */ // TODO - suppose to remove listeners ??? log.info(String.format("closed port %s", portName)); } public String getName() { return portName; } abstract public boolean isHardware(); public boolean isListening() { return listening; } public boolean isOpen() { return isOpen; } public void listen(HashMap<String, SerialDataListener> listeners) { // opened = new CountDownLatch(1); // try { if (this.listeners != null) { log.info("here"); } this.listeners = listeners; if (readingThread == null) { ++pIndex; threadName = String.format("%s.portListener %s", portName, pIndex); readingThread = new Thread(this, threadName); readingThread.start(); /* * - this might be a good thing .. wait until the reading thread starts - * but i don't remember if JSSC works this way synchronized (lock) { * lock.wait(); } */ } else { log.info(String.format("%s already listening", portName)); } // Thread.sleep(100); - added connect retry logic in Arduino // taking out arbitrary sleeps // } catch (InterruptedException e) { // } } public void open() throws IOException { log.info(String.format("opening port %s", portName)); isOpen = true; } abstract public int read() throws Exception; /** * reads from Ports input stream and puts it on the Serials main RX line - to * be published and buffered */ @Override public void run() { /* * - this might be a good thing .. wait until the reading thread starts - * but i don't remember if JSSC works this way synchronized(lock){ * lock.notifyAll(); } */ log.info(String.format("listening on port %s", portName)); listening = true; Integer newByte = -1; try { // opened.countDown(); // TODO - if (Queue) while take() // normal streams are processed here - rxtx is abnormal while (listening && ((newByte = read()) > -1)) { // "real" java byte // 255 / -1 will // kill this for (String key : listeners.keySet()) { listeners.get(key).onByte(newByte); // log.info(String.format("%d",newByte)); } ++stats.total; if (stats.total % stats.interval == 0) { stats.ts = System.currentTimeMillis(); stats.delta = stats.ts - stats.lastTS; stats.lineSpeed = (8 * stats.interval) / stats.delta; for (String key : listeners.keySet()) { listeners.get(key).updateStats(stats); } // publishQueueStats(stats); stats.lastTS = stats.ts; } // log.info(String.format("%d",newByte)); } log.info(String.format("%s no longer listening - last byte %d ", portName, newByte)); } catch (InterruptedException x) { log.info(String.format("InterruptedException %s stopping ", portName)); } catch (InterruptedIOException c) { log.info(String.format("InterruptedIOException %s stopping ", portName)); } catch (Exception e) { Logging.logError(e); } finally { // allow the thread calling close // to proceed /* * if (closed != null){ closed.countDown(); } */ log.info(String.format("stopped listening on %s", portName)); } } /** * "real" serial function stubbed out in the abstract class in case the serial * implementation does not actually implement this method e.g. (bluetooth, * iostream, tcp/ip) * * @param state */ public void setDTR(boolean state) { } /** * The way rxtxLib currently works - is it will give a -1 on a read when it * has no data to give although in the specification this means end of stream * - for rxtxLib this is not necessarily the end of stream. The implementation * there - the thread is in rxtx - and will execute serialEvent when serial * data has arrived. This might have been a design decision. The thread which * calls this is in the rxtxlib - so we have it call the run() method of a * non-active thread class. * * needs to be buried in rxtxlib implementation * */ abstract public void write(int b) throws Exception; abstract public void write(int[] data) throws Exception; public boolean setParams(int rate, int dataBits, int stopBits, int parity) throws Exception { // TODO Auto-generated method stub return false; } }