简体   繁体   中英

How do I detect an increase in movement then a sudden stop using the device's accelerometer?

I'm trying to detect a motion that you would get from bumping two phones with each other.

My question is, is an accelerometer the right sensor for this?

If so, how would I implement it?

Otherwise, what sensor should I use and in what way should I use it?

According to the guide at https://developer.android.com/guide/topics/sensors/sensors_overview , TYPE_LINEAR_ACCELERATION seems like the right one to use, however I can't figure out how to use it.

Here's how you could do it.

1- Initialize sensor object and register for sensor updates events callback like this

    private void initSensorObject() {
       SensorManager sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);
        Sensor _Sensor = sensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        sensorMgr.registerListener(sensorEventListener, _Sensor,
                SensorManager.SENSOR_DELAY_FASTEST);
    }

2- Handle the sensor callback in the following way which detects linear acceleration exluding the effect of gravity and then an abrupt stop in motion (I wrote this for two tap motions like a tap then stop, again a tap then again stop. I'll explain the whole process for better understanding, you can easily modify it for only one tap detection.) I have added comments in the code to make it self-explanatory.

    /*
 * Following are the parameters for Tap detection Algorithm
 */
private static float SPEED_THRESHOLD_RISE1;
private static float SPEED_THRESHOLD_DROP1;
private static float SPEED_THRESHOLD_RISE2;
private static float SPEED_THRESHOLD_DROP2;

private static int DROP_DELTA;
private static int RISE2_DELTA;
private int SENSITIVITY_INDEX = TapParam.SEN_DEFAULT;

private static final int TAP_STATE_RISE1 = 0;
private static final int TAP_STATE_DROP1 = 1;
private static final int TAP_STATE_RISE2 = 2;
private static final int TAP_STATE_DROP2 = 3;

private int tappingState = TAP_STATE_RISE1;
private boolean tapLastStateOnce = false;

private long lastSensorUpdate;
private long tap1DroppedAt = 0;
private int mathMeanIndex = 0;
private float[] lastLinearAcc = new float[3];
private float[] acceleSet = new float[TapParam.AM_SIZE];
private int acceleIndex = 0;
private float[] gravity = new float[3];
private float lastAccele = -99; // an arbitrarily very small value


    /**
 * onSensorChanged is called when the Motion Sensor value
 * is changed and then run the algorithm to detect your desired motion.
 *
 * @return void
 */
private SensorEventListener sensorEventListener = new SensorEventListener() {

    @Override
    public void onSensorChanged(SensorEvent event) {

        long curSensorTime = System.currentTimeMillis();
        if ((curSensorTime - lastSensorUpdate) < TapParam.SENSOR_RE_READ_TIME)
            return;
        lastSensorUpdate = curSensorTime;

        acceleSet[acceleIndex] = getMotionAcceleration(event, curSensorTime);
        acceleIndex = (acceleIndex + 1) % TapParam.AM_SIZE;
        if (mathMeanIndex < TapParam.AM_SIZE)
            mathMeanIndex++;
        float accele = Util.getArithmeticMean(acceleSet);

        switch (tappingState) {
            case TAP_STATE_RISE1:
                if (accele > SPEED_THRESHOLD_RISE1) {
                    tappingState = TAP_STATE_DROP1;
                    resetTapStateDropping();
                    mathMeanIndex = 0;
                }
                break;
            case TAP_STATE_DROP1:
                if (accele <= SPEED_THRESHOLD_DROP1) {
                    tappingState = TAP_STATE_RISE2;
                    resetTapStateRise2();
                    tap1DroppedAt = curSensorTime;
                    mathMeanIndex = 0;
                }
                break;
            case TAP_STATE_RISE2:
                if (curSensorTime - tap1DroppedAt >= TapParam.DELAY_BETWEEN_TAPS) {
                    if (accele > SPEED_THRESHOLD_RISE2) {
                        tappingState = TAP_STATE_DROP2;
                        resetTapStateDropping();
                        mathMeanIndex = 0;
                    }
                }
                break;
            case TAP_STATE_DROP2:
                if ((!tapLastStateOnce) && (accele <= SPEED_THRESHOLD_DROP2)) {
                    tapLastStateOnce = true;
                    resetTapStateRise2();
                    mathMeanIndex = 0;

                    onTapTapDetected();
                }
                break;

            default:
                tappingState = TAP_STATE_RISE1;
                break;
        }
    }

    /**
     * onAccuracyChanged inter shall be called when hardware IMU
     * (Inertial Measurement Unit a.k.a Motion Sensor) of the device change
     * its accuracy value.
     *
     * @return void
     */
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        setThresholdValues();

    }
};
    /**
 * It shall return the Linear Acceleration of the device. The force of
 * gravity shall be filtered out.
 *
 * @return float - Linear acceleration
 */
private float getMotionAcceleration(SensorEvent event, long curSensorTime) {

    // In this code, alpha is calculated as t / (t + dT),
    // where t is the low-pass filter's time-constant and
    // dT is the event delivery rate.

    final float alpha = 0.8f;
    float[] linearAcc = new float[3];

    // 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.
    linearAcc[0] = event.values[0] - gravity[0];
    linearAcc[1] = event.values[1] - gravity[1];
    linearAcc[2] = event.values[2] - gravity[2];

    float accele = (Math.abs(lastLinearAcc[0] - linearAcc[0])
            + Math.abs(lastLinearAcc[1] - linearAcc[1]) + Math
            .abs(lastLinearAcc[2] - linearAcc[2])) / 3;
    lastLinearAcc = linearAcc;

    return accele;
}

   /**
 * resetTapStateRise2 shall reset the tapping state if
 * second Tap is not detected within TAP_RISE2_TIME time.
 *
 * @return void
 */
