package com.roboclub.robobuggy.nodes.baseNodes; import com.roboclub.robobuggy.main.RobobuggyLogicNotification; import com.roboclub.robobuggy.main.RobobuggyMessageLevel; import purejavacomm.CommPort; import purejavacomm.CommPortIdentifier; import purejavacomm.NoSuchPortException; import purejavacomm.PortInUseException; import purejavacomm.SerialPort; import purejavacomm.UnsupportedCommOperationException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Abstract class extended to create a decorator node that uses * serial communications */ public abstract class SerialNode extends BuggyDecoratorNode { private static final int TIMEOUT = 2000; //Need to manage real, simulated, and calculated sensors // Initialize first private String threadName; private SerialPort sp; // Initialize after getting a serial port private InputStream serialInput; private OutputStream serialOutput; private Thread ioThread; private boolean running = false; private byte[] buf = new byte[128]; private int start = 0; private int numBytes = 0; /** * Creates a {@link SerialNode} decorator for the specified {@link BuggyNode} * * @param base {@link BuggyNode} to decorate * @param threadName name of the thread * @param portName name of the desired serial port * @param baudRate baud rate of the serial port */ public SerialNode(BuggyNode base, String threadName, String portName, int baudRate) { super(base, portName); this.setName(threadName); this.threadName = threadName; // need to accept a vaild port name if (portName.equals("")) { return; } this.sp = connect(portName, baudRate); } // Open a serial port // Returns null if unable to connect, otherwise SerialPort private static SerialPort connect(String portName, int baudRate) { CommPortIdentifier portIdentifier; try { portIdentifier = CommPortIdentifier.getPortIdentifier(portName); if (portIdentifier.isCurrentlyOwned()) { new RobobuggyLogicNotification("Error: Port Currently in use:" + portIdentifier.getName(), RobobuggyMessageLevel.EXCEPTION); } else { CommPort commPort = portIdentifier.open(portName, TIMEOUT); if (commPort instanceof SerialPort) { SerialPort serialPort = (SerialPort) commPort; serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); return serialPort; } } } catch (NoSuchPortException | PortInUseException | UnsupportedCommOperationException e) { new RobobuggyLogicNotification("Error: Unable to connect to port" + portName, RobobuggyMessageLevel.EXCEPTION); } return null; } /** * Call to send bytes over the serial port * * @param bytes byte array to send * @return true iff the bytes are transmitted successfully */ public boolean send(byte[] bytes) { if (serialOutput == null) { return false; } try { serialOutput.write(bytes); serialOutput.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return true; } /** * Sets up a new thread to read in the serial data * {@inheritDoc} */ @Override protected boolean startDecoratorNode() { // Set port to be the right baud rate int baudRate = getBaudRate(); try { sp.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); } catch (UnsupportedCommOperationException e) { // TODO Auto-generated catch block new RobobuggyLogicNotification("Unsupported communication operation over serial port." + e.getMessage(), RobobuggyMessageLevel.EXCEPTION); return false; } catch (NullPointerException e) { new RobobuggyLogicNotification("Null Pointer Exception, unable to initialize the serial port. " + e.getMessage(), RobobuggyMessageLevel.EXCEPTION); return false; } // Set port to be non-blocking....maybe. sp.disableReceiveTimeout(); sp.disableReceiveThreshold(); // Set port to have a reasonable input buffer size? try { serialInput = sp.getInputStream(); serialOutput = sp.getOutputStream(); } catch (IOException e) { new RobobuggyLogicNotification("serial broken", RobobuggyMessageLevel.EXCEPTION); return false; } //Set the node to running running = true; // Begin the madness ioThread = new Thread(new IoThread(), threadName + "-serial"); ioThread.setPriority(Thread.MAX_PRIORITY); ioThread.start(); return true; } /** * Shuts down the Serial reader thread * {@inheritDoc} */ @Override protected boolean shutdownDecoratorNode() { running = false; // iothread will commence shutdown next loop. // Wait forever to join...hope that we're not seized up... if (ioThread != null) { try { ioThread.join(); } catch (InterruptedException e) { e.printStackTrace(); return false; } } return true; } /** * Returns true iff the data type is readable by this current node * * @param sample byte array data * @return true iff the data type is readable by this current node */ public abstract boolean matchDataSample(byte[] sample); /** * Returns the number of bytes needed to match * * @return the number of bytes needed to match */ public abstract int matchDataMinSize(); /** * Returns the expected baud rate of the current device * * @return the expected baud rate of the current device */ public abstract int getBaudRate(); /** * Peel is called once. User should read as many messages as possible * * @param buffer byte array read from the serial port * @param start offset into buffer * @param bytesAvailable number of bytes available to read * @return the number of bytes read, or 1 on failure */ public abstract int peel(byte[] buffer, int start, int bytesAvailable); /** * Private class used as a thread to read serial messages */ private class IoThread implements Runnable { private int asleepTime = (1000 / 60); @Override public void run() { while (running) { try { numBytes += serialInput.read(buf, start + numBytes, buf.length - numBytes); while (true) { int numRead = peel(buf, start, numBytes); if (numRead == 0) { break; } start += numRead; numBytes -= numRead; } // Shift the array by the amount that we read. // TODO this is stupid and should be fixed for (int i = 0; i < numBytes; i++) { buf[i] = buf[start + i]; } start = 0; try { Thread.sleep(asleepTime); } catch (InterruptedException e) { new RobobuggyLogicNotification("sleep .... interrupted?", RobobuggyMessageLevel.EXCEPTION); } } catch (IOException e) { //NOTE: Does not handle reconnects // TODO handle this error reasonably. sp.close(); setNodeState(NodeState.DISCONNECTED); e.printStackTrace(); } } } } }