How to shake up and down using Accelerometer in Android? - android

I tried the following way, but it doesn't work perfectly.. If device is kept in normal way, then also it fires shake event.
#Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
long curTime = System.currentTimeMillis();
// only allow one update every 200Ms.
if ((curTime - lastUpdate) > 200) {
lastUpdate = curTime;
x = event.values[SensorManager.DATA_X];
y = event.values[SensorManager.DATA_Y];
z = event.values[SensorManager.DATA_Z];
Vibrator vibrate = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (Round(y, 4) > 22) {
vibrate.vibrate(200);
Log.d("sensor", "==== Up Detected===");
} else if (Round(y, 4) < -20) {
vibrate.vibrate(200);
Log.d("sensor", "==== Down Detected=== ");
}
mLastX = x;
mLastY = y;
mLastZ = z;
}
}
public static float Round(float Rval, int Rpl) {
float p = (float) Math.pow(10, Rpl);
Rval = Rval * p;
float tmp = Math.round(Rval);
return (float) tmp / p;
}
Please Help.
Any Help would be highly appreciated..
Thanks

Sensor values are very raw and noisy. You need to add a layer of digital signal processing on top of them to get good results. Just using the raw values will lead to a lot of jitter in your results. You're trying to do some basic ones with your time delay, but you need to do more filtering.
Also, you're vibrating in response to a shake. That vibrate will cause the accelerometer to see movement, leading to more false positives.

Apply lowpass filter on as your y-axis values.
The basic filter will be as mentioned in documentation.
public void onSensorChanged(SensorEvent event){
// In this example, 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.8;
// 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];
}

Related

How to Calculate vertical distance traveled by Android Phone when falling from hand?

