Measuing car rotation with Android phone - android

I have found many threads about how to handle the device' rotation,orientation with motion and position sensors.
I would like to create an app which i will use in my car, first i would like to measure the rotation degree of the car.
So i put my phone to a phone case and for example when i turn left with the car i would like to see the car' turning degree on the phone.
Is it possible by magnetic and accelero meter?
I post a code that for first i think okay. (let's say that i hold my phone "portait" mode so not landscape for first)
private static SensorManager sensorService;
//magnetic
private Sensor mSensor;
//accelerometer
private Sensor gSensor;
private float[] mValuesMagnet = new float[3];
private float[] mValuesAccel = new float[3];
private float[] mValuesOrientation = new float[3];
private float[] mRotationMatrix = new float[9];
#Override
public void onCreate(Bundle savedInstanceState) {
nf.setMaximumFractionDigits(2);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sensorService = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
this.mSensor = sensorService.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
this.gSensor = sensorService.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorService.registerListener(this, gSensor, SensorManager.SENSOR_DELAY_NORMAL);
sensorService.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
}
#Override
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
System.arraycopy(event.values, 0, mValuesAccel, 0, 3);
break;
case Sensor.TYPE_MAGNETIC_FIELD:
System.arraycopy(event.values, 0, mValuesMagnet, 0, 3);
break;
}
SensorManager.getRotationMatrix(mRotationMatrix, null, mValuesAccel, mValuesMagnet);
SensorManager.getOrientation(mRotationMatrix, mValuesOrientation);
// double azimuth = Math.toDegrees(mValuesOrientation[0]); //azimuth, rotation around the Z axis.
// double pitch = Math.toDegrees(mValuesOrientation[1]); // pitch, rotation around the X axis.
double roll = Math.toDegrees(mValuesOrientation[2]); //roll, rotation around the Y axis.
//normalize
// azimuth = azimuth>0?azimuth:azimuth+360;
roll = roll>0?roll:roll+360;
String txt = "roll= "+Math.round(roll);
((EditText)findViewById(R.id.szog)).setText(txt);
}
Questions:
- How accurate will this app in a car? (what can i do to be more accurate?)
- What should i do when i hold my phone at "landscape" mode?
Is the roll from orientation still okay?
Please note that this is a very first try so there are so much to do!
But first i want to see how can i achive that
Thanks!

If you had your Android device set up like a compass, then there would be an arrow that always pointed to magnetic north. So by measuring the change in the direction of that arrow, you could measure the rotation of your car.
To get a better indication of the orientation of your Android device, use
Sensor.TYPE_GRAVITY and Sensor.TYPE_MAGNETIC_FIELD, instead of Sensor.TYPE_ACCELEROMETER and Sensor.TYPE_MAGNETIC_FIELD. This is better because Sensor.TYPE_GRAVITY tries to filter out the effects due to the movement of the device.
If the Android device is lying flat on a surface, then the direction of magnetic north is defined by the azimuth that comes out of SensorManager.getOrientation(...). When it's standing up or when it's on its side then it's a bit more complicated. However, I suggest starting with the device lying flat first because that's the easiest case, and then you can progress to more difficult cases.

Related

Get phone angle (pitch)

I really hope someone is able to help me:
I am working on an Android App that displays the angle the phone is held
I am using this code:
protected void onCreate(Bundle savedInstanceState)
{
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, mAccelerometer,100000);
mSensorManager.registerListener(this, mMagnetometer, 100000);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = lowPass(event.values.clone(), mGravity);
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mGeomagnetic = lowPass(event.values.clone(), mGeomagnetic);
if (mGravity != null && mGeomagnetic != null) {
boolean success = SensorManager.getRotationMatrix(Ra, Ia, mGravity, mGeomagnetic);
if (success) {
SensorManager.getOrientation(Ra, orientation);
float tempxGy = (orientation[0]);// az
float tempyGy = (orientation[1]);// pitch
float tempzGy = (orientation[2]); //roll
}
Im further processing the values that I get here but thats not the problem here.
When the phone is flat on the table the values look like that:
roll -0,018
pitch 0,024
yaw 2,51
that looks fine to me (also if I convert the values to euler angles)
now imagine that I take the phone from the table - the bottom of the phone (where the micro usb port is) stays on the table and I lift the side with the camera
the values of the pitch are getting more and more negative - till the pitch reaches -1.53 (-90 in euler) as soon as this value is reached the values are increasing again and if the phone is on the table with the display facing the table the pitch is back to 0 again.
The problem is, that I have to differenciate between the two values (for example -1 and -1
How can I do that? The roll isnt a problem as the roll has a 180 degree maximum and not this 90 degree problem..
I really hope you can help me!
So
if(mGravity[2]<0) orientation[1] = (float) (Math.PI + orientation[1]);
is enough to get values that are like I need them --- but now I have a new problem - I would like to remap the coordinate system, so I get 0 degrees (euler) when the phone is on the table and +90 if the phone is standing on the micro usb port
I tried
SensorManager.remapCoordinateSystem(Ra, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, Ri);
but now the roll and pitch are swapped --- what would I have to do to simply get positive pitch values as long as the display is facing up and negative values if the display is facing down?
Is the remap the wrong idea and I could do it simpler by doing something else?

How to get a phone's azimuth with compass readings and gyroscope readings?

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];
}

