Monday, March 24, 2008

fantom API and JAVA

found this blog of Emmanuel: (all credits to @author Emmanuel Pirsch)
http://epirsch.blogspot.com/2008/02/jna-love-nxt.html
good news for the struggle to get ECLIPSE JAVA and bluetooth together.

Since
Emmanuel Pirsch only presented the base of the project, with just an example how start a prog on the brick, we could try out our skills making this in an ECLIPSE project and making classes for some sensors.
It worked!

So what did we do?
added the exception classes.
I saw the timeout is used totally to search for devices before starting the program, so i reduced the time-out to 5 seconds, can be probably even less. (The C++ and the C# libs connect within a few seconds if all is well.)
Later on
Emmanuel Pirsch suggested the possibility to connect directly to the brick (see his comment) we tried his suggestion and this worked, reducing the waiting time to nearly nothing. (You have to do the search first to get the exact name, after that, you can use this name.)

I tested this projects and our sketchy classes with HSL homebrew for playing a sound on the brick, the lightsensor (Custom) and the I2C sensors(sonar, clock for the moment). It works!

The direct commands must be used without the starting code (which chooses between expecting a response or not, this is indicated in the method.)
Playing a sound uses command 0x03 freq freq duration duration 0x00
Reading a sensor: 0x07 port 0x00 and expecting back a byte[15] (so not the byte[18] of C++)


public void playSound(int freq, int duration)
{
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(6);

command.put((byte)0x03);//direct command play tone
command.put((byte)freq);
command.put((byte)(freq >>8));
command.put((byte)duration);
command.put((byte)(duration>>8));
command.put((byte)0x00);

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);
System.out.println(status.getStatus().toString());
}

public int getBattery()
{
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(1);

command.put((byte)0x0B);//direct command get battery

byte[] response = new byte[4]; //this 4 bytes are vital to get the following readings right

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, true, command, command.capacity(), response, response.length, status);
return response[0];
}


public int readSensor(int port)
{
//used for the lightsensor
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(3);

command.put((byte)0x07);//direct command read sensor
command.put((byte)port);
command.put((byte)0x00);

byte[] response = new byte[15];//expected back

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, true, command, command.capacity(), response, response.length, status);

//the values are not really what they should be at the moment???
int raw=((0xff & response[5]) | (response[6] << adc="((0xff" best="((0xff" calcu="((0xff">

while changing this in the connect method:
timeout 5 instead of 30.


Pointer iNXTIterator= fantom.nFANTOM100_createNXTIterator(true, 5, status);


Alternative in case you already have the name available:
In the Fantom
interface add:
Pointer nFANTOM100_createNXT(String resourceString, Status status, boolean checkFirmwareVersion);

Then in the NXT.java class:

private Pointer directConnect()throws UnableToCreateNXTException
{
Status status= new Status();
Pointer iNXT = fantom.nFANTOM100_createNXT("BTH::5brickXT::00:16:53:01:BA:74::8", status, false );

if (Status.Statuses.SUCCESS.equals(status.getStatus())) {
return iNXT;
} else {
throw new UnableToCreateNXTException( " not able to create connection" );
}
}



and adding the exceptions: (in the package treelaws.mindstorm)


package treelaws.mindstorm;

public class NXTNotFoundException extends Exception
{
public NXTNotFoundException(final String message) {
super(message);
}
}

package treelaws.mindstorm;

public class UnableToCreateNXTException extends Exception

{
public UnableToCreateNXTException(final String message) {
super( message );
}
}

then adding the throw in some methods, requiring this:


public NXT(String name) throws NXTNotFoundException, UnableToCreateNXTException
private Pointer connect(String name) throws NXTNotFoundException, UnableToCreateNXTException

and changing the exceptions ( in method connect) to include the message:


throw new UnableToCreateNXTException( " not able to create connection" );
throw new NXTNotFoundException( " no nxt found" );


The lejos in the brick still does not allow a new connection (after having had a connection) it has to be restarted.

Later on I tested the more complicated I2C sensors: the sonar, and the clock, all went well, here is the class:
package treelaws.mindstorm;

import java.nio.ByteBuffer;

import com.sun.jna.Pointer;

import treelaws.fantom.Fantom;
import treelaws.fantom.Status;

public class I2Csensor {

private static Fantom fantom = Fantom.INSTANCE;
private Pointer nxtPointer;
int myPort;
String sensorName;

public I2Csensor (int port, Pointer NXTPointer, String name )
{
myPort = port;
nxtPointer = NXTPointer;
sensorName = name;
setUpI2CSensor();
}

public int setUpI2CSensor()
{
//used for the distance, clock, compass, acceleration sensors
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(4);

command.put((byte)0x05);//setinputmode
command.put((byte)myPort);//port
command.put((byte)0x0B);//sensortype LOWSPEED_9V
command.put((byte)0x00);//sensormode

byte[] response = new byte[2];

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, true, command, command.capacity(), response, response.length, status);


return response[0]; //status
}

public int getStatusI2CSensor()
{
//used for the distance
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(2);

command.put((byte)0x0E);//setinputmode
command.put((byte)myPort);//port

byte[] response = new byte[1];

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, true, command, command.capacity(), response, response.length, status);
return response[0]; //number of bytes ready to read
}

public int readI2C()
{
int value = -999;
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(6);

command.put((byte)0x0F);//LSWRITE
command.put((byte)myPort); //port
command.put((byte)2);//lenght of bytes to send

if ( sensorName == "sonar" || sensorName == "compass") { //sonar and compass
command.put((byte)1);//length of bytes to receive sonar
command.put((byte)0x02);//sonar address sonar: 0x02
command.put((byte)0x42);//register to read sonar: 0x42
}
if ( sensorName == "clock" ) {
command.put((byte)1);//length of bytes to receive
command.put((byte)0xD0);//sonar address clock: 0x02
command.put((byte)0x00);//register to read clock seconds: 0x42
}

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);

int nb = 1; //number of bytes to read
if ( waitI2CSensor( nb) )
value = readBytesI2CSensor( nb) ;
return value;
}

