package org.myrobotlab.service; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.myrobotlab.codec.serial.Codec; import org.myrobotlab.codec.serial.CodecOutputStream; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceType; import org.myrobotlab.io.FileIO; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.serial.Port; import org.myrobotlab.serial.PortQueue; import org.myrobotlab.serial.SerialControl; import org.myrobotlab.serial.PortStream; import org.myrobotlab.service.interfaces.QueueSource; import org.myrobotlab.service.interfaces.SerialDataListener; import org.myrobotlab.service.interfaces.ServiceInterface; import org.slf4j.Logger; /** * * Serial - a service that allows reading and writing to a serial port device. * */ public class Serial extends Service implements SerialControl, QueueSource, SerialDataListener { /** * general read timeout - 0 is infinite > 0 is number of milliseconds to * wait up to, until data is returned timeout = null: wait forever timeout = * 0: non-blocking mode (return immediately on read) timeout = x: set * timeout to x milliseconds */ private Integer timeoutMS = null; private static final long serialVersionUID = 1L; // rates public final static Integer BAUD_2400 = 2400; public final static Integer BAUD_4800 = 4800; public final static Integer BAUD_9600 = 9600; public final static Integer BAUD_19200 = 19200; public final static Integer BAUD_38400 = 38400; public final static Integer BAUD_57600 = 57600; public final static Integer BAUD_115200 = 115200; /** * deprecated hardware library */ final public static String HARDWARE_LIBRARY_RXTX = "org.myrobotlab.serial.PortRXTX"; /** * different hardware library - hotspot only */ final public static String HARDWARE_LIBRARY_JSSC = "org.myrobotlab.serial.PortJSSC"; /** * Android only bluetooth library */ final public static String HARDWARE_LIBRARY_ANDROID_BLUETOOTH = "android.somethin"; transient public final static Logger log = LoggerFactory.getLogger(Serial.class); /** * cached list of portnames on the system */ static HashSet<String> portNames = new HashSet<String>(); /** * blocking and non-blocking publish/subscribe reading is possible at the * same time. If blocking is not used then the internal buffer will fill to * the BUFFER_SIZE and just be left - overrun data will be lost */ int BUFFER_SIZE = 1024; /** * blocking queue for blocking rx read requests */ transient BlockingQueue<Integer> blockingRX = new LinkedBlockingQueue<Integer>(); /** * our set of ports we have access to. This is a shared resource between ALL * serial services. It should be possible simply to iterate through this * list to get all (cached) names .. operating system ports may have changed * and need re-querying * * it also might be worthwhile to keep a "static" list for remote and * virtual ports so that remote and other services can have access to that * list * * has to be transient because many Ports are not serializable * * remote manipulations and identification should always be done through * portNames */ transient static HashMap<String, Port> ports = new HashMap<String, Port>(); /** * all the ports we are currently connected to typically there is 0 to 1 * connected ports - however the serial service has the ability to "fork" * ports where it is connected to 2 or more ports simultaneously */ transient HashMap<String, Port> connectedPorts = new HashMap<String, Port>(); /** * used as the "default" port - now that Serial can multiplex with multiple * ports - the default is used for methods which are not explicit ... e.g. * connect(), disconnect() etc.. are now equivalent to connect(portName), * disconnect(portName) */ String portName = null; /** * last port name which was connected to - still has name when portName is * null and disconnected */ public String lastPortName; /** * "the" port - there is only one although we can fork and multiplex others. * This is the port which we determine if this Serial service is connected * or not */ transient Port port = null; /** * we need to dynamically load our preferred hardware type, because we may * want to change it or possibly the platform does not support it. When it * is null - we will let MRL figure out what is best. */ String hardwareLibrary = null; /** * rx files saved to an output stream */ transient CodecOutputStream outRX = new CodecOutputStream("rx", this); /** * tx bytes saved to an output stream */ transient CodecOutputStream outTX = new CodecOutputStream("tx", this); /** * number of tx bytes */ int txCount = 0; /** * number of received bytes */ int rxCount = 0; /** * default bps */ int baudrate = 115200; /** * default databits */ int databits = 8; /** * default stopbits */ int stopbits = 1; /** * default parity */ int parity = 0; /** * list of RX listeners - if "local" they will be immediately called back by * the serial device's thread when data arrives, if they are "remote" they * should be published to. They can subscribe to the publishRX method when a * lister is added. Serial is the first listener added to this map */ transient HashMap<String, SerialDataListener> listeners = new HashMap<String, SerialDataListener>(); /** * conversion utility TODO - support endianess * * @param bytes * @param offset * @param length * @return */ public static int bytesToInt(int[] bytes, int offset, int length) { return (int) bytesToLong(bytes, offset, length); } /** * conversion utility TODO - support endianess * * @param bytes * @param offset * @param length * @return */ public static long bytesToLong(int[] bytes, int offset, int length) { long retVal = 0; for (int i = 0; i < length; ++i) { retVal |= (bytes[offset + i] & 0xff); if (i != length - 1) { retVal <<= 8; } } return retVal; } /* * DEPRECATED BECAUSE SIGNED BYTE ARRAYS ARE SILLY public static byte[] * intArrayToByteArray(int[] src) { * * if (src == null) { return null; } * * byte[] ret = new byte[src.length]; for (int i = 0; i < src.length; ++i) { * ret[i] = (byte) src[i]; } return ret; } */ /** * Static list of third party dependencies for this service. The list will * be consumed by Ivy to download and manage the appropriate resources * * @return */ public Serial(String n) { super(n); listeners.put(n, this); // outbox.setBlocking(true); // outbox.maxQueue = 1; } public void addByteListener(SerialDataListener listener) { addByteListener(listener.getName()); } /** * awesome method - which either sets up the pub/sub remote or assigns a * local reference from the publishing thread * * good pattern in that all logic is in this method which uses a string * "name" parameter - addByteListener(SerialDataListener listener) will call * this method too rather than implementing its own local logic * * FIXME - DO THIS STUFF (AND THE PUBLISHING/TESTING) IN THE FRAMEWORK * * @param name */ public void addByteListener(String name) { ServiceInterface si = Runtime.getService(name); // if (si instanceof SerialDataListener && si.isLocal()){ if (SerialDataListener.class.isAssignableFrom(si.getClass()) && si.isLocal()) { // direct callback listeners.put(si.getName(), (SerialDataListener) si); } else { // pub sub addListener("publishRX", si.getName(), "onByte"); addListener("publishConnect", si.getName(), "onConnect"); addListener("publishDisconnect", si.getName(), "onDisconnect"); } } /** * method similar to InputStream's * * @return */ public int available() { return blockingRX.size(); } /** * clears the rx buffer */ public void clear() { blockingRX.clear(); outRX.clear(); outTX.clear(); } /** * for backwards compatibility * * @param name * @throws IOException */ public void connect(String name) throws IOException { open(name); } public void connect(String name, int baudRate, int dataBits, int stopBits, int parity) throws IOException { open(name); setParams(baudRate, dataBits, stopBits, parity); } public void open(String name) throws IOException { open(name, baudrate, databits, stopbits, parity); } public boolean setParams(int baudRate, int dataBits, int stopBits, int parity) throws IOException { try { log.info("setParams {} {} {} {}", baudRate, dataBits, stopBits, parity); if (port == null || !port.isOpen()) { log.error("port is null or not opened"); return false; } if (port.setParams(baudRate, dataBits, stopBits, parity)) { return true; } else { return false; } } catch (Exception e) { throw new IOException(e); } } /** * The main simple connect - it attempts to connect to one of the known * ports in memory if that fails it will try to connect to a hardware port * * TODO - make "connecting" to pre-existing ports re-entrant !!! * * connect - optimized to have a SerialDataListener passed in - which will * optimize data streaming back from the port. * * preference is to have this local optimizaton * * connect = open + listen * * @param inPortName * @param listener * @return * @throws IOException * @throws Exception */ public void open(String inPortName, int baudrate, int databits, int stopbits, int parity) throws IOException { info("connect to port %s %d|%d|%d|%d", inPortName, baudrate, databits, stopbits, parity); this.baudrate = baudrate; this.databits = databits; this.stopbits = stopbits; this.parity = parity; // two possible logics to see if we are connected - look at the // state of the port // on the static resource - or just check to see if its on the // "connectedPort" set // #1 check to see if were already connected a port // if (this.portName != null && this.portName.equals(portName) && // ports.containsKey(portName)) { if (this.portName != null) { Port port = ports.get(portName); if (port.isOpen() && port.isListening()) { info("already connected to %s - disconnect first to reconnect", portName); return; } } // #2 connect to a pre-existing if (ports.containsKey(inPortName)) { connectPort(ports.get(inPortName), null); lastPortName = portName; return; } // #3 we dont have an existing port - so we'll try a hardware port // connect at defaullt parameters - if you need custom parameters // create the hardware port first Port port = createHardwarePort(inPortName, baudrate, databits, stopbits, parity); if (port == null) { return; } connectPort(port, null); lastPortName = portName; // even when the JNI says it is connected // rarely is everything ready to go // give us half a second for all the buffers // & hardware to be ready // sleep(1500); } /** * FIXME - implement connects to a FilePlayer details of tx/rx and timing * can be part os a SerialFilePlayer implementation * * @param name */ public boolean connectFilePlayer(String name) { // return false; } /** * FIXME - implement Baddass loopback null/modem cable - auto creates a new * Serial service and connects to it FIXME - no need for null/modem cable * virtual port ? * * @param name */ public boolean connectLoopback(String name) { // TODO - implement log.info("implement me"); return false; } public Port connectPort(Port newPort, SerialDataListener listener) throws IOException { port = newPort; // portName = port.getName(); ports.put(port.getName(), port); portNames.add(port.getName()); invoke("getPortNames"); if (listener != null) { listeners.put(listener.getName(), listener); } if (!port.isOpen()) { port.open(); } port.listen(listeners); connectedPorts.put(port.getName(), newPort); // FYI !!! // give us a second before we advertise the port // is open - often the hardware or JNI buffers // are not ready even though we have "opened" it // sleep(1000); // invoking remote & local onConnect invoke("publishConnect", port.getName()); for (String key : listeners.keySet()) { // NOT A GOOD OPTIMIZATION - AS THE "EVENT" IS MUCH MORE IMPORTANT // THAN THE SPEED OF THE DATA // listeners.get(key).onConnect(portName); send(listeners.get(key).getName(), "onConnect", port.getName()); } // we have a portName and we are connected portName = port.getName(); // save(); why? broadcastState(); return port; } public boolean connectTcp(String host, int port) throws IOException { Port tcpPort = createTCPPort(host, port, this); connectPort(tcpPort, this); return true; } /** * Dynamically create a hardware port - this method is needed to abtract * away the specific hardware library. Its advantageous to have abstraction * when interfacing with a specific implementation (JNI/JNA - other?). The * abstraction allows the service to attempt to choose the correct library * depending on platform or personal user choice. * * We don't want the whole Serial service to explode because of an Import of * an implementation which does not exist on a specific platform. I know * this from experience :) */ public Port createHardwarePort(String name, int rate, int databits, int stopbits, int parity) { log.info(String.format("creating %s port %s %d|%d|%d|%d", hardwareLibrary, name, rate, databits, stopbits, parity)); try { hardwareLibrary = getHardwareLibrary(); Class<?> c = Class.forName(hardwareLibrary); Constructor<?> constructor = c.getConstructor(new Class<?>[] { String.class, int.class, int.class, int.class, int.class }); Port hardwarePort = (Port) constructor.newInstance(name, rate, databits, stopbits, parity); info("created port %s %d|%d|%d|%d - goodtimes", name, rate, databits, stopbits, parity); ports.put(name, hardwarePort); return hardwarePort; } catch (Exception e) { error(e); Logging.logError(e); } return null; } public Port createTCPPort(String host, int tcpPort, SerialDataListener listener) throws IOException { info("connectTCP %s %d", host, tcpPort); @SuppressWarnings("resource") Socket socket = new Socket(host, tcpPort); String portName = String.format("%s.%s", getName(), socket.getRemoteSocketAddress().toString()); Port socketPort = new PortStream(portName, socket.getInputStream(), socket.getOutputStream()); ports.put(portName, socketPort); return socketPort; } public PortQueue createVirtualPort(String name) { BlockingQueue<Integer> rx = new LinkedBlockingQueue<Integer>(); BlockingQueue<Integer> tx = new LinkedBlockingQueue<Integer>(); PortQueue portQueue = new PortQueue(name, rx, tx); ports.put(name, portQueue); return portQueue; } /** * decode relies on the rx codec decode method which will block a thread * until the out rx stream data buffer decodes a message which can be * returned * * @return */ public String decode() { Codec codec = outRX.getCodec(); if (codec != null) { return codec.decode(); } return null; } /** * disconnect = close + remove listeners all ports on serial network */ public void disconnect() { if (!connectedPorts.containsKey(portName)) { info("disconnect unknown port %s", portName); } if (portName == null) { info("already disconnected"); return; } // remote published disconnect invoke("publishDisconnect", port.getName()); // local disconnect for (String key : listeners.keySet()) { // DUMB OPTIMIZATION - THE EVENT IS FAR MORE IMPORTANT THAN THE // SPEED OF THE DATA // listeners.get(key).onDisconnect(portName); send(listeners.get(key).getName(), "onDisconnect", port.getName()); } info("disconnecting all ports"); // forked ports for (String portName : connectedPorts.keySet()) { Port port = connectedPorts.get(portName); port.close(); } connectedPorts.clear(); portName = null; port = null; broadcastState(); } public String getHardwareLibrary() { // if user has forced a specific library // use it - customer is always right !!! if (hardwareLibrary != null) { return hardwareLibrary; } // otherwise make a "best" guess Platform platform = Platform.getLocalInstance(); if (platform.isDalvik()) { return HARDWARE_LIBRARY_ANDROID_BLUETOOTH; } else { return HARDWARE_LIBRARY_JSSC; // return HARDWARE_LIBRARY_RXTX; buh bye !! } } public HashMap<String, SerialDataListener> getListeners() { return listeners; } public Port getPort() { return port; } /** * get the port name this serial service is currently attached to * * @return */ public String getPortName() { return portName; } /** * "all" currently known ports - if something is missing refresh ports * should be called to force hardware search * * @throws ClassNotFoundException */ @Override public List<String> getPortNames() { return new ArrayList<String>(portNames); } SerialControl getPortSource() { try { hardwareLibrary = getHardwareLibrary(); log.info(String.format("loading class: %s", hardwareLibrary)); Class<?> c = Class.forName(getHardwareLibrary()); return (SerialControl) c.newInstance(); } catch (Exception e) { Logging.logError(e); } return null; } @Override public BlockingQueue<?> getQueue() { return blockingRX; } public Codec getRXCodec() { return outRX.getCodec(); } public String getRXCodecKey() { if (outRX == null) { return null; } return outRX.getKey(); } public int getRXCount() { return rxCount; } public int getTimeout() { return timeoutMS; } public Codec getTXCodec() { if (outTX == null) { return null; } return outTX.getCodec(); } public String getTXCodecKey() { if (outTX == null) { return null; } return outTX.getKey(); } public boolean isConnected() { return portName != null; } public boolean isRecording() { boolean ret = (outRX != null && outRX.getOut() != null) || (outTX != null && outTX.getOut() != null); return ret; } /** * onByte is typically the functions clients of the Serial service use when * they want to consume serial data. * * The serial service implements this function primarily so it can test * itself * * readFromPublishedByte is a catch mechanism to verify tests * * @throws IOException */ @Override public final Integer onByte(Integer newByte) throws IOException { newByte = newByte & 0xff; ++rxCount; // publish the rx byte ! invoke("publishRX", newByte); if (blockingRX.size() < BUFFER_SIZE) { blockingRX.add(newByte); } // FILE I/O outRX.write(newByte); return newByte; } @Override public String onConnect(String portName) { info("%s connected to %s", getName(), portName); return portName; } @Override public String onDisconnect(String portName) { info("%s disconnected from %s", getName(), portName); return portName; } /** * successful connection event * * @param portName * @return */ public String publishConnect(String portName) { info("%s publishConnect %s", getName(), portName); return portName; } /** * disconnect event * * @param portName * @return */ public String publishDisconnect(String portName) { return portName; } /** * event to return list of ports of all ports this serial service can see * * @param portNames * @return */ public List<String> publishPortNames(List<String> portNames) { return portNames; } /** * main line RX publishing point * * @param data * @return */ public int publishRX(Integer data) { return data; } /** * main line TX publishing point * * @param display * @return */ public Integer publishTX(Integer data) { return data; } /** * FIXME - make like http://pyserial.sourceforge.net/pyserial_api.html with * blocking & timeout InputStream like interface - but regrettably * InputStream IS NOT A F#(@!! INTERFACE !!!! * * WORTHLESS INPUTSTREAM FUNCTION !! -- because if the size of the buffer is * ever bigger than the read and no end of stream has occurred it will block * forever :P * * pass through to the serial device * * @param temp * @return * @throws IOException */ public int read() throws IOException, InterruptedException { if (timeoutMS == null) { return blockingRX.take(); } Integer newByte = blockingRX.poll(timeoutMS, TimeUnit.MILLISECONDS); if (newByte == null) { String error = String.format("%d ms timeout was reached - no data", timeoutMS); error(error); throw new IOException(error); } return newByte; } public int read(byte[] data) throws IOException, InterruptedException { for (int i = 0; i < data.length; ++i) { data[i] = (byte) read(); } return data.length; } /** * Read size bytes from the serial port. If a timeout is set it may return * less characters as requested. With no timeout it will block until the * requested number of bytes is read. * * @param size * @return * @throws InterruptedException */ public byte[] read(int length) throws InterruptedException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); int count = 0; Integer newByte = null; while (count < length) { if (timeoutMS == null) { newByte = blockingRX.take(); } else { newByte = blockingRX.poll(timeoutMS, TimeUnit.MILLISECONDS); } if (newByte == null) { if (count == 0) { error("got nothing!"); return null; } else { error("expecting %d bytes got %d", length, count); break; } } bytes.write(newByte.intValue() & 0xff); ++count; } return bytes.toByteArray(); } public int read(int[] data) throws InterruptedException { int count = 0; Integer newByte = null; while (count < data.length) { if (timeoutMS == null) { newByte = blockingRX.take(); } else { newByte = blockingRX.poll(timeoutMS, TimeUnit.MILLISECONDS); } if (newByte == null) { error("expecting %d bytes got %d", data.length, count); return count; } data[count] = newByte; ++count; } return count; } public byte[] readLine() throws InterruptedException { return readLine('\n'); } public byte[] readLine(char deliminater) throws InterruptedException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); Integer newByte = null; while (newByte == null || newByte != deliminater) { if (timeoutMS == null) { newByte = blockingRX.take(); } else { newByte = blockingRX.poll(timeoutMS, TimeUnit.MILLISECONDS); } if (newByte == null) { info("non blocking got nothing"); return bytes.toByteArray(); } bytes.write(newByte.intValue() & 0xff); } return bytes.toByteArray(); } public String readString() throws InterruptedException { byte[] bytes = readLine('\n'); return new String(bytes); } public String readString(char delimiter) throws InterruptedException { byte[] bytes = readLine(delimiter); return new String(bytes); } /** * read a string back from the serial port * * @param length * - the number of bytes to read back * @param timeoutMS * - the amount of time to wait blocking until we return. 0 ms * means the reading thread will potentially block forever. * @return String form of the bytes read * @throws InterruptedException */ public String readString(int length) throws InterruptedException { byte[] bytes = read(length); return new String(bytes); } // FIXME remove blocking public // FIXME overload with timeouts etc - remove exposed blocking // FIXME - implement public byte[] readToDelimiter(String delimeter) { return null; } public void record() throws FileNotFoundException { String filename = String.format("rxtx.%s.%d.data", getName(), System.currentTimeMillis()); record(filename); } public void record(String filename) throws FileNotFoundException { recordTX(String.format("%s.tx.%s", filename, outTX.getCodecExt())); recordRX(String.format("%s.rx.%s", filename, outRX.getCodecExt())); } public void recordRX(String filename) throws FileNotFoundException { outRX.record(filename); } public void recordTX(String filename) throws FileNotFoundException { outTX.record(filename); } /** * force refreshing ports * * @return */ public List<String> refresh() { // all current ports portNames.addAll(ports.keySet()); // plus hardware ports SerialControl portSource = getPortSource(); if (portSource != null) { List<String> osPortNames = portSource.getPortNames(); for (int i = 0; i < osPortNames.size(); ++i) { portNames.add(osPortNames.get(i)); } } broadcastState(); return new ArrayList<String>(portNames); } public void removeByteListener(SerialDataListener listener) { removeByteListener(listener.getName()); } public void removeByteListener(String name) { ServiceInterface si = Runtime.getService(name); // if (si instanceof SerialDataListener && si.isLocal()){ if (SerialDataListener.class.isAssignableFrom(si.getClass()) && si.isLocal()) { // direct callback listeners.remove(si.getName()); } else { // pub sub removeListener("publishRX", si.getName(), "onByte"); removeListener("publishConnect", si.getName(), "onConnect"); removeListener("publishDisconnect", si.getName(), "onDisconnect"); } } public void reset() { clear(); setTimeout(null); rxCount = 0; txCount = 0; } public void setBufferSize(int size) { BUFFER_SIZE = size; } /** * uses key ascii, decimal, hex, arduino ... to dynamically set file * formatter * * @param key * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException * @throws IllegalArgumentException * @throws SecurityException * @throws NoSuchMethodException */ public void setCodec(String key) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { outRX.setCodec(key); outTX.setCodec(key); broadcastState(); } public void setDTR(boolean state) { port.setDTR(state); } public String setHardwareLibrary(String clazz) { hardwareLibrary = clazz; return hardwareLibrary; } public void setRXCodec(Codec codec) { outRX.setCodec(codec); } /** * default timeout for all reads 0 = infinity > 0 - will wait for the number * in milliseconds if the data has not arrived then an IOError will be * thrown * * @param timeout * @return */ public Integer setTimeout(Integer timeout) { timeoutMS = timeout; return timeout; } public void setTXCodec(Codec codec) { outTX.setCodec(codec); } public void stopRecording() { try { outRX.close(); outTX.close(); broadcastState(); } catch (Exception e) { Logging.logError(e); } } @Override public void stopService() { super.stopService(); disconnect(); stopRecording(); } @Override public String toString() { return String.format("%s->%s", getName(), portName); } // write(byte[] b) IOException public void write(byte[] data) throws Exception { for (int i = 0; i < data.length; ++i) { write(data[i] & 0xff); // recently removed - & 0xFF } } // TODO: remove this method use write(int[] b) instead public void write(int b) throws Exception { // int newByte = data & 0xFF; if (connectedPorts.size() == 0) { error("can not write to a closed port!"); } for (String portName : connectedPorts.keySet()) { Port writePort = connectedPorts.get(portName); writePort.write(b); } // main line TX invoke("publishTX", b); ++txCount; outTX.write(b); } public void write(int[] data) throws Exception { // If the port is JSSC we can just write the array. for (String portName : connectedPorts.keySet()) { Port writePort = connectedPorts.get(portName); // take advantage to write the array in one call. writePort.write(data); // still need to publishtx.. // TODO: make publishTX publish an int array. not one at a time. for (int i = 0; i < data.length; ++i) { // main line TX invoke("publishTX", data[i]); ++txCount; outTX.write(data[i]); } } } // ============= write methods begin ==================== // write(String data) not in OutputStream public void write(String data) throws Exception { write(data.getBytes()); } public void writeString(String data) throws Exception { write(data.getBytes()); } // FIXME - change Codec based on file extension !!! // file (formatter/parser) --to--> tx public void writeFile(String filename) { try { byte[] fileData = FileIO.toByteArray(new File(filename)); /* * TODO - ENCODING !!! if (txCodec != null) { // FIXME parse the * incoming file for (int i = 0; i < fileData.length; ++i) { // * FIXME - determine what is needed / expected to parse // * write(txFormatter.parse(fileData[i])); } } else { */ for (int i = 0; i < fileData.length; ++i) { write(fileData[i]); } // } } catch (Exception e) { error(e); } } /** * This static method returns all the details of the class without it having * to be constructed. It has description, categories, dependencies, and peer * definitions. * * @return ServiceType - returns all the data * */ static public ServiceType getMetaData() { ServiceType meta = new ServiceType(Serial.class.getCanonicalName()); meta.addDescription("reads and writes data to a serial port"); meta.addCategory("sensor", "microcontroller", "control"); meta.addDependency("com.googlecode.jssc", "2.8.0"); return meta; } public void connect() throws IOException { connect(lastPortName); } public static void main(String[] args) { LoggingFactory.getInstance().configure(); LoggingFactory.getInstance().setLevel(Level.INFO); // TODO - test blocking / non blocking / time-out blocking / reading an // array (or don't bother?) or do with length? num bytes to block or // timeout // TODO - if I am connected to a different serial port // get that name - disconnect - and then reconnect when done // FIXME - very little functionality for a combined tx rx file // TODO - test sendFile & record // TODO - speed test // TODO use utility methods to help parse read data types // because we should not assume we know the details of ints longs etc // nor // the endianess // utility methods - ascii // FIXME - // test case write(-1) as display becomes -1 ! - file is // different than gui !?!?! try { Serial serial = (Serial) Runtime.start("serial", "Serial"); Runtime.start("python", "Python"); Runtime.start("webgui", "WebGui"); boolean done = true; if (done) { return; } int timeout = 500;// 500 ms serial timeout // Runtime.start("gui", "GUIService"); // Runtime.start("webgui", "WebGui"); // get serial handle and creates a uart & virtual null modem cable // Serial serial = (Serial) Runtime.start("serial", "Serial"); serial.setTimeout(timeout); String port = "COM15"; // EASY VIRTUAL SWITCH // ---- Virtual Begin ----- VirtualDevice virtual = (VirtualDevice) Runtime.start("virtual", "VirtualDevice"); virtual.createVirtualSerial(port); Serial uart = virtual.getUart(port); uart.setTimeout(300); // ---- Virtual End ----- serial.open(port); // verify the null modem cable is connected if (!serial.isConnected()) { throw new IOException(String.format("%s not connected", serial.getName())); } if (!uart.isConnected()) { throw new IOException(String.format("%s not connected", uart.getName())); } // start binary recording serial.record("serial"); uart.record("uart"); // test blocking on exact size serial.write(10); serial.write(20); serial.write(30); serial.write(40); serial.write(50); serial.write(60); serial.write(70); uart.write("000D\r"); // read back log.info(serial.readString(5)); // blocking read with timeout String data = "HELLO"; uart.write(data); String hello = serial.readString(data.length()); if (!data.equals(hello)) { throw new IOException("data not equal"); } serial.info("read back [%s]", hello); serial.info("array write"); serial.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 127, (byte) 128, (byte) 254, (byte) 255 }); uart.clear(); serial.write("this is the end of the line \n"); serial.clear(); // TODO: why are we doing this? burn the first line. serial.readLine(); byte[] readBackArray = uart.readLine(); log.info(Arrays.toString(readBackArray)); // FIXME !!! - bug - we wrote a big array to serial - // then immediately cleared the uart buffer // in fact we cleared it so fast - that the serial data ---going // to----> uart // has not reached uart (because there is some overhead in moving, // reading and formatting the incoming data) // then we start checking values in "test blocking" this by that // time the serial data above has hit the // uart // with a virtual null modem cable I could "cheat" and flush() could // look at the serial's tx buffer size // and block until its cleared - but this would not be typical of // "real" serial ports // but it could stabilize the test // in the real world we don't know when the sender to // our receiver is done - so we'll sleep here sleep(300); serial.info("clear buffers"); serial.clear(); uart.clear(); if (serial.available() != 0) { throw new IOException("available data after clear"); } // support write(int) kill pill or not ? // I say yes serial.info("testing blocking"); for (int i = 255; i > -1; --i) { serial.write(i); int readBack = uart.read(); log.info(String.format("written %d read back %d", i, readBack)); if (i < 256 && i > -1) { if (readBack != i) { throw new IOException(String.format("read back not the same as written for value %d %d !", i, readBack)); } } } // FIXME - test the -1 write(int) kill pill // serial.write(-1) -> should close port !!! // in the real world we don't know when the sender to // our receiver is done - so we'll sleep here sleep(300); serial.info("clear buffers"); serial.clear(); uart.clear(); // test publish/subscribe nonblocking serial.addByteListener(serial); // <-- FIXME CREATES INFINITE LOOP // BUG uart.write(64); // TODO - low level details of strings & timeouts // TODO - filename serial.clear(); serial.setCodec("ascii"); uart.setCodec("ascii"); // basic record String inRecord = "this is a short ascii row\n"; uart.write(inRecord); // String record = serial.readString(); serial.clear(); uart.clear(); serial.record("serialASC"); uart.record("uartASC"); serial.stopRecording(); uart.stopRecording(); // ======= decimal format begin =========== serial.setCodec("decimal"); uart.setCodec("decimal"); // default non-binary format is ascii decimal serial.record("serial.2"); uart.record("uart.2"); // uart.record("test/Serial/uart.2"); serial.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, (byte) 255 }); // we have to pause here momentarily // so the data can be written and read from the virtual null modem // cable (on different threads) // before we close the file streams sleep(30); // uart.stopRecording(); serial.stopRecording(); // ======= decimal format end =========== // ======= hex format begin =========== serial.setCodec("hex"); serial.record("hex.3"); // uart.record("test/Serial/uart.3"); serial.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, (byte) 255 }); sleep(30); serial.broadcastState(); serial.stopRecording(); // uart.stopRecording(); // ======= hex format begin =========== // parsing of files based on extension check // TODO flush & close tests ? // serial.disconnect(); // uart.disconnect(); // log.info(status.flatten().toString()); } catch (Exception e) { Logging.logError(e); } } }