2

I'm trying to develop an app where the user can hit an invisible drum using the motion of the phone. So that when the phone is flicked downwards a drum is sounded at the end of the flick.

I have managed to get 90% of this working, by detecting when a large, quick movement suddenly stops. But although the drum is being sounded after a flick (good) it's also being sounded at the end of a pull (not desirable).

By flick I mean the phone is being flicked forwards and downwards, as if you are striking a drum with it, and by pull I mean you are returning your arm back to the starting position.

Does anyone know an efficient way of determining when a flick occurs but not a push?

Any ideas will be gratefully received.

Thanks

EXISTING CODE:

package com.example.testaccelerometer;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity implements SensorEventListener{

    public static TextView results;

    public static TextView clickresults;

    StringBuilder builder = new StringBuilder();

      private float mAccelNoGrav;
      private float mAccelWithGrav;
      private float mLastAccelWithGrav;

    public static boolean shakeIsHappening;
    public static int beatnumber = 0;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        results = (TextView) findViewById(R.id.results);
        clickresults = (TextView) findViewById(R.id.clickresults);

        SensorManager manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        Sensor accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        if(!manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_FASTEST)){
            builder.append("Problem with Accelerometer - Shaking will not work");
        };


        mAccelNoGrav = 0.00f;
        mAccelWithGrav = SensorManager.GRAVITY_EARTH;
        mLastAccelWithGrav = SensorManager.GRAVITY_EARTH;



    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        builder.setLength(0);
        builder.append("X " + event.values[0] + "\nY " + event.values[1] + "\nZ " + event.values[2]);
    results.setText(builder.toString());


        float x = event.values[0];
          float y = event.values[1];
          float z = event.values[2];
          mLastAccelWithGrav = mAccelWithGrav;
          mAccelWithGrav = android.util.FloatMath.sqrt(x*x + y*y + z*z);
          float delta = mAccelWithGrav - mLastAccelWithGrav;
          mAccelNoGrav = mAccelNoGrav * 0.9f + delta;

    if (mAccelNoGrav >8.5) {
        shakeIsHappening = true;
        //clickresults.append(" click " + mAccel);
    }

    if (shakeIsHappening == true && mAccelNoGrav <2) {
        beatnumber++;
clickresults.append(" click number: " + beatnumber + "\n" + "PA: " + mLastAccelWithGrav + " CA:" + mAccelNoGrav + "\n ");
shakeIsHappening = false;
    }

    }


    @Override
    protected void onResume() {
      super.onResume();
    // YOU DO NEED TO TRY AND REREGISTER IT NOW
    }

    @Override
    protected void onPause() {
        // YOU DO NEED TO TRY AND UNREGISTER IT NOW
      super.onPause();
    }


}
Dicky Moore
  • 956
  • 3
  • 10
  • 32
  • Can you post the formula that you're using to detect the flick? There should be some way to detect up or down flick / up or down are relative to current gravitation. – gunar Sep 03 '13 at 20:46
  • Thankyou, yes I'll do that now. – Dicky Moore Sep 03 '13 at 20:48
  • I am not a Physics expert, but what does `x*x + y*y + z*z` represent? I would rather base my logic on a history of recent events if the difference between x,y,z doesn't go [beyond a threshold](http://stackoverflow.com/questions/5271448/how-to-detect-shake-event-with-android). – gunar Sep 03 '13 at 20:59
  • they represent the acceleration across any axis, so the phone detects the flick event no-matter how you hold it. – Dicky Moore Sep 03 '13 at 21:17
  • If the accelerometer can detect the difference between being moved upwards and being moved downwards then that could do the trick. I'm not sure if we can use gravity to detect this but I'll try to work it out. – Dicky Moore Sep 03 '13 at 21:18
  • I'm going to try and get the app to work out whether the broad movement was an upward or downward movement on the z-axis. At the moment I have trouble working out how to get that information, have posted a question here: http://stackoverflow.com/questions/18615026/android-pushing-accelerometer-data-into-an-array – Dicky Moore Sep 04 '13 at 13:27

2 Answers2

1

You can use the geo-magnet sensor in conjunction with your accelerometer sensor to determine which edge of the device is facing toward the ground, and then exclude any acceleration events in the opposite direction

http://developer.android.com/reference/android/hardware/SensorManager.html#getOrientation(float[], float[])

jbr3zy
  • 644
  • 3
  • 8
0

I eventually figured out how to work this out, simply using the z axis to work out whether the motion is towards the ground or not.

The z axis data is fed into the z array (minus the force of gravity). When acceleration goes over a certain level, we record the highest number, highZ and the lowest number, lowZ. These mark out the range of the hand movement between high and low points. When the acceleration goes below 2, we check to see if the latest bit of data in the Z array is equal to the high point or the low point, and this tells us whether the movement was a flick or a pull.

It's probably not the most efficient way to work this out, but it's working now so I'm happy. Thanks for all your help, everyone.

Here's my finished code:

public class MainActivity extends Activity implements SensorEventListener {

    private float mAccelNoGrav;
    private float mAccelWithGrav;
    private float mLastAccelWithGrav;

    ArrayList<Float> z = new ArrayList<Float>();

    public static float finalZ;

    public static boolean shakeIsHappening;
    public static int beatnumber = 0;
    public static float highZ;
    public static float lowZ;
    public static boolean flick;
    public static boolean pull;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        results = (TextView) findViewById(R.id.results);
        clickresults = (TextView) findViewById(R.id.clickresults);

        SensorManager manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        Sensor accelerometer = manager
                .getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        if (!manager.registerListener(this, accelerometer,
                SensorManager.SENSOR_DELAY_FASTEST)) {
            builder.append("Problem with Accelerometer - Shaking will not work");
        }
        ;

        mAccelNoGrav = 0.00f;
        mAccelWithGrav = SensorManager.GRAVITY_EARTH;
        mLastAccelWithGrav = SensorManager.GRAVITY_EARTH;

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onSensorChanged(SensorEvent event) {


        float x = event.values[0];
        float y = event.values[1];
        z.add((event.values[2])-SensorManager.GRAVITY_EARTH);
        mLastAccelWithGrav = mAccelWithGrav;
        mAccelWithGrav = android.util.FloatMath.sqrt(x * x + y * y + z.indexOf(z.size()-1) * z.indexOf(z.size()-1));
        float delta = mAccelWithGrav - mLastAccelWithGrav;
        mAccelNoGrav = mAccelNoGrav * 0.9f + delta; // Low-cut filter

        if (mAccelNoGrav > 8.5) {
            shakeIsHappening = true;

            z.clear();

            if  (z.indexOf(z.size()-2) > z.indexOf(z.size()-1)) {
                clickresults.append(" Z shrinking" + z);
            } else if (z.indexOf(z.size()-2) < z.indexOf(z.size()-1)) {
                clickresults.append(" Z growing" + z);
            }

        } 




        if (shakeIsHappening == true && mAccelNoGrav < 2) {

            finalZ = z.get(z.size()-1);
            highZ= z.get(z.size()-1);
            lowZ= z.get(z.size()-1);
            for (int i = 0; i < z.size(); i++) {
                if (z.get(i) > highZ) {
                    highZ = z.get(i);
                } else if ((z.get(i) < lowZ)) {
                    lowZ = z.get(i);
            }
            if (highZ==finalZ) {
                flick = true;
                pull = false;
            } else if (lowZ==finalZ) {
                flick = false;
                pull = true;

            }

            if (flick) {
            beatnumber++;
            clickresults.append(" click number: " + beatnumber + "\n" + "PA: "
                    + mLastAccelWithGrav + " CA:" + mAccelNoGrav + "\n " + "Lz " + z.indexOf(z.size()-2) +"z " + z.indexOf(z.size()-1) + "\n" + "\n");
            shakeIsHappening = false;
            }

            z.clear();

        } }

    }

    @Override
    protected void onResume() {
        super.onResume();
        // YOU DO NEED TO TRY AND REREGISTER IT NOW
    }

    @Override
    protected void onPause() {
        // YOU DO NEED TO TRY AND UNREGISTER IT NOW
        super.onPause();
    }

}
Dicky Moore
  • 956
  • 3
  • 10
  • 32