p public int getStatusI2CSensor()
{
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(2);

command.put((byte)0x0E);//setinputmode
command.put((byte)myPort);//port

byte[] response = new byte[1];

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, true, command, command.capacity(), response, response.length, status);

return response[0]; //number of bytes ready to read
}

public int readI2C()
{
int value = -999;
//System.out.println( "--startread---" + port );
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(6);

command.put((byte)0x0F);//LSWRITE
command.put((byte)myPort); //port
command.put((byte)2);//lenght of bytes to send

if ( sensorName == "sonar" || sensorName == "compass") { //sonar and compass
command.put((byte)1);//length of bytes to receive sonar
command.put((byte)0x02);//sonar address sonar: 0x02
command.put((byte)0x42);//register to read sonar: 0x42
}
if ( sensorName == "clock" ) {
command.put((byte)1);//length of bytes to receive
command.put((byte)0xD0);//address clock: 0xD0
command.put((byte)0x00);//register to read clock seconds: 0x00 - seconds
}

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);

int nb = 1; //number of bytes to read
if ( waitI2CSensor( nb) )
value = readBytesI2CSensor( nb) ;
return value;
}

public boolean waitI2CSensor( int numberBytes)
{
int nB = getStatusI2CSensor();
while ( nB < nb =" getStatusI2CSensor();" status=" new" command=" ByteBuffer.allocate(4);" response =" new" sensorname ="=" a =" response[3]">>4) *6;//otherwise the number of seconds will be jumping
System.out.println("clock " + response[0] + " " + response[1] + " " + response[2] + " "+ response[3] + " " + response[4]+ " " + response[5]+ " " + response[6]);
return a;
}
if ( sensorName == "sonar" || sensorName == "compass") {

return 0xff & response[3]; } //value read, starting from 5
return -999;//no known sensor
}
}

Remark: using this constructor to make instances for the compass and the sonar sensor, using the above example, somehow these two sensors seem to "switch" values. hmmmmmmmmm...:-)


To make a map of the surroundings, we also need a motor class, a sketch using the tacholimit:
package treelaws.mindstorm;

import com.sun.jna.Pointer;
import java.nio.ByteBuffer;

import treelaws.fantom.*;
import treelaws.fantom.Status;

public class motor {

private static Fantom fantom = Fantom.INSTANCE;
private Pointer nxtPointer;
private int motorPort;

int MOTORON = 0x01;
int BRAKE = 0x02;
int REGULATED = 0x04;

int REGULATION_MODE_IDLE = 0x00;
int REGULATION_MODE_MOTOR_SPEED = 0x01;
int REGULATION_MODE_MOTOR_SYNC = 0x02;

int MOTOR_RUN_STATE_IDLE = 0x00;
int MOTOR_RUN_STATE_RAMPUP = 0x10;
int MOTOR_RUN_STATE_RUNNING = 0x20;
int MOTOR_RUN_STATE_RAMPDOWN = 0x40;

public motor (int port, Pointer NXTPointer)
{
motorPort = port;
nxtPointer = NXTPointer;
}

public void reset()
{
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(2);

command.put((byte)0x0A);//reset
command.put((byte)motorPort);//port

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);
System.out.println("reset" + " " + status.code);
}

