Android device orientation without geomagnetic

2019-02-08 14:09发布

I need to get device orientation. As I know usually used TYPE_ACCELEROMETER and TYPE_MAGNETIC_FIELD sensors. My problem is that SensorManager.getDefaultSensor returns me null for geomagnetic sensor. It returns null for TYPE_ORIENTATION sensor too.

manager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensorAcc = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), //normal object
        sensorMagn = manager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); //null
orientationListener = new OrientationSensorListener();
manager.registerListener(orientationListener, sensorAcc, 10);
manager.registerListener(orientationListener, sensorMagn, 10);

I need another ways to get device orientation.

7条回答
手持菜刀,她持情操
2楼-- · 2019-02-08 14:42

Orientation can be decomposed in three Euler angle : Pitch, Roll and Azimuth.

With only accelerometer datas, you cannot compute your Azimuth, neither the sign of your pitch.

You can try something as this to know something about your pitch and roll :

    private final float[] mMagnet = new float[3];               // magnetic field vector
    private final float[] mAcceleration = new float[3];         // accelerometer vector
    private final float[] mAccMagOrientation = new float[3];    // orientation angles from mAcceleration and mMagnet
    private float[] mRotationMatrix = new float[9];             // accelerometer and magnetometer based rotation matrix

    public void onSensorChanged(SensorEvent event) {
    switch (event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            System.arraycopy(event.values, 0, mAcceleration, 0, 3);   // save datas
            calculateAccMagOrientation();                       // then calculate new orientation
            break;
        case Sensor.TYPE_MAGNETIC_FIELD:
            System.arraycopy(event.values, 0, mMagnet, 0, 3);         // save datas
            break;
        default: break;
    }
}
public void calculateAccMagOrientation() {
    if (SensorManager.getRotationMatrix(mRotationMatrix, null, mAcceleration, mMagnet))
        SensorManager.getOrientation(mRotationMatrix, mAccMagOrientation);
    else { // Most chances are that there are no magnet datas
        double gx, gy, gz;
        gx = mAcceleration[0] / 9.81f;
        gy = mAcceleration[1] / 9.81f;
        gz = mAcceleration[2] / 9.81f;
        // http://theccontinuum.com/2012/09/24/arduino-imu-pitch-roll-from-accelerometer/
        float pitch = (float) -Math.atan(gy / Math.sqrt(gx * gx + gz * gz));
        float roll = (float) -Math.atan(gx / Math.sqrt(gy * gy + gz * gz));
        float azimuth = 0; // Impossible to guess

        mAccMagOrientation[0] = azimuth;
        mAccMagOrientation[1] = pitch;
        mAccMagOrientation[2] = roll;
        mRotationMatrix = getRotationMatrixFromOrientation(mAccMagOrientation);
    }
}
public static float[] getRotationMatrixFromOrientation(float[] o) {
    float[] xM = new float[9];
    float[] yM = new float[9];
    float[] zM = new float[9];

    float sinX = (float) Math.sin(o[1]);
    float cosX = (float) Math.cos(o[1]);
    float sinY = (float) Math.sin(o[2]);
    float cosY = (float) Math.cos(o[2]);
    float sinZ = (float) Math.sin(o[0]);
    float cosZ = (float) Math.cos(o[0]);

    // rotation about x-axis (pitch)
    xM[0] = 1.0f;xM[1] = 0.0f;xM[2] = 0.0f;
    xM[3] = 0.0f;xM[4] = cosX;xM[5] = sinX;
    xM[6] = 0.0f;xM[7] =-sinX;xM[8] = cosX;

    // rotation about y-axis (roll)
    yM[0] = cosY;yM[1] = 0.0f;yM[2] = sinY;
    yM[3] = 0.0f;yM[4] = 1.0f;yM[5] = 0.0f;
    yM[6] =-sinY;yM[7] = 0.0f;yM[8] = cosY;

    // rotation about z-axis (azimuth)
    zM[0] = cosZ;zM[1] = sinZ;zM[2] = 0.0f;
    zM[3] =-sinZ;zM[4] = cosZ;zM[5] = 0.0f;
    zM[6] = 0.0f;zM[7] = 0.0f;zM[8] = 1.0f;

    // rotation order is y, x, z (roll, pitch, azimuth)
    float[] resultMatrix = matrixMultiplication(xM, yM);
    resultMatrix = matrixMultiplication(zM, resultMatrix);
    return resultMatrix;
}
public static float[] matrixMultiplication(float[] A, float[] B) {
    float[] result = new float[9];

    result[0] = A[0] * B[0] + A[1] * B[3] + A[2] * B[6];
    result[1] = A[0] * B[1] + A[1] * B[4] + A[2] * B[7];
    result[2] = A[0] * B[2] + A[1] * B[5] + A[2] * B[8];

    result[3] = A[3] * B[0] + A[4] * B[3] + A[5] * B[6];
    result[4] = A[3] * B[1] + A[4] * B[4] + A[5] * B[7];
    result[5] = A[3] * B[2] + A[4] * B[5] + A[5] * B[8];

    result[6] = A[6] * B[0] + A[7] * B[3] + A[8] * B[6];
    result[7] = A[6] * B[1] + A[7] * B[4] + A[8] * B[7];
    result[8] = A[6] * B[2] + A[7] * B[5] + A[8] * B[8];

    return result;
}
查看更多
等我变得足够好
3楼-- · 2019-02-08 14:50

