I am trying to get the tilt angel from Android orientation.
The values which i am getting from
SensorManager.getOrientation(mRotationMatrix, mValuesOrientation)
is not degrees.... On putting the phone on a horizontal plane it returns improper values
I have tries using few methods found on net without any luck..
So far tried
Math.sin(Math.toRadians(mValuesOrientation[0]));
Math.toDegrees(mValuesOrientation[0]);
and others
Code is below
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SensorManager sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
final float[] mValuesMagnet = new float[3];
final float[] mValuesAccel = new float[3];
final float[] mValuesOrientation = new float[3];
final float[] mRotationMatrix = new float[9];
final Button btn_valider = (Button) findViewById(R.id.button1);
final TextView txt1 = (TextView) findViewById(R.id.editText1);
final SensorEventListener mEventListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public void onSensorChanged(SensorEvent event) {
// Handle the events for which we registered
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;
}
};
};
// You have set the event lisetner up, now just need to register this with the
// sensor manager along with the sensor wanted.
setListners(sensorManager, mEventListener);
btn_valider.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
SensorManager.getRotationMatrix(mRotationMatrix, null, mValuesAccel, mValuesMagnet);
SensorManager.getOrientation(mRotationMatrix, mValuesOrientation);
String test;
/* double accX = -mValuesOrientation[0]/SensorManager.GRAVITY_EARTH;
double accY = -mValuesOrientation[1]/SensorManager.GRAVITY_EARTH;
double accZ = -mValuesOrientation[2]/SensorManager.GRAVITY_EARTH;
double totAcc = Math.sqrt((accX*accX)+(accY*accY)+(accZ*accZ));
double tiltX = Math.asin(accX/totAcc);
double tiltY = Math.asin(accY/totAcc);
double tiltZ = Math.asin(accZ/totAcc);*/
//float tiltX = mValuesOrientation[0] * 57.2957795f;
//float tiltY = mValuesOrientation[1] * 57.2957795f;
//float tiltZ = mValuesOrientation[2] * 57.2957795f;
//double tiltX =Math.sin(Math.toRadians(mValuesOrientation[0]));
//double tiltY =Math.sin(Math.toRadians(mValuesOrientation[1]));
//double tiltZ =Math.sin(Math.toRadians(mValuesOrientation[2]));
//String.format("Azimuth: %.2f\n\nPitch:%.2f\nRoll", azimuth,
// pitch, roll);
double tiltX = Math.toDegrees(mValuesOrientation[0]);
double tiltY = Math.toDegrees(mValuesOrientation[1]);
double tiltZ = Math.toDegrees(mValuesOrientation[2]);
test = "results New: " +tiltX +" "+tiltY+ " "+ tiltZ;
Log.d("test", test);
txt1.setText(test);
}
});
}
public void setListners(SensorManager sensorManager, SensorEventListener mEventListener)
{
sensorManager.registerListener(mEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(mEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
The getOrientation return the azimuth, pitch and roll in Radian.
mValuesOrientation[0] is the azimuth, rotation about the z-axis.
mValuesOrientation[1] is the pitch, rotation about the x-axis.
mValuesOrientation[2] is the roll, rotation about the y-axis.
If your phone is lying flat on a table, the pitch and roll should be almost 0, but not the azimuth. It is only 0 if your phone longer size, the y axis, is pointing exactly toward magnetic North.
You can use pitch or roll (depending on the device orientation) to calculate the inclination of the phone, ie the angle between the surface of the screen and the world xy plane or you can use the inclination of my answer at How to measure the tilt of the phone in XY plane using accelerometer in Android
You should also read my answer at Convert magnetic field X, Y, Z values from device into global reference frame to get a better understanding of the rotation matrix.
Related
I am doing a project about video stabilization. Now, I am trying to get the accurate Orientation first. I already read about the sensors event. I know that I can't only use the SensorManager.getOrientation() to get the accurate orientation. Also, I know using accelerometer and integrate of gyroscope can get the correct pitch and roll.
Now,I want to know how can I get the correct yaw too?
Thank you for your time!
With this code you get a accurate orientation by using the accelerometer and the magnetic field sensor. (What is the alternative to android orientation sensor?)
public class OrientationTestActivity extends Activity implements SensorEventListener
{
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Sensor mMagnetometer;
private float[] mLastAccelerometer = new float[3];
private float[] mLastMagnetometer = new float[3];
private boolean mLastAccelerometerSet = false;
private boolean mLastMagnetometerSet = false;
private float[] mR = new float[9];
private float[] mOrientation = new float[3];
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
protected void onResume() {
super.onResume();
mLastAccelerometerSet = false;
mLastMagnetometerSet = false;
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_NORMAL);
}
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public void onSensorChanged(SensorEvent event) {
if (event.sensor == mAccelerometer) {
System.arraycopy(event.values, 0, mLastAccelerometer, 0, event.values.length);
mLastAccelerometerSet = true;
} else if (event.sensor == mMagnetometer) {
System.arraycopy(event.values, 0, mLastMagnetometer, 0, event.values.length);
mLastMagnetometerSet = true;
}
if (mLastAccelerometerSet && mLastMagnetometerSet) {
SensorManager.getRotationMatrix(mR, null, mLastAccelerometer, mLastMagnetometer);
SensorManager.getOrientation(mR, mOrientation);
Log.i("OrientationTestActivity", String.format("Orientation: %f, %f, %f",
mOrientation[0], mOrientation[1], mOrientation[2]));
}
}
}
With this you get mOrientation which contains azimuth, pitch and roll.
As explained here https://math.stackexchange.com/questions/296799/azimuth-vs-yaw yaw is the change in azimuth.
I created a view from ImageView. this imageview is a ball on screen(Depending on the acceleration sensor). now how can i do getting current positon it on the screen ?because I don't want the ball be out of the screen. like :
All my code :
public class MainActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor accelerometer;
AnimatedView animatedView = null;
ShapeDrawable mDrawable = new ShapeDrawable();
public static int x;
public static int y;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accelerometer = sensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
animatedView = new AnimatedView(this);
setContentView(animatedView);
}
#Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, accelerometer,
SensorManager.SENSOR_DELAY_GAME);
}
#Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}
#Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
#Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
x -= ((int) event.values[0])*4;
y += ((int) event.values[1])*4;
}
}
public class AnimatedView extends ImageView {
static final int width = 100;
static final int height = 100;
public AnimatedView(Context context) {
super(context);
// TODO Auto-generated constructor stub
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xffffAC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
#Override
protected void onDraw(Canvas canvas) {
mDrawable.setBounds(x, y, x + width, y + height);
mDrawable.draw(canvas);
invalidate();
}
}
}
other question,
Which one is better to use?TYPE_ACCELEROMETER or TYPE_GRAVITY?
I want to support more devices.
You already know the position, which is your x and y.
What you need to do is to stop these values from falling of the screen.
First, get the size of your screen:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
now size holds the screenSize.
Then add the following to your method
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
x -= ((int) event.values[0])*4;
y += ((int) event.values[1])*4;
if(x < 0)
x = 0;
else if(x > size.x)
x = size.x
if(y < 0)
y = 0;
else if (y > size.y)
y = size.y
}
I would also recomend that you use a thread to handle the x and y position. You can control how often a thread will run, the sensor will trigger every time an event happens.
For a better understanding of the Android Sensors, read this: http://www.codeproject.com/Articles/729759/Android-Sensor-Fusion-Tutorial
Some sensors are hardware sensors, others are software sensors that combine hardware sensors.
In your case: Im not sure what the purpose of your app is, but I guess that you want to move the ball by tilting the screen. I would say that GRAVITY works perfectly for that.
You can retrieve the location of a view by calling the getTop() and getLeft() functions which return x and y coordinates respectively, reference: http://developer.android.com/reference/android/view/View.html#Position
Choosing the type depends on the goal offcourse, using Acceleromtere means measuring the acceleration of the device whilst gravity measures the gravitational force on the device.
Please note that acceleromter values include gravity values as mentioned here: http://developer.android.com/guide/topics/sensors/sensors_motion.html
I personally would suggest using Accelerometer or the Gyroscope. The gyroscope doesn't use acceleration but the actual orientation of the device in space, which might be what you are actually looking for.
SO im trying to make a App that tracks which direction the phone is pointed VIA the compass and once a button is hit on the screen it displays the number of where it is pointed in degrees. So far i understand how the compass is created but can not find which values are the pointed direction in relation to North. Here is what i have so far.
public class compass extends Activity implements OnClickListener, SensorEventListener{
private final SensorManager DirPoint;
float var;
TextView theNumber;
Button DirectionIn;
public compass(){
DirPoint = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
public void onSensorChanged(SensorEvent event) {}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
theNumber = (textView) findViewById(R.id.output);
DirectionIn =(Button) findViewById(R.Id.Buton);
DirectionIn.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
//gets direction of phone compass
// ((TextView)findViewById(R.id.output)).setText(var);
}
}
}
}
Any help would be welcomed or if im headed in the right direction even would be nice.
You have to implement a "compass". You can do this like this:
Let your activity implement the SensorEventListener and add the necessary fields:
public class CompassActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor accelerometer;
private Sensor magnetometer;
private float[] lastAccelerometer = new float[3];
private float[] lastMagnetometer = new float[3];
private boolean lastAccelerometerSet = false;
private boolean lastMagnetometerSet = false;
private float[] rotationMatrix = new float[9];
private float[] orientation = new float[3];
private float currentDegree = 0f;
In the onCreate method of the activity get and start the two sensors, the accelerometer and the magnetometer:
// onCreate method stub ...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
sensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI);
// more onCreate method stub ....
In the method of the SensorEventListener you can now calculate the heading of the phone and calculate the bearing between the current location and a other location:
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor == this.accelerometer) {
System.arraycopy(event.values, 0, this.lastAccelerometer, 0, event.values.length);
this.lastAccelerometerSet = true;
} else if (event.sensor == this.magnetometer) {
System.arraycopy(event.values, 0, this.lastMagnetometer, 0, event.values.length);
this.lastMagnetometerSet = true;
}
if (this.lastAccelerometerSet && this.lastAccelerometerSet) {
SensorManager.getRotationMatrix(this.rotationMatrix,null, this.lastAccelerometer, this.lastMagnetometer);
SensorManager.getOrientation(this.rotationMatrix, this.orientation);
float azimuthInRadiands = this.orientation[0];
// this is now the heading of the phone. If you want
// to rotate a view to north don´t forget that you have
// to rotate by the negative value.
float azimuthInDegrees = (float) Math.toDegrees(azimuthInRadiands);
}
}
But don´t forget that there is much more behind a compass. You have to show the user if the magnetic field sensor is uncalibrated. You have to calculate the difference between the magnetic and the geographic north...
I have created a small compass helper class. The HowTo is in the readme. It will provide you all the information you need to present a compass on the screen:
Compass Assistant on GitHub
It provides you the current heading of the device. Please don´t hesitate to ask me if you have problems.
For Details look here
Use a compination of TYPE_ACCELEROMETER and TYPE_MAGNETIC_FIELD.
Sensor beschleunigung = sensor.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
Sensor magnetometer = sensor.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(0);
sensor.registerListener(handler, beschleunigung, SensorManager.SENSOR_DELAY_NORMAL, null);
sensor.registerListener(handler, magnetometer, SensorManager.SENSOR_DELAY_NORMAL, null);
and
handler = new SensorEventListener() {
float[] mGravity;
float[] mGeomagnetic;
#Override
public void onSensorChanged( SensorEvent event ) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = event.values;
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mGeomagnetic = event.values;
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);
azimut = orientation[0]; // orientation contains: azimut, pitch and roll
}
}
NavigationArrow.this.setOffsetFromNorth((float) Math.toDegrees(azimut));
}
#Override
public void onAccuracyChanged( Sensor sensor, int accuracy ) {
}
};
Hope that helps :)
I am facing a challenge in my new application, i want to find the android phone screen facing ceiling or floor.Can i able to make this with the android sensors.If any body tried this please share your valuable ideas and tutorial if available.
In oncreate
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
List<Sensor> sensorList = sensorManager
.getSensorList(Sensor.TYPE_ACCELEROMETER);
if (sensorList.size() > 0) {
accelerometerPresent = true;
accelerometerSensor = sensorList.get(0);
}
register the SensorEventListener
sensorManager.registerListener(accelerometerListener,
accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
and the SensorEventListener implementation
private SensorEventListener accelerometerListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public void onSensorChanged(SensorEvent event) {
float z_value = event.values[2];
if (z_value == 10) {
// up facing
} else if(z_value == -10)
// down facing
}
}
};
In my two device it shows z_values = 10 when it is upfacing and horizontal and in the opposite case it is showing - 10. check your cases.
You can use the SensorEventListener to achieve this. Have a look at my below code for a better understanding. You can also look at this in the developer docs for more about sensors.
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public class SensorClass implements SensorEventListener {
private float mInR[];
private float mI[];
private float mGravity[];
private float mGeomag[];
private float mOrientVals[];
final float rad2deg = 180/((float) Math.PI);
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here when accuracy changed
}
#Override
public void onSensorChanged(SensorEvent event) {
float azimuth = 0f;
float pitch = 0f;
float roll = 0f;
// If the sensor data is unreliable return
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE)
return;
// Gets the value of the sensor that has been changed
switch (event.sensor.getType()){
case Sensor.TYPE_ACCELEROMETER:
mGravity = event.values.clone();
break;
case Sensor.TYPE_MAGNETIC_FIELD:
mGeomag = event.values.clone();
break;
}
if (mGravity != null && mGeomag != null){
// checks that the rotation matrix is found
if (SensorManager.getRotationMatrix(mInR, mI, mGravity, mGeomag)){
// magnetic north and the horizon respectively
SensorManager.getOrientation(mInR, mOrientVals);
azimuth = mOrientVals[0]*rad2deg;
pitch = mOrientVals[1]*rad2deg;
roll = mOrientVals[2]*rad2deg;
}
}
}
}
In my app i wanted to show device direction such as north, south, east, west. For that i am using accelerometer and magnetic sensor and tried with following code.
public class MainActivity extends Activity implements SensorEventListener
{
public static float swRoll;
public static float swPitch;
public static float swAzimuth;
public static SensorManager mSensorManager;
public static Sensor accelerometer;
public static Sensor magnetometer;
public static float[] mAccelerometer = null;
public static float[] mGeomagnetic = null;
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
public void onSensorChanged(SensorEvent event)
{
// onSensorChanged gets called for each sensor so we have to remember the values
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
{
mAccelerometer = event.values;
}
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
{
mGeomagnetic = event.values;
}
if (mAccelerometer != null && mGeomagnetic != null)
{
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mAccelerometer, mGeomagnetic);
if (success)
{
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
// at this point, orientation contains the azimuth(direction), pitch and roll values.
double azimuth = 180 * orientation[0] / Math.PI;
//double pitch = 180 * orientation[1] / Math.PI;
//double roll = 180 * orientation[2] / Math.PI;
Toast.makeText(getApplicationContext(), "azimuth: "+azimuth, Toast.LENGTH_SHORT).show();
//Toast.makeText(getApplicationContext(), "pitch: "+pitch, Toast.LENGTH_SHORT).show();
//Toast.makeText(getApplicationContext(), "roll: "+roll, Toast.LENGTH_SHORT).show();
}
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
#Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_GAME);
}
#Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this, accelerometer);
mSensorManager.unregisterListener(this, magnetometer);
}
}
I read some artical and come to know that azimuth value is use for getting direction. But it not shows proper value i.e it always shows value between 103 to 140 in any direction. I am using samsung galaxy s for test purpose. where i goes wrong.
Any help will be appreciated...thank you
EDIT: I'm going to keep this answer up because there are no other answers here, but my gut feeling looking at this code again is it probably doesn't work well. Use at your own risk, and if anyone ever gives a decent answer I'll delete this
Here's a compass sensor I wrote for my own use. It works, somewhat. In reality it needs better filtering- the results from the sensor are very noisy, I built a filter on top of it to slow the updates and it improved things, but it needs a much better filter if it was to be used in production
package com.gabesechan.android.reusable.sensor;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Message;
public class CompassSensor {
SensorManager sm;
int lastDirection = -1;
int lastPitch;
int lastRoll;
boolean firstReading = true;
HashSet<CompassListener> listeners = new HashSet<CompassListener>();
static CompassSensor mInstance;
public static CompassSensor getInstance(Context ctx){
if(mInstance == null){
mInstance = new CompassSensor(ctx);
}
return mInstance;
}
private CompassSensor(Context ctx){
sm = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);
onResume();
}
public void onResume(){
sm.registerListener(sensorListener, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
sm.registerListener(sensorListener, sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI);
firstReading = true;
//Restart the timer only if we have listeners
if(listeners.size()>0){
handler.sendMessageDelayed(Message.obtain(handler, 1),1000);
}
}
public void onPause(){
sm.unregisterListener(sensorListener);
handler.removeMessages(1);
}
private final SensorEventListener sensorListener = new SensorEventListener(){
float accelerometerValues[] = null;
float geomagneticMatrix[] = null;
public void onSensorChanged(SensorEvent event) {
if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE)
return;
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
accelerometerValues = event.values.clone();
break;
case Sensor.TYPE_MAGNETIC_FIELD:
geomagneticMatrix = event.values.clone();
break;
}
if (geomagneticMatrix != null && accelerometerValues != null && event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
float[] R = new float[16];
float[] I = new float[16];
float[] outR = new float[16];
//Get the rotation matrix, then remap it from camera surface to world coordinates
SensorManager.getRotationMatrix(R, I, accelerometerValues, geomagneticMatrix);
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);
float values[] = new float[4];
SensorManager.getOrientation(outR,values);
int direction = normalizeDegrees(filterChange((int)Math.toDegrees(values[0])));
int pitch = normalizeDegrees(Math.toDegrees(values[1]));
int roll = normalizeDegrees(Math.toDegrees(values[2]));
if((int)direction != (int)lastDirection){
lastDirection = (int)direction;
lastPitch = (int)pitch;
lastRoll = (int)roll;
}
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
//Normalize a degree from 0 to 360 instead of -180 to 180
private int normalizeDegrees(double rads){
return (int)((rads+360)%360);
}
//We want to ignore large bumps in individual readings. So we're going to cap the number of degrees we can change per report
private static final int MAX_CHANGE = 3;
private int filterChange(int newDir){
newDir = normalizeDegrees(newDir);
//On the first reading, assume it's right. Otherwise NW readings take forever to ramp up
if(firstReading){
firstReading = false;
return newDir;
}
//Figure out how many degrees to move
int delta = newDir - lastDirection;
int normalizedDelta = normalizeDegrees(delta);
int change = Math.min(Math.abs(delta),MAX_CHANGE);
//We always want to move in the direction of lower distance. So if newDir is lower and delta is less than half a circle, lower lastDir
// Same if newDir is higher but the delta is more than half a circle (you'd be faster in the other direction going lower).
if( normalizedDelta > 180 ){
change = -change;
}
return lastDirection+change;
}
public void addListener(CompassListener listener){
if(listeners.size() == 0){
//Start the timer on first listener
handler.sendMessageDelayed(Message.obtain(handler, 1),1000);
}
listeners.add(listener);
}
public void removeListener(CompassListener listener){
listeners.remove(listener);
if(listeners.size() == 0){
handler.removeMessages(1);
}
}
public int getLastDirection(){
return lastDirection;
}
public int getLastPitch(){
return lastPitch;
}
public int getLastRoll(){
return lastPitch;
}
private void callListeners(){
for(CompassListener listener: listeners){
listener.onDirectionChanged(lastDirection, lastPitch, lastRoll);
}
}
//This handler is run every 1s, and updates the listeners
//Static class because otherwise we leak, Eclipse told me so
static class IncomingHandler extends Handler {
private final WeakReference<CompassSensor> compassSensor;
IncomingHandler(CompassSensor sensor) {
compassSensor = new WeakReference<CompassSensor>(sensor);
}
#Override
public void handleMessage(Message msg)
{
CompassSensor sensor = compassSensor.get();
if (sensor != null) {
sensor.callListeners();
}
sendMessageDelayed(Message.obtain(this, 1), 1000);
}
}
IncomingHandler handler = new IncomingHandler(this);
public interface CompassListener {
void onDirectionChanged(int direction, int pitch, int roll);
}
}