com.kircherelectronics.fusedgyroscopeexplorer.sensor.GravitySensor.java Source code

Java tutorial

Introduction

Here is the source code for com.kircherelectronics.fusedgyroscopeexplorer.sensor.GravitySensor.java

Source

package com.kircherelectronics.fusedgyroscopeexplorer.sensor;

import java.util.ArrayList;
import java.util.Iterator;

import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

import com.kircherelectronics.fusedgyroscopeexplorer.sensor.observer.GravitySensorObserver;

/*
 * Fused Gyroscope Explorer
 * Copyright (C) 2013, Kaleb Kircher - Kircher Engineering, LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Gravity Sensor is a subject in an Observer Pattern for classes that need to
 * be provided with acceleration measurements. Acceleration Sensor implements
 * Sensor.TYPE_GRAVITY and provides methods for managing SensorEvents and
 * rotations. Unlike Sensor.TYPE_ACCELERATION, Sensor.TYPE_GRAVITY uses a sensor
 * fusion to help sequester the gravity and linear acceleration components of
 * the signal, providing only the gravity measurement. This is advantageous in
 * many implementations. For example, Sensor.TYPE_GRAVITY provides more accurate
 * tilt compensations than Sensor.TYPE_ACCELERATION, especially while the device
 * is experiencing linear acceleration.
 * 
 * Note that Sensor.TYPE_GRAVITY is not implemented on all Android devices.
 * Appropriate steps to fall back onto Sensor.TYPE_ACCELERATION should be taken.
 * To help facilitate this process, GravityAccelerationSensor and
 * AccelerationSensor both register the type AccelerationSensorObserver, so
 * observers can be ambiguous to the configuration.
 * 
 * @author Kaleb
 * @version %I%, %G%
 */
public class GravitySensor implements SensorEventListener {
    /*
     * Developer Note: Quaternions are used for the internal representations of
     * the rotations which prevents the polar anomalies associated with Gimbal
     * lock when using Euler angles for the rotations.
     */

    private static final String tag = GravitySensor.class.getSimpleName();

    // Keep track of observers.
    private ArrayList<GravitySensorObserver> observersAcceleration;

    // Keep track of the application mode. Vehicle Mode occurs when the device
    // is in the Landscape orientation and the sensors are rotated to face the
    // -Z-Axis (along the axis of the camera).
    private boolean vehicleMode = false;

    // We need the Context to register for Sensor Events.
    private Context context;

    // Keep a local copy of the acceleration values that are copied from the
    // sensor event.
    private float[] gravity = new float[3];

    // The time stamp of the most recent Sensor Event.
    private long timeStamp = 0;

    // Quaternion data structures to rotate a matrix from the absolute Android
    // orientation to the orientation that the device is actually in. This is
    // needed because the the device sensors orientation is fixed in hardware.
    // Also remember the many algorithms require a NED orientation which is not
    // the same as the absolute Android orientation. Do not confuse this
    // rotation with a rotation into absolute earth frame!
    private Rotation yQuaternion;
    private Rotation xQuaternion;
    private Rotation rotationQuaternion;

    // We need the SensorManager to register for Sensor Events.
    private SensorManager sensorManager;

    // The vectors that will be rotated when the application is in Vehicle Mode.
    private Vector3D vIn;
    private Vector3D vOut;

    /**
     * Initialize the state.
     * 
     * @param context
     *            the Activities context.
     */
    public GravitySensor(Context context) {
        super();

        this.context = context;

        initQuaternionRotations();

        observersAcceleration = new ArrayList<GravitySensorObserver>();

        sensorManager = (SensorManager) this.context.getSystemService(Context.SENSOR_SERVICE);

    }

    /**
     * Register for Sensor.TYPE_GRAVITY measurements.
     * 
     * @param observer
     *            The observer to be registered.
     */
    public void registerGravityObserver(GravitySensorObserver observer) {
        // If there are currently no observers, but one has just requested to be
        // registered, register to listen for sensor events from the device.
        if (observersAcceleration.size() == 0) {
            sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY),
                    SensorManager.SENSOR_DELAY_FASTEST);
        }

        // Only register the observer if it is not already registered.
        int i = observersAcceleration.indexOf(observer);
        if (i == -1) {
            observersAcceleration.add(observer);
        }
    }

    /**
     * Remove Sensor.TYPE_GRAVITY measurements.
     * 
     * @param observer
     *            The observer to be removed.
     */
    public void removeGravityObserver(GravitySensorObserver observer) {
        int i = observersAcceleration.indexOf(observer);
        if (i >= 0) {
            observersAcceleration.remove(i);
        }

        // If there are no observers, then don't listen for Sensor Events.
        if (observersAcceleration.size() == 0) {
            sensorManager.unregisterListener(this);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do nothing.
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
            System.arraycopy(event.values, 0, gravity, 0, event.values.length);

            timeStamp = event.timestamp;

            if (vehicleMode) {
                gravity = quaternionToDeviceVehicleMode(gravity);
            }

            notifyGravityObserver();
        }
    }

    /**
     * Vehicle mode occurs when the device is put into the landscape
     * orientation. On Android phones, the positive Y-Axis of the sensors faces
     * towards the top of the device. In vehicle mode, we want the sensors to
     * face the negative Z-Axis so it is aligned with the camera of the device.
     * 
     * @param vehicleMode
     *            true if in vehicle mode.
     */
    public void setVehicleMode(boolean vehicleMode) {
        this.vehicleMode = vehicleMode;
    }

    /**
     * To avoid anomalies at the poles with Euler angles and Gimbal lock,
     * quaternions are used instead.
     */
    private void initQuaternionRotations() {
        // Rotate by 90 degrees or pi/2 radians.
        double rotation = Math.PI / 2;

        // Create the rotation around the x-axis
        Vector3D xV = new Vector3D(1, 0, 0);
        xQuaternion = new Rotation(xV, rotation);

        // Create the rotation around the y-axis
        Vector3D yV = new Vector3D(0, 1, 0);
        yQuaternion = new Rotation(yV, -rotation);

        // Create the composite rotation.
        rotationQuaternion = yQuaternion.applyTo(xQuaternion);
    }

    /**
     * Notify observers with new measurements.
     */
    private void notifyGravityObserver() {
        // The iterator is a work around for a concurrency exception...
        GravitySensorObserver observer;

        ArrayList<GravitySensorObserver> notificationList = new ArrayList<GravitySensorObserver>(
                observersAcceleration);

        for (Iterator<GravitySensorObserver> iterator = notificationList.iterator(); iterator.hasNext();) {
            observer = iterator.next();

            observer.onGravitySensorChanged(this.gravity, this.timeStamp);
        }
    }

    /**
     * Orient the measurements from the absolute Android device rotation into
     * the current device orientation. Note that the rotation is different based
     * on the current rotation of the device relative to the absolute Android
     * rotation. Do not confuse this with a rotation into absolute earth frame,
     * or the NED orientation that the algorithm assumes.
     * 
     * @param measurements
     *            the measurements referenced to the absolute Android
     *            orientation.
     * @return the measurements referenced to the current device rotation.
     * 
     * @see http 
     *      ://developer.android.com/reference/android/hardware/SensorEvent.html
     *      #values
     */
    private float[] quaternionToDeviceVehicleMode(float[] matrix) {

        vIn = new Vector3D(matrix[0], matrix[1], matrix[2]);
        vOut = rotationQuaternion.applyTo(vIn);

        float[] rotation = { (float) vOut.getX(), (float) vOut.getY(), (float) vOut.getZ() };

        return rotation;
    }
}