6

I am trying to write a method that collects accelerometer sensor values over a specific time period and returns the average of the sensor readings for that period.

It should be a synchronous i.e. blocking method that once it is called will block the calling thread for sometime and then will return the sensor average

I did check the below similar questions but does not seem to have a proper working solution for my case:

SensorEventListener in separate thread

Android - how to run your sensor ( service, thread, activity )?

Android sensors and thread

A method for waiting for sensor data

I've also tried to use Executors similar to this question, but could not get it to work as I want.

Below is my code skeleton, where the method sensorAverage is a blocking method that will calculate the accelerometer sensor average over a period equals to the timeout parameter

Average average = new Average(); // Some class to calculate the mean

double sensorAverage(long timeout){
    Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
    sensorManager.registerListener(this, sensor,SensorManager.SENSOR_DELAY_NORMAL);

    // This does not work
    Thread.sleep(timeout);

    sensorManager.unregisterListener(this);

    return average.value();
}

public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
        double x2 = Math.pow(event.values[0], 2);
        double y2 = Math.pow(event.values[1], 2);
        double z2 = Math.pow(event.values[2], 2);
        average.add(Math.sqrt((x2 + y2 + z2)));
    }
}

Edit: I am aware that I need another thread, but the problem that I need to run it for a specific period only and so far I cannot find a proper working solution. Because when I use another thread I get the sensor average always 0

Community
  • 1
  • 1
iTech
  • 18,192
  • 4
  • 57
  • 80

2 Answers2

4

I managed to implement a solution which exactly does what I want.

A blocking method that collects sensor values for specific period and returns the statistics of all sensor readings i.e. mean and variance.

It is possible to simply store all the sensor's values and then calculate the mean and variance; however you might run out of memory in case of collecting high frequency sensor over extended period of time.

I found a better solution to calculate the mean and variance for a stream of data in real-time (i.e. without storing the sensor values) using the below RunningStat class

Example code:

// Calculate statistics of accelerometer values over 300 ms (a blocking method)
RunningStat[] stats = SensorUtils.sensorStats(context, 
                                                  Sensor.TYPE_ACCELEROMETER, 300)
double xMean = stats[0].mean();
double xVar  = stats[0].variance();

Full class code:

public class SensorUtils {

// Collect sensors data for specific period and return statistics of 
// sensor values e.g. mean and variance for x, y and z-axis
public static RunningStat[] sensorStats(Context context, int sensorType,
        long timeout) throws Exception {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<RunningStat[]> future = executor.submit(new SensorTask(context,
            sensorType, timeout));

    RunningStat[] stats = future.get();
    return stats;

}

private static class SensorTask implements Callable<RunningStat[]> {
    private final Context context;
    private final long timeout;
    private final int sensorType;
    // We need a dedicated handler for the onSensorChanged
    HandlerThread handler = new HandlerThread("SensorHandlerThread");

    public SensorTask(Context context, int sensorType, long timeout) {
        this.context = context;
        this.timeout = timeout;
        this.sensorType = sensorType;
    }

    @Override
    public RunningStat[] call() throws Exception {
        final SensorCollector collector = new SensorCollector(context);
        handler.start();
        Thread sensorThread = new Thread() {
            public void run() {
                collector.start(sensorType,
                        new Handler(handler.getLooper()));
            };
        };
        sensorThread.start();
        Thread.sleep(timeout);
        return collector.finishWithResult();
    }
}

private static class SensorCollector implements SensorEventListener {
    protected Context context;
    protected RunningStat[] runningStat;
    protected SensorManager sensorManager;
    protected int sensorType;

    public SensorCollector(Context context) {
        this.context = context;
    }

