/******************************************************************************* * Gaggle is Copyright 2010 by Geeksville Industries LLC, a California limited liability corporation. * * Gaggle is distributed under a dual license. We've chosen this approach because within Gaggle we've used a number * of components that Geeksville Industries LLC might reuse for commercial products. Gaggle can be distributed under * either of the two licenses listed below. * * 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. * * Commercial Distribution License * If you would like to distribute Gaggle (or portions thereof) under a license other than * the "GNU General Public License, version 2", contact Geeksville Industries. Geeksville Industries reserves * the right to release Gaggle source code under a commercial license of its choice. * * GNU Public License, version 2 * All other distribution of Gaggle must conform to the terms of the GNU Public License, version 2. The full * text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt. ******************************************************************************/ package com.geeksville.location; import java.util.Observable; import java.util.Observer; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.location.Location; import android.os.IBinder; import android.util.Log; import com.geeksville.android.ChangeHandler; import com.geeksville.gaggle.GagglePrefs; import com.geeksville.gaggle.R; /** * Reads positions from the GPS and writes them to a PositionWriter (probably a * log file) * * @author kevinh * */ public class GPSToPositionWriter extends AbstractLocationListener implements ServiceConnection, Observer { private Location initialPos; private PositionWriter dest; private Context context; private IGPSClient gps; private int pollInterval; private static final String TAG = "GPSToPositionWriter"; private int numPoints; /** * Distance in meters from initial position before we'll detect it as a launch */ private int launchDistanceY, launchDistanceX; /** * How many meters to a degree (approx) */ private static final double metersLatitudePerDeg = 111131.745; private static final double metersLongitudePerDeg = 78846.80572069259; /** * The person watching us */ ChangeHandler myObserver; AccelerometerClient accel = null; IBarometerClient baro = null; public void setObserver(ChangeHandler obs) { myObserver = obs; } private void setStatus(Status stat) { curStatus = stat; if (myObserver != null) myObserver.onChanged(this); } public enum Status { OFF, WAIT_FOR_LOCK, WAIT_FOR_LAUNCH, IN_FLIGHT, LANDED } private Status curStatus = Status.OFF; public GPSToPositionWriter(Context _context) { context = _context; } public Status getStatus() { return curStatus; } public boolean isLogging() { return curStatus != Status.OFF && curStatus != Status.LANDED; } /** * A human readable description of our status * * @return */ public String getStatusString() { switch (curStatus) { case OFF: return context.getString(R.string.off); case WAIT_FOR_LOCK: return context.getString(R.string.acquiring_gps_fix); case WAIT_FOR_LAUNCH: return String.format( context.getString(R.string.waiting_for_launch_d_pts), numPoints); case IN_FLIGHT: return String.format(context.getString(R.string.in_flight_d_pts), numPoints); case LANDED: return context.getString(R.string.landed); } throw new IllegalStateException("Unknown GPS logging state"); } /** * Stop logging */ public synchronized void stopLogging() { if (isLogging()) { gps.removeLocationListener(this); if (curStatus == Status.IN_FLIGHT) { dest.emitEpilog(); // flightStopTime = new Date(); } gps.stopForeground(); // If we never started flying, just return to off setStatus(curStatus == Status.IN_FLIGHT ? Status.LANDED : Status.OFF); GPSClient.unbindFrom(context, this); if (accel != null) { accel.deleteObserver(this); accel = null; } if (baro != null) { baro.deleteObserver(this); baro = null; } } } public void startLogging(Context context, PositionWriter dest, int pollIntervalSecs, int launchDistanceX, int launchDistanceY) { this.context = context; if (!isLogging()) { accel = AccelerometerClient.create(context); if (accel != null) accel.addObserver(this); try { baro = BarometerClient.create(context); } catch (VerifyError ex) { Log.e(TAG, "Not on 1.5: " + ex); } if (baro != null) baro.addObserver(this); this.numPoints = 0; this.pollInterval = pollIntervalSecs; this.dest = dest; this.launchDistanceX = launchDistanceX; this.launchDistanceY = launchDistanceY; GPSClient.bindTo(context, this); } else throw new IllegalStateException("Already logging"); } @Override public void onServiceConnected(ComponentName name, IBinder service) { gps = (IGPSClient) service; // FIXME - move to correct place gps.startForeground(context.getString(R.string.tracklog_started), context.getString(R.string.capturing_tracklog)); setStatus(Status.WAIT_FOR_LOCK); GagglePrefs prefs = new GagglePrefs(context); long minTime = prefs.getGPSUpdateFreq() * 1000; // num msec between events float minDist = prefs.getGPSUpdateDist(); // need at least this many meters gps.addLocationListener(minTime, minDist, this); } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } /** * Called after we have a GPS lock */ private void newStateWaitForLaunch() { setStatus(Status.WAIT_FOR_LAUNCH); } /** * Called once we've determined we've moved far enough from launch * * @param initialPoints * points we've saved up that should now be added to the tracklog */ private synchronized void newStateFlight(Location[] initialPoints) { // flightStartTime = new Date(); setStatus(Status.IN_FLIGHT); dest.emitProlog(); for (Location l : initialPoints) emitPosition(l); } /** * Add a point to the tracklog * * @param location */ private void emitPosition(Location location) { double lat = location.getLatitude(); double longitude = location.getLongitude(); float kmPerHr = location.hasSpeed() ? (float) (location.getSpeed() * 3.6) : Float.NaN; // convert m/sec to km/hr float[] accelVals = (accel != null) ? accel.getValues() : null; float vspd = (baro != null) ? baro.getVerticalSpeed() : Float.NaN; // The emulator will falsely claim 0 for the first point reported - // skip it if (lat != 0.0) dest.emitPosition(location.getTime(), lat, longitude, location.hasAltitude() ? (float) location.getAltitude() : Float.NaN, (int) location.getBearing(), kmPerHr, accelVals, vspd); } @Override public void onLocationChanged(Location location) { numPoints++; setStatus(curStatus); // For debugging switch (curStatus) { case OFF: // Ignore - stale message break; case WAIT_FOR_LOCK: // We need a 3d position before we can even start moving if (location.hasAltitude()) { initialPos = location; newStateWaitForLaunch(); // We must now have a lock } break; case WAIT_FOR_LAUNCH: if (location.hasAltitude()) { int deltay = Math.abs(((int) initialPos.getAltitude()) - ((int) location.getAltitude())); double metersLat = metersLatitudePerDeg * Math.abs(initialPos.getLatitude() - location.getLatitude()); double metersLong = metersLongitudePerDeg * Math.abs(initialPos.getLongitude() - location.getLongitude()); /* * Too low a precision to be useful (at my house it shows 2000 meter * distance int deltax = (int) LocationUtils.LatLongToMeter((float) * initialPos.getLatitude(), (float) initialPos .getLongitude(), (float) * location.getLatitude(), (float) location .getLongitude()); */ if (launchDistanceY == 0 || launchDistanceX == 0 || metersLat >= launchDistanceX || metersLong >= launchDistanceX || deltay >= launchDistanceY) { Log.i(TAG, String.format("Launch detected dx=%f, dy=%f, dz=%d", metersLong, metersLat, deltay)); newStateFlight(new Location[] { initialPos, location }); } } break; case IN_FLIGHT: emitPosition(location); break; case LANDED: // Ignore - stale message break; } } /** * If onProviderDisabled called, then stop track log */ @Override public void onProviderDisabled(String provider) { stopLogging(); } @Override public void update(Observable observable, Object data) { // TODO Auto-generated method stub } }