public void speedMotor( int power, int turnratio)
{
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(13);

command.put((byte)0x04);//LSREAD
command.put((byte)motorPort);//port
command.put((byte)power);//power -100 - 100
command.put((byte)MOTORON );//mode
command.put((byte)REGULATION_MODE_MOTOR_SYNC );//regulation synchronized
command.put((byte)(128 - turnratio) );//turnratio
command.put((byte)REGULATION_MODE_MOTOR_SPEED );//runstate
command.put((byte)0x50 );//tacholimit = 0
command.put((byte)0x00 );//tacholimit = 0>> 8
command.put((byte)0x00 );//tacholimit = 0>> 16
command.put( (byte)0x00 );//tacholimit = 0>> 24
command.put( (byte)0x00 );//tacholimit = 0>> 32
command.put( (byte)0x00 );//closing

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);

}

public void stopMotor()
{
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(12);

command.put((byte)0x04);//LSREAD
command.put((byte)motorPort);//port
command.put((byte)0x00);//power -100 - 100
command.put( (byte)REGULATED );//mode
command.put( (byte)REGULATION_MODE_MOTOR_SYNC );//regulation
command.put( (byte)0x00 );//turnratio
command.put( (byte)MOTOR_RUN_STATE_IDLE );//runstate
command.put( (byte)0x00 );//tacholimit = 0
command.put( (byte)0x00 );//tacholimit = 0
command.put( (byte)0x00 );//tacholimit = 0
command.put( (byte)0x00 );//tacholimit = 0
command.put( (byte)0x00 );//tacholimit = 0

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);

}
}


With a bit of JAVA Graphic, i got this result, NXT scanning for objects, using compass and sonar, some strange effects have to be investigated,
some other effects are resulting from the sonar, which is not a straightforward linear razorsharp distance device!














Connection setup XP or VISTA (tested for both)
Needed:
  • installing NXT Mindstorms CD (this installs the fantom lib)
  • bluetooth dongle
make a project, install all the classes given in the referenced blog of Emmanuel Pirsch, (include your own classes or use the classes sketched here)
You have to install a comport:
In bluetooth manager Search for device
Wizard, manual key 1234
ready with wizard
first use connect method in the JAVA project with search for device
copy name of brick BTH::name of device::Mac-address::comport
then you can switch to directConnect

Troubleshooting:
After having worked with a fantom lib project yesterday, today it won't connect!
I had to redo the COMPORT settings. Desinstall existing COMPORTS, do a search, give the preconfigured key, check the ports again.
Possible cause: the Brick was running from my laptop on XP, and Michel tested it from VISTA. This might have reconfigured the brick???

Big problem with the fantom lib or this kind of interface: writing commands does not seem to be working.
(see solution...:-( )
LSWRITE is ok to check registers, reading values etc, but LSWRITE to the command register with a command has no effect. Testing for several I2C sensors, compaas, camera, and clock. The clock has a RAM, which can be written to using C++ (my own classes in the Anders lib).
This is the code:

public void sendCommand(byte cmd)
{
System.out.println(" sendCommand " + cmd);
Status status= new Status();
ByteBuffer command= ByteBuffer.allocate(7);

command.put((byte)0x0F);//LSWRITE
command.put((byte)myPort); //port
command.put((byte)3);//length of bytes to send

command.put((byte)0);//length of bytes to receive camera
command.put((byte)0x02);//camera address camera: 0x02
command.put((byte)0x41);//command address
command.put((byte)cmd);//command byte to send

fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);
System.out.println( "sendcommand back: " + nb + " char= " + cmd + " " + myPort ) ;
pause(100); //should check I2C status
}

This means that reading values from the usual
I2C sensors is possible (Compass, Clock, Tilt for instance) but no changes can be made, nor the camera can be put in the following objects mode.

Solution: the bluetooth connection is on a master- slave basis which means that writing registers is not possible. So reading from sensors is ok, but starting the camera, or setting the time in the clock sensor is not possible.



reference:
http://nxtasy.org/2006/12/23/fantomtalk/
http://mindstorms.lego.com/Overview/NXTreme.aspx

0 comments: