Lego MindStorms

Lego MindStorms is a ``Robotics Invention System'' that allows you to build Lego toys with a programmable computer. This chapter looks at the issues in interfacing with a specialised hardware device, using MindStorms as example

Contents

  1. MindStorms
  2. MindStorms as a Jini Service
  3. RCXPort
  4. RCX Programs
  5. Jini classes
  6. Entry objects for a robot
  7. Adding entries to a server
  8. A client-side RCX class
  9. Higher level mechanisms: Not Quite C

1. MindStorms

Lego MindStorms (http://www.legomindstorms.com) is a ``Robotics Invention System'' which consists of a number of Lego parts and a microcomputer called the RCX, plus an infra-red transmitter (connected to the serial port of an ordinary computer) and various sensors and motors. Using this, one can build an almost indefinite variety of Lego robots that can be controlled by the RCX. This computer can be sent ``immediate'' commands, or can have a (small) program downloaded and then run.

MindStorms is a pretty cool system, that can be driven at a number of levels. A primary audience for programming this is children, and there is a visual programming environment to help in this. This visual environment only runs on Windows or Macintosh machines which are connected to the RCX by their serial port and the infrared transmitter. Behind this environment is a Visual Basic set of procedures captured in an OCX, and behind that is the machine code of the RCX which can be sent as byte codes on the serial port.

2. MindStorms as a Jini Service

A MindStorms robot can be programmed and run from an infrared transmitter attached to the serial port of a computer. There is no security or real location for the RCX: it will accept commands from any transmitter in range. We will assume a ``home'' computer for it.

There must be a way of communicating with this device. For a MindStorms robot this is by the serial port, but other devices may have different mechanisms. Communication may be by Java code or by native code. Even if Java code is used, at some stage it must drop down to the native code level in order to communicate with the device - the only question is whether you write the native code or someone else does it for you and wraps it up in Java object methods.

For the serial port, Sun has an extension package - the commAPI - to talk to serial and parallel ports (http://java.sun.com/products/javacomm/index.html) . This gives platform-independent Java code, and also platform specific native code libraries supplied as DLL's for Windows and Solaris. I am running Linux on my laptop, so I need a Linux version of the DLL. This has been made by Trent Jarvi (jarvi@ezlink.com), and can be found at http://jarvi.ezlink.com/rxtx/. The native code part of communicating to the device has been done for us, and it is all wrapped up in a set of portable Java classes.

The RCX expects particular message formats, such as starting with standard headers. A Java package to make this easier is available by Dario Laverde at http://www.escape.com/~dario/java/rcx. There are other packages that will do the same thing: see the ``Lego Mindstorms Internals'' page by Russell Nelson at http://www.crynwr.com/lego-robotics/.

With this as background, we can look at how to make an RCX into a Jini service. It will involve being able to construct an RCX program on a client and send this back to the server where it can be sent on to the RCX via the serial port. This will then allow a client to control a Mindstorms robot remotely. Actually, the Jini part is pretty easy - the hard part was tracking down all the bits and pieces needed to drive the RCX from Java. With your own lumps of hardware, the hard part will be writing the JNI and Java code to drive it.

3. RCXPort

The package by Dario Laverde defines various classes, of which the most important is RCXPort:


package rcx;

public class RCXPort {
    public RCXPort(String port);
    public void addRCXListener(RCXListener rl);
    public boolean open();
    public void close();
    public boolean isOpen();
    public OutputStream getOutputStream();
    public InputStream getInputStream();
    public synchronized boolean write(byte[] bArray);
    public void processRead();
    public String getLastError();
    public void showTable();
    public static byte[] parseString(String str);
}

Not all of these should have been declared public, but never mind - we can just ignore the ones we don't want. The ones we do want are

  1. The constructor RCXPort(). This takes the name of a port as parameter, and this should be something like COM1 for Windows and /dev/ttyS0 for Linux.
  2. The method write() is used to send an array of opcodes and their arguments to the RCX. This is machine code and you can only read it by a dis-assembler or a Unix tool like octal dump (od -t xC).
  3. The static method parseString() can be used to translate a string of insrtuctions in readable form to an array of byte for sending to the RCX. It isn't as good as an assembler, as you have to give strings such as "21 81" to start the A motor. To use this for Jini, we will have to use a non-static method in our interface since static methods are not allowed.
  4. To handle responses from the RCX, a listener may be added by addRCXListener(). The listener must implement the interface
    
    package rcx;
    
    import java.util.*;
    
    /* 
     * RCXListener                      copyleft (GPL) 1998 Dario Laverde
     *  - work in progress                               dario@escape.com
     */
    public interface RCXListener extends EventListener {
        public void receivedMessage(byte[] message);
        public void receivedError(String error);
    }
    
      

4. RCX Programs

At the lowest level, the RCX is controlled by machine-code programs sent via the infrared link. It will respond to these by stopping and starting motors, changing speed, etc. As it completes commands or receives information from sensors, it can send replies back to the host computer. The RCX can handle instructions sent directly, or have a program downloaded into firmware and run from there.

Kekoa Proudfoot has produced a list of the opcodes understood by the RCX which is available at http://graphics.stanford.edu/~kekoa/rcx. Using these and the rcx.RCXPort from Dario Laverde means we can control the RCX from the ``home'' computer by standalone programs such as



/**
 * TestRCX.java
 *
 *
 * Created: Wed Jun  2 13:34:12 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

package standalone;

import rcx.*;

public class TestRCX implements RCXListener {
    static final String PORT_NAME = "/dev/ttyS0"; // Linux

    public TestRCX() {
	RCXPort port = new RCXPort(PORT_NAME);

	port.addRCXListener(this);

	byte[] byteArray;

	// send ping message, reply should be e7 or ef
	byteArray = RCXPort.parseString("10"); // Alive
	port.write(byteArray);

	// beep twice
	byteArray = RCXPort.parseString("51 01"); // Play sound
	port.write(byteArray);

	// turn motor A on (forwards)
	byteArray = RCXPort.parseString("e1 81"); // Set motor direction
	port.write(byteArray);
	byteArray = RCXPort.parseString("21 81"); // Set motor on
	port.write(byteArray);
	try {
	    Thread.currentThread().sleep(1000);
	} catch(Exception e) {
	}

	// turn motor A off
	byteArray = RCXPort.parseString("21 41"); // Set motor off
	port.write(byteArray);

	// turn motor A on (backwards)
	byteArray = RCXPort.parseString("e1 41"); // Set motor direction
	port.write(byteArray);
	byteArray = RCXPort.parseString("21 81"); // Set motor on
	port.write(byteArray);
	try {
	    Thread.currentThread().sleep(1000);
	} catch(Exception e) {
	}

	// turn motor A off
	byteArray = RCXPort.parseString("21 41"); // Set motor off
	port.write(byteArray);
    }

    /**
     * listener method for messages from the RCX
     */
    public void receivedMessage(byte[] message) {
	if (message == null) {
	    return;
	}
	StringBuffer sbuffer = new StringBuffer();
        for(int n = 0; n < message.length; n++) {       
            int newbyte = (int) message[n];
            if (newbyte < 0) {
		newbyte += 256;
	    }
            sbuffer.append(Integer.toHexString(newbyte) + " ");
	}
	System.out.println("response: " + sbuffer.toString());
    }

    /**
     * listener method for error messages from the RCX
     */
    public void receivedError(String error) {
	System.err.println("Error: " + error);
    }
    
    public static void main(String[] args) {
	new TestRCX();
    }
    
} // TestRCX

5. Jini classes

We shall follow the pattern of ``option3'' in the chapter on ``Simple Examples''. This means constructing a hierarchy of classes



Figure 1: Jini classes for RCX

The RCXPortInterface just defines the methods we shall be making available from the Jini service. It doesn't have to follow the RCXPort methods completely, because these will be wrapped up in implementation classes such as RCXPortImpl. The interface is defined as



/**
 * RCXPortInterface.java
 *
 *
 * Created: Wed Jun  2 23:08:12 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

package rcx.jini;

import net.jini.core.event.RemoteEventListener;

public interface RCXPortInterface extends java.io.Serializable {

    /** 
     * constants to distinguish message types
     */
    public final long ERROR_EVENT = 1;
    public final long MESSAGE_EVENT = 2;

    /**
     * Write an array of bytes that are RCX commands
     * to the remote RCX.
     */
    public boolean write(byte[] byteCommand) throws java.rmi.RemoteException;

    /**
     * Parse a string into a set of RCX command bytes
     */
    public byte[] parseString(String command) throws java.rmi.RemoteException;

    /**
     * Add a RemoteEvent listener to the RCX for messages and errors
     */
    public void addListener(RemoteEventListener listener) 
	throws java.rmi.RemoteException;

    /**
     * The last message from the RCX
     */
    public byte[] getMessage(long seqNo)
	throws java.rmi.RemoteException;

    /**
     * The error message from the RCX
     */
    public String getError(long seqNo)
	throws java.rmi.RemoteException;

} // RCXPortInterface