So I have used the accelerometer for checking if the phone is falling. This part is working great.
I used the below-given link for the above purpose.
https://github.com/altermarkive/experimental-fall-detector-android-app
Now the next step was to calculate the distance it traveled vertically. I have tried so many things for this purpose but every time the height is coming incorrect. Also, the height varies on different devices differently.
I am getting the acceleration data from the Accelerometer sensor, after which I have used the below-given formulas for calculating the distance traveled.
The 1st code I tried : -
long curTime = System.currentTimeMillis();
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// sampling frequency f= 10Hz.
if ((curTime - lastUpdate) > CHECK_INTERVAL) {
long diffTime = (curTime - lastUpdate);
lastUpdate = curTime;
accel_values = event.values.clone();
if (last_accel_values != null) {
mAccelLast = mAccelCurrent;
mAccelCurrent =(float)Math.sqrt(accel_values[0]* accel_values[0] +
accel_values[1]*accel_values[1]
+ accel_values[2]*accel_values[2]);
Message msg = mHandler.obtainMessage(Constants.MESSAGE_CHANGED);
Bundle bundle = new Bundle();
bundle.putFloat(Constants.VALUE, mAccelCurrent);
msg.setData(bundle);
mHandler.sendMessage(msg);
mWindow.add(mAccelCurrent);
if (mWindow.isFull() && mWindow.isFallDetected()){
Log.w(TAG, "Fall detected by window class");
actime = curTime - diffTime;
velocity = actime * acceleration;
avgvelocity = velocity / 2;
height = avgvelocity * actime;
mWindow.clear();
msg = mHandler.obtainMessage(Constants.MESSAGE_EMERGENCY);
mHandler.sendMessage(msg);
}
}
last_accel_values = accel_values.clone();
}
}
The 2nd code I tried : -
final double alpha = 0.8;
double gravity[] = new double[3], linear_acceleration[] = new double[3];
// Isolate the force of gravity with the low-pass filter.
gravity[0] = alpha * gravity[0] + (1 - alpha) * sensorEvent.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * sensorEvent.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * sensorEvent.values[2];
double curr_gravity = gravity[0] + gravity[1] + gravity[2];
// Remove the gravity contribution with the high-pass filter.
linear_acceleration[0] = sensorEvent.values[0] - gravity[0];
linear_acceleration[1] = sensorEvent.values[1] - gravity[1];
linear_acceleration[2] = sensorEvent.values[2] - gravity[2];
double curr_acc = linear_acceleration[0] + linear_acceleration[1] + linear_acceleration[2];
long seconds = System.currentTimeMillis();
double velocity = curr_acc * seconds;
double init_vel = velocity / 2;
double time = (velocity - init_vel) / curr_gravity;
double height = (((seconds * 9.8)/2) - init_vel);
The 3rd formula I tried:-
long curTime = System.currentTimeMillis();
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// sampling frequency f= 10Hz.
if ((curTime - lastUpdate) > CHECK_INTERVAL) {
long diffTime = (curTime - lastUpdate);
lastUpdate = curTime;
accel_values = event.values.clone();
if (last_accel_values != null) {
mAccelLast = mAccelCurrent;
mAccelCurrent =(float)Math.sqrt(accel_values[0]* accel_values[0] +
accel_values[1]*accel_values[1]
+ accel_values[2]*accel_values[2]);
Message msg = mHandler.obtainMessage(Constants.MESSAGE_CHANGED);
Bundle bundle = new Bundle();
bundle.putFloat(Constants.VALUE, mAccelCurrent);
msg.setData(bundle);
mHandler.sendMessage(msg);
mWindow.add(mAccelCurrent);
if (mWindow.isFull() && mWindow.isFallDetected()){
Log.w(TAG, "Fall detected by window class");
actime = curTime - diffTime;
height = 0.5*9.8*actime*actime;
mWindow.clear();
msg = mHandler.obtainMessage(Constants.MESSAGE_EMERGENCY);
mHandler.sendMessage(msg);
}
}
last_accel_values = accel_values.clone();
}
}
The fourth formula and the current code in use:-
public void onSensorChanged(SensorEvent event) {
long curTime = System.currentTimeMillis();
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// sampling frequency f= 10Hz.
if ((curTime - lastUpdate) > CHECK_INTERVAL) {
lastUpdate = curTime;
accel_values = event.values.clone();
if (last_accel_values != null) {
mAccelLast = mAccelCurrent;
final double alpha = 0.8;
double gravity[] = new double[3], linear_acceleration[] = new double[3];
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];
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
double mAccelCurrent = linear_acceleration[0] + linear_acceleration[1] + linear_acceleration[2];
double loX = event.values[0];
double loY = event.values[1];
double loZ = event.values[2];
double loAccelerationReader = Math.sqrt(Math.pow(loX, 2)
+ Math.pow(loY, 2)
+ Math.pow(loZ, 2));
DecimalFormat precision = new DecimalFormat("0.00");
double ldAccRound = Double.parseDouble(precision.format(loAccelerationReader));
// Send the value back to the Activity
Message msg = mHandler.obtainMessage(Constants.MESSAGE_CHANGED);
Bundle bundle = new Bundle();
bundle.putFloat(Constants.VALUE, (float) mAccelCurrent);
msg.setData(bundle);
mHandler.sendMessage(msg);
mWindow.add((float) mAccelCurrent);
if (mWindow.isFull() && mWindow.isFallDetected() && ldAccRound > 12d) {
long stop = System.currentTimeMillis();
Log.e(TAG, "Fall detected by window class");
long time = stop - curTime;
double currtime1 = (double) time / 1000;
velocity = currtime1 * mAccelCurrent;
height = velocity * currtime1 * currtime1;
Log.e("Height", "Vel : " + velocity + ", Avg : " + avgvelocity + ", Height : " +
height);
mWindow.clear();
if (height > 0) {
AppPreferences.setFall(context, String.valueOf(height));
/*mAccelCurrent = 0;
velocity = 0;
avgvelocity = 0;
height = 0;*/
msg = mHandler.obtainMessage(Constants.MESSAGE_EMERGENCY);
mHandler.sendMessage(msg);
} else
Toast.makeText(context, "Height not calculated properly. Please drop again",
Toast.LENGTH_LONG).show();
}
}
last_accel_values = accel_values.clone();
}
}
}
I also tried using other sensors for the same purpose like Barometer but I'm getting the same result from them (height is not coming as expected). Also, I tried looking for third party SDKs but they are also using their own hardware.
Any kind of help will be appreciated. Also if anyone could guide in the proper way, that would be really helpful.
Thanks in advance.
It seems like you have the same mistake in all three approaches. In all three, at some point you multiply your acceleration with an absolute timestamp from System.currentTimeMillis(). This is milliseconds since 1970 and has no direct meaning in your situation unless you use it to calculate time intervals as differences. In some examples you do that, but you subtract it again from System.currentTimeMillis() which again results in milliseconds since 1970.
It looks like you try to implement s = 1/2 a t², but this is only valid for a constant acceleration from rest over a time interval t. If you want to derive the distance from a sequence of measure accelerations, you need to numerically integrate them (this sounds harder than it is) and probably want to employ some filtering.
However, my recommendation is to simply assume a free fall with an acceleration of 9.81 m/s². This neglects air drag or local variations in Earth's acceleration, but unless you want to use this in some weird situations, this is probably much more precise than using the readings from the sensor. Espacially when the phone rotates, but also due to bad calibration and some filtering to split actual acceleration from gravitational acceleration, I would not expect the sensor readings to be superior to approximating a perfect free fall. After all, smartphones are rather dense objects and not to much influenced by air drag.
The upside is that you can simply use s = 1/2 a t². Just make sure that t is not a time interval since 1970, but the timer interval from the beginning of the fall (which you said you can detect reliably) until the end of the fall (i.e. the difference of the time stamp at the beginning and the time stamp at the end). Also, I would suggest to use the time from the sensor events instead of System.currentTimeMillis() as it has a better resolution and is designed for this type of calculations.

