/* GNU General Public License CacheWolf is a software for PocketPC, Win and Linux that enables paperless caching. It supports the sites geocaching.com and opencaching.de Copyright (C) 2006 CacheWolf development team See http://www.cachewolf.de/ for more information. 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package CacheWolf.navi; import net.ax86.GPS; import net.ax86.GPSException; import org.json.JSONException; import org.json.JSONObject; import CacheWolf.MainForm; import CacheWolf.MainTab; import CacheWolf.Preferences; import CacheWolf.controls.InfoBox; import CacheWolf.database.CWPoint; import CacheWolf.database.CacheHolder; import CacheWolf.utils.MyLocale; import ewe.fx.Color; import ewe.io.IOException; import ewe.io.SerialPort; import ewe.io.SerialPortOptions; import ewe.net.Socket; import ewe.sys.mThread; import ewe.ui.FormBase; import ewe.util.mString; /** * Non-Gui Class to handle all things regarding navigation<br> * (GPS, Sun direction etc.)<br> * start offset in localisation file: 4400<br> * * @author Pfeffer * */ public class Navigate { public static Navigate itself; public static int luminary = SkyOrientation.SUN; public static CWGPSPoint gpsPos = new CWGPSPoint(); public static CWPoint destination = new CWPoint(); public CacheHolder destinationCache; public boolean destinationIsCache = false; public Track curTrack = null; public Color trackColor = new Color(255, 0, 0); // red public CWPoint skyOrientationDir = new CWPoint(); public GotoPanel gotoPanel = null; private MovingMap movingMap = null; public GpsdThread gpsdThread = null; public OldGpsdThread oldGpsdThread = null; public SerialThread serThread = null; public UpdateThread tickerThread; public boolean gpsRunning = false; boolean lograw = false; int logIntervall = 10; public Navigate() { itself = this; gotoPanel = MainTab.itself.gotoPanel; movingMap = MainTab.itself.movingMap; } public void startGps(boolean loggingOn, int loggingIntervall) { lograw = loggingOn; logIntervall = loggingIntervall; // TODO switch on and off during serthread running switch (Preferences.itself().useGPSD) { // Tblue> TODO: NEW vs. OLD: This is ugly! The only line that's // different is the one where the object is created! case Preferences.GPSD_FORMAT_NEW: try { gpsdThread = new GpsdThread(gpsPos); gpsdThread.start(); startDisplayTimer(); gpsRunning = true; curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps if (lograw) gpsPos.startLog(MainForm.profile.dataDir, logIntervall, CWGPSPoint.LOGALL); if (gotoPanel != null) gotoPanel.gpsStarted(); if (movingMap != null) movingMap.gpsStarted(); } catch (IOException e) { new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4408, "Could not connect to GPSD: ") + e.getMessage() + MyLocale.getMsg(4409, "\nPossible reasons:\nGPSD is not running or GPSD host is not reachable")).wait(FormBase.OKB); } catch (Exception e) { // Other error (JSON/GPS). new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(99999, "Could not initialize GPSD connection: ") + e.getMessage()).wait(FormBase.OKB); } break; case Preferences.GPSD_FORMAT_OLD: try { oldGpsdThread = new OldGpsdThread(gpsPos); oldGpsdThread.start(); startDisplayTimer(); gpsRunning = true; curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps if (lograw) gpsPos.startLog(MainForm.profile.dataDir, logIntervall, CWGPSPoint.LOGALL); if (gotoPanel != null) gotoPanel.gpsStarted(); if (movingMap != null) movingMap.gpsStarted(); } catch (IOException e) { new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4408, "Could not connect to GPSD: ") + e.getMessage() + MyLocale.getMsg(4409, "\nPossible reasons:\nGPSD is not running or GPSD host is not reachable")).wait(FormBase.OKB); } break; case Preferences.GPSD_DISABLED: default: if (serThread != null) if (serThread.isAlive()) return; // TODO use gpsRunning try { serThread = new SerialThread(Preferences.itself().mySPO, gpsPos, (Preferences.itself().forwardGPS ? Preferences.itself().forwardGpsHost : "")); if (Preferences.itself().forwardGPS && !serThread.tcpForward) { new InfoBox(MyLocale.getMsg(144, "Warning"), MyLocale.getMsg(4401, "Ignoring error:\n could not forward GPS data to host:\n") + Preferences.itself().forwardGpsHost + "\n" + serThread.lastError + MyLocale.getMsg(4402, "\nstop and start GPS to retry")).exec(); } serThread.start(); startDisplayTimer(); gpsRunning = true; curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps if (lograw) gpsPos.startLog(MainForm.profile.dataDir, logIntervall, CWGPSPoint.LOGALL); if (gotoPanel != null) gotoPanel.gpsStarted(); if (movingMap != null) movingMap.gpsStarted(); } catch (IOException e) { new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4404, "Could not connect to GPS-receiver.\n Error while opening serial Port ") + e.getMessage() + MyLocale.getMsg(4405, "\npossible reasons:\n Another (GPS-)program is blocking the port\nwrong port\nOn Loox: active infra-red port is blocking GPS")).wait(FormBase.OKB); } catch (UnsatisfiedLinkError e) { new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4404, "Could not connect to GPS-receiver.\n Error while opening serial Port ") + MyLocale.getMsg(4406, "Please copy jave_ewe.dll into the directory of the cachewolf program")).wait(FormBase.OKB); } break; } } public void startDisplayTimer() { tickerThread = new UpdateThread(this, 1000); tickerThread.start(); } public void stopDisplayTimer() { if (tickerThread != null) tickerThread.stop(); } public void stopGps() { if (serThread != null) serThread.stop(); if (gpsdThread != null) gpsdThread.stop(); if (oldGpsdThread != null) oldGpsdThread.stop(); stopDisplayTimer(); gpsPos.stopLog(); gpsRunning = false; if (gotoPanel != null) gotoPanel.gpsStoped(); if (movingMap != null) movingMap.gpsStoped(); } public boolean isGpsPosValid() { return ((serThread != null && serThread.isAlive()) || (gpsdThread != null && gpsdThread.isAlive()) || (oldGpsdThread != null && oldGpsdThread.isAlive())) && gpsPos.isValid(); // && gpsPos.getfiex(); } public void setDestination(String LatLon) { setDestination(new CWPoint(LatLon)); } public void setDestination(CWPoint d) { if (d != null && d.isValid()) { destinationIsCache = false; destination = new CWPoint(d); gotoPanel.destChanged(destination); movingMap.destChanged(destination); } else { new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(1507, "Coordinates are out of range:") + "\n" + MyLocale.getMsg(1508, "latitude") + ": " + destination.latDec + "\n " + MyLocale.getMsg(1509, "longditue") + ": " + destination.lonDec).wait(FormBase.OKB); } } public void setDestination(CacheHolder ch) { CWPoint d = ch.getWpt(); if (d.isValid()) { destinationIsCache = true; destinationCache = ch; destination = new CWPoint(d); gotoPanel.destChanged(destination); movingMap.destChanged(ch); } } /** * use the constants SkyOrientation.SUN, SkyOrientation.MOON etc. * * @param lu */ public void setLuminary(int lu) { luminary = lu; } public void ticked() { int fix = gpsPos.getFix(); if (fix > 0 && (gpsPos.getSats() >= 0)) { if (curTrack == null) curTrack = new Track(trackColor); try { curTrack.add(gpsPos); } catch (IndexOutOfBoundsException e) { // track full -> create a new one curTrack = new Track(trackColor); curTrack.add(gpsPos); if (movingMap != null) movingMap.addTrack(curTrack); } try { SkyOrientation.getSunAzimut(gpsPos.Time, gpsPos.Date, gpsPos.latDec, gpsPos.lonDec); double jd = SkyOrientation.utc2juliandate(gpsPos.Time, gpsPos.Date); skyOrientationDir = SkyOrientation.getLuminaryDir(luminary, jd, gpsPos); } catch (NumberFormatException e) { // irgendeine Info zu Berechnung des Sonnenaziumt fehlt (insbesondere Datum und Uhrzeit sind nicht unbedingt gleichzeitig verf�gbar wenn es einen Fix gibt) skyOrientationDir.set(-361, -361); // any value out of range (bigger than 360) will prevent drawArrows from drawing it } } else { skyOrientationDir.set(-361, -361); // any value out of range (bigger than 360) will prevent drawArrows from drawing it } gotoPanel.updateGps(fix); if (movingMap != null) movingMap.updatePositionFromGps(fix); } } /** * Thread for reading data from gpsd. * * @author Tilman Blumenbach */ class GpsdThread extends mThread { GPS gpsObj; CWGPSPoint myGPS; boolean run; public GpsdThread(CWGPSPoint GPSPoint) throws IOException, JSONException, GPSException { JSONObject response; int proto_major; myGPS = GPSPoint; gpsObj = new GPS(Preferences.itself().gpsdHost, Preferences.itself().gpsdPort); gpsObj.stream(GPS.WATCH_ENABLE); // Check major protocol version: response = gpsObj.read(); if (!response.getString("class").equals("VERSION")) { throw new GPSException("Expected VERSION object at connect."); } else if ((proto_major = response.getInt("proto_major")) != 3) { throw new GPSException("Invalid protocol API version; got " + proto_major + ", want 3."); } } public void run() { JSONObject response; String respClass; int noData = 0; int notInterpreted = 0; boolean gotValidData = false; // redundant, but compiler complains. run = true; while (run) { if (gpsObj != null) { gotValidData = false; try { /* * Tblue> This is ugly, but BufferedReader::ready() seems to * be broken in Ewe, so instead of only polling when * there is no data from gpsd (by checking the return * value of GPS::waiting(), we poll on every iteration. * Not ideal, but works for now. */ gpsObj.poll(); /* * Tblue> TODO: I think this call should not block, but * my GPS class does not yet support non-blocking * reads... */ response = gpsObj.read(); // If we get here we have got some data: noData = 0; respClass = response.getString("class"); if (respClass.equals("DEVICE") && response.has("activated") && response.getDouble("activated") != 0) { // This is a new device, we need to tell gpsd we want to watch it: Preferences.itself().log("New GPS device, sending WATCH command."); gpsObj.stream(GPS.WATCH_ENABLE); } else if (respClass.equals("POLL")) { gotValidData = myGPS.examineGpsd(response); } else if (respClass.equals("ERROR")) { // Preferences.itself().log( "Ignored gpsd error: " + response.getString( "message" ) ); } } catch (Exception e) { // Something bad happened, will just ignore this JSON // object: // Ignored Exception", e, true); gotValidData = false; } if (gotValidData) { notInterpreted = 0; } else { notInterpreted++; } if (notInterpreted > 22) { myGPS.noInterpretableData(); } } try { sleep(1000); } catch (InterruptedException e) { // Preferences.itself().log("Ignored Exception", e, true); } noData++; if (noData > 5) { myGPS.noDataError(); } } // while myGPS.noData(); } public void stop() { run = false; if (gpsObj != null) { gpsObj.cleanup(); } } } class OldGpsdThread extends mThread { Socket gpsdSocket; CWGPSPoint myGPS; boolean run, tcpForward; Socket tcpConn; String lastError; public OldGpsdThread(CWGPSPoint GPSPoint) throws IOException { try { gpsdSocket = new Socket(Preferences.itself().gpsdHost, Preferences.itself().gpsdPort); } catch (IOException e) { throw new IOException(Preferences.itself().gpsdHost); } // catch (UnsatisfiedLinkError e) {} // TODO in original java-vm myGPS = GPSPoint; } public void run() { String gpsResult; int noData = 0; int notinterpreted = 0; run = true; while (run) { try { sleep(900); noData++; if (noData > 5) { myGPS.noDataError(); } } catch (InterruptedException e) { // Preferences.itself().log("Ignored Exception", e, true); } if (gpsdSocket != null) { gpsResult = getGpsdData("ADPQTV\r\n"); if (gpsResult != null) { noData = 0; if (myGPS.examineOldGpsd(gpsResult)) notinterpreted = 0; else notinterpreted++; if (notinterpreted > 22) myGPS.noInterpretableData(); } } // myGPS.printAll(); } // while myGPS.noData(); } private String getGpsdData(String command) { byte[] rcvBuff = new byte[1024 * 10]; // when some action takes a long time (eg. loading or zooming a map), a lot of data can be in the buffer, read that at once int rcvLength = 0; try { gpsdSocket.write(command.getBytes()); } catch (IOException e) { Preferences.itself().log("Socket exception", e, true); } try { sleep(100); } catch (InterruptedException e) { // Preferences.itself().log("Ignored exception", e, true); } try { rcvLength = gpsdSocket.read(rcvBuff); } catch (IOException e) { Preferences.itself().log("Socket exception", e, true); } String str = null; if (rcvLength > 0) { str = mString.fromAscii(rcvBuff, 0, rcvLength); } return str; } public void stop() { run = false; if (gpsdSocket != null) gpsdSocket.close(); } } /** * Thread for reading data from COM-port * */ class SerialThread extends mThread { SerialPort comSp; byte[] comBuff = new byte[1024 * 10]; // when some action takes a long time (eg. loading or zooming a map), a lot of data can be in the buffer, read that at once int comLength = 0; CWGPSPoint myGPS; boolean run, tcpForward; Socket tcpConn; String lastError = ""; public SerialThread(SerialPortOptions spo, CWGPSPoint GPSPoint, String forwardIP) throws IOException { try { spo.portName = CacheWolf.utils.Common.fixSerialPortName(spo.portName); comSp = new SerialPort(spo); } catch (IOException e) { throw new IOException(spo.portName); } // catch (UnsatisfiedLinkError e) {} // TODO in original java-vm if (forwardIP.length() > 0) { try { tcpConn = new Socket(forwardIP, 23); tcpForward = true; } catch (ewe.net.UnknownHostException e) { tcpForward = false; lastError = e.getMessage(); } catch (IOException e) { tcpForward = false; lastError = e.getMessage(); } } myGPS = GPSPoint; } public void run() { int noData = 0; int notinterpreted = 0; run = true; while (run) { try { sleep(1000); noData++; if (noData > 5) { myGPS.noDataError(); } } catch (InterruptedException e) { // Preferences.itself().log("Ignored Exception", e, true); } if (comSp != null) { comLength = comSp.nonBlockingRead(comBuff, 0, comBuff.length); if (comLength > 0) { noData = 0; String str = mString.fromAscii(comBuff, 0, comLength); if (tcpForward) { try { tcpConn.write(comBuff, 0, comLength); } catch (IOException e) { tcpForward = false; } } if (myGPS.examine(str)) notinterpreted = 0; else notinterpreted++; if (notinterpreted > 22) myGPS.noInterpretableData(); } } } // while myGPS.noData(); tcpConn.close(); } public void stop() { run = false; if (comSp != null) comSp.close(); } } /** * Class for creating a new mThread to create timer ticks to be able to do form.close in the ticked-thread. Using the Vm.requestTimer-Method causes "ewe.sys.EventDirectionException: This task cannot be done within a Timer Tick." in the ewe-vm when * form.close is called. */ class UpdateThread extends mThread { public boolean run; public int calldelay; public Navigate ticked; public UpdateThread(Navigate gp, int cd) { ticked = gp; calldelay = cd; } public void run() { run = true; while (run) { try { sleep(calldelay); } catch (InterruptedException e) { } try { ticked.ticked(); } catch (Exception e) { // Preferences.itself().log("Navigate.UpdateThread.run(): Ignored Exception. There should not be an Exception, so please report it in the cachewolf forum at www.geoclub.de", e, true); } } } public void stop() { run = false; } }