We have chosen to make it a sub-package of the rcx package to make its role clearer. Note that it has no static methods, but makes parseString() into an ordinary instance method.

This interface contains two types of methods: those used to prepare and send messages to the RCX (write() and parseString()), and those for handling messages sent from the RCX (addListener(), getMessage() and getError()). Adding a listener means that it will be informed of events generated by implementations of this interface by having the listener's notify() method called. However, a RemoteEvent does not contain detailed information about what has happened, only an event type (MESSAGE_EVENT or ERROR_EVENT). It is up to the listener to make queries back into the object to discover what the event meant, which it does by getMessage() and getError().

The interface RemoteRCXPort adds the Remote interface as before



/**
 * RemoteRCXPort.java
 *
 *
 * Created: Wed Jun  2 23:13:17 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

package rcx.jini;

import java.rmi.Remote;

public interface RemoteRCXPort extends RCXPortInterface, Remote {
    
} // RemoteRCXPort

The RCXPortImpl constructs its own RCXPort object and feeds methods through to it, such as write(). Since it extends UnicastRemoteObject it also adds exceptions to each method, which cannot be done to the original RCXPort class. In addition, it picks up the value of the port name from the port property. (This follows the example of the RCXLoader in the rcx package which gives a GUI interface to driving the RCX.) It looks for this property in a file parameters.txt which should have lines such as


    port=/dev/ttyS0
The RCXPortImpl also acts as a listener for ``ordinary'' RCX events signalling messages from the RCX. It uses the callback methods receivedMessage() and receivedError() to create a new RemoteEvent object and send it to the implementation's listener object (if any) by calling its notify() method.

The implementation looks like



/**
 * RCXPortImpl.java
 *
 *
 * Created: Wed Jun  2 23:14:58 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

package rcx.jini;

import java.rmi.server.UnicastRemoteObject;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import rcx.*;
import java.io.*;
import java.util.*;

public class RCXPortImpl extends UnicastRemoteObject
    implements RemoteRCXPort, RCXListener {

    protected String error = null;
    protected byte[] message = null;
    protected RCXPort port = null;
    protected RemoteEventListener listener = null;
    protected long messageSeqNo, errorSeqNo;  

    public RCXPortImpl() 
	throws java.rmi.RemoteException {

	Properties parameters;
	String portName = null;
	File f = new File("parameters.txt");
        if (!f.exists()) {
            f = new File(System.getProperty("user.dir")
                         + System.getProperty("path.separator")
                         + "parameters.txt");
        }
        if (f.exists()) {
            try {
		FileInputStream fis = new FileInputStream(f);
		parameters = new Properties();
		parameters.load(fis);
		fis.close();
                portName = parameters.getProperty("port");
            } catch (IOException e) { }
        } else {
	    System.err.println("Can't find parameters.txt with \"port=...\" specified");
	    System.exit(1);
	}
	
        port = new RCXPort(portName);
        port.addRCXListener(this);
	
    }
    
    public boolean write(byte[] byteCommands)
	throws java.rmi.RemoteException {
	return port.write(byteCommands);
    }

    public byte[] parseString(String command) 
	throws java.rmi.RemoteException {
	return RCXPort.parseString(command);
    }

    /**
     * Received a message from the RCX.
     * Send it to the listener
     */
    public void receivedMessage(byte[] message) {

	this.message = message;

	// Send it out to listener
	if (listener == null) {
	    return;
	}

	RemoteEvent evt = new RemoteEvent(this, MESSAGE_EVENT, messageSeqNo++, null);
	try {
	    listener.notify(evt); 
	} catch(net.jini.core.event.UnknownEventException e) {
	    e.printStackTrace();
	} catch(java.rmi.RemoteException e) {
	    e.printStackTrace();
	}
    }

    /**
     * Received an error message from the RCX.
     * Send it to the listener
     */
    public void receivedError(String error) {
	// System.err.println(error);

	// Send it out to listener
	if (listener == null) {
	    return;
	}
	this.error = error;
	RemoteEvent evt = new RemoteEvent(this, ERROR_EVENT, errorSeqNo, null);
	try {
	    listener.notify(evt);
	} catch(net.jini.core.event.UnknownEventException e) {
	    e.printStackTrace();
	} catch(java.rmi.RemoteException e) {
	    e.printStackTrace();
	}
    }

    /**
     * Expected use: the RCX has returned a message, 
     * and we have informed the listeners. They query
     * this method to find the message for the message
     * seqence number they were given in the RemoteEvent.
     * We could use this as an index into a table of messages.
     */
    public byte[] getMessage(long msgSeqNo) {
	return message;
    }

    /**
     * Expected use: the RCX has returned an error message, 
     * and we have informed the listeners. They query
     * this method to find the error message for the error message
     * seqence number they were given in the RemoteEvent.
     * We could use this as an index into a table of messages.
     */
    public String getError(long errSeqNo) {
	return error;
    }

    /**
     * Add a listener for RCX messages.
     * Should allow more than one, or throw
     * TooManyListeners if more than one registers
     */
    public void addListener(RemoteEventListener listener) {
	this.listener = listener;
	messageSeqNo = 0;
	errorSeqNo = 0;
    }
} // RCXPortImpl

