package org.openpnp.machine.reference.driver; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.TimeoutException; import javax.swing.Action; import javax.swing.Icon; import org.openpnp.gui.support.PropertySheetWizardAdapter; import org.openpnp.gui.support.Wizard; import org.openpnp.machine.reference.ReferenceDriver; import org.openpnp.machine.reference.ReferencePasteDispenser; import org.openpnp.machine.reference.driver.wizards.AbstractSerialPortDriverConfigurationWizard; import org.openpnp.model.Location; import org.openpnp.spi.PropertySheetHolder; import org.simpleframework.xml.Attribute; import jssc.SerialPort; import jssc.SerialPortException; import jssc.SerialPortList; import jssc.SerialPortTimeoutException; /** * A base class for basic SerialPort based Drivers. Includes functions for connecting, * disconnecting, reading and sending lines. */ public abstract class AbstractSerialPortDriver implements ReferenceDriver, Closeable { public enum DataBits { Five(SerialPort.DATABITS_5), Six(SerialPort.DATABITS_6), Seven(SerialPort.DATABITS_7), Eight(SerialPort.DATABITS_8); public final int mask; private DataBits(int mask) { this.mask = mask; } } public enum StopBits { One(SerialPort.STOPBITS_1), OnePointFive(SerialPort.STOPBITS_1_5), Two(SerialPort.STOPBITS_2); public final int mask; private StopBits(int mask) { this.mask = mask; } } public enum FlowControl { Off(SerialPort.FLOWCONTROL_NONE), RtsCts(SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT), XonXoff(SerialPort.FLOWCONTROL_XONXOFF_IN | SerialPort.FLOWCONTROL_XONXOFF_OUT); public final int mask; private FlowControl(int mask) { this.mask = mask; } } public enum Parity { None(SerialPort.PARITY_NONE), Mark(SerialPort.PARITY_MARK), Space(SerialPort.PARITY_SPACE), Even(SerialPort.PARITY_EVEN), Odd(SerialPort.PARITY_ODD); public final int mask; private Parity(int mask) { this.mask = mask; } } @Attribute(required = false) protected String portName; @Attribute(required = false) protected int baud = 115200; @Attribute(required = false) protected FlowControl flowControl = FlowControl.Off; @Attribute(required = false) protected DataBits dataBits = DataBits.Eight; @Attribute(required = false) protected StopBits stopBits = StopBits.One; @Attribute(required = false) protected Parity parity = Parity.None; @Attribute(required = false) protected boolean setDtr = false; @Attribute(required = false) protected boolean setRts = false; protected SerialPort serialPort; protected SerialInputStream input; protected OutputStream output; protected synchronized void connect() throws Exception { disconnect(); serialPort = new SerialPort(portName); serialPort.openPort(); serialPort.setParams(baud, dataBits.mask, stopBits.mask, parity.mask, setRts, setDtr); serialPort.setFlowControlMode(flowControl.mask); input = new SerialInputStream(serialPort); input.setTimeout(500); output = new SerialOutputStream(serialPort); } protected synchronized void disconnect() throws Exception { if (serialPort != null && serialPort.isOpened()) { serialPort.closePort(); input = null; output = null; serialPort = null; } } @Override public void dispense(ReferencePasteDispenser dispenser, Location startLocation, Location endLocation, long dispenseTimeMilliseconds) throws Exception { // Do nothing. This is just stubbed in so that it can be released // without breaking every driver in the wild. } public String[] getPortNames() { return SerialPortList.getPortNames(); } /** * Read a line from the serial port. Blocks for the default timeout. If the read times out a * TimeoutException is thrown. Any other failure to read results in an IOExeption; * * @return * @throws TimeoutException * @throws IOException */ protected String readLine() throws TimeoutException, IOException { StringBuffer line = new StringBuffer(); while (true) { try { int ch = input.read(); if (ch == -1) { return null; } else if (ch == '\n' || ch == '\r') { if (line.length() > 0) { return line.toString(); } } else { line.append((char) ch); } } catch (IOException ex) { if (ex.getCause() instanceof SerialPortTimeoutException) { throw new TimeoutException(ex.getMessage()); } throw ex; } } } @Override public void close() throws IOException { try { disconnect(); } catch (Exception e) { throw new IOException(e); } } public String getPortName() { return portName; } public void setPortName(String portName) { this.portName = portName; } public int getBaud() { return baud; } public void setBaud(int baud) { this.baud = baud; } public FlowControl getFlowControl() { return flowControl; } public void setFlowControl(FlowControl flowControl) { this.flowControl = flowControl; } public DataBits getDataBits() { return dataBits; } public void setDataBits(DataBits dataBits) { this.dataBits = dataBits; } public StopBits getStopBits() { return stopBits; } public void setStopBits(StopBits stopBits) { this.stopBits = stopBits; } public Parity getParity() { return parity; } public void setParity(Parity parity) { this.parity = parity; } public boolean isSetDtr() { return setDtr; } public void setSetDtr(boolean setDtr) { this.setDtr = setDtr; } public boolean isSetRts() { return setRts; } public void setSetRts(boolean setRts) { this.setRts = setRts; } @Override public Icon getPropertySheetHolderIcon() { return null; } @Override public String getPropertySheetHolderTitle() { return getClass().getSimpleName(); } @Override public PropertySheetHolder[] getChildPropertySheetHolders() { return null; } @Override public Action[] getPropertySheetHolderActions() { return null; } @Override public PropertySheet[] getPropertySheets() { return new PropertySheet[] {new PropertySheetWizardAdapter(getConfigurationWizard())}; } @Override public Wizard getConfigurationWizard() { return new AbstractSerialPortDriverConfigurationWizard(this); } /** * SerialInputStream and SerialOutputStream are from the pull request referenced in: * https://github.com/scream3r/java-simple-serial-connector/issues/17 * * If that pull request is ever merged we can update and remove these. */ /** * Class that wraps a {@link SerialPort} to provide {@link InputStream} functionality. This * stream also provides support for performing blocking reads with timeouts. <br> * It is instantiated by passing the constructor a {@link SerialPort} instance. Do not create * multiple streams for the same serial port unless you implement your own synchronization. * * @author Charles Hache <chalz@member.fsf.org> * */ public class SerialInputStream extends InputStream { private SerialPort serialPort; private int defaultTimeout = 0; /** * Instantiates a SerialInputStream for the given {@link SerialPort} Do not create multiple * streams for the same serial port unless you implement your own synchronization. * * @param sp The serial port to stream. */ public SerialInputStream(SerialPort sp) { serialPort = sp; } /** * Set the default timeout (ms) of this SerialInputStream. This affects subsequent calls to * {@link #read()}, {@link #blockingRead(int[])}, and {@link #blockingRead(int[], int, int)} * The default timeout can be 'unset' by setting it to 0. * * @param time The timeout in milliseconds. */ public void setTimeout(int time) { defaultTimeout = time; } /** * Reads the next byte from the port. If the timeout of this stream has been set, then this * method blocks until data is available or until the timeout has been hit. If the timeout * is not set or has been set to 0, then this method blocks indefinitely. */ @Override public int read() throws IOException { return read(defaultTimeout); } /** * The same contract as {@link #read()}, except overrides this stream's default timeout with * the given timeout in milliseconds. * * @param timeout The timeout in milliseconds. * @return The read byte. * @throws IOException On serial port error or timeout */ public int read(int timeout) throws IOException { byte[] buf = new byte[1]; try { if (timeout > 0) { buf = serialPort.readBytes(1, timeout); } else { buf = serialPort.readBytes(1); } return buf[0]; } catch (Exception e) { throw new IOException(e); } } /** * Non-blocking read of up to buf.length bytes from the stream. This call behaves as * read(buf, 0, buf.length) would. * * @param buf The buffer to fill. * @return The number of bytes read, which can be 0. * @throws IOException on error. */ @Override public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } /** * Non-blocking read of up to length bytes from the stream. This method returns what is * immediately available in the input buffer. * * @param buf The buffer to fill. * @param offset The offset into the buffer to start copying data. * @param length The maximum number of bytes to read. * @return The actual number of bytes read, which can be 0. * @throws IOException on error. */ @Override public int read(byte[] buf, int offset, int length) throws IOException { if (buf.length < offset + length) length = buf.length - offset; int available = this.available(); if (available > length) available = length; try { byte[] readBuf = serialPort.readBytes(available); System.arraycopy(readBuf, 0, buf, offset, length); return readBuf.length; } catch (Exception e) { throw new IOException(e); } } /** * Blocks until buf.length bytes are read, an error occurs, or the default timeout is hit * (if specified). This behaves as blockingRead(buf, 0, buf.length) would. * * @param buf The buffer to fill with data. * @return The number of bytes read. * @throws IOException On error or timeout. */ public int blockingRead(byte[] buf) throws IOException { return blockingRead(buf, 0, buf.length, defaultTimeout); } /** * The same contract as {@link #blockingRead(byte[])} except overrides this stream's default * timeout with the given one. * * @param buf The buffer to fill. * @param timeout The timeout in milliseconds. * @return The number of bytes read. * @throws IOException On error or timeout. */ public int blockingRead(byte[] buf, int timeout) throws IOException { return blockingRead(buf, 0, buf.length, timeout); } /** * Blocks until length bytes are read, an error occurs, or the default timeout is hit (if * specified). Saves the data into the given buffer at the specified offset. If the stream's * timeout is not set, behaves as {@link #read(byte[], int, int)} would. * * @param buf The buffer to fill. * @param offset The offset in buffer to save the data. * @param length The number of bytes to read. * @return the number of bytes read. * @throws IOException on error or timeout. */ public int blockingRead(byte[] buf, int offset, int length) throws IOException { return blockingRead(buf, offset, length, defaultTimeout); } /** * The same contract as {@link #blockingRead(byte[], int, int)} except overrides this * stream's default timeout with the given one. * * @param buf The buffer to fill. * @param offset Offset in the buffer to start saving data. * @param length The number of bytes to read. * @param timeout The timeout in milliseconds. * @return The number of bytes read. * @throws IOException On error or timeout. */ public int blockingRead(byte[] buf, int offset, int length, int timeout) throws IOException { if (buf.length < offset + length) throw new IOException("Not enough buffer space for serial data"); if (timeout < 1) return read(buf, offset, length); try { byte[] readBuf = serialPort.readBytes(length, timeout); System.arraycopy(readBuf, 0, buf, offset, length); return readBuf.length; } catch (Exception e) { throw new IOException(e); } } @Override public int available() throws IOException { int ret; try { ret = serialPort.getInputBufferBytesCount(); if (ret >= 0) return ret; throw new IOException("Error checking available bytes from the serial port."); } catch (Exception e) { throw new IOException("Error checking available bytes from the serial port."); } } } /** * Class that wraps a {@link SerialPort} to provide {@link OutputStream} functionality. <br> * It is instantiated by passing the constructor a {@link SerialPort} instance. Do not create * multiple streams for the same serial port unless you implement your own synchronization. * * @author Charles Hache <chalz@member.fsf.org> * */ public class SerialOutputStream extends OutputStream { SerialPort serialPort; /** * Instantiates a SerialOutputStream for the given {@link SerialPort} Do not create multiple * streams for the same serial port unless you implement your own synchronization. * * @param sp The serial port to stream. */ public SerialOutputStream(SerialPort sp) { serialPort = sp; } @Override public void write(int b) throws IOException { try { serialPort.writeInt(b); } catch (SerialPortException e) { throw new IOException(e); } } @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(byte[] b, int off, int len) throws IOException { byte[] buffer = new byte[len]; System.arraycopy(b, off, buffer, 0, len); try { serialPort.writeBytes(buffer); } catch (SerialPortException e) { throw new IOException(e); } } } }