/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker 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, either version 3 of the License, or
(at your option) any later version.
Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.android;
import java.util.Map;
import com.rareventure.android.AndroidPreferenceSet.AndroidPreferences;
import com.rareventure.gps2.GTG;
import android.hardware.SensorManager;
import android.util.Log;
import android.widget.Toast;
/**
* Detects periods of calmness given accelerometer readings
*/
//TODO 3: maybe use a more fair ... look forward and backwards STOPPED and MOVING indicator. Yes,
// we'll have to nitfy the the clients later, but at least we get good data when we display it
// in the reviewer.
//TODO 3: Also consider this forward and back looking for the compass. With both the accelerometer
// and the compass forward and back, we may get better direction
public class CalmnessDetector2
{
public float grav = SensorManager.GRAVITY_EARTH;
public State state = State.MOVING;
public static enum State { STOPPED, MOVING };
public Preferences prefs = new Preferences();
//TODO 3: An alternative may be to take an integral minus a certain level. So if the value is 8 we subtract 5 (constant) and use
//3 as the height of our integral. (if below 5 we take it as zero)
/**
* Time the force is above a constant value. These are taken over segments
*/
private int [] aboveGPeriods = new int [prefs.aboveGSegmentCount];
/**
* The movement in G's compared to gravity calculated from the last accel reading
*/
private float lastG;
private int rawAboveGIndex;
/**
* The time of the last accel reading
*/
private long lastTime;
/**
* The amount of time spent above the movement threshold in the last period
*/
private int totalAboveG;
public long currSegmentStartMs;
public CalmnessDetector2()
{
}
public boolean processAccelData(float x, float y, float z, long time) {
//note that we are only called when the sensor *has changed*. So if it is sitting on a pile of sand for a half hour
//in the basement, it might not notify us of any change.
//So we have to assume that the last reading has been that value for the time since we are now notified.
//Because so, we use lastG in our calculations and time - lastTime to indicate the duration it
//was at that value
float g = (float) Math.sqrt(x * x + y * y + z * z);
//if we just started
if(lastTime == 0)
{
//skip trying to update the state
currSegmentStartMs = lastTime = time;
lastG = g;
return false;
}
//sometimes time is equal to lastTime
if(time <= lastTime)
{
// Log.e("GPS", "updateState: lastTime is after time??? time "+time+" lastTime "+lastTime);
return false;
}
boolean result = updateState(lastTime, time);
lastG = g;
grav = lastG * (time - lastTime) * prefs.gravitationCenterUpdateConstant
+ grav * (1f - (time - lastTime) * prefs.gravitationCenterUpdateConstant);
lastTime = time;
return result;
}
private boolean updateState(long lastTime, long time)
{
//TODO 3: measurements are coming every quarter of a second, can we (should we) adjust the sample size based on the data received?
//if we are above the force detection
boolean aboveG = getForce() > prefs.minGMovementDetector;
long timeOfMeasurement = time - lastTime;
//TODO 3: we are sometimes getting in an infinite loop in the while below. I used to think it may be there is just a lot of difference
// between time and last time, but now I think its because lastTime sometimes equals time. Verify this
if(timeOfMeasurement > 600000)
{
Log.e("GPS", "updateState: too long of a difference, timeOfMeasurement="+timeOfMeasurement+", time="+time+", lastTime="+lastTime);
return false;
}
int hackCount = 0;
//note that lastTime should always be later than currSegmentStartMs
while(timeOfMeasurement > 0)
{
long startTimeMs = currSegmentStartMs > lastTime ? currSegmentStartMs : lastTime;
long endTimeMs = currSegmentStartMs + prefs.aboveGSegmentSizeMs < time ? currSegmentStartMs + prefs.aboveGSegmentSizeMs
: time;
if(aboveG)
{
this.totalAboveG += endTimeMs - startTimeMs;
this.aboveGPeriods[rawAboveGIndex] += endTimeMs - startTimeMs;
}
timeOfMeasurement -= endTimeMs - startTimeMs;
//E/GPS (11575): updateState: took 1000 tries, don't know what the heck, timeOfMeasurement=1159553, time=1336878214452, lastTime=1336878214213 startTime: 1336878215609 endTime: 1336878214452
// E/GPS (11575): updateState: took 1000 tries, don't know what the heck, timeOfMeasurement=698854, time=1336878214912, lastTime=1336878214452 startTime: 1336878215609 endTime: 1336878214912
// E/GPS (11575): updateState: took 1000 tries, don't know what the heck, timeOfMeasurement=457153, time=1336878215153, lastTime=1336878214912 startTime: 1336878215609 endTime: 1336878215153
// E/GPS (11575): updateState: took 1000 tries, don't know what the heck, timeOfMeasurement=216672, time=1336878215393, lastTime=1336878215153 startTime: 1336878215609 endTime: 1336878215393
//HACK
if(hackCount++ > 1000)
{
Log.e("GPS", "updateState: took 1000 tries, don't know what the heck, timeOfMeasurement="+timeOfMeasurement+
", time="+time+", lastTime="+lastTime+" startTime: "+startTimeMs+" endTime: "+endTimeMs);
return false;
}
//if this measurement continues beyond the segment
if(currSegmentStartMs + prefs.aboveGSegmentSizeMs <= time)
{
//move to the next one
rawAboveGIndex = (rawAboveGIndex + 1) % prefs.aboveGSegmentCount;
currSegmentStartMs += prefs.aboveGSegmentSizeMs;
this.totalAboveG -= aboveGPeriods[rawAboveGIndex];
aboveGPeriods[rawAboveGIndex] = 0;
}
}
long periodStartTimeMs = currSegmentStartMs - (prefs.aboveGSegmentCount - 1) * prefs.aboveGSegmentSizeMs;
if(state == State.STOPPED)
{
//if we need to switch from stopped to moving
if(((float)totalAboveG) / (time - periodStartTimeMs) > prefs.minPercMovingToSwitchFromStoppedToMoving)
{
state = State.MOVING;
return true;
}
}
else if(state == State.MOVING)
{
//if we need to switch from moving to stopped
if(((float)totalAboveG) / (time - periodStartTimeMs) <= prefs.maxPercMovingToSwitchFromMovingToStopped)
{
state = State.STOPPED;
return true;
}
}
return false;
}
public float getForce()
{
//PERF: maybe used force squared? or some weird value?
return (float)Math.abs(lastG - grav) / SensorManager.GRAVITY_EARTH;
}
public static class Preferences implements AndroidPreferences
{
/**
* Total number of segments (MUST be a power of 2)
*/
public int aboveGSegmentCount = 16;
/**
* Size of each segment in milleseconds
*/
public int aboveGSegmentSizeMs = 60 * 1000 / aboveGSegmentCount;
/**
* The maximum percentage of time moving
* to switch from a moving state to a stopped state
*/
//TODO 3: optimize these values
public float maxPercMovingToSwitchFromMovingToStopped = .005f;
/**
* The minimum percentage of time moving to switch
* from a stopped state to a moving state (shoud be higher than
* maxPercMovingToSwitchFromMovingToStopped)
*/
//TODO 3: optimize these values
public float minPercMovingToSwitchFromStoppedToMoving = .01f;
/**
* The minimum time necessary before we may switch from stopped to moving
*/
public long minTimeToSwitchFromStopToMovingMs = 500;
/**
* The amount to update to adjust the gravitational to the current value per ms
*/
public float gravitationCenterUpdateConstant = 1f / (30 * 60 * 1000); //let it completely readjust over 30 minutes or so
/**
* The amount of force in g's to indicate movement
*/
public float minGMovementDetector = .2f;
}
}