Lego Mindstorms Programming: RCX Communications
Topics in this Chapter
- The Communications API
- Uploading Map Data
- Controlling the RCX Through a Network
- Controlling the RCX from a Web Page
- Alternate Data Transfer Methods
- Alternate Communication Uses
Communications between the RCX brick and a PC can greatly expand the potential of robotics projects. Memory is quite limited on the RCX but today's PCs contain an abundance. A clever programmer can off load complex code to the PC side, allowing the PC to control the RCX brick. For example, one acclaimed RCX project involves a Rubik's Cube solver that scans the faces of the cube, then rotates the cube until it is solved. This project actually uses an algorithm on the PC to analyze the cube faces and calculate a solutiona very memory-intensive operation. Once the solution is found it uploads the sequence of moves as an array to the RCX brick.
Of course, it is also possible for the RCX to send useful data back to the PC to be analyzed. Projects are often created using the RCX to monitor repetitive events, such as toilet flushes or the number of times a light has been turned on in a room. Light levels are also sometimes measured over long periods of time, especially for experiments in which it is necessary to see how much light a plant is taking in over time. Of course, mapping a location and sending the map coordinates back to the PC is a classic example of data collection, which is shown in this chapter.
There are also possibilities for telerobotics, the ability to control a robot from vast distances. Using the java.io package, commands to the RCX can be sent across any network, including the Internet. This opens up the possibility of controlling and monitoring experiments away from the lab. This chapter shows you how to control the RCX from any computer using Java data streams, as well as setting up a simple Web server to control a robot through a Web page using JavaScript.
Finally, it is also possible for two RCX bricks to communicate with each other using infrared signals. Communication between two RCX bricks is often used to make a "super RCX brick" with six inputs, six outputs, and 64 kB of memory. In this architecture, usually one brick is the controller brick, and the other merely takes commands for turning motors on and off or reading sensors. RCX robots can also be built to interact with each other, sending messages to achieve interesting "social" behavior. Communications truly opens up incredible possibilities for robotics.
The Communications API
Data flow is the lifeblood of computers, and leJOS allows communication on many levels: IR tower to RCX, RCX to RCX, and probably even IR tower to IR tower (although there isn't much practical use for this). In fact, leJOS can also receive data from the LEGO MINDSTORMS remote control (see Appendix A)both to the RCX, or even to the IR tower to control your computer. All of these combinations can be handled with leJOS.
The leJOS Communications API can be found in the java.io, pc.irtower.comm, and josx.platform.rcx.comm packages. The communications classes use streams, just like the standard java.io package, so anyone familiar with streams will find it easy to use. The leJOS java.io package contains only the most basic streams relevant to sending and receiving data: InputStream, OutputStream, DataInputStream, and DataOutputStream. Input/Output Streams are the foundation of Streams, and they are only useful for sending bytes. If you want to send other data types such as characters, integers, and floating-point numbers, you will need to use data streams (see "DataInputStream" and "DataOutputStream" later).
InputStream
InputStream is the superclass of all classes representing an input stream of bytes. It is an abstract class so it cannot be instantiated on its own. The main function of InputStream is to return the next byte of input from a data source. In leJOS an instance of InputStream can be obtained using DataPort.getInputStream() (see "PCDataPort" and "RCXDataPort" later).
java.io.InputStream
public int read() throws IOException
Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. This method blocks (waits) until input data is available, the end of the stream is detected, or an exception is thrown.public int read(byte[] b) throws IOException
Reads some number of bytes from the input stream and stores them in the buffer array b. The number of bytes actually read is returned as an integer. This method blocks until input data is available, the end of the file is detected, or an exception is thrown.Parameters:
b
The buffer into which the data is read.
public int read(byte[] b, int off, int len) throws IOException
Reads up to len bytes of data from the input stream into an array of bytes. An attempt is made to read as many as len bytes, but a smaller number may be read, possibly zero. The number of bytes actually read is returned as an integer.Parameters:
b
The buffer into which the data is read.
off
The start offset in array b at which the data is written.
len
The maximum number of bytes to read.
public void close() throws IOException
Closes this input stream, calls flush() and releases any system resources associated with the stream.
NOTE
In standard java.io.InputStream the methods mark() and reset() are used to jump back to a previous point in a stream. There are also methods for skip() and available(). The leJOS java.io.InputStream does not support any of these methods.
OutputStream
OutputStream is the superclass of all classes representing an output stream of bytes. It is an abstract class so it cannot be instantiated on its own. Its main function is to send a byte of data to a destination. Like InputStream, an instance of OutputStream can be obtained using DataPort.getOutput-Stream() (see "PCDataPort" and "RCXDataPort" later).
java.io.OutputStream
public void write(int b) throws IOException
Writes the specified byte to this output stream. The general contract for write is that one byte is written to the output stream. The byte to be written is the eight low-order bits of the argument b. The 24 high-order bits of b are ignored.public void write(byte b[]) throws IOException
Writes b.length bytes from the specified byte array to this output stream. The general contract for write(b) is that it should have exactly the same effect as the call write(b, 0, b.length).Parameters:
b
The data.
public void write(byte b[], int off, int len) throws IOException
Writes len bytes from the specified byte array starting at offset off to this output stream. The general contract for write(b, off, len) is that some of the bytes in the array b are written to the output stream in order; element b[off] is the first byte written and b[off+len-1] is the last byte written by this operation.Parameters:
b
The data.
off
The start offset in the data.
len
The maximum number of bytes to write.
public void flush() throws IOException
Flushes this output stream and forces any buffered output bytes to be written out. The general contract of flush() is that calling it is an indication that, if any bytes previously written have been buffered by the implementation of the output stream, such bytes should immediately be written to their intended destination.WARNING
Flush is one of the most important but often forgotten methods of streams. The nonuse of this method probably accounts for most bugs when using the java.io package. Don't forget to call flush() after sending data, otherwise the data may never be sent to the destination!
public void close() throws IOException
Closes this output stream and releases any system resources associated with this stream. The general contract of close is that it closes the output stream. A closed stream cannot perform output operations and cannot be reopened. A call to flush() is made in this method as well.
DataInputStream
DataInputStream extends InputStream, so it has all the methods of Input-Stream implemented (discussed earlier). This method allows data types other than bytes to be sent. This includes short, int, float, double, char, and boolean. Unlike the standard Sun java.io.DataInputStream class, the leJOS version does not include methods for receiving strings. If you wish to receive strings you can simply write code to read char values and assemble a new string. Also, to save memory the leJOS DataInputStream class does not extend FilterInputStream nor does it implement a DataInput interface.
NOTE
The java.io classes are used on the PC side as well as on the RCX. The PC side uses standard Sun Java classes, but the RCX uses special java.io classes written specifically for the RCX. Both "brands" communicate fine with one another, however (i.e., the leJOS OutputStream can talk to the Sun InputStream and vice versa).
java.io.DataInputStream
public DataInputStream(InputStream in)
Returns an instance of DataInputStream. The constructor requires an InputStream object obtained using RCXDataPort.getInputStream().Parameters:
in
The input stream.
public final boolean readBoolean() throws IOException
Used to send a Boolean value through a stream. Reads one input byte and returns true if that byte is nonzero, false if that byte is zero.public final byte readByte() throws IOException
Reads and returns one input byte. The byte is treated as a signed value in the range 128 through 127, inclusive.public final short readShort() throws IOException
Reads two input bytes and returns a short value.public final char readChar() throws IOException
Reads an input char and returns the char value (a Unicode char is made up of two bytes).public final int readInt() throws IOException
Reads four input bytes and returns an int value.public final float readFloat() throws IOException
Reads four input bytes and returns a float value.public final double readDouble() throws IOException
Reads eight input bytes and returns a double value.
DataOutputStream
If DataInputStream is the catcher then DataOutputStream is the pitcher. It encodes various data types into byte values and sends them across a data stream. DataOutputStream extends OutputStream, so it has all the methods described in the OutputStream API. Unlike the Sun DataOutputStream, DataOutputStream does not extend FilterOutputStream, nor does it implement DataOutput. It has most methods of the standard java.io.DataOutput-Stream, but excludes methods dealing with text data transfer.
java.io.DataOutputStream
public DataOutputStream(OutputStream out)
Creates a new data output stream to write data to the specified underlying output stream.Parameters:
out
The output stream.
public final void writeBoolean(boolean v) throws IOException
Writes a Boolean value to this output stream.
Parameters:
v
A Boolean value.
public final void writeByte(int v) throws IOException
Writes to the output stream the eight low-order bits of the argument v.Parameters:
v
A byte value.
public final void writeShort(int v) throws IOException
Writes two bytes to the output stream to represent the value of the argument.Parameters:
v
A short value.
public final void writeChar(int v) throws IOException
Writes a char value, which is comprised of two bytes, to the output stream.Parameters:
v
A char value.
public final void writeInt(int v) throws IOException
Writes an int value, which is comprised of four bytes, to the output stream.Parameters:
v
An int value.
public final void writeFloat(float v) throws IOException
Writes a float value, which is comprised of four bytes, to the output stream.Parameters:
v
A float value.
public final void writeDouble(double v) throws IOException
Writes a double value, which is comprised of eight bytes, to the output stream.Parameters:
v
A double value.
DataPort
DataPort is an abstract class at the top of the RCX communications hierarchy. A data port is a pretty general term, and can refer to a USB port, a serial port, or an RCX IR port. The DataPort class functions much like java.net.Socket in standard Java. These classes have in common the ability to hand out InputStream and OutputStream objects, which are absolutely vital for sending and receiving data in Java. Anyone familiar with Sockets should be comfortable using DataPort.
josx.platform.rcx.comm.DataPort
public InputStream getInputStream()
Returns an input stream for this DataPort.public OutputStream getOutputStream()
Returns an output stream for this DataPort.NOTE
You might wonder how this receives an InputStream or OutputStream object because these are abstract classes. The underlying returned classes are actually called RCXInputStream and RCXOutputStream, which are protected inner classes of DataPort.
public void close()
Closes this DataPort.public void setTimeOut(int timeOut)
The timeOut value represents the amount of time the DataPort will keep trying to exchange data. If it fails to receive a response from the target it will keep trying for this number of milliseconds. (The value is 0 by default, meaning it will keep trying forever.)Parameters:
timeOut
The number of milliseconds to keep trying if data communication fails.
public int getTimeOut()
Returns the current timeout value for this DataPort.
PCDataPort
PCDataPort extends the DataPort abstract class and implements all abstract methods. The main purpose of this class is to provide an InputStream or Out-putStream on the PC side to communicate through the IR tower. This class cannot and should not be used in any code intended for the RCX brick. To use this code in your regular Java code, simply import the pc.irtower.comm package (or import just the PCDataPort class).
Communication with the IR tower is complicated by the fact that the IR tower can only receive data while the green LED is on. If an RCX brick is sitting in front of the IR tower and it starts sending data, the IR tower does not detect this. It sits there, unpowered, until the PC side sends data. In other words, the PC must initiate all data transfers. The leJOS API deals with this problem, however, so it is almost invisible for a programmer:
pc.irtower.comm.PCDataPort
public PCDataPort(String port) throws IOException
Returns an instance of PCDataPort.Parameters:
port
A string describing the port to use. Accepts a value from COM1 to COM4 or USB (case does not matter).
public InputStream getInputStream()
Returns an input stream for this DataPort.public OutputStream getOutputStream()
Returns an output stream for this DataPort.
RCXDataPort
RCXData port allows data to be sent from the RCX IR port to another source and vice versa. This class is very easy to use, and the only methods of importance come from extending DataPortgetInputStream() and getOutput-Stream():
josx.platform.rcx.comm.RCXDataPort
public RCXDataPort()
Returns an instance of RCXDataPort.public InputStream getInputStream()
Returns an input stream for this DataPort.public OutputStream getOutputStream()
Returns an output stream for this DataPort.NOTE
There are plans to implement a java.net package for leJOS so the RCX brick could communicate through IP addresses directly to the Internet. (This will require a small server class on the PC side.)
Installation
There is nothing special to do to use the leJOS Communications API with the RCX, but the PC side must have access to special classes to work. Currently the leJOS Communications API depends on the JavaComm API to work. This is needed because different platforms use different forms of communicating with ports, so a platform-independent solution is needed.
WARNING
By the time this book hits the shelves installation of the Javacomm API may be unnecessary. There are plans to include Java Native Interface (JNI) communications within leJOS, eliminating the need for Javacomm installation. Check with the leJOS readme file first for installation notes.
Win32
Download the Windows version of JavaComm 2.0 from Sun at java.sun.com/products/javacomm/.
Extract the zipped file to any directory.
Copy Win32comm.dll to the bin directory of your JDK (e.g., C:\jdk1.3.1\bin).
Copy comm.jar to the lib directory of your JDK (e.g., C:\jdk1.3.1\lib).
Copy javax.comm.properties to the lib directory of your JDK (e.g., C:\jdk1.3.1\lib).
Add the comm.jar file to the CLASSPATH system variable (e.g., set CLASSPATH=c:\jdk1.1.6\lib\comm.jar).
Linux
Download the Solaris CommAPI release at java.sun.com/products/ javacomm.
Copy comm.jar into your JDK bin directory.
Add this JAR file to your CLASSPATH (e.g., export CLASS-PATH=$CLASSPATH:/usr/local/jdk1.1.5/bin/comm.jar).
Download and build RXTX at http://www.rxtx.org
Add the RXTX directory to your CLASSPATH as instructed in the RXTX documentation.
Create a text file in your JDK lib directory. This file must be named javax.comm.properties. The contents of this file is a single line that should read:
Driver=gnu.io.RXTXCommDriver
You can test your installation by running the BlackBox demo included with CommAPI: java -classpath BlackBox.jar:$CLASSPATH BlackBox
IDE Setup
If you plan on using an IDE (such as JCreator) to program your PC side programs, then it is necessary to include some settings in the IDE. JCreator automatically overrides the system CLASSPATH settings when compiling and running Java programs (it does not override these settings for the leJOS tools, however). For this reason we must add the settings to JCreator.
-
In JCreator, select Project Project Settings and click the Required Libraries tab (Figure 111).
Figure 11-1 Project settings dialog box.
-
Click New to add a new CLASSPATH setting and a dialog box appears (Figure 112). Type in leJOS Comm as the name. Click Add Add Package, and browse to the leJOS classes.jar file (e.g., C:\lejos\lib\classes.jar). Click OK when done.
Figure 11-2 Adding leJOS to the CLASSPATH.
-
Now add the Javacomm CLASSPATH setting. Click New and type Javacomm as the name (Figure 113). Click Add Add Package, and browse to the comm.jar file (e.g., C:\jdk1.3.1\lib\comm.jar). Click OK when done.
Figure 11-3 Adding Javacomm to the CLASSPATH
-
Select the check boxes next to the two settings (Figure 114). That's it! You can now use the Compile File and Execute File buttons on the toolbar for PC Java programs using leJOS Comm.
Figure 11-4 Activating the CLASSPATH setting for the project.