package lejos.nxt.comm; import lejos.util.Delay; /** * Low-level RS485/BitBus Network implementation * This class provides simple low level access to the underlying RS485 hardware * implemented on port 4 of the Lego NXT. * * It also supports a higher level network connection based on this hardware, in * the form of a BitBus netowk node implmentation. For full details see the * BitBus specifiaction at: * http://www.bitbus.org/ * Basically the network provides a simple master/slave implementation using * SDLC packet framing with CRC-16-CITT error checking. In this implementation * the on the wire format has been modified to use the Lego defined baud rate * of 921600 bps and uses byte stuffing rather than bit stuffing (since there is * no hardware support). The protocol has been integrated with the standard * leJOS connection object and offers up to 7 connections. The BitBus broadcast * mechanism is used to provide a higher level mapping between NXT address (we * use the Bluetooth address of the nxt), or name and the underlying BitBus * address (a single byte). * * The original implementation used Java for frame handling. However this was * found to add considerable overhead to the process. To improve performance * (by a factor of approximately 10), the lowest level routines have been * re-implemented in C and moved into the firmware as native methods. * * @author Andy Shaw * */ public class RS485 extends NXTCommDevice { public static final int BUFSZ = 128; public static final int MAX_CONNECTIONS = 8; static final int CRC_CITT = 0x1021; static final int CRC_INIT = 0xffff; static final byte BB_EXTRA = 6; static final byte BB_FLAG = 0x7e; static final byte BB_ESCAPE = 0x7d; static final byte BB_XOR = 0x20; static final byte BB_SEQMASK = 0x7; static final byte BB_SNRM = (byte)0x93; static final byte BB_DISC = 0x53; static final byte BB_UA = 0x73; static final byte BB_FRMR = (byte) 0x97; static final byte BB_IFRAME = 0x10; static final byte BB_IMASK = 0x11; static final byte BB_ACK = 0x11; static final byte BB_ACKMASK = 0x1b; static final byte BB_ACKRR = BB_ACK; static final byte BB_ACKNR = BB_ACK|0x4; static final byte BB_BROADCAST = 0; static final byte BB_INVALID = (byte)0xff; static final int BB_ASEQSHIFT = 5; static final int BB_SSEQSHIFT = 1; static final int BB_DATAOFFSET = 2; static final int BB_CSUMSZ = 2; static final int FRAME_TIMEOUT = 10; static final int REPLY_RETRY = 5; static final int REQUEST_RETRY = 5; static final int RECV_RETRY = 1000; static final int DS_DISABLED = 0; static final int DS_SLAVE = 1; static final int DS_MASTER = 2; static final int ST_FLAG = 0; static final int ST_ESCAPE = 1; static final int ST_DATA = 2; static Controller controller; static { controller = new Controller(); } private RS485() { } /** * BitBus network interface control class. * Controls access to the underlying RS485 port and operates it as part of a * BitBus network. Schedules and performs packet I/O operations via the port. * Allocates network connections and addresses. * Will operate in either BitBus master or slave mode. * */ static class Controller extends Thread { private char[] CRCTable; private byte[] frame; private int frameLen; private final RS485Connection connections[] = new RS485Connection[MAX_CONNECTIONS]; private RS485Connection slaveConnections[] = new RS485Connection[MAX_CONNECTIONS]; private int devMode = DS_DISABLED; private byte[] netAddress = new byte[ADDRESS_LEN]; private byte[] netName = new byte[NAME_LEN]; private int connectionCnt = 0; private int slaveCnt = 0; private int retryCnt = 0; Controller() { // Allocate space for frame, allow for max byte stuffing frame = new byte[(BUFSZ+BB_EXTRA)*2]; initCRCTable256(CRC_CITT); devMode = DS_DISABLED; connectionCnt = 0; slaveCnt = 0; retryCnt = 0; setDaemon(true); start(); } /** * Set the local address for this node. */ private void setAddress() { //RConsole.println("Load address " + NXTCommDevice.getAddress()); netAddress = stringToAddress(NXTCommDevice.getAddress()); //RConsole.println("name " + NXTCommDevice.getName()); netName = stringToName(NXTCommDevice.getName()); } /** * Set the operating mode for this node. This may require reseting exisiting * connections. * @param newMode */ private void setMode(int newMode) { synchronized(connections) { //RConsole.println("setMode " + newMode); if (devMode == newMode) return; //RConsole.println("change mode from " + devMode); // stop things while we sort stuff out devMode = DS_DISABLED; // Now set things up for the new mode if (newMode == DS_DISABLED) RS485.hsDisable(); else { // Make sure our address is correct and up to date. setAddress(); RS485.hsEnable(); if (newMode == DS_MASTER) { // Reset the network for(int i = 0; i < REPLY_RETRY; i++) { Delay.msDelay(FRAME_TIMEOUT); sendControl(BB_BROADCAST, BB_DISC, (byte)0); } // Give things time to settle Delay.msDelay(FRAME_TIMEOUT*REPLY_RETRY*REQUEST_RETRY); } // drop any old frames while(recvFrame() > 0) Thread.yield(); } devMode = newMode; //RConsole.println("Mode set to " + devMode); } } /** * Rebuild the mapping from address to connection for slave connections. * This table allows fast lookup of slave address to the corresponding * connection object. * Maintain a count of the number of active slave connections for this device. * NOTE: as a special case the entry for channel 0 (the broadcast address), * is associated with the first listening connection, thus allowing it to * respond to address assignment requests */ private void updateSlaveConnections() { synchronized(connections) { slaveCnt = 0; if (devMode < DS_SLAVE) return; // First remove all current associations for(int i = 0; i < slaveConnections.length; i++) slaveConnections[i] = null; // Now build up the map for(int i = 0; i < connections.length; i++) { if (connections[i] != null) { if (connections[i].bbAddress == BB_INVALID) { if (slaveConnections[BB_BROADCAST] == null) slaveConnections[BB_BROADCAST] = connections[i]; } else { slaveConnections[connections[i].bbAddress] = connections[i]; // We have at least one active slave slaveCnt++; } } } if (slaveCnt == 0) retryCnt = 0; } } /** * Disconnect all active connections. */ void disconnectAll() { synchronized(connections) { for(int i = 0; i < MAX_CONNECTIONS; i++) if (connections[i] != null) connections[i].disconnected(); } } /** * Allocate a new connection channel, and return a connection object for * it. The channel number is used as the BitBus address for this * connection. If this is the first active connection * set the mode of the device to be either master or slave. * @param mode * @return The new connection object or null if none are available */ RS485Connection newConnection(int mode) { // Find a free slot, note that we never use address 0, for master side // addreses. This is the broadcast address. // Make sure we are in the correct mode if (devMode != mode && devMode != DS_DISABLED) return null; synchronized(connections) { if (connectionCnt == 0) setMode(mode); for(int i = (mode == DS_SLAVE ? 0 : 1); i < connections.length; i++) if (connections[i] == null) { connections[i] = new RS485Connection(this, i); connectionCnt++; updateSlaveConnections(); return connections[i]; } } return null; } /** * Free a connection and make the channel available for re-use. If this is * the last connection in use return the state of the device to disabled * @param con the connection being released. */ void freeConnection(RS485Connection con) { if (con == null) return; int i = con.connNo; if (i < 0 || i >= connections.length) return; synchronized(connections) { if (connections[i] == null) return; //RConsole.println("freeConnection " + i + " state " + connections[i].state); // Unbind things connections[i].connNo = -1; connections[i] = null; updateSlaveConnections(); if (--connectionCnt <= 0) { setMode(DS_DISABLED); } } } /** * Create the CRC lookup table based upon the supplied polynomial * definition. For more details see: * http://en.wikipedia.org/wiki/Cyclic_redundancy_check * http://wiki.wxwidgets.org/Development:_Small_Table_CRC * @param crc_poly */ private void initCRCTable256(int crc_poly) { int i, val, result; CRCTable = new char[256]; for (val = 0; val < 256; val++) { result = val << 8; for (i = 0; i < 8; i++) if ((result & 0x8000) != 0) result = (result << 1) ^ crc_poly; else result <<= 1; CRCTable[val] = (char)(result & 0xffff); } } /** * Send a frame. We use the firmware to assemble the actual bytes, * performing byte stuffing and calculating the checksum value. We * supply a pre-computed CRC table to aid in this process. * @param address The BitBus address for this packet * @param control The BitBus control field * @param data The data to be sent * @param offset Offset of the start of the data * @param len Length of the data. * @return number of bytes sent */ private int sendFrame(byte address, byte control, byte [] data, int offset, int len ) { int cnt; while ((cnt = hsSend(address, control, data, offset, len, CRCTable)) == 0) Thread.yield(); return cnt; } /** * Send an IFrame to the specified address. Send a BitBud IFrame (one * containing data). * @param address The BitBus address * @param ack The seq number being acknowledged by this frame * @param seq The seq number for this frame. * @param data * @param offset * @param len * @return number of data bytes sent */ public int sendData(byte address, byte ack, byte seq, byte [] data, int offset, int len) { //RConsole.println("sendData address " + address + " len " + len + " seq " + seq + " ack " + ack); if (len > BUFSZ) len = BUFSZ; byte control = BB_IFRAME; control |= (byte)(ack << BB_ASEQSHIFT); control |= (byte)(seq << BB_SSEQSHIFT); if (sendFrame(address, control, data, offset, len) > 0) return len; else return 0; } /** * Send a control frame. Send a BitBus control frame (one with no data). * @param address The BitBus address * @param control BitBus control field * @param ack The seq number being acknowledged by this frame. * @return number of bytes sent */ private int sendControl(byte address, byte control, byte ack) { //RConsole.println("sendControl address " + address + " control " + control + " ack " + ack + " time " + (int)System.currentTimeMillis()); return sendFrame(address, (byte)(control | (ack << BB_ASEQSHIFT)), null, 0, 0); } /** * Recv a frame from the hardware into the frame buffer. Handle byte * stuffing and check the CRC. Timeout if no frame is available or only * a partial frame is read. * @return < 0 no data 0 in frame > 0 the length of the frame */ private int recvFrame() { int endTime = (int) System.currentTimeMillis() + FRAME_TIMEOUT; // Wait for a a packet to start or timeout int ret; while((ret = hsRecv(frame, frame.length, CRCTable, 1)) < 0 && (int)System.currentTimeMillis() < endTime) Thread.yield(); frameLen = ret; // did we get a complete frame? if (ret > 0) return ret; // Have we timed out? if (ret < 0) return -1; // No so we have a partial frame give it time to complete endTime = (int) System.currentTimeMillis() + FRAME_TIMEOUT; while((ret = hsRecv(frame, frame.length, CRCTable, 0)) == 0 && (int)System.currentTimeMillis() < endTime) Thread.yield(); frameLen = ret; if (ret > 0) return ret; // Frame timeout return -1; } /** * Assign an address. Attempt to dynamically assign an address to a slave device. * We do this by broadcasting the address and the name of the slave along with the * assigned address. Slave must be in disconnected state to respond to this. * @param address * @param remAddress * @param remName * @return number of bytes sent */ int assignAddress(byte addr, byte[] remAddress, byte[] remName) { //RConsole.println("Assign address " + devName + " " + devAddress); byte [] data = new byte[1+2*ADDRESS_LEN+2*NAME_LEN]; int offset = 1; //First byte is the address to be assigned data[0] = addr; // Now copy in the address bytes //RConsole.println("1"); if (remAddress != null && remAddress.length == ADDRESS_LEN) System.arraycopy(remAddress, 0, data, offset, ADDRESS_LEN); offset += ADDRESS_LEN; // and the name //RConsole.println("2"); if (remName != null && remName.length <= NAME_LEN) System.arraycopy(remName, 0, data, offset, remName.length); offset += NAME_LEN; //RConsole.println("3"); // Copy in our netAddress and netName System.arraycopy(netAddress, 0, data, offset, ADDRESS_LEN); offset += ADDRESS_LEN; //RConsole.println("4"); System.arraycopy(netName, 0, data, offset, NAME_LEN); // Now broadcast it //RConsole.println("5"); return sendData(BB_BROADCAST, (byte)0, (byte)0, data, 0, data.length); } /** * Compare two byte arrays, return true if they contain the same values. * @param b1 * @param off1 * @param b2 * @param off2 * @param len * @return */ boolean match(byte [] b1, int off1, byte[] b2, int off2, int len) { for(int i = 0; i < len; i++) { //RConsole.println("Match " + b1[off1+i] + " with " + b2[off2+i]); if (b1[off1 + i] != b2[off2 + i]) return false; } return true; } /** * Check to see if we are being assigned a new dynamic netAddress. If * we are make a note of the new address so that we will respond to * requests directed to it. * @param con Connection object to be bound to the new address */ void checkAddress(RS485Connection con) { // First check to see if we have a match on the netAddress. int offset = BB_DATAOFFSET + 1; // if either the netAddress or the netName matches we accept the assignment if (match(frame, offset, netAddress, 0, netAddress.length) || match(frame, offset + ADDRESS_LEN, netName, 0, netName.length)) { //RConsole.println("Got netAddress match " + frame[BB_DATAOFFSET]); offset += ADDRESS_LEN + NAME_LEN; // Get the remote netAddress and netName byte[] remAddr = new byte[ADDRESS_LEN]; byte[] remName = new byte[NAME_LEN]; System.arraycopy(frame, offset, remAddr, 0, remAddr.length); offset += ADDRESS_LEN; System.arraycopy(frame, offset, remName, 0, remName.length); con.bind(frame[BB_DATAOFFSET], addressToString(remAddr), nameToString(remName)); con.state = RS485Connection.CS_CONNECTING2; updateSlaveConnections(); } } /** * Poll the specified slave device. * @param chan */ boolean pollSlave(RS485Connection con) { // Gain access to the shared data synchronized(con) { int now = (int)System.currentTimeMillis(); //RConsole.println("Poll slave state " + con.state + " time " + now); // Now we need to work out exactly what to do switch (con.state) { case RS485Connection.CS_IDLE: case RS485Connection.CS_DISCONNECTED: // nothing to do in these states. return false; case RS485Connection.CS_DISCONNECTING2: // Need to close down this connection sendControl(con.bbAddress, BB_DISC, (byte)0); break; case RS485Connection.CS_CONNECTING1: // Need to do netAddress assignment. We do this via broadcast assignAddress(con.bbAddress, stringToAddress(con.address), stringToName(con.remoteName)); sendControl(con.bbAddress, BB_DISC, (byte)0); break; case RS485Connection.CS_CONNECTING2: // Need to initialise the device sendControl(con.bbAddress, BB_SNRM, (byte)0); break; default: // If we have data, send it. if (con.send()) break; // Do we have space for more data? if (con.inBuf.length - con.inCnt >= BUFSZ) sendControl(con.bbAddress, BB_ACKRR, con.inSeq); else sendControl(con.bbAddress, BB_ACKNR, con.inSeq); break; } } return true; } /** * Handle a response from a slave device. * @param con The connection to the slave */ void processResponse(RS485Connection con) { byte addr = frame[0]; byte control = frame[1]; // Access shared data //RConsole.println("processResponse addr" + addr + " control " + control + " time " + (int)System.currentTimeMillis()); synchronized(con) { con.retryCnt = 0; // Make sure it is addressed to us if (addr == con.bbAddress) { if (control == BB_FRMR) { // Slave has disconnected con.disconnect(); return; } switch(con.state) { case RS485Connection.CS_IDLE: case RS485Connection.CS_DISCONNECTED: // Should never happen drop the packet. break; case RS485Connection.CS_DISCONNECTING2: // Has the slave accepted the disconnect? if (control == BB_UA) con.disconnected(); break; case RS485Connection.CS_CONNECTING1: if (control == BB_UA) { //RConsole.println("Got conn ack"); con.state = RS485Connection.CS_CONNECTING2; } break; case RS485Connection.CS_CONNECTING2: // Has the slave accepted the new connection? if (control == BB_UA) { //RConsole.println("Got conn ack"); con.state = RS485Connection.CS_CONNECTING3; con.notifyAll(); } break; default: // If this is a data packet try and accept the data if ((control & BB_IMASK) == BB_IFRAME) { con.recv((byte)((control >>> BB_SSEQSHIFT) & BB_SEQMASK), frame, BB_DATAOFFSET, frameLen - (BB_DATAOFFSET + BB_CSUMSZ)); con.ack((byte)((control >>> BB_ASEQSHIFT) & BB_SEQMASK), false); } else if ((control & BB_ACKMASK) == BB_ACK) con.ack((byte)((control >>> BB_ASEQSHIFT) & BB_SEQMASK), (control & BB_ACKNR) == BB_ACKNR); else // Unexpected packet con.disconnect(); break; } } else if (addr == BB_BROADCAST) { // Broadcast packet. Decide if we should deal with it if (control == BB_DISC) // Another master has come on line we give up control disconnectAll(); } } } /** * Handle a request from the master. We respond to messages directed to * any one of our open connections or to broadcast requests. * @param con */ void processRequest() { RS485Connection con; byte addr = frame[0]; byte control = frame[1]; if (addr >= slaveConnections.length) return; con = slaveConnections[addr]; if (addr == BB_BROADCAST) { // Broadcast packet. Decide if we should deal with it if (control == BB_DISC) // A new master has come on line we reset disconnectAll(); else if ((control & BB_IMASK) == BB_IFRAME) { // Got a broadcast data packet must be trying to do netAddress // assignment. Check to see if we are interested. if (con != null) { synchronized(con) { if (con.state == RS485Connection.CS_CONNECTING1) checkAddress(con); } } } return; } // Do we have an active connection for this address? if (con == null) return; // Access shared data synchronized(con) { //RConsole.println("processRequest addr " + addr + " control " + control + " time " + (int)System.currentTimeMillis()); con.retryCnt = 0; if (control == BB_DISC) { sendControl(addr, BB_UA, (byte)0); // We have been told to disconnect or we are being connected // to (in which case we ignore this command). if (con.state != RS485Connection.CS_CONNECTING2) con.disconnected(); return; } switch(con.state) { case RS485Connection.CS_IDLE: case RS485Connection.CS_DISCONNECTED: // Should nevr happen abort, by falling through case RS485Connection.CS_DISCONNECTING2: // Tell the master our connection has been closed. sendControl(addr, BB_FRMR, (byte)0); break; case RS485Connection.CS_CONNECTING2: // Has the connection been opened if (control == BB_SNRM) { // Yes so move to next state. con.state = RS485Connection.CS_CONNECTING3; con.notifyAll(); sendControl(addr, BB_UA, (byte)0); } break; default: // If this is a data packet try and accept the data if ((control & BB_IMASK) == BB_IFRAME) { con.recv((byte)((control >>> BB_SSEQSHIFT) & BB_SEQMASK), frame, BB_DATAOFFSET, frameLen - (BB_DATAOFFSET + BB_CSUMSZ)); con.ack((byte)((control >>> BB_ASEQSHIFT) & BB_SEQMASK), false); } else if ((control & BB_ACKMASK) == BB_ACK) con.ack((byte)((control >>> BB_ASEQSHIFT) & BB_SEQMASK), (control & BB_ACKNR) == BB_ACKNR); else // Unexpected packet con.disconnect(); // If we are still connected if (con.state >= RS485Connection.CS_DISCONNECTING) { // try and send any data we may have if (!con.send()) { // Otherwise send the appropriate ack if (con.inBuf.length - con.inCnt >= BUFSZ) sendControl(con.bbAddress, BB_ACKRR, con.inSeq); else sendControl(con.bbAddress, BB_ACKNR, con.inSeq); } } break; } } } /** * Low level communications thread processing. Used to actually * send and receive data beteen devices. */ public void run() { while(true) { switch(devMode) { case DS_DISABLED: Delay.msDelay(1); break; case DS_MASTER: // We need to poll each of the connected devices for (int i = 1; i < connections.length; i++) { RS485Connection con = connections[i]; if (con != null) { if (pollSlave(con)) { int cnt; for(cnt = 0; cnt < REPLY_RETRY; cnt++) if (recvFrame() >= 0) break; // Have we been waiting too long? if (cnt >= REPLY_RETRY) { //RConsole.println("Timeout"); if (++con.retryCnt > REQUEST_RETRY) { //RConsole.println("Request timeout chan " + i); con.disconnected(); } } else processResponse(con); } } } break; case DS_SLAVE: { if (recvFrame() >= 0) processRequest(); else if (slaveCnt > 0 && ++retryCnt > RECV_RETRY) { //RConsole.println("Retry count exceeded"); disconnectAll(); } break; } } Thread.yield(); } } } /** * Connect to a remote device either by name or by address. * @param target The address/name of the remote device to connect to * @param mode I/O mode to use for this connection * @return null if failed to connect, or a NXTConnection object */ public static RS485Connection connect(String target, int mode) { // Allocate a chan for the new connection. RS485Connection con; if (target == null) return null; con = controller.newConnection(DS_MASTER); if (con == null) return null; synchronized(con) { int chan = con.connNo; //RConsole.println("Got connection " + chan); // Make sure we have not been disconnected. if (con.state == RS485Connection.CS_DISCONNECTED) return null; if (isAddress(target)) con.bind((byte)chan, target, null); else con.bind((byte) chan, null, target); // Now try to do the connect //RConsole.println("Try to connect"); // Do Address assignmant and try to connect con.state = RS485Connection.CS_CONNECTING1; try{con.wait();}catch(Exception e){} if (con.state == RS485Connection.CS_CONNECTING3) { // We found a slave with the correct Name/Address con.state = RS485Connection.CS_CONNECTED; con.setIOMode(mode); //RConsole.println("Connected"); return con; } // failed to connect con.disconnected(); controller.freeConnection(con); } //RConsole.println("Connection failed"); return null; } /** * Connect to a remote device by name/address * @param target The name/address of the remote device * @return null if failed to connect, or a NXTConnection object */ public static RS485Connection connect(String target) { return connect(target, 0); } /** * Wait for a connection from another nxt * @param timeout How long to wait for the connect 0 means wait forever * @param mode The I/O mode to use for this connection * @return null if failed to connect, or a NXTConnection object */ public static RS485Connection waitForConnection(int timeout, int mode) { RS485Connection con; if (timeout == 0) timeout = 0x7ffffff; con = controller.newConnection(DS_SLAVE); if (con == null) return null; //RConsole.println("Wait for connect on chan " + con.connNo); // Keep trying allowing for resets for(;;) { synchronized(con) { con.reset(); con.state = RS485Connection.CS_CONNECTING1; do{ try{ con.wait(timeout < 1000 ? timeout : 1000);} catch(InterruptedException e){break;} timeout -= 1000; } while (timeout > 0 && con.state >= RS485Connection.CS_CONNECTING1 && con.state < RS485Connection.CS_CONNECTING3); //RConsole.println("After wait state " + con.state); if (con.state == RS485Connection.CS_CONNECTING3) { // Now connected //RConsole.println("Now connected..."); con.state = RS485Connection.CS_CONNECTED; con.setIOMode(mode); return con; } else if (con.state > RS485Connection.CS_DISCONNECTED) { // Failed to connect free things up //RConsole.println("Connection failed"); con.disconnect(); controller.freeConnection(con); return null; } // Must have been a network reset/timeout try again } } } /** * Firmware routine to send a BitBus packet. * Create a SDLC/BitBus packet including framing and byte stuffing operations * Calculate and include an CITT 16 CRC checksum, using the supplied * pre-calculated lookup table. * @param address BitBus address * @param control BitBus control field * @param data Data to send * @param offset offset of start of data * @param len Length of data must be <= the firmware BUFSZ (currently 128 bytes) * @param crc Lookup table for crc calculation. * @return >= 0 number of bytes sent, < 0 error */ static native int hsSend(byte address, byte control, byte[]data, int offset, int len, char[]crc); /** * Firmware routine to read a BitBus packet. * Read a valid BitBus packet from the network. This routine does not block * and may need to be called multiple times to assemble a complete packet. * Any packets that have a bad crc are discarded. * @param data Location to place the packet * @param len Max length of the packet (must be at least 4 bytes in len) * @param crc Lookup table for crc calculations. * @param reset If != 0 the packet state is reset. * @return < 0 no data == 0 Partial packet > 0 length of valid packet. */ static native int hsRecv(byte[]data, int len, char[]crc, int reset); /** * Enable the RS485 hardware port. */ public static native void hsEnable(); /** * Disable the RS485 hardware port. */ public static native void hsDisable(); /** * Low level read from the RS485 port * @param buf * @param offset * @param len * @return the number of bytes read */ public static native int hsRead(byte [] buf, int offset, int len); /** * Low level write to the RS485 hardware port. * @param buf * @param offset * @param len * @return the number of bytes written */ public static native int hsWrite(byte[] buf, int offset, int len); /** * Class to provide polymorphic access to the connection methods. * Gets returned as a singleton by getConnector and can be used to create * connections. */ static class Connector extends NXTCommConnector { /** * Open a connection to the specified name/address using the given I/O mode * @param target The name or address of the device/host to connect to. * @param mode The I/O mode to use for this connection * @return A NXTConnection object for the new connection or null if error. */ public NXTConnection connect(String target, int mode) { return RS485.connect(target, mode); } /** * Wait for an incomming connection, or for the request to timeout. * @param timeout Time in msDelay to wait for the connection to be made * @param mode I/O mode to be used for the accpeted connection. * @return A NXTConnection object for the new connection or null if error. */ public NXTConnection waitForConnection(int timeout, int mode) { return RS485.waitForConnection(timeout, mode); } } static NXTCommConnector connector = null; /** * Provides access to the singleton connection object. * This object can be used to create new connections. * @return the connector object */ public static NXTCommConnector getConnector() { if (connector == null) connector = new Connector(); return connector; } }