You have to add some permissions to the manifest. The docs states:

the default sensor matching the requested type and wakeUp properties if one exists and the application has the necessary permissions, or null otherwise

I know it sounds counter intuitive, but apparently the permissions you need are:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

(or a sub-set of those).

See here: manifest.xml when using sensors

查看更多
戒情不戒烟
4楼-- · 2019-02-08 14:52

i did something like :

public class MainActivity extends AppCompatActivity
{
    SensorManager sensorManager;
    Sensor sensor;
    ImageView imageViewProtractorPointer;

    /////////////////////////////////////
    ///////////// onResume //////////////
    /////////////////////////////////////
    @Override
    protected void onResume()
    {
        super.onResume();
        // register sensor listener again if return to application
        if(sensor !=null) sensorManager.registerListener(sensorListener,sensor,SensorManager.SENSOR_DELAY_NORMAL);
    }
    /////////////////////////////////////
    ///////////// onCreate //////////////
    /////////////////////////////////////
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageViewProtractorPointer = (ImageView)findViewById(R.id.imageView2);
        // get the SensorManager
        sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        // get the Sensor ACCELEROMETER
        sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }
    /////////////////////////////////////
    ///////////// onPause ///////////////
    /////////////////////////////////////
    @Override
    protected void onPause()
    {
        // Unregister the sensor listener to prevent battery drain if not in use
        super.onPause();
        if(sensor !=null) sensorManager.unregisterListener(sensorListener);
    }

    /////////////////////////////////////////////
    /////////// SensorEventListener /////////////
    /////////////////////////////////////////////
    SensorEventListener sensorListener = new SensorEventListener()
    {
        @Override
        public void onSensorChanged(SensorEvent sensorEvent)
        {
            // i will use values from 0 to 9 without decimal
            int x = (int)sensorEvent.values[0];
            int y = (int)sensorEvent.values[1];
            int angle = 0;

            if(y>=0 && x<=0) angle = x*10;
            if(x<=0 && y<=0) angle = (y*10)-90;
            if(x>=0 && y<=0) angle = (-x*10)-180;
            if(x>=0 && y>=0) angle = (-y*10)-270;

            imageViewProtractorPointer.setRotation((float)angle);
        }
        @Override
        public void onAccuracyChanged(Sensor sensor, int i){}
    };
}

if you want to understand about my if statements see this image image

for my use i lock the screen in a portrait mode , and a use 2 images to show the angle on screen , this is my screenshot screenshot

i'm still have to make it little better , just not enough time for it.

i hope this help , if you need a full code let me know.

查看更多
淡お忘
6楼-- · 2019-02-08 15:02

You can try the GEOMAGNETIC_ROTATION_VECTOR like this:

private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)

And compute the sensor info with this:

...
// Rotation matrix based on current readings from accelerometer and magnetometer.
final float[] rotationMatrix = new float[9];
mSensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading);

// Express the updated rotation matrix as three orientation angles.
final float[] orientationAngles = new float[3];
mSensorManager.getOrientation(rotationMatrix, orientationAngles);

Extracted from android docs: https://developer.android.com/guide/topics/sensors/sensors_position.html

add the proper permissions in the manifest.

Hope this helps.

查看更多
再贱就再见
7楼-- · 2019-02-08 15:04

For anyone who is still confused by this problem, say: you want to get the orientation of your phone (the azimuth, pitch and roll), but sometimes the magnetic field is unstable, so that the orientation you get is also unstable. The answers above may help you get the pitch and roll angle, but you still cannot get the azimuth angle. They said it is impossible. Then you become desperate. So, what should you do to solve this problem?

If you only care about the orientation and you don't care about where north is, here is my suggestion, try this sensor, it works awesome in my case:

TYPE_GAME_ROTATION_VECTOR
查看更多
登录 后发表回答