package org.syzygy.gps;
import org.syzygy.util.WrappedException;
import org.syzygy.util.midp.StreamUtil;
import org.syzygy.util.midp.StringUtil;
import javax.microedition.io.StreamConnection;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
/**
* An NmeaSource is a LocationSource which parses NMEA Sentences,
* see, e.g., http://www.gpsinformation.org/dale/nmea.htm
* <p/>
* This class was written by Stephen Crane (jscrane@gmail.com)
* and is released under the terms of the GNU GPL
* (http://www.gnu.org/licenses/gpl.html).
*/
public final class NmeaSource extends LocationSource
{
private interface SentenceProcessor
{
void process(String[] data, int last, Location location);
}
public NmeaSource(StreamConnection conn, final int channels)
{
this.conn = conn;
processors.put("GGA", new SentenceProcessor()
{
public void process(String[] data, int last, Location l)
{
l.setGpsTime(data[0]);
l.setLatitude(decimalize(data[1], "S".equals(data[2])));
l.setLongitude(decimalize(data[3], "W".equals(data[4])));
l.setIsError("0".equals(data[5]));
l.setSatellites(readInt(data[6]));
l.setAltitude(readDouble(data[8]));
notifyLocation(l);
}
});
processors.put("GLL", new SentenceProcessor()
{
public void process(String[] data, int last, Location l)
{
l.setLatitude(decimalize(data[0], "S".equals(data[1])));
l.setLongitude(decimalize(data[2], "W".equals(data[3])));
l.setGpsTime(data[4]);
l.setIsError("V".equals(data[5]));
notifyLocation(l);
}
});
processors.put("RMC", new SentenceProcessor()
{
public void process(String[] data, int last, Location l)
{
l.setGpsTime(data[0]);
l.setIsError("V".equals(data[1]));
l.setLatitude(decimalize(data[2], "S".equals(data[3])));
l.setLongitude(decimalize(data[4], "W".equals(data[5])));
l.setSpeed(readDouble(data[6]));
l.setCourse(readDouble(data[7]));
l.setUtcDate(data[8]);
notifyLocation(l);
}
});
processors.put("GSV", new SentenceProcessor()
{
public void process(String[] data, int last, Location l)
{
int n = Integer.parseInt(data[0]);
int seq = Integer.parseInt(data[1]);
int idx = seq == 1 ? 0 : index;
for (int i = 6; i < last && idx < channels; i += 4, idx++)
signals[idx] = readInt(data[i]);
index = idx;
if (seq == n) {
notifySignals(signals);
for (int i = 0; i < channels; i++)
signals[i] = 0;
}
}
private int index = 0;
private final int[] signals = new int[channels];
});
}
static double readDouble(String s)
{
return StringUtil.isEmpty(s) ? 0.0 : Double.valueOf(s).doubleValue();
}
static int readInt(String s)
{
return StringUtil.isEmpty(s) ? 0 : Integer.valueOf(s).intValue();
}
static double decimalize(String s, boolean isNegative)
{
double d = Util.decimalize(readDouble(s));
return isNegative ? -d : d;
}
static String unDecimalize(double n, boolean isLongitude)
{
if (n < 0)
n = -n;
String s = Double.toString(Util.undecimalize(n));
if (isLongitude && n < 100)
s = "0" + s;
return s.substring(0, 9);
}
/**
* Returns a waypoint string for sending to the device.
* Note: this does <i>not</i> include the initial "$" or the
* terminating <cr><lf>.
* Note: If the name parameter is longer than 46 characters
* it will be truncated to keep the total length == 80.
*
* @param lat the waypoint's latitude
* @param lon the waypoint's longitude
* @param name the waypoint's name
* @return the waypoint string
*/
static String waypoint(double lat, double lon, String name)
{
String ns = "N", ew = "E";
if (lon < 0) {
ns = "S";
lon = -lon;
}
if (lat < 0) {
ew = "W";
lat = -lat;
}
String s = "GPWPL," + unDecimalize(lat, false) + "," +
ns + "," + unDecimalize(lon, true) + "," + ew + "," + name;
if (s.length() > 77)
s = s.substring(0, 76);
return s + "*" + Integer.toHexString(checksum(s, 0, s.length())).toUpperCase();
}
boolean parseSentence(String line, Location location)
{
int len = line.length();
if (len < 9 || len > 80 || line.indexOf('*') == -1)
return false;
String op = line.substring(2, 5);
SentenceProcessor sp = (SentenceProcessor) processors.get(op);
if (sp == null) {
System.err.println("unprocessed: " + op);
return false;
}
String s = line.substring(6);
int i;
for (i = 0; i < data.length; i++) {
int p = s.indexOf(',');
if (p == -1)
p = s.indexOf('*');
if (p == -1)
break;
data[i] = s.substring(0, p);
s = s.substring(p + 1);
}
if (i == data.length || StringUtil.isEmpty(s))
return false;
int check = Integer.parseInt(s, 16);
int sum = checksum(line, 0, len - 3);
System.err.println("checksum=" + Integer.toHexString(check) +
" computed=" + Integer.toHexString(sum));
if (check == sum) {
sp.process(data, i, location);
return true;
}
return false;
}
static int checksum(String s, int start, int end)
{
System.err.println(s.substring(start, end));
int sum = 0;
for (int i = start; i < end; i++)
sum ^= s.charAt(i);
return sum;
}
String readSentence(InputStream input)
throws IOException
{
buf.setLength(0);
for (; ;) {
int ch = input.read();
if (ch == 0x0d)
break;
if (ch == '$')
return readSentence(input);
buf.append((char) ch);
}
input.read(); // skip \n
return buf.toString();
}
public void run()
{
Location location = new Location();
String sentence = null;
try {
synchronized (this) {
input = conn.openInputStream();
}
do {
while (input.read() != '$') ;
sentence = readSentence(input);
System.err.println(sentence);
parseSentence(sentence, location);
} while (!isStopped());
notifyLocation(null);
} catch (Exception e) {
notifyError(new WrappedException(e, sentence));
} finally {
StreamUtil.safeClose(input);
StreamUtil.safeClose(conn);
}
}
private final Hashtable processors = new Hashtable();
private final StreamConnection conn;
private final String[] data = new String[40];
private final StringBuffer buf = new StringBuffer();
private InputStream input = null;
}