Interfacing with the RCX Java APIThere are multiple mechanisms by which the Jini proxy could interface with theRCX—in our example, we shall use the RCX Java API.We will create a servicep
Trang 1method calls; it knows how to communicate back to the original serviceobject, which may well be running on another machine, and that orig-inal object does all of the processing.
This flow is shown in Figure 9.8
Now that would make sense; if the service object was expecting to nicate with an RCX via a serial port, then it still could because the object would
commu-be invoked in the original JVM that created it And if that object held references
to other objects, these references would still be valid for the same reasons
The only problem is that we have to write an absolutely enormous amount
of code to achieve this complex interaction between objects across the network,right? Wrong! Fortunately, Jini’s use of RMI means that this can be achieved withessentially no coding whatsoever! There are several ways to implement this, butthe one we will use in the examples is by extending a class known as
java.rmi.server.UnicastRemoteObject Basically, the steps required are as follows:
1 Make sure that the class of the object that is to be registered as a service
extends UnicastRemoteObject (and also ensure that it still implements
Serializable).
2 When you compile your service’s class (for example, MyCoolService.class),
run the RMI compiler against it to produce a stub class
1 Service object created
2 Stub object registered with lookup service
Stub object
Stub object (serialized)
3 Stub object reconstructed in client JVM
5 Stub object communicates back to service object
4 Methods called against stub object locally
Trang 2HTTP server.
That’s it! That’s all you have to do; the RMI compiler will generate all of thecode required for the stub so that the stub and the original object know how tocommunicate across JVMs and even across a network.You still specify the originalclass name in your code; Jini knows to instantiate the stub and register that with
reggie instead of your local object.
Generating the stub class using the RMI compiler is no trouble at all.The
compiler executable is rmic, and the argument is the class file that the stub is
being produced for.The output is the class name followed by an underscore,
fol-lowed by Stub, folfol-lowed by the class extension So, if the original class was called
MyCoolServiceClass.class, the generated stub would automatically be called MyCoolServiceClass_Stub.class.
The most commonly used options for rmic in this context are:
actually generates a java source file on its way to generating the classfile, and this option stops the source file from being deleted; handy ifyou want to understand a little more about what’s going on behind thescenes
files, with “_stub” and “_skel” suffixes.The skeleton files are no longerrequired, so this option suppresses their creation
So, running the command:
rmic -v1.2 mindstorm.MindstormProxy
would create a single stub file called MindstormProxy_Stub.class in the mindstorm
directory
Proxies and Service Architectures
Having now covered some of the basics of registering and using a Jini service, wealso need to consider how to decide on the correct architecture for a specific sit-uation, and most appropriately in this context, how to select the right architec-ture for a situation involving LEGO MINDSTORMS and Jini
www.syngress.com
Trang 3Selecting the Right Architecture
As with most things, there are good and bad ways to approach the architecture of
a Jini federation, but often no “right” or “wrong” way.The following sections willserve as a guide to how your particular solutions may be designed
Using Proxies
As we have already covered, Jini is often considered appropriate as a technologyfor allowing the services of a wide range of devices to be readily discovered andutilized in a distributed environment If you think about it, it makes perfect sensethat a large device such as a household air conditioning unit may contain enoughcomputing power to run Java, (including RMI), a TCP/IP stack, and Jini, so that
it can register its own services.What then if the device contained a much lesspowerful embedded system (perhaps an 8- or 16-bit microcontroller), with only aprimitive communications facility (perhaps RS-232) and limited RAM (measured
in Kbytes rather than Mbytes or Gbytes)? That’s when you would need a proxy
A proxy in the Jini context is a service that runs on a more powerful puting platform (such as a PC or perhaps a home control system) and whichstands between the client and something like an embedded device.The thingabout the proxy is that to the client, the proxy appears to actually be the serviceitself However, the reality is that the proxy is a layer of abstraction; it handles all
com-of the communication with the device that is physically performing the service(over RS-232 in our previous example), and it handles such tasks as service regis-tration and lease renewal Figure 9.9 shows the basic architecture of a systemwhere a single object acts as a proxy, and the physical service is carried out by aLEGO MINDSTORMS device
A RCX Jini Proxy Service
In this section, we will delve into building an example Jini service and client thatwill allow multiple LEGO MINDSTORMS to interact with each other.Thiswill follow a design similar to that shown in Figure 9.9, where a proxy architec-ture is used
Why a Proxy?
If you were thinking that our discussions about embedded devices with limitedcomputing powers and primitive communications abilities could apply to the
www.syngress.com
Trang 4implement a form of Java Virtual Machine within the RCX, it really doesn’tcome close to having the computing power required to run Jini.
That said, the Jini proxy architecture makes perfect sense in the case of theRCX Since the only way it has of connecting to a network is via an infraredlink connected to some form of more powerful computer, that computer is theideal candidate to act as proxy So, a Jini federation of MINDSTORMS is a veryreal possibility; one or many machines connected to infrared towers register proxyservices.To the client or clients, these proxy services appear as the services them-
selves—the client calls a method DoWhateverRobotsDo(), and the robot responds
by doing it.What has really taken place though is that the proxy has interpretedthe method call and executed it by sending and receiving a set of RCX opcodesover the infrared connection
1 Proxy object created 2 Stub object registered with lookup service
Stub object
Stub object (serialized)
3 Stub object reconstructed in client JVM
5 Stub object communicates back to proxy object
4 Methods called against stub object locally
Embedded device (e.g., LEGO MINDSTORMS) Proprietary protocol (e.g., RCX opcodes over infrared link)
Trang 5Interfacing with the RCX Java API
There are multiple mechanisms by which the Jini proxy could interface with theRCX—in our example, we shall use the RCX Java API.We will create a serviceprogram that will instantiate a proxy object (and its corresponding stub), and reg-ister this with the lookup service, and we will run more than one instance of thisprogram (using more than one RCX, each connected to its own machine).When
a client invokes methods against these proxy objects, the proxies will actuallymake use of the RCX Java API to send and receive the RCX opcodes to com-mand the RCX to behave a certain way and to receive feedback from the RCX
Using the RCX Jini Service:
Example Server and Client
Now we will cover a more complex example scenario that involves one or moreLEGO MINDSTORMS In this example, the MINDSTORMS will effectivelycommunicate with each other (via a central client program) to perform a kind ofsynchronized dance
The desired outcome is this: for a pair of MINDSTORMS, the first will form a certain “dance step” (actually one of eight predefined movements); thesecond will imitate the exact movement, wait several seconds, and then performits own randomly chosen movement.This will then be imitated by the first, and
per-so on until the program is terminated or the batteries run out!
If there is only one MINDSTORMS robot in the Jini Federation, it will justhave to be content with imitating its own movements Likewise, if there are morethan two robots, they will form a “daisy chain,” with each one copying the moves
of the previous one in the chain before deciding on its own original step
Figure 9.10 shows the basic scenario for this example, assuming two robots
As you can see in this diagram, the PCs which are connected to the infraredtowers will act as proxies for RCX units themselves Also, because some of thecomponents of this system are likely to reside on different machines (or at least indifferent JVMs in the case where one machine may control multiple RCXs viamultiple serial ports), the proxy objects must be executed in the JVM of the ser-vice that registers them.Therefore, it will actually be stub objects that will be reg-
istered with reggie and these stub objects will communicate via RMI with the
proxies
www.syngress.com
Trang 6A RCX Jini Server
The first step is to create an interface that can be known to both client and vice Again, as in the simple Jini example shown previously, we will create this
ser-interface in a package called shared (see Figure 9.11).The ser-interface defines some
methods to allow certain attributes to be retrieved, as well as the method
imitateThis(), which is a command to a MINDSTORMS robot to imitate a
cer-tain dance step
Infrared
PC and Infrared Tower
MINDSTORMS robot 1
Locator Service (reggie)
Client Register service proxy Register service proxy
Lookup both service proxies
Send commands
Receive messages
Send commands Receive
messages
Continued
Trang 7// Define the two possible message types:
// EVENT_RCX_MSG is a message from a remote RCX.
// EVENT_DANCE_STEP is a notification that a Mindstorm // has completed a dance step.
public final int EVENT_RCX_MSG = 1;
public final int EVENT_DANCE_STEP = 2;
// Method to retrieve the ID of a Mindstorm.
public String getRcxId() throws RemoteException;
// Tell a Mindstorm to imitate a dance step
public void imitateThis(int danceStep) throws RemoteException;
// Retrieve the most recent RCX Message received Should // be called in response to receiving an EVENT_RCX_MSG.
public byte[] GetLastRCXMessage() throws RemoteException;
// Retrieve the most recent dance step performed // by an RCX Should be called in response to // receiving an EVENT_DANCE_STEP.
public int GetLastStepPerformed() throws RemoteException;
// Allow the client to register a listener // to receive events from the service
public void RegisterRemoteListener(RemoteEventListener listener)
www.syngress.com
Continued
Trang 8throws RemoteException;
}
The proxy class will be called mindstorm.MindstormProxy It will make extensive
use of the RCX Java classes, so make sure you are familiar with the RCX Javamechanisms before you embark on this one.This proxy will also make use of mul-tithreading; it will spawn a new thread of execution and return, before proceedingthrough the cycle of imitating a dance step, pausing for 10 seconds, and thendeciding on another dance step to perform.This is so that the client program isnot held up for the entire 10-plus seconds, and it is a very common technique touse for this kind of scenario in Java In this case, the class that will process the
additional thread (ImitationThread) has been implemented as an inner class It is a
class declared within another class, which has implicit access to the members ofthe enclosing class and is not visible outside of that class Just remember that when
MindstormProxy.java is compiled, two class files will be produced—MindstormProxy class and MindstormProxy$ImitationThread.class—and that both these files must be
in the Java runtime’s CLASSPATH for the example to work
The other thing to note about this class is that instances of it will be required
to execute on the machine that they were created on (because they will be municating out a physical serial port to an RCX device), so we will be required
com-to register an instance of a proxy that will be capable of communicating backwith the equivalent object of this class from a remote client As already discussed,
we are very fortunate that RMI can provide this functionality with very littleeffort on our part As can be seen from the following code, the proxy class will
extend UnicastRemoteObject.The only other thing you must remember to do is
run the RMI compiler against the compiled class file to produce a stub class.Youcan invoke the compiler with a command such as:
rmic -v1.2 mindstorm.MindstormProxy
After running rmic, you should now have produced a file called
MindstormProxy_Stub.class in the /mindstorm subdirectory; this file is the
proxy code (see Figure 9.12).That certainly beats coding it by hand!
www.syngress.com
Trang 9// Make sure to generate a stub class using the // RMI compiler since this class extends // UnicastRemoteObject.
protected static final byte FORWARDS = (byte)0x80;
protected static final byte BACKWARDS = (byte)0x00;
protected static final byte ON = (byte)0x80;
protected static final byte OFF = (byte)0x40;
protected static final byte MOTOR_A = (byte)0x01;
protected static final byte MOTOR_C = (byte)0x04;
protected static final byte MOTOR_DIR = (byte)0xe1;
protected static final byte MOTOR_ON_OFF = (byte)0x21;
// Store our unique ID so that the client
www.syngress.com
Continued
Trang 10// can differentiate between robots.
protected String rcxId = null;
protected RCXPort rcxPort = null;
protected RemoteEventListener remoteListener = null;
// Store any message received from the RCX // as a raw byte array for retrieval by the client.
// Note that any subsequent message will overwrite // the existing one and if messages are retireved // in quick succession, data may be lost A more // robust implementation could be employed for a // production system
protected byte[] lastRCXEvent = null;
// Similarly for the most recent dance step performed
protected int lastDanceStep = 0;
protected long seqNo = 0;
public MindstormProxy() throws RemoteException { }
public String getRcxId() { return rcxId;
Trang 11// Register as a listener with the RCX rcxPort.addRCXListener(this);
}
protected void executeMovement(int movementId) { // Execute one of our robot's 8
// spectacular dance steps.
System.out.println("Executing step: " + movementId);
switch(movementId) { case 0:
// Directly forward setMotorDir(FORWARDS, (byte)(MOTOR_A | MOTOR_C));
motorOn((byte)(MOTOR_A | MOTOR_C));
break;
case 1:
// Directly back setMotorDir(BACKWARDS, (byte)(MOTOR_A | MOTOR_C));
motorOn((byte)(MOTOR_A | MOTOR_C));
break;
case 2:
// Rotate right setMotorDir(FORWARDS, MOTOR_A);
Trang 12break;
case 6:
// Reverse right setMotorDir(BACKWARDS, MOTOR_A);
motorOn(MOTOR_A);
break;
case 7:
// Reverse left setMotorDir(BACKWARDS, MOTOR_C);
motorOn(MOTOR_C);
break;
} // Each dance step is of a 0.3 second duration try {
Thread.sleep(300);
} catch(InterruptedException e) { //
} motorsOff();
}
protected void motorOn(byte motors) { // Turn one or both motors on byte[] msg = new byte[] {};
sendToRcx(
new byte[] { MOTOR_ON_OFF, (byte)(ON | motors)});
www.syngress.com
Continued
Trang 13protected void motorsOff() { // Turn both motors off sendToRcx(
new byte[] { MOTOR_ON_OFF, (byte)(OFF | MOTOR_A | MOTOR_C)});
}
protected void sendToRcx(byte[] msg) {
System.out.println(
"Sending to port: " + byteArrayToString(msg));
if (!rcxPort.write(msg)) { System.err.println("Error writing to port");
} }
public void imitateThis(int danceStep) throws RemoteException {
// This should be the dance step that the other // robot has just performed We will attempt to // imitate it Handle this from a new thread.
new ImitationThread(danceStep, this).start();
www.syngress.com
Continued
Trang 14public void receivedMessage(byte[] msg) { // Receive messages from the RCX
if (null != msg) { System.out.println(
"RCX message: " + byteArrayToString(msg));
if (null != remoteListener) { // Store the event contents // for retrieval by the listener lastRCXEvent = msg;
// Notify the listener of the event.
// Listener will have to call back to // obtain the details.
RemoteEvent evt = new RemoteEvent(
this, EVENT_RCX_MSG, seqNo++, null);
try { remoteListener.notify(evt);
} catch(UnknownEventException e) { System.err.println("Event exception");
} catch(java.rmi.RemoteException e) { System.err.println("Remote exception");
}
} } }
www.syngress.com
Continued
Trang 15protected String byteArrayToString(byte[] msg) {
// Convert an array of bytes to a human-readable // string
StringBuffer sBuf = new StringBuffer();
for(int ix = 0; ix < msg.length; ix++) { int dm =
msg[ix] >= 0 ? (int)msg[ix] : ((int)msg[ix]) + 256;
}
public byte[] GetLastRCXMessage() { // This will be called by the client // to retrieve details of the last message // after we have sent a notification.
return lastRCXEvent;
}
public int GetLastStepPerformed() { // This will be called by the client // to retrieve details of the last step // after we have sent a notification.
return lastDanceStep;
www.syngress.com
Continued
Trang 16public void RegisterRemoteListener(
RemoteEventListener listener) { // Called by the client to register its // listener object (which should also extend // UnicastRemoteObject so that we can notify it // remotely).
remoteListener = listener;
}
// Declare this thread class as an inner class class ImitationThread extends Thread {
protected int step;
protected MindstormProxy proxy = null;
public ImitationThread(
int danceStep, MindstormProxy proxy) {
// Store the reference to the // object that created us as well // as the dance step to execute.
this.proxy = proxy;
step = danceStep;
}
public void run() {
// Firstly execute the move in // imitation of the other robot.
Trang 17} catch(InterruptedException e) { // Do nothing
}
// Now randomly pick a new movement // (between 0 and 7 inclusive).
int newMovement;
newMovement = (int)(8 * (Math.random()));
// Perform our randomly-selected movement executeMovement(newMovement);
// Now let the remote client // know that we just performed it.
new RemoteEvent(proxy, EVENT_DANCE_STEP, seqNo++, null);
try { remoteListener.notify(evt);
} catch(UnknownEventException e) { System.err.println("Event exception"); } catch(java.rmi.RemoteException e) { System.err.println(e.toString()); }
} } } }
www.syngress.com
Trang 18server that will instantiate a proxy object, register its stub with any lookup vices on the network, and keep it alive and keep the lease renewed.
ser-There are a number of differences between this service and the one welooked at in the preceding simple example One of these differences is that we
will be using multicast instead of unicast lookups to find instances of reggie (the
lookup service).Whereas, in the checksum example, we specified the IP address
of a specific lookup service instance, here we will use multicast to find anynumber of lookup services that may exist on our network Once we find them,
we will register the proxy object (actually its stub) with each of them
Another difference between this and our simple example is that in this nario we are expecting multiple services on the network to register objectsimplementing the same common interface.This is because this scenario involvesmultiple MINDSTORMS robots, and we need some way to differentiatebetween them Because there is no distinguishing identifier inherent in the basicRCX firmware, we will assign an identifying name to each proxy object instead
sce-This will be passed in as a command line parameter to the service as a string; itcan be any name at all as long as you use a different name for any other robotsyou register
Along with the robot’s identifier, we will expect another command lineparameter: the port identifier.This is the actual port name to be used by RCXJava, such as “COM1” for a Windows machine
It is important that this service keep itself alive after it has done the job ofregistering the proxy with the lookup service.This is not just for the purpose oflease renewal, but also because we are really only registering a stub for remoteuse, and the proxy object itself will run in the JVM of this service and willreceive calls remotely from the client via RMI If this JVM goes away, so does theproxy, and the client will start to see all kinds of remote exceptions and noresponses from the robot
The code for the service is as shown in Figure 9.13
Trang 19// This service will be responsible for creating // an instance of the proxy and registering it // with the Jini locator service.
protected MindstormProxy proxy = null;
// The LeaseRenewalManager will ensure that the // lease with the locator service is regularly // renewed.
protected LeaseRenewalManager leaseManager
Trang 20// The name of the serial port.
static protected String portId = null;
static public void main(String args[]) {
// Since reggie is running with a security policy, // we will have to as well This assumes that // the policy file is located at
// c:\jini1_2\policy\policy.all // Adjust this for specific installations
keepAlive.wait();
} catch(java.lang.InterruptedException e) { // do nothing
www.syngress.com
Continued
Trang 21} } }
public MindstormService() {
try { // Lookup using Multicast (i.e., look for any lookup // services on the network) We will receive a // callback to the discovered() method with a // list of all lookup services found.
LookupDiscovery lookupDiscovery = new LookupDiscovery(
LookupDiscovery.ALL_GROUPS);
lookupDiscovery.addDiscoveryListener(this);
} catch(IOException e) { System.err.println(e.toString());
System.exit(1);
} }
public void discarded(DiscoveryEvent event) { // Must be implemented from the
// DiscoveryListener interface.
}
public void discovered(DiscoveryEvent event) {
//try { // Must be implemented from the // DiscoveryListener interface.
// This method will be called with a list
www.syngress.com
Continued
Trang 22// of all lookup services found // (actually their registrar proxies).
ServiceRegistrar[] regArray = event.getRegistrars();
try { proxy = new MindstormProxy();
} catch (RemoteException e) { System.err.println(e.toString());
SecurityManager sm = System.getSecurityManager();
for (int ix = 0; ix < regArray.length; ix++) { ServiceRegistrar svcRegistrar = regArray[ix];
www.syngress.com
Continued
Trang 23// register ourselves as a service
ServiceItem serviceItem = new ServiceItem(
null, proxy, null);
// Request a 60 second lease duration This // means that the lease will require renewing // at least once every 10 seconds.
ServiceRegistration serviceRegistration = null;
try { serviceRegistration = svcRegistrar.register(
serviceItem, Lease.FOREVER);
} catch (RemoteException e) { // If the service registration // fails, we can still try with // any other lookup services // on the network.
leaseManager.renewUntil(
serviceRegistration.getLease(), Lease.FOREVER,
Lease.ANY, this);