package org.hihan.girinoscope.comm;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.hihan.girinoscope.Native;
/**
* Reading operations are semi-interruptible here. As long as nothing as been
* read, it can be interrupted, but once something has been read, it continues
* up to the line / buffer completion. This behavior is here to avoid to stop
* reading a bunch of data sent by the Girino. It do it fast enough not to
* bother us. The only delay we want to interrupt is when nothing is coming, per
* instance when we wait for the trigger to happen. A crossover can still occur
* - the trigger happening the same time the user cancel the operation - but it
* is not likely to happen and the Girino doesn’t support a complex enough
* protocol to prevent this kind of problem anyway. On the other hand, the
* consequence is not fatal. We will read garbage the next time, display some
* error to the user and move along.
*/
public class Serial implements Closeable {
private static final Logger logger = Logger.getLogger(Serial.class.getName());
static {
Native.setLibraryPath();
}
/**
* The port we're normally going to use. Port detection could be forced by
* setting a property: -Dgnu.io.rxtx.SerialPorts=portName
*/
private static final Pattern[] ACCEPTABLE_PORT_NAMES = {
//
Pattern.compile("/dev/tty\\.usbserial-.+"), // Mac OS X
Pattern.compile("/dev/tty\\.usbmodem.+"), // Mac OS X
Pattern.compile("/dev/ttyACM\\d+"), // Raspberry Pi
Pattern.compile("/dev/ttyUSB\\d+"), // Linux
// Pattern.compile("/dev/rfcomm\\d+"), // Linux Bluetooth
Pattern.compile("COM\\d+"), // Windows
};
/** Milliseconds to block while waiting for port open. */
private static final int TIME_OUT = 2000;
/** Default bits per second for COM port. */
private static final int DATA_RATE = 115200;
/** Milliseconds to wait when no input is available. */
private static final int READ_DELAY = 200;
private SerialPort serialPort;
/** The output stream to the port. */
private InputStream input;
/**
* A BufferedReader which will be fed by a InputStreamReader converting the
* bytes into characters making the displayed results codepage independent.
*/
private OutputStream output;
public Serial(CommPortIdentifier portId) throws Exception {
connect(portId);
}
public void connect(CommPortIdentifier portId) throws Exception {
serialPort = (SerialPort) portId.open(getClass().getName(), TIME_OUT);
serialPort.setSerialPortParams(DATA_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
output = serialPort.getOutputStream();
input = serialPort.getInputStream();
serialPort.notifyOnDataAvailable(false);
}
public static List<CommPortIdentifier> enumeratePorts() {
List<CommPortIdentifier> ports = new LinkedList<CommPortIdentifier>();
Enumeration<?> portEnum = CommPortIdentifier.getPortIdentifiers();
while (portEnum.hasMoreElements()) {
CommPortIdentifier portIdentifier = (CommPortIdentifier) portEnum.nextElement();
for (Pattern acceptablePortName : ACCEPTABLE_PORT_NAMES) {
String portName = portIdentifier.getName();
if (acceptablePortName.matcher(portName).matches()) {
ports.add(portIdentifier);
}
}
}
return ports;
}
public String readLine() throws IOException {
StringBuilder line = new StringBuilder();
int length = 0;
try {
while (true) {
int c;
if ((input.available() > 0 || line.length() > 0) && (c = input.read()) >= 0) {
line.append((char) c);
++length;
boolean eol = length >= 2 && line.charAt(length - 2) == '\r' && line.charAt(length - 1) == '\n';
if (eol) {
line.setLength(length - 2);
break;
}
} else {
/*
* Sleeping here allows us to be interrupted (the serial
* input is not interruptible itself).
*/
Thread.sleep(READ_DELAY);
}
}
} catch (InterruptedException e) {
logger.log(Level.FINE, "Read aborted");
return null;
}
logger.log(Level.FINE, "< ({0})", line);
return line.toString();
}
public int readBytes(byte[] buffer) throws IOException {
int offset = 0;
try {
while (offset < buffer.length) {
if (input.available() > 0 || offset > 0) {
int size = input.read(buffer, offset, buffer.length - offset);
if (size < 0) {
break;
}
offset += size;
} else {
/*
* Sleeping here allows us to be interrupted (the serial
* input is not interruptible itself).
*/
Thread.sleep(READ_DELAY);
}
}
} catch (InterruptedException e) {
logger.log(Level.FINE, "Read aborted");
return -1;
}
logger.log(Level.FINE, "< {0} byte(s)", offset);
return offset;
}
public void writeLine(String line) throws IOException {
for (int i = 0; i < line.length(); ++i) {
output.write(line.charAt(i));
}
output.flush();
logger.log(Level.FINE, "> ({0})", line);
}
@Override
public void close() {
if (serialPort != null) {
try {
output.flush();
} catch (IOException e) {
logger.log(Level.WARNING, "When flushing output before closing serial.", e);
}
serialPort.close();
serialPort = null;
}
}
}