The RCXPortProxy will run over on the client side (well, at least an RMI stub for it will). It's constructor takes a RCXPortImpl object and passes on all methods to it.



/**
 * RCXPortProxy.java
 *
 *
 * Created: Wed Jun  2 23:30:07 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

package rcx.jini;

import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.UnknownEventException;
import rcx.*;

public class RCXPortProxy implements RCXPortInterface/*, RemoteEventListener*/ {

    protected RemoteRCXPort service = null;

    public RCXPortProxy(RemoteRCXPort serv) {
	service = serv;
    }

    public boolean write(byte[] commands) 
	throws java.rmi.RemoteException {
	return service.write(commands);
    }

    public byte[] parseString(String str) 
	throws java.rmi.RemoteException {
	return service.parseString(str);
    }

    public void addListener(RemoteEventListener listener) {
	try {
	    service.addListener(listener);
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }

    public byte[] getMessage(long seqNo) 
	throws java.rmi.RemoteException {
	return service.getMessage(seqNo);
    }

    public String getError(long seqNo)
	throws java.rmi.RemoteException {
	return service.getError(seqNo);
    }

} // RCXPortProxy

6. Getting it running

To make use of these classes, we need to provide a server to get the service put onto the network, and some clients to make use of the service. This section will just look at a simple way of doing this, and later sections will try to put in more structure.

A simple server follows exactly the pattern of ``option 3'' in the chapter on ``Simple Examples'', just substituting RCXPort for FileClassifier, and using a JoinManager:



package rcx.jini;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceID;
import com.sun.jini.lease.LeaseRenewalManager;
import com.sun.jini.lookup.JoinManager;
import com.sun.jini.lookup.ServiceIDListener;

/**
 * RCXServer.java
 *
 *
 * Created: Wed Mar 17 14:23:44 1999
 *
 * @author Jan Newmarch
 * @version 1.1
 *    added LeaseRenewalManager
 *    moved sleep() from constructor to main()
 */

public class RCXServer implements ServiceIDListener {

