package lejos.nxt.comm;
import java.io.*;
import lejos.nxt.*;
/**
* Provides a Bluetooth connection
* Supports both packetized, raw and stream based communincation.
* Blocking and non-blocking I/O.
* Notes:
* Because of the limited buffer space and the way that several connections
* have to share the interface to the Bluetooth device data may be lost. This
* will happen if a switch into command mode is required when there is data
* arriving from the remote connection that can not be placed into the input
* buffer. Every attempt is made to avoid this but it can happen. Application
* programs can help avoid this problem by:
* 1) Using just a single Bluetooth connection
* 2) Using Bluetooth commands while data transfers are in progress.
* 3) Performing application level flow control to avoid more then 256 bytes
* of data being sent from the remote side at any one time.
* 4) Reading any pending data as soon as possible.
* If data is lost then calls to read and write will return -2 to indicate the
* problem. If using packet mode then the input stream can be re-synchronized
* by issuing a read to discard the partial packet which may be in the input
* buffer.
*
* When operating in RAW mode bytes are read/written as is. This mode is useful
* for talking to none leJOS/Lego devices.
* When operating in PACKET mode the standard Lego 2 byte header is added to
* each packet (and is expected to be present on each incoming packet). Use this
* mode when talking to other leJOS/Lego devices.
*/
public class BTConnection extends NXTConnection
{
private static final int BTC_FLUSH_WAIT = 10;
public static final int AM_DISABLE = 0;
public static final int AM_ALWAYS = 1;
public static final int AM_OUTPUT = 2;
int chanNo;
byte handle;
int switchMode;
public BTConnection(int chan)
{
state = CS_IDLE;
chanNo = chan;
bufSz = Bluetooth.BUFSZ;
is = null;
os = null;
}
synchronized void reset()
{
// Called by the low level implementation if things go wrong!
state = CS_IDLE;
inBuf = null;
outBuf = null;
notifyAll();
}
/**
* Bind the low level I/O handle to a connection object
* set things up ready to go.
*/
synchronized void bind(byte handle, String address, int mode)
{
if (inBuf == null )
inBuf = new byte[Bluetooth.BUFSZ];
if (outBuf == null)
outBuf = new byte[Bluetooth.BUFSZ];
setIOMode(mode);
inCnt = 0;
inOffset = 0;
outCnt = 0;
outOffset = 0;
state = CS_CONNECTED;
switchMode = AM_ALWAYS;
this.handle = handle;
pktLen = 0;
this.address = address;
}
/**
* Send an EOF packet to the remote system.
*/
synchronized void sendEOF()
{
// Nothing to do for Bluetooth. We rely on the underlying transport
// for EOF
}
/**
* Disconnect the device/channel
*/
void disconnect()
{
//LCD.drawString("disconnect", 0, 5);
Bluetooth.closeConnection(handle);
}
/**
* Low level output function. Take any data in the output buffer and write
* it to the device. Called by the Bluetooth thread when this channel is
* active, to perform actual data I/O.
*/
synchronized void send()
{
//RConsole.print("send\n");
if (outOffset >= outCnt) return;
//RConsole.println("Pending " + Bluetooth.btPending());
// Transmit the data in the output buffer
int cnt = Bluetooth.btWrite(outBuf, outOffset, outCnt - outOffset);
//1 RConsole.print("Send " + cnt + "\n");
outOffset += cnt;
if (outOffset >= outCnt)
{
//RConsole.print("Send complete\n");
outOffset = 0;
outCnt = 0;
notifyAll();
}
else
{
//RConsole.print("send remaining " + (outCnt - outOffset) + "\n");
}
}
/**
* 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
*/
synchronized int flushBuffer(boolean wait)
{
if (outOffset >= outCnt) return 1;
if (wait)
try {wait();} catch(Exception e){}
return outCnt - outOffset;
}
/**
* Low level input function. Called by the Bluetooth thread to transfer
* input from the system into the input buffer.
*/
synchronized void recv()
{
//1 RConsole.print("recv\n");
// Read data into the input buffer
while (inCnt < inBuf.length)
{
if (inCnt == 0) inOffset = 0;
int offset = (inOffset + inCnt) % inBuf.length;
int len = (offset >= inOffset ? inBuf.length - offset : inOffset - offset);
//RConsole.print("inCnt " + inCnt + " inOffset " + inOffset + " offset " + offset + " len " + len + "\n");
int cnt = Bluetooth.btRead(inBuf, offset, len);
if (cnt <= 0) break;
inCnt += cnt;
//1 RConsole.print("recv " + inCnt + "\n");
}
if (inCnt > 0) notifyAll();
}
/**
* 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
*/
synchronized int fillBuffer(boolean wait)
{
if (inCnt > 0) return inCnt;
if (wait)
try{wait();}catch(Exception e){}
return inCnt;
}
/**
* Low level function called by the Bluetooth thread. It basically answers
* the question: Should I switch to this channel and perform I/O? The answer
* to this question can be controlled using the setActiveMode method.
* @ return true if the channel is interesting!
*/
synchronized boolean needsAttention()
{
//1 if (chanNo == 0) RConsole.print("na s" + state + " i " + inCnt + "\n");
//RConsole.print("needs attention\n");
// return true if we need to perform low level I/O on this channel
if (state < CS_DISCONNECTING || switchMode == AM_DISABLE) return false;
// If we have any output then need to send it
if (outOffset < outCnt) return true;
if (switchMode == AM_OUTPUT) return false;
// If we do not have any input need to see if there is more waiting
if (inCnt <= 0) return true;
return false;
}
/**
* Set the channel switching mode. Allows control of when we will switch to
* this channel. By default we will switch to this channel to check for
* input. However if AM_OUTPUT is set we only switch if we have output
* waiting to be sent.
* @param mode The switch control mode.
*/
public void setActiveMode(int mode)
{
switchMode = mode;
}
private boolean pendingInput()
{
return (Bluetooth.btPending() & Bluetooth.BT_PENDING_INPUT) != 0;
}
/**
* Prepare the low level Bluetooth interface for a switch into command mode.
* To switch to command mode we need to be sure that there is no pending
* input for this channel. To do this we ready any data into the available
* input buffers. If all else fails we discard data. When we return the
* interface should be ready to be switched.
*/
synchronized void flushInput()
{
// Need to be sure that there is no input in the input buffer before
// we switch mode.
if (state == CS_IDLE) return;
//RConsole.print("Flush\n");
// Try to empty the low level input buffer while giving the
// application chance to help by reading the data.
int timeout = (int)System.currentTimeMillis() + BTC_FLUSH_WAIT;
while (timeout > (int)System.currentTimeMillis())
{
// Read as much as we can
while (pendingInput() && inCnt < inBuf.length)
recv();
// Give the app chance to process it
try{wait(1);}catch(Exception e){}
}
if (!pendingInput()) return;
//1 RConsole.print("Dropping packets\n");
// If we still have input we are now in big trouble we will have
// to discard data. Note even if we read all of the data we need
// to linger a little to see if more arrives.
timeout = (int)System.currentTimeMillis() + BTC_FLUSH_WAIT;
while (pendingInput() || (timeout > (int)System.currentTimeMillis()))
{
while (read(null, inBuf.length, false) > 0)
;
recv();
}
// Mark the channel as having lost data
if (state == CS_CONNECTED)
state = CS_DATALOST;
}
/**
* Close the stream for this connection.
* This suspends the connection and switch the BC4 chip to command mode.
*
*/
public void closeStream() {
// Nothing to do for Bluetooth
}
/**
* Open the stream for this connection.
* This resumes the connection and switches the BC4 chip to data mode.
*
*/
public void openStream() {
// Nothing to do for Bluetooth
}
/**
* Get the signal strength of this connection.
* This necessitates closing and reopening the data stream.
*
* @return a value from 0 to 255
*/
public int getSignalStrength() {
int strength = Bluetooth.getSignalStrength(handle);
return strength;
}
}