0

I am trying to get the users magnetic heading using the SensorEventListener. Sadly on Android this seams to be a bit of a hassle.

On my HTC, my readings seam to be fairly accurate. On my Samsung Galaxy S3, the readings are totally random. I know there must be something wrong with my class though since the compass apps from the Play store seam to work just fine.

My code is:

public class HeadingSensor implements SensorEventListener {
private static final String LOG_TAG = "sw_HeadingSensor";

private SensorManager mSensorManager;

private long mLastHeadingUpdate;
private int mMinUpdateFrequency = 500;//milliseconds
private HeadingListener mCallback;

private float[] mGravity;
private float[] mGeomagnetic;

public interface HeadingListener {
    public void headingChanged(int heading);
}

public HeadingSensor(Context context, HeadingListener headingListener) {
    mCallback = headingListener;
    mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
}

public void registerListener() {
    Log.d(LOG_TAG, "listener registered");

    mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI);
    mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);
}

public void unregisterListener() {
    Log.d(LOG_TAG, "listener unregistered");

    mSensorManager.unregisterListener(this);
}

@Override
public void onSensorChanged(SensorEvent sensorEvent) {
    if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        mGravity = sensorEvent.values;
    }

    if (sensorEvent.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
        mGeomagnetic = sensorEvent.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) {
            long currentTime = System.currentTimeMillis();

            if ((currentTime - mLastHeadingUpdate) > mMinUpdateFrequency) {
                mLastHeadingUpdate = currentTime;

                float orientation[] = new float[3];
                SensorManager.getOrientation(R, orientation);
                float azimuthInRadians = orientation[0];
                int azimuthInDegress = (int)(Math.toDegrees(azimuthInRadians) + 360) % 360;

                if (null != mCallback) {
                    mCallback.headingChanged(azimuthInDegress);
                }
            }
        }
    }
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) { }
}

I have read as many tutorials, references, etc as possible but I can't find a good way to get the compass heading. Can someone show me what is wrong with this class?

shiznatix
  • 1,087
  • 1
  • 20
  • 37
  • Have you taken your S3 outside? The compass on my Moto G is extremely susceptible to interference. – Kevin Krumwiede Dec 19 '14 at 09:15
  • Yes, and at the same time if I leave the phone in the same position at my desk then the compass app reports everything correctly while my code gives very random readings. – shiznatix Dec 19 '14 at 09:22

1 Answers1

0

You should use TYPE_GRAVITY instead of TYPE_ACCELEROMETER else you should low pass filter TYPE_ACCELEROMETER. Your problem lies mainly in

mGravity = sensorEvent.values;

and

mGeomagnetic = sensorEvent.values;

Since sensorEvent is a local variable and either mGravity or mGeomagnetic points to this value, either of these can be anything by the next time onSensorChanged is called. It should be

mGravity = sensorEvent.values.clone();

and

mGeomagnetic = sensorEvent.values.clone();

You can look at my answer at Android getOrientation Azimuth gets polluted when phone is tilted

Community
  • 1
  • 1
Hoan Nguyen
  • 18,033
  • 3
  • 50
  • 54