private void resetTapStateRise2() {
    handleResetTapState.removeCallbacks(runResetTapState);
    handleResetTapState.postDelayed(runResetTapState, RISE2_DELTA);
}
    private Handler handleResetTapState = new Handler();
private Runnable runResetTapState = new Runnable() {

    @Override
    public void run() {
        tappingState = TAP_STATE_RISE1;
        tapLastStateOnce = false;
    }
};

    /**
 * resetTapStateDropping shall reset the tapping state if
 * Tap Drop is not detected within TAP_DROP_TIME time.
 *
 * @return void
 */
private void resetTapStateDropping() {
    handleResetTapState.removeCallbacks(runResetTapState);
    handleResetTapState.postDelayed(runResetTapState, DROP_DELTA);
}
    private Handler handleResetTapState = new Handler();
private Runnable runResetTapState = new Runnable() {

    @Override
    public void run() {
        tappingState = TAP_STATE_RISE1;
        tapLastStateOnce = false;
    }
};

3- Here is a working parameters file to help you get started. thresholds array defines 10 levels of sensitivity of how hard or how soft you want to tap your phone to get detected as a valid motion

TapParam.java

final class TapParam {

static final int SEN_DEFAULT = 4;
static final int SEN_MIN = 0;
static final int SEN_MAX = 9;
static final int DELAY_BETWEEN_TAPS = 75;
static final int SENSOR_RE_READ_TIME = 1;
static final int AM_SIZE = 5;

// Columns: A   B   Y   D   T1  T2  T3
private static final double[][] thresholds = new double[][]{
        {0.8483763, 0.33935052, 0.5655842, 0.33935052, 175, 300, 175},
        {0.95167595, 0.38067037, 0.6344506, 0.38067037, 175, 300, 175},
        {1.0836192, 0.4334477, 0.7224128, 0.4334477, 175, 300, 175},
        {1.8552876, 0.742115, 1.2368584, 0.742115, 175, 300, 175},
        {2.4327612, 0.9731045, 1.6218408, 0.9731045, 175, 300, 175},
        {3.5321822, 1.4128729, 2.354788, 1.4128729, 175, 300, 175},
        {6.4446864, 2.5778747, 4.296458, 2.5778747, 175, 300, 175},
        {8.2, 3.5, 5.4, 2.6, 175, 300, 175},
        {9.8, 4.0, 6.0, 2.9, 175, 300, 175},
        {12, 6.0, 8.0, 3.1, 175, 300, 175}
};

private static int indexLimiting(int index) {
    return (index > SEN_MAX) ? SEN_MAX : (index < SEN_MIN) ? SEN_MIN : index;
}

static float getRISE1(int index) {
    index = indexLimiting(index);
    return (float) thresholds[index][0];
}

static float getDROP1(int index) {
    index = indexLimiting(index);
    return (float) thresholds[index][1];
}

static float getRISE2(int index) {
    index = indexLimiting(index);
    return (float) thresholds[index][2];
}

static float getDROP2(int index) {
    index = indexLimiting(index);
    return (float) thresholds[index][3];
}

static float getDROP_DELTA1(int index) {
    index = indexLimiting(index);
    return (float) thresholds[index][4];
}

static float getRISE_DELTA2(int index) {
    index = indexLimiting(index);
    return (float) thresholds[index][5];
}
}

UPDATE:

/**
 * setThresholdValues method shall calculate the Threshold values according
 * to the accuracy value of the Motion Sensor.
 */
private void setThresholdValues() {

    if (_Sensor == null)
        return;

    SPEED_THRESHOLD_RISE1 = TapParam.getRISE1(SENSITIVITY_INDEX);
    SPEED_THRESHOLD_DROP1 = TapParam.getDROP1(SENSITIVITY_INDEX);
    SPEED_THRESHOLD_RISE2 = TapParam.getRISE2(SENSITIVITY_INDEX);
    SPEED_THRESHOLD_DROP2 = TapParam.getDROP2(SENSITIVITY_INDEX);
}

/**
 * Method shall return the average (Arithmetic Mean) of the set of values
 * passed as parameter.
 *
 * @param float[] set - the set of values
 * @return float - arithmetic mean
 */
static float getArithmeticMean(float[] set) {
    double sum = 0;
    for (float aSet : set) {
        sum += aSet;
    }
    return (float) sum / set.length;
}

UPDATE 2:

Call setTapTapSensitivity() in onCreate() of your activity

    private void setTapTapSensitivity() {
    setTapTapSensitivity(3); //You can try 0 to 9 for 10 levels of sensitivity defined in TapParam.java. I have tried 3 and it works for a moderate tap
}

private void setTapTapSensitivity(int sensitivityIndex) {
    RISE2_DELTA = (int) TapParam.getRISE_DELTA2(sensitivityIndex);
    DROP_DELTA = (int) TapParam.getDROP_DELTA1(sensitivityIndex);
    SENSITIVITY_INDEX = sensitivityIndex;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM