package lejos.addon.gps;
import java.io.*;
import java.util.*;
import lejos.nxt.Button;
/**
* This class manages data received from a GPS Device.
* SimpleGPS Class manages the following NMEA Sentences
* which supply location, heading, and speed data:
*
* <li>GPGGA (location data)
* <li>GPVTG (heading and speed data)
* <li>GPGSA (accuracy information)
*
* <p>This class is primarily for use by the javax.microedition.location package. The preferred
* class to use for obtaining GPS data is the GPS class.</p>
*
* @author BB
*/
public class SimpleGPS extends Thread {
/**
* BUFF is the amount of bytes to read from the stream at once.
* It should not be longer than the number of characters in the shortest
* NMEA sentence otherwise it might cause a bug.
*/
private final int BUFF = 20;
private byte [] segment = new byte[BUFF];
private StringBuffer currentSentence = new StringBuffer();
private String START_CHAR = "$";
private InputStream in;
public int errors = 0; // TODO: DELETE ME. Testing purposes only.
//Classes which manages GGA, VTG, GSA Sentences
protected GGASentence ggaSentence;
protected VTGSentence vtgSentence;
private GSASentence gsaSentence;
//Data
private StringTokenizer tokenizer;
// Security
private boolean shutdown = false;
// Listener-notifier
static protected Vector listeners = new Vector();
/**
* The constructor. It needs an InputStream
*
* @param in An input stream from the GPS receiver
*/
public SimpleGPS(InputStream in) {
this.in = in;
ggaSentence = new GGASentence();
vtgSentence = new VTGSentence();
gsaSentence = new GSASentence();
// Juan: Don't comment out the next line.
// This should be a daemon thread so VM exits when user program terminates.
this.setDaemon(true); // Must be set before thread starts
this.start();
}
/* GETTERS & SETTERS */
/**
* Get Latitude
*
* @return the latitude
*/
public double getLatitude() {
return ggaSentence.getLatitude();
}
/**
* Get Latitude Direction
*
* @return the latitude direction
*/
public char getLatitudeDirection(){
return ggaSentence.getLatitudeDirection();
}
/**
* Get Longitude
*
* @return the longitude
*/
public double getLongitude() {
return ggaSentence.getLongitude();
}
/**
* Get Longitude Direction
*
* @return the longitude direction
*/
public char getLongitudeDirection(){
return ggaSentence.getLongitudeDirection();
}
/**
* The altitude above mean sea level
*
* @return Meters above sea level e.g. 545.4
*/
public float getAltitude(){
return ggaSentence.getAltitude();
}
/**
* Returns the number of satellites being tracked to
* determine the coordinates.
* @return Number of satellites e.g. 8
*/
public int getSatellitesTracked(){
return ggaSentence.getSatellitesTracked();
}
/**
* Fix quality:
* <li>0 = invalid
* <li>1 = GPS fix (SPS)
* <li>2 = DGPS fix
* <li>3 = PPS fix
* <li>4 = Real Time Kinematic
* <li>5 = Float RTK
* <li>6 = estimated (dead reckoning) (2.3 feature)
* <li>7 = Manual input mode
* <li>8 = Simulation mode
*
* @return the fix quality
*/
public int getFixMode(){
return ggaSentence.getFixQuality();
}
/**
* Get the last time stamp from the satellite for GGA sentence.
*
* @return Time as a UTC integer. 123459 = 12:34:59 UTC
*/
public int getTimeStamp() {
return ggaSentence.getTime();
}
/**
* Get speed in kilometers per hour
*
* @return the speed in kilometers per hour
*/
public float getSpeed() {
return vtgSentence.getSpeed();
}
/**
* Get the course heading of the GPS unit.
* @return course (0.0 to 360.0)
*/
public float getCourse() {
return vtgSentence.getTrueCourse();
}
/**
* Selection type of 2D or 3D fix
* <li> 'M' = manual
* <li> 'A' = automatic
* @return selection type - either 'A' or 'M'
*/
public String getSelectionType(){
return gsaSentence.getMode();
}
/**
* 3D fix - values include:
* <li>1 = no fix
* <li>2 = 2D fix
* <li>3 = 3D fix
*
* @return fix type (1 to 3)
*/
public int getFixType(){
return gsaSentence.getModeValue();
}
/**
* Get an Array of Pseudo-Random Noise codes (PRN). You can look up a list of GPS satellites by
* this number at: http://en.wikipedia.org/wiki/List_of_GPS_satellite_launches
* Note: This number might be similar or identical to SVN.
*
* @return array of PRNs
*/
public int[] getPRN(){
return gsaSentence.getPRN();
}
/**
* Get the 3D Position Dilution of Precision (PDOP). When visible GPS satellites are close
* together in the sky, the geometry is said to be weak and the DOP value is high; when far
* apart, the geometry is strong and the DOP value is low. Thus a low DOP value represents
* a better GPS positional accuracy due to the wider angular separation between the
* satellites used to calculate a GPS unit's position. Other factors that can increase
* the effective DOP are obstructions such as nearby mountains or buildings.
*
* @return The PDOP (PDOP * 6 meters = the error to expect in meters) -1 means PDOP is unavailable from the GPS.
*/
public float getPDOP(){
return gsaSentence.getPDOP();
}
/**
* Get the Horizontal Dilution of Precision (HDOP). When visible GPS satellites are close
* together in the sky, the geometry is said to be weak and the DOP value is high; when far
* apart, the geometry is strong and the DOP value is low. Thus a low DOP value represents
* a better GPS positional accuracy due to the wider angular separation between the
* satellites used to calculate a GPS unit's position. Other factors that can increase
* the effective DOP are obstructions such as nearby mountains or buildings.
*
* @return the HDOP (HDOP * 6 meters = the error to expect in meters) -1 means HDOP is unavailable from the GPS.
*/
public float getHDOP(){
return gsaSentence.getHDOP();
}
/**
* Get the Vertical Dilution of Precision (VDOP). When visible GPS satellites are close
* together in the sky, the geometry is said to be weak and the DOP value is high; when far
* apart, the geometry is strong and the DOP value is low. Thus a low DOP value represents
* a better GPS positional accuracy due to the wider angular separation between the
* satellites used to calculate a GPS unit's position. Other factors that can increase
* the effective DOP are obstructions such as nearby mountains or buildings.
*
* @return the VDOP (VDOP * 6 meters = the error to expect in meters) -1 means VDOP is unavailable from the GPS.
*/
public float getVDOP(){
return gsaSentence.getVDOP();
}
/**
* Method used to close connection. There is no real need to call this method.
* Included in case programmer wants absolutely clean exit.
*/
public void close() throws IOException {
this.shutdown = true;
in.close();
}
/**
* Keeps reading sentences from GPS receiver stream and extracting data.
* This is a daemon thread so when program ends it won't keep running.
*/
public void run() {
String token;
String s;
while(!shutdown) {
s = getNextString();
// TODO: This shouldn't be necessary. getNextString() runs through the Checksum:
// Check if sentence is valid:
if(s.indexOf('*') < 0) {
continue;
}
if(s.indexOf('$') < 0) {
continue;
}
//2008/07/28
//Debug checksum validation
//Class 19: java.lang.StringIndexOutOfBoundsException
// TODO: I suspect we don't need this try-catch block anymore.
try{
if(NMEASentence.isValid(s)){
tokenizer = new StringTokenizer(s);
token = tokenizer.nextToken();
// Choose which type of sentence to parse:
sentenceChooser(token, s); // Method to make subclass more efficient - no redundant code.
}
}catch(StringIndexOutOfBoundsException e){
System.err.println("SimpleGPS.run() error. StringIndexOutOfBounds");
}catch(ArrayIndexOutOfBoundsException e2){
//Jab
//Bug detected: 06/08/2008
System.err.println("SimpleGPS.run() error. ArrayIndexOutOfBounds");
}
//2008/07/18
//Increase the list with more NMEA Sentences
}
}
/**
* Internal helper method to aid in the subclass architecture. Overwritten by subclass.
* @param header
* @param s
*/
protected void sentenceChooser(String header, String s) {
if (header.equals(GGASentence.HEADER)){
this.ggaSentence.setSentence(s);
notifyListeners(this.ggaSentence);
}else if (header.equals(VTGSentence.HEADER)){
this.vtgSentence.setSentence(s);
notifyListeners(this.vtgSentence);
}else if (header.equals(GSASentence.HEADER)){
gsaSentence.setSentence(s);
notifyListeners(this.gsaSentence);
}
}
static protected void notifyListeners(NMEASentence sen){
/* TODO: Problem is ggaSentence is a reused object in this API.
* Should really pass a copy of the NMEASentence to notify (and the copy
* must have all the appropriate GGA data, not just NMEA). However, check
* if there are any listeners before making unnecessary copy. */
for(int i=0; i<listeners.size();i++){
GPSListener gpsl = (GPSListener)listeners.elementAt(i);
gpsl.sentenceReceived(sen);
}
}
/**
* Pulls the next NMEA sentence as a string
* @return NMEA string, including $ and end checksum
*/
private String getNextString() {
boolean done = false;
String sentence = "";
int endIndex = 0;
do{
// Read in buf length of sentence
try {
// TODO: Does in.read() pause the thread or does this eat up unnecessary
// CPU cycles? Maybe add a Thread.sleep here that cuts out CPU waste.
// This in.read() method reads in BUFF length of bytes every time.
in.read(segment);
}catch (IOException e) {
// TODO: How to handle error?
}catch(Exception e){
// TODO: ??
}
// Append char[] data into currentSentence
for(int i=0;i<BUFF;i++)
currentSentence.append((char)segment[i]);
// Search for $ symbol (indicates start of new sentence)
if(currentSentence.indexOf(START_CHAR, 1) >= 0) {
done = true;
}
// TODO: Probably better to throw exception here if GPS disconnects
//In case user turns off GPS Device / GPS Device has low batteries / Other disconnect scenarios
// There is also the listener of LocationProvider.
if(currentSentence.length() >= 500){
errors++;
//2008/09/06 : JAB
//Reset
//currentSentence = new StringBuffer();
//segment = new byte[BUFF];
System.err.println("Bug in SimpleGPS.getNextString() detected. > 500");
System.err.println("Sentence: " + currentSentence.toString());
//If detect a problem with InputStream
//System detect the event and notify the problem with the
//Enabling the flag internalError
return null;
}
}while(!done);
try{
endIndex = currentSentence.indexOf(START_CHAR, 1);
sentence = currentSentence.substring(0, endIndex);
// Crop print current sentence
currentSentence.delete(0, endIndex);
}catch(Exception e){
// TODO: Why catch a runtime exception here?
System.err.println("Exception in SimpleGPS.getNextString() " + e.getMessage());
}
return sentence;
}
/* EVENTS*/
/**
* add a listener to manage events with GPS
*
* @param listener
*/
static public void addListener (GPSListener listener){
listeners.addElement(listener);
}
/**
* Remove a listener
*
* @param listener
*/
static public void removeListener (GPSListener listener)
{
listeners.removeElement(listener);
}
}