I'm developing a Android application and I would like to know if is possible detect the direction of movement with one axis fixed. For example, I want put my phone on the table and detect the direction when I move it (left, right, up and down). The distance is not necessary, I just want know the accurate direction.
Yes.
Using the SensorEventListener.onSensorChanged(SensorEvent event) you can determine the values provided along the X & Y axis. You would need to record these values and then compare them to any new values that you receive on subsequent calls to the onSensorChanged method to get a delta value. If the delta value on one axis is positive then the device is moving one way, if its negative its moving the opposite way.
You will probably need to fine tune both the rate at which you receive accelerometer events and the threshold at which you consider a delta value to indicate a change in direction.
Here's a quick code example of what I'm talking about:
public class AccelerometerExample extends Activity implements SensorEventListener {
TextView textView;
StringBuilder builder = new StringBuilder();
float [] history = new float[2];
String [] direction = {"NONE","NONE"};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = new TextView(this);
setContentView(textView);
SensorManager manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor accelerometer = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);
}
#Override
public void onSensorChanged(SensorEvent event) {
float xChange = history[0] - event.values[0];
float yChange = history[1] - event.values[1];
history[0] = event.values[0];
history[1] = event.values[1];
if (xChange > 2){
direction[0] = "LEFT";
}
else if (xChange < -2){
direction[0] = "RIGHT";
}
if (yChange > 2){
direction[1] = "DOWN";
}
else if (yChange < -2){
direction[1] = "UP";
}
builder.setLength(0);
builder.append("x: ");
builder.append(direction[0]);
builder.append(" y: ");
builder.append(direction[1]);
textView.setText(builder.toString());
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// nothing to do here
}
}
This code will only provide you with the general direction on the X and Y axis that the phone has moved in. To provide a more fine grained determination of direction (e.g. to attempt to mimic the movement of a computer mouse) you might find that a phone's accelerometer is not fit for purpose.
To attempt this, I would first set the sensor delay to SensorManager.SENSOR_DELAY_FAST and create a list of multiple history events so that I could detect movement over time and not be influenced by slight movements in the opposite direction that often happen when taking accelerometer measurements at such a fine level. You would also need to measure the amount of time that has passed to help calculate the accurate measure of movement as suggested in your comments.
From what I've read, with the sensors one can detect only accelerations and phone orientation. So you can easily detect the start of the movement (and in which direction) and the stop of the movement, since the velocity is changing so there is acceleration (when stoping acceleration is against the velocity direction).
If the phone is moving with constant velocity, the accelerometers will give zero values (linear acceleration which subtracts gravity). So in order to know if the phone is moving you should compute the velocity at each instant, by
V(t)=V(t-1)+a*dt
in which:
V(t-1) is the known velocity at previous instant,
V(t) is the velocity at current instant,
a is the acceleration (consider the acceleration in previous instant or
mean acceleration between previous and current instant).
The problem is that due to the uncertainty of the sensor values, you might end up summing small errors at each instant, which will lead to erroneous velocity values. Probably you'll have to adjust a low pass filter to the values.
Related
Lets say you have the acceleration readings in all the 3 dimensions i.e X, Y and Z. How do you infer using the readings the phone was tilted left or right? The readings get generated every 20ms.
I actually want the logic of inferring the tilt from the readings. The tilt needs to be smooth.
A tilt can be detected in a sort of diferent ways. You can take into account 1 axis, 2 axis, or the 3 axis. Depending on how accurate you want it, and how much you feel like fighting with maths.
If you use only one axis, it is quite simple. Think the mobile is completely horizontal, and you move it like this:
using just one axis, lets say, axis x, will be enough, since you can detect accurately a change in that axis position, since even any small movement will do a change in the axis.
But, if your application is only reading that axis, and the user has the phone almost vertical, the difference in x axis will be really small even rotating the phone a big angle.
Anyways,for applications that only need coarse resolution, a single-axis can be used.
Referring to basic trigonometry, the projection of the gravity vector on the x-axis produces an output acceleration equal to the sine of the angle between the accelerometer x-axis and the horizon.
This means that having the values of an axis (those are acceleration values) you can calculate the angle in which the device is.
this means that the value given to you by the sensor, is = to 9,8 * sine of the angle, so doing the maths you can get the actual angle.
But don't worry, you don't even have to do this. Since the values are more or less proportional, as you can see in the table below, you can work directly with the value of the sensor, without taking much care of what angle represents, if you don't need it to be much accurate, since a change in that value means a proportional change in the angle, so with a few test, you will find out how big should be the change in order to be relevant to you.
So, if you take the value over the time, and compare to each other, you can figure out how big the rotation was. For this,
you consider just one axis. this will be axis X.
write a function to get the difference in the sensor value for that axis between one function call, and the next
Decide a maximum time and a minimum sensor difference, that you will consider a valid movement (e.g. a big rotation is good but only if it is fast enough, and a fast movement is good only if the difference in the angle is big enough)
if you detect two measurements that accomplish those conditions, you take note of half tilt done (in a boolean for instance), and start measuring again, but now, the new reference value is the value that was considered half tilt.
if the last difference was positive, now you need a negative difference, and if the last difference was negative, now you need a positive difference; this is, coming back. so start taking values comparing the new reference value with the new values coming from the sensor, and see if one accomplish what you decided in point 3.
if you find a valid value (accomplishing value difference and time conditions ), you have a tilt. But if you dont get a good value and the time is consumed, you reset everything: let your reference value be the last one, reset the timers, reset the half-tilt-done boolean to false, and keep measuring.
I hope this is good enough for you. For sure you can find some libraries or code snippets to help you out with this, but i think is good, as you say, to know the logic of inferring the tilt from the readings
The pictures was taken from this article, wich i recomend to read if you want to improve the accuracy and consider 2 o 3 axis for the tilt
The commonsware Sensor Monitor app does a pretty good job with this. It converts the sensor readouts to X, Y, Z values on each sensor reading, so it's pretty easy from there to determine which way the device is moving.
https://github.com/commonsguy/cw-omnibus/tree/master/Sensor/Monitor
Another item worth noting (from the Commonsware book):
There are four standard delay periods, defined as constants on the
SensorManager class:
SENSOR_DELAY_NORMAL, which is what most apps would use for broad changes, such as detecting a screen rotating from portrait to
landscape
SENSOR_DELAY_UI, for non-game cases where you want to update the UI continuously based upon sensor readings
SENSOR_DELAY_GAME, which is faster (less delay) than SENSOR_DELAY_UI, to try to drive a higher frame rate
SENSOR_DELAY_FASTEST, which is the “firehose” of sensor readings, without delay
You can use the accelerometer and magnetic field sensor to accomplish this. You can call this method in your OnSensorChanged method to detect if the phone was tilt upwards. This currently only works if the phone is held horizontally. Check the actual blog post for a more complete solution.
http://www.ahotbrew.com/how-to-detect-forward-and-backward-tilt/
public boolean isTiltUpward()
{
if (mGravity != null && mGeomagnetic != null)
{
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
if (success)
{
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
/*
* If the roll is positive, you're in reverse landscape (landscape right), and if the roll is negative you're in landscape (landscape left)
*
* Similarly, you can use the pitch to differentiate between portrait and reverse portrait.
* If the pitch is positive, you're in reverse portrait, and if the pitch is negative you're in portrait.
*
* orientation -> azimut, pitch and roll
*
*
*/
pitch = orientation[1];
roll = orientation[2];
inclineGravity = mGravity.clone();
double norm_Of_g = Math.sqrt(inclineGravity[0] * inclineGravity[0] + inclineGravity[1] * inclineGravity[1] + inclineGravity[2] * inclineGravity[2]);
// Normalize the accelerometer vector
inclineGravity[0] = (float) (inclineGravity[0] / norm_Of_g);
inclineGravity[1] = (float) (inclineGravity[1] / norm_Of_g);
inclineGravity[2] = (float) (inclineGravity[2] / norm_Of_g);
//Checks if device is flat on ground or not
int inclination = (int) Math.round(Math.toDegrees(Math.acos(inclineGravity[2])));
/*
* Float obj1 = new Float("10.2");
* Float obj2 = new Float("10.20");
* int retval = obj1.compareTo(obj2);
*
* if(retval > 0) {
* System.out.println("obj1 is greater than obj2");
* }
* else if(retval < 0) {
* System.out.println("obj1 is less than obj2");
* }
* else {
* System.out.println("obj1 is equal to obj2");
* }
*/
Float objPitch = new Float(pitch);
Float objZero = new Float(0.0);
Float objZeroPointTwo = new Float(0.2);
Float objZeroPointTwoNegative = new Float(-0.2);
int objPitchZeroResult = objPitch.compareTo(objZero);
int objPitchZeroPointTwoResult = objZeroPointTwo.compareTo(objPitch);
int objPitchZeroPointTwoNegativeResult = objPitch.compareTo(objZeroPointTwoNegative);
if (roll < 0 && ((objPitchZeroResult > 0 && objPitchZeroPointTwoResult > 0) || (objPitchZeroResult < 0 && objPitchZeroPointTwoNegativeResult > 0)) && (inclination > 30 && inclination < 40))
{
return true;
}
else
{
return false;
}
}
}
return false;
}
Is this what you're looking for?
public class AccelerometerHandler implements SensorEventListener
{
float accelX;
float accelY;
float accelZ;
public AccelerometerHandler(Context paramContext)
{
SensorManager localSensorManager = (SensorManager)paramContext.getSystemService("sensor");
if (localSensorManager.getSensorList(1).size() != 0)
localSensorManager.registerListener(this, (Sensor)localSensorManager.getSensorList(1).get(0), 1);
}
public float getAccelX()
{
return this.accelX;
}
public float getAccelY()
{
return this.accelY;
}
public float getAccelZ()
{
return this.accelZ;
}
public void onAccuracyChanged(Sensor paramSensor, int paramInt)
{
}
public void onSensorChanged(SensorEvent paramSensorEvent)
{
this.accelX = paramSensorEvent.values[0];
this.accelY = paramSensorEvent.values[1];
this.accelZ = paramSensorEvent.values[2];
}
}
I wish to get my phone's current orientation by the following method:
Get the initial orientation (azimuth) first via the getRotationMatrix() and getOrientation().
Add the integration of gyroscope reading over time to it to get the current orientation.
Phone Orientation:
The phone's x-y plane is fixed parallel with the ground plane. i.e., is in a "texting-while-walking" orientation.
"getOrientation()" Returnings:
Android API allows me to easily get the orientation, i.e., azimuth, pitch, roll, from getOrientation().
Please note that this method always returns its value within the range: [0, -PI] and [o, PI].
My Problem:
Since the integration of the gyroscope reading, denoted by dR, may be quite big, so when I do CurrentOrientation += dR, the CurrentOrientation may exceed the [0, -PI] and [o, PI] ranges.
What manipulations are needed so that I can ALWAYS get the current orientation within the the [0, -PI] and [o, PI] ranges?
I have tried the following in Python, but I highly doubt its correctness.
rotation = scipy.integrate.trapz(gyroSeries, timeSeries) # integration
if (headingDirection - rotation) < -np.pi:
headingDirection += 2 * np.pi
elif (headingDirection - rotation) > np.pi:
headingDirection -= 2 * np.pi
# Complementary Filter
headingDirection = ALPHA * (headingDirection - rotation) + (1 - ALPHA) * np.mean(azimuth[np.array(stepNo.tolist()) == i])
if headingDirection < -np.pi:
headingDirection += 2 * np.pi
elif headingDirection > np.pi:
headingDirection -= 2 * np.pi
Remarks
This is NOT that simple, because it involves the following trouble-makers:
The orientation sensor reading goes from 0 to -PI, and then DIRECTLY JUMPS to +PI and gradually gets back to 0 via +PI/2.
The integration of the gyrocope reading also leads to some trouble. Should I add dR to the orientation or subtract dR.
Do please refer to the Android Documentations first, before giving a confirmed answer.
Estimated answers will not help.
The orientation sensor actually derives its readings from the real magnetometer and the accelerometer.
I guess maybe this is the source of the confusion. Where is this stated in the documentation? More importantly, does the documentation somewhere explicitly state that the gyro readings are ignored? As far as I know the method described in this video is implemented:
Sensor Fusion on Android Devices: A Revolution in Motion Processing
This method uses the gyros and integrates their readings. This pretty much renders the rest of the question moot; nevertheless I will try to answer it.
The orientation sensor is already integrating the gyro readings for you, that is how you get the orientation. I don't understand why you are doing it yourself.
You are not doing the integration of the gyro readings properly, it is more complicated than CurrentOrientation += dR (which is incorrect). If you need to integrate the gyro readings (I don't see why, the SensorManager is already doing it for you) please read Direction Cosine Matrix IMU: Theory how to do it properly (Equation 17).
Don't try integrating with Euler angles (aka azimuth, pitch, roll), nothing good will come out.
Please use either quaternions or rotation matrices in your computations instead of Euler angles. If you work with rotation matrices, you can always convert them to Euler angles, see
Computing Euler angles from a rotation matrix by Gregory G. Slabaugh
(The same is true for quaternions.) There are (in the non-degenrate case) two ways to represent a rotation, that is, you will get two Euler angles. Pick the one that is in the range you need. (In case of gimbal lock, there are infinitely many Euler angles, see the PDF above). Just promise you won't start using Euler angles again in your computations after the rotation matrix to Euler angles conversion.
It is unclear what you are doing with the complementary filter. You can implement a pretty damn good sensor fusion based on the Direction Cosine Matrix IMU: Theory manuscript, which is basically a tutorial. It's not trivial to do it but I don't think you will find a better, more understandable tutorial than this manuscript.
One thing that I had to discover myself when I implemented sensor fusion based on this manuscript was that the so-called integral windup can occur. I took care of it by bounding the TotalCorrection (page 27). You will understand what I am talking about if you implement this sensor fusion.
UPDATE: Here I answer your questions that you posted in comments after accepting the answer.
I think the compass gives me my current orientation by using gravity and magnetic field, right? Is gyroscope used in the compass?
Yes, if the phone is more or less stationary for at least half a second, you can get a good orientation estimate by using gravity and the compass only. Here is how to do it: Can anyone tell me whether gravity sensor is as a tilt sensor to improve heading accuracy?
No, the gyroscopes are not used in the compass.
Could you please kindly explain why the integration done by me is wrong? I understand that if my phone's pitch points up, euler angle fails. But any other things wrong with my integration?
There are two unrelated things: (i) the integration should be done differently, (ii) Euler angles are trouble because of the Gimbal lock. I repeat, these two are unrelated.
As for the integration: here is a simple example how you can actually see what is wrong with your integration. Let x and y be the axes of the horizontal plane in the room. Get a phone in your hands. Rotate the phone around the x axis (of the room) by 45 degrees, then around the y axis (of the room) by 45 degrees. Then, repeat these steps from the beginning but now rotate around the y axis first, and then around the x axis. The phone ends up in a totally different orientation. If you do the integration according to CurrentOrientation += dR you will see no difference! Please read the above linked Direction Cosine Matrix IMU: Theory manuscript if you want to do the integration properly.
As for the Euler angles: they screw up the stability of the application and it is enough for me not to use them for arbitrary rotations in 3D.
I still don't understand why you are trying to do it yourself, why you don't want to use the orientation estimate provided by the platform. Chances are, you cannot do better than that.
I think you should avoid the depreciated "Orientation Sensor", and use sensor fusion methods like getRotationVector, getRotationMatrix that already implement fusion algorithms specially of Invensense, which already use gyroscope data.
If you want a simple sensor fusion algorithm called a balance filter
(refer http://www.filedump.net/dumped/filter1285099462.pdf) can be used. Approach is as in
http://postimg.org/image/9cu9dwn8z/
This integrates the gyroscope to get angle, then high-pass filters the result to remove
drift, and adds it to the smoothed accelerometer and compass results. The integrated, high-pass-fil-tered gyro data and the accelerometer/compass data are added in such a way that the two parts add
to one, so that the output is an accurate estimate in units that make sense.
For the balance filter, the time constant may be tweaked to tune the response. The shorter the time
constant, the better the response but the more acceleration noise will be allowed to pass through.
To see how this works, imagine you have the newest gyro data point (in rad/s) stored in gyro, the
newest angle measurement from the accelerometer is stored in angle_acc, and dtis the time from
the last gyro data until now. Then your new angle would be calculated using
angle = b * (angle + gyro*dt) + (1 - b) *(angle_acc);
You may start by trying b = 0.98 for instance. You will also probably want to use a fast gyroscope measurement time dt so the gyro doesn’t drift more than a couple of degrees before the next measurement is taken. The balance filter is useful and simple to implement, but is not the ideal sensor fusion approach.
Invensense’s approach involves some clever algorithms and probably some form of Kalman filter.
Source: Professional Android Sensor Programming, Adam Stroud.
If the azimuth value is inaccurate due to magnetic interference, there is nothing that you can do to eliminate it as far as I know. To get a stable reading of the azimuth you need to filter the accelerometer values if TYPE_GRAVITY is not available. If TYPE_GRAVITY is not available, then I am pretty sure that the device does not have a gyro, so the only filter that you can use is low pass filter. The following code is an implementation of a stable compass using TYPE_GRAVITY and TYPE_MAGNETIC_FIELD.
public class Compass implements SensorEventListener
{
public static final float TWENTY_FIVE_DEGREE_IN_RADIAN = 0.436332313f;
public static final float ONE_FIFTY_FIVE_DEGREE_IN_RADIAN = 2.7052603f;
private SensorManager mSensorManager;
private float[] mGravity;
private float[] mMagnetic;
// If the device is flat mOrientation[0] = azimuth, mOrientation[1] = pitch
// and mOrientation[2] = roll, otherwise mOrientation[0] is equal to Float.NAN
private float[] mOrientation = new float[3];
private LinkedList<Float> mCompassHist = new LinkedList<Float>();
private float[] mCompassHistSum = new float[]{0.0f, 0.0f};
private int mHistoryMaxLength;
public Compass(Context context)
{
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
// Adjust the history length to fit your need, the faster the sensor rate
// the larger value is needed for stable result.
mHistoryMaxLength = 20;
}
public void registerListener(int sensorRate)
{
Sensor magneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
if (magneticSensor != null)
{
mSensorManager.registerListener(this, magneticSensor, sensorRate);
}
Sensor gravitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
if (gravitySensor != null)
{
mSensorManager.registerListener(this, gravitySensor, sensorRate);
}
}
public void unregisterListener()
{
mSensorManager.unregisterListener(this);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
#Override
public void onSensorChanged(SensorEvent event)
{
if (event.sensor.getType() == Sensor.TYPE_GRAVITY)
{
mGravity = event.values.clone();
}
else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
{
mMagnetic = event.values.clone();
}
if (!(mGravity == null || mMagnetic == null))
{
mOrientation = getOrientation();
}
}
private void getOrientation()
{
float[] rotMatrix = new float[9];
if (SensorManager.getRotationMatrix(rotMatrix, null,
mGravity, mMagnetic))
{
float inclination = (float) Math.acos(rotMatrix[8]);
// device is flat
if (inclination < TWENTY_FIVE_DEGREE_IN_RADIAN
|| inclination > ONE_FIFTY_FIVE_DEGREE_IN_RADIAN)
{
float[] orientation = sensorManager.getOrientation(rotMatrix, mOrientation);
mCompassHist.add(orientation[0]);
mOrientation[0] = averageAngle();
}
else
{
mOrientation[0] = Float.NAN;
clearCompassHist();
}
}
}
private void clearCompassHist()
{
mCompassHistSum[0] = 0;
mCompassHistSum[1] = 0;
mCompassHist.clear();
}
public float averageAngle()
{
int totalTerms = mCompassHist.size();
if (totalTerms > mHistoryMaxLength)
{
float firstTerm = mCompassHist.removeFirst();
mCompassHistSum[0] -= Math.sin(firstTerm);
mCompassHistSum[1] -= Math.cos(firstTerm);
totalTerms -= 1;
}
float lastTerm = mCompassHist.getLast();
mCompassHistSum[0] += Math.sin(lastTerm);
mCompassHistSum[1] += Math.cos(lastTerm);
float angle = (float) Math.atan2(mCompassHistSum[0] / totalTerms, mCompassHistSum[1] / totalTerms);
return angle;
}
}
In your activity instantiate a Compass object say in onCreate, registerListener in onResume and unregisterListener in onPause
private Compass mCompass;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mCompass = new Compass(this);
}
#Override
protected void onPause()
{
super.onPause();
mCompass.unregisterListener();
}
#Override
protected void onResume()
{
super.onResume();
mCompass.registerListener(SensorManager.SENSOR_DELAY_NORMAL);
}
Its better to let android's implementation of Orientation detection handle it. Now, yes values you get are from -PI to PI, and you can convert them to degrees (0-360).Some Relevant parts:
Saving data to be processed:
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
switch (sensorEvent.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
mAccValues[0] = sensorEvent.values[0];
mAccValues[1] = sensorEvent.values[1];
mAccValues[2] = sensorEvent.values[2];
break;
case Sensor.TYPE_MAGNETIC_FIELD:
mMagValues[0] = sensorEvent.values[0];
mMagValues[1] = sensorEvent.values[1];
mMagValues[2] = sensorEvent.values[2];
break;
}
}
Calculating roll, pitch and yaw (azimuth).mR and mI are arrys to hold rotation and inclination matrices, mO is a temporary array. The array mResults has the values in degrees, at the end:
private void updateData() {
SensorManager.getRotationMatrix(mR, mI, mAccValues, mMagValues);
/**
* arg 2: what world(according to app) axis , device's x axis aligns with
* arg 3: what world(according to app) axis , device's y axis aligns with
* world x = app's x = app's east
* world y = app's y = app's north
* device x = device's left side = device's east
* device y = device's top side = device's north
*/
switch (mDispRotation) {
case Surface.ROTATION_90:
SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, mR2);
break;
case Surface.ROTATION_270:
SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, mR2);
break;
case Surface.ROTATION_180:
SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_MINUS_X, SensorManager.AXIS_MINUS_Y, mR2);
break;
case Surface.ROTATION_0:
default:
mR2 = mR;
}
SensorManager.getOrientation(mR2, mO);
//--upside down when abs roll > 90--
if (Math.abs(mO[2]) > PI_BY_TWO) {
//--fix, azimuth always to true north, even when device upside down, realistic --
mO[0] = -mO[0];
//--fix, roll never upside down, even when device upside down, unrealistic --
//mO[2] = mO[2] > 0 ? PI - mO[2] : - (PI - Math.abs(mO[2]));
//--fix, pitch comes from opposite , when device goes upside down, realistic --
mO[1] = -mO[1];
}
CircleUtils.convertRadToDegrees(mO, mOut);
CircleUtils.normalize(mOut);
//--write--
mResults[0] = mOut[0];
mResults[1] = mOut[1];
mResults[2] = mOut[2];
}
I am trying to calculate a new X position based on Sensor.TYPE_LINEAR_ACCELERATION that i will be applying to my android game.
I an starting with the following variables all set to 0:
float newX=0;
float lastX0 =0;
float lastX1 =0;
and my Time Variables set initially like so:
float newTime = System.currentTimeMillis()/1000;
float lastTime0 = lastTime1 = newTime;
Then my On SensorChange looks like this:
public onSensorChanged(SensorEvent evt){
if (type == Sensor.TYPE_LINEAR_ACCELERATION) {
newTime = System.currentTimeMillis()/1000;
float oldDeltaTime = lastTime1-lastTime0;
float newDeltaTime = newTime- lastTime1;
if(oldDeltaTime<1)oldDeltaTime =1;
newX = lastX1 + ((lastX1 - lastX0)/oldDeltaTime)*newDeltaTime +(evt.values[0]/2)*(newDeltaTime*newDeltaTime);
lastX0 = lastX1;
lastX1 = newX;
lastTime0 = lastTime1;
lastTime1 = newTime;
Log.v("SENSOR MAN LINEAR", "new X:"+newX);
}
}
but i am getting 0 all the time for newX in my log.
Has anyone successfully translated LinearAcceleration into position?
can anyone figure out what i am doing wrong..
i am crying inside!
My experience is that the errors associated with measuring Sensor.TYPE_LINEAR_ACCELERATION make it impossible to get a good estimate of position from Sensor.TYPE_LINEAR_ACCELERATION alone.
I'm not sure why you're getting zero all the time. To debug that, I'd simply Log.v all your variables to work out what the problem is.
I'm also not sure that your formula for newX is making the best use of the sensor values. Acceleration is a change of speed, and you implementation doesn't seem to correspond to that.
Finally, the event values depend on the orientation of the device. So to have any chance of success, you need to combine with compass sensor readings to try and work out how the Sensor.TYPE_LINEAR_ACCELERATION values map to directions in the real world.
This question already has answers here:
How to use Accelerometer to measure distance for Android Application Development
(2 answers)
Closed 9 years ago.
I know i am opening up a can of worms with trying to get the linear motion of a device using the accelerometer, but please just humor me.
I am trying to figure out the right formula to take the Sensor.TYPE_LINEAR_ACCELEROMETER (which i believe is normal accelerometer data minus gravity) and essentially say "this much time has passed and the i have accelerated x amount since last time, so i have traveled d amount.
should be something like distanceTraveledOnX = linearAccerationOfX * TimePassed;
easy enough in the real world right? if i have been going 1 mile a minute for 10minutes then i have traveled 10 miles.. speed * time = distance
problem is im not sure what the linearAcceleration is using for unit of measure. I know my timePassed is in NanoSeconds as i am saying (in my onSensorChanged)
currentTime = System.nanoTime();//var of type (double)
timePassed = currentTime - lastTime;
lastTime = currentTime;
can someone please help me figure out the formula for translating the linearAcceleration value to a nanoSecond measurement..
thanks
EDIT
here is the code im currently using but im always getting 0 :
public void onSensorChanged(SensorEvent evt) {
if (type == Sensor.TYPE_LINEAR_ACCELERATION) {
newTime = System.currentTimeMillis()/1000;
float oldVelocity = lastTime1-lastTime0;
float newVelocity = newTime- lastTime1;
if(oldVelocity<1)oldVelocity =1;
newX = lastX1 + ((lastX1 - lastX0)/oldVelocity)*newVelocity +(evt.values[0]/2)*(newVelocity*newVelocity);
lastX0 = lastX1;
lastX1 = newX;
lastTime0 = lastTime1;
lastTime1 = newTime;
Log.v("SENSOR MAN LINEAR", "new X:"+newX);
}
}
This stuff is high school physics, and if you don't know the difference between acceleration and velocity, you'll need to review it before you have any hope here.
I can tell you this much: the linear acceleration readings from a cell phone or tablet aren't remotely precise or accurate enough to do what you want without constant correction (via gps or other methods). There is an entire field of study trying to solve this problem. I've attended conferences on it.
That said, you also need to take into account that the orientation of your device will also change, unless this is some sort of special application, e.g. the device is trapped onto a sled which can only move in one direction.
Let's assume that case, and assume that the device is strapped to your sled with the right side of the device (+X axis) aligned in the direction of travel. Let's also assume that the initial position of the sled is known (call it X0) when the program starts, and that the initial velocity is zero.
Your code looks approximately like this:
double x0; // previous position, meters
double x; // current position
double v0; // previous velocity, meters/second
double v; // current velocity
long t0; // previous time, nanoseconds
long t; // current time
public void onStart() {
x0 = getInitialPosition();
x = x0;
v0 = 0;
v = v;
t0 = System.getCurrentTime() * 1000000;
// Enable sensors; left as an exercise for the reader
}
public void onSensorChanged(SensorEvent event) {
// Assume linear acceleration is the only active sensor
double accel = event.values[0]; // X axis is our axis of acceleration
t = event.timestamp;
double dt = (t - t0) * .000001;
v = v0 + accel * dt;
x = x0 + v * dt;
t0 = t;
v0 = v;
x0 = x;
}
This is by no means a complete solution. Doing this right involves differential equations which I'm not equipped to explain here (translation: I've forgotten everything I learned in college). However, if your acceleration value is accurate enough, and your time slice is short enough, this is viable.
If you need to solve this in more than one direction, it's only slightly more complicated provided that the device never changes orientation. If it does, then you also need to capture the rotation sensor and learn about quaternions and rotation matrices.
And even if you do everything right, errors will still accumulate, so now you want some sort of correction factor based on GPS, known geometry of the environment (e.g. if you're indoors and the software has a map of the building, it can make corrections when you turn a corner), and other environmental clues such as WiFi hotspots in known locations.
You might want to read up on Kalman filters at this point.
Executive summary: this is a HARD problem in the general case, and if you solve it, there's probably fame and fortune waiting for you.
Well, the correct form, known from school, is
finalXPosition = (linearAcceleration*timePassed^2)/2+ initialVelocity*timePassed+initialXPosition
finalVelocity = initialVelocity*timePassed
chaining these chunks you'll get your theoretical values.
In practice, best results are achieved by regular calibration of initialXPosition and initialVelocity through GPS.
simple example to receive calibrated horizontal acceleration in onSensorChanged:
class Integrator {
private float position = 0f;
private float velocity = 0f;
public void setGpsPosition (float gpsPosition) {
position = gpsPosition;
}
public void setGpsVelocity (float gpsVelocity) {
velocity = gpsVelocity;
}
public void onAccelerationChangeHandler(float acceleration, float timePassed) {
position += acceleration*timePassed*timePassed/2f + velocity*timePassed;
velocity += acceleration*timePassed;
}
public float getCurrentPosition() {
return position;
}
}
usage for x-acceleration:
long lastTime = 0;
public void onSensorChanged(SensorEvent evt) {
if (evt.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
long newTime = System.currentTimeMillis();
OnAccelerationChangeHandler(evt.values[0], (newTime-lastTime)/1000);
lastTime = newTime;
}
Please, note that outside a minute scale the error makes this all meaningless w/o gps correction. Understand, that if you are walking at constant speed the sensor won't give you anything at all.
I am getting various sensor readings from my device (programing for android) and i am looking to get the roll (which seems to be a number 1-100) converted into an angle in degrees and also convert the magnetometer heading into degrees..
any simple equations would be appreciated.. my geometry is a fleeting memory..
public void onSensorChanged(SensorEvent evt) {
int type=evt.sensor.getType();
if(type == Sensor.TYPE_ORIENTATION){
azimuth = evt.values[0]; // azimuth rotation around the z-axis
pitch = evt.values[1]; // pitch rotation around the x-axis
roll = evt.values[2]; // roll rotation around the y-axis
}
//Smoothing the sensor data a bit seems like a good idea.
if (type == Sensor.TYPE_MAGNETIC_FIELD) {
orientation[0]=(orientation[0]*1+evt.values[0])*0.5f;
orientation[1]=(orientation[1]*1+evt.values[1])*0.5f;
orientation[2]=(orientation[2]*1+evt.values[2])*0.5f;
} else if (type == Sensor.TYPE_ACCELEROMETER) {
acceleration[0]=(acceleration[0]*2+evt.values[0])*0.33334f;
acceleration[1]=(acceleration[1]*2+evt.values[1])*0.33334f;
acceleration[2]=(acceleration[2]*2+evt.values[2])*0.33334f;
}
if ((type==Sensor.TYPE_MAGNETIC_FIELD) || (type==Sensor.TYPE_ACCELEROMETER)) {
float newMat[]=new float[16];
//Toast toast = Toast.makeText(ctx.getApplicationContext(), "accel", Toast.LENGTH_SHORT);
//toast.show();
SensorManager.getRotationMatrix(newMat, null, acceleration, orientation);
if(displayOri==0||displayOri==2){
SensorManager.remapCoordinateSystem(newMat,SensorManager.AXIS_X*-1, SensorManager.AXIS_MINUS_Y*-1,newMat);
}else{
SensorManager.remapCoordinateSystem(newMat,SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X,newMat);
}
matrix=newMat;
}
}
I should add that i am not sure i just want roll.. my app locks in landscape mode but obviously the user can turn their phone to any angle on any access.. so i probably need all three of the above to get the angle im looking for.
the angle in question is as if the user is looking through their phone, no matter how they are holding it, at the real world and i want the angle they are looking off the horizon..
for instance if they are looking at the horizon i want 90degrees returned, if they are looking straight up in the sky i should get 180, straight down -180.. then i will also want the degrees from magnetic north that they are looking.. using the magnetometer
values[2] should allready contain degree-value, that's mentioned in a reference:
values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive
values when the z-axis moves toward the x-axis.
Update
Take a look at this picture: http://developer.android.com/images/axis_device.png
Here you see blue axis - Y axis. When your phone turns around it, it's called "rolling". The angle of the rotation will be contained in values[2].