    protected RCXPortImpl impl;
    protected RCXPortProxy proxy;
    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();
    
    public static void main(String argv[]) {
	new RCXServer();
    }

    public RCXServer() {
	try {
	    impl = new RCXPortImpl();
	} catch(Exception e) {
            System.err.println("New impl: " + e.toString());
            System.exit(1);
	}

	// set RMI security manager
	System.setSecurityManager(new RMISecurityManager());

	// make a proxy that knows our implementation
	proxy = new RCXPortProxy(impl);

	// find, register, lease, etc
	try {
	    JoinManager joinMgr = new JoinManager(proxy,
						  null,
						  this,
						  new LeaseRenewalManager());
	    joinMgr.setGroups(null);
	} catch(java.io.IOException e) {
	    e.printStackTrace();
	}
    }
    
    public void serviceIDNotify(ServiceID serviceID) {
	System.out.println("Got service ID " + serviceID.toString());
    }
} // RCXServer

Why is it simplistic as a service? Well, it doesn't contain any information to allow a client to distinguish one Lego Mindstorms robot from another, so that if there are many robots on the network then a client could ask the wrong one to do things!

An equally simplistic client to make the RCX do a few actions is given below. In addition to sending a set of commands to the RCX, it must also listen for replies from the RCX. We separate out this listener as an EventHandler for readability. The listener will act as a remote event listener, with its notify() method called from the server. This can be done by letting it run an RMI stub on the server, so we subclass it from UnicastRemoteObject.



package client;

import rcx.jini.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.UnknownEventException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
/**
 * TestRCX.java
 *
 *
 * Created: Wed Mar 17 14:29:15 1999
 *
 * @author Jan Newmarch
 * @version 1.1
 *    added GUI front-end
 */

public class TestRCX implements DiscoveryListener {