Low Pass Filter for Android Pressure Sensor

Have tried to adapt the android developer documentation for accelerator LPF but it does not seem to work with pressure
float pressure_value = 0.0f;
float height = 0.0f;
float height2 = (float) 964.98;
final float alpha = (float) 0.8;
if( Sensor.TYPE_PRESSURE == event.sensor.getType() ) {
pressure_value = event.values[0];
event.values[0] = alpha * event.values[0] + (1 - alpha) * event.values[0];
event.values[1] = alpha * event.values[1] + (1 - alpha) * event.values[1];
event.values[2] = alpha * event.values[2] + (1 - alpha) * event.values[2];
does anyone have some insight?
The pressure sensor only has one value i.e. event.values[0]. A simple low pass filter for that would look like:
pressure = alpha*event.values[0] + (1 - alpha)*pressure
Extremely oversimplified explanation:
For alpha=0.8, the 'new' pressure value is 80% of the actual current pressure supplied by the sensor + 20% of the value of the 'old' pressure. Increasing the alpha value will make it more responsive to pressure fluctuations, lower alpha values will make it less noisy (more filtered).
More explanatory code:
private float alpha = 0.8f;
private float filteredPressure = 0.0f;
#Override
public final void onSensorChanged(SensorEvent event) {
if (Sensor.TYPE_PRESSURE == event.sensor.getType()) {
float currentPressure = event.values[0];
filteredPressure = (alpha*currentPressure) + (1 - alpha)*filteredPressure;
}
}

Get Gravity from Android Accelerometer

I'm working with the Accelerometer Sensor from my Android, but I have a problem.
I need to get only gravity and i don't know how to do.
I read in the official documentation of Android the following method:
public void onSensorChanged(SensorEvent event)
{
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter's time-constant
// and dT, the event delivery rate
final float alpha = 0.8;
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];
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
}
So I guess would be good if I take the gravity[] values.
I'm I right?
Sensor is: BMA250 3 axis accelerometer, documentation is in:
BMA250

Android magnetometer global coordinates

