package javax.microedition.location;
import java.io.*;
import javax.bluetooth.*;
import javax.microedition.io.*;
import lejos.addon.gps.*;
/**
* This class is not visible to users and should not be instantiated directly. Instead it
* is retrieved from the factory method LocationProvider.getInstance().
* @author BB
*
*/
class BTGPSLocationProvider extends LocationProvider implements DiscoveryListener, GPSListener {
SimpleGPS gps = null;
DiscoveryAgent da;
RemoteDevice btDevice = null;
// LocationListener variables:
private Thread listyThread = null;
private boolean listenerRunning = true;
private GPSListener gpsl = null; // JSR-179 only allows one LocationListener at a time.
/**
* doneInq is used to ensure the code doesn't try to connect to the GPS device
* before the Bluecore chip is done the inquiry. If you try to connect before the inquiry is
* done it will cause a malfunction. This is due to our Bluecove code in leJOS, which requires
* the programmer to be very careful.
*/
boolean doneInq = false;
// I think this indicates the BT device is a GPS unit: TODO: Wrong. Keyboard has same signature.
private static final int GPS_MAJOR = 0x1F00;
protected BTGPSLocationProvider() throws LocationException {
// TODO: Move this to searchConnect method?
// TODO: The problem here is that it searches every time. Slow. Need to try Properties?
// TODO: BIG ONE: Should only connect to GPS that isPaired() (from menu). Will
// allow some degree of control over which GPS is connects to in classroom.
try {
da = LocalDevice.getLocalDevice().getDiscoveryAgent();
da.startInquiry(DiscoveryAgent.GIAC, this);
} catch (BluetoothStateException e) {
throw new LocationException(e.getMessage());
}
while(!doneInq) {Thread.yield();}
if(btDevice == null) throw new LocationException("No device found");
String address = btDevice.getBluetoothAddress();
String btaddy = "btspp://" + address;
try {
StreamConnectionNotifier scn = (StreamConnectionNotifier)Connector.open(btaddy);
if(scn == null) throw new LocationException("Bad BT address");
StreamConnection c = scn.acceptAndOpen();
/* This problem below occurred one time for my Holux GPS. The solution was to
* remove the device from the Bluetooth menu, then find and pair again.
*/
if(c == null)throw new LocationException("Failed. Try pairing at menu again");
InputStream in = c.openInputStream();
if(in != null) {
gps = new SimpleGPS(in);
// c.close(); // TODO: Clean up when done. HOW TO HANDLE IN LOCATION?
}
} catch(IOException e) {
throw new LocationException(e.getMessage());
}
// Add itself to SimpleGPS as listener
SimpleGPS.addListener(this);
}
public Location getLocation(int timeout) throws LocationException,
InterruptedException {
/* TODO The timeout might play to the fact that it is still acquiring satellites?
* I was wondering about that before. Maybe it makes sense to have timeout in SimpleGPS?
* TODO: Solution! Keep asking for altitude until is positive? (longitude can be negative)
* Or perhaps just until speed positive? (set those after)
* TODO: -1 in timeout is supposed to represent the default timeout (GPSListener?)
* TODO: I don't know if this is supposed to wait for the GPS to provide a new
* coordinate data or if it is okay to pass the latest cached GPS coordinates.
* Is the purpose of the timeout that it gets a new updated location that
* is not the previously returned or cached one?
*/
if(timeout == 0)
throw new IllegalArgumentException("timeout cannot equal 0");
// Timeout results in LocationException:
long startTime = System.currentTimeMillis();
// TODO: Perhaps initialize and test for NaN instead.
while(gps.getLatitude() == 0 & gps.getLongitude() == 0) {
if(timeout != -1 & System.currentTimeMillis() - startTime > (timeout * 1000))
throw new LocationException("GPS timed out");
Thread.sleep(100); /* NOTE: This might very occasionally cause an error because
* Thread.yield() seems to cause sentence parsing to start too soon.
* (try changing sleep() to yield() to see what happens)
* Perhaps something needs to be synchronized? */
}
QualifiedCoordinates qc = new QualifiedCoordinates(gps.getLatitude(), gps.getLongitude(), gps.getAltitude(), (gps.getHDOP() * 6), (gps.getVDOP() * 6));
Location loc = new Location(qc, gps.getSpeed(), gps.getCourse(), gps.getTimeStamp(),
0,null); // TODO: Implement location method and extraInfo (0 and null for now)
return loc;
}
public int getState() {
// TODO Auto-generated method stub
return 0;
}
public void reset() {
// TODO Auto-generated method stub
}
public void setLocationListener(LocationListener listener, int interval,
int timeout, int maxAge) {
// * Stop all previous listener threads *
listenerRunning = false;
if(listyThread != null) {
while(listyThread.isAlive()) {Thread.yield();} // End old thread
listyThread = null; // Discard the listener thread instance
}
// * Remove any listeners from GPSListener *
if (listener == null) {
// Remove current listener from SimpleGPS
SimpleGPS.removeListener(gpsl);
gpsl = null;
return; // No listener provided, so return now so it dosn't make a new one
}
// * Inner classes need final variables *
final int to = timeout;
final LocationListener l = listener;
final LocationProvider lp = this;
final int delay = interval * 1000; // Oddly interval is in seconds, and not float
// Make new thread here and start it if interval > 0, else if -1
// then use the GPSListener interface.
if (interval > 0) { // Notify according to interval by user
listyThread = new Thread() {
public void run() {
while(listenerRunning) {
try {
// TODO: Probably only notify if location changed? Need to compare to old.
// TODO: Make helper method since this is used below too.
l.locationUpdated(lp, lp.getLocation(to));
Thread.sleep(delay);
} catch (LocationException e) {
// TODO Auto-generated catch block
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
}
}
};
listyThread.setDaemon(true); // so JVM exits if thread is still running
listenerRunning = true;
listyThread.start();
} else if(interval < 0) { // If interval is -1, use default update interval
// In our case, update as soon as new coordinates are available from GPS (via GPSListener)
// TODO: Alternate method: Use GPSListener for ProximityListener and this.
gpsl = new GPSListener() {
public void sentenceReceived(NMEASentence sen) {
// Check if GGASentence. Means that new location info is ready
if(sen.getHeader().equals(GGASentence.HEADER)) {
try {
// TODO: Probably only notify if location changed? Need to compare to old.
l.locationUpdated(lp, lp.getLocation(to));
} catch (LocationException e) {
// TODO Auto-generated catch block
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
}
}
};
SimpleGPS.addListener(gpsl);
}
// TODO: Need to implement LocationListener.providerStateChanged()
}
/**
* This method is from GPSListener, used to notify the ProximityListener.
*/
public void sentenceReceived(NMEASentence sen) {
// Check if this sentence has location info
if(sen.getHeader().equals(GGASentence.HEADER)) {
Coordinates cur = null;
Location loc = null;
try {
loc = this.getLocation(-1);
cur = loc.getQualifiedCoordinates();
} catch(InterruptedException e) {
// TODO: This method should bail properly if it fails.
System.err.println("Fail 1");
} catch (LocationException e) {
// TODO: This method should bail properly if it fails.
System.err.println("Fail 2");
}
for(int i=0; i<listeners.size();i++){
Object [] array = (Object [])listeners.elementAt(i);
ProximityListener pl = (ProximityListener)array[0];
Coordinates to = (Coordinates)array[1];
Float rad = (Float)array[2];
// Now check radius against coordinate and notify listener.
if(cur.distance(to) <= rad.floatValue()) {
// Remove this ProximityListener because it should be notified only once.
// I prefer to do this BEFORE notifying the pl because the user might try
// to re-add the pl in proximityEvent().
LocationProvider.removeProximityListener(pl);
pl.proximityEvent(to, loc);
}
}
// TODO: Handle LocationListeners here instead of inner?
}
}
/* DiscoveryListener methods: */
public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
//System.err.println(btDevice.getFriendlyName(false) + " discovered.");
/*
System.err.println("Major = " + cod.getMajorDeviceClass());
System.err.println("Minor = " + cod.getMinorDeviceClass());
System.err.println("Service = " + cod.getServiceClasses());
System.err.println("GPS_MAJOR = " + GPS_MAJOR);
System.err.println("Authenticated? " + btDevice.isAuthenticated());
*/
if((cod.getMajorDeviceClass() & GPS_MAJOR) == GPS_MAJOR) {
if(btDevice.isAuthenticated()) { // Check if paired.
this.btDevice = btDevice;
da.cancelInquiry(this);
}
}
}
public void inquiryCompleted(int discType) {
doneInq = true;
}
}