    public static void main(String argv[]) {
	new TestRCX();

        // stay around long enough to receive replies
        try {
            Thread.currentThread().sleep(10000L);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }
    }

    public TestRCX() {
	System.setSecurityManager(new RMISecurityManager());

	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);

    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();
	Class [] classes = new Class[] {RCXPortInterface.class};
	RCXPortInterface port = null;
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);
 
        for (int n = 0; n < registrars.length; n++) {
	    System.out.println("Service found");
            ServiceRegistrar registrar = registrars[n];
	    try {
		port = (RCXPortInterface) registrar.lookup(template);
	    } catch(java.rmi.RemoteException e) {
		e.printStackTrace();
		System.exit(2);
	    }
	    if (port == null) {
		System.out.println("port null");
		continue;
	    }

	    // add an EventHandler as an RCX Port listener
	    try {
		port.addListener(new EventHandler(port));
	    } catch(Exception e) {
		e.printStackTrace();
	    }
	}
    }

    public void discarded(DiscoveryEvent evt) {
	// empty
    }

    class EventHandler extends UnicastRemoteObject
                       implements RemoteEventListener, ActionListener {

	protected RCXPortInterface port = null;

	JFrame frame;
	JTextArea text;

        public EventHandler(RCXPortInterface port) throws RemoteException {
            super() ;
	    this.port = port;

	    frame = new JFrame("Lego MindStorms");
	    Container content = frame.getContentPane();
	    JLabel label = new JLabel(new ImageIcon("images/mindstorms.jpg"));
	    JPanel pane = new JPanel();	    
	    pane.setLayout(new GridLayout(2, 3));

	    content.add(label, "North");
	    content.add(pane, "Center");

	    JButton btn = new JButton("Forward");
	    pane.add(btn);
	    btn.addActionListener(this);

	    btn = new JButton("Stop");
	    pane.add(btn);
	    btn.addActionListener(this);

	    btn = new JButton("Back");
	    pane.add(btn);
	    btn.addActionListener(this);

	    btn = new JButton("Left");
	    pane.add(btn);
	    btn.addActionListener(this);

	    btn = new JButton("");
	    pane.add(btn);
	    btn.addActionListener(this);

	    btn = new JButton("Right");
	    pane.add(btn);
	    btn.addActionListener(this);

	    frame.pack();
	    frame.setVisible(true);
        }

	public void actionPerformed(ActionEvent evt) {
	    String name = evt.getActionCommand();
	    byte[] command;

	    try {
	    if (name.equals("Forward")) {
		command = port.parseString("e1 81");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
		command = port.parseString("21 81");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
	    } else  if (name.equals("Stop")) {
		command = port.parseString("21 41");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
	    } else  if (name.equals("Back")) {
		command = port.parseString("e1 41");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
		command = port.parseString("21 81");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}

	    } else  if (name.equals("Left")) {
		command = port.parseString("e1 04");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
		command = port.parseString("21 84");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
		Thread.sleep(100);
		command = port.parseString("21 44");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}

	    } else  if (name.equals("Straight")) {

	    } else  if (name.equals("Right")) {
		command = port.parseString("e1 84");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
		command = port.parseString("21 84");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
		Thread.sleep(100);
		command = port.parseString("21 44");
		if (! port.write(command)) {
		    System.err.println("command failed");
		}
	    }
	    } catch(Exception e) {
		e.printStackTrace();
	    }
	}

        public void notify(RemoteEvent evt) throws UnknownEventException, 
                                                 java.rmi.RemoteException {
	    // System.out.println(evt.toString());
    
	    long id = evt.getID();
	    long seqNo = evt.getSequenceNumber();
	    if (id == RCXPortInterface.MESSAGE_EVENT) {
		byte[] message = port.getMessage(seqNo);
		StringBuffer sbuffer = new StringBuffer();
		for(int n = 0; n < message.length; n++) {       
		    int newbyte = (int) message[n];
		    if (newbyte < 0) {
			newbyte += 256;
		    }
		    sbuffer.append(Integer.toHexString(newbyte) + " ");
		}
		System.out.println("MESSAGE: " + sbuffer.toString());
	    } else if (id == RCXPortInterface.ERROR_EVENT) {
		System.out.println("ERROR: " + port.getError(seqNo));
	    } else {
		throw new UnknownEventException("Unknown message " + evt.getID());
  	    }
        }
    }

} // TestRCX

