Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to measure the how far the phone travels vertically

Tags:

android

I'm just wondering if it is possible / where would I get started in measuring the upwards and downwards movement of an Android device. I'd need it to be as accurate as possible.

I'm quite lost of where to look I've seen there are certain methods here, and looking at these old questions they mention it's quite difficult but I wanted to know if there had been any improvements since then in newer versions of android.

The image below is an extra example of the the direction I would like the phone to move in.

enter image description here

like image 420
Jack Avatar asked Oct 05 '16 21:10

Jack


3 Answers

Before going to solution, I would split the solution in small assets and put the puzzle for solution

  1. We need to listen to phone sensors
  2. We need to check phone is in vertical position or not
  3. We need to make a time counter that register the phone vertically and count up
  4. We need to display the time on screen

For step one, I would implement SensorEventListener to our class, this will allow us to use onSensorChanged method.

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        listenToSensors(event);
    }
}

private void listenToSensors(SensorEvent event) {
    if (isPhoneVertical(event)) {
        timeCounter();
        if (mStatus) {
            mStartTime = getStartTime();
            mStatus = false;
        }
    } else {
        if (!mStatus) {
            mTotalTime = getDiffTime() + mTotalTime;
            mStatus = true;
        }
    }
}

For step two, I have built a method called isPhoneVertical to check if our phone is in vertical way or not, it is primarily checking the y axis. You can change the steep degree by changing maxVertical. Less value her less steep, 0 means the phone should almost be 100% vertical. For my test it is set to 3.

private boolean isPhoneVertical(SensorEvent event) {
    float[] values = event.values;
    double y = values[1];
    // do not change this value
    double yAxisInitValue = 10.0;
    double verMargin = yAxisInitValue - maxVertical;

    return y >= verMargin;
}

For step 3 I have made few method to check start time and stop time and update a global variable that keep track of time in seconds.

private long getStartTime() {
    return System.currentTimeMillis() / 1000;
}

private long getDiffTime() {
    return getStartTime() - mStartTime;
}

For step 4 I have made a regular runOnUiThread to update the time on screen.

private void timeCounter() {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mView1.setText("Phone has been vertical for: " + getDiffTime() + " seconds");
            mView2.setText("The total time: " + (mTotalTime + getDiffTime()) + "");
        }
    });
}

That said this solution is to illustrate how this goal can be reached, I am sure it can be done different ways. But I wanted to show the logic behind the solution.

And here is a screen shot of the app that counts the time for each time the phone is vertical and total time it has been vertical.

enter image description here

The solution including some explanations:

MainActivity.java

public class MainActivity extends Activity implements SensorEventListener {
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;
    private TextView mView1, mView2;
    private long mStartTime;
    private long mTotalTime;
    private boolean mStatus = true;
    // less value her less steep, 0 means the phone should almost be 100% vertical
    // try it out
    private double maxVertical = 3.0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mView1 = (TextView) findViewById(R.id.textView1);
        mView2 = (TextView) findViewById(R.id.textView2);

        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            listenToSensors(event);
        }
    }

    private void listenToSensors(SensorEvent event) {
        if (isPhoneVertical(event)) {
            timeCounter();
            if (mStatus) {
                mStartTime = getStartTime();
                mStatus = false;
            }
        } else {
            if (!mStatus) {
                mTotalTime = getDiffTime() + mTotalTime;
                mStatus = true;
            }
        }
    }

    // This method return true only for specific phone orientation
    // y axis for vertical orientation
    private boolean isPhoneVertical(SensorEvent event) {
        float[] values = event.values;
        double y = values[1];
        // do not change this value
        double yAxisInitValue = 10.0;
        double verMargin = yAxisInitValue - maxVertical;

        return y >= verMargin;
    }

    private long getStartTime() {
        return System.currentTimeMillis() / 1000;
    }

    private long getDiffTime() {
        return getStartTime() - mStartTime;
    }

    // update steps in user interface
    private void timeCounter() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mView1.setText("Phone has been vertical for: " + getDiffTime() + " seconds");
                mView2.setText("The total time: " + (mTotalTime + getDiffTime()) + "");
            }
        });
    }

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

    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this,
                mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // if you want to collect data while mobile screen off, just disable the
        // following line, the app will still collecting sensor data
        mSensorManager.unregisterListener(this);
    }
}

Activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.maytham.verticaltravels.MainActivity">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:textSize="20sp"
        android:text="TextView1" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="57dp"
        android:textSize="20sp"
        android:text="TextView2" />
</RelativeLayout>

I will leave some link for reading as well.

  • https://developer.android.com/reference/android/hardware/SensorManager.html
  • http://www.livescience.com/40103-accelerometer-vs-gyroscope.html
  • https://physics.stackexchange.com/questions/41653/how-do-i-get-the-total-acceleration-from-3-axes
  • https://code.tutsplus.com/tutorials/how-to-recognize-user-activity-with-activity-recognition--cms-25851
  • http://www.vogella.com/tutorials/AndroidSensor/article.html
  • How to get Android phone orientation matching human orientation?

If you think I might add more information that can help you further with your question please let me know. It is also unclear if you was looking for walking detection/counter, if that is some thing you are interesting in, look at following answers/links.

  • Activity Recognition Api
  • How to detect walking with Android accelerometer
  • how to calculate exact foot step count using accelerometer in android?
  • Detect Movement Accurately using Accelerometer in Android

and GitHub source codes

  • https://github.com/bagilevi/android-pedometer
  • https://github.com/tartakynov/robowalk
like image 125
Maytham Avatar answered Nov 16 '22 02:11

Maytham


Before,We have to know about sensor API which is we are using.

Android Sensor API

