package lejos.nxt.comm; /** * Connection object for an RS485/BitBus connection * This object models a single BitBus connection. It works closely with * the BitBus controller object to perform packet based I/O. It provides * buffer space, address bindings and flow control for connection. It also * handles higher level connection state (connection establishment, termination * etc). * @author andy */ public class RS485Connection extends NXTConnection { // Additional states for this type static final int CS_CONNECTING1 = CS_EOF+1; static final int CS_CONNECTING2 = CS_EOF+2; static final int CS_CONNECTING3 = CS_EOF+3; int connNo; byte bbAddress; String remoteName; byte inSeq; byte outSeq; int sentCnt; boolean flowControl = false; int retryCnt; RS485.Controller controller; RS485Connection(RS485.Controller cont, int chan) { state = CS_IDLE; controller = cont; connNo = chan; is = null; os = null; bbAddress = RS485.BB_INVALID; bufSz = RS485.BUFSZ; } public String getName() { return remoteName; } synchronized void reset() { // Called by the low level implementation if things go wrong! state = CS_IDLE; inBuf = null; outBuf = null; bbAddress = RS485.BB_INVALID; notifyAll(); } /** * Bind the low level I/O handle to a connection object * set things up ready to go. */ synchronized void bind(byte bbAddr, String remAddress, String remName) { // Allocate buffers if required. Note we use a double sized input // buffer to help flow control. if (inBuf == null ) inBuf = new byte[2*RS485.BUFSZ]; if (outBuf == null) outBuf = new byte[RS485.BUFSZ]; setIOMode(PACKET); inCnt = 0; inOffset = 0; outCnt = 0; outOffset = 0; sentCnt = 0; pktLen = 0; inSeq = 0; outSeq = 0; bbAddress = bbAddr; address = remAddress; remoteName = remName; flowControl = false; retryCnt = 0; } /** * Send an EOF packet to the remote system. */ synchronized void sendEOF() { // Nothing to do for RS485/BitBus. We rely on the underlying transport // for EOF } /** * Disconnect the device/channel */ synchronized void disconnect() { if (state > CS_DISCONNECTING2) state = CS_DISCONNECTING2; } /** * Low level output function. Take any data in the output buffer and write * it to the device. Called by the network thread when this channel is * active, to perform actual data I/O. * @return true if we sent data false otherwise. */ synchronized boolean send() { //RConsole.println("Send outcnt " + outCnt + " offset " + outOffset + " flow " + flowControl); // Do we have anything to send, and are we allowed to send it? if (outOffset >= outCnt || flowControl) return false; //RConsole.println("Pending " + Bluetooth.btPending()); // Transmit the data in the output buffer sentCnt = controller.sendData(bbAddress, inSeq, outSeq, outBuf, outOffset, outCnt - outOffset); //RConsole.println("Send outcnt " + outCnt + " offset " + outOffset + " written " + sentCnt); return sentCnt > 0; } /** * Acknowledge previously sent frames. Called by the netowrk thread when * an acknowledgement is received. Free up buffer space that is no longer * needed for retransmissions etc. * @param seq * @param flow Indicates that we should apply flow control on this connection. */ synchronized void ack(byte seq, boolean flow) { //RConsole.println("ack seq " + seq + " expect " + (outSeq+1) + " flow " + flow); // Record flow control state. flowControl = flow; //1 RConsole.print("Send " + cnt + "\n"); // Is this an ack for the last packet? if (seq == ((outSeq + 1) & RS485.BB_SEQMASK)) { // Got a good ack for the last packet sent outOffset += sentCnt; sentCnt = 0; if (outOffset >= outCnt) { // Sent all we have... free up the space. //RConsole.print("Send complete\n"); outOffset = 0; outCnt = 0; notifyAll(); //Thread.yield(); } else { //RConsole.print("send remaining " + (outCnt - outOffset) + "\n"); // We have more data waiting to go, send it... } // Move the outSeq on outSeq = seq; } else if (seq == outSeq) { // All packets acked, nothing to do } else // Bad seq number disconnect. disconnect(); } /** * 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 network thread to transfer * input from the system into the input buffer. */ synchronized void recv(byte seq, byte[] data, int dataOffset, int dataLen) { //RConsole.println("recv seq" + seq + " expect " + inSeq + " len " + dataLen); // Check that the seq number is ok if (seq == inSeq) { // Do we have room for the data if (dataLen <= inBuf.length - inCnt) { while (dataLen > 0) { if (inCnt == 0) inOffset = 0; int offset = (inOffset + inCnt) % inBuf.length; int len = (offset >= inOffset ? inBuf.length - offset : inOffset - offset); if (len > dataLen) len = dataLen; System.arraycopy(data, dataOffset, inBuf, offset, len); //RConsole.print("inCnt " + inCnt + " inOffset " + inOffset + " offset " + offset + " len " + len + "\n"); inCnt += len; dataOffset += len; dataLen -= len; //1 RConsole.print("recv " + inCnt + "\n"); } // Move the sequence number on inSeq = (byte)((inSeq + 1) & RS485.BB_SEQMASK); } } else if (seq == ((inSeq-1) & RS485.BB_SEQMASK)) { // Duplicate packet, ignore it. } else // Bad sequence number. Disconnect disconnect(); if (inCnt > 0) notifyAll(); //Thread.yield(); } /** * 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; } /** * Tell the lower levels that they can release any resources for this * connection. */ void freeConnection() { // Connections in a connect state handle the underlying connection // explicitly and so we do not free them //RConsole.println("free connection state " + state + " CS_CONNECTING " + CS_CONNECTING1); if (state < CS_CONNECTING1) controller.freeConnection(this); } }