📜 ⬆️ ⬇️

Android: measuring speed and distance with an accelerometer

Since I had a google phone, thoughts periodically wander in my head “what would be so funny to do with this very phone?” Having played with accelerometer control toys, I thought, what else can I do with this sensor? Of course, measure the acceleration! And, as a result, calculate the speed and distance traveled. Of course, using only an accelerometer imposes a number of restrictions on the measured: firstly, the movement must be straightforward, secondly - the orientation of the device in space should not change, thirdly - it is desirable to calibrate the sensor before starting the measurement. I’ll say right away that there are ways to soften these requirements, but more about that later.

The main question, as usual, "why?". Why is this if there is a GPS? Well, right remark. However, GPS does not work everywhere, but an accelerometer - it is on the phone with it. For example, tried to catch satellites in the subway? ..

From "Why" figured out, go to "How" ...
')
In order to respond to a change in acceleration, you must implement the SensorEventListener interface somewhere. Since we have not yet figured out what to do with it, we will create an abstract class
public abstract class Accelerometer implements SensorEventListener { protected float lastX; protected float lastY; protected float lastZ; public abstract Point getPoint(); public void onAccuracyChanged(Sensor arg0, int arg1) { } } 

And at the same time, the class for storing the sensor counter readings:
 public class Point { private float x = 0; private float y = 0; private float z = 0; private int cnt = 1; public float getX() { return x/(float)cnt; } public float getY() { return y/(float)cnt; } public float getZ() { return z/(float)cnt; } public Point(float x, float y, float z, int cnt) { this.x = x; this.y = y; this.z = z; this.cnt = cnt; } } 