    protected void start(int sensorType, Handler handle) {
        if (runningStat == null) {
            runningStat = new RunningStat[3];
            runningStat[0] = new RunningStat(3);
            runningStat[1] = new RunningStat(3);
            runningStat[2] = new RunningStat(3);
        } else {
            runningStat[0].clear();
            runningStat[1].clear();
            runningStat[2].clear();
        }
        this.sensorType = sensorType;
        sensorManager = (SensorManager) context
                .getSystemService(Context.SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(sensorType);
        sensorManager.registerListener(this, sensor,
                SensorManager.SENSOR_DELAY_NORMAL, handle);
    }

    public RunningStat[] finishWithResult() {
        if (sensorManager != null) {
            sensorManager.unregisterListener(this);
        }
        return runningStat;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == sensorType) {
            runningStat[0].push(event.values[0]);
            runningStat[1].push(event.values[1]);
            runningStat[2].push(event.values[2]);
        }
    }
}

}

Here is the RunningStat code, which is a very handy class to calculate the mean and variance for stream of data without storing the data itself (perfect for calculating statistics of high frequency sensors with very small memory footprint)

//See Knuth TAOCP vol 2, 3rd edition, page 232
public class RunningStat {
private int n;
private double oldM, newM, oldS, newS;
private int precision = -1;

// An estimate for the t-value (can be read from the t-distribution table)
private static final double T_THRESHOLD = 1.68;

public RunningStat(int precision) {
    this.precision = precision;
}

public RunningStat() {
}

public void clear() {
    n = 0;
}

public void push(double x) {
    n++;


    if (n == 1) {
        oldM = newM = x;
        oldS = 0.0;
    } else {
        newM = oldM + (x - oldM) / n;
        newS = oldS + (x - oldM) * (x - newM);

        // set up for next iteration
        oldM = newM;
        oldS = newS;
    }
}

public int count() {
    return n;
}

public double mean() {
    double mean = (n > 0) ? newM : 0.0;
    if (precision > 0) {
        return round(mean, precision);
    }
    return mean;
}

// The upper bound of the mean confidence interval
public double meanUpper() {
    double mean = (n > 0) ? newM : 0.0;
    double stdError = stdDeviation() / Math.sqrt(n);
    double upperMean = mean + T_THRESHOLD * stdError;
    if (precision > 0) {
        return round((n > 0) ? upperMean : 0.0, precision);
    }
    return upperMean;
}

// The lower bound of the mean confidence interval
public double meanLower() {
    double mean = (n > 0) ? newM : 0.0;
    double stdError = stdDeviation() / Math.sqrt(n);
    double lowerMean = mean - T_THRESHOLD * stdError;
    if (precision > 0) {
        return round((n > 0) ? lowerMean : 0.0, precision);
    }
    return lowerMean;
}

public double variance() {
    if (precision > 0) {
        return round(((n > 1) ? newS / (n - 1) : 0.0), precision);
    }
    return ((n > 1) ? newS / (n - 1) : 0.0);
}

public double stdDeviation() {
    if (precision > 0) {
        return round(Math.sqrt(variance()), precision);
    }
    return Math.sqrt(variance());
}

public void setPrecision(int precision) {
    this.precision = precision;
}

    public static double round(double value, int precision) {
         BigDecimal num = new BigDecimal(value);
         num = num.round(new MathContext(precision, RoundingMode.HALF_UP));
         return num.doubleValue();
    }

// A small test case
public static void main(String[] args) {
    int n = 100;
    RunningStat runningStat = new RunningStat();
    double[] data = new double[n];
    double sum = 0.0;
    for (int i = 0; i < n; i++) {
        data[i] = i * i;
        sum += data[i];
        runningStat.push(data[i]);
        System.out.println(runningStat.mean() + " - "
                + runningStat.variance() + " - "
                + runningStat.stdDeviation());
    }

    double mean = sum / n;
    double sum2 = 0.0;

    for (int i = 0; i < n; i++) {
        sum2 = sum2 + (data[i] - mean) * (data[i] - mean);
    }

    double variance = sum2 / (n - 1);
    System.out.println("\n\n" + mean + " - " + variance + " - "
            + Math.sqrt(variance));
}
}
iTech
  • 18,192
  • 4
  • 57
  • 80
  • Why would you not use callbacks to update a hashmap and when you need the average run the calculation on the hashmap ? – Jimmy Kane Apr 14 '16 at 08:51
  • 1
    It won't be efficient to calculate the average when needed, it is better to use the running average so it is automatically calculated with every single new data point. – iTech May 12 '16 at 19:35