Get tilt angle from the android accelerometer

I have a class that implements SensorEventListener and I would like to get
the tilt Angle of my device using the Accelerometer.
I looked for examples on the internet but they use
Sensor.TYPE_MAGNETIC_FIELD.
I believe my device doesn't have this sensor because when I do the
following check
manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size(), I get zero.
Is there a way to get the tilt Angle by just using
Sensor.TYPE_ACCELEROMETER values?
As people suggested you can use the accelerometer and magnetic sensor to do this.
Here is a blog post with a complete solution.
http://www.ahotbrew.com/how-to-detect-forward-and-backward-tilt/
Try this,
SensorManager sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
final SensorEventListener mEventListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
System.arraycopy(event.values, 0, mValuesAccel, 0, 3);
break;
case Sensor.TYPE_MAGNETIC_FIELD:
System.arraycopy(event.values, 0, mValuesMagnet, 0, 3);
break;
}
};
};
setListners(sensorManager, mEventListener);
SensorManager.getRotationMatrix(mRotationMatrix, null, mValuesAccel, mValuesMagnet);
SensorManager.getOrientation(mRotationMatrix, mValuesOrientation);
final CharSequence test;
test = ","+mValuesOrientation[0] +","+mValuesOrientation[1]+ ","+ mValuesOrientation[2];
You can use the accelerometer to get a tilt reading. If you set up an accelerometer you will notice it includes the force of gravity. So if you phone is face-up on a table the z-axis will register somewhere close to 9.81 (the force of gravity) and the x and y axes will be at 0. As you tilt the phone the force of gravity will be projected onto the x and/or y axis. Thus you the x and y values will tell you the tilt of the phone.

Sensors TYPE_ACCELEROMETER and TYPE_MAGNETIC_FIELD - They work on phones, but doesn't on tablets

In my 2d game I have the following code which is responsible for game entity control (flying plane). It all seems to be working fine when it comes for phones, but unfortunately I've getting some information that on Android tablets the steering is completely unreliable (axis are messed up, or it doesn't work at all). Unfortunately I don't have a tablet of my own, so I cannot investigate it closer. So.. what's wrong with the following code? (for the clarity I put only the code related to sensors)
// ...
private float[] accelerometerValues;
private float[] magneticFieldValues;
private float[] R;
private float[] I;
private float[] outR;
private float[] sensorValues;
private Sensor accelerometer;
private Sensor magneticField;
// ...
// ... sensor initialization
sensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
if(sensorManager == null)
return;
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
// ...
// ... onPause() sensor is being unregistered
public void onResume() {
if(!sensorManager.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_GAME) ||
!sensorManager.registerListener(sensorListener, magneticField, SensorManager.SENSOR_DELAY_GAME))
// ...
// ...
sensorListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor arg0, int arg1) {
}
public void onSensorChanged(android.hardware.SensorEvent event) {
synchronized(InputMgr.this) {
switch(event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
System.arraycopy(event.values, 0, accelerometerValues, 0, 3);
break;
case Sensor.TYPE_MAGNETIC_FIELD:
System.arraycopy(event.values, 0, magneticFieldValues, 0, 3);
break;
}
}
}
};
// ...
// used somewhere in the game
public void getSensorValues(float values[]) {
synchronized(InputMgr.this) {
SensorManager.getRotationMatrix(R, I, accelerometerValues, magneticFieldValues);
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_X, outR);
SensorManager.getOrientation(outR, sensorValues);
System.arraycopy(sensorValues, 0, values, 0, sensorValues.length);
}
}
This wont cheer you up much but I have a similar problem. I can see a TYPE_MAGNETIC_FIELD sensor, I can add a an event listener to it but I never get any data from it. Other sensors work fine. This is on a Galaxy Tab 7.
onSensorChanged(SensorEvent event) never fires a case Sensor.TYPE_MAGNETIC_FIELD:
As such I get no data from the Magnetic Field Sensor.
if you solve it let us know =)
On devices whose default orientation is landscape (-> most tablets), the sensor values are kind of 'wrong' (I don't know why). So you need to catch those devices and remap your Rotation Matrix.
To check whether the matrix needs to be remapped, you can use this code:
public boolean needToRemapOrientationMatrix;
// compute once (e.g. in onCreate() of your Activity):
Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int orientation;
if(display.getWidth() < display.getHeight()) orientation = Configuration.ORIENTATION_PORTRAIT;
else if(display.getWidth() > display.getHeight()) orientation = Configuration.ORIENTATION_LANDSCAPE;
else orientation = Configuration.ORIENTATION_SQUARE;
int rotation = display.getRotation();
needToRemapOrientationMatrix =
(orientation==Configuration.ORIENTATION_LANDSCAPE && (rotation==Surface.ROTATION_0 || rotation==Surface.ROTATION_180)) ||
(orientation==Configuration.ORIENTATION_PORTRAIT && (rotation==Surface.ROTATION_90 || rotation==Surface.ROTATION_270));
And when you read the sensor values, remap the matrix if needed:
public void getSensorValues(float values[]) {
synchronized(InputMgr.this) {
SensorManager.getRotationMatrix(R, I, accelerometerValues, magneticFieldValues);
if(needToRemapOrientationMatrix)
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, R);
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_X, outR);
SensorManager.getOrientation(outR, sensorValues);
System.arraycopy(sensorValues, 0, values, 0, sensorValues.length);
}
}
This worked for me, I hope it helps.

