/* Privacy Friendly Pedometer is licensed under the GPLv3. Copyright (C) 2017 Tobias Neidig 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, either version 3 of the License, or (at your option) any later version. 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, see <http://www.gnu.org/licenses/>. */ package org.secuso.privacyfriendlyactivitytracker.services; import android.content.SharedPreferences; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.preference.PreferenceManager; import android.util.Log; import org.secuso.privacyfriendlyactivitytracker.R; import java.util.Arrays; /** * Uses the accelerometer to detect steps. * Publishes the detected steps to any subscriber. * * @author Tobias Neidig * @version 20160802 */ public class AccelerometerStepDetectorService extends AbstractStepDetectorService { public static final boolean debug = false; private static final String LOG_TAG = AccelerometerStepDetectorService.class.getName(); private float[] gravity = new float[3]; private float[] linear_acceleration = new float[3]; private float last_sign; private float[] last_extrema = new float[2]; private float last_acceleration_value; private float last_acceleration_diff; private long last_step_time; private long[] mLastStepDeltas = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; private int mLastStepDeltasIndex = 0; private float[] mLastStepAccelerationDeltas = {-1, -1, -1, -1, -1, -1}; private int mLastStepAccelerationDeltasIndex = 0; private float accelerometerThreshold; private int valid_steps = 0; private int validStepsThreshold = 0; //private float[] /** * Creates an AccelerometerStepDetectorService. */ public AccelerometerStepDetectorService() { this(""); // required empty constructor } /** * Creates an AccelerometerStepDetectorService. * * @param name Name for the worker thread, use it for debugging purposes */ public AccelerometerStepDetectorService(String name) { super(name); } @Override public void onCreate() { super.onCreate(); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); accelerometerThreshold = Float.parseFloat(sharedPref.getString(getString(R.string.pref_accelerometer_threshold), "0.75")); validStepsThreshold = Integer.parseInt(sharedPref.getString(getString(R.string.pref_accelerometer_steps_threshold), "10")); } @Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { return; } if (event.values.length != 3) { Log.e(LOG_TAG, "Invalid sensor values."); } // the following part will add some basic low/high-pass filter // to ignore earth acceleration final float alpha = 0.8f; // Isolate the force of gravity with the low-pass filter. gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]; gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]; gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]; // Remove the gravity contribution with the high-pass filter. linear_acceleration[0] = event.values[0] - gravity[0]; linear_acceleration[1] = event.values[1] - gravity[1]; linear_acceleration[2] = event.values[2] - gravity[2]; float acceleration = linear_acceleration[0] + linear_acceleration[1] + linear_acceleration[2]; float current_sign = Math.signum(acceleration); if (current_sign == last_sign) { // the maximum is not reached yet, keep on waiting return; } if (!isSignificantValue(acceleration)) { // not significant (acceleration delta is too small) return; } float acceleration_diff = Math.abs(last_extrema[current_sign < 0 ? 1 : 0] /* the opposite */ - acceleration); if (!isAlmostAsLargeAsPreviousOne(acceleration_diff)) { if (debug) Log.i(LOG_TAG, "Not as large as previous"); last_acceleration_diff = acceleration_diff; return; } if (!wasPreviousLargeEnough(acceleration_diff)) { if (debug) Log.i(LOG_TAG, "Previous not large enough"); last_acceleration_diff = acceleration_diff; return; } long current_step_time = System.currentTimeMillis(); if (last_step_time > 0) { long step_time_delta = current_step_time - last_step_time; // Ignore steps with more than 180bpm and less than 20bpm if (step_time_delta < 60 * 1000 / 180) { if (debug) Log.i(LOG_TAG, "Too fast."); return; } else if (step_time_delta > 60 * 1000 / 20) { if (debug) Log.i(LOG_TAG, "Too slow."); last_step_time = current_step_time; valid_steps = 0; return; } // check if this occurrence is regular with regard to the step frequency data if (!isRegularlyOverTime(step_time_delta)) { last_step_time = current_step_time; if (debug) Log.i(LOG_TAG, "Not regularly over time."); return; } last_step_time = current_step_time; // check if this occurrence is regular with regard to the acceleration data if (!isRegularlyOverAcceleration(acceleration_diff)) { last_acceleration_value = acceleration; last_acceleration_diff = acceleration_diff; if (debug) Log.i(LOG_TAG, "Not regularly over acceleration" + Arrays.toString(mLastStepAccelerationDeltas)); valid_steps = 0; return; } last_acceleration_value = acceleration; last_acceleration_diff = acceleration_diff; // okay, finally this has to be a step valid_steps ++; if (debug) Log.i(LOG_TAG, "Detected step. Valid steps = " + valid_steps); // count it only if we got more than validStepsThreshold steps if(valid_steps == validStepsThreshold){ this.onStepDetected(valid_steps); }else if(valid_steps > validStepsThreshold){ this.onStepDetected(1); } } last_step_time = current_step_time; last_acceleration_value = acceleration; last_acceleration_diff = acceleration_diff; last_sign = current_sign; last_extrema[current_sign < 0 ? 0 : 1] = acceleration; } /** * Determines if this value is significant. * * @param val the value to check * @return true if it is significant else false */ private boolean isSignificantValue(float val) { return Math.abs(val) > accelerometerThreshold; } /** * The current acceleration difference has to be almost as large as the last one. * * @param diff The acceleration difference between current and last value * @return true if almost as large as last one */ private boolean isAlmostAsLargeAsPreviousOne(float diff) { return diff > last_acceleration_diff * 0.5; } /** * Determines if the last maximum was great enough * * @param diff the current acceleration diff * @return true if was great enough else false */ private boolean wasPreviousLargeEnough(float diff) { return last_acceleration_diff > diff / 3; } /** * Checks if the given delta time (between current and last step) is regularly. * The value is regularly if at most 20 percent of the older values differs from the given value * significantly. * * @param delta The difference between current and last step time * @return true if is regularly else false */ private boolean isRegularlyOverTime(long delta) { mLastStepDeltas[mLastStepDeltasIndex] = delta; mLastStepDeltasIndex = (mLastStepDeltasIndex + 1) % mLastStepDeltas.length; int numIrregularValues = 0; for (long mLastStepDelta : mLastStepDeltas) { if (Math.abs(mLastStepDelta - delta) > 200) { numIrregularValues++; break; } } return numIrregularValues < 1;//mLastStepDeltas.length*0.2; } /** * Checks if the given diff (between current and last acceleration data) is regularly in respect * to the older values. * The value is regularly if at most 20 percent of the older values differs from the given value * significantly. * * @param diff The difference between current and last acceleration value * @return true if is regularly else false */ private boolean isRegularlyOverAcceleration(float diff) { mLastStepAccelerationDeltas[mLastStepAccelerationDeltasIndex] = diff; mLastStepAccelerationDeltasIndex = (mLastStepAccelerationDeltasIndex + 1) % mLastStepAccelerationDeltas.length; int numIrregularAccelerationValues = 0; for (float mLastStepAccelerationDelta : mLastStepAccelerationDeltas) { if (Math.abs(mLastStepAccelerationDelta - last_acceleration_diff) > 0.5) { numIrregularAccelerationValues++; break; } } return numIrregularAccelerationValues < mLastStepAccelerationDeltas.length * 0.2; } @Override public int getSensorType() { return Sensor.TYPE_ACCELEROMETER; } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { super.onSharedPreferenceChanged(sharedPreferences, key); if (key.equals(getString(R.string.pref_accelerometer_threshold))) { accelerometerThreshold = Float.parseFloat(sharedPreferences.getString(getString(R.string.pref_accelerometer_threshold), "0.75")); } if (key.equals(getString(R.string.pref_accelerometer_steps_threshold))) { validStepsThreshold = Integer.parseInt(sharedPreferences.getString(getString(R.string.pref_accelerometer_steps_threshold), "10")); } } }