package lejos.nxt.comm;
import lejos.nxt.*;
import java.io.*;
import javax.microedition.io.*;
import lejos.util.Delay;
/**
* Generic lejos nxt connection class. Provide access to standard read/write
* methods. This code supports both asynchronous (used for Bluetooth connections)
* and synchronous (used for USB and RS485) I/O operations for the actual reading
* and writing of the low level buffers.
* NOTE: The code in this class makes a number of assumptions:
* 1. The input and output buffers have been sized to match the underlying
* device, and the packet header size. In particular the synchronous
* code assumes that the entire output buffer can be written using a single
* atomic write operation.
* 2. Although the code can handle any size packet header the default is
* assumed to be 2 bytes. If this is not the case (for example USB), then
* the setIOMode function must be over-ridden.
* 3. This class allows the use of a "soft" EOF implementation which uses a
* zero length packet as an EOF marker. This only operates when in packet
* mode and can be overridden. Currently this is used for USB and RS485
* devices. It is not used for Bluetooth connections.
* 4. Some devices (like USB), have an inherent packet structure. The current
* PC assumes that when in packet mode an entire packet will fit within
* a single USB packet. This limits the maximum packet size which can be
* used over USB connections to 63 bytes. This code does not currently
* enforce this limit.
* @author andy
*/
public abstract class NXTConnection implements StreamConnection {
/* Connection modes */
public static final int LCP = 1;
public static final int PACKET = 0;
public static final int RAW = 2;
static final int CS_IDLE = 0;
static final int CS_DISCONNECTED = 1;
static final int CS_DISCONNECTING2 = 2;
static final int CS_DISCONNECTING = 3;
static final int CS_CONNECTED = 4;
static final int CS_DATALOST = 5;
static final int CS_EOF = 6;
static final int DEF_HEADER = 2;
private static final int CLOSETIMEOUT1 = 1000;
private static final int CLOSETIMEOUT2 = 500;
int state = CS_IDLE;
int header;
byte [] inBuf;
byte [] outBuf;
int inCnt;
int inOffset;
int outCnt;
int outOffset;
int pktOffset;
int pktLen;
int bufSz;
InputStream is;
OutputStream os;
String address;
public String getAddress() {
return address;
}
/**
* Write all of the current output buffer to the device.
* NOTE: To ensure correct operation of packet mode, this function should
* only return 1 if all of the data will eventually be written. It should
* avoid writing part of the data.
* @param wait if true wait until the output has been written
* @return -ve if error, 0 if not written, +ve if written
*/
abstract int flushBuffer(boolean wait);
/**
* Attempt to write bytes to the Bluetooth connection. Optionally wait if it
* is not possible to write at the moment. Supports both packet and stream
* write operations. If in packet mode a set of header bytes indicating
* the size of the packet will be sent ahead of the data.
* NOTE: If in packet mode and writing large packets (> 254 bytes), then
* the blocking mode (wait = true), should be used to ensure that the packet
* is sent correctly.
* @param data The data to be written.
* @param len The number of bytes to write.
* @param wait True if the call should block until all of the data has
* been sent.
* @return > 0 number of bytes written.
* 0 Request would have blocked (and wait was false).
* -1 An error occurred
* -2 Data has been lost (See notes above).
*/
public synchronized int write(byte [] data, int len, boolean wait)
{
// Place the data to be sent in the output buffer. If there is no
// space and wait is true then wait for space.
int offset = -header;
int hdr = len;
//1 RConsole.print("write " + len +" bytes\n");
if (state == CS_DATALOST)
{
state = CS_CONNECTED;
return -2;
}
if (state < CS_CONNECTED) return -1;
if (outCnt > 0 && !wait) return 0;
// Make sure we have a place to put the data
ioloop: while (offset < len)
{
while (outCnt >= outBuf.length)
{
//RConsole.print("Buffer cnt " + outCnt + "\n");
if (!wait && header == 0) break ioloop;
//RConsole.print("Waiting in write\n");
if (flushBuffer(true) < 0) disconnected();
//RConsole.print("Wakeup state " + state + "\n");
if (state < CS_CONNECTED) break ioloop;
}
if (offset < 0)
{
// need to add header byte(s)
outBuf[outCnt++] = (byte) hdr;
hdr >>= 8;
offset++;
}
else
{
int cnt = (outBuf.length - outCnt);
if (cnt > len - offset) cnt = len - offset;
System.arraycopy(data, offset, outBuf, outCnt, cnt);
outCnt += cnt;
offset += cnt;
}
}
//if (offset != 0) LCD.drawInt(offset, 4, 0, 1);
// Send the data. If there is a problem report that the data was not sent.
int written = flushBuffer(wait);
if (written > 0) return offset;
if (written < 0) disconnected();
return written;
}
/**
* Get any available data into the input buffer.
* @param wait if true wait for data to be available.
* @return -ve if error, 0 if not read, +ve if read
*/
abstract int fillBuffer(boolean wait);
/**
* Attempt to read data from the connection. Optionally wait for data to
* become available. Supports both packet and stream mode operations. When
* in packet mode the packet length bytes are automatically processed. The
* read will return just a single packet. If the packet is larger then the
* requested length then the rest of the packet will be returned in the
* following reads. If wait is true then in packet mode the call will wait
* until either the entire packet can be read or outLen bytes are available.
* In stream mode the call will return if at least 1 byte has been read.
* @param data Location to return the data. If null the data is discarded.
* @param outLen Max number of bytes to read.
* @param wait Should the call block waiting for data.
* @return > 0 number of bytes read.
* 0 no bytes available (and wait was false).
* -1 EOF/Connection closed.
* -2 data lost (see notes).
* -3 Some other error
*/
public synchronized int read(byte [] data, int outLen, boolean wait)
{
// If wait is true wait until we can read at least one byte. if the
// packet has a header and data is not large enough for the data then
// the next read will continue to read the packet
int offset = 0;
//RConsole.println("read state " + state + " incnt " + inCnt);
if (state == CS_IDLE) return -3;
if (state == CS_DATALOST)
{
state = CS_CONNECTED;
return -2;
}
if (state == CS_EOF)
{
inCnt = 0;
inOffset = 0;
return -1;
}
if (fillBuffer(false) < 0) disconnected();
if (state == CS_DISCONNECTED && inCnt <= 0) return -1;
if (!wait && inCnt <= 0) return 0;
//LCD.drawInt(pktOffset, 4, 0, 3);
if (header == 0)
{
// Stream mode just read what we can
pktOffset = 0;
pktLen = outLen;
}
ioloop:while (pktOffset < pktLen)
{
//if (debug)RConsole.print(" inCnt " + inCnt + " pktOffset " + pktOffset + " pktLen " + pktLen + "\n");
// Make sure we have something to read
while (inCnt <= 0)
{
//if (debug)RConsole.print("About to wait inOff " + inOffset + " inCnt " + inCnt + "\n");
if (!wait) return offset;
// We have no data in the input buffer, check for errors.
if (state != CS_CONNECTED)
{
//RConsole.println("Wait read state " + state + " offset " + offset);
// return partial data if we have it...
if (offset > 0) break ioloop;
if (state == CS_DISCONNECTED) return -1;
if (state == CS_DATALOST)
{
state = CS_CONNECTED;
return -2;
}
return -3;
}
if (fillBuffer(true) < 0) disconnected();
//if (debug)RConsole.print("wakeup cnt " + inCnt + "\n");
}
if (pktOffset < 0)
{
// Deal with the header, at this point we have at least one header byte
pktLen += ((int) inBuf[inOffset++] & 0xff) << (header + pktOffset)*8;
pktOffset++;
inCnt--;
//RConsole.print("Header len " +pktLen + " offset " + pktOffset + "\n");
}
else
{
if (offset >= outLen) return offset;
// Transfer as much as we can in one go...
int len = (inOffset + inCnt > inBuf.length ? inBuf.length - inOffset : inCnt);
if (len > outLen - offset) len = outLen - offset;
if (len > pktLen - pktOffset) len = pktLen - pktOffset;
if (data != null)
System.arraycopy(inBuf, inOffset, data, offset, len);
offset += len;
inOffset += len;
pktOffset += len;
inCnt -= len;
// If not in packet mode we can return anytime now we have some data
if (header == 0) wait = false;
}
inOffset = inOffset % inBuf.length;
}
// End of packet set things up for next time
//RConsole.println("Read len " + offset + " buf " + inCnt);
pktOffset = -header;
pktLen = 0;
// Check for EOF
if (header > 0 && offset == 0)
{
state = CS_EOF;
return -1;
}
return offset;
}
/**
* Indicate the number of bytes available to be read. Supports both packet
* mode and stream connections.
* @param what 0 (all modes) return the number of bytes that can be
* read without blocking.
* 1 (packet mode) return the number of bytes still to be
* read from the current packet.
* 2 (packet mode) return the length of the current packet.
* @return number of bytes available
*/
public synchronized int available(int what)
{
if (state == CS_IDLE) return -1;
if (state == CS_DATALOST)
{
state = CS_CONNECTED;
return -2;
}
fillBuffer(false);
if (header > 0)
{
// if not in a packet try and read the header
if (pktOffset < 0) read(null, 0, false);
if (pktOffset < 0) return 0;
if (what == 2) return pktLen;
int ret = pktLen - pktOffset;
// If we have been asked what is actually available limit it.
// otherwise we return the number of bytes in the current packet
if (what == 0 && ret > inCnt) ret = inCnt;
return ret;
}
else
return inCnt;
}
public int available()
{
return available(0);
}
void setHeader(int sz)
{
header = sz;
pktOffset = -header;
pktLen = 0;
}
/**
* Set operating mode. Controls the packet/stream mode of this channel.
* For packet mode it defines the header size to be used.
* @param mode I/O mode to be used for this connection. NXTConnection.RAW, .LCP, or .PACKET
*/
public void setIOMode(int mode)
{
if (mode == PACKET || mode == LCP)
setHeader(DEF_HEADER);
else
setHeader(0);
}
/**
* Perform a blocking read on the connection
* @param data byte array to store the results.
* @param len max number of bytes to read
* @return actual number of bytes read, return < 0 for error
*/
public int read(byte [] data, int len)
{
return read(data, len, true);
}
/**
* Perform a blocking write on the connection
* @param data byte array to be written.
* @param len number of bytes to write
* @return actual number of bytes written, return < 0 for error
*/
public int write(byte [] data, int len)
{
return write(data, len, true);
}
/**
* Return the InputStream for this connection.
*
* @return the input stream
*/
public InputStream openInputStream() {
return (is != null ? is : (is = new NXTInputStream(this, bufSz - header)));
}
/**
* Return the OutputStream for this connection
*
* @return the output stream
*/
public OutputStream openOutputStream() {
return (os != null ? os : (os = new NXTOutputStream(this, bufSz - header)));
}
/**
* Return the DataInputStream for this connect
*
* @return the data input stream
*/
public DataInputStream openDataInputStream() {
return new DataInputStream(openInputStream());
}
/**
* Return the DataOutputStream for this connection.
*
* @return the data output stream
*/
public DataOutputStream openDataOutputStream() {
return new DataOutputStream(openOutputStream());
}
/**
* Called when the remote side of the connection disconnects.
* Mark the connection as now disconected.
*/
synchronized boolean disconnected()
{
// Connection has been closed wake up anything waiting
//RConsole.print("Disconnected " + handle + "\n");
notifyAll();
//RConsole.println("Disconnected state " + state);
// don't allow multiple disconnects, or disconnect of a closed connection'
if (state <= CS_DISCONNECTED) return false;
// Free any associated connection structures
// NOTE We do this before changing state so that the underlying code
// can access the current state.
freeConnection();
state = CS_DISCONNECTED;
outCnt = 0;
return true;
}
/**
* Send an EOF packet to the remote system.
*/
synchronized void sendEOF()
{
// try and make sure we have room to send the EOF packet
if (header > 0)
{
for(int i = 0; state >= CS_CONNECTED && outCnt > 0 && i < CLOSETIMEOUT2; i++ )
{
flushBuffer(false);
try {wait(1);} catch(Exception e){}
}
// Send it.
write(null, 0, false);
}
}
/**
* Disconnect the device/channel
*/
void disconnect()
{
disconnected();
}
/**
* Tell the lower levels that they can release any resources for this
* connection.
*/
void freeConnection()
{
}
/**
* Close the connection. Flush any pending output. Inform the remote side
* that the connection is now closed. Free resources.
*/
public void close()
{
//RConsole.print("Close\n");
//LCD.drawInt(1, 8, 0, 6);
//LCD.drawInt(state, 8, 8, 6);
if (state == CS_IDLE) return;
synchronized (this)
{
if (state >= CS_CONNECTED)
{
sendEOF();
state = CS_DISCONNECTING;
}
}
//RConsole.print("Close1\n");
// If we have any output pending give it chance to go... and discard
// any input. We allow longer if we have pending output, just in case we
// need to switch streams.
//RConsole.println("Closing 1 cnt is " + outCnt);
//LCD.drawInt(2, 8, 0, 6);
//LCD.drawInt(state, 8, 8, 6);
for(int i = 0; state == CS_DISCONNECTING && (outCnt > 0 && i < CLOSETIMEOUT1); i++ )
{
flushBuffer(false);
read(null, inBuf.length, false);
Delay.msDelay(1);
}
//RConsole.println("Closing 2 cnt is " + outCnt);
//LCD.drawInt(3, 8, 0, 6);
//LCD.drawInt(state, 8, 8, 6);
for(int i = 0; state == CS_DISCONNECTING && i < CLOSETIMEOUT2; i++ )
{
flushBuffer(false);
read(null, inBuf.length, false);
Delay.msDelay(1);
}
synchronized(this)
{
// Dump any remaining output
//LCD.drawInt(4, 8, 0, 6);
//LCD.drawInt(state, 8, 8, 6);
outCnt = 0;
if (state == CS_EOF) state = CS_DISCONNECTING;
}
if (state == CS_DISCONNECTING)
// Must not be synchronized here or we get a deadlock
disconnect();
//LCD.drawInt(5, 8, 0, 6);
//LCD.drawInt(state, 8, 8, 6);
synchronized(this)
{
//LCD.drawInt(6, 8, 0, 6);
//LCD.drawInt(state, 8, 8, 6);
//RConsole.print("Close3\n");
while (state > CS_DISCONNECTED)
try{wait();}catch(Exception e){}
//LCD.drawInt(7, 8, 0, 6);
//LCD.drawInt(state, 8, 8, 6);
//RConsole.print("Close4\n");
state = CS_IDLE;
inBuf = null;
outBuf = null;
}
//RConsole.print("Close complete\n");
}
/**
* Discard and input
* Dumps any input data
*/
synchronized void discardInput()
{
do {
inCnt = 0;
inOffset = 0;
try {wait(1);} catch(Exception e) {}
fillBuffer(false);
} while (inCnt > 0);
// Reset packet stream
setHeader(header);
}
/**
* Read a packet from the stream. Do not block and for small packets
* (< bufSz), do not return a partial packet.
* @param buf Buffer to read data into.
* @param len Number of bytes to read.
* @return > 0 number of bytes read.
* other values see read.
*/
public int readPacket(byte buf[], int len)
{
// Check to see if we have a full packet if the packet is small
int pkt = available(1);
if (pkt == -2) return -2;
if (pkt < bufSz && available(0) < pkt) return 0;
return read(buf, len, false);
}
/**
* Send a data packet.
* Must be in data mode.
* @param buf the data to send
* @param bufLen the number of bytes to send
* @return number of bytes written
*/
public int sendPacket(byte [] buf, int bufLen)
{
if (bufLen <= outBuf.length - header)
{
return write(buf, bufLen, false);
}
return 0;
}
}