I’m having trouble getting good orientation sensor readings. The sensor readings seemed unreliable, so I tested my code against two free sensor test apps (Sensor Tester (Dicotomica) and Sensor Monitoring (R's Software)). I found that while my readings often agreed with the sensor test apps, occasionally the values for azimuth/yaw, and roll differed by up to 40 degrees, although the pitch reading mostly agreed. The two free apps always seemed to agree with each other.
I put my code into a tiny Android activity and got the same inconsistency. The code is as follows:
public class MainActivity extends Activity implements SensorEventListener {
private SensorManager mSensorManager;
private float[] AccelerometerValues;
private float[] MagneticFieldValues;
private float[] RotationMatrix;
private long nextRefreshTime; // used to ensure dump to LogCat occurs no more than 4 times a second
private DecimalFormat df; // used for dumping sensors to LogCat
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSensorManager = (SensorManager)getSystemService(android.content.Context.SENSOR_SERVICE);
Sensor SensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, SensorAccelerometer, SensorManager.SENSOR_DELAY_UI);
Sensor SensorMagField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(this, SensorMagField, SensorManager.SENSOR_DELAY_UI);
AccelerometerValues = new float[3];
MagneticFieldValues = new float[3];
RotationMatrix = new float[9];
nextRefreshTime = 0;
df = new DecimalFormat("#.00");
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
System.arraycopy(event.values, 0, AccelerometerValues, 0, AccelerometerValues.length);
else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
System.arraycopy(event.values, 0, MagneticFieldValues, 0, MagneticFieldValues.length);
if (AccelerometerValues != null && MagneticFieldValues != null) {
if(SensorManager.getRotationMatrix(RotationMatrix, null, AccelerometerValues, MagneticFieldValues)) {
float[] OrientationValues = new float[3];
SensorManager.getOrientation(RotationMatrix, OrientationValues);
// chance conventions to match sample apps
if (OrientationValues[0] < 0) OrientationValues[0] += 2*(float)Math.PI;
OrientationValues[2] *= -1;
// dump to logcat 4 times a second
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis > nextRefreshTime) {
nextRefreshTime = currentTimeMillis+250;
Log.i("Sensors", // arrange output so that numbers line up in columns :-)
"(" + AngleToStr(OrientationValues[0]) + "," + AngleToStr(OrientationValues[1]) + "," + AngleToStr(OrientationValues[2])
+ ") ("+FloatToStr(AccelerometerValues[0]) + "," + FloatToStr(AccelerometerValues[1]) + "," + FloatToStr(AccelerometerValues[2])
+ ") ("+FloatToStr(MagneticFieldValues[0]) + "," + FloatToStr(MagneticFieldValues[1]) + "," + FloatToStr(MagneticFieldValues[2])+")");
}
}
}
}
private String AngleToStr(double AngleInRadians) {
String Str = " "+Integer.toString((int)Math.toDegrees(AngleInRadians));
return Str.substring(Str.length() - 3);
}
private String FloatToStr(float flt) {
String Str = " "+df.format(flt);
return Str.substring(Str.length() - 6);
}
@Override
protected void onDestroy() {
super.onDestroy();
mSensorManager.unregisterListener(this);
}
@Override
public void onAccuracyChanged(Sensor arg0, int arg1) { }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
I’m using a Galaxy Note 2 running Jelly Bean 4.1.1. Can anyone tell me what I’m doing wrong?
Update 24-Mar-2013: More information. (1) I've disabled switches between portrait and landscape in the manifest, so getWindowManager().getDefaultDisplay().getRotation() is always zero. Hence I don't think remapCoordSystem would help here, because that's for switching axes, whereas the errors that I'm seeing aren't big errors, they're much more subtle. (2) I've checked the accuracy sensitivity, and the inconsistencies occur when both sensors claim to have high accuracy.
As an example of the inconsistencies that I'm seeing, when the code above give me (azimuth,pitch,roll) = (235,-52,-11) then the two free apps show similar values. But when I see (278, -58, -52) the apps show (256, -58, -26), so big differences in both Azimuth and roll, although pitch seems OK.
I think the best way of defining your orientation angles when the device isn't flat is to use a more appropriate angular co-ordinate system that the standard Euler angles that you get from SensorManager.getOrientation(...)
. I suggest the one that I describe here on math.stackexchange.com. I've also put some code that does implements it in an answer here. Apart from a good definition of azimuth, it also has a better definition of the pitch angle, which this methodology defines as rotation out of the horizontal plane irrespective of which axis the rotation occurs along.
You can get full details from the two links that I've given in the first paragraph. However, in summary, your rotation matrix R from SensorManager.getRotationMatrix(...) is
where (Ex, Ey, Ez), (Nx, Ny, Nz) and (Gx, Gy, Gz) are vectors pointing due East, North, and in the direction of Gravity. Then the azimuth angle that you want is given by