Compass and Accelerometer precision

i made my own application in Android that use compass and accelerometer sensors to display the degrees of rotation and inclination of my device. I initialized all the listener and objects i needed (i followed some tutorials), and now i can catch the degrees as i wished. The problem is that the measures that sensors return aren't accurate. I mean even if i try to round the values of degrees i catch from the sensor, they oscillate between -/+ 7 (or 8) degrees every fraction of a second, even if i stay in a grass field away from any disturbing source. What i want to have is a accurate measure of the degrees, something like a method to round the values i recieve from the sensors.
float[] mags = null;
float[] accels = null;
float[] R = new float[matrix_size];
float[] outR = new float[matrix_size];
float[] I = new float[matrix_size];
float[] values = null;
private void startSensor() {
sensorMan.registerListener(this, sensorMan.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI);
sensorMan.registerListener(this, sensorMan.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);
}
#Override
public void onSensorChanged(SensorEvent event) {
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
return;
}
switch (event.sensor.getType()) {
case Sensor.TYPE_MAGNETIC_FIELD:
mags = event.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER:
accels = event.values.clone();
break;
}
if (mags != null && accels != null) {
SensorManager.getRotationMatrix(R, I, accels, mags);
// Correct if screen is in Landscape
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_X,
SensorManager.AXIS_Z, outR);
SensorManager.getOrientation(outR, values);
azimuth = (float) Math.round((Math.toDegrees(values[0]))*7)/7;
azimuth = ( azimuth + 360)%360;
//here is inclination. The problem is just the same with compass
//inclination=-Math.round((float) (values[1]*(360/(2*Math.PI))));
//other code to update my view
//in azimuth i have the degree value. It changes continuously
//even if i aim still the same direction
}
}
See my answer here: Smoothing data from a sensor
I run this filter on both the accelerometer and magetometer event values before passing them to SensorManager.getRotationMatrix(). I think this algorithm has the advantage of not having to keep a large array of historic values, just the prior low-pass output array.
The algorithm was derived from this Wikipedia entry: http://en.wikipedia.org/wiki/Low-pass_filter#Algorithmic_implementation
What you're seeing is the real thing- the orientation sensors on most phones are only good enough to give you a rough compass heading.
If you want to smooth the displayed value out so it gives you something that's doesn't appear to change randomly I recommend implementing a http://en.wikipedia.org/wiki/Moving_average or other smoothing filter in Java on that orientation result.
For the highest performance you could write the filter using the NDK and use the Boost Accumulators library: http://www.boost.org/doc/libs/1_46_1/doc/html/accumulators.html
I did this using a Kalman filter from here:
Greg Czerniak's Website
I'm sending data to a udp port and smoothing it on the PC using python.
But I guess you can find a Kalman filter implementation for java/android out there.

Categories

Resources