Why is one simplistic as a client? It tries to find all robots on the local network, and sends the same set of commands to it. Worse, if a robot has registered with, say, half-a-dozen service locators, and the client finds all of these, then it will send the same set of commands six times to the same robot! Some smarts are needed here...

7. Entry objects for a robot

The RCX was not designed for network visibility. It has no concept of identity or location. The closest it comes to this is when it communicates to other RCX's by the infrared transmitter: then one RCX may have to decide if it is the master, which it does by setting a local variable to `master' if it broadcasts before it receives, while the other RCX's will set the variable to `slave' if they receive before broadcasting. Crude, but it works.

In a Jini environment, there may be many RCX devices. These devices are not `tied' to any particular computer, as they will respond to any infrared transmitter on the correct frequency talking the right protocol. All the devices within range of a transmitter will accept signals from the transmitter, although this can cause problems as the source computers tend to assume that there is only one target at a time and can get confused by responses from multiple RCXs. The advice is to ``turn off all but one RCX when a program is being downloaded'' to avoid this confusion. Then turn on the next, and download to it, etc.

An RCX may also be mobile - it can control motors, so if it is placed in a mobile robot it can drive itself out of the range of one PC and (maybe) into the range of another. There are no mechanisms to signal either passing out of range, or coming into range.

The RCX is a poorly behaved animal from a network viewpoint. However, we will need to distinguish between different RCX's in order to drive the correct ones. An Entry class to distinguish them should contain information such as

  1. An identifier for robot type, such as ``Robo 1'', ``Acrobot 1'', etc. This will allow the robot that the RCX is built into to be determined. The RCX will have no knowledge of this - it must be externally supplied.
  2. The RCX can be driven by direct commands, or by executing a program already downloaded (there may be upto five of these). An identifier for each program downloaded should be available for choice.
  3. The RCX will have some sort of location, although it may move around to a limited extent. This information may be available from the controlling computer, using the Jini Location or Address classes.
There may be other useful attributes, and there are certainly issues about how the information could be stored and accessed from an RCX. However, they stray beyond the bounds of this chapter.


8. Adding entries to a server

See next installment :-)

9. A client-side RCX class

In the simplistic client given earlier, there were many steps that will be the same for all clients that can drive the RCX. In a similar way that JoinManager simplifies repetitive code on the server side, we can define a ``convenience'' class for the RCX that will do the same on the client side. The aim is to supply a class that will make remote RCX programming as easy as local RCX programming.

A class to encapsulate client-side behaviour may as well look as much as possible like the local RCXPort class. We define its (public) methods as


public class JiniRCXPort {
    public JiniRCXPort();
    public void addRCXListener(RCXListener l);
    public boolean write(byte[] bArray);
    public byte[] parseString(String str);
}
This class should have some control over how it looks for services, by including entry information, group information about locators, and any specific locators it should try. There are a variety of constructors, all ending up in

    public JiniRCXPort(Entry[] entries,
		       java.lang.String[] groups, 
		       LookupLocator[] locators)
The class is also concerned with uniqueness issues, as it should not attempt to send the same instructions to an RCX more than once. However, it could send the same instructions to more than one RCX if it matches the search criteria. So this class maintains a list of RCX's, and does not add to the list if it has already seen the RCX from another service locator. This implies that a single RCX is registered with the same ServiceID with all locators, which it has because the RCX server uses JoinManager.

10. Higher level mechanisms: Not Quite C

See next installment :-)

11. Summary

This chapter has considered some of the issues involved in making a piece of hardware with a Jini service. This was illustrated with Lego MindStorms, where a lrage part of the base work of native code libraries and encapsulation in Java classes has already been done. Even then, there is still much work to make it into a suitable Jini service, and these have been discussed. This not yet complete, and more work remains to be done for Lego MindStorms.

This file is Copyright ©Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name

The copyright is the OpenContent License (http://www.opencontent.org/opl.shtml), which is the ``document'' version of the GNU OpenSource license.