And think about what to do next. The period of updating information from the sensor in the SENSOR_DELAY_GAME mode is approximately 20 milliseconds. This is quite often, our task does not require this. On the other hand, taking readings less often, we run the risk of getting into “outliers” and losing accuracy. It is logical to somehow regularly receive the average acceleration value, say, for the last second. To store an array and calculate the average value is expensive, it is much easier to add all the obtained values ​​and divide by the quantity. Also, consider dX, dY, dZ - our not yet implemented calibration.
Here's what happens:
 public class XYZAccelerometer extends Accelerometer { private static final int BUFFER_SIZE = 500; // calibration private float dX = 0; private float dY = 0; private float dZ = 0; // buffer variables private float X; private float Y; private float Z; private int cnt = 0; // returns last SenorEvent parameters public Point getLastPoint(){ return new Point(lastX, lastY, lastZ, 1); } // returrns parameters, using buffer: average acceleration // since last call of getPoint(). public Point getPoint(){ if (cnt == 0){ return new Point(lastX, lastY, lastZ, 1); } Point p = new Point(X, Y, Z, cnt); reset(); return p; } // resets buffer public void reset(){ cnt = 0; X = 0; Y = 0; Z = 0; } public void onSensorChanged(SensorEvent se) { float x = se.values[SensorManager.DATA_X] + dX; float y = se.values[SensorManager.DATA_Y] + dY; float z = se.values[SensorManager.DATA_Z] + dZ; lastX = x; lastY = y; lastZ = z; X+= x; Y+= y; Z+= z; if (cnt < BUFFER_SIZE-1) { cnt++; } else { reset(); } } public void setdX(float dX) { this.dX = dX; } public void setdY(float dY) { this.dY = dY; } public void setdZ(float dZ) { this.dZ = dZ; } } 


With your permission, I will skip the descriptions of the sensor calibration methods. Suffice it to say that it is necessary to remove pokings for some time, then install the corresponding dX, dY, dZ in our XYZAccelerometer. This procedure cannot be neglected, because while we are sleeping , the acceleration of free fall is constantly acting, and the sensor measures it.

For greater importance, let's get a class for storing and calculating the motion parameters on the interval:

 public class MeasurePoint { private float x; private float y; private float z; private float speedBefore; private float speedAfter; private float distance; private float acceleration; private long interval; public MeasurePoint(float x, float y, float z, float speedBefore, long interval) { this.x = x; this.y = y; this.z = z; this.speedBefore = speedBefore; this.interval = interval; speedAfter = 0; calc(); } private void calc(){ //Acceleration as projection of current vector on average acceleration = Math.sqrt(this.x*this.x+this.y*this.y*+this.z*this.z); float t = ((float)interval / 1000f); speedAfter = speedBefore + acceleration * t; distance = speedBefore*t + acceleration*t*t/2; } // add getters } 


And a class for storing information about the entire experiment:

 public class MeasureData { // points from accelerometr private LinkedList accData; private LinkedList data; // timer interval of generating points private long interval; public MeasureData(long interval) { this.interval = interval; accData = new LinkedList (); data = new LinkedList (); } public void addPoint(Point p){ accData.add(p); } public void process(){ for(int i = 0; i < accData.size(); ++i){ Point p = accData.get(i); float speed = 0; if(i > 0){ speed = data.get(i-1).getSpeedAfter(); } data.add(new MeasurePoint(p.getX(), p.getY(), p.getZ(), speed, interval)); } } public float getLastSpeed(){ return data.getLast().getSpeedAfter(); } public float getLastSpeedKm(){ float ms = getLastSpeed(); return ms*3.6f; } } 


I think everything is simple and clear. It remains only to use it in our Activity ... which, by the way, is not yet. Let's start with the layout:

 <serviceLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <serviceButton android:id="@+id/btn" android:text="TEST" android:layout_width="300px" android:layout_height="200px" android:onClick="onButtonTest" /> <serviceTextView android:id = "@+id/txt" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text=":" /> <service/LinearLayout> 


And the code:

 public class TestActivity extends Activity { static final int TIMER_DONE = 2; static final int START = 3; private StartCatcher mStartListener; private XYZAccelerometer xyzAcc; private SensorManager mSensorManager; private static final long UPDATE_INTERVAL = 500; private static final long MEASURE_TIMES = 20; private Timer timer; private TextView tv; private Button testBtn; int counter; private MeasureData mdXYZ; /** handler for async events*/ Handler hRefresh = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case TIMER_DONE: onMeasureDone(); String es1 = Float.toString(Math.round(mdXYZ.getLastSpeedKm()*100)/100f); tv.append(" END SPEED " + es1 + " \n"); enableButtons(); break; case START: tv.append(" START"); timer = new Timer(); timer.scheduleAtFixedRate( new TimerTask() { public void run() { dumpSensor(); } }, 0, UPDATE_INTERVAL); break; } } }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv = (TextView) findViewById(R.id.txt); testBtn = (Button) findViewById(R.id.btn); } @Override protected void onResume() { super.onResume(); tv.append("\n .."); mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); setAccelerometer(); setStartCatcher(); mSensorManager.registerListener(xyzAcc, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); } @Override protected void onPause() { mSensorManager.unregisterListener(xyzAcc); super.onPause(); } public void onButtonTest(View v) { disableButtons(); mdXYZ = new MeasureData(UPDATE_INTERVAL); counter = 0; tv.setText(""); hRefresh.sendEmptyMessage(START); } void dumpSensor() { ++counter; mdXYZ.addPoint(xyzAcc.getPoint()); if (counter > MEASURE_TIMES) { timer.cancel(); hRefresh.sendEmptyMessage(TIMER_DONE); } } private void enableButtons() { testBtn.setEnabled(true); } private void setAccelerometer() { xyzAcc = new XYZAccelerometer(); mSensorManager.registerListener(xyzAcc, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI); } private void disableButtons() { testBtn.setEnabled(false); } private void onMeasureDone() { mdXYZ.process(); } } 


That's all. Surprisingly, on a level trajectory, this method gives very good measurement accuracy.

I enclose a graph of one experiment: the blue line is the speed calculated by the accelerometer, the red one is taken from the GPS with the maximum frequency. Black blot - the speed of the speedometer at the end of the experiment.

image

Source: https://habr.com/ru/post/131555/


All Articles