3

You are essentially asking for a shake detector functionality, you can't block the main thread because you are highly likely to run accross ANR errors

You could try using the java class from Jake Wharton of Action Bar Sherlock fame https://github.com/square/seismic/tree/master/library/src/main/java/com/squareup/seismic

which will do pretty much what you are asking for, you would just need to adapt it slightly to meet your requirements, You could add onStart and onStop listeners and fire them from the start and stop methods and tie them up to your activity

It's not entirely clear what you are wishing to do exactly so it;s difficult to advise further but I am suire that what you want can be achieved without too much effort and achieved asynchronously thereby avoiding ANR's with a bit of thought using the shake detector as a base for what you want to do.

The isShaking method is probably where you might want to start making your amendments by taking a look at the sampleCount and acceleratingCount variables to see how they might help you.

boolean isShaking() {
  return newest != null
      && oldest != null
      && newest.timestamp - oldest.timestamp >= MIN_WINDOW_SIZE
      && acceleratingCount >= (sampleCount >> 1) + (sampleCount >> 2);
}

You could adjust thse values to determine how many samples or how long you want to detect movement for.

There is already a sample list that you can use to make your calculations with, just
pass it back to the onStop listener

/** Copies the samples into a list, with the oldest entry at index 0. */
List<Sample> asList() {
  List<Sample> list = new ArrayList<Sample>();
  Sample s = oldest;
  while (s != null) {
    list.add(s);
    s = s.next;
  }
  return list;
}

Update in response to comment You could determine if there is no movement simply by using a flag which is set to false in the onStop listener call back and you have complete control over how long "still" and comparing against a time stamp since the last stop to determine if the device has been still enough for long enough to meet your requirements

jamesc
  • 12,423
  • 15
  • 74
  • 113
  • Thanks for referring me to the ShakeDetection it is helpful. What I am trying to achieve is simply to check if the device is still i.e. not moving. Since I will be calling this in multiple places in my app I want to avoid the hassle of having asynchronous call. So I want the method to simply hide all the details and just return the average over which I will use to test against a threshold. – iTech Mar 13 '14 at 00:03
  • You could create a fragment that listens for movement which could then be used in whatever activities you want it in. Just tie in a listener and you have your asynchronicity so there is no complexity with threads it's dead siomple and I think a neat solution to your problem! – jamesc Mar 13 '14 at 00:10
  • The challenge is that I want the sensor collection to be running *only* for the specified time period to not drain the battery. So the synchronous method should start the sensor collection thread wait till the timeout, stop sensor thread and then return the average. – iTech Mar 13 '14 at 00:13
  • You could do that by not attaching the sensormanager until you need it maybe? – jamesc Mar 13 '14 at 00:15
  • If you really want a blocking operation then you could possibly attach the sensormanager, block the main thread in a while flag == false loop and set the flag in the onStop listener after checking the samples? Not entirely sure if that would work though! – jamesc Mar 13 '14 at 00:21
  • The devil is in the detail :), I am actually surprised to not find a complete working solution for this. For me it looks like a functionality that is useful to many applications. But so far I could not find a complete example, I will check the ShockDetection to see if it is possible to be modified in the way I want it. Thanks – iTech Mar 13 '14 at 00:31
  • 2
    Isn't it always! I had many days of pain until I found the ShakeDetector and adapted it for my needs, I am sure that adapting this detector or even a simpler implementation of it will be your salvation in the end :) Good luck! – jamesc Mar 13 '14 at 00:43