Android sensor API provides many classes and interface. The important classes and interfaces of sensor API are as follows:

1) SensorManager class

The android.hardware.SensorManager class provides methods :

to get sensor instance,
to access and list sensors,
to register and unregister sensor listeners etc.

You can get the instance of SensorManager by calling the method getSystemService() and passing the SENSOR_SERVICE constant in it.

SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE);  

2) Sensor class

The android.hardware.Sensor class provides methods to get information of the sensor such as sensor name, sensor type, sensor resolution, sensor type etc.

3) SensorEvent class

Its instance is created by the system. It provides information about the sensor.

4) SensorEventListener interface

It provides two call back methods to get information when sensor values (x,y and z) change or sensor accuracy changes. Public and abstract methods Description

  void onAccuracyChanged(Sensor sensor, int accuracy)

    //it is called when sensor accuracy is changed.

    *void onSensorChanged(SensorEvent event)*

    //it is called when sensor values are changed.

Note: assume *X=horizontal side,Y=Vertical side and Z=elevation * of the device.

create a simple xml like active_main.xml

 <RelativeLayout xmlns:androclass="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        tools:context=".MainActivity" >  

        <TextView  
            android:id="@+id/textView1"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_alignParentLeft="true"  
            android:layout_alignParentTop="true"  
            android:layout_marginLeft="92dp"  
            android:layout_marginTop="114dp"  
            android:text="TextView" />  

    </RelativeLayout>  

Next create a simple activity like MainActivity.java to measure directions like downward/upwards.

import android.app.Activity;  
import android.os.Bundle;  
import android.widget.TextView;  
import android.widget.Toast;  
import android.hardware.SensorManager;  
import android.hardware.SensorEventListener;  
import android.hardware.SensorEvent;  
import android.hardware.Sensor;  
import java.util.List;  
public class MainActivity extends Activity {  
    SensorManager sm = null;  
    TextView textView1 = null;  
    List list;  

    SensorEventListener sel = new SensorEventListener(){  
        public void onAccuracyChanged(Sensor sensor, int accuracy) {}  
        public void onSensorChanged(SensorEvent event) {  
            float[] values = event.values;  
            textView1.setText("x: "+values[0]+"\ny: "+values[1]+"\nz: "+values[2]);  
        }  
    };  

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

        /* Get a SensorManager instance */  
        sm = (SensorManager)getSystemService(SENSOR_SERVICE);  

        textView1 = (TextView)findViewById(R.id.textView1);  

        list = sm.getSensorList(Sensor.TYPE_ACCELEROMETER);  
        if(list.size()>0){  
            sm.registerListener(sel, (Sensor) list.get(0), SensorManager.SENSOR_DELAY_NORMAL);  
        }else{  
            Toast.makeText(getBaseContext(), "Error: No Accelerometer.", Toast.LENGTH_LONG).show();  
        }  
    }  

    @Override  
    protected void onStop() {  
        if(list.size()>0){  
          sm.unregisterListener(sel);  
        }  
        super.onStop();  
    }  
}  

Output seems like:

X=0.0[No Move On Horizontal]

Y=9.77622[Moved on Vertically]

Z=0.813417[Move on Elevation]

like image 30
brahmy adigopula Avatar answered Nov 16 '22 03:11

brahmy adigopula


Case 1: Using GPS

GPS built in in almost all android devices can provide position with best possible accuracy of about 2-3 meters horizontally and 4-5 meters vertically. If that vertical accuracy is ok for your case, you can get altitude before the movement starts, and when it ends, compare them and get vertical movement.

If you need better accuracy, no android customer level device GPS can provide it. There are special professional GPS devices (used by land surveyors, costing 1000s of dollars, and very rarely running android os) that can provide cm-level accuracy, but this is not what we are talking about here.

Ofcource, you need to be outdoors in order to have good GPS reception.

Case 2: Using motion sensors

If one knows initial speed, acceleration, and time can calculate distance moved. Have a look at this http://www.dummies.com/education/science/physics/finding-distance-using-initial-velocity-time-and-acceleration/

We have

distance=start speed+ (1/2) * acceleration * time^2

In our case we know time (actually time-span) from the devices inbuilt clock, and we can get acceleration using the device's sensors. Note that TYPE_ACCELEROMETER sensor gives us acceleration force along each axis including gravity, so we have to subtract the gravitational force (we get it from TYPE_GRAVITY sensor). Keep in mind that when someone picks up a device, his hand doesn't necessarily moves in steady speed or acceleration. Therefore, changes in speed and acceleration must be taken into consideration.

Concerning start speed there are several cases.

In case it is zero (user is standing in a building, picks phone from a table and moves it to his ear) you just omit it from the formulas.

In case it not zero so that

speed=device speed+user's "vehicle" speed

, we have to use GPS to calculate it. If the user is in a lifting helicopter we can do this. In case he is in a elevator or going up the stairs of a building we cannot because there is no GPS reception there. Things get more complicated in case the helicopter or the elevator accelerates too because there is no way to automatically separate helicopter/elevator acceleration from "user moving the device" acceleration. Same when the user is walking. In that case continuous changes of speed, acceleration and direction take place as he does his steps and the body moves up and down.

I confess that I have no idea on the actual accuracy one can get in calculated distance by using the motion sensors method. And as you see from what I am writing there is no way I think to find a solution that works under every circumstances. But in the special case that the user is still and just moves the device vertically you can do something I believe.

P.S. I am not getting into details on how to get GPS data or Motion sensor data from the device sensors because you can easily google it and find help on that. I believe what is more important is to understand the maths and physics that get involved in the devices vertical movement.

like image 44
geo Avatar answered Nov 16 '22 03:11

geo