package lejos.addon.gps;
import java.util.*;
import lejos.nxt.Button;
/**
* Class designed to manage all NMEA Sentence.
*
* GGA and RMC Sentence needs to validate data.
* This class has methods to validate received data
*
* @author BB
*/
abstract public class NMEASentence {
static byte checksum;
protected String nmeaSentence = null;
protected StringTokenizer st; // TODO This isn't a great location for this. Only used in parse() of subclasses.
private long timeStamp = -1;
/* GETTERS & SETTERS */
/**
* Retrieve the header constant for this sentence.
* @return The NMEA header string ($GPGGA, $GPVTG, etc...)
*/
// TODO: Maybe getSentenceType()?
// TODO: Should it return the $ too, or maybe have a list of constants?
abstract public String getHeader();
/**
* Set a new nmea sentence into the object
*
* @param sentence
*/
public void setSentence(String sentence){
this.timeStamp = System.currentTimeMillis();
nmeaSentence = sentence;
}
/**
* This method returns the system time at which the data for the NMEA Sentence
* was collected. It uses System.currentTimeMillis() to create the time stamp.
* Note: It might seem strange not to use the satellite time to time-stamp the
* data, but in fact the javax.microedition.location API calls for the system time.
* @return system time when the data was collected
*/
public long getTimeStamp() {
// TODO leJOS returns an int internally, so this is limited to about 24 days worth
// of continuous operation. If a program runs longer than this the timestamp is invalid.
return timeStamp;
}
/**
* This method is called by all the getter methods. It checks if a new sentence has
* been received since the last call.
* It sets nmeaSentence to null to act as flag for when method called again.
*/
protected synchronized void checkRefresh() {
if(nmeaSentence != null) {
// First need to cut off verification code at end of sentence:
int end = nmeaSentence.indexOf('*');
if(end < 0) end = nmeaSentence.length();
String nmeaSub = nmeaSentence.substring(0, end);
parse(nmeaSub);
nmeaSentence = null; // Once data is parsed, discard string (used as flag)
}
}
/**
* Abstract method to parse out all relevant data from the nmeaSentence.
*/
abstract protected void parse(String sentence);
/* CHECKSUM METHODS */
/**
* Return if your NMEA Sentence is valid or not
*
* @param sentence the NMEA sentence
* @return true iff the NMEA Sentence is true
*/
public static boolean isValid(String sentence){
int end = sentence.indexOf('*');
String checksumStr = sentence.substring(end + 1, end + 3);
byte checksumByte = convertChecksum(checksumStr);
return(checksumByte == calcChecksum(sentence));
}
/**
* Method designed to calculate a checksum using data
*
* @return
*/
private static byte calcChecksum(String sentence) {
int start = sentence.indexOf('$');
int end = sentence.indexOf('*');
if(end < 0) {
end = sentence.length();
}
byte checksum = (byte) sentence.charAt(start + 1);
for (int index = start + 2; index < end; ++index) {
checksum ^= (byte) sentence.charAt(index);
}
return checksum;
}
/**
* Method used to create a checksum with String
*
* @param checksum_string
* @return
*/
private static byte convertChecksum(String checksum_string) {
byte checksum;
checksum = (byte)((hexCharToByte(checksum_string.charAt(0)) & 0xF ) << 4 );
checksum = (byte)(checksum | hexCharToByte(checksum_string.charAt(1)) & 0xF );
return checksum;
}
/**
* NOTE: This functionality can be replaced by Byte.parseByte()
* if we ever make a Byte class.
* @param hex_char
* @return
*/
// TODO: I think we can use Integer.parseInt() and use optional 16 base conversion.
private static byte hexCharToByte(char hex_char) {
if( hex_char > 57 )
return((byte)(hex_char - 55));
else
return((byte)(hex_char - 48));
}
/* UTILITIES METHODS */
/**
* Any GPS Receiver gives Lat/Lon data in the following way:
*
* http://www.gpsinformation.org/dale/nmea.htm
* http://www.teletype.com/pages/support/Documentation/RMC_log_info.htm
*
* 4807.038,N Latitude 48 deg 07.038' N
* 01131.000,E Longitude 11 deg 31.000' E
*
* This data is necessary to convert to Decimal Degrees.
*
* Latitude values has the range: -90 <-> 90
* Longitude values has the range: -180 <-> 180
*
* @param dd_mm the day and month
* @return the decimal degrees
*/
// TODO: Might as well make static and public
protected double degreesMinToDegrees(String dd_mm) {
int dotPosition = dd_mm.indexOf('.');
double degrees = Double.parseDouble(dd_mm.substring(0, dotPosition - 2));
double minutes = Double.parseDouble(dd_mm.substring(dotPosition - 2));
return (degrees + (minutes / 60.0));
}
}