/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Author: Damian Waradzyn
*/
package com.mapmidlet.gps;
import java.io.*;
import java.util.*;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import com.mapmidlet.CloudGps;
import com.mapmidlet.misc.*;
import com.mapmidlet.options.Options;
import com.mapmidlet.projection.WorldCoordinate;
/**
* This class is responsible for maintaining connection with GPS device or NMEA
* log file. In a separate thread it read and parses NMEA sentences writing
* results to GpsState.
*
* @author Damian Waradzyn
*/
public class GpsConnection {
private InputStream inputStream = null;
private final GpsState gpsState;
private boolean exit;
private DataOutputStream logOutput = null;
public GpsConnection(GpsState gpsState) {
this.gpsState = gpsState;
}
public void open() throws IOException {
thread = new Thread(new Runnable() {
public void run() {
exit = false;
Options options = Options.getInstance();
int tries = 0;
while (inputStream == null) {
try {
if (options.replayMode) {
inputStream = Connector.openInputStream(options.replayFileName);
FileConnection conn = (FileConnection) Connector.open(options.replayFileName,
Connector.READ);
gpsState.replySize = conn.fileSize();
gpsState.replyPosition = 0;
conn.close();
} else {
inputStream = Connector.openInputStream(options.gpsUrl);
}
} catch (Throwable e) {
tries++;
CloudGps.setError(e);
e.printStackTrace();
if (tries >= 3) {
gpsState.state = GpsState.CONNECTION_ERROR;
throw new RuntimeException(e.getClass().getName() + ": " + e.getMessage());
}
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
if (options.replayMode) {
logOutput = null;
} else {
IOTool.ensureDirExist(IOTool.NMEA_LOGS_DIR);
logOutput = IOTool.createLogFile();
}
gpsState.state = GpsState.CONNECTED_NO_FIX;
String line;
int length, idx1;
byte[] buf;
byte[] buff = new byte[512];
StringBuffer tmp = new StringBuffer(256);
while (!exit) {
try {
if (inputStream == null) {
return;
}
if (inputStream.available() > 0 || options.replayMode) {
length = inputStream.read(buff, 0, buff.length);
if (length < 0) {
close();
if (options.replayMode) {
options.replayMode = false;
}
return;
}
if (options.replayMode) {
gpsState.replyPosition += length;
}
if (length < buff.length) {
buf = new byte[length];
System.arraycopy(buff, 0, buf, 0, length);
tmp.append(new String(buf));
} else {
tmp.append(new String(buff));
}
idx1 = tmp.toString().indexOf("\r\n");
while (idx1 >= 0) {
line = tmp.toString().substring(0, idx1);
try {
if (logOutput != null) {
logOutput.write(line.getBytes());
logOutput.write("\r\n".getBytes());
}
parseNmea(line);
if (options.replayMode && line.length() > 6 && "GPRMC".equals(line.substring(1, 6))) {
try {
Thread.sleep(1000 / options.replaySpeed);
} catch (InterruptedException ie) {
}
}
} catch (Throwable e) {
e.printStackTrace();
CloudGps.setError(e);
}
tmp = new StringBuffer(tmp.toString().substring(idx1 + 2));
idx1 = tmp.toString().indexOf("\r\n");
}
} else {
Thread.yield();
}
} catch (Throwable e) {
CloudGps.setError(e);
}
}
}
});
thread.start();
}
public void close() {
gpsState.state = GpsState.CONNECTING;
if (thread != null) {
exit = true;
if (!Thread.currentThread().equals(thread)) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
thread = null;
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
CloudGps.setError(e);
}
}
inputStream = null;
if (logOutput != null) {
try {
logOutput.close();
} catch (IOException e) {
CloudGps.setError(e);
}
logOutput = null;
}
}
public InputStream getInputStream() {
return inputStream;
}
/**
* Parsing NMEA - see http://home.mira.net/~gnb/gps/nmea.html
*
* @param nmea
*/
public void parseNmea(String nmea) {
if (Options.getInstance().debugMode) {
System.out.println(nmea);
}
if (nmea.length() < 9 || !nmea.startsWith("$")) {
System.out.println("invalid nmea sentence: '" + nmea + "'");
return;
}
String msgType = nmea.substring(1, 6);
if ("GPRMC".equals(msgType)) {
parseRmc(nmea);
} else if ("GPVTG".equals(msgType)) {
parseVtg(nmea);
} else if ("GPGSA".equals(msgType)) {
parseGsa(nmea);
} else if ("GPGSV".equals(msgType)) {
parseGsv(nmea);
} else if ("GPGGA".equals(msgType)) {
parseGga(nmea);
}
}
private void parseVtg(String nmea) {
String[] strings = StringTool.tokenize(nmea, ',');
if ("".equals(strings[7])) {
gpsState.state = GpsState.CONNECTED_NO_FIX;
} else {
if (!"".equals(strings[1]) && !"nan".equals(strings[1])) {
gpsState.compass = Double.parseDouble(strings[1]);
} else {
gpsState.compass = -1;
}
gpsState.groundSpeed = Double.parseDouble(strings[7]);
}
}
private void parseRmc(String nmea) {
String[] strings = StringTool.tokenize(nmea, ',');
if ("A".equals(strings[2])) {
gpsState.time = strings[1];
setCoordinates(strings, 3);
gpsState.state = GpsState.CONNECTED_FIX;
// conversion between knots and kmph
if (!"".equals(strings[7])) {
gpsState.groundSpeed = Double.parseDouble(strings[7]) * 1.852;
} else {
gpsState.groundSpeed = -1;
}
if (!"".equals(strings[8])) {
try {
gpsState.compass = Double.parseDouble(strings[8]);
} catch (NumberFormatException nfe) {
gpsState.compass = -1;
}
} else {
gpsState.compass = -1;
}
} else {
gpsState.state = GpsState.CONNECTED_NO_FIX;
}
}
private void setCoordinates(String[] strings, int i) {
if (gpsState.worldCoordinate == null) {
gpsState.worldCoordinate = new WorldCoordinate();
} else {
if (gpsState.prevWorldCoordinate == null) {
gpsState.prevWorldCoordinate = new WorldCoordinate();
}
gpsState.prevWorldCoordinate.latitude = gpsState.worldCoordinate.latitude;
gpsState.prevWorldCoordinate.longitude = gpsState.worldCoordinate.longitude;
}
if ("N".equals(strings[i + 1])) {
gpsState.worldCoordinate.latitude = getCoordinate(strings[i]);
} else {
gpsState.worldCoordinate.latitude = -getCoordinate(strings[i]);
}
if ("E".equals(strings[i + 3])) {
gpsState.worldCoordinate.longitude = getCoordinate(strings[i + 2]);
} else {
gpsState.worldCoordinate.longitude = -getCoordinate(strings[i + 2]);
}
gpsState.prevCoordinateUpdateMilis = gpsState.coordinateUpdateMilis;
gpsState.coordinateUpdateMilis = System.currentTimeMillis();
}
Vector satellites;
Hashtable activeSats;
private Thread thread;
private void parseGga(String nmea) {
String[] strings = StringTool.tokenize(nmea, ',');
gpsState.time = strings[1];
if (strings[3].length() > 0) {
// gpsState.state = GpsState.CONNECTED_FIX_3D;
// setCoordinates(strings, 2);
gpsState.altitude = Double.parseDouble(strings[9]);
}
}
private double getCoordinate(String string) {
// string = 5005.075330
// result = 50 + 05.075330 * 100.0 / 60.0
double tmp = Double.parseDouble(string);
double d = tmp / 100.0;
double floor = Math.floor(d);
return floor + (d - floor) * 5.0 / 3.0;
}
private void parseGsv(String nmea) {
String[] strings = StringTool.tokenize(nmea, ',');
if (strings[2].equals("1")) {
satellites = new Vector(24);
}
gpsState.satellitesInView = Integer.parseInt(strings[3]);
int max;
if (strings[1].equals(strings[2])) {
max = Integer.parseInt(strings[3]) - (Integer.parseInt(strings[2]) - 1) * 4;
} else {
max = 4;
}
for (int i = 1; i <= max; i++) {
int baseIndex = i * 4;
if (strings[baseIndex].equals("")) {
break;
}
GpsState.Satellite satellite = new GpsState.Satellite();
satellite.prn = strings[baseIndex];
if (strings[baseIndex + 3].length() > 0) {
if (i == max) {
String s;
int tmp = strings[baseIndex + 3].indexOf('*');
if (tmp >= 0) {
s = strings[baseIndex + 3].substring(0, tmp);
} else {
s = strings[baseIndex + 3];
}
if (s.length() > 0) {
satellite.snr = Integer.parseInt(s);
} else {
satellite.snr = -1;
}
} else {
satellite.snr = Integer.parseInt(strings[baseIndex + 3]);
}
} else {
satellite.snr = -1;
}
satellites.addElement(satellite);
}
if (strings[1].equals(strings[2])) {
gpsState.satellites = satellites;
}
}
private void parseGsa(String nmea) {
String[] strings = StringTool.tokenize(nmea, ',');
// if (strings[2].charAt(0) == '1') {
// gpsState.state = GpsState.CONNECTED_NO_FIX;
// } else {
// gpsState.state = GpsState.CONNECTED_FIX;
// }
activeSats = new Hashtable();
for (int i = 3; i < 13; i++) {
if ("".equals(strings[i])) {
break;
}
activeSats.put(strings[i], strings[i]);
}
gpsState.activeSats = activeSats;
}
}