I have an app that reads the values returned by the magnetometer.
The way it does it at the moment is in the phone's coordinates system.
I want to be able to always read the magnetic field vector in a global coordinates system (x,y,z - east, north, sky)
The code I tried (inspired from this question) will result in a value of 0 for the x axis component, and variable y,z depending on the way I tilt the phone. As far I understood, the rotation matrix transform should make my coordinates system global, but it doesn't seem that way.
The expected behaviour would be to have the same value on the x,y,z components as long as I hold the phone in one place, no matter its tilt.
private final float alpha = (float) 0.8;
private float gravity[] = new float[3];
private float magnetic[] = new float[3];
public void onSensorChanged(SensorEvent event) {
Sensor sensor = event.sensor;
if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// 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];
} else if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magnetic[0] = event.values[0];
magnetic[1] = event.values[1];
magnetic[2] = event.values[2];
float[] R = new float[9]; //rotation matrix
float[] I = new float[9]; //inclination
SensorManager.getRotationMatrix(R, I, gravity, magnetic);
float [] A_D = event.values.clone(); // device coordinates
float [] A_W = new float[3]; // global coodinates
A_W[0] = R[0] * A_D[0] + R[1] * A_D[1] + R[2] * A_D[2];
A_W[1] = R[3] * A_D[0] + R[4] * A_D[1] + R[5] * A_D[2];
A_W[2] = R[6] * A_D[0] + R[7] * A_D[1] + R[8] * A_D[2];
Log.d("Field","\nX :"+A_W[0]+"\nY :"+A_W[1]+"\nZ :"+A_W[2]);
}
The code is correct but there is always fluctuation as gravity is only an estimate. Actually, if the device is still, the accelerometer in the world coordinate should theoretically be the same independent of position since the only force acting on it is minus gravity and thus accelerometer should be gravity when the device is still. You can only expect that the East and North coordinates are very small when the device is still.

Linear acceleration direction to track upward and downward movement of phone

I am trying to track the movement of the device only on the vertical direction, i.e. upward and downward movement. This should be irrespective of the orientation of the device. Things that i already know or have tried are these
Linear acceleration is given by sensor TYPE_LINEAR_ACCELERATION and the axes is the phone axes and hence tracking any particular axes does not make a difference.
I tried applying transpose or inverse of rotation vector( inverse or transpose for the rotation vector are same) and then tried tracking the z direction of the linear acceleration vector. Does not seem to help.
I am trying to do a dot product with gravity values (TYPE_GRAVITY) to get the direction of the acceleration but it seems to be error prone. Even when i move my device swiftly up, it says going down.
I will outline this method here
dotProduct = vectorA[0]*vectorB[0]+vectorA[1]*vectorB[1] + vectorA[2]*vectorB[2];
cosineVal = dotProduct/(|vectorA|*|vectorB|)
if(cosineVal > 0 ) down else Up.
What is the flaw with the method ? Please help, I have been stuck on this for some time now.
As I see it, in the 3rd method you trying to find the cos of angle between two vectors (gravity vector and acceleration vector). And the idea is if the angle is close to 180 degrees you have up movement, if angle is close to 0 degrees you have down movement. Cosine is function that has positive value when angle is from -90 to 90 degrees. So when your cosineVal value is positive it means phone is going down and even if cosineVal closer to 1 movement is straight down. So it is true vice versa. When cosine is negative ( from 90 degrees to 270) you have up movement.
Probably you can get vectors from Sensor.TYPE_ACCELEROMETER from https://developer.android.com/reference/android/hardware/SensorEvent.html#values there you have gravity vector and acceleration vector.
I made a code snippet below you can try.
public class MainActivity extends AppCompatActivity implements SensorEventListener {
private float[] gravity = new float[3];
private float[] linear_acceleration = new float[3];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onSensorChanged(SensorEvent event) {
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter's time-constant
// and dT, the event delivery rate
final float alpha = 0.8f;
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];
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 scalarProduct = gravity[0] * linear_acceleration[0] +
gravity[1] * linear_acceleration[1] +
gravity[2] * linear_acceleration[2];
float gravityVectorLength = (float) Math.sqrt(gravity[0] * gravity[0] +
gravity[1] * gravity[1] + gravity[2] * gravity[2]);
float lianearAccVectorLength = (float) Math.sqrt(linear_acceleration[0] * linear_acceleration[0] +
linear_acceleration[1] * linear_acceleration[1] + linear_acceleration[2] * linear_acceleration[2]);
float cosVectorAngle = scalarProduct / (gravityVectorLength * lianearAccVectorLength);
TextView tv = (TextView) findViewById(R.id.tv);
if (lianearAccVectorLength > 2) {//increase to detect only bigger accelerations, decrease to make detection more sensitive but noisy
if (cosVectorAngle > 0.5) {
tv.setText("Down");
} else if (cosVectorAngle < -0.5) {
tv.setText("Up");
}
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
}

Categories

Resources