blob: ef18b1291d00f600c2ff067958b4a95991f92a10 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.serial;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.cdt.serial.internal.Messages;
/**
* @since 5.8
*/
public class SerialPort {
private final String portName;
private boolean isOpen;
private boolean isPaused;
private Object pauseMutex = new Object();
private int baudRate = StandardBaudRates.getDefault();
private ByteSize byteSize = ByteSize.B8;
private Parity parity = Parity.None;
private StopBits stopBits = StopBits.S1;
private long handle;
private static final String PORT_OPEN = Messages.getString("SerialPort.PortIsOpen"); //$NON-NLS-1$
private static final Map<String, LinkedList<WeakReference<SerialPort>>> openPorts = new HashMap<>();
static {
try {
System.loadLibrary("serial"); //$NON-NLS-1$
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
}
private InputStream inputStream = new InputStream() {
private byte[] rbuff = new byte[256];
private int rpos = 0;
private int rlen = 0;
@Override
public int read() throws IOException {
if (isOpen()) {
if (rpos >= rlen) {
while (true) {
rlen = read1(handle, rbuff, 0, rbuff.length);
if (rlen < 0) {
if (isPaused) {
synchronized (pauseMutex) {
while (isPaused) {
try {
pauseMutex.wait();
} catch (InterruptedException e) {
return -1;
}
}
}
} else {
return -1;
}
} else if (rlen > 0) {
// Reset the pointer, as we have new data
rpos = 0;
break;
}
}
}
return rbuff[rpos++];
} else {
return -1;
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (isOpen()) {
int n = rlen - rpos;
if (n > 0) {
if (len < n) {
n = len;
}
System.arraycopy(rbuff, rpos, b, off, n);
rpos += n;
return n;
} else {
n = read1(handle, b, off, len);
if (n <= 0 && isPaused) {
synchronized (pauseMutex) {
while (isPaused) {
try {
pauseMutex.wait();
} catch (InterruptedException e) {
return -1;
}
}
}
return read1(handle, b, off, len);
} else {
return n;
}
}
} else {
return -1;
}
}
@Override
public void close() throws IOException {
SerialPort.this.close();
}
@Override
public int available() throws IOException {
if (isOpen()) {
return available0(handle);
} else {
return 0;
}
}
};
private OutputStream outputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
if (isOpen()) {
try {
write0(handle, b);
} catch (IOException e) {
if (isPaused) {
synchronized (pauseMutex) {
while (isPaused) {
try {
pauseMutex.wait();
} catch (InterruptedException e1) {
throw e;
}
}
}
write0(handle, b);
}
}
}
}
@Override
public void write(byte[] buff, int off, int len) throws IOException {
if (isOpen()) {
try {
write1(handle, buff, off, len);
} catch (IOException e) {
if (isPaused) {
synchronized (pauseMutex) {
while (isPaused) {
try {
pauseMutex.wait();
} catch (InterruptedException e1) {
throw e;
}
}
}
write1(handle, buff, off, len);
}
}
}
}
@Override
public void close() throws IOException {
SerialPort.this.close();
}
};
private static String adjustPortName(String portName) {
if (System.getProperty("os.name").startsWith("Windows") && !portName.startsWith("\\\\.\\")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return "\\\\.\\" + portName; //$NON-NLS-1$
} else {
return portName;
}
}
/**
* Create a serial port that connect to the given serial device.
*
* @param portName
* name for the serial device.
*/
public SerialPort(String portName) {
this.portName = adjustPortName(portName);
}
private native long open0(String portName, int baudRate, int byteSize, int parity, int stopBits) throws IOException;
private native void close0(long handle) throws IOException;
private native int read1(long handle, byte[] b, int off, int len) throws IOException;
private native int available0(long handle) throws IOException;
private native void write0(long handle, int b) throws IOException;
private native void write1(long handle, byte[] b, int off, int len) throws IOException;
private static native String getPortName(int i) throws IOException;
private static String[] listDevs(final Pattern pattern) {
File dev = new File("/dev"); //$NON-NLS-1$
File[] files = dev.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
if (files == null) {
return new String[0];
}
String[] names = new String[files.length];
for (int i = 0; i < files.length; i++) {
names[i] = files[i].getAbsolutePath();
}
return names;
}
/**
* List the available serial ports.
*
* @return serial ports
*/
public static String[] list() throws IOException {
String osName = System.getProperty("os.name"); //$NON-NLS-1$
if (osName.equals("Mac OS X")) { //$NON-NLS-1$
return listDevs(Pattern.compile("cu\\..*")); //$NON-NLS-1$
} else if (osName.equals("Linux")) { //$NON-NLS-1$
return listDevs(Pattern.compile("(ttyUSB|ttyACM|ttyS).*")); //$NON-NLS-1$
} else if (osName.startsWith("Windows")) { //$NON-NLS-1$
List<String> ports = new ArrayList<>();
int i = 0;
String name = null;
do {
try {
name = getPortName(i++);
if (name != null) {
ports.add(name);
}
} catch (IOException e) {
// TODO log the exception
e.printStackTrace();
}
} while (name != null);
return ports.toArray(new String[ports.size()]);
} else {
return new String[0];
}
}
/**
* Return an the SerialPort with the given name or null if it hasn't been allocated yet. This
* would be used by components that need to pause and resume a serial port.
*
* @param portName
* @return
* @since 1.1
*/
public static SerialPort get(String portName) {
synchronized (openPorts) {
LinkedList<WeakReference<SerialPort>> list = openPorts.get(adjustPortName(portName));
if (list == null) {
return null;
}
Iterator<WeakReference<SerialPort>> i = list.iterator();
while (i.hasNext()) {
WeakReference<SerialPort> ref = i.next();
SerialPort port = ref.get();
if (port == null) {
i.remove();
} else {
return port;
}
}
return null;
}
}
/**
* Return the name for this serial port.
*
* @return serial port name
*/
public String getPortName() {
return portName;
}
public InputStream getInputStream() {
return inputStream;
}
public OutputStream getOutputStream() {
return outputStream;
}
public synchronized void open() throws IOException {
handle = open0(portName, baudRate, byteSize.getSize(), parity.ordinal(), stopBits.ordinal());
isOpen = true;
synchronized (openPorts) {
LinkedList<WeakReference<SerialPort>> list = openPorts.get(portName);
if (list == null) {
list = new LinkedList<>();
openPorts.put(portName, list);
}
list.addFirst(new WeakReference<>(this));
}
}
public synchronized void close() throws IOException {
if (isOpen) {
isOpen = false;
close0(handle);
handle = 0;
synchronized (openPorts) {
LinkedList<WeakReference<SerialPort>> list = openPorts.get(portName);
if (list != null) {
Iterator<WeakReference<SerialPort>> i = list.iterator();
while (i.hasNext()) {
WeakReference<SerialPort> ref = i.next();
SerialPort port = ref.get();
if (port == null || port.equals(this)) {
i.remove();
}
}
}
}
try {
// Sleep for a second since some serial ports take a while to actually close
Thread.sleep(500);
} catch (InterruptedException e) {
// nothing to do
}
}
}
public boolean isOpen() {
return isOpen;
}
public void pause() throws IOException {
if (!isOpen) {
return;
}
synchronized (pauseMutex) {
isPaused = true;
close0(handle);
try {
// Sleep for a second since some serial ports take a while to actually close
Thread.sleep(500);
} catch (InterruptedException e) {
// nothing to do
}
}
}
public void resume() throws IOException {
synchronized (pauseMutex) {
if (!isPaused) {
return;
}
isPaused = false;
handle = open0(portName, baudRate, byteSize.getSize(), parity.ordinal(), stopBits.ordinal());
isOpen = true;
pauseMutex.notifyAll();
}
}
/**
*
* @param rate
* @throws IOException
* @deprecated Use {@link #setBaudRateValue(int)}
*/
@Deprecated
public void setBaudRate(BaudRate rate) throws IOException {
if (isOpen) {
throw new IOException(PORT_OPEN);
}
this.baudRate = rate.getRate();
}
/**
* @since 1.2
*/
public void setBaudRateValue(int rate) throws IOException {
if (isOpen) {
throw new IOException(PORT_OPEN);
}
this.baudRate = rate;
}
/**
* @return the baud rate or closest match. Will only
* return same value as {@link #setBaudRate(BaudRate)},
* may not match value passed {@link #setBaudRateValue(int)}
* @deprecated Use {@link #getBaudRateValue()}
*/
@Deprecated
public BaudRate getBaudRate() {
return BaudRate.getClosest(baudRate);
}
/**
* @since 1.2
*/
public int getBaudRateValue() {
return baudRate;
}
public void setByteSize(ByteSize size) throws IOException {
if (isOpen) {
throw new IOException(PORT_OPEN);
}
this.byteSize = size;
}
public ByteSize getByteSize() {
return byteSize;
}
public void setParity(Parity parity) throws IOException {
if (isOpen) {
throw new IOException(PORT_OPEN);
}
this.parity = parity;
}
public Parity getParity() {
return parity;
}
public void setStopBits(StopBits stopBits) throws IOException {
if (isOpen) {
throw new IOException(PORT_OPEN);
}
this.stopBits = stopBits;
}
public StopBits getStopBits() {
return stopBits;
}
}