jtermios.windows.JTermiosImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of filo-firmata Show documentation
Show all versions of filo-firmata Show documentation
FiloFirmata is a client library for the Firmata protocol used with hardware project boards.
/*
* Copyright (c) 2011, Kustaa Nyholm / SpareTimeLabs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*/
package jtermios.windows;
import java.util.*;
import java.util.regex.Pattern;
import com.sun.jna.*;
import com.sun.jna.ptr.IntByReference;
import static jtermios.JTermios.*;
import static jtermios.JTermios.JTermiosLogging.*;
import jtermios.*;
import jtermios.windows.WinAPI.*;
import static jtermios.windows.WinAPI.*;
import static jtermios.windows.WinAPI.DCB.*;
public class JTermiosImpl implements jtermios.JTermios.JTermiosInterface {
private volatile int m_ErrNo = 0;
private volatile boolean m_PortFDs[] = new boolean[FDSetImpl.FD_SET_SIZE];
private volatile Hashtable m_OpenPorts = new Hashtable();
private class Port {
volatile int m_FD = -1;
volatile boolean m_Locked;
volatile HANDLE m_Comm;
volatile int m_OpenFlags;
volatile DCB m_DCB = new DCB();
volatile COMMTIMEOUTS m_Timeouts = new COMMTIMEOUTS();
volatile COMSTAT m_COMSTAT = new COMSTAT();
volatile int[] m_ClearErr = { 0 };
volatile Memory m_RdBuffer = new Memory(2048);
volatile int[] m_RdErr = { 0 };
volatile int m_RdN[] = { 0 };
volatile OVERLAPPED m_RdOVL = new OVERLAPPED();
volatile HANDLE m_ReadCancelObject;
volatile HANDLE[] m_ReadWaitObjects = new HANDLE[2];
volatile Memory m_WrBuffer = new Memory(2048);
volatile COMSTAT m_WrStat = new COMSTAT();
volatile int[] m_WrErr = { 0 };
volatile int m_WrN[] = { 0 };
volatile int m_WritePending;
volatile OVERLAPPED m_WrOVL = new OVERLAPPED();
volatile HANDLE m_WriteCancelObject;
volatile HANDLE[] m_WriteWaitObjects = new HANDLE[2];
volatile boolean m_WaitPending;
volatile int m_SelN[] = { 0 };
volatile HANDLE WaitCommEventCancelObject;
volatile OVERLAPPED m_SelOVL = new OVERLAPPED();
volatile IntByReference m_EventFlags = new IntByReference();
volatile Termios m_Termios = new Termios();
volatile int MSR; // initial value
// these cached values are used to detect changes in termios structure and speed up things by avoiding unnecessary updates
volatile int m_VTIME = -1;
volatile int m_VMIN = -1;
volatile int m_c_speed = -1;
volatile int m_c_cflag = -1;
volatile int m_c_iflag = -1;
volatile int m_c_oflag = -1;
synchronized public void fail() throws Fail {
int err = GetLastError();
Memory buffer = new Memory(2048);
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, (int) buffer.size(), null);
log = log && log(1, "fail() %s, Windows GetLastError()= %d, %s\n", lineno(1), err, buffer.getWideString(0));
// FIXME here convert from Windows error code to 'posix' error code
Fail f = new Fail();
throw f;
}
synchronized public void lock() throws InterruptedException {
while (m_Locked)
wait();
m_Locked = true;
}
synchronized public void unlock() {
if (!m_Locked)
throw new IllegalArgumentException("Port was not locked");
m_Locked = false;
notifyAll();
}
synchronized public void waitUnlock() {
while (m_Locked) {
try {
wait();
} catch (InterruptedException e) {
// interruption cannot cancel wait
}
}
}
public void open(String filename, int flags) throws Fail {
synchronized (JTermiosImpl.this) {
m_FD = -1;
for (int i = 0; i < m_PortFDs.length; ++i) {
if (!m_PortFDs[i]) {
m_FD = i;
m_PortFDs[i] = true;
m_OpenPorts.put(m_FD, this);
m_OpenFlags = flags;
if (!filename.startsWith("\\\\"))
filename = "\\\\.\\" + filename;
m_Comm = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, null);
if (INVALID_HANDLE_VALUE == m_Comm) {
if (GetLastError() == ERROR_FILE_NOT_FOUND)
m_ErrNo = ENOENT;
else
m_ErrNo = EBUSY;
fail();
}
if (!SetupComm(m_Comm, (int) m_RdBuffer.size(), (int) m_WrBuffer.size()))
fail(); // FIXME what would be appropriate error code here
cfmakeraw(m_Termios);
cfsetispeed(m_Termios, B9600);
cfsetospeed(m_Termios, B9600);
m_Termios.c_cc[VTIME] = 0;
m_Termios.c_cc[VMIN] = 0;
updateFromTermios(this);
WaitCommEventCancelObject = CreateEvent(null, false, false, null);
if (WaitCommEventCancelObject == INVALID_HANDLE_VALUE)
fail();
m_ReadCancelObject = CreateEvent(null, true, false, null);
if (m_ReadCancelObject == INVALID_HANDLE_VALUE)
fail();
m_ReadWaitObjects[1] = m_ReadCancelObject;
m_WriteCancelObject = CreateEvent(null, true, false, null);
if (m_WriteCancelObject == INVALID_HANDLE_VALUE)
fail();
m_WriteWaitObjects[1] = m_WriteCancelObject;
m_RdOVL.writeField("hEvent", CreateEvent(null, true, false, null));
if (m_RdOVL.hEvent == INVALID_HANDLE_VALUE)
fail();
m_WrOVL.writeField("hEvent", CreateEvent(null, true, false, null));
if (m_WrOVL.hEvent == INVALID_HANDLE_VALUE)
fail();
m_SelOVL.writeField("hEvent", CreateEvent(null, true, false, null));
if (m_SelOVL.hEvent == INVALID_HANDLE_VALUE)
fail();
return;
}
}
throw new RuntimeException("Too many ports open");
}
}
public void close() {
synchronized (JTermiosImpl.this) {
if (m_FD >= 0) {
m_OpenPorts.remove(m_FD);
m_PortFDs[m_FD] = false;
m_FD = -1;
}
HANDLE h; // / 'hEvent' might never have been 'read' so read it to this var first
if (m_ReadCancelObject != null)
SetEvent(m_ReadCancelObject);
synchronized (m_RdBuffer) {
h = (HANDLE) m_RdOVL.readField("hEvent");
m_RdOVL = null;
if (h != null && !h.equals(NULL) && !h.equals(INVALID_HANDLE_VALUE))
CloseHandle(h);
if (m_ReadCancelObject != null && m_ReadCancelObject != NULL && m_ReadCancelObject != INVALID_HANDLE_VALUE)
CloseHandle(m_ReadCancelObject);
m_ReadCancelObject = null;
}
if (m_WriteCancelObject != null)
SetEvent(m_WriteCancelObject);
synchronized (m_WrBuffer) {
h = (HANDLE) m_WrOVL.readField("hEvent");
m_WrOVL = null;
if (h != null && !h.equals(NULL) && !h.equals(INVALID_HANDLE_VALUE))
CloseHandle(h);
if (m_WriteCancelObject != null && m_WriteCancelObject != NULL && m_WriteCancelObject != INVALID_HANDLE_VALUE)
CloseHandle(m_WriteCancelObject);
m_ReadCancelObject = null;
}
if (WaitCommEventCancelObject != null)
SetEvent(WaitCommEventCancelObject);
waitUnlock();
if (WaitCommEventCancelObject != null && WaitCommEventCancelObject != NULL && WaitCommEventCancelObject != INVALID_HANDLE_VALUE)
CloseHandle(WaitCommEventCancelObject);
WaitCommEventCancelObject = null;
h = (HANDLE) m_SelOVL.readField("hEvent");
m_SelOVL = null;
if (h != null && !h.equals(NULL) && !h.equals(INVALID_HANDLE_VALUE))
CloseHandle(h);
if (m_Comm != null && m_Comm != NULL && m_Comm != INVALID_HANDLE_VALUE)
CloseHandle(m_Comm);
m_Comm = null;
}
}
};
static class Fail extends Exception {
}
//static private class FDSetImpl extends FDSet {
// static final int FD_SET_SIZE = 256; // Windows supports max 255 serial ports so this is enough
// static final int NFBBITS = 32;
// int[] bits = new int[(FD_SET_SIZE + NFBBITS - 1) / NFBBITS];
//}
static private class FDSetImpl implements FDSet {
static final int FD_SET_SIZE = 256; // Windows supports max 255 serial ports so this is enough
static final int NFBBITS = 32;
int[] bits = new int[(FD_SET_SIZE + NFBBITS - 1) / NFBBITS];
public void FD_CLR(int fd) {
bits[fd / NFBBITS] &= ~(1 << (fd % NFBBITS));
}
public boolean FD_ISSET(int fd) {
return (bits[fd / NFBBITS] & (1 << (fd % NFBBITS))) != 0;
}
public void FD_SET(int fd) {
bits[fd / NFBBITS] |= 1 << (fd % NFBBITS);
}
public void FD_ZERO() {
java.util.Arrays.fill(bits, 0);
}
}
public JTermiosImpl() {
log = log && log(1, "instantiating %s\n", getClass().getCanonicalName());
}
public int errno() {
return m_ErrNo;
}
public void cfmakeraw(Termios termios) {
termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
termios.c_oflag &= ~OPOST;
termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios.c_cflag &= ~(CSIZE | PARENB);
termios.c_cflag |= CS8;
}
public int fcntl(int fd, int cmd, int arg) {
Port port = getPort(fd);
if (port == null)
return -1;
if (F_SETFL == cmd)
port.m_OpenFlags = arg;
else if (F_GETFL == cmd)
return port.m_OpenFlags;
else {
m_ErrNo = ENOTSUP;
return -1;
}
return 0;
}
public int tcdrain(int fd) {
Port port = getPort(fd);
if (port == null)
return -1;
try {
synchronized (port.m_WrBuffer) {
if (!FlushFileBuffers(port.m_Comm))
port.fail();
return 0;
}
} catch (Fail f) {
return -1;
}
}
public int cfgetispeed(Termios termios) {
return termios.c_ispeed;
}
public int cfgetospeed(Termios termios) {
return termios.c_ospeed;
}
public int cfsetispeed(Termios termios, int speed) {
termios.c_ispeed = speed;
return 0;
}// Error code for Interrupted = EINTR
public int cfsetospeed(Termios termios, int speed) {
termios.c_ospeed = speed;
return 0;
}
public int open(String filename, int flags) {
Port port = new Port();
try {
port.open(filename, flags);
return port.m_FD;
} catch (Fail f) {
port.close();
return -1;
}
}
private static void nanoSleep(long nsec) throws Fail {
try {
Thread.sleep((int) (nsec / 1000000), (int) (nsec % 1000000));
} catch (InterruptedException ie) {
throw new Fail();
}
}
private int getCharBits(Termios tios) {
int cs = 8; // default to 8
if ((tios.c_cflag & CSIZE) == CS5)
cs = 5;
if ((tios.c_cflag & CSIZE) == CS6)
cs = 6;
if ((tios.c_cflag & CSIZE) == CS7)
cs = 7;
if ((tios.c_cflag & CSIZE) == CS8)
cs = 8;
if ((tios.c_cflag & CSTOPB) != 0)
cs++; // extra stop bit
if ((tios.c_cflag & PARENB) != 0)
cs++; // parity adds an other bit
cs += 1 + 1; // start bit + stop bit
return cs;
}
private static int min(int a, int b) {
return a < b ? a : b;
}
private static int max(int a, int b) {
return a > b ? a : b;
}
public int read(int fd, byte[] buffer, int length) {
Port port = getPort(fd);
if (port == null)
return -1;
synchronized (port.m_RdBuffer) {
try {
// limit reads to internal buffer size
if (length > port.m_RdBuffer.size())
length = (int) port.m_RdBuffer.size();
if (length == 0)
return 0;
int error;
if ((port.m_OpenFlags & O_NONBLOCK) != 0) {
clearCommErrors(port);
int available = port.m_COMSTAT.cbInQue;
if (available == 0) {
m_ErrNo = EAGAIN;
return -1;
}
length = min(length, available);
} else {
clearCommErrors(port);
int available = port.m_COMSTAT.cbInQue;
int vtime = 0xff & port.m_Termios.c_cc[VTIME];
int vmin = 0xff & port.m_Termios.c_cc[VMIN];
if (vmin == 0 && vtime == 0) {
// VMIN = 0 and VTIME = 0 => totally non blocking,if data is
// available, return it, ie this is poll operation
// For reference below commented out is how timeouts are set for this vtime/vmin combo
//touts.ReadIntervalTimeout = MAXDWORD;
//touts.ReadTotalTimeoutConstant = 0;
//touts.ReadTotalTimeoutMultiplier = 0;
if (available == 0)
return 0;
length = min(length, available);
}
if (vmin == 0 && vtime > 0) {
// VMIN = 0 and VTIME > 0 => timed read, return as soon as data is
// available, VTIME = total time
// For reference below commented out is how timeouts are set for this vtime/vmin combo
//touts.ReadIntervalTimeout = 0;
//touts.ReadTotalTimeoutConstant = vtime;
//touts.ReadTotalTimeoutMultiplier = 0;
// NOTE to behave like unix we should probably wait until there is something available
// and then try to do a read as many bytes as are available bytes at that point in time.
// As this is coded now, this will attempt to read as many bytes as requested and this may end up
// spending vtime in the read when a unix would return less bytes but as soon as they become
// available.
}
if (vmin > 0 && vtime > 0) {
// VMIN > 0 and VTIME > 0 => blocks until VMIN chars has arrived or between chars expired,
// note that this will block if nothing arrives
// For reference below commented out is how timeouts are set for this vtime/vmin combo
//touts.ReadIntervalTimeout = vtime;
//touts.ReadTotalTimeoutConstant = 0;
//touts.ReadTotalTimeoutMultiplier = 0;
length = min(max(vmin, available), length);
}
if (vmin > 0 && vtime == 0) {
// VMIN > 0 and VTIME = 0 => blocks until VMIN characters have been
// received
// For reference below commented out is how timeouts are set for this vtime/vmin combo
//touts.ReadIntervalTimeout = 0;
//touts.ReadTotalTimeoutConstant = 0;
//touts.ReadTotalTimeoutMultiplier = 0;
length = min(max(vmin, available), length);
}
}
if (!ResetEvent(port.m_RdOVL.hEvent))
port.fail();
if (!ReadFile(port.m_Comm, port.m_RdBuffer, length, port.m_RdN, port.m_RdOVL)) {
if (GetLastError() != ERROR_IO_PENDING)
port.fail();
port.m_ReadWaitObjects[0] = port.m_RdOVL.hEvent;
if (WaitForMultipleObjects(2, port.m_ReadWaitObjects, false, INFINITE) != WAIT_OBJECT_0)
port.fail();
if (!GetOverlappedResult(port.m_Comm, port.m_RdOVL, port.m_RdN, true))
port.fail();
}
port.m_RdBuffer.read(0, buffer, 0, port.m_RdN[0]);
return port.m_RdN[0];
} catch (Fail ie) {
return -1;
}
}
}
public int write(int fd, byte[] buffer, int length) {
Port port = getPort(fd);
if (port == null)
return -1;
synchronized (port.m_WrBuffer) {
try {
if (port.m_WritePending > 0) {
while (true) {
port.m_WriteWaitObjects[0] = port.m_WrOVL.hEvent;
int res = WaitForMultipleObjects(2, port.m_WriteWaitObjects, false, INFINITE);
if (res == WAIT_TIMEOUT) { // Hmmm, can this ever happen, why we have that here
clearCommErrors(port);
log = log && log(1, "write pending, cbInQue %d cbOutQue %d\n", port.m_COMSTAT.cbInQue, port.m_COMSTAT.cbOutQue);
continue;
}
if (!GetOverlappedResult(port.m_Comm, port.m_WrOVL, port.m_WrN, false))
port.fail();
if (port.m_WrN[0] != port.m_WritePending) // I exptect this is never going to happen, if it does
new RuntimeException("Windows OVERLAPPED WriteFile failed to write all, tried to write " + port.m_WritePending + " but got " + port.m_WrN[0]);
break;
}
port.m_WritePending = 0;
}
if ((port.m_OpenFlags & O_NONBLOCK) != 0) {
if (!ClearCommError(port.m_Comm, port.m_WrErr, port.m_WrStat))
port.fail();
int room = (int) port.m_WrBuffer.size() - port.m_WrStat.cbOutQue;
if (length > room)
length = room;
}
int old_flag;
if (!ResetEvent(port.m_WrOVL.hEvent))
port.fail();
if (length > port.m_WrBuffer.size())
length = (int) port.m_WrBuffer.size();
port.m_WrBuffer.write(0, buffer, 0, length); // copy from buffer to Memory
boolean ok = WriteFile(port.m_Comm, port.m_WrBuffer, length, port.m_WrN, port.m_WrOVL);
if (!ok) {
if (GetLastError() != ERROR_IO_PENDING)
port.fail();
port.m_WritePending = length;
}
//
return length; // port.m_WrN[0];
} catch (Fail f) {
return -1;
}
}
}
public int close(int fd) {
Port port = getPort(fd);
if (port == null)
return -1;
port.close();
return 0;
}
public int tcflush(int fd, int queue) {
Port port = getPort(fd);
if (port == null)
return -1;
try {
if (queue == TCIFLUSH) {
if (!PurgeComm(port.m_Comm, PURGE_RXABORT))
port.fail();
} else if (queue == TCOFLUSH) {
if (!PurgeComm(port.m_Comm, PURGE_TXABORT))
port.fail();
} else if (queue == TCIOFLUSH) {
if (!PurgeComm(port.m_Comm, PURGE_TXABORT))
port.fail();
if (!PurgeComm(port.m_Comm, PURGE_RXABORT))
port.fail();
} else {
m_ErrNo = ENOTSUP;
return -1;
}
return 0;
} catch (Fail f) {
return -1;
}
}
/*
* (non-Javadoc) Basically this is wrong, as tcsetattr is supposed to set
* only those things it can support and tcgetattr is the used to see that
* what actually happened. In this instance tcsetattr never fails and
* tcgetattr always returns the last settings even though it possible (even
* likely) that tcsetattr was not able to carry out all settings, as there
* is no 1:1 mapping between Windows Comm API and posix/termios API.
*
* @see jtermios.JTermios.JTermiosInterface#tcgetattr(int, jtermios.Termios)
*/
public int tcgetattr(int fd, Termios termios) {
Port port = getPort(fd);
if (port == null)
return -1;
termios.set(port.m_Termios);
return 0;
}
public int tcsendbreak(int fd, int duration) {
Port port = getPort(fd);
if (port == null)
return -1;
try {
if (!SetCommBreak(port.m_Comm))
port.fail();
nanoSleep(duration * 250000000L);
if (!ClearCommBreak(port.m_Comm))
port.fail();
return 0;
} catch (Fail f) {
return -1;
}
}
public int tcsetattr(int fd, int cmd, Termios termios) {
if (cmd != TCSANOW)
log(0, "tcsetattr only supports TCSANOW\n");
Port port = getPort(fd);
if (port == null)
return -1;
synchronized (port.m_Termios) {
try {
port.m_Termios.set(termios);
updateFromTermios(port);
return 0;
} catch (Fail f) {
return -1;
}
}
}
// FIXME this needs serious code review from people who know this stuff...
public int updateFromTermios(Port port) throws Fail {
Termios tios = port.m_Termios;
int c_speed = tios.c_ospeed;
int c_cflag = tios.c_cflag;
int c_iflag = tios.c_iflag;
int c_oflag = tios.c_oflag;
if (c_speed != port.m_c_speed || c_cflag != port.m_c_cflag || c_iflag != port.m_c_iflag || c_oflag != port.m_c_oflag) {
DCB dcb = port.m_DCB;
if (!GetCommState(port.m_Comm, dcb))
port.fail();
dcb.DCBlength = dcb.size();
dcb.BaudRate = c_speed;
if (tios.c_ospeed != tios.c_ispeed)
log(0, "c_ospeed (%d) != c_ispeed (%d)\n", tios.c_ospeed, tios.c_ispeed);
int flags = 0;
// rxtx does: if ( s_termios->c_iflag & ISTRIP ) dcb.fBinary = FALSE;
// but Winapi doc says fBinary always true
flags |= fBinary;
if ((c_cflag & PARENB) != 0)
flags |= fParity;
if ((c_iflag & IXON) != 0)
flags |= fOutX;
if ((c_iflag & IXOFF) != 0)
flags |= fInX;
if ((c_iflag & IXANY) != 0)
flags |= fTXContinueOnXoff;
if ((c_iflag & CRTSCTS) != 0) {
flags |= fRtsControl;
flags |= fOutxCtsFlow;
}
// Following have no corresponding functionality in unix termios
// fOutxDsrFlow = 0x00000008;
// fDtrControl = 0x00000030;
// fDsrSensitivity = 0x00000040;
// fErrorChar = 0x00000400;
// fNull = 0x00000800;
// fAbortOnError = 0x00004000;
// fDummy2 = 0xFFFF8000;
dcb.fFlags = flags;
// Don't touch these, windows seems to use: XonLim 2048 XoffLim 512 and who am I to argue with those
//dcb.XonLim = 0;
//dcb.XoffLim = 128;
byte cs = 8;
int csize = c_cflag & CSIZE;
if (csize == CS5)
cs = 5;
if (csize == CS6)
cs = 6;
if (csize == CS7)
cs = 7;
if (csize == CS8)
cs = 8;
dcb.ByteSize = cs;
if ((c_cflag & PARENB) != 0) {
if ((c_cflag & PARODD) != 0 && (c_cflag & CMSPAR) != 0)
dcb.Parity = MARKPARITY;
else if ((c_cflag & PARODD) != 0)
dcb.Parity = ODDPARITY;
else if ((c_cflag & CMSPAR) != 0)
dcb.Parity = SPACEPARITY;
else
dcb.Parity = EVENPARITY;
} else
dcb.Parity = NOPARITY;
dcb.StopBits = (c_cflag & CSTOPB) != 0 ? ((csize == CS5) ? ONE5STOPBITS : TWOSTOPBITS) : ONESTOPBIT;
dcb.XonChar = tios.c_cc[VSTART]; // In theory these could change but they only get updated if the baudrate/char size changes so this could be a time bomb
dcb.XoffChar = tios.c_cc[VSTOP]; // In practice in PJC these are never changed so updating on the first pass is enough
dcb.ErrorChar = 0;
// rxtx has some thing like
// if ( EV_BREAK|EV_CTS|EV_DSR|EV_ERR|EV_RING | ( EV_RLSD & EV_RXFLAG )
// )
// dcb.EvtChar = '\n';
// else
// dcb.EvtChar = '\0';
// But those are all defines so there is something fishy there?
dcb.EvtChar = '\n';
dcb.EofChar = tios.c_cc[VEOF];
if (!SetCommState(port.m_Comm, dcb))
port.fail();
port.m_c_speed = c_speed;
port.m_c_cflag = c_cflag;
port.m_c_iflag = c_iflag;
port.m_c_oflag = c_oflag;
}
int vmin = port.m_Termios.c_cc[VMIN] & 0xFF;
int vtime = (port.m_Termios.c_cc[VTIME] & 0xFF) * 100;
if (vmin != port.m_VMIN || vtime != port.m_VTIME) {
COMMTIMEOUTS touts = port.m_Timeouts;
// There are really no write timeouts in classic unix termios
// FIXME test that we can still interrupt the tread
touts.WriteTotalTimeoutConstant = 0;
touts.WriteTotalTimeoutMultiplier = 0;
if (vmin == 0 && vtime == 0) {
// VMIN = 0 and VTIME = 0 => totally non blocking,if data is
// available, return it, ie this is poll operation
touts.ReadIntervalTimeout = MAXDWORD;
touts.ReadTotalTimeoutConstant = 0;
touts.ReadTotalTimeoutMultiplier = 0;
}
if (vmin == 0 && vtime > 0) {
// VMIN = 0 and VTIME > 0 => timed read, return as soon as data is
// available, VTIME = total time
touts.ReadIntervalTimeout = 0;
touts.ReadTotalTimeoutConstant = vtime;
touts.ReadTotalTimeoutMultiplier = 0;
}
if (vmin > 0 && vtime > 0) {
// VMIN > 0 and VTIME > 0 => blocks until VMIN chars has arrived or between chars expired,
// note that this will block if nothing arrives
touts.ReadIntervalTimeout = vtime;
touts.ReadTotalTimeoutConstant = 0;
touts.ReadTotalTimeoutMultiplier = 0;
}
if (vmin > 0 && vtime == 0) {
// VMIN > 0 and VTIME = 0 => blocks until VMIN characters have been
// received
touts.ReadIntervalTimeout = 0;
touts.ReadTotalTimeoutConstant = 0;
touts.ReadTotalTimeoutMultiplier = 0;
}
if (!SetCommTimeouts(port.m_Comm, port.m_Timeouts))
port.fail();
port.m_VMIN = vmin;
port.m_VTIME = vtime;
log = log && log(2, "vmin %d vtime %d ReadIntervalTimeout %d ReadTotalTimeoutConstant %d ReadTotalTimeoutMultiplier %d\n", vmin, vtime, touts.ReadIntervalTimeout, touts.ReadTotalTimeoutConstant, touts.ReadTotalTimeoutMultiplier);
}
return 0;
}
private int maskToFDSets(Port port, FDSet readfds, FDSet writefds, FDSet exceptfds, int ready) throws Fail {
clearCommErrors(port);
int emask = port.m_EventFlags.getValue();
int fd = port.m_FD;
if ((emask & EV_RXCHAR) != 0 && port.m_COMSTAT.cbInQue > 0) {
FD_SET(fd, readfds);
ready++;
}
if ((emask & EV_TXEMPTY) != 0 && port.m_COMSTAT.cbOutQue == 0) {
FD_SET(fd, writefds);
ready++;
}
return ready;
}
private void clearCommErrors(Port port) throws Fail {
synchronized (port.m_COMSTAT) {
if (!ClearCommError(port.m_Comm, port.m_ClearErr, port.m_COMSTAT))
port.fail();
}
}
public int select(int n, FDSet readfds, FDSet writefds, FDSet exceptfds, TimeVal timeout) {
// long T0 = System.currentTimeMillis();
int ready = 0;
LinkedList locked = new LinkedList();
try {
try {
LinkedList waiting = new LinkedList();
for (int fd = 0; fd < n; fd++) {
boolean rd = FD_ISSET(fd, readfds);
boolean wr = FD_ISSET(fd, writefds);
FD_CLR(fd, readfds);
FD_CLR(fd, writefds);
if (rd || wr) {
Port port = getPort(fd);
if (port == null)
return -1;
try {
port.lock();
locked.add(port);
clearCommErrors(port);
// check if there is data to be read, as WaitCommEvent
// does check for only *new* data that and thus
// might wait indefinitely if select() is called twice
// without first reading away all data
if (rd && port.m_COMSTAT.cbInQue > 0) {
FD_SET(fd, readfds);
ready++;
}
if (wr && port.m_COMSTAT.cbOutQue == 0) {
FD_SET(fd, writefds);
ready++;
}
if (port.m_WaitPending) {
if (!SetCommMask(port.m_Comm, 0))
port.fail();
GetOverlappedResult(port.m_Comm, port.m_SelOVL, port.m_SelN, false);
port.m_WaitPending = false;
}
if (!ResetEvent(port.m_SelOVL.hEvent))
port.fail();
int flags = 0;
if (rd)
flags |= EV_RXCHAR;
if (wr)
flags |= EV_TXEMPTY;
if (!SetCommMask(port.m_Comm, flags))
port.fail();
if (WaitCommEvent(port.m_Comm, port.m_EventFlags, port.m_SelOVL)) {
if (!GetOverlappedResult(port.m_Comm, port.m_SelOVL, port.m_SelN, false))
port.fail();
// actually it seems that overlapped
// WaitCommEvent never returns true so we never get here
ready = maskToFDSets(port, readfds, writefds, exceptfds, ready);
} else {
// FIXME if the port dies on us what happens
if (GetLastError() != ERROR_IO_PENDING)
port.fail();
waiting.add(port);
port.m_WaitPending = true;
}
} catch (InterruptedException ie) {
m_ErrNo = EINTR;
return -1;
}
}
}
if (ready == 0) {
int waitn = waiting.size();
if (waitn > 0) {
HANDLE[] wobj = new HANDLE[waiting.size() * 2];
int i = 0;
for (Port port : waiting) {
wobj[i++] = port.m_SelOVL.hEvent;
wobj[i++] = port.WaitCommEventCancelObject;
}
int tout = timeout != null ? (int) (timeout.tv_sec * 1000 + timeout.tv_usec / 1000) : INFINITE;
// int res = WaitForSingleObject(wobj[0], tout);
int res = WaitForMultipleObjects(waitn * 2, wobj, false, tout);
if (res == WAIT_TIMEOUT) {
// work around the fact that sometimes we miss
// events
for (Port port : waiting) {
clearCommErrors(port);
int[] mask = { 0 };
if (!GetCommMask(port.m_Comm, mask))
port.fail();
if (port.m_COMSTAT.cbInQue > 0 && ((mask[0] & EV_RXCHAR) != 0)) {
FD_SET(port.m_FD, readfds);
log = log && log(1, "missed EV_RXCHAR event\n");
return 1;
}
if (port.m_COMSTAT.cbOutQue == 0 && ((mask[0] & EV_TXEMPTY) != 0)) {
FD_SET(port.m_FD, writefds);
log = log && log(1, "missed EV_TXEMPTY event\n");
return 1;
}
}
}
if (res != WAIT_TIMEOUT) {
i = (res - WAIT_OBJECT_0) / 2;
if (i < 0 || i >= waitn)
throw new Fail();
if (((res - WAIT_OBJECT_0) & 1) == 1) {
// it was the cancel sema4 so just return
return 0;
}
Port port = waiting.get(i);
if (!GetOverlappedResult(port.m_Comm, port.m_SelOVL, port.m_SelN, false))
port.fail();
ready = maskToFDSets(port, readfds, writefds, exceptfds, ready);
port.m_WaitPending = false;
}
} else {
if (timeout != null)
nanoSleep(timeout.tv_sec * 1000000000L + timeout.tv_usec * 1000);
else {
m_ErrNo = EINVAL;
return -1;
}
return 0;
}
}
} catch (Fail f) {
f.printStackTrace();
return -1;
}
} finally {
for (Port port : locked)
port.unlock();
}
// long T1 = System.currentTimeMillis();
// System.err.println("select() " + (T1 - T0));
return ready;
}
public int poll(Pollfd fds[], int nfds, int timeout) {
m_ErrNo = EINVAL;
return -1;
}
public int poll(int fds[], int nfds, int timeout) {
m_ErrNo = EINVAL;
return -1;
}
public boolean canPoll() {
return false;
}
public void perror(String msg) {
if (msg != null && msg.length() > 0)
System.out.print(msg + ": ");
System.out.printf("%d\n", m_ErrNo);
}
// This is a bit pointless function as Windows baudrate constants are
// just the baudrates so basically this is a no-op, it returns what it gets
// Note this assumes that the Bxxxx constants in JTermios have the default
// values ie the values are the baudrates.
private static int baudToDCB(int baud) {
switch (baud) {
case 110:
return CBR_110;
case 300:
return CBR_300;
case 600:
return CBR_600;
case 1200:
return CBR_1200;
case 2400:
return CBR_2400;
case 4800:
return CBR_4800;
case 9600:
return CBR_9600;
case 14400:
return CBR_14400;
case 19200:
return CBR_19200;
case 38400:
return CBR_38400;
case 57600:
return CBR_57600;
case 115200:
return CBR_115200;
case 128000:
return CBR_128000;
case 256000:
return CBR_256000;
default:
return baud;
}
}
public FDSet newFDSet() {
return new FDSetImpl();
}
public void FD_CLR(int fd, FDSet set) {
if (set == null)
return;
FDSetImpl p = (FDSetImpl) set;
p.bits[fd / FDSetImpl.NFBBITS] &= ~(1 << (fd % FDSetImpl.NFBBITS));
}
public boolean FD_ISSET(int fd, FDSet set) {
if (set == null)
return false;
FDSetImpl p = (FDSetImpl) set;
return (p.bits[fd / FDSetImpl.NFBBITS] & (1 << (fd % FDSetImpl.NFBBITS))) != 0;
}
public void FD_SET(int fd, FDSet set) {
if (set == null)
return;
FDSetImpl p = (FDSetImpl) set;
p.bits[fd / FDSetImpl.NFBBITS] |= 1 << (fd % FDSetImpl.NFBBITS);
}
public void FD_ZERO(FDSet set) {
if (set == null)
return;
FDSetImpl p = (FDSetImpl) set;
java.util.Arrays.fill(p.bits, 0);
}
public int ioctl(int fd, int cmd, int[] arg) {
Port port = getPort(fd);
if (port == null)
return -1;
try {
if (cmd == FIONREAD) {
clearCommErrors(port);
arg[0] = port.m_COMSTAT.cbInQue;
return 0;
} else if (cmd == TIOCMSET) {
int a = arg[0];
if ((a & TIOCM_DTR) != 0)
port.MSR |= TIOCM_DTR;
else
port.MSR &= ~TIOCM_DTR;
if (!EscapeCommFunction(port.m_Comm, ((a & TIOCM_DTR) != 0) ? SETDTR : CLRDTR))
port.fail();
if ((a & TIOCM_RTS) != 0)
port.MSR |= TIOCM_RTS;
else
port.MSR &= ~TIOCM_RTS;
if (!EscapeCommFunction(port.m_Comm, ((a & TIOCM_RTS) != 0) ? SETRTS : CLRRTS))
port.fail();
return 0;
} else if (cmd == TIOCMGET) {
int[] stat = { 0 };
if (!GetCommModemStatus(port.m_Comm, stat))
port.fail();
int s = stat[0];
int a = arg[0];
if ((s & MS_RLSD_ON) != 0)
a |= TIOCM_CAR;
else
a &= ~TIOCM_CAR;
if ((s & MS_RING_ON) != 0)
a |= TIOCM_RNG;
else
a &= ~TIOCM_RNG;
if ((s & MS_DSR_ON) != 0)
a |= TIOCM_DSR;
else
a &= ~TIOCM_DSR;
if ((s & MS_CTS_ON) != 0)
a |= TIOCM_CTS;
else
a &= ~TIOCM_CTS;
if ((port.MSR & TIOCM_DTR) != 0)
a |= TIOCM_DTR;
else
a &= ~TIOCM_DTR;
if ((port.MSR & TIOCM_RTS) != 0)
a |= TIOCM_RTS;
else
a &= ~TIOCM_RTS;
arg[0] = a;
return 0;
} else {
m_ErrNo = ENOTSUP;
return -1;
}
} catch (Fail f) {
return -1;
}
}
private void set_errno(int x) {
m_ErrNo = x;
}
private void report(String msg) {
System.err.print(msg);
}
private Port getPort(int fd) {
synchronized (this) {
Port port = m_OpenPorts.get(fd);
if (port == null)
m_ErrNo = EBADF;
return port;
}
}
private static String getString(byte[] buffer, int offset) {
StringBuffer s = new StringBuffer();
byte c;
while ((c = buffer[offset++]) != 0)
s.append((char) c);
return s.toString();
}
public String getPortNamePattern() {
return "^COM.*";
}
public List getPortList() {
Pattern p = JTermios.getPortNamePattern(this);
byte[] buffer;
int size = 0;
for (size = 16 * 1024; size < 1024 * 1024; size *= 2) {
buffer = new byte[size];
int res = QueryDosDevice(null, buffer, buffer.length);
if (res > 0) { //
LinkedList list = new LinkedList();
int offset = 0;
String port;
while ((port = getString(buffer, offset)).length() > 0) {
if (p.matcher(port).matches())
list.add(port);
offset += port.length() + 1;
}
return list;
} else {
int err = GetLastError();
if (err != ERROR_INSUFFICIENT_BUFFER) {
log = log && log(1, "QueryDosDeviceW() failed with GetLastError() = %d\n", err);
return null;
}
}
}
log = log && log(1, "Repeated QueryDosDeviceW() calls failed up to buffer size %d\n", size);
return null;
}
public void shutDown() {
for (Port port : m_OpenPorts.values()) {
try {
log = log && log(1, "shutDown() closing port %d\n", port.m_FD);
port.close();
} catch (Exception e) {
// should never happen
e.printStackTrace();
}
}
}
public int setspeed(int fd, Termios termios, int speed) {
int br = speed;
switch (speed) {
case 50:
br = B50;
break;
case 75:
br = B75;
break;
case 110:
br = B110;
break;
case 134:
br = B134;
break;
case 150:
br = B150;
break;
case 200:
br = B200;
break;
case 300:
br = B300;
break;
case 600:
br = B600;
break;
case 1200:
br = B1200;
break;
case 1800:
br = B1800;
break;
case 2400:
br = B2400;
break;
case 4800:
br = B4800;
break;
case 9600:
br = B9600;
break;
case 19200:
br = B19200;
break;
case 38400:
br = B38400;
break;
case 7200:
br = B7200;
break;
case 14400:
br = B14400;
break;
case 28800:
br = B28800;
break;
case 57600:
br = B57600;
break;
case 76800:
br = B76800;
break;
case 115200:
br = B115200;
break;
case 230400:
br = B230400;
break;
}
int r;
if ((r = cfsetispeed(termios, br)) != 0)
return r;
if ((r = cfsetospeed(termios, br)) != 0)
return r;
if ((r = tcsetattr(fd, TCSANOW, termios)) != 0)
return r;
return 0;
}
public int pipe(int[] fds) {
m_ErrNo = EMFILE; // pipe() not implemented on